1. 程式人生 > >Android WebView JS的注入

Android WebView JS的注入

JavaScript在Web開發中非常有用,而現在越來越多的App介面也由Javascript來建立,我們需要解決一個問題:java與javaScript怎麼進行互動呢?
例如,我們可以在html中建立一個按鈕,為按鈕事件新增一個介面。然後你可以使用html按鈕跳轉到另一個Activity中。

(1)本地Html檔案

新增一個新的Assert資料夾

右鍵點選“ App ”module>new>folder>assert folder,然後將為您建立一個Assert Folder。

建立一個html檔案

之後,通過右鍵單擊Assert Folder>new >file建立一個html檔案。將html檔案命名為“sample.html”。然後按照下面的原始碼新增html檔案中的按鈕並建立javascript介面​​。

<html>
<head>
    <style>
body{

}
input{

width: 300px;
padding:10px;

}
div#content{
padding:20px;

}
</style>
    <script type="text/javascript">
    function showToastA(toastmsg) {
        InterfaceName.showToast(toastmsg);
    }
 function navigateToAnotherActivityA()
{
InterfaceName.navigateToAnotherActivity(); }
</script> </head> <body> <center> <h3>Javascript bind to Android</h3> <div> <input type="button" value="Show Toast" onClick="showToastA('Message from Javascript')" /><br/><br
/>
<input type="button" value="Go to Another Activity" onClick="navigateToAnotherActivityA()" /> </div> </center> </body> </html>

建立第二個Activity

建立html檔案後,轉到專案中建立一個新的活動,所以稍後你將使用html按鈕導航到這個Activity。
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:id="@+id/activity_main"
    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.questdot.javascriptwebviewexample.MainActivity">

    <WebView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/webkit"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</RelativeLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        WebView browser;
        browser=(WebView)findViewById(R.id.webkit);

        browser.getSettings().setJavaScriptEnabled(true);

        browser.addJavascriptInterface(new WebAppInterface(this), "InterfaceName");

        browser.loadUrl("file:///android_asset/sample.html");
    }

    public class WebAppInterface {
        Context mContext;

        WebAppInterface(Context c) {
            mContext = c;
        }

        @JavascriptInterface
        public void showToast(String toast) {
            Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
        }


        @JavascriptInterface
        public void navigateToAnotherActivity(){
            AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);

            alertDialog.setTitle("Alert Message");

            alertDialog.setMessage("You want to Go another Activity?");

            alertDialog.setPositiveButton("YES",
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {

                            Intent chnIntent = new Intent(MainActivity.this, SecondActivity.class);
                            startActivity(chnIntent);
                        }
                    });

            alertDialog.setNegativeButton("NO",
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {

                            dialog.cancel();
                        }
                    });
            // Showing Alert Message
            alertDialog.show();
        }
    }
}

addJavascriptInterface中的第二個引數“InterfaceName”應該與html檔案中的相同,否則將報錯。
還要注意的是4.2之前向webview注入的物件所暴露的介面toString沒有註釋語句@JavascriptInterface,而4.2及以後的則多了註釋語句@JavascriptInterface
經過查官方文件所知,因為這個介面允許JavaScript 控制宿主應用程式,這是個很強大的特性,但同時,在4.2的版本前存在重大安全隱患,因為javascript 可以使用反射訪問注入webview的Java物件的public fields,在一個包含不信任內容的WebView中使用這個方法,會允許攻擊者去篡改宿主應用程式,使用宿主應用程式的許可權執行java程式碼。因此4.2以後,任何為js暴露的介面,都需要加。

(2)動態網頁

圖片點選

我們還可以對網頁中的圖片新增點選事件,獲得該圖片的資訊,比如連結等。
主要程式碼如下:

 // 注入js函式監聽  
    private void addImageClickListner() {  
        // 這段js函式的功能就是,遍歷所有的img結點,並新增onclick函式,函式的功能是在圖片點選的時候呼叫本地java介面並傳遞url過去  
        contentWebView.loadUrl("javascript:(function(){" +  
        "var objs = document.getElementsByTagName(\"img\"); " +   
                "for(var i=0;i<objs.length;i++)  " +   
        "{"  
                + "    objs[i].onclick=function()  " +   
        "    {  "   
                + "        window.imagelistner.openImage(this.src);  " +   
        "    }  " +   
        "}" +   
        "})()");  
    }  
// js通訊介面  
    public class JavascriptInterface {  

        private Context context;  

        public JavascriptInterface(Context context) {  
            this.context = context;  
        }  

        public void openImage(String img) {  
            System.out.println(img);  
            //  
            Intent intent = new Intent();  
            intent.putExtra("image", img);  
            intent.setClass(context, ShowWebImageActivity.class);  
            context.startActivity(intent);  
            System.out.println(img);  
        }  
    }  
// 監聽  
    private class MyWebViewClient extends WebViewClient {  
        @Override  
        public boolean shouldOverrideUrlLoading(WebView view, String url) {  

            return super.shouldOverrideUrlLoading(view, url);  
        }  

        @Override  
        public void onPageFinished(WebView view, String url) {  

            view.getSettings().setJavaScriptEnabled(true);  

            super.onPageFinished(view, url);  
            // html載入完成之後,新增監聽圖片的點選js函式  
            addImageClickListner();  

        }  

        @Override  
        public void onPageStarted(WebView view, String url, Bitmap favicon) {  
            view.getSettings().setJavaScriptEnabled(true);  

            super.onPageStarted(view, url, favicon);  
        }  

        @Override  
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  

            super.onReceivedError(view, errorCode, description, failingUrl);  

        }  
    }  
// 隨便找了個帶圖片的網站  
        contentWebView.loadUrl("http://www.weim.me/12408.html");  
        // 新增js互動介面類,並起別名 imagelistner  
        contentWebView.addJavascriptInterface(new JavascriptInterface(this), "imagelistner");  
        contentWebView.setWebViewClient(new MyWebViewClient());  

這種注入JS的方法基本可以說是放之四海而皆準的方法,我們還可以用此方法攔截按鈕的點選事件。

按鈕點選

我們以豆瓣登陸這個網頁為例。

檢視網頁原始碼,登陸按鈕在這段程式碼中可以找到:

<form id="lzform" name="lzform" method="post" onsubmit="return validateForm(this);" action="https://accounts.douban.com/login">
  <div style="display:none;">
    <img src="https://www.douban.com/pics/blank.gif" onerror="document.lzform.action='https://accounts.douban.com/login'"/>
  </div>
  <input name="source" type="hidden" value="book"/>
    <input name="redir" type="hidden" value="https://book.douban.com/subject/1041482/"/>
  <div class="item-right">
    <a href="?redir=https://book.douban.com/subject/1041482/&amp;source=book&amp;login_type=sms">手機驗證碼登入</a>
  </div>
  <div class="item">
    <label>帳號</label>
    <input id="email" name="form_email" type="text" class="basic-input"
           maxlength="60" value="郵箱/手機號/使用者名稱" tabindex="1"/>
  </div>
  <div class="item">
    <label>密碼</label>
    <input id="password" name="form_password" type="password" class="basic-input" maxlength="20" tabindex="2"/>
  </div>
  <!-- tsYR99_fISw | 175.191.30.52 -->

  <div class="item">
    <label>&nbsp;</label>
    <p class="remember">
      <input type="checkbox" id="remember" name="remember" tabindex="4"/>
      <label for="remember" class="remember">下次自動登入</label>
      | <a href="https://accounts.douban.com/resetpassword">忘記密碼了</a>
    </p>
  </div>
  <div class="item">
    <label>&nbsp;</label>
    <input type="submit" value="登入" name="login" class="btn-submit" tabindex="5"/>
  </div>




<div class="item item-3rd">
    <label>
    第三方登入:
    </label>
    <a target="_top" href="https://www.douban.com/accounts/connect/wechat/?from=book&amp;redir=https%3A//book.douban.com/subject/1041482/" class="item-wechat"><img src="https://img3.doubanio.com/f/accounts/1b6cc3ca91f78cf47f41eafa91fbcd4918ae239c/pics/connect_wechat.png" title="微信"></a>
    <a target="_top" href="https://www.douban.com/accounts/connect/sina_weibo/?from=book&amp;redir=https%3A//book.douban.com/subject/1041482/&amp;fallback=" class="item-weibo"><img src="https://img3.doubanio.com/f/accounts/e2f1d8c0ede93408b46cbbab4e613fb29ba94e35/pics/connect_sina_weibo.png" title="新浪微博"></a>
</div>

</form>

那麼要怎麼獲取這個登入按鈕的事件呢?關鍵程式碼如下:

//注入javascript到頁面
webview1.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        StringBuilder sb = new StringBuilder();
        //獲得網頁中所有form節點的標籤陣列,這裡獲得陣列的第一個form節點
        //onsubmit 事件會在表單中的確認按鈕被點選時發生。
        sb.append("document.getElementsByTagName('form')[0].onsubmit = function () {");
        //方法中的區域性變數,用來儲存使用者名稱和密碼
        sb.append("var objPWD, objAccount;var str = '';");
        //獲得form節點下所有的input節點陣列
        sb.append("var inputs = document.getElementsByTagName('input');");
        sb.append("for (var i = 0; i < inputs.length; i++) {");
        //獲得輸入的密碼
        sb.append("if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}");
        //獲得輸入的使用者名稱
        sb.append("else if (inputs[i].name.toLowerCase() === 'form_email') {objAccount = inputs[i];}");
        sb.append("}");
        sb.append("if (objAccount != null) {str += objAccount.value;}");
        sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}");
        sb.append("window.MYOBJECT.processHTML(str);");
        sb.append("return true;");
        sb.append("};");

        view.loadUrl("javascript:" + sb.toString());
    }

});

//建立一個由javascrip呼叫的類
class MyJavaScriptInterface
{
    @JavascriptInterface
    public void processHTML(String html)
    {
        //called by javascript
           Log.d("henrytest", html);

            AlertDialog.Builder builder = new AlertDialog.Builder(Main.this);
            builder.setTitle("AlertDialog from app")
                    .setMessage(html)
                    .setPositiveButton(android.R.string.ok,
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    // TODO Auto-generated method stub

                                }
                            })
                    .setCancelable(false).show();
    }
}
//為javascript註冊介面
webview1.getSettings().setJavaScriptEnabled(true);
webview1.addJavascriptInterface(new MyJavaScriptInterface(), "MYOBJECT");

這裡寫圖片描述