本文來自尚妝Android團隊路飛
發表於尚妝github博客,歡迎訂閱!html
目前weex已在尚妝旗下的達人店app上線了一個經常使用的訂單管理頁面,截止目前Android上未發現問題,渲染時間在100-300ms之間。前端
做爲Android開發,此文首先會從Android的角度爲主來記錄接入的過程,但願給未接入的同窗更方便省時地接入weex提供一點幫助。其中會涉及到預加載
,降級
,熱更新
,埋點
以及在app不更新的狀況下動態配置新頁面
等問題,這些Android和iOS都是統一的邏輯,但願和你們一塊兒交流。前端方面能夠參考我同事寫的《weex入門實踐(前端視角)》,iOS能夠參考我同事寫的weex 實踐(iOS 視角)vue
其實對於module、component的定義,以及IWXImgLoaderAdapter、IWXHttpAdapter等adapter的重寫,在playgroud和weexteam裏都已經有很好的例子了。java
compile 'com.taobao.android:weex_sdk:0.10.0’ compile 'com.android.support:support-v4:24.0.0' compile 'com.android.support:appcompat-v7:24.0.0' compile 'com.android.support:recyclerview-v7:24.0.0' compile 'com.squareup.okhttp:okhttp:2.3.0' compile 'com.squareup.okhttp:okhttp-ws:2.3.0' compile 'com.alibaba:fastjson:1.2.8' //(可選)支持調試的依賴,參考https://github.com/weexteam/weex-devtools-android/blob/master/README-zh.md compile 'com.taobao.android:weex_inspector:0.0.8.5' compile 'com.google.code.findbugs:jsr305:2.0.1' compile 'com.taobao.android:weex_inspector:0.0.8.5'
在原來的project上,新建單獨的 weex module。代碼結構以下:android
經過類WeexManager來統一管理weex相關的配置,如下是WeexManager裏的init函數的主要內容,在application的onCreate裏調用:git
public void init(Application application, IWeexService weexService) { //經過在線參數控制是否使用weex,ConfigManager是尚妝的在線參數模塊,之後有機會再簡單介紹一下 if (!ConfigManager.getBoolean(CONFIG_WEEX_ENABLE, true)) { return; } context = application.getApplicationContext(); weexDir = context.getDir(WEEX_MODULE, Context.MODE_PRIVATE); //根據須要註冊圖片、網絡、存儲等adapter WXSDKEngine.initialize(application, new InitConfig.Builder() .setImgAdapter(new FrescoImageAdapter()) .setUtAdapter(new UserTrackAdapter()) .setStorageAdapter(new StorageAdapter()) .setHttpAdapter(new OkHttpAdapter()) .setURIAdapter(new CustomURIAdapter()) .build()); this.weexService = weexService; //獲取本地緩存的weex js配置 configList = WXJsonUtils.getList(SHStorageManager.get(WEEX_MODULE, WEEX_CONFIG, ""), WeexConfig.class); update(); try { //頁面通用的一些接口 WXSDKEngine.registerModule("shopBase", ShopModule.class); //主要是a標籤的跳轉 WXSDKEngine.registerModule("event", WXEventModule.class); //模態對話框 WXSDKEngine.registerModule("shopModal", ModalModule.class); //用fresco重寫圖片組件 WXSDKEngine.registerComponent("image", FrescoImageComponent.class); } catch (WXException e) { LogUtils.e(e); } SHEventBus.register(ModuleName.WEEX, "weexDebugHost", new ISHEventBusCallback<String>() { @Override public void handle(String debugHost, String s) { if (!TextUtils.isEmpty(s)) { LogUtils.e(s); return; } if (TextUtils.isEmpty(debugHost)) { WXEnvironment.sRemoteDebugMode = false; } else { WXEnvironment.sRemoteDebugMode = true; WXEnvironment.sRemoteDebugProxyUrl = "ws://" + debugHost + "/debugProxy/native"; } WXSDKEngine.reload(); } }); SHEventBus.register(ModuleName.WEEX, "netChanged", new ISHEventBusCallback<Boolean>() { @Override public void handle(Boolean result, String s) { if (!TextUtils.isEmpty(s)) { LogUtils.e(s); } else { if (result.booleanValue()) { update(); } } } }); //獲取weex配置,更新js文件 weexConfigRequest.setCallBack(new IRequestCallBack<SHResponse<List<WeexConfig>>>() { @Override public void onResponseSuccess(SHResponse<List<WeexConfig>> response) { if (response.isSuccess && null != response.data) { SHStorageManager.putToDisk(WEEX_MODULE, WEEX_CONFIG, JsonUtils.toJson(response.data)); configList = response.data; update(); } } @Override public void onResponseError(int i) { } }); weexConfigRequest.start(); }
1)考慮到第一次接入weex,有點擔憂兼容問題,萬一引發崩潰等不肯定因素,因此這裏作了一個開關。其實每接入一個新的sdk都最好有個控制開關
,以免由於不肯定因素致使不穩定。github
2)weexDir是js的下載存儲路徑,爲了加快頁面打開時間,會對js進行預加載到本地
web
3) sdk對a標籤的處理只調用了"event"的openURL接口,可是卻沒有註冊"event"
。因此須要本身實現WXEventModule,並註冊。json
4)模態對話框ModalModule
的實現參考sdk裏的WXModalUIModule瀏覽器
5)FrescoImageAdapter
和FrescoImageComponent
的實現依賴咱們開源的SHImageView支持webp,支持壓縮,支持沒有協議的連接(忽略協議可讓瀏覽器根據頁面時http或者https自動選擇使用的協議,從而避免了網站改成https的狀況下仍然訪問http資源而沒法訪問的問題。)
6)OkHttpAdapter的實現參考github上zjutkz同窗的實現 OkHttpAdapter,感謝,通過改寫,支持沒有協議的連接,支持cookie
7)ShopModule是自定義的Module,定義通用的一些接口,好比設置title bar是否顯示,以及title bar的title;關閉當前頁面,分享,錯誤日誌收集等。
8)UserTrackAdapter用於埋點,另外能夠在ShopModule裏自定義接口收集埋點、錯誤信息等。
9)CustomURIAdapter用於支持相對地址,具體實現參見如下:
public class CustomURIAdapter implements URIAdapter { @NonNull @Override public Uri rewrite(WXSDKInstance instance, String type, Uri uri) { if (null == uri) { return null; } String url = uri.toString(); if (url.startsWith("http")) { return uri; }else if (url.startsWith("//")) { if (SHStorageManager.get("APP", "https", true)) { url = "https:" + url; }else { url = "http:" + url; } }else { url = SHHost.getMobileHost() + url; } return Uri.parse(url); } }
這邊考慮到之後頁面有可能嵌入到其餘activity,因此把weex的渲染放入新建的WeexFragment
。而後新建WeexActivity來引用該WeexFragment 。全部的單獨頁面的weex渲染都使用這個WeexActivity
,非單獨頁面的使用weexFragment
,這樣新加頁面時,無需從新註冊activity。weex處理邏輯統一,方便管理,方便動態配置。經過統一跳轉協議跳轉到WeexActivity,經過intent傳入兩個參數url和h5
。
showjoyshop://page.sh/weex
intent參數:url
:js連接,能夠是本地的存儲地址/sdcard/com.showjoy.shop/weex/order.js,也能夠是線上連接 https://xxxxx/0.4.3/order.js
h5
:用來降級的h5頁面連接,當渲染失敗時,會跳轉到該h5頁面
首先實例化WXSDKInstance
wxInstance = new WXSDKInstance(activity); wxInstance.registerRenderListener(this); wxInstance.onActivityCreate(); registerBroadcastReceiver();
1)當前類實現接口IWXRenderListener,能夠參考weexteam裏的AbsWeexActivity實現
2)註冊的廣播是DefaultBroadcastReceiver,能夠能夠參考weexteam裏的AbsWeexActivity實現
而後講一下渲染,支持本地js以及線上js
if (url.startsWith("http")) { wxInstance.renderByUrl( getPageName(), url, options, jsonInitData, CommonUtils.getDisplayWidth(activity), CommonUtils.getDisplayHeight(activity), WXRenderStrategy.APPEND_ASYNC); }else { new Thread(new Runnable() { @Override public void run() { String file = WeexUtils.readFile(url); handler.sendMessage(handler.obtainMessage(LOAD_LOCAL_FILE, file)); } }).start(); }
其中,getPageName()自定義便可,getDisplayWidth和getDisplayHeight獲取屏幕寬高。
傳入本地的存儲地址時,先讀取文件,而後同個Handler在UI線程渲染,以下:
接收LOAD_LOCAL_FILE後handler裏的實現:
case LOAD_LOCAL_FILE: if (activity.getLifeState() != LifeState.DESTORY ) { if (wxInstance != null) { String content = (String) msg.obj; if (TextUtils.isEmpty(content)) { SHJump.openUrl(activity, h5Url); finishActivity(); }else { wxInstance.render((String) msg.obj, null, null); } } } break;
這裏getLifeState()是咱們本身BaseActivity的實現,能夠自行判斷。SHJump和finishActivity都是本身的實現,你們本身實現便可。
渲染回調的實現,按須要處理便可,渲染成功後隱藏loading,view建立後添加view。渲染異常時降級跳轉到h5
。以下:
Override public void onViewCreated(WXSDKInstance instance, View view) { //viewMap.put(weexJsUrl, view); addWeexView(view); } @Override public void onRenderSuccess(WXSDKInstance instance, int width, int height) { toHideLoading(); } @Override public void onRefreshSuccess(WXSDKInstance instance, int width, int height) { toHideLoading(); } @Override public void onException(WXSDKInstance instance, String errCode, String msg) { LogUtils.e("weex exception:", errCode, msg); SHJump.openUrlForce(activity, h5Url); finishActivity(); }
爲了實現如圖的tab,一開始在.vue文件裏使用tabbar組件,後來發如今Android機型適配上不夠好。因而後來就將兩個tab作成兩個頁面,生成兩個js文件。首先渲染「個人訂單.js」,生成以下的界面。
而後點擊「本店訂單」時,調用自定義module裏的接口loadPage
,參數爲h5的連接。三端實現接口loadPage,h5直接跳轉,而iOS和Android經過h5連接從weex跳轉配置裏找到對應的js,從新渲染顯示
。下面具體作幾點說明:
1)定義Map<String, WXSDKInstance> wxsdkInstanceMap;來存儲不一樣js的WXSDKInstance,定義Map<String, View> viewMap來存儲不一樣js渲染後的View。之因此要存儲多個WXSDKInstance,是由於WXSDKInstance不能重複渲染,並且當WXSDKInstance destory後,以前渲染的view裏的內容也會被清空。注意在在頁面destory時,記得把全部WXSDKInstance都destory就行了。
2)viewMap裏的key對應頁面的js。點擊tab切換頁面時,如對應的js已渲染,則直接取出view來顯示。
3)上文提到的weex跳轉配置
,在如下的跳轉規則裏一同介紹。
跳轉規則以下圖,若是看不清,能夠到新頁面放大查看。
主要介紹一下兩個配置參數:
在參數weexPages配置全部的weex頁面。
示例以下:
[ { "page":"order", "url":"https://dshdjshjbx.js", "md5":"323827382huwhdjshdjs", "h5":"http://dsds.html" "v":"1.5.0" }, { "page":"detail", "url":"https://dsdsds.js", "md5":"323827382huwhdjshdjs", "h5":"http://dsds.html" "v":"1.5.0" } ]
page
: 對應統一跳轉的 path
url
: 須要渲染的js,
md5
: js文件的md5值用於校驗,
h5
: 渲染失敗後的降級方案,
v
: 最低支持的版本號
在頁面訪問h5頁面時,拿url跟weexPages裏的url進行對比,若是一致就採用weex打開。這裏的對比,目前還比較簡單粗暴,後續會進行優化,最終目標是只對比
?以前的一部分,後面的參數經過intent傳入到weex頁面,參與weex的渲染。
這樣就達到了動態攔截,動態上線weex的目的。
前面講到爲了加快weex打開時間,會預加載js,這裏就介紹一下js預加載的實現。
1)每次更新完配置文件,遍歷,查看是否存在md5一致的page_xxx.js文件,若是不存在則更新.
2)下載完成後,保存格式爲xxx.js,校驗md5
相同的話,記錄文件的最後修改時間;
不一樣的話,刪除已下載文件,從新下載,重複校驗流程。
3)支持統一跳轉協議,page對應目前app端的統一跳轉協議裏的page,有必要的時候能夠替換原來的native頁面,解決native頁面錯誤不能及時修復的問題。加載失敗的話,打開h5頁面。
4)每次打開指定頁面的時候,先檢查本地是否有對應page文件,再檢驗最後修改時間是否跟記錄的一致
一致就加載
不一致就用線上url。
問題一:上線後,發如今一些機型渲染失敗,public void onException(WXSDKInstance instance, String errCode, String msg)回調裏,errCode返回wx_create_instance_error,msg返回createInstance fail!
解決辦法:將apk解壓出來後,發現編譯出了支持5種abi的包。然而libweexv8.so只在armeabi和x86裏有,缺乏對其它三種abi的支持,那麼若是應用運行於arm64-v8a,x86_64,armeabi-v7a爲首選abi的設備上時,就會加載失敗了。其實arm64-v8a,armeabi-v7a,x86_64這三個abi,應用並非必需要作支持,手機通常都會提供自動兼容。因此咱們只要把對x86, arm64-v8a,x86_64的支持去掉就能夠。以下在主模塊的build.gradle的android裏的defaultConfig內添加以下內容:
defaultConfig { ndk { abiFilters "armeabi", "x86" } }
問題二:OkHttpAdapter裏調用onHttpFinish出現解析異常,日誌以下:
com.alibaba.fastjson.JSONException: syntax error, pos 2 at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1300) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1210) at com.alibaba.fastjson.JSON.parse(JSON.java:109) at com.alibaba.fastjson.JSON.parse(JSON.java:100) at com.taobao.weex.http.WXStreamModule.parseJson(WXStreamModule.java:378) at com.taobao.weex.http.WXStreamModule$2.onResponse(WXStreamModule.java:365) at com.taobao.weex.http.WXStreamModule$StreamHttpListener.onHttpFinish(WXStreamModule.java:523) at com.showjoy.weex.commons.adapter.OkHttpAdapter$6.onResponse(OkHttpAdapter.java:161) at okhttp3.RealCall$AsyncCall.execute(RealCall.java:133) at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) at java.lang.Thread.run(Thread.java:818)
解決方法:catch異常
try { if (null != listener) { listener.onHttpFinish(wxResponse); } } catch (Exception e) { LogUtils.e(e); }
問題三:相對地址以及線上線下環境切換問題。
解決方法:在最新版本已支持相對地址,在.vue文件裏連接以及請求地址使用相對地址,h5頁面自動選擇該頁面使用的域名,而在iOS和Android都作攔截處理,根據當前環境添加相應的域名。
Android 實現URIAdapter 注入
iOS 實現WXURLRewriteProtocol 注入
參考連接:
https://github.com/weexteam/
http://weex-project.io/doc/
https://github.com/alibaba/weex/