• 安卓中WebView和原生的通信示例Demo(含完整源码)


    一、前言

    所谓通信,说白了就是WebView和原生互相调用对方的函数、互相传值。WebView和原生通信的方式也很简单,不过最好是自己动手实现一下,才能够记得牢固。不管是面试还是笔试,都会经常考到。我也看过其他大佬的文章,不过有些地方没有给出必要的说明,让人看后还是不知道怎么实现,所以本文的最后会给出整个项目的源码,若有不清楚的地方,下载下来跑一遍就能够理解了。Demo的截图如下:
    在这里插入图片描述

    二、原生调用js

    方法1:通过WebView对象的loadUrl()方法

    loadUrl()方法不仅可以加载一个html网页,还可以调用js的方法,是不是很神奇呢!这里给出的例子是原生调用js的cat()方法,loadUrl()方式的缺点是不方便获取js方法的返回值,如果你给这个cat()方法返回一个值,你会发现html页面的body直接就展示这个返回值了,这显然不是你想要的结果吧。

    原生java代码

    mWebView.loadUrl("javascript:cat()");
    
    • 1

    html

    let flag1 = true
    function cat() {
                document.getElementById('div1').innerHTML = flag1 ? "小肥咪" : "大肥咪"
                flag1 = !flag1
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    方法2:通过WebView对象的evaluateJavascript()方法

    上面说了loadUrl()方式的缺点是不方便获取js方法的返回值,那么想要方便地获取js方法的返回值,可以使用evaluateJavascript()这个方法。

    原生java代码

     mWebView.evaluateJavascript("javascript:sayHello()", new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {//可以在这个回调方法里面拿到js方法的返回值
                            Log.d(TAG, "onReceiveValue: "+value);
                        }
                    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    html

    let flag2 = true
     function sayHello() {
                document.getElementById("div2").innerHTML = flag2 ? "喵喵喵喵叫" : "嗷嗷叫"
                flag2 = !flag2
                return "我是js方法的返回值"
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    三、js调用原生

    js调用原生的话,这里介绍3种方式。

    方法1:通过WebView对象的addJavascriptInterface()方法

    这种方法其实就是用了对象映射,使用起来也不复杂。我们需要写一个类,在里面写上自己的方法,再把@JavascriptInterface注解加到你的方法上面。addJavascriptInterface()这个方法接收两个参数,第一个参数直接new出来一个刚才你自定义的类的对象;addJavascriptInterface()方法的第二个参数是一个字符串,是可以自定义的,这里我直接自定义为"android",其实也就是js里面用到的,作为一个对象名。

    原生java代码

    //自定义一个类
    class MyNative{
            @JavascriptInterface
            public void getNative1(String who){
                Log.d(TAG, "getNative1: "+who);
                Toast.makeText(MainActivity.this, "js通过对象映射addJavascriptInterface的方式调用了原生的方法", Toast.LENGTH_SHORT).show();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    MyNative myNative = new MyNative();//不要使用匿名内部类放入addJavascriptInterface中
    //让js调用原生的方法
     mWebView.addJavascriptInterface(myNative,"android");//这里的android也可以写成别的字符串,待会js里面需要用到这个名字
    
    • 1
    • 2
    • 3

    html

    下面的android对应的就是原生java代码里面你取的对象名

    <button class="btn" onclick="window.android.getNative1('张三')">addJavascriptInterafcebutton>
    
    • 1

    方法2:通过重写WebViewClient对象的shouldOverrideUrlLoading()方法

    shouldOverrideUrlLoading()这个方法返回true,可以将url拦截(不打开新的网页)。在js里面调用的话,我们其实是需要写上一个url的。在url上可以拼接一些参数,传给原生使用。

    原生java代码

    mWebView.setWebViewClient(new WebViewClient(){
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {//js调用原生的方式二
                    Log.d(TAG, "shouldOverrideUrlLoading: ");
    
                    Uri url = request.getUrl();
                    if (url!=null){
                        String schemenName = url.getScheme();//约定好的协议名称,这里我约定成了myscheme,经测试,协议名需要完全为小写字母
                        if ("myscheme".equals(schemenName)){
                            String authority = url.getAuthority();//约定好的地址名
                            if ("myAddress".equals(authority)){
                                String userName = url.getQueryParameter("userName");//获取用户名参数
                                String password = url.getQueryParameter("password");//获取密码参数
                                Log.d(TAG, "shouldOverrideUrlLoading: 获取到js传过来的用户名userName为:"+userName+",密码为:"+password);
                                Toast.makeText(MainActivity.this, "通过重写shouldOverrideUrlLoading的方式让js调用原生的方法", Toast.LENGTH_SHORT).show();
                            }
                            return true;
                        }
                    }
                    return super.shouldOverrideUrlLoading(view,request);
                }
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    html

     <button class="btn" onclick="document.location = 'myscheme://myAddress?userName=张三&password=123456'">shouldOverrideUrlLoadingbutton>
    
    • 1

    方法3: 通过重写WebChromeClient对象的onJsAlert()、onJsConfirm()或者onJsPrompt()方法

    这种方式其实就是拦截js中的三种弹框,我觉得有点投机取巧,假设网页不是放在安卓中的WebView里面展示,直接从浏览器打开,那么弹框其实还是可以看到的。

    原生java代码

    mWebView.setWebChromeClient(new WebChromeClient(){
                @Override
                public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                    return super.onJsAlert(view, url, message, result);
                }
    
                @Override
                public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                    return super.onJsConfirm(view, url, message, result);
                }
    
                @Override
                public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                    Log.d(TAG, "onJsPrompt: ");
                    Uri uri = Uri.parse(message);
                    if (uri!=null){
                        String schemeName = uri.getScheme();//自定义的协议名称
                        if("myscheme".equals(schemeName)){
                            String authority = uri.getAuthority();
                            if ("myAddress".equals(authority)){//自定义的地址名
                                String userName = uri.getQueryParameter("userName");//获取js传过来的参数userName
                                String password = uri.getQueryParameter("password"); //获取js传过来的password
                                Toast.makeText(MainActivity.this, "通过拦截prompt弹框的方式让js调用原生的方法,原生获取到的userName为:"+userName+",password为:"+password, Toast.LENGTH_SHORT).show();
                            }
                            result.cancel();//这行要加上,不然onJsPrompt只会被调用一次,而且后续的js方法也会失效
                            return true;//返回true表示拦截弹框,只要符合约定的协议,就将js中的prompt弹框拦截,不让其弹出来
                        }
                    }
                    return super.onJsPrompt(view,url,message,defaultValue,result);
                }
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    html

    <button class="btn" onclick="showPromptDialog()">onJsPromptbutton>
    
    • 1
    function showPromptDialog() {
                prompt("myscheme://myAddress?userName=张三&password=123456")
            }
    
    • 1
    • 2
    • 3

    四、代码汇总

    看了上面零零散散的代码,你是不是还不明白具体怎么调用?没关系,我相信一次性粘贴完所有的代码,你可以很轻松地理解。

    4.1 MainActivity.java代码

    package com.example.webviewtest;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.net.Uri;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.webkit.JavascriptInterface;
    import android.webkit.JsPromptResult;
    import android.webkit.JsResult;
    import android.webkit.ValueCallback;
    import android.webkit.WebChromeClient;
    import android.webkit.WebResourceRequest;
    import android.webkit.WebSettings;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    import android.widget.Button;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
        private WebView mWebView;
        private Button loadUrlBtn;
        private Button evaluateBtn;
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initViews();
            initEvents();
        }
    
        private void initViews() {
            mWebView=findViewById(R.id.webView);
            mWebView.loadUrl("file:///android_asset/index.html");
            mWebView.setWebViewClient(new WebViewClient(){
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {//js调用原生的方式二
                    Log.d(TAG, "shouldOverrideUrlLoading: ");
    
                    Uri url = request.getUrl();
                    if (url!=null){
                        String schemenName = url.getScheme();//约定好的协议名称,这里我约定成了myscheme,经测试,协议名需要完全为小写字母
                        if ("myscheme".equals(schemenName)){
                            String authority = url.getAuthority();//约定好的地址名
                            if ("myAddress".equals(authority)){
                                String userName = url.getQueryParameter("userName");//获取用户名参数
                                String password = url.getQueryParameter("password");//获取密码参数
                                Log.d(TAG, "shouldOverrideUrlLoading: 获取到js传过来的用户名userName为:"+userName+",密码为:"+password);
                                Toast.makeText(MainActivity.this, "通过重写shouldOverrideUrlLoading的方式让js调用原生的方法", Toast.LENGTH_SHORT).show();
                            }
                            return true;
                        }
                    }
                    return super.shouldOverrideUrlLoading(view,request);
                }
            });
            mWebView.setWebChromeClient(new WebChromeClient(){
                @Override
                public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                    return super.onJsAlert(view, url, message, result);
                }
    
                @Override
                public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                    return super.onJsConfirm(view, url, message, result);
                }
    
                @Override
                public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                    Log.d(TAG, "onJsPrompt: ");
                    Uri uri = Uri.parse(message);
                    if (uri!=null){
                        String schemeName = uri.getScheme();//自定义的协议名称
                        if("myscheme".equals(schemeName)){
                            String authority = uri.getAuthority();
                            if ("myAddress".equals(authority)){//自定义的地址名
                                String userName = uri.getQueryParameter("userName");//获取js传过来的参数userName
                                String password = uri.getQueryParameter("password"); //获取js传过来的password
                                Toast.makeText(MainActivity.this, "通过拦截prompt弹框的方式让js调用原生的方法,原生获取到的userName为:"+userName+",password为:"+password, Toast.LENGTH_SHORT).show();
                            }
                            result.cancel();//这行要加上,不然onJsPrompt只会被调用一次,而且后续的js方法也会失效
                            return true;//返回true表示拦截弹框,只要符合约定的协议,就将js中的prompt弹框拦截,不让其弹出来
                        }
                    }
                    return super.onJsPrompt(view,url,message,defaultValue,result);
                }
            });
            WebSettings settings = mWebView.getSettings();
            settings.setJavaScriptEnabled(true);
            settings.setCacheMode(WebSettings.LOAD_NO_CACHE);//不使用缓存
            loadUrlBtn=findViewById(R.id.loadUrlBtn);
            evaluateBtn=findViewById(R.id.evaluateBtn);
        }
    
        private void initEvents(){
    
            //原生调用js的第一种方式:调用WebView的loadUrl()方法,会使得页面重新刷新一次,不方便获取js函数的返回值
            loadUrlBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mWebView.loadUrl("javascript:cat()");
                }
            });
    
            //原生调用js的第二种方式,调用WebView的evaluateJavascript()方法
            evaluateBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mWebView.evaluateJavascript("javascript:sayHello()", new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {//可以在这个回调方法里面拿到js方法的返回值
                            Log.d(TAG, "onReceiveValue: "+value);
                        }
                    });
                }
            });
    
    
            MyNative myNative = new MyNative();//不要使用匿名内部类放入addJavascriptInterface中
            //让js调用原生的方法
            mWebView.addJavascriptInterface(myNative,"android");
        }
    
    
        class MyNative{
            @JavascriptInterface
            public void getNative1(String who){
                Log.d(TAG, "getNative1: "+who);
                Toast.makeText(MainActivity.this, "js通过对象映射addJavascriptInterface的方式调用了原生的方法", Toast.LENGTH_SHORT).show();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    4.2 Html代码

    DOCTYPE html>
    
    <head>
        <title>首页title>
        <script>
            let flag1 = true
            let flag2 = true
    
            //让原生调用的方法1
            function cat() {
                document.getElementById('div1').innerHTML = flag1 ? "小肥咪" : "大肥咪"
                flag1 = !flag1
            }
    
            //让原生调用的方法2
            function sayHello() {
                document.getElementById("div2").innerHTML = flag2 ? "喵喵喵喵叫" : "嗷嗷叫"
                flag2 = !flag2
                return "我是js方法的返回值"
            }
    
    
            function showPromptDialog() {
                prompt("myscheme://myAddress?userName=张三&password=123456")
            }
        script>
    
        <style>
            .body {
                background-color: #c7edcc;
                margin: 0;
            }
    
            .container {
                height: 100vh;
                display: flex;
                flex-direction: column;
                margin-left: 6.0185vw;
                margin-right: 6.0185vw;
            }
    
            h1 {
                text-align: center;
            }
    
            .btnDiv {
                margin-top: 50px;
                display: flex;
                flex-direction: column;
            }
    
            .btn {
                height: 40px;
                margin-bottom: 10px;
            }
        style>
    head>
    
    <body class="body">
        <div class="container">
            <h1>我是WebViewh1>
            <div id="div1">从前有只猫div>
            <div id="div2">奶牛猫好乖啊啊div>
            <div class="btnDiv">
                <button class="btn" onclick="window.android.getNative1('张三')">addJavascriptInterafcebutton>
                <button class="btn" onclick="document.location = 'myscheme://myAddress?userName=张三&password=123456'">shouldOverrideUrlLoadingbutton>
                <button class="btn" onclick="showPromptDialog()">onJsPromptbutton>
            div>
        div>
    body>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    五、 总结

    本篇文章介绍的通信方式共有5种,其中原生调用js有2种,js调用原生有3种。
    整个项目的源码已经放到码云上了,点我跳转到码云
    你可以将源码下载下来,在AS上跑一遍。

  • 相关阅读:
    14_Nginx正则表达式_动静分离配置
    [RK3568][Android11]内核Oops日志分析
    Elasticsearch节点、副本、分片规划
    安全加固:Eureka服务实例安全令牌配置全解析
    测试老鸟告诉你,直击准备2024年初/高级测试技术面试...
    添加nginx中的stream模块
    Probuf定义详解
    supervisor守护python进程报FATAL错 spawn error
    金融机构系统性风险分析(Domestic+MES模型)200701-202012
    MSQL系列(八) Mysql实战-SQL存储引擎
  • 原文地址:https://blog.csdn.net/Deep_rooted/article/details/127839436