Android WebView同Js的交互方案

本文發(fā)表于KuTear's Blog,轉(zhuǎn)載請注明

開源方案

實現(xiàn)

Jockey

基本原理

復(fù)寫WebViewClientshouldOverrideUrlLoading(),通過對URL的scheme和action來判斷是否由App執(zhí)行某個Action.

使用

        mJockey = JockeyImpl.getDefault();
        mJockey.configure(mWebView);
        mJockey.setWebViewClient(mWebViewClient);
        mJockey.on("log", new JockeyHandler() { //添加Action
            @Override
            public void doPerform(Map<Object, Object> payload) {
                String value = "color=" + payload.get("color");
                Log.d("jockey", value);
            }
        });

源碼解讀

JockeyImplconfigure(...)函數(shù)中

@SuppressLint("SetJavaScriptEnabled")
@Override
 public void configure(WebView webView) {
      webView.getSettings().setJavaScriptEnabled(true);
      //設(shè)置默認(rèn)的WebViewClient
        webView.setWebViewClient(this.getWebViewClient());//JockeyWebViewClient
}

在設(shè)置自定義WebViewClient時不能直接在WebView設(shè)置,因為這樣會覆蓋底層的JockeyWebViewClient,需要使用

mJockey.setWebViewClient(myWebViewClient);

在這里的看看實現(xiàn)

    @Override
    public void setWebViewClient(WebViewClient client) {
        //裝飾模式
        this._client.setDelegate(client); //這里的_client就是JockeyWebViewClient
    }

核心代碼就在JockeyWebViewClientshouldOverrideUrlLoading(...)函數(shù)

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (delegate() != null
                && delegate().shouldOverrideUrlLoading(view, url))
            return true; //用戶的Client處理
        try {
            URI uri = new URI(url);
            if (isJockeyScheme(uri)) {  //判讀是否為 jockey://開始
                processUri(view, uri);  //對URL進(jìn)行處理
                return true;
            }
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (HostValidationException e) {
            e.printStackTrace();
            Log.e("Jockey", "The source of the event could not be validated!");
        }
        return false; //宿主不處理,由WebView自行處理
    }

接著就是對感興趣的URL進(jìn)行處理

public void processUri(WebView view, URI uri)
            throws HostValidationException {
        String[] parts = uri.getPath().replaceAll("^\\/", "").split("/");
        String host = uri.getHost();
        JockeyWebViewPayload payload = checkPayload(_gson.fromJson(
                uri.getQuery(), JockeyWebViewPayload.class));   //參數(shù)解析,在自己項目中可以替換為自己的解析方式
        if (parts.length > 0) {
            if (host.equals("event")) {
                getImplementation().triggerEventFromWebView(view, payload); //getImplementation() 返回 JockeyImpl
            } else if (host.equals("callback")) {
                getImplementation().triggerCallbackForMessage(
                        Integer.parseInt(parts[0]));
            }
        }
    }
protected void triggerEventFromWebView(final WebView webView,JockeyWebViewPayload envelope) {
        final int messageId = envelope.id;
        String type = envelope.type;//Action類型,如本節(jié)開始的'log'
        if (this.handles(type)) {
            JockeyHandler handler = _listeners.get(type); 
            //每一個事件都有一個對WebView js的回調(diào)
            handler.perform(envelope.payload, new JockeyHandler.OnCompletedListener() {
                @Override
                public void onCompleted() {
                    // This has to be done with a handler because a webview load
                    // must be triggered
                    // in the UI thread
                    _handler.post(new Runnable() {
                        @Override
                        public void run() {
                            triggerCallbackOnWebView(webView, messageId);
                        }
                    });
                }
            });
        }
    }

現(xiàn)在可以反過來看看本節(jié)開始的mJsckey.on()方法的實現(xiàn)

@Override
public void on(String type, JockeyHandler... handler) {
    if (!this.handles(type)) {
            _listeners.put(type, new CompositeJockeyHandler());
    }
    _listeners.get(type).add(handler);  //又是裝飾模式
}

CompositeJockeyHandleradd()的實現(xiàn)

    public void add(JockeyHandler ... handler) {
        _handlers.addAll(Arrays.asList(handler));
    }

就是把自定義的JockeyHandler添加到變量_handlers中去了.
現(xiàn)在可以知道上面_listeners.get(type)得到的一定是CompositeJockeyHandler,執(zhí)行perform函數(shù),

@Override
public void perform(String payload, OnCompletedListener listener) {
        this._listener = listener;
        this._accumulator = new AccumulatingListener();
        doPerform(payload);
}
    
@Override
protected void doPerform(String payload) {
        for (JockeyHandler handler : _handlers)
            handler.perform(payload, this._accumulator);
}

在此,我們就可以看見,事件到達(dá)用戶自已的JockeyHandlerperform(),perform()又調(diào)用onPerform()函數(shù).OK,現(xiàn)在自己處理事件咯~

對于Java調(diào)用js部分,原理就更加的簡單,就一句話

webView.load("javascript:XXXXXXX");

JsBridge

JsBridge是大頭鬼的開源項目,star和fork的量很高,但是我個人覺得他的實現(xiàn)并沒有Jockey好,比如對自定義的WebViewClient,它要添加WebViewClient必須重寫BridgeWebView,并且WebViewClient必須是BridgeWebViewClient的子類.這樣不僅對開發(fā)者來說使用復(fù)雜程度提升了,另外暴露了太多開發(fā)者本不需要關(guān)注的接口.

基本原理

Jockey一樣,復(fù)寫WebViewClientshouldOverrideUrlLoading()方法.源碼部分也是基本和上面一致,這里不再多說.

RainbowBridge

基本原理

RainbowBridge的原理和上面兩個不同,上面是通過URL來觸發(fā)事件的執(zhí)行,而RainbowBridge是通過Js來控制事件的執(zhí)行.Android在WebChromeClient有對一些原生的Js事件進(jìn)行捕獲,最常見的就是alert對話框,但是alert對話框在原本Js中出現(xiàn)的頻率較高,如果我們對這個事件攔截掉,可能有一些異常,所以可以攔截一個相對較少出現(xiàn)的事件,通過處理這個事件的信息從而達(dá)到執(zhí)行事件.

源碼解讀

public class JsBridgeWebChromeClient extends WebChromeClient {
    @Override
    public final boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        result.confirm();
        JsCallJava.newInstance().call(view,message);//在這里處理事件
        return true;
    }
    ...
}

下面簡單看一下對事件的處理.

public void call(WebView webView, String message) {
    if (webView == null || TextUtils.isEmpty(message))
            return;
    parseMessage(message); //解析參數(shù)到mParms
    invokeNativeMethod(webView); //根據(jù)參數(shù)掉本地方法,這里使用了反射. 實現(xiàn)比較靈活
 }

同樣,對于原生發(fā)送消息到Js依舊是使用WebView.load("JavaScript:XXXX").

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肋层,一起剝皮案震驚了整個濱河市懊亡,隨后出現(xiàn)的幾起案子辣恋,更是在濱河造成了極大的恐慌谊娇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡金句,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門吕嘀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來违寞,“玉大人,你說我怎么就攤上這事偶房〕寐” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵棕洋,是天一觀的道長彰阴。 經(jīng)常有香客問我,道長拍冠,這世上最難降的妖魔是什么尿这? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮庆杜,結(jié)果婚禮上射众,老公的妹妹穿的比我還像新娘。我一直安慰自己晃财,他們只是感情好叨橱,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著断盛,像睡著了一般罗洗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钢猛,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天伙菜,我揣著相機與錄音,去河邊找鬼命迈。 笑死贩绕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的壶愤。 我是一名探鬼主播淑倾,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼征椒!你這毒婦竟也來了娇哆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤勃救,失蹤者是張志新(化名)和其女友劉穎碍讨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剪芥,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡垄开,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了税肪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溉躲。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖益兄,靈堂內(nèi)的尸體忽然破棺而出锻梳,到底是詐尸還是另有隱情,我是刑警寧澤净捅,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布疑枯,位于F島的核電站,受9級特大地震影響蛔六,放射性物質(zhì)發(fā)生泄漏荆永。R本人自食惡果不足惜废亭,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望具钥。 院中可真熱鬧豆村,春花似錦、人聲如沸骂删。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宁玫。三九已至粗恢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欧瘪,已是汗流浹背眷射。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恋追,地道東北人凭迹。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像苦囱,于是被迫代替她去往敵國和親嗅绸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容