Android的WebView與H5前端JS代碼交互的那點(diǎn)事

demo地址:http://www.reibang.com/p/c20513cad758
前段時(shí)間項(xiàng)目有深度和前端對接過政模,也是碰了一些坑改鲫,現(xiàn)在有時(shí)間就拿出來分享下

JS調(diào)用原生不外乎就兩種追迟,一種是傳假的url艘刚,也就是url攔截的方式蚌成,類似于下面這種:

//js代碼
 function sendCommand(param){
    var url="js-call://"+param;
    document.location = url;
 }
 sendCommand("PlaySnake");

//Java代碼
 mWebView.setWebViewClient(new WebViewClient() {
     @Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
         if (url.contains("js-call:")) {
             if (url.contains("PlaySnake")) {
                Log.d("X5WebViewActivity", "玩蛇");
             } else if (url.contains("WhatDoesTheFoxSay")) {
                Log.d("X5WebViewActivity", "叮鈴鈴鈴叮鈴鈴");
             } else {
                showInfoAsToast("龜兒娃,你調(diào)得不對");
             }
             return false;
         }
         view.loadUrl(url);
         return true;       
     }
 });

這種方法來調(diào)用原生晌砾,好處就是集成比較迅速坎拐,約定一個(gè)標(biāo)識(shí),類似于示例中的“js-call”养匈,再約定一波Type哼勇,比如“玩蛇”之類的,代碼很簡單呕乎,畢竟大家都很忙积担。

但是如果你打算長期把這個(gè)項(xiàng)目做下去的話,這種方式還是不要了吧猬仁,缺點(diǎn)太明顯了帝璧。首先是給原生傳數(shù)據(jù),只能是字符串湿刽;然后業(yè)務(wù)擴(kuò)展起來的烁,你的else if越寫越多,里面再加一大把switch诈闺,代碼越來臃腫撮躁,維護(hù)起來那感覺真的酸爽。

另一種就是通過谷歌提供的JS與Java綁定的接口买雾,約定好要交互的對象名把曼,類似于下面的“App”

//通過WebView提供的addJavascriptInterface這行代碼,我們在瀏覽器的JS環(huán)境中創(chuàng)建了一個(gè)"App"對象
//這個(gè)對象下的函數(shù)就是自定義接口類里面通過 @JavascriptInterface注解的Java方法轉(zhuǎn)換而來的

mWebView.addJavascriptInterface(new JavaFuckJSInterface(this), "App");

/**
 * 自定義的交互接口類
 */
public class JavaFuckJSInterface{
    private WeakReference<X5WebViewActivity> x5WebViewActivity;

    public JavaFuckJSInterface(X5WebViewActivity context) {
        x5WebViewActivity = new WeakReference<>(context);
    }
    //通過這個(gè)@JavascriptInterface轉(zhuǎn)化成綁定的“App”對象下的同名函數(shù)漓穿,js代碼可以直接調(diào)用
    @JavascriptInterface
    public void presentCamera(String data) {
        //拍照上傳
        x5WebViewActivity.get().presentCamera(data);
    }
}

//js代碼
  var parameter = {};
  parameter.size = "1024*768";
  parameter.format = "JPEG";
  var parameterStr = JSON.stringify(parameter);
  App.presentCamera(parameterStr);

這樣寫的話嗤军,規(guī)范了不少,即使函數(shù)再多晃危,這個(gè)接口里面也是一目了然叙赚,調(diào)函數(shù)就是調(diào)函數(shù),傳參數(shù)就是傳參數(shù)僚饭,相比于之前那個(gè)方法震叮,可讀性高了不少

不過上面寫的這些破玩意網(wǎng)上資料一大把,我特么是吃多了么鳍鸵,再寫一遍苇瓣?

NoNoNo,這些東西確實(shí)足夠我們與JS交互了偿乖,但是前端不想搞JSON.stringify(parameter)這種操作啊击罪,他要直接傳對象過來。為什么別人IOS都可以拿到我的對象贪薪,你拿的就是undefined媳禁?為什么別人IOS能給我對象,你就不給我對象画切,偏要給我字符串竣稽?憑什么別人IOS能拿到我的匿名回調(diào)函數(shù)來調(diào)用,你偏偏讓我寫一個(gè)回調(diào)函數(shù)給你調(diào)霍弹?

我:你皮任你皮
前端:我很皮毫别?這只是很正常的需求好吧

ok,也不是不能做到庞萍,不過這就需要通過注入JS代碼來完成了
talk is cheap , show me the code
下面這個(gè)微型的SDK能夠?qū)崿F(xiàn)互調(diào)傳JSON對象拧烦,調(diào)用js傳入的匿名函數(shù)

 //需要注入的js代碼,加//"是因?yàn)楹啎鴷?huì)忽略\"這個(gè)回引號(hào)钝计,不加的話后面的代碼都是字符串的顏色了

 //原理是通過這個(gè)SDKNativeEvents來保存?zhèn)魅氲哪涿瘮?shù)callback恋博,等原生做完該做的操作之后
 //接著去調(diào)用sdk_nativeCallback這個(gè)函數(shù)來運(yùn)行存進(jìn)去的callback
 var SDKNativeEvents = {}
 function sdk_launchFunc(funcName,data,callback){
      if(!data){
          alert(\"必須傳入data\");//"
          return;
      }
      if(!callback){
          alert(\"必須傳入回調(diào)function\");//"
          return;
      }
      SDKNativeEvents[funcName] = callback;
      var jsObj={};
      jsObj.funcName=funcName;
      jsObj.data=JSON.stringify(data);
      var str = JSON.stringify(jsObj);
      App.native_launchFunc(str)  //這個(gè)函數(shù)要在JavascriptInterface里申明
  }
  function sdk_nativeCallback(funcName,data){
      var obj= JSON.parse(data);
      if(SDKNativeEvents[funcName]){
          SDKNativeEvents[funcName](obj);
          if(funcName != \"updateLocation\"){//定位回調(diào)會(huì)不定時(shí)去重復(fù)觸發(fā),不做置空操作"
              SDKNativeEvents[funcName] = null;
          }
      }
  }
  //下面實(shí)現(xiàn)的功能和通過@JavascriptInterface注解的Java方法是一樣的私恬,App為約定好的注入對象名
  //App.xxx為暴露給前端的js函數(shù)
  App.login = function(data,callback){
      sdk_launchFunc(\"login\",data,callback);//"
  }
  App.xxxxxxxxxxxxx = function(data,callback){
      sdk_launchFunc(\"xxxxxxxxxxxxx\",data,callback);//"
  }
  ...

上面那些App.xxx的函數(shù)其實(shí)也可以不用注入债沮,實(shí)現(xiàn)起來就是把 sdk_launchFunc這個(gè)函數(shù)注入到App對象下面,讓前端直接調(diào)用本鸣,這樣不用增加一個(gè)調(diào)用就多注入一個(gè)函數(shù)疫衩,前端只用改funcName就能實(shí)現(xiàn)所有的調(diào)用。但是我覺得荣德,調(diào)函數(shù)就是調(diào)函數(shù)闷煤,傳參數(shù)就是傳參數(shù)童芹,將每個(gè)功能拆成function可以提高代碼的可讀性

注入JS代碼也很簡單,把上面那些js代碼都粘貼到string這個(gè)資源文件里面鲤拿,再通過mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code1))來注入就行假褪,其中js_sdk_code1就是js代碼的字符串
示例代碼:

//在網(wǎng)頁加載時(shí)提前注入,可以保證頁面一旦加載完畢前端就能立即調(diào)到函數(shù)
 mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView webView, int i) {
                super.onProgressChanged(webView, i);
                if (i >= 10 && canInject) {
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code1));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code2));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code3));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code4));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code5));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code6));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code7));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code8));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code9));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code10));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code11));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code12));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code13));
                    mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code14));
                    canInject = false;
                }
                if (i == 100) {
                    canInject = true;
                }
          }
});

這個(gè)時(shí)候有人就要問了近顷,怎么注入這么多次生音,我也不想啊,這里有個(gè)坑的窒升,一次注入的代碼超過三行左右(分號(hào)結(jié)束為一行)吧缀遍,就會(huì)有幾率出現(xiàn)注入失敗,會(huì)造成所有js代碼都沒法注入進(jìn)去饱须,我就干脆直接一次注入一行代碼來跳出這個(gè)坑域醇,比如下面的js_sdk_code3就可以注入,雖然這個(gè)function內(nèi)部有好幾行代碼冤寿,但是整體來說也算一行代碼歹苦,這行代碼定義了這個(gè)function。然而我又試了督怜,在這個(gè)function里面再多加一行代碼就會(huì)注入失敗殴瘦,搞得現(xiàn)在我也不確定他失敗的零界點(diǎn)在哪里,反正盡量拆開注入吧号杠。

將要注入的js代碼拆開注入

細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了蚪腋,搞了這么多花里胡哨的,最關(guān)鍵的原生怎么來響應(yīng)js的調(diào)用還沒說明姨蟋,別急屉凯,下面上代碼

    //@JavascriptInterface的代碼應(yīng)該放在哪里不用我講了吧
    //通過與js交互的接口類來拿到做什么事,以及傳過來的JSON對象轉(zhuǎn)成的字符串
    @JavascriptInterface
    public void native_launchFunc(String data) {
        try {
            JSONObject jsonObject = new JSONObject(data);
            String funcName = jsonObject.getString("funcName");
            String dataStr = jsonObject.getString("data");
            switchName(funcName, dataStr);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
    
    private void switchName(String funcName, String dataStr) {
        if (funcName == null) {
            return;
        }
        switch (funcName) {
            case "login":
                x5WebViewActivity.get().login(data);
                break;
            case "xxx":
                x5WebViewActivity.get().xxx(data);
                break;
        }
    }

     //這里演示調(diào)用了login讓原生來登陸眼溶,等登陸成功之后悠砚,我們?nèi)フ{(diào)用js的匿名回調(diào),并傳入token
      JsonObject jsonObject = new JsonObject();
      jsonObject.addProperty("token", PreferencesHelper.getInstance().getToken());
      String js = "javascript:sdk_nativeCallback(\'login\',\'" + jsonObject + "\')";
      mWebView.loadUrl(js);

Android原生調(diào)用JS代碼也有兩種堂飞,一種是通過上面的loadUrl灌旧,一種是下面這種:

    String script = "sdk_nativeCallback(\'login\',\'" + jsonObject + "\')";
    mWebView.evaluateJavascript(script, responseJson -> {
         if (!TextUtils.isEmpty(responseJson)) {
              //拿到j(luò)s函數(shù)的返回值
         }
  });

區(qū)別就是一個(gè)能拿到j(luò)s函數(shù)的返回值,一個(gè)拿不到绰筛,這個(gè)根據(jù)自己的需求來選用

前端js調(diào)用原生傳入匿名回調(diào)的示例代碼:

//js代碼
 var fucker = {};
 fucker.name = "pdd";
 fucker.age = 18;
 App.login(fucker, function (data) {
       if (data.err) {
          alert(data.err);
       }
        alert(data.token);
 });

我們可以看到枢泰,前端給我們傳入的是對象和匿名回調(diào)函數(shù),匿名回調(diào)需要的參數(shù)依然是個(gè)對象铝噩,我們通過注入的SDK保存了這個(gè)回調(diào)函數(shù)衡蚂,并自己做了對象和字符串轉(zhuǎn)換,實(shí)際上Java代碼最終拿到和傳出去還都是字符串,我們通過這個(gè)sdk統(tǒng)一的進(jìn)行了轉(zhuǎn)換毛甲,前端js代碼那邊不用判斷手機(jī)是iPhone或者是Android年叮,統(tǒng)一發(fā)出和接受對象,傳入回調(diào)函數(shù)丽啡,能夠減少他們很多工作量谋右。

同學(xué)們,下課

對了补箍,因?yàn)锳ndroid版本不一致,webview的兼容性參差不齊啸蜜,選用了騰訊的X5內(nèi)核瀏覽器來加載坑雅,其中有個(gè)坑就是全屏播放視頻會(huì)有qq瀏覽器的廣告,這個(gè)可以通過代碼去掉衬横,也拿出來分享下吧:

 //去掉QQ瀏覽器廣告
 private void removeTbsAd() {
    getWindow().getDecorView().addOnLayoutChangeListener
            ((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                ArrayList<View> outView = new ArrayList<>();
                View decorView = getWindow().getDecorView();
                decorView.findViewsWithText(outView, "相關(guān)視頻", View.FIND_VIEWS_WITH_TEXT);
                decorView.findViewsWithText(outView, "QQ瀏覽器", View.FIND_VIEWS_WITH_TEXT);
                if (outView.size() > 0) {
                    outView.get(0).setVisibility(View.GONE);
                }
            });
 }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裹粤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜂林,更是在濱河造成了極大的恐慌遥诉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件噪叙,死亡現(xiàn)場離奇詭異矮锈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)睁蕾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門苞笨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人子眶,你說我怎么就攤上這事瀑凝。” “怎么了臭杰?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵粤咪,是天一觀的道長。 經(jīng)常有香客問我渴杆,道長寥枝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任将塑,我火速辦了婚禮脉顿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘点寥。我一直安慰自己艾疟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蔽莱,像睡著了一般弟疆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盗冷,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天怠苔,我揣著相機(jī)與錄音,去河邊找鬼仪糖。 笑死柑司,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锅劝。 我是一名探鬼主播攒驰,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼故爵!你這毒婦竟也來了玻粪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤诬垂,失蹤者是張志新(化名)和其女友劉穎劲室,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體结窘,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡很洋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晦鞋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹲缠。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖悠垛,靈堂內(nèi)的尸體忽然破棺而出线定,到底是詐尸還是另有隱情,我是刑警寧澤确买,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布斤讥,位于F島的核電站,受9級特大地震影響湾趾,放射性物質(zhì)發(fā)生泄漏芭商。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一搀缠、第九天 我趴在偏房一處隱蔽的房頂上張望铛楣。 院中可真熱鬧,春花似錦艺普、人聲如沸簸州。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岸浑。三九已至搏存,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矢洲,已是汗流浹背璧眠。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留读虏,地道東北人责静。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像掘譬,于是被迫代替她去往敵國和親泰演。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,522評論 25 707
  • 之前有寫的相關(guān)文章葱轩,一是太亂了,二是東西沒給全藐握,很難理解靴拱。應(yīng)讀者要求給出完整的安卓和前端h5交互的Demo:git...
    逆水行舟丶閱讀 6,392評論 10 9
  • 從小到大,買了那么多衣服猾普,卻從來學(xué)不會(huì)砍價(jià)袜炕。跟朋友談到都只能笑著說我不會(huì)砍價(jià)。說起來好像很呆萌善良的樣子初家。但作為一...
    天天天天天玉閱讀 437評論 0 1
  • 有關(guān)本章A Difference of Opinion的理解: 文中說的"全球變暖偎窘,冰川融化,是人類活動(dòng)溜在,尤其化石...
    sxrunn閱讀 119評論 0 1
  • 愛情掖肋, 只是一瞬間的感覺 仆葡。 如果你的愛情暫停留在曾經(jīng), 它只屬于那個(gè)時(shí)間志笼, 如果你的愛情停留在生命里沿盅, 它就會(huì)成...
    依曼說閱讀 257評論 0 0