需求:
在使用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中采集相关的数据。