最新公司新啟動(dòng)了一個(gè)項(xiàng)目仰泻,大致架構(gòu)就是,原生客戶端相當(dāng)于一個(gè)殼篮愉,通過(guò)WebView嵌套著含有主要內(nèi)容的H5般眉,客戶端對(duì)數(shù)據(jù)進(jìn)行管理,如數(shù)據(jù)緩存或數(shù)據(jù)接口請(qǐng)求潜支,然后將數(shù)據(jù)處理好傳給前端,而前端要做的就是將數(shù)據(jù)以各種方式顯示出來(lái)柿汛,有點(diǎn)MVP中V層和M層的意思冗酿。
由于整個(gè)項(xiàng)目涉及到大量和H5的交互邏輯埠对,所以很自然的,我們使用上了JSBridge裁替。很不幸项玛,在用的過(guò)程中我們發(fā)現(xiàn)了不少問(wèn)題,也踩了很多坑弱判。所以在這里總結(jié)一下襟沮,希望可以幫助到遇到這些問(wèn)題的人。
1昌腰、異步問(wèn)題
在native和前端在互相調(diào)用事件的過(guò)程中需保證雙方的注冊(cè)時(shí)機(jī)以及生命周期开伏,即調(diào)用方在調(diào)用的時(shí)候需保證注冊(cè)方已經(jīng)注冊(cè)好。道理很簡(jiǎn)單遭商,沒(méi)有注冊(cè)好固灵,何談?wù){(diào)用。所以在某些異步事件情況下劫流,應(yīng)該保證數(shù)據(jù)管理方是調(diào)用者巫玻,接收數(shù)據(jù)端是注冊(cè)方。因?yàn)楫惒綌?shù)據(jù)何時(shí)下載/請(qǐng)求好祠汇,只有數(shù)據(jù)管理方知道仍秤,也就能確定合適的調(diào)用時(shí)機(jī),而注冊(cè)方要做的是—盡量早的注冊(cè)在可很,在我們這次項(xiàng)目里诗力,native app就是調(diào)用方,而前端是注冊(cè)方根穷。
例如姜骡,在webview初始化的時(shí)候,客戶端調(diào)用前端的方法時(shí)應(yīng)保證前端的事件已經(jīng)全部注冊(cè)好的情況下屿良,即webview加載完成的時(shí)候圈澈。
//webview加載完畢
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//傳遞數(shù)據(jù)給前端
webView.callHandler("setData", json, new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
}
2、嚴(yán)格模式下的數(shù)據(jù)問(wèn)題
前端在嚴(yán)格模式下調(diào)用natvie方法并傳過(guò)來(lái)的數(shù)據(jù)json串可能會(huì)在每個(gè)字段和數(shù)據(jù)帶上 " " ",或“ / ”尘惧,這樣我們?cè)诮馕龀蓪?shí)體bean對(duì)象的時(shí)候會(huì)報(bào)解析錯(cuò)誤而閃退康栈。
所以穩(wěn)妥起見(jiàn),native在使用前端傳過(guò)來(lái)的數(shù)據(jù)的第一步要做的就是過(guò)濾(需根據(jù)數(shù)據(jù)本身定好過(guò)濾規(guī)則):
webView.registerHandler("getData", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
data.replace("\"", "").replace("\\", "");
}
});
3喷橙、子線程無(wú)法調(diào)用問(wèn)題
native調(diào)前端的方法(callHandler)在子線程調(diào)不到啥么,換句話說(shuō)就是,本地調(diào)前端的方法時(shí)需保證在主線程贰逾。
即 runOnMainThread() 或使用RxJava的
.observeOn(AndroidSchedulers.mainThread())
來(lái)調(diào)度線程悬荣。
4、無(wú)法收到回調(diào)
native調(diào)前端的方法疙剑,前端會(huì)回傳給native一個(gè)回調(diào)告訴native是否有調(diào)到這個(gè)方法氯迂,即onCallBack方法践叠。但如果想要在這個(gè)方法的內(nèi)部進(jìn)行一些邏輯處理,它可能會(huì)讓你失望嚼蚀,因?yàn)檫@個(gè)回調(diào)有些不穩(wěn)定禁灼,所以有時(shí)我們根本不知道前端到底有沒(méi)有調(diào)到我們的方法,總之轿曙,不是萬(wàn)不得已就不要在原生回調(diào)里寫上邏輯處理弄捕。如果一定需要:
解決辦法是本地再注冊(cè)一個(gè)方法,當(dāng)native調(diào)到前端的方法的時(shí)候导帝,和前端協(xié)商好讓前端立馬再調(diào)一下native的一個(gè)方法守谓,native的方法被調(diào)到即說(shuō)明 natvie已經(jīng)成功調(diào)到前端的方法,而不要使用方法本身的回調(diào)舟扎。
webView.callHandler("test", "", new CallBackFunction() {
@Override
public void onCallBack(String data) {
LogUtil.i("test");
}
});
webView.registerHandler("testBack", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
LogUtil.i("testBack");
}
});
和前端協(xié)商好分飞,當(dāng)我們調(diào)用“test”方法的時(shí)候,讓前端調(diào)用我們注冊(cè)的“testBack”睹限,當(dāng)testBack被調(diào)用的時(shí)候譬猫,我們就認(rèn)為test方法有被前端調(diào)用,這樣就可以規(guī)避onCallBack的不穩(wěn)定的問(wèn)題羡疗。
5染服、不要通過(guò)Gradle遠(yuǎn)程依賴,盡量使用module本地依賴
最重要的一條叨恨,不要通過(guò)gradle遠(yuǎn)程依賴JsBridge柳刮,不要通過(guò)gradle遠(yuǎn)程依賴JsBridge,不要通過(guò)gradle遠(yuǎn)程依賴JsBridge痒钝,重要的事情說(shuō)三遍
implementation'com.github.lzyzsd:jsbridge:1.0.4'
這會(huì)導(dǎo)致callHandler的方法在某些場(chǎng)景下的調(diào)用不穩(wěn)定秉颗,即有時(shí)候可以調(diào)到有時(shí)候調(diào)不到。
webView.callHandler("test", "", new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
解決辦法有兩點(diǎn):
解決方案一: 將庫(kù)下載到本地直接以module的形式依賴送矩。
沒(méi)了蚕甥。。栋荸。
這樣解決了call前端方法不穩(wěn)定的問(wèn)題菇怀,甚至解決了onCallBack回調(diào)不穩(wěn)定的問(wèn)題。
答案應(yīng)該是如issue上所說(shuō)晌块。
坦白說(shuō)爱沟,翻了眾多issue,看到這條issue的時(shí)候我的內(nèi)心是崩潰的匆背,一直以為是native和前端的聯(lián)調(diào)出了問(wèn)題呼伸。
雖然這樣貌似可以解決問(wèn)題但是我們還有方案二
解決方案二: callHandler()方法在某些場(chǎng)景下的低概率不穩(wěn)定問(wèn)題影響太不好了,以至于我們?cè)谟梅桨敢唤鉀Q問(wèn)題后還是不放心钝尸,于是就加上了另外一套容錯(cuò)機(jī)制:
Observable observable = Observable.interval(0, 1, TimeUnit.SECONDS);
//”test“方法有失敗的概率 寫個(gè)2秒循環(huán)的定時(shí)器重試括享,成功調(diào)用后退出并釋放定時(shí)器(經(jīng)過(guò)測(cè)試在不穩(wěn)定的時(shí)候連續(xù)調(diào)用2-3次就能重試成功)闽铐。
mObserver =new DisposableObserver() {
@Override
public void onNext(@NonNull Long aLong) {
webView.callHandler("test", json, new CallBackFunction() {
@Override
public void onCallBack(String data) {
//使用本地module依賴的方式貌似是解決了onCallBack不穩(wěn)定的問(wèn)題。
//走到這里說(shuō)明調(diào)用到了奶浦,結(jié)束循環(huán)。
dispose();
}
});
}
};observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mObserver);
以上的邏輯就是:如果第一次調(diào)用callHandler踢星,如果1秒鐘之后還沒(méi)有收到回調(diào)我們會(huì)再次進(jìn)行重試澳叉,知道成功為止。觀察情況是沐悦,如果有出現(xiàn)異常成洗,大概在重復(fù)調(diào)用第2-3次的時(shí)候可以成功,成功之后跳出并釋放計(jì)時(shí)器藏否。不過(guò)經(jīng)過(guò)大量的測(cè)試瓶殃,我們發(fā)現(xiàn),在使用了方案一的情況下副签,幾乎已經(jīng)沒(méi)有會(huì)使用到方案二的情況遥椿,不過(guò)為了容錯(cuò),我們依然保留淆储。
結(jié)語(yǔ):
以上就是使用JSBridge踩過(guò)的坑冠场,可以說(shuō)不穩(wěn)定的這種偶現(xiàn)問(wèn)題實(shí)在是太頭疼了,如果以后還有在使用JSBridge開(kāi)發(fā)一些復(fù)雜場(chǎng)景的話本砰,希望大家能基于這幾條原則(問(wèn)題)碴裙,除了以上說(shuō)的,個(gè)人還有一些反思:
一点额、遇到一些開(kāi)源庫(kù)的問(wèn)題舔株,多去翻翻issue上前人提過(guò)的問(wèn)題,說(shuō)不定會(huì)有意外收獲还棱,issue也沒(méi)有的話再去看看源碼實(shí)現(xiàn)载慈。
二、遇到問(wèn)題诱贿,適當(dāng)?shù)某鋈プ咦咄拗祝粑粑迈r空氣,腦袋清醒的時(shí)候珠十,發(fā)現(xiàn)很多問(wèn)題其實(shí)不是問(wèn)題料扰。