Android Webview與JS交互之DSbridge源碼分析

自從有了Webview與JS交互,而我又使用過并且有了一定的理解哈误,真心的體會(huì)到讓開發(fā)者能在web頁相關(guān)開發(fā)為所欲為慰丛。demo地址

image.png

比如,在一個(gè)活動(dòng)頁面,需要用戶登陸后的userId簿盅,才能領(lǐng)取活動(dòng)頁面的獎(jiǎng)品挥下,怎么獲取呢?這個(gè)時(shí)候桨醋,你可以通過js跳轉(zhuǎn)到登錄頁棚瘟,登陸成功帶著userId回到活動(dòng)頁,就可以進(jìn)行下一步動(dòng)作了喜最,美滋滋~
上面只是個(gè)簡單的例子偎蘸,本章的主角是DSbridge,不過瞬内,在這之前迷雪,我們先回顧下,在沒封裝的時(shí)候虫蝶,Webview與JS交互的實(shí)現(xiàn)章咧。(我之前一直在用的方式)

/**js調(diào)用android端的方法:**/
//主要看showLog這個(gè)方法
public void initView() { 
 webView.getSettings().setJavaScriptEnabled(true);                                     webView.addJavascriptInterface(new TestEntity(),"test");
}
public classTestEntity { 
 @JavascriptInterface
public void showLog(String data) {
      Log.i("android",data);
  }
}

js調(diào)用showLog()方法:

function handleAndroidMethod() {
    test.showLog('hhh');
}

android 調(diào)用js 的方法:

//js里面方法
function jsMethod(data) {
....方法實(shí)現(xiàn)
}

android這邊這樣調(diào)用:

webview.loadUrl('jsMethod('測試')')
  很好,傳統(tǒng)的是如上這樣實(shí)現(xiàn)能真,我們回到DSbridge赁严,DSbridge其實(shí)就是在這樣的交互中封裝了一下。那么在分析封裝實(shí)現(xiàn)之前粉铐,我們來看看疼约,DSbridge完成交互中的流程。(還是以分享的例子)

android需要處理的:

public void initView() {
webView= (DWebView) findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.setJavascriptInterface(newJsApiEntity(this));
}
public class JsApiEntity {
private Activity mActivity;
public JsApiEntity(Activity mActivity) {
this.mActivity= mActivity;
}
//for synchronous invocation 同步
@JavascriptInterface
String testSyn(JSONObject jsonObject)throwsJSONException {
return jsonObject.getString("msg") +"[syn call]";
}
//for asynchronous invocation  異步
//分享相關(guān)
@JavascriptInterface
void share(JSONObject jsonObject,CompletionHandler handler)throwsJSONException {
      EventBus.getDefault().post(jsonObject,"SHOW_SHARE_DIALOG");
      handler.complete("對應(yīng)后臺(tái)里面的flag");//回調(diào)數(shù)據(jù)給H5
           }
}

js那邊調(diào)用:

<script>
    var title = "我是標(biāo)題";
    var content = "我是內(nèi)容";
    var url = "落地頁的鏈接";
    function share() {
        dsBridge.call("share",  {title: title,content:content,
            imageUrl: "圖片鏈接喲",url:url}, function(flag){alert(flag);})
    }
</script>

很對蝙泼,上面就是使用DSbridge之后的使用詳情了程剥。其實(shí)還是挺有意思的,只要調(diào)用dsBridge.call()就可以了踱承,參數(shù)是之前定義的方法名稱(比如 share)倡缠。
DSbridge做的事情,就是在js調(diào)用的原生代碼時(shí)候做了封裝茎活,每次調(diào)用都是用dsBridge.call()來處理昙沦,然后具體的就根據(jù)參數(shù)來區(qū)分了。
我們來看看他的主要源碼部分 DWebView.java:

void init() {
......
 super.addJavascriptInterface(new Object() {
            int i = 0;

            @JavascriptInterface
            @Keep
            public String call(String methodName, String args) {
                String error = "Js bridge method called, but there is not a JavascriptInterface object, please set JavascriptInterface object first!";
                if(DWebView.this.jsb == null) {
                    Log.e("SynWebView", error);
                    return "";
                } else {
                    Class cls = DWebView.this.jsb.getClass();

                    try {
                        boolean asyn = false;
                        JSONObject arg = new JSONObject(args);
                        final String callback = "";

                        Method e;
                        try {
                            callback = arg.getString("_dscbstub");
                            arg.remove("_dscbstub");
                            e = cls.getDeclaredMethod(methodName, new Class[]{JSONObject.class, CompletionHandler.class});
                            asyn = true;
                        } catch (Exception var12) {
                            e = cls.getDeclaredMethod(methodName, new Class[]{JSONObject.class});
                        }

                        if(e == null) {
                            error = "ERROR! \n Not find method \"" + methodName + "\" implementation! ";
                            Log.e("SynWebView", error);
                            DWebView.this.evaluateJavascript(String.format("alert(decodeURIComponent(\"%s\"})", new Object[]{error}));
                            return "";
                        }

                        JavascriptInterface annotation = (JavascriptInterface)e.getAnnotation(JavascriptInterface.class);
                        if(annotation != null) {
                            e.setAccessible(true);
                            Object ret;
                            if(asyn) {
                                ret = e.invoke(DWebView.this.jsb, new Object[]{arg, new CompletionHandler() {
                                    public void complete(String retValue) {
                                        this.complete(retValue, true);
                                    }

                                    public void complete() {
                                        this.complete("", true);
                                    }

                                    public void setProgressData(String value) {
                                        this.complete(value, false);
                                    }

                                    private void complete(String retValue, boolean complete) {
                                        try {
                                            if(retValue == null) {
                                                retValue = "";
                                            }

                                            retValue = URLEncoder.encode(retValue, "UTF-8").replaceAll("\\+", "%20");
                                            String e = String.format("%s(decodeURIComponent(\"%s\"));", new Object[]{callback, retValue});
                                            if(complete) {
                                                e = e + "delete window." + callback;
                                            }

                                            DWebView.this.evaluateJavascript(e);
                                        } catch (UnsupportedEncodingException var4) {
                                            var4.printStackTrace();
                                        }

                                    }
                                }});
                            } else {
                                ret = e.invoke(DWebView.this.jsb, new Object[]{arg});
                            }

                            if(ret == null) {
                                ret = "";
                            }

                            return ret.toString();
                        }

                        error = "Method " + methodName + " is not invoked, since  it is not declared with JavascriptInterface annotation! ";
                        DWebView.this.evaluateJavascript(String.format("alert(\'ERROR \\n%s\')", new Object[]{error}));
                        Log.e("SynWebView", error);
                    } catch (Exception var13) {
                        DWebView.this.evaluateJavascript(String.format("alert(\'ERROR! \\n調(diào)用失斣乩蟆:函數(shù)名或參數(shù)錯(cuò)誤 [%s]\')", new Object[]{var13.getMessage()}));
                        var13.printStackTrace();
                    }

                    return "";
                }
            }

            @JavascriptInterface
            @Keep
            public void returnValue(int id, String value) {
                OnReturnValue handler = (OnReturnValue)DWebView.this.handlerMap.get(Integer.valueOf(id));
                if(handler != null) {
                    handler.onValue(value);
                    DWebView.this.handlerMap.remove(Integer.valueOf(id));
                }

            }

            @JavascriptInterface
            @Keep
            public void init() {
                DWebView.this.injectJs();
            }
        }, "_dsbridge");

......
 public void setJavascriptInterface(Object object) {
        this.jsb = object;
    }
}

很好盾饮,我們先看init方法(@Keep,其實(shí)就是為了不被混淆懒熙,不用在意)丘损,從
Class cls = DWebView.this.jsb.getClass()里面一開始就很明顯的說出了意圖,使用反射來處理工扎,通過e = cls.getDeclaredMethod獲取到this.jsb里面的方法徘钥,方法區(qū)分同步和異步,還有這個(gè)call方法是不是很眼熟肢娘?public String call(String methodName, String args) 就是js調(diào)用原生代碼里面用到的呈础,methodName就是需要調(diào)用的方法舆驶。
我們回到這個(gè)jsb ,其實(shí)就是通過setJavascriptInterface方法設(shè)置進(jìn)來的而钞,在上面例子里面其實(shí)就是我們定義的JsApiEntity對象沙廉,現(xiàn)在,通過反射臼节,把需要調(diào)用的Method (e)獲取到了撬陵。接下來肯定是通過注解來調(diào)用方法了,我們接著看:

 JavascriptInterface annotation = (JavascriptInterface)e.getAnnotation(JavascriptInterface.class);
   if(annotation != null) {'....''}

使用了@JavascriptInterface注解的网缝,才會(huì)進(jìn)入里面巨税,里面其實(shí)就是通過e.invoke來執(zhí)行js調(diào)用的方法了。

本文demo地址:https://github.com/niyige/DSBridgeWebDemo
有什么問題歡迎交流:893007592@qq.com

相關(guān)資料:
https://github.com/wendux/DSBridge-Android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末途凫,一起剝皮案震驚了整個(gè)濱河市垢夹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌维费,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件促王,死亡現(xiàn)場離奇詭異犀盟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蝇狼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門阅畴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人迅耘,你說我怎么就攤上這事贱枣。” “怎么了颤专?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵纽哥,是天一觀的道長。 經(jīng)常有香客問我栖秕,道長春塌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任簇捍,我火速辦了婚禮只壳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暑塑。我一直安慰自己吼句,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布事格。 她就那樣靜靜地躺著惕艳,像睡著了一般搞隐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尔艇,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天尔许,我揣著相機(jī)與錄音,去河邊找鬼终娃。 笑死味廊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棠耕。 我是一名探鬼主播余佛,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窍荧!你這毒婦竟也來了辉巡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蕊退,失蹤者是張志新(化名)和其女友劉穎郊楣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓤荔,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡净蚤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了输硝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片今瀑。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖点把,靈堂內(nèi)的尸體忽然破棺而出橘荠,到底是詐尸還是另有隱情,我是刑警寧澤郎逃,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布嗽仪,位于F島的核電站扣囊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刻蟹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一茅主、第九天 我趴在偏房一處隱蔽的房頂上張望续语。 院中可真熱鬧腻豌,春花似錦、人聲如沸型宙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妆兑。三九已至魂拦,卻和暖如春毛仪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芯勘。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工箱靴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荷愕。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓衡怀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親安疗。 傳聞我的和親對象是個(gè)殘疾皇子抛杨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評論 25 707
  • 前言 總結(jié) Android WebView 常用的相關(guān)知識(shí)點(diǎn),令包含以下干貨內(nèi)容分析:Js注入漏洞荐类、WebView...
    無名小子的雜貨鋪閱讀 69,800評論 17 169
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉怖现,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評論 0 9
  • JSBridge 1. Why do we need JSBridge? 2. Why is “JS”Bridge...
    loveqin閱讀 9,167評論 0 7
  • 中星說:“四叔到底是四叔!白雪不敢走的玉罐,她一走屈嗤,我的秦腔振興計(jì)劃就塌火了!”夏天智說:“你有秦腔振興計(jì)劃吊输?你來你來...
    姜辣素閱讀 237評論 0 0