Android和JS的交互

现在很多App为了降低开发成本和维护成本,都会内置Web网页,在安卓端是通过WebView来实现的,这就要求我们开发人员具备Android和JS交互的开发能力。

我将通过这篇文章来记录我的学习历程,在学习过程中我还是通过一个demo来加深理解,下面先贴上效果图:


安卓与js的交互

效果图分别展示了js调用安卓方法的三种效果以及安卓调用js方法的两种效果,下面我把这五个方法拆开来单个学习理解。

一、先介绍一下安卓调用js方法的两种实现方式 :

1、通过webview对象调用方法loadUrl(),这样可以实现安卓调用js的方法;
2、通过webview对象调用方法evaluateJavascript(),实现安卓调用js方法。

下面从代码来具体看这两种方法的实现过程:
先贴上html文件:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>安卓与js交互</title>

    <script>

        function callJS1(){
            alert("安卓调用JS中的callJs方法");
        }

    </script>
</head>

<body>
<p>这是一个webview </p>
</body>
</html>

这里为了代码看起来清晰明了,只放了需要的部分。代码中只放了一个功能供安卓调用。下面是安卓端的代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private WebView mWebView;
    private Button mBtn1,mBtn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        configWebview();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void configWebview() {
        WebSettings settings = mWebView.getSettings();
        //允许与js交互
        settings.setJavaScriptEnabled(true);
        //允许js弹窗
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        //加载html文件
        mWebView.loadUrl("file:///android_asset/javascript.html");


        mWebView.setWebChromeClient(new WebChromeClient(){

            //这里的参数message就是html文件中callJS1()方法下alert中的文本
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                //创建一个弹窗
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setTitle("安卓与JS交互");
                builder.setMessage(message);
                builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                builder.setCancelable(false);
                builder.create().show();
                return true;
            }
        }

    private void initView() {
        mWebView = findViewById(R.id.webview);
        mBtn1 = findViewById(R.id.btn1);
        mBtn2 = findViewById(R.id.btn2);

        mBtn1.setOnClickListener(this);
        mBtn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()){
            case R.id.btn1:
                //通过loadUrl()方法调用JS的方法
                getMethodByLoadUrl();
                break;
        }
    }

    private void getMethodByLoadUrl() {
        mWebView.post(new Runnable() {
            @Override
            public void run() {
                //这种方法调用js的方法没有返回值
                mWebView.loadUrl("javascript:callJS1()");
            }
        });
    }
}

安卓和js交互的中间桥梁就是安卓中的控件WebView,其对象一边绑定html文件,一边和安卓系统交互,这样就连接起了安卓和js。

通过WebView的对象调用方法getSettings()获取WebSettings对象,从对象名称就能看出这个类是来设置WebView一些属性的:

WebSettings settings = mWebView.getSettings();

拿到WebSettings对象后,通过以下代码设置安卓系统和js交互的权限:

settings.setJavaScriptEnabled(true);

webview中经常有一些弹窗,我们可以通过以下代码来设置js弹窗的权限:

settings.setJavaScriptCanOpenWindowsAutomatically(true);

WebView是一个View,我们就可以加载之前写好的html文件,WebView可以自动完成对页面的渲染:

mWebView.loadUrl("file:///android_asset/javascript.html");

我这里是加载的工程下assets文件下的一个本地html文件,在实际开发中将url更换成远程的就可以。

在这个方法中因为没有去获取返回值,所以设置了一个弹窗来检验安卓调用js方法的结果,所以在设置WebView属性的时候设置了同意js弹窗的权限,即settings.setJavaScriptCanOpenWindowsAutomatically(true);,这里WebView只是一个View,一扇窗户(载体),里面的窗花(弹窗)需要我们自己去定义,WebView给我们提供了方法去处理js中弹窗,就是setWebChromeClient()方法,创建弹窗的方法就是原生安卓的方法,下面贴上代码:

        mWebView.setWebChromeClient(new WebChromeClient(){

            //这里的参数message就是html文件中callJS1()方法下alert中的文本
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                //创建一个弹窗
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setTitle("安卓与JS交互");
                builder.setMessage(message);
                builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                builder.setCancelable(false);
                builder.create().show();
                return true;
            }
        }

好了,以上就是安卓调用js方法的第一种方式,用WebView提供的方法loadUrl()实现。
由于第一种方法存在一个缺点就是,安卓无法高效的获取到返回值,所以在Android4.4以后安卓系统提供了第二种方法,就是用WebView提供的方法evaluateJavaScript()来实现安卓调用js的方法,这种方法能直接获取到返回值,相较于第一种比较高效,下面来看看第二种方法的实现:

html部分代码:

function callJS2(){
    return "js返回的数据";
}

这部分js功能中,我只返回了一个值,目的就是校验第二种方法获取返回值。

安卓端部分代码:

mWebView.evaluateJavascript("javascript:callJS2()", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                //js返回值处理
                tipString(value);
            }
});

从代码中看出,第二种方法传入了两个参数,第一个参数对应js中的方法,第二个参数是一个回调对象,并且重写了一个方法,方法中包含一个String类型参数,这个参数就是js方法中返回的值。

上面学习了安卓调用js方法的两种方式,下面做个总结:

第一种方法比较方便简洁,但是获取返回值比较繁琐,需要js再去调用安卓的方法将返回值传过来,这样效率很低;
第二种方法能够获取到js的返回值,但是只支持Android4.4以上的设备,向下兼容性比较差。

综于以上总结,在实际开发中 ,为了兼容各个版本,我们可以在Android4.4以前的系统使用方法一,在4.4以上的用方法二。


二、学习完了安卓调用js的方法,下面来学习js调用安卓的方法,先简单介绍实现js调用安卓方法的三种方式:

`1、通过WebView对象调用方法addJavascriptInterface()将安卓对象映射成一个字符串,这样就可以在js中通过这个字符串调用安卓中的方法;
2、安卓端通过WebViewClient的shouldOverrideUrlLoading ()方法回调拦截url,然后解析url的协议,判断是否和预先约定好的一致,做出相应的操作;
3、安卓端通过WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt()消息.

下面来逐个学习,先贴上html文件 :

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>安卓与js交互</title>

    <script>
        function callAndroid1(){
            android.test("js调用了安卓的test方法");
        }

        function callAndroid2(){
            document.location="js://webview?name=张三&age=24";
        }

        function callAndroid3(){
            var result=prompt("js://android?name=李四&age=24");
            alert(result);
        }

    </script>
</head>


<body>
<p>这是一个webview </p>

<button type="button" id="btn1" onclick="callAndroid1()">点击调用安卓的方法1</button>
<button type="button" id="btn2" onclick="callAndroid2()">点击调用安卓的方法2</button>
<button type="button" id="btn3" onclick="callAndroid3()">点击调用安卓的方法3</button>
</body>
</html>

代码中定义了三个js方法,分别是callAndroid1()、callAndroid2()和callAndroid3(),还有三个按钮,点击的时候分别去调用三个方法,下面看看安卓端的代码是怎么实现js和安卓的交互的:
首先,第一个方法是映射一个安卓对象,所以需要先创建一个类,里面包含供js调用的方法:

public class Android {
    private Context context;

    Android(Context context) {
        this.context = context;
    }

    @JavascriptInterface
    public void test(String msg){
        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
    }
}

建好了类,定义好了供js调用的方法后,通过如下代码完成安卓对象的映射:

mWebView.addJavascriptInterface(new Android(this),"android");

以上代码将安卓对象映射成android,所以在html文件中就有android.test("js调用了安卓的test方法");,到了这就完成了js对安卓方法的调用。

不过这里需要注意一点:这种方法在Android4.2以前存在漏洞,就是映射之后这个类中所有的方法都会暴漏在js中,当有些方法我们是不希望js能够调用到的时候,这个时候就存在安全威胁,所以在4.2以后 ,安卓系统让能够被js调用的方法加上注释@JavascriptInterface

学习完了第一种方式,下面学习第二种方式,html文件和第一种差不多,上面已经给出,这里直接贴上安卓端的处理部分代码:

        mWebView.setWebViewClient(new WebViewClient(){

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                Uri uri = Uri.parse(url);
                if (uri.getScheme() != null && uri.getScheme().equals("js")) {
                    if (uri.getAuthority() != null && uri.getAuthority().equals("webview")) {
                        Set<String> names = uri.getQueryParameterNames();
//                        tip(names);
                        //调用Android中的方法
                        tipString(uri.getQueryParameter("name"));
                    }
                    //处理完了记得return  不然会出错
                    return true;
                }
                return super.shouldOverrideUrlLoading(view, url);
            }
        });

代码中WebViewClient通过shouldOverrideUrlLoading拦截url,并转换成uri,然后获取url的协议,即uri.getScheme()uri.getAuthority(),然后判断是否和事先约定好的一致,当一致的时候再来做出相应的操作,在这里我是将拼接在url的参数取出来并显示在屏幕上。

上面就是第二种方式的实现过程,他不存在第一种方法那样的漏洞,但是也存在一个缺点 ,因为这种方法是通过拦截url,然后判断协议的方式来调用安卓方法的,在js将url传递过来后,安卓就失去了和js的通信,所以它也存在和安卓通过loadUrl()调用js方法一样的缺点,就是无法高效获取返回值,要想取得返回值,只能让安卓再去调用js的方法将返回值传递给js,这样效率明显不理想,所以在一些不需要返回值的交互场景中推荐这种方法 。

接下来学习第三种方式,这里只是学习了输入框,另外的警告框和确认框差不多的。
安卓端的处理部分代码:

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
       Uri uri = Uri.parse(message);
       if (uri.getScheme() != null && uri.getScheme().equals("js")) {
             if (uri.getAuthority() != null && uri.getAuthority().equals("android")) {
                  //通过这个方法将安卓中的数据返回到js中去
                  result.confirm("JS调用安卓的方法");
             }
             //处理完了记得return  不然会出错
             return true;
       }
       return super.onJsPrompt(view, url, message, defaultValue, result);
}

这种方法和第二种差不多,都是判断url的协议,然后根据判断的结果做出相应的操作,它和第二种方法的区别就是第三种方法js能够获取到返回值,因为js中警告框、确认框和输入框这三种弹框都有返回值,并且都能监听到选择或是输入的结果。这也使得第三种方法能满足大多数场合。

好了 ,以上就是对js调用安卓方法三种实现方式的学习,这里总结以下:

第一种方法方便简洁,但在Android4.2以前存在漏洞,4.2以后的场合可以 考虑这种方法;
第二种方法不存在漏洞了,但是url的协议判断过程相对来说复杂,而且获取返回值也比较复杂,不需要返回值的场合可以考虑用这种方法;
第三种方法不存在漏洞,判断url协议和第二种方法一样,但这种方法能获取到返回值,所以第三种方法能满足大多数场合。

以上就是本人对Android和JS交互的学习,希望发现文中有不当之处的朋友们能够指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容