Android中webview填坑系列——向webview注入本地js檔案
需求:
在使用webview載入H5介面時,注入一個本地js檔案(該js檔案的作用是採集H5頁面中使用者的點選事件)。也就是在webview打開了一個H5頁面的時候動態的注入一個js,讓該js和頁面並行執行各自的邏輯。
思路:
查閱了一下網上的資料,思路也就兩種,這裡先列出來,後面再對這兩種思路做評判,如下所示:
(1)第一種思路:將本地的js檔案讀出來已字串的形式,通過webview.loadUrl("javascript:",""); 注入進入,注入的時機是在onPageFinished方法中注入。
String jsStr = ""; try { InputStream in = DefaultDCollectionClient.mContext.getAssets().open("jsStr.js"); byte buff[] = new byte[1024]; ByteArrayOutputStream fromFile = new ByteArrayOutputStream(); do { int numRead = in.read(buff); if (numRead <= 0) { break; } fromFile.write(buff, 0, numRead); } while (true); jsStr = fromFile.toString(); in.close(); fromFile.close(); } catch (IOException e) { e.printStackTrace(); }
在onPageFinished方法中執行如下程式碼:
@Override
public void onPageFinished(WebView view, String url)
{
super.onPageFinished(view, url);
webview.loadUrl("javascript:" + jsStr );
}
(2)第二種思路:是將本地assets中的js檔案以script標籤的形式直接注入到html中。
String js = "var newscript = document.createElement(\"script\");"; js+= "newscript.src=\"file:///android_asset/jsStr.js\";"; js += "document.body.appendChild(newscript);";
在onPageFinished方法中執行
@Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); webview.loadUrl("javascript:" + js); }
本地assets中的 js 檔案程式碼比較長我就只放個提綱在這裡。
(function(){
var device = Device = (function () { ...})();
function inject(json){...}
inject("log-test-begin");
function getInfo(element){...}
function getChain(element){...}
function addObserver(element, event, func, capture){...}
inject("log-test-before");
window.onload = function(){...}
}())
---------------------------------華麗的分割線----------------------------
上面兩種思路基本就是網上能搜到的大多數文章的思路,本人親測對於我目前的需求這兩種方式都無效,並且假如webview或者H5設定了js防注入的話,第二種方式直接就炸了,直接無效。於是乎咋辦呢??? 當前是看webview的原始碼。。。解決方案:
通過看webview的原始碼發現還有一個方法比較有意思,就是下面這個方法。
/**
* Asynchronously evaluates JavaScript in the context of the currently displayed page.
* If non-null, |resultCallback| will be invoked with any result returned from that
* execution. This method must be called on the UI thread and the callback will
* be made on the UI thread.
* <p>
* Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
* later, JavaScript state from an empty WebView is no longer persisted across navigations like
* {@link #loadUrl(String)}. For example, global variables and functions defined before calling
* {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
* {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
*
* @param script the JavaScript to execute.
* @param resultCallback A callback to be invoked when the script execution
* completes with the result of the execution (if any).
* May be null if no notificaion of the result is required.
*/
public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
checkThread();
mProvider.evaluateJavaScript(script, resultCallback);
}
他是可以在UI執行緒執行的非同步載入js的方法,於是乎就用它來試一下,程式碼如下,
// 1) 從檔案中讀取js為字串
String jsStr = "";
try {
InputStream in = DefaultDCollectionClient.mContext.getAssets().open("rattrap.js");
byte buff[] = new byte[1024];
ByteArrayOutputStream fromFile = new ByteArrayOutputStream();
do {
int numRead = in.read(buff);
if (numRead <= 0) {
break;
}
fromFile.write(buff, 0, numRead);
} while (true);
jsStr = fromFile.toString();
in.close();
fromFile.close();
} catch (IOException e) {
e.printStackTrace();
}
// 2) 將讀取的js字串注入webview中
@Override public void onPageStarted (WebView view, String url, Bitmap favicon){
if (Build.VERSION.SDK_INT >= 19) {
webView.evaluateJavascript(jsStr, new ValueCallback<String>() {
@Override public void onReceiveValue(String value) {//js與native互動的回撥函式
Logs.d(TAG, "value=" + value);
}
});
}
}
或許細心的會發現我注入的時機是在 onPageStarted 方法中 ,由於 webView.evaluateJavascript 是非同步操作,所以需要在onPageStarted 中注入,因為webview執行onPageFinished 方法時表示頁面已經載入完成,開始執行js,那麼在onPageFinished中注入的話,等我們的js注入時,H5頁面的js基本執行完畢,我們注入的js就不會生效。當然別忘了新增webView.getSettings().setJavaScriptEnabled(true); 這樣我們的js就已字串的形式注入到當前H5介面中了,當用戶在H5介面中做操作的話,我們就可以在注入的js中採集相關的資料。