WebView与JS交互

1、交互方式总结

对于 Android 调用 JS 代码的方法有 2 种: 
a. 通过 WebView 的 loadUrl() 
b. 通过 WebView 的 evaluateJavascript()

对于 JS 调用 Android 代码的方法有 3 种: 
a. 通过 WebView 的 addJavascriptInterface() 进行对象映射 
b. 通过 WebViewClient 的 shouldOverrideUrlLoading() 方法回调拦截 url 
c. 通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt() 方法回调拦截 JS 对话框 alert()、confirm()、prompt() 消息

2、Android 调用 JS

  • 方式 1:通过 WebView 的 loadUrl()
    JS 代码:
// JS 代码
<script>
   // Android 需要调用的方法
   function callJS() {
      alert("Android 调用了 JS 的 callJS 方法");
   }
</script>

Android 代码:

// 注意调用的 JS 方法名要对应上
// 调用 javascript 的 callJS() 方法                 
mWv.loadUrl("javascript:callJs('" + mMsg + "')");

完整代码:

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) {
                // 通过 Handler 发送消息
                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;
            }
        });
    }
}
特别注意:JS 代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。
  • 方式2:通过 WebView 的 evaluateJavascript()
    优点:该方法比第一种方法效率更高、使用更简洁。
    因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。
    Android 4.4 后才可使用
// 只需要将第一种方法的 loadUrl() 换成下面该方法即可
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
        }
    });
}
0.5252186051721934.png

使用建议
两种方法混合使用,即 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 返回的结果
        }
    });
}

3、JS 通过 WebView 调用 Android 代码

  • 方式 1:通过 WebView 的 addJavascriptInterface() 进行对象映射
    步骤 1:定义一个与 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 文件夹里

<script>
     function callAndroid() {
        // 由于对象映射,所以调用 test 对象等于调用 Android 映射的对象
        window.test.hello("js 调用了 android 中的 hello 方法");
     }
</script>

步骤 3:在 Android 里通过 WebView 设置 Android 类与 JS 代码的映射

        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");

特点
优点:使用简单
缺点:存在严重的漏洞问题(Android 4.2 之后函数以 @JavascriptInterface 进行注解从而避免漏洞攻击)

  • 方式 2:通过 WebViewClient 的方法 shouldOverrideUrlLoading() 回调拦截 url
    具体原理:
    (1) Android 通过 WebViewClient 的回调方法 shouldOverrideUrlLoading() 拦截 url
    (2) 解析该 url 的协议
    (3) 如果检测到是预先约定好的协议,就调用相应方法
    具体使用:
    步骤 1:在 JS 约定所需要的 Url 协议
<script>
    function callAndroid(){
       /*约定的 url 协议为:js://webview?arg1=111&arg2=222*/
       document.location = "js://webview?arg1=111&arg2=222";
    }
</script>

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

// 复写 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 上
         Set<String> collection = uri.getQueryParameterNames();
         Iterator<String> iterator = collection.iterator();
         while (iterator.hasNext()) {
             String next = iterator.next();
             String value = uri.getQueryParameter(next);
             System.out.println("key:" + next + ",value:" + value);
          }
        }
        return true;
      }
      return super.shouldOverrideUrlLoading(view, url);
   }
 }
);

特点
优点:不存在方式 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() 消息
    在 JS 中,有三个常用的对话框方法:


    0.4602223544273103.png

    方式 3 的原理:Android 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt() 方法回调分别拦截 JS 对话框 (即上述三个方法),得到他们的消息内容,然后解析即可。
    下面的例子将用拦截 JS 的输入框(即 prompt() 方法)说明 :
    a.常用的拦截是:拦截 JS 的输入框(即 prompt() 方法)
    b.因为只有 prompt() 可以返回任意类型的值,操作最全面方便、更加灵活;而 alert() 对话框没有返回值;confirm() 对话框只能返回两种状态(确定 / 取消)两个值
    步骤 1:加载 JS 代码,如下:

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

当使用 mWebView.loadUrl("file:///android_asset/javascript.html") 加载了上述 JS 代码后,就会触发回调 onJsPrompt(),具体如下:
如果是拦截警告框(即 alert() ),则触发回调 onJsAlert();
如果是拦截确认框(即 confirm() ),则触发回调onJsConfirm();
步骤 2:在 Android 通过 WebChromeClient 复写 onJsPrompt()

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("demo")) {
               // 执行 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);
     }
 }
);
0.3452358047020512.png

4、总结

0.894173224369111.png

5、WebView 的坑

(1) API 16 之前版本存在远程代码执行漏洞,该漏洞源自于程序没有正确限制使用 WebView.addJavascriptInterface 方法,攻击者可以使用 Java Reflection API 利用该漏洞执行任意 Java 对象和方法。
(2) WebView 的销毁和内存泄漏问题。WebView 的完全销毁是件麻烦事,一旦销毁流程不正确,极易容易导致内存泄漏。
原因:WebView 会关联一个 Activity,WebView 执行的操作是在新线程当中回收,时间 Activity 没有办法确认,Activity 的生命周期和 WebView 线程生命周期不一致导致 WebView 一直执行,因为 WebView 内部持有 Activity 的引用,导致Activity 一直不能被回收,原理类似于匿名内部类持有外部类的引用一样。那么如何解决呢?解决方案如下:
a、使含 WebView 的界面在独立进程(销毁时 System.exit(0)),简单暴力,涉及到进程间通信。(开发过程中常用)
b、动态添加 WebView,对传入 WebView 中使用 Context 弱引用,动态添加 WebView 意思在布局中创建一个 ViewGroup 用来放置 WebView,Activity 创建add 进来,Activity 停止时 remove 掉。
c、从根源解决
前面的方法都没有解决我内存泄漏的问题,然后我看到了一篇文章是从源码角度分析了 webview 内存泄漏的原因,最后按作者的方法解决了问题,后面会贴上原文地址。这里简单说一下:
原文里说的 webview 引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了 component callbacks,但是未正常反注册而导致的。
org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在 attach 和 detach 处进行注册和反注册 component callback;
在 onDetachedFromWindow() 方法的第一行中:

if (isDestroyed()) return;

如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行 unregister 的操作;我们的 activity 退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy() 的执行时间又在 onDetachedFromWindow 之前,所以就会导致不能正常进行 unregister()。
然后解决方法就是:让 onDetachedFromWindow 先走,在主动调用 destroy() 之前,把 webview 从它的 parent 上面移除掉。
完整的 activity 的 onDestroy() 方法:

@Override
protected void onDestroy() {
    if (mWebView != null) {
        // 如果先调用 destroy() 方法,则会命中 if (isDestroyed()) return; 这一行代码,
        // 需要先 onDetachedFromWindow(),再 destory()
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }
        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        mWebView.destroy();
    }
    super.on Destroy();
}

(3)、jsbridge
通过 javascript 构建一个桥,桥的两端一个端是客户端,一端是服务端,它可以让本地端调用远端的 web 代码,也可以让远端调用本地的代码。
(4)、WebViewClient.onPageFinished(不靠谱)–> WebChromeClient.onProgressChanged(靠谱)
(5)、后台耗电(性能优化)
WebView 开启网页时会自己开启线程,如果没有合理的销毁,那么残余线程就会一直运行,so 这会非常耗电的,解决方案:有一种暴力方式就是 Activity 的 onDestroy 中调用 System.exit() 方法把虚拟机关闭,也可以结合自己应用的 WebView 的情况设计出一个温柔的方案。
(6)、WebView 硬件加速导致的页面渲染问题
WebView 硬件加速偶尔导致页面白块,页面闪烁,但是加载速度比较快,解决方案:关闭硬件加速。

例子

Android studio 中创建 html 文件
//www.greatytc.com/p/9a74d4700e31

1、先创建一个 assets 文件夹,在文件夹里创建一个 test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>我是个水哥</title>
</head>
<body>
<input type="text" value="我是输入框" id="edit">
<button id="btn1" onclick="callAndroid()">调用 Android 方式 1</button>
<button id="btn2" onclick="callAndroid2()">调用 Android 方式 2</button>
<button id="btn3" onclick="callAndroid3()">调用 Android 方式 3</button>


<script>
  // Android 调用 Js
  function callJs(msg) {
    //想要传递过来的内容显示在输入框中
    var edit = document.getElementById("edit");
    edit.value = msg;

  }

  // Js 调用 Android -- 方法 1,addJavaScriptInterface
  function callAndroid() {
    // android 要通过对象调用方法,所以需要将 java 的对象映射过来
    // 由于对象映射,所以调用 test 对象等于调用 Android 映射的对象
    window.zhangsan.callAndroid("js 调用了 android 中的方法");
  }

  // Js 调用 Android -- 方法 2,shouldOverrideUrlLoading
 function callAndroid2() {
    /*约定的 url 协议为:js://webview?arg1=111&arg2=222*/
    document.location = "js://webview?arg1=111&arg2=222";
 }

  // Js 调用 Android -- 方法 3,WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt()
 function callAndroid3() {
    // 调用 prompt()
    var result=prompt("js://demo?arg1=3333&arg2=4444");
    // 弹窗
    alert(result);
 }

</script>
</body>
</html>
2、xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Android 调用 JS ----- 1"/>
    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Android 调用 JS ----- 2"/>

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </WebView>

</LinearLayout>
3、AndroidJs(一个与 JS 对象映射关系的 Android 类)(JS 通过 WebView 调用 Android 代码时方式 1 时使用)
import android.webkit.JavascriptInterface;

// 继承自 Object 类
public class AndroidJs extends Object {
    // 定义 JS 需要调用的方法
    // 被 JS 调用的方法必须加入 @JavascriptInterface 注解
    @JavascriptInterface
    public void hello(String msg) {
        System.out.println("JS 调用了 Android 的 hello 方法");
    }
}
4、Activity
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.Toast;

import java.util.Iterator;
import java.util.Set;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn1;
    private Button btn2;
    private WebView webview;
    private String msg = "哈哈哈哈哈哈哈哈哈";

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

    private void initView() {
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);
        webview = (WebView) findViewById(R.id.webview);

        // 设置与 Js 交互的权限
        WebSettings settings = webview.getSettings();

        // 设置允许 JS 弹窗
        settings.setJavaScriptEnabled(true);

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

        // Js 调用 Android 方法 1
        // 参数 1:桥梁类的对象
        // 参数 2:对象的名字
        // AndroidJs zhangsan = new AndroidJs();
        webview.addJavascriptInterface(new AndroidJs(), "zhangsan");

        // Js 调用 Android -- 方法 2,shouldOverrideUrlLoading
        // 复写 WebViewClient 类的 shouldOverrideUrlLoading 方法
        webview.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 上
                        Set<String> collection = uri.getQueryParameterNames();
                        Iterator<String> iterator = collection.iterator();
                        while (iterator.hasNext()) {
                            String next = iterator.next();
                            String value = uri.getQueryParameter(next);
                            System.out.println("key:" + next + ",value:" + value);
                        }
                    }
                    return true;
                }
                return super.shouldOverrideUrlLoading(view, url);
            }
        });

        // Js 调用 Android -- 方法3,WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt()
        webview.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("demo")) {
                        // 执行 JS 所需要调用的逻辑
                        System.out.println("js 调用了 Android 的方法");
                        // 可以在协议上带有参数并传递到 Android 上
                        Set<String> collection = uri.getQueryParameterNames();
                        Iterator<String> iterator = collection.iterator();
                        while (iterator.hasNext()) {
                            String next = iterator.next();
                            String value = uri.getQueryParameter(next);
                            System.out.println("key:" + next + ",value:" + value);
                        }
                        // 参数 result:代表消息框的返回值(输入值)
                        result.confirm("第三种方法的 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);
            }
        });

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn1:
                webview.loadUrl("javascript:callJs('" + msg + "')");
                break;
            case R.id.btn2:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    webview.evaluateJavascript("javascript:callJs('" + msg + "')", new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {
                            // 接收返回值用的
                            Toast.makeText(MainActivity.this, value, Toast.LENGTH_SHORT).show();
                        }
                    });
                }
                break;
        }
    }
}

通过 WebView 选择图片、视频、文件时可以查询
https://www.apiref.com/android-zh/android/webkit/WebChromeClient.FileChooserParams.html

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

推荐阅读更多精彩内容