WebView深度学习(一)之WebView的基本使用以及Android和js的交互

简介: 前言:随着市场需求的不断变化,原生安卓已经无法满足客户的需要了,现在很多app都在使用Android和h5的交互实现某些功能,比如商品详情页,文章详情页面,商品点评页面,还有某些复杂的展示页面等等,设置登陆页面都有可能是和js交互做到的。

前言:随着市场需求的不断变化,原生安卓已经无法满足客户的需要了,现在很多app都在使用Android和h5的交互实现某些功能,比如商品详情页,文章详情页面,商品点评页面,还有某些复杂的展示页面等等,设置登陆页面都有可能是和js交互做到的。通过交互可以很快速的达到效果,原生的安卓去做的话就会很麻烦。今天我就简单讲一下使用WebView做到js代码和安卓的交互,通过一个小demo教你学会js和Android的交互。

首先来看看这篇博客要讲解内容的大纲(这个图是我自己画的,网上找不到的)
WebView详解.png

目录:
一、WebView的基本使用
二、安卓通过WebView和js交互
三、WebView的那些坑
四、WebView的内存泄漏怎么办?
五、WebView的使用漏洞 及其修复方式
六、WebView 的缓存机制 & 资源预加载方案

由于内容太多,分三篇讲解:


⇒ 一、WebView的基本使用

WebView是一个基于webkit引擎、展现web页面的控件。Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。
  WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载(直接使用html文件(网络上或本地assets中)作布局)、渲染Wb页面、页面交互(和js交互)进行强大的处理。

(一)常用方法

  • (1) WebView的状态
//激活WebView为活跃状态,能正常执行网页的响应
webView.onResume() ;

//当页面被失去焦点被切换到后台不可见状态,需要执行onPause
//通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.onPause();

//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview
//它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
webView.pauseTimers()

//恢复pauseTimers状态
webView.resumeTimers();

//销毁Webview
//在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview
//但是注意:webview调用destory时,webview仍绑定在Activity上
//这是由于自定义webview构建时传入了该Activity的context对象
//因此需要先从父容器中移除webview,然后再销毁webview:
rootLayout.removeView(webView); 
webView.destroy();
  • (2) 关于前进 / 后退网页
//是否可以后退
Webview.canGoBack() 
//后退网页
Webview.goBack()

//是否可以前进                     
Webview.canGoForward()
//前进网页
Webview.goForward()

//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps) 

常见用法:Back键控制网页后退

问题:在不做任何处理前提下 ,浏览网页时点击系统的“Back”键,整个 Browser 会调用 finish()而结束自身
目标:点击返回后,是网页回退而不是推出浏览器

解决方案:在当前Activity中处理并消费掉该 Back 事件
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { 
    mWebView.goBack();
    return true;
    }
    return super.onKeyDown(keyCode, event);
}
  • (3) 清除缓存数据
//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
Webview.clearCache(true);

//清除当前webview访问的历史记录
//只会webview访问历史记录里的所有记录除了当前访问记录
Webview.clearHistory();

//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
Webview.clearFormData();

(二)常用类

  • (1) WebSettings类(主要作用是:对WebView进行配置和管理)
//生成一个WebView组件(两种方式)
//方式1:直接在在Activity中生成
WebView webView = new WebView(this)
//方法2:在Activity的layout文件里添加webview控件:
WebView webview = (WebView) findViewById(R.id.webView1);

//声明WebSettings子类
WebSettings webSettings = webView.getSettings();

//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);  

//支持插件
webSettings.setPluginsEnabled(true); 

//设置自适应屏幕,两者合用(下面这两个方法合用)
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小 
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小

//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存 
webSettings.setAllowFileAccess(true); //设置可以访问文件 
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口 
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式

//设置WebView缓存(当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹,请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下)

//优先使用缓存: 
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 
    //缓存模式如下:
    //LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
    //LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
    //LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
    //LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

//不使用缓存: 
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

//结合使用(离线加载)(注意:每个 Application 只调用一次 WebSettings.setAppCachePath(),WebSettings.setAppCacheMaxSize())

if (NetStatusUtil.isConnected(getApplicationContext())) {//判断网络是否连接
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。
} else {
    webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
}

webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能
webSettings.setDatabaseEnabled(true);   //开启 database storage API 功能
webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能

String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); //设置  Application Caches 缓存目录
  • (2) WebViewClient类(主要作用是:处理各种通知 & 请求事件)
//步骤1. 定义Webview组件
Webview webview = (WebView) findViewById(R.id.webView1);

//步骤2. 选择加载方式
//方式a. 加载一个网页:
webView.loadUrl("http://www.google.com/");
//方式b:加载apk包中的html页面
webView.loadUrl("file:///android_asset/test.html");
//方式c:加载手机本地的html页面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

//步骤3. 复写shouldOverrideUrlLoading()方法,
webView.setWebViewClient(new WebViewClient(){
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    //使得打开网页时不调用系统浏览器, 而是在本WebView中显示
    view.loadUrl(url);
    return true;
  }

  @Override
  public void  onPageStarted(WebView view, String url, Bitmap favicon) {
    //设定加载开始的操作
  }

  @Override
  public void onPageFinished(WebView view, String url) {
    //设定加载结束的操作
  }

  @Override
  public boolean onLoadResource(WebView view, String url) {
    //设定加载资源的操作
  }

  @Override
  public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){
    switch(errorCode){
    //该方法传回了错误码,根据错误类型可以进行不同的错误分类处理 
      case HttpStatus.SC_NOT_FOUND:
        view.loadUrl("file:///android_assets/error_handle.html");
        break;
    }
  }

  @Override    
  public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {//处理https请  
    handler.proceed();    //表示等待证书响应
    // handler.cancel();      //表示挂起连接,为默认方式
    // handler.handleMessage(null);    //可做其他处理
  }    
});
  • (3) WebChromeClient类( 作用:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。)
  webview.setWebChromeClient(new WebChromeClient(){
      @Override
      public void onProgressChanged(WebView view, int newProgress) {
          if (newProgress < 100) {
          String progress = newProgress + "%";
          progress.setText(progress);
          }else{
          // to do something...
          }
      }

      @Override
      public void onReceivedTitle(WebView view, String title) {
         titleview.setText(title);
      }      
  });
  • 一个demo示范一下以上几个类的用法:

activity_main.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.carson_ho.webview_demo.MainActivity">


   <!-- 获取网站的标题-->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""/>

    <!--开始加载提示-->
    <TextView
        android:id="@+id/text_beginLoading"
        android:layout_below="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""/>

    <!--获取加载进度-->
    <TextView
        android:layout_below="@+id/text_beginLoading"
        android:id="@+id/text_Loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""/>

    <!--结束加载提示-->
    <TextView
        android:layout_below="@+id/text_Loading"
        android:id="@+id/text_endLoading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""/>

    <!--显示网页区域-->
    <WebView
        android:id="@+id/webView1"
        android:layout_below="@+id/text_endLoading"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_marginTop="10dp" />
</RelativeLayout>

java如下:

import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {
    WebView mWebview;
    WebSettings mWebSettings;
    TextView beginLoading,endLoading,loading,mtitle;

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

        mWebview = (WebView) findViewById(R.id.webView1);
        beginLoading = (TextView) findViewById(R.id.text_beginLoading);
        endLoading = (TextView) findViewById(R.id.text_endLoading);
        loading = (TextView) findViewById(R.id.text_Loading);
        mtitle = (TextView) findViewById(R.id.title);
        mWebSettings = mWebview.getSettings();
        mWebview.loadUrl("http://www.baidu.com/");
        //设置不用系统浏览器打开,直接显示在当前Webview
        mWebview.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

        //设置WebChromeClient类
        mWebview.setWebChromeClient(new WebChromeClient() {
            //获取网站标题
            @Override
            public void onReceivedTitle(WebView view, String title) {
                System.out.println("标题在这里");
                mtitle.setText(title);
            }

            //获取加载进度
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                if (newProgress < 100) {
                    String progress = newProgress + "%";
                    loading.setText(progress);
                } else if (newProgress == 100) {
                    String progress = newProgress + "%";
                    loading.setText(progress);
                }
            }
        });

        //设置WebViewClient类
        mWebview.setWebViewClient(new WebViewClient() {
            //设置加载前的函数
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                System.out.println("开始加载了");
                beginLoading.setText("开始加载了");
            }

            //设置结束加载函数
            @Override
            public void onPageFinished(WebView view, String url) {
                endLoading.setText("结束加载了");
            }
        });
    }

    //点击返回上一页面而不是退出浏览器
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && mWebview.canGoBack()) {
            mWebview.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    //销毁Webview
    @Override
    protected void onDestroy() {
        if (mWebview != null) {
            mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
            mWebview.clearHistory();
            ((ViewGroup) mWebview.getParent()).removeView(mWebview);
            mWebview.destroy();
            mWebview = null;
        }
        super.onDestroy();
    }
}

⇒ 二、安卓通过WebView和js交互

Android与js通过WebView互相调用方法,二者沟通的桥梁是WebView,实际上是:

  • Android去调用JS的代码
  • JS去调用Android的代码

对于 Android调用JS代码 的方法有2种:

    1. 通过WebView的loadUrl()
    1. 通过WebView的evaluateJavascript()

对于 JS调用Android代码 的方法有3种:

    1. 通过WebView的addJavascriptInterface()进行对象映射
    1. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
    1. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

(一) Android调用 JS 代码

  • (1)通过WebView的loadUrl()

实例说明:为了方便展示,本文是采用Andorid调用本地JS代码说明。实际开发中,Android更多的是调用远程JS代码,即将加载的JS代码路径改成url即可。

  • 步骤1:将需要调用的JS代码以.html格式放到 src/main/assets 文件夹里

需要加载JS代码:javascript.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <title>Android调用 JS 代码demo</title>
// JS代码
     <script>
       // Android需要调用的方法
       function callJS(){
          alert("Android调用了JS的callJS方法");
       }
    </script>
   </head>
</html>
  • 步骤2:在Android里通过WebView设置调用JS代码

Android代码:MainActivity.java

 public class MainActivity extends AppCompatActivity {

    WebView mWebView;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
       mWebView =(WebView) findViewById(R.id.webview);
        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // 先载入JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");
        button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 必须另开线程进行JS方法调用(否则无法调用)
                mWebView.post(new Runnable() {
                    @Override
                    public void run() {

                        // 注意调用的JS方法名要对应上
                        // 调用javascript的callJS()方法
                        mWebView.loadUrl("javascript:callJS()");
                    }
                });

            }
        });

        // 由于设置了弹窗检验调用结果,所以需要支持js对话框
        // webview只是载体,内容的渲染需要使用webviewChromClient类去实现
        // 通过设置WebChromeClient对象处理JavaScript的对话框
        //设置响应js 的Alert()函数
        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
                b.setTitle("Alert");
                b.setMessage(message);
                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                b.setCancelable(false);
                b.create().show();
                return true;
            }
        });
    }
}
Android调用 JS代码 通过WebView的loadUrl()
  • (2)通过WebView的evaluateJavascript()

说明:该方法比第一种方法效率更高,因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。该方法在Android 4.4 后才可使用。

// 只需要将第一种方法的loadUrl()换成下面该方法即可

mWebView.evaluateJavascript("javascript:callJS()", 
    new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
        }
    });
}
  • (3)两种方法的对比
调用方式 优点 缺点 使用场景
调用loadUrl() 方便简洁 效率低,获取返回值麻烦 不需要获取返回值,对性能要求较低时
调用evaluateJavascript() 效率高 向下兼容性差(仅用于4.4+) 适用于4.4+
  • (4)使用建议

两种方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2

// Android版本变量
final int version = Build.VERSION.SDK_INT;
// 因为该方法在 Android 4.4 版本才可使用,所以使用时需进行版本判断
if (version < 18) {
    mWebView.loadUrl("javascript:callJS()");
} else {
    mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
        }
    });
}

(二) JS通过WebView调用 Android 代码

(1)通过WebView的addJavascriptInterface()进行对象映射
  • 1. 定义一个与JS对象映射关系的Android类:
// 定义一个与JS对象映射关系的Android类AndroidtoJs , 继承自Object类
public class AndroidtoJs extends Object {
    // 定义JS需要调用的方法
    // 被JS调用的方法必须加入@JavascriptInterface注解
    @JavascriptInterface
    public void hello(String msg) {
        System.out.println("JS调用了Android的hello方法");
    }
}
  • 2. 将需要调用的JS代码以.html格式放到 src/main/assets 文件夹里

以下是需要加载JS代码:javascript.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <title>Carson</title>  
      <script>

         function callAndroid(){
        // 由于对象映射,所以调用test对象等于调用Android映射的对象
            test.hello("js调用了android中的hello方法");
         }
      </script>
   </head>
   <body>
      //点击按钮则调用callAndroid函数
      <button type="button" id="button1" onclick="callAndroid()"></button>
   </body>
</html>
  • 3. 在Android里通过WebView设置Android类与JS代码的映射
public class MainActivity extends AppCompatActivity {

    WebView mWebView;

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

        mWebView = (WebView) findViewById(R.id.webview);
        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);

        // 通过addJavascriptInterface()将Java对象映射到JS对象
        //参数1:Javascript对象名
        //参数2:Java对象名
        mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象

        // 加载JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");
       //后面的无关代码省略。。。

看看效果图如下,看下图和log提示:

通过WebView的addJavascriptInterface()进行对象映射

(2)通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url

具体原理: Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url,解析该 url 的协议,如果检测到是预先约定好的协议,就调用Android相应的方法 。

  • 1.在JS约定所需要的Url协议

这里的示例用的是本地的html代码,放到src/main/assets文件夹里,文件名为javascript.html。开发中肯定是从服务器获取对应资源。

<!DOCTYPE html>
<html>

   <head>
      <meta charset="utf-8">
      <title>Carson_Ho</title>

     <script>
         function callAndroid(){
            /*约定的url协议为:js://webview?arg1=111&arg2=222*/
            document.location = "js://webview?arg1=111&arg2=222";
         }
      </script>
</head>

<!-- 点击按钮则调用callAndroid()方法  -->
   <body>
     <button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
   </body>
</html>

当该JS通过Android的mWebView.loadUrl("file:///android_asset/javascript.html")加载后,就会回调shouldOverrideUrlLoading()

  • 2.在Android通过WebViewClient复写shouldOverrideUrlLoading()
public class MainActivity extends AppCompatActivity {

    WebView mWebView;
//    Button button;

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

        mWebView = (WebView) findViewById(R.id.webview);

        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // 步骤1:加载JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");


        // 复写WebViewClient类的shouldOverrideUrlLoading方法
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                // 步骤2:根据协议的参数,判断是否是所需要的url
                // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
                Uri uri = Uri.parse(url);                                 

                // 如果url的协议 = 预先约定的 js 协议
                // 就解析往下解析参数
                if ( uri.getScheme().equals("js")) {
                    // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                    // 所以拦截url,下面JS开始调用Android需要的方法
                    if (uri.getAuthority().equals("webview")) {
                        //  步骤3:
                        // 执行JS所需要调用的逻辑
                        System.out.println("js调用了Android的方法");
                        // 可以在协议上带有参数并传递到Android上
                        HashMap<String, String> params = new HashMap<>();
                        Set<String> collection = uri.getQueryParameterNames();
                    }
                    return true;
                }
                return super.shouldOverrideUrlLoading(view, url);
            }
        );
   }
}

看看效果图:

  • 3.特点:
优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。 如果JS想要得到Android方法的返回值,只能通过 WebView 的
loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:

   // Android:MainActivity.java
   mWebView.loadUrl("javascript:returnResult(" + result + ")");

    // JS:javascript.html
    function returnResult(result){
        alert("result is" + result);
    }

(3)通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
  • 1.JS中的三个常用的对话框方法介绍:
方法 作用 返回值 备注
alert() 弹出警告窗 没有 在文本加入\n可换行
confirm() 弹出确认框 连个返回值 返回布尔值,通过该值可以判断点击是确认还是取消(true表示点击了确认,false表示点击了取消)
prompt() 弹出输入框 任意设置返回值 点击确认返回输入框中的值,点击取消返回null
  • 2.原理:Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框 (即上面表格中的三个方法),得到他们的消息内容,然后解析即可。

下面的例子将用拦截 JS的输入框(即prompt()方法)说明 :

常用的拦截是:拦截 JS的输入框(即prompt()方法)。因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值。

  • 3.步骤1:加载JS代码,这里的示例用的是本地的html代码,放到src/main/assets文件夹里,文件名为javascript.html。开发中肯定是从服务器获取对应资源。
    <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Carson_Ho</title>

        <script>

            function clickprompt(){
            // 调用prompt()
            var result=prompt("js://demo?arg1=111&arg2=222");
            alert("demo " + result);
            }
        </script>
        </head>

        <!-- 点击按钮则调用clickprompt()  -->
        <body>
            <button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button>
        </body>
    </html>
  • 4.步骤2:在Android通过WebChromeClient复写onJsPrompt()
    public class MainActivity extends AppCompatActivity {

        WebView mWebView;
    //    Button button;

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

            mWebView = (WebView) findViewById(R.id.webview);

            WebSettings webSettings = mWebView.getSettings();

            // 设置与Js交互的权限
            webSettings.setJavaScriptEnabled(true);
            // 设置允许JS弹窗
            webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

            // 先加载JS代码
            // 格式规定为:file:///android_asset/文件名.html
            mWebView.loadUrl("file:///android_asset/javascript.html");


            mWebView.setWebChromeClient(new WebChromeClient() {
                // 拦截输入框(原理同方式2)
                // 参数message:代表promt()的内容(不是url)
                // 参数result:代表输入框的返回值
                @Override
                public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                // 根据协议的参数,判断是否是所需要的url(原理同方式2)
                // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                    Uri uri = Uri.parse(message);
                    // 如果url的协议 = 预先约定的 js 协议
                    // 就解析往下解析参数
                    if ( uri.getScheme().equals("js")) {
                        // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                        // 所以拦截url,下面JS开始调用Android需要的方法
                        if (uri.getAuthority().equals("webview")) {
                            // 执行JS所需要调用的逻辑
                            System.out.println("js调用了Android的方法");
                            // 可以在协议上带有参数并传递到Android上
                            HashMap<String, String> params = new HashMap<>();
                            Set<String> collection = uri.getQueryParameterNames();

                            //参数result:代表消息框的返回值(输入值)
                            result.confirm("js调用了Android的方法成功啦");
                        }
                        return true;
                    }
                    return super.onJsPrompt(view, url, message, defaultValue, result);
                }

                // 通过alert()和confirm()拦截的原理相同,此处不作过多讲述

                // 拦截JS的警告框
                @Override
                public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                    return super.onJsAlert(view, url, message, result);
                }

                // 拦截JS的确认框
                @Override
                public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                    return super.onJsConfirm(view, url, message, result);
                }
            });
        }
    }

当使用mWebView.loadUrl("file:///android_asset/javascript.html")加载了上述JS代码后,就会触发回调onJsPrompt()。如果是拦截警告框(即alert()),则触发回调onJsAlert();如果是拦截确认框(即confirm()),则触发回调onJsConfirm();


(4)三种方式的对比 & 使用场景
调用方式 优点 缺点 使用场景
通过addJavascriptInterface()进行添加对象映射 方便简洁 Android4.2以下存在漏洞问题 Android4.2以上相对简单应用场景
通过WebView的方法shouldOverrideUrlLoading()回调拦截url 不存在漏洞问题 使用复杂,需要进行协议约束,从native层到web层传递值比较繁琐 不需要返回值情况下互调场景
通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框消息 不存在漏洞问题 使用复杂,需要进行协议约束 能满足大多数情况下互调场景

关于WebView的那些坑,欢迎查看我的下一篇博客:
WebView深度学习(二)之全面总结WebView遇到的坑及优化


参考博文:
http://blog.csdn.net/carson_ho/article/details/64904691
http://bbs.csdn.net/topics/390905615
http://blog.csdn.net/cyuyanshujujiegou/article/details/52267817

相关文章
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
38 0
|
7天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
9 0
|
7天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
8 0
|
1月前
|
小程序 JavaScript 前端开发
【微信小程序】--WXML & WXSS & JS 逻辑交互介绍(四)
【微信小程序】--WXML & WXSS & JS 逻辑交互介绍(四)
|
1月前
|
移动开发 JavaScript Android开发
Android与Html5交互
Android与Html5交互
|
1月前
|
JavaScript 前端开发
javascript中的交互效果
javascript中的交互效果
|
2月前
|
前端开发 JavaScript
前端 JavaScript 与 HTML 怎么实现交互
前端 JavaScript 与 HTML 怎么实现交互
|
2月前
|
Rust 前端开发 JavaScript
Rust与JavaScript的跨语言交互:探索与实践
本文旨在探讨Rust与JavaScript之间的跨语言交互方法。我们将深入了解WebAssembly(Wasm)的角色,以及它如何使得Rust与JavaScript能够在Web应用中和谐共处。此外,我们还将介绍Rust与JavaScript的集成方式,包括Rust编译到Wasm、使用wasm-bindgen进行Rust与JavaScript的绑定,并通过实际案例展示如何实现两者之间的交互。
|
2月前
|
XML Android开发 数据格式
安卓和webview交互
安卓和webview交互
25 0
|
3月前
|
存储 监控 前端开发
JavaScript手册:公司员工电脑监控软件前端交互的代码设计
在当今信息时代,随着公司对员工电脑活动的监控需求不断增加,前端交互的代码设计变得尤为关键。本手册将深入探讨JavaScript编写的公司员工电脑监控软件监控代码,着重介绍如何设计能够在不引起怀疑的情况下,实现对员工电脑活动的细致监控。
229 2