Android開發,WebView下Java和JavaScript相互調用(1)

本文參考了兩篇文章,修改了部分代碼,添加很多註釋,幫助新手理解

文章來源1
文章來源2

效果圖

這裏寫圖片描述

點擊“調用alert”按鈕,在Android中捕獲JS alert,並用Android組件(AlertDialog)替換

這裏寫圖片描述

點擊“調用java方法”按鈕,在JS中調用並傳遞參數到Java中的方法

這裏寫圖片描述

代碼部分

js_interact_demo.html

<html>  
<head>  
  <title>JS交互</title>  
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>  
  <script type="text/javascript">  
    function invokedByJava(param) {  
        document.getElementById("content").innerHTML = "Java has invoked JS function and returnd the data:"+param;  
    }  
  </script>  
</head>  
<body>  
  <p id="content"></p>  
  <p>  
  <input type="button" value="調用Java方法" onclick="window.stub.jsMethod('來至JS的參數');" />  
  <input type="button" value="調用alert" onclick="alert('hello')" />  
  </p>  
</body>  
</html>

res/layout目錄下:
web_view.xml

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  android:orientation="vertical"  
  android:layout_width="match_parent"  
  android:layout_height="match_parent">  
    <Button android:id="@+id/web_view_invoke_js"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="調JS方法"/>  
    <LinearLayout android:layout_width="fill_parent"  
        android:layout_height="wrap_content">  
        <EditText android:id="@+id/web_view_text"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_weight="1"/>  
        <Button android:id="@+id/web_view_search"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="瀏覽"/>  
    </LinearLayout>  
    <WebView android:id="@+id/web_view"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent" />  
</LinearLayout>  

WebViewDemo.java

package com.example.webviewdemo;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.Toast;
import android.graphics.Bitmap;
import android.webkit.JsResult;


@SuppressLint("JavascriptInterface")
public class WebViewDemo extends Activity {
    private WebView mWebView;//創建私有WebView對象

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);//照抄就行了
        setContentView(R.layout.web_view);//載入web_view佈局文件

        findViewById(R.id.web_view_invoke_js).setOnClickListener(new OnClickListener() {//創建點擊監聽器(調JS方法)
            @Override
            public void onClick(View v) {//重構點擊方法
                //調用JS方法,並傳遞參數
                mWebView.loadUrl("javascript:invokedByJava('我來自Java')");//直接訪問js中的invokeByJava方法
            }
        });

        mWebView = (WebView)findViewById(R.id.web_view);//找到web_view
        mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);//設置滾動條
        mWebView.getSettings().setBuiltInZoomControls(true);//允許滾動條
        mWebView.getSettings().setJavaScriptEnabled(true);//允許webview的js有效

        /* 
          WebView默認用系統自帶瀏覽器處理頁面跳轉。
                            爲了讓頁面跳轉在當前WebView中進行,重寫WebViewClient。
                            但是按BACK鍵時,不會返回跳轉前的頁面,而是退出本Activity。重寫onKeyDown()方法來解決此問題。
         */
        mWebView.setWebViewClient(new WebViewClient() {
            //setWebViewClient方法的作用是
            //Sets the WebViewClient that will receive various notifications and requests. 
            //This will replace the current handler.
            //重寫WebViewClient,使得webview中頁面的跳轉是在內部進行,而不是跳轉到系統瀏覽器
            //WebViewClient主要幫助WebView處理各種通知、請求事件的
            //WebChromeClient主要輔助WebView處理Javascript的對話框、網站圖標、網站title、加載進度等
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);//使用當前WebView處理跳轉
                return true;//true表示此事件在此處被處理,不需要再廣播
            }

            /*onPageStarted作用:
            Notify the host application that a page has started loading.
            通知宿主應用程序的頁已開始加載。
            This method is called once for each main frame load 
            爲每個主frame調用一次該方法
            so a page with iframes or framesets will call onPageStarted one time for the main frame.
            因此有iframes或者framsets的頁面將會爲主frame調用一次onPageStarted方法
            This also means that onPageStarted will not be called when the contents of an embedded frame changes,
            這也意味着,當內部frame的內容改變時,onPageStarted將不會被調用。
            i.e. clicking a link whose target is an iframe.
            比如,單擊是iframe的鏈接*/
            /*參數:
            view:The WebView that is initiating the callback.
            url:The url to be loaded.
            favicon(圖標):The favicon for this page if it already exists in the database.*/
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                //有頁面跳轉時被回調
            }
            @Override
            public void onPageFinished(WebView view, String url) {
                //頁面跳轉結束後被回調
            }

            /*Report an error to the host application. These errors are unrecoverable 
             (i.e. the main resource is unavailable). 
            The errorCode parameter corresponds to one of the ERROR_* constants.*/            
            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                Toast.makeText(WebViewDemo.this, "Oh no! " + description, Toast.LENGTH_SHORT).show();
                //當出現錯誤時,通知宿主程序,Toast顯示的內容
                //show()作用:Show the view for the specified duration.
            }
        });

        /*
                            當WebView內容影響UI時調用WebChromeClient的方法
         */
        mWebView.setWebChromeClient(new WebChromeClient() {
            /**
             * 處理JavaScript Alert事件
             */
            @Override
            public boolean onJsAlert(WebView view, String url,String message, final JsResult result) {
                //用Android組件替換,構建一個Builder來顯示網頁中的alert對話框
                new AlertDialog.Builder(WebViewDemo.this)
                    .setTitle("JS提示")
                    .setMessage(message)
                    .setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    })
                    .setCancelable(false)
                    .create().show();
                return true;
            }
        });

        /*
                          綁定Java對象到WebView,這樣可以讓JS與Java通信(JS訪問Java方法)
                          第一個參數是自定義類對象,映射成JS對象
                          第二個參數是第一個參數的JS別名
                          調用示例:
            mWebView.loadUrl("javascript:window.stub.jsMethod('param')");
         */
        mWebView.addJavascriptInterface(new JsToJava(), "stub");//js訪問java的關鍵方法

        final EditText mEditText = (EditText)findViewById(R.id.web_view_text);//找到edittext

        findViewById(R.id.web_view_search).setOnClickListener(new OnClickListener() {//爲button設置點擊監聽器
            @Override
            public void onClick(View view) {//點擊事件觸發的方法
                String url = mEditText.getText().toString();//取文本框輸入的內容,並轉換爲字符串
                if (url == null || "".equals(url)) {//判斷,如果文本框中的內容爲空值,或者url的網址爲空?
                    Toast.makeText(WebViewDemo.this, "請輸入URL", Toast.LENGTH_SHORT).show();//給出toast提示
                } else {
                    if (!url.startsWith("http:") && !url.startsWith("file:")) {//如果內容以http:和file開頭
                        url = "http://" + url;//爲url賦值
                    }
                    mWebView.loadUrl(url);//傳入賦值後的url並加載
                }
            }
        });
        //默認頁面,本地html
        mWebView.loadUrl("file:///android_asset/js_interact_demo.html");
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {//重寫back退回方法
        //處理WebView跳轉返回
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {//如果
            mWebView.goBack();//webview返回,而不是退出當前activity
            return true;//返回真
        }
        return super.onKeyDown(keyCode, event);//
    }

    private class JsToJava {

        @JavascriptInterface
        //添加@JavascriptInterface註釋
        //解決Uncaught TypeError: Object [object Object] has no method  安全限制問題
        public void jsMethod(String paramFromJS) {
            Log.i("CDH", "paramFromJS");
            Toast.makeText(WebViewDemo.this, "Oh Yeah!終於調用了java,感動 ", Toast.LENGTH_SHORT).show();
        }
    }
}

疑問解答

Alert無法彈出
你應該是沒有設置WebChromeClient,按照以下代碼設置

myWebView.setWebChromeClient(new WebChromeClient() {});

Uncaught ReferenceError: functionName is not defined
問題出現原因,網頁的js代碼沒有加載完成,就調用了js方法。解決方法是在網頁加載完成之後調用js方法

myWebView.setWebViewClient(new WebViewClient() {

@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//在這裏執行你想調用的js函數
}

});

Uncaught TypeError: Object [object Object] has no method
安全限制問題
如果只在4.2版本以上的機器出問題,那麼就是系統處於安全限制的問題了。Android文檔這樣說的

Caution: If you’ve set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available your web page code (the method must also be public). If you do not provide the annotation, then the method will not accessible by your web page when running on Android 4.2 or higher.

中文大意爲

警告:如果你的程序目標平臺是17或者是更高,你必須要在暴露給網頁可調用的方法(這個方法必須是公開的)加上@JavascriptInterface註釋。如果你不這樣做的話,在4.2以以後的平臺上,網頁無法訪問到你的方法。

兩種解決方法
將targetSdkVersion設置成17或更高,引入@JavascriptInterface註釋
自己創建一個註釋接口名字爲@JavascriptInterface,然後將其引入。注意這個接口不能混淆。
注,創建@JavascriptInterface代碼

public @interface JavascriptInterface {
}

代碼混淆問題
如果在沒有混淆的版本運行正常,在混淆後的版本的代碼運行錯誤,並提示Uncaught TypeError: Object [object Object] has no method,那就是你沒有做混淆例外處理。 在混淆文件加入類似這樣的代碼

-keep class com.example.javajsinteractiondemo$JsInteration {
*;
}
All WebView methods must be called on the same thread
過濾日誌曾發現過這個問題。

E/StrictMode( 1546): java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {528712d4} called on Looper (JavaBridge, tid 121) {52b6678c}, FYI main Looper is Looper (main, tid 1) {528712d4})
E/StrictMode( 1546): at android.webkit.WebView.checkThread(WebView.java:2063)
E/StrictMode( 1546): at android.webkit.WebView.loadUrl(WebView.java:794)
E/StrictMode( 1546): at com.xxx.xxxx.xxxx.xxxx.xxxxxxx$JavaScriptInterface.onCanGoBackResult(xxxx.java:96)
E/StrictMode( 1546): at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
E/StrictMode( 1546): at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
E/StrictMode( 1546): at android.os.Handler.dispatchMessage(Handler.java:102)
E/StrictMode( 1546): at android.os.Looper.loop(Looper.java:136)
E/StrictMode( 1546): at android.os.HandlerThread.run(HandlerThread.java:61)
在js調用後的Java回調線程並不是主線程。如打印日誌可驗證

ThreadInfo=Thread[WebViewCoreThread,5,main]
解決上述的異常,將webview操作放在主線程中即可。

webView.post(new Runnable() {
@Override
public void run() {
webView.loadUrl(YOUR_URL).
}
});

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章