「Android」 详细全面的基于vue2.0Weex接入过程(Android视角)
本文來自尚妝Android團隊路飛
發表于尚妝github博客,歡迎訂閱!
一、說在前面的話
目前weex已在尚妝旗下的達人店app上線了一個常用的訂單管理頁面,截止目前Android上未發現問題,渲染時間在100-300ms之間。
作為Android開發,此文首先會從Android的角度為主來記錄接入的過程,希望給未接入的同學更方便省時地接入weex提供一點幫助。其中會涉及到預加載,降級,熱更新,埋點以及在app不更新的情況下動態配置新頁面等問題,這些Android和iOS都是統一的邏輯,希望和大家一起交流。前端方面可以參考我同事寫的《基于vue2.0的weex實踐(前端視角)》,iOS可以參考我同事寫的weex 實踐(iOS 視角)
二、Android接入過程
其實對于module、component的定義,以及IWXImgLoaderAdapter、IWXHttpAdapter等adapter的重寫,在playgroud和weexteam里都已經有很好的例子了。
1、gradle依賴
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'復制代碼2、新建weex module
在原來的project上,新建單獨的 weex module。代碼結構如下:
3、初始化weex
通過類WeexManager來統一管理weex相關的配置,以下是WeexManager里的init函數的主要內容,在application的onCreate里調用:
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);//根據需要注冊圖片、網絡、存儲等adapterWXSDKEngine.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>() {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>() {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>>>() {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();}}public void onResponseError(int i) {}});weexConfigRequest.start();}復制代碼1)考慮到第一次接入weex,有點擔心兼容問題,萬一引起崩潰等不確定因素,所以這里做了一個開關。其實每接入一個新的sdk都最好有個控制開關,以避免因為不確定因素導致不穩定。
2)weexDir是js的下載存儲路徑,為了加快頁面打開時間,會對js進行預加載到本地
3) sdk對a標簽的處理只調用了"event"的openURL接口,但是卻沒有注冊"event"。所以需要自己實現WXEventModule,并注冊。
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@Overridepublic 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);} }復制代碼4、新建統一的weex頁面
這邊考慮到以后頁面有可能嵌入到其他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頁面
5、開始渲染js,失敗后降級到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() {@Overridepublic 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(); }復制代碼6、多個js在同個頁面渲染
為了實現如圖的tab,一開始在.vue文件里使用tabbar組件,后來發現在Android機型適配上不夠好。于是后來就將兩個tab做成兩個頁面,生成兩個js文件。首先渲染“我的訂單.js”,生成如下的界面。
Alt text然后點擊“本店訂單”時,調用自定義module里的接口loadPage,參數為h5的鏈接。三端實現接口loadPage,h5直接跳轉,而iOS和Android通過h5鏈接從weex跳轉配置里找到對應的js,重新渲染顯示。下面具體做幾點說明:
1)定義Map wxsdkInstanceMap;來存儲不同js的WXSDKInstance,定義Map viewMap來存儲不同js渲染后的View。之所以要存儲多個WXSDKInstance,是因為WXSDKInstance不能重復渲染,而且當WXSDKInstance destory后,之前渲染的view里的內容也會被清空。注意在在頁面destory時,記得把所有WXSDKInstance都destory就好了。
2)viewMap里的key對應頁面的js。點擊tab切換頁面時,如對應的js已渲染,則直接取出view來顯示。
3)上文提到的weex跳轉配置,在以下的跳轉規則里一同介紹。
二、App的跳轉規則的weex支持方案設計
跳轉規則如下圖,如果看不清,可以到新頁面放大查看。
App跳轉框架主要介紹一下兩個配置參數:
- 在參數weexPages配置所有的weex頁面。
示例如下:
page: 對應統一跳轉的 path
url: 需要渲染的js,
md5: js文件的md5值用于校驗,
h5: 渲染失敗后的降級方案,
v: 最低支持的版本號
在頁面訪問h5頁面時,拿url跟weexPages里的url進行對比,如果一致就采用weex打開。這里的對比,目前還比較簡單粗暴,后續會進行優化,最終目標是只對比?之前的一部分,后面的參數通過intent傳入到weex頁面,參與weex的渲染。
這樣就達到了動態攔截,動態上線weex的目的。
三、js預加載方案
前面講到為了加快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" } }復制代碼enter image description here問題二:OkHttpAdapter里調用onHttpFinish出現解析異常,日志如下:
com.alibaba.fastjson.JSONException: syntax error, pos 2at 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 注入
參考鏈接:
github.com/weexteam/
weex-project.io/doc/
github.com/alibaba/wee…
總結
以上是生活随笔為你收集整理的「Android」 详细全面的基于vue2.0Weex接入过程(Android视角)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redhat6.4执行二进制程序报错:/
- 下一篇: LLVM每日谈之二十三 LLVM/C