App與Js交互(二)Android

目錄

示例代碼

Demo: https://github.com/gwpp/jsinterface

前言

《App與Js交互(一)iOS》中我們?cè)敿?xì)的列舉了iOS與JS的各種交互方式嫩实,那么Android端的交互又是怎樣的呢?下面就來為大家一一介紹。
ps:本人iOS出身,Android學(xué)習(xí)時(shí)間不長(zhǎng),如果有BUG還請(qǐng)?jiān)谙路皆u(píng)論中及時(shí)反饋坛吁,感謝非常。

Android系統(tǒng)中的交互

方案一铐尚,攔截跳轉(zhuǎn)

  • 初始化:

    // WebView默認(rèn)是不支持Android&JS通信的拨脉,要在WebView初始化的時(shí)候打開這個(gè)開關(guān)
    mWebView.getSettings().setJavaScriptEnabled(true);
    
  • 原生調(diào)用JS:
    Android調(diào)用JS的常規(guī)方法有兩種,如下:

    • 方法 A:

      mWebView.loadUrl("javascript:alert('1234')");
      

      是的宣增,你沒有看錯(cuò)玫膀,就是渲染URL的方法,它也可以用來執(zhí)行js代碼爹脾,但是弊端就是執(zhí)行完了這段代碼后WebView上原有的內(nèi)容有可能會(huì)被覆蓋帖旨,并且拿不到JS方法的return值,所以一般不會(huì)使用這種方式灵妨。

    • 方法 B:

      String js = "getCookieWithKey('username')";
      mWebView.evaluateJavascript(js, new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String s) {
          // 這里可以處理被調(diào)用js方法的return
          showNativeMessage("調(diào)用JS方法后得到的返回值是:" + s);
        }
      });
      

      這種方法會(huì)比方法A好很多解阅,首先不會(huì)影響WebView原本渲染的內(nèi)容,其次它還支持JS方法的返回值泌霍,所以在正常開發(fā)中更多時(shí)候用的是方法B货抄。

  • JS調(diào)用原生:
    用過WebView的同學(xué)應(yīng)該都知道有個(gè)東西叫做WebViewClient,這個(gè)東西就可以實(shí)現(xiàn)我們的需求——攔截跳轉(zhuǎn)。

    // JS代碼 ===========================
    // 登錄
    window.location = 'app://login?account=13011112222&password=123456';
    // 登出
    window.location = 'app://share?title=分享的標(biāo)題&desc=分享的描述'
    
    // Android代碼 =======================
    private void setupWebView() {
        WebViewClient webViewClient = new WebViewClient() {
        // 老方法
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Uri uri = Uri.parse(url);
            // 如果攔截單的鏈接是app協(xié)議的就說明是我們需要處理的鏈接
            if (uri.getScheme().contentEquals("app")) {
                callNative(uri);
                // 返回true就是告訴WebView該鏈接不需要你處理了蟹地,已經(jīng)被我“消費(fèi)”了积暖。
                return true;
            }
            return super.shouldOverrideUrlLoading(view, url);
        }
    
        // 新方法,API21之后支持
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            if (Build.VERSION.SDK_INT < 21) {
                return super.shouldOverrideUrlLoading(view, request);
            }
            Uri uri = request.getUrl();
            if (uri.getScheme().contentEquals("app")) {
                callNative(uri);
                return true;
            }
            return super.shouldOverrideUrlLoading(view, request);
        }
      };
      mWebView.setWebViewClient(webViewClient);
    }
    
    /**
     * js 調(diào)用原生方法時(shí)的特殊跳轉(zhuǎn)鏈接
     * @param uri 特殊的跳轉(zhuǎn)鏈接
     */
    private void callNative(Uri uri){
        String host = uri.getHost();
        if (host.contentEquals("login")) {
            String account = uri.getQueryParameter("account");
            String password = uri.getQueryParameter("password");
            showNativeMessage(String.format("執(zhí)行登錄操作怪与,賬號(hào)為:%s夺刑,密碼為:%s", account, password));
        } else if (host.contentEquals("share")) {
            String title = uri.getQueryParameter("title");
            String desc = uri.getQueryParameter("desc");
            showNativeMessage(String.format("執(zhí)行分享操作,title為:【%s】分别,desc為:【%s】", title, desc));
        }
    }
    

方案二遍愿,JavaScriptInterface

  • 初始化:

    // WebView默認(rèn)是不支持Android&JS通信的,要在WebView初始化的時(shí)候打開這個(gè)開關(guān)
    mWebView.getSettings().setJavaScriptEnabled(true);
    
  • 原生調(diào)用JS:
    同方案一的原生調(diào)用JS

  • JS調(diào)用原生:
    我們可以暴露一個(gè)Java的Object給WebView供JS調(diào)用茎杂。什么意思错览?就是說JS可以調(diào)用我們Java對(duì)象的某些方法,這里說的某些方法就是@JavascriptInterface注解修飾的方法煌往,示例如下:

    // JS代碼 =========================
    // 登錄
    app.login("13011112222", "123456");
    // 登出
    app.logout();
    // 獲取用戶信息
    var info = app.getLoginUser();
    showResponse(info);
    
    // Android代碼 =====================
    private void setupWebView() {
        mWebView.addJavascriptInterface(new JsInterfaceLogic(this), "app");
    }
    
    /**
      *  暴露出去給JS調(diào)用的Java對(duì)象
      */
    class JsInterfaceLogic {
        private BaseFragment mFragment;
        public JsInterfaceLogic(BaseFragment mFragment) {
            this.mFragment = mFragment;
        }
    
        @JavascriptInterface
        public void login(String account, String password) {
            mFragment.showNativeMessage(String.format("執(zhí)行登錄操作,賬號(hào)為:%s轧邪,密碼為:%s", account, password));
        }
    
        @JavascriptInterface
        public void logout() {
            mFragment.showNativeMessage("執(zhí)行【登出】操作");
        }
    
        @JavascriptInterface
        public String getLoginUser() {
            return new JSONObject(new HashMap(4) {{
                put("user_id", 666);
                put("username", "你就說6不6");
                put("sex", "未知");
                put("isStudent", false);
            }}).toString();
        }
    }
    

方案三刽脖,JSBridge

  • 說明:Android原生是不支持這種方式的,我們需要依賴于一個(gè)三方庫 —— JsBridge忌愚,這是一個(gè)很有名的庫曲管,具體有多牛逼這里也不做過多需求,百度一下你就知道硕糊。

  • 初始化代碼:

    // JS初始化代碼
    /**
     * 初始化jsbridge
     * @param readyCallback 初始化完成后的回調(diào)
     */
     function initJsBridge(readyCallback) {
         var u = navigator.userAgent;
         var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android終端
         var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端
         // 注冊(cè)jsbridge
         function connectWebViewJavascriptBridge(callback) {
             if (isAndroid) {
                 if (window.WebViewJavascriptBridge) {
                     callback(WebViewJavascriptBridge)
                 } else {
                     document.addEventListener(
                         'WebViewJavascriptBridgeReady'
                         , function () {
                             callback(WebViewJavascriptBridge)
                         },
                         false
                     );
                 }
                 return;
             }
             if (isiOS) {
                 if (window.WebViewJavascriptBridge) {
                     return callback(WebViewJavascriptBridge);
                 }
                 if (window.WVJBCallbacks) {
                     return window.WVJBCallbacks.push(callback);
                 }
                 window.WVJBCallbacks = [callback];
                 var WVJBIframe = document.createElement('iframe');
                 WVJBIframe.style.display = 'none';
                 WVJBIframe.src = 'https://__bridge_loaded__';
                 document.documentElement.appendChild(WVJBIframe);
                 setTimeout(function () {
                     document.documentElement.removeChild(WVJBIframe)
                 }, 0)
             }
         }
         // 調(diào)用注冊(cè)方法
         connectWebViewJavascriptBridge(function (bridge) {
             if (isAndroid) {
                 bridge.init(function (message, responseCallback) {
                     console.log('JS got a message', message);
                     responseCallback(data);
                 });
             }
              
             // 只有在這里注冊(cè)過的方法院水,在原聲代碼里才能用callHandler的方式調(diào)用
             bridge.registerHandler('jsbridge_showMessage', function (data, responseCallback) {
                 showResponse(data);
             });
             bridge.registerHandler('jsbridge_getJsMessage', function (data, responseCallback) {
                  showResponse(data);
                  responseCallback('native 傳過來的是:' + data);
              });
    
              readyCallback();
          });
      }
    
    // Android初始化代碼
    mWeb.registerHandler("getOS", new BridgeHandler() {
        @Override
        public void handler(String data, CallBackFunction function) {
            HashMap response = new HashMap(){{
                put("error", 0);
                put("message", "");
                put("data", new HashMap(){{
                    put("os", "android");
                }});
            }};
            function.onCallBack(response.toString());
        }
    });
    mWeb.registerHandler("login", new BridgeHandler() {
        @Override
        public void handler(String data, CallBackFunction function) {
            Gson gson = new Gson();
            final User user = gson.fromJson(data, User.class);
            HashMap response = new HashMap(){{
                put("error", 0);
                put("message", "");
                put("data", String.format("執(zhí)行登錄操作,賬號(hào)為:%s简十、密碼為:%s", user.getAccount(), user.getPassword()));
            }};
            function.onCallBack(response.toString());
        }
    });
    
  • 原生調(diào)JS

    // 使用callHandler的方式調(diào)用JS中已經(jīng)注冊(cè)過的方法
    mWeb.callHandler("jsbridge_getJsMessage", message, new CallBackFunction() {
        @Override
        public void onCallBack(String data) {
            showNativeMessage(String.format("原生調(diào)用JsBridge方法后檬某,Js方法的返回值為:【%s】", data));
        }
    });
    
  • JS調(diào)用原生

    // 首先調(diào)用JSBridge初始化代碼,完成后再設(shè)置其他
    initJsBridge(function () {
      $("#getOS").click(function () {
            // 通過JsBridge調(diào)用原生方法螟蝙,寫法固定恢恼,第一個(gè)參數(shù)時(shí)方法名,第二個(gè)參數(shù)時(shí)傳入?yún)?shù)胰默,第三個(gè)參數(shù)時(shí)響應(yīng)回調(diào)
            window.WebViewJavascriptBridge.callHandler('getOS', null, function (response) {
            showResponse(response);
          });
      });
     });
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末场斑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牵署,更是在濱河造成了極大的恐慌漏隐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奴迅,死亡現(xiàn)場(chǎng)離奇詭異青责,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門爽柒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吴菠,“玉大人,你說我怎么就攤上這事浩村∽隹” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵心墅,是天一觀的道長(zhǎng)酿矢。 經(jīng)常有香客問我,道長(zhǎng)怎燥,這世上最難降的妖魔是什么瘫筐? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮铐姚,結(jié)果婚禮上策肝,老公的妹妹穿的比我還像新娘。我一直安慰自己隐绵,他們只是感情好之众,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著依许,像睡著了一般棺禾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上峭跳,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天膘婶,我揣著相機(jī)與錄音,去河邊找鬼蛀醉。 笑死悬襟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的滞欠。 我是一名探鬼主播古胆,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼筛璧!你這毒婦竟也來了逸绎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤夭谤,失蹤者是張志新(化名)和其女友劉穎棺牧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朗儒,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颊乘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年参淹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏悄。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浙值,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出檩小,到底是詐尸還是另有隱情开呐,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布规求,位于F島的核電站筐付,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏阻肿。R本人自食惡果不足惜瓦戚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丛塌。 院中可真熱鬧较解,春花似錦、人聲如沸赴邻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乍楚。三九已至,卻和暖如春届慈,著一層夾襖步出監(jiān)牢的瞬間徒溪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工金顿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留臊泌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓揍拆,卻偏偏與公主長(zhǎng)得像渠概,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫂拴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353