App与Js交互(二)Android

目录

示例代码

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

前言

《App与Js交互(一)iOS》中我们详细的列举了iOS与JS的各种交互方式,那么Android端的交互又是怎样的呢?下面就来为大家一一介绍。
ps:本人iOS出身,Android学习时间不长,如果有BUG还请在下方评论中及时反馈,感谢非常。

Android系统中的交互

方案一,拦截跳转

  • 初始化:

    // WebView默认是不支持Android&JS通信的,要在WebView初始化的时候打开这个开关
    mWebView.getSettings().setJavaScriptEnabled(true);
    
  • 原生调用JS:
    Android调用JS的常规方法有两种,如下:

    • 方法 A:

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

      是的,你没有看错,就是渲染URL的方法,它也可以用来执行js代码,但是弊端就是执行完了这段代码后WebView上原有的内容有可能会被覆盖,并且拿不到JS方法的return值,所以一般不会使用这种方式。

    • 方法 B:

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

      这种方法会比方法A好很多,首先不会影响WebView原本渲染的内容,其次它还支持JS方法的返回值,所以在正常开发中更多时候用的是方法B。

  • JS调用原生:
    用过WebView的同学应该都知道有个东西叫做WebViewClient,这个东西就可以实现我们的需求——拦截跳转。

    // JS代码 ===========================
    // 登录
    window.location = 'app://login?account=13011112222&password=123456';
    // 登出
    window.location = 'app://share?title=分享的标题&desc=分享的描述'
    
    // Android代码 =======================
    private void setupWebView() {
        WebViewClient webViewClient = new WebViewClient() {
        // 老方法
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Uri uri = Uri.parse(url);
            // 如果拦截单的链接是app协议的就说明是我们需要处理的链接
            if (uri.getScheme().contentEquals("app")) {
                callNative(uri);
                // 返回true就是告诉WebView该链接不需要你处理了,已经被我“消费”了。
                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 调用原生方法时的特殊跳转链接
     * @param uri 特殊的跳转链接
     */
    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("执行登录操作,账号为:%s,密码为:%s", account, password));
        } else if (host.contentEquals("share")) {
            String title = uri.getQueryParameter("title");
            String desc = uri.getQueryParameter("desc");
            showNativeMessage(String.format("执行分享操作,title为:【%s】,desc为:【%s】", title, desc));
        }
    }
    

方案二,JavaScriptInterface

  • 初始化:

    // WebView默认是不支持Android&JS通信的,要在WebView初始化的时候打开这个开关
    mWebView.getSettings().setJavaScriptEnabled(true);
    
  • 原生调用JS:
    同方案一的原生调用JS

  • JS调用原生:
    我们可以暴露一个Java的Object给WebView供JS调用。什么意思?就是说JS可以调用我们Java对象的某些方法,这里说的某些方法就是@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调用的Java对象
      */
    class JsInterfaceLogic {
        private BaseFragment mFragment;
        public JsInterfaceLogic(BaseFragment mFragment) {
            this.mFragment = mFragment;
        }
    
        @JavascriptInterface
        public void login(String account, String password) {
            mFragment.showNativeMessage(String.format("执行登录操作,账号为:%s,密码为:%s", account, password));
        }
    
        @JavascriptInterface
        public void logout() {
            mFragment.showNativeMessage("执行【登出】操作");
        }
    
        @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原生是不支持这种方式的,我们需要依赖于一个三方库 —— JsBridge,这是一个很有名的库,具体有多牛逼这里也不做过多需求,百度一下你就知道。

  • 初始化代码:

    // JS初始化代码
    /**
     * 初始化jsbridge
     * @param readyCallback 初始化完成后的回调
     */
     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终端
         // 注册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)
             }
         }
         // 调用注册方法
         connectWebViewJavascriptBridge(function (bridge) {
             if (isAndroid) {
                 bridge.init(function (message, responseCallback) {
                     console.log('JS got a message', message);
                     responseCallback(data);
                 });
             }
              
             // 只有在这里注册过的方法,在原声代码里才能用callHandler的方式调用
             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("执行登录操作,账号为:%s、密码为:%s", user.getAccount(), user.getPassword()));
            }};
            function.onCallBack(response.toString());
        }
    });
    
  • 原生调JS

    // 使用callHandler的方式调用JS中已经注册过的方法
    mWeb.callHandler("jsbridge_getJsMessage", message, new CallBackFunction() {
        @Override
        public void onCallBack(String data) {
            showNativeMessage(String.format("原生调用JsBridge方法后,Js方法的返回值为:【%s】", data));
        }
    });
    
  • JS调用原生

    // 首先调用JSBridge初始化代码,完成后再设置其他
    initJsBridge(function () {
      $("#getOS").click(function () {
            // 通过JsBridge调用原生方法,写法固定,第一个参数时方法名,第二个参数时传入参数,第三个参数时响应回调
            window.WebViewJavascriptBridge.callHandler('getOS', null, function (response) {
            showResponse(response);
          });
      });
     });
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270