美文网首页Android
Android WebView Java 和 JavaScrip

Android WebView Java 和 JavaScrip

作者: yuzhiyi_宇 | 来源:发表于2018-11-04 13:00 被阅读54次

    在 Androi 开发中,我们使用 Webview 组件来加载 HTML5 页面,WebView 默认提供了让 Java 和 HTML5 页面中的 JavaScript 脚本交互的能力。

    Java 调用 JavaScript

    Java 调用 JavaScript 中的函数,只需要执行以下代码。

    String html = " <!DOCTYPE html>\n" +
            "<html>\n" +
            "<head>\n" +
            "<meta content=\"text/html;charset=UTF-8\"/>" +
            "<script language=\"javascript\">" +
            "function toast() {alert(\"test\");}" +
            "</script>\n" +
            "</head>\n" +
            "</html>"
    
    webView = findViewById(R.id.webview);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebChromeClient(new WebChromeClient() {});
    webView.loadData(html, "text/html", "UTF-8");
    webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            webView.loadUrl("javascript:toast()");
        }
    });
    

    注意

    1. 如果不设置 WebChromeClient,Alert 无法弹出。
    webView.setWebChromeClient(new WebChromeClient() {});
    
    1. 网页的js代码没有加载,就调用了js方法,会报以下错误。
    I/chromium: [INFO:CONSOLE(1)] "Uncaught ReferenceError: toast is not defined", source:  (1)
    

    所以在网页加载完成之后调用js方法或者限制用户在页面加载完成之前不允许调用JS的事件。

    webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            webView.loadUrl("javascript:toast()");
        }
    });
    

    JavaScript 调用 Java

    方案一
    WebView 提供了 WebSetting 工具类来实现让 WebView 中的 JavaScript 脚本调用 Android 应用的 Java 方法。

    • 调用与 WebView 关联的 WebSettings 实例的 setJavaScriptEnabled 方法是否使用 JavsScript 的功能。
    • 调用 WebView 的 addJavascriptInterface 方法将应用中的 Java 对象暴露给 JavaScript。
    • 在 JavaScript 脚本中调用步骤二暴露出来的 Java 对象的方法。
    public class WebViewActivity extends AppCompatActivity {
    
        private WebView webView;
    
        @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_webview);
    
            String html = " <!DOCTYPE html>\n" +
                    "<html>\n" +
                    "<head>\n" +
                    "<meta content=\"text/html;charset=UTF-8\"/>" +
                    "</head>\n" +
                    "<body style=\"background-color:black\">\n" +
                    "<input type=\"button\" value=\"显示Toast\" onclick=\"injectedObject.showToast('test')\"/>" +
                    "</body>\n" +
                    "</html>";
    
            webView = findViewById(R.id.webview);
            webView.getSettings().setJavaScriptEnabled(true);
            webView.addJavascriptInterface(new JsObject(), "injectedObject");
            webView.loadData(html, "text/html", "UTF-8");
        }
    
        class JsObject {
            @JavascriptInterface
            public String toString() {
                return "injectedObject";
            }
    
            @JavascriptInterface
            public void showToast(String message) {
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
            }
        }
    }
    

    这种方法在 Android 4.2 之前会存在安全隐患,会引起 WebView 远程代码执行漏洞。安卓4.2 开始,谷歌修复了这个漏洞,可以安全的使用上述的方法,唯一需要修改的是对暴露给 JavaScript 调用的方法增加 @JavascriptInterface。
    方案二
    如果需要支持 Android 4.2 之前的系统,需要用另外的方案规避这个问题,不再使用 addJavascriptInterface 这种方式。
    JavaScript 有三种常用的消息提示框,分别是:弹出警告框 alert,弹出确认框 confirm 和弹出输入框 prompt。对应到 Android 的 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) {
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
    

    这三个方法参数唯一的区别是返回给 JavaScript 的结果类型不一样,前两者是 JsResult,这个类中带有一个布尔类型的结果值,而 onJsPrompt 是 JsPromptResult 类型,是 JsResult 的子类,带有一个字符串类型的结果值。

    public class JsResult {
        /**
         * Callback interface, implemented by the WebViewProvider implementation to receive
         * notifications when the JavaScript result represented by a JsResult instance has
         * @hide Only for use by WebViewProvider implementations
         */
        @SystemApi
        public interface ResultReceiver {
            public void onJsResultComplete(JsResult result);
        }
        // This is the caller of the prompt and is the object that is waiting.
        @UnsupportedAppUsage
        private final ResultReceiver mReceiver;
        // This is a basic result of a confirm or prompt dialog.
        private boolean mResult;
    
        /**
         * Handle the result if the user cancelled the dialog.
         */
        public final void cancel() {
            mResult = false;
            wakeUp();
        }
    
        /**
         * Handle a confirmation response from the user.
         */
        public final void confirm() {
            mResult = true;
            wakeUp();
        }
    
        /**
         * @hide Only for use by WebViewProvider implementations
         */
        @SystemApi
        public JsResult(ResultReceiver receiver) {
            mReceiver = receiver;
        }
    
        /**
         * @hide Only for use by WebViewProvider implementations
         */
        @SystemApi
        public final boolean getResult() {
            return mResult;
        }
    
        /* Notify the caller that the JsResult has completed */
        private final void wakeUp() {
            mReceiver.onJsResultComplete(this);
        }
    }
    
    public class JsPromptResult extends JsResult {
        // String result of the prompt
        private String mStringResult;
    
        /**
         * Handle a confirmation response from the user.
         */
        public void confirm(String result) {
            mStringResult = result;
            confirm();
        }
    
        /**
         * @hide Only for use by WebViewProvider implementations
         */
        @SystemApi
        public JsPromptResult(ResultReceiver receiver) {
            super(receiver);
        }
    
        /**
         * @hide Only for use by WebViewProvider implementations
         */
        @SystemApi
        public String getStringResult() {
            return mStringResult;
        }
    }
    

    String 类型的结果值可以携带更多的信息,因此,选择 onJsPrompt 方法作为解决方案,通过这个方法,我们能够将 JavaScript 中将字符串信息(对应 onJsPrompt 入参中的 message)传递给 Java,而 Java 执行完成之后能够把返回结果的字符串形式(对应 onJsPrompt 的返回值 mStringResult)传递给 JavaScript。基本思路如下。

    • 首先需要基于这个字符串定义好通信的协议,可以是 JSON 格式,这个字符串中可能包含调用的类型 type,方法名 method,方法参数 args 等。
    • 在 JavaScript 中封装一个方法,它最终通过调用 prompt 方法实现将上面的文本协议信息传递给 Java 层 WebChromeClient 类的 onJsPrompt 方法,在这个方法中对协议信息进行解析,可以得到类型、方法名、参数等信息,通过 Java 的发射机制可以实现调用到对应的 Java 方法。
    • 步骤二的 Java 方法执行完毕后,同理,需要定义好返回值的协议格式,并通过 JsPromptResult 返回给 JavaScript。

    safe-java-js-webview-bridge 基于上诉方案已经实现了。

    相关文章

      网友评论

        本文标题:Android WebView Java 和 JavaScrip

        本文链接:https://www.haomeiwen.com/subject/omsdtqtx.html