
原文链接:https://my.oschina.net/cjlice/blog/1531256
刚学 Android 用 WebView 来做应用
Android Studio 下载: http://www.android-studio.org/
写此文时最新稳定版是 2.3.3,预览版 3.0.0,3.0.0 支持使用 Kotlin 进行开发,有点坑,毕竟还不够成熟
安卓开发文档:https://developer.android.google.cn/develop/index.html
网上比较流行的 js 与 java 交互解决方案:https://github.com/lzyzsd/JsBridge
百度、Google 过好多文章,都是对addJavascriptInterface和evaluateJavascript很粗暴的解释,很少具体实例代码,所以自己整理了一下,以下方案仅对 android4.4 以上可用
照着安卓官方开发文档新建一个安卓工程出来不难,就不详说了,直接上代码,看注释
MainActivity.java
package com.example.demo.mydemo; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; public class MainActivity extends AppCompatActivity { WebView view; @Override protected void onCreate(Bundle savedInstanceState) { view = new WebView(this); super.onCreate(savedInstanceState); // 注释默认视图,稍候将我们创建的 WebView 渲染出来 // setContentView(R.layut.activity_main); // 开启对 Javascript 支持,WebView 默认是不支持 Javascript 的 view.getSettings().setJavascriptEnabled(true); // 允许 chrome 浏览器进行远程调试 view.setWebContentsDebuggingEnabled(true); // 如果不重写 shouldOverrideUrlLoading 这个方法,默认使用外部浏览器打开网页 view.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { if (Build.VERSION.SDK_INT >= 21) { view.loadUrl(request.getUrl().toString()); } else { view.loadUrl(request.toString()); } return false; } }); // 注入可在 webview 用 js 调用 java 方法的对象和变量 Android view.addJavascriptInterface(new MyJs(), "Android"); // 加载首页 view.loadUrl("file:///android_asset/index.html"); // 使 activity 显示 webview 的内容 setContentView(view); } public class MyJs { // 注册给 js 用的方法 @JavascriptInterface public void showToast (final String id, final String context) { // 在 Android Monitor 的 logcat 可以查看这句 java 的输出 Log.i("toast", context); // 调用 js 的全局方法将数据返回给 js (必须用 UI 线程,否则 app 会崩溃) runOnUiThread(new Runnable() { @Override public void run() { // 返回给 js 的回调数据, // id 用于给 js 找到对应的局部回调函数, // context 应该是 java 获取到的数据,这里为了方便,直接将 js 传过来的数据返回 String data = "{id:" + id + ", result: '" + context + "'}"; //执行 js 的方法(只能执行 windows 域下的全局方法) view.evaluateJavascript("exec(" + data + ")", new ValueCallback<String>() { @Override public void onReceiveValue(String s) { // 当 exec 方法执行完成,会回调这个 java 方法 // s 是 exec 的 return 值(一般很少用到) Log.i("callback", s); } }); } }); } } // 处理返回键 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode==KeyEvent.KEYCODE_BACK) { if(view.canGoBack()) { view.goBack();//返回上一页面 return true; } else { System.exit(0);//退出程序 } } return super.onKeyDown(keyCode, event); } } 在 WebView 里必须使用 UI 线程的官方原文:https://developer.android.google.cn/guide/webapps/migrating.html#Threads
If you call methods on WebView from any thread other than your app's UI thread, it can cause unexpected results.
我自己翻译:如果你使用了非 UI 线程的其它线程去调用 WebView 里的方法,将会产生不被期待的结果。
上文我说 APP 会崩溃是实践所得。
创建index.html和index.js文件,放到 assets 文件夹里,如果项目里没 assets 文件夹,可以这样创建:File > New > Folder > Assets Folder
index.html
<html> <head> <meta charset="utf-8" /> <title>Hello Demo</title> </head> <body> <button id="a">A</button> <script src="index.js"></script> </body> </html> index.js
// 用于区别回调 var id = 0 // 回调池,存放局部回调 window.pool = [] // 给 java 调用的全局方法 window.exec = function (data) { for (var i = 0; i < pool.length; i++) { if (pool[i][0] == data.id) { pool[i][1](data.result) pool.splice(i, 1) return true } } } // 封闭 java 方法的全局空间,方便给 js 调用 window.orz = { showToast: function (msg, callback) { Android.showToast(++id, msg) pool.push([id, callback]) } } document.getElementById('a').Onclick= function () { // 经过上面的一层封装,调用原生并执行回调就很方便了 orz.showToast('Call', function (txt) { // 这里用了 console 输出,需要在谷歌浏览器 Remote devices 对应的 Inspect 的 Console 查看,不能用 alert,alert 方法还没实现 console.log(txt) }) orz.showToast('Me', function (txt) { console.log(txt) }) orz.showToast('Superman', function (txt) { console.log(txt) }) } 总结:js 和 java 交互的原理主要是用addJavascriptInterface给 WebView 注入 java 方法,让 js 可以调用,然后用evaluateJavascript执行 js 方法将数据传回 js,也可以用view.loadUrl("Javascript:...")来代替evaluateJavascript。
1 skyshy 2017 年 9 月 16 日 如果 index.html 不是为 **filte://** app 本地文件,而为 **http(s)://**的一个 URL 文件路径,如果页面里引用很多其他资源( js、css、images 和 iframe 等),怎么让页面加载到 [`DOMContentLoaded]( https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded)` 的时候就有 webview 注入的 js 呢? |
2 skyshy 2017 年 9 月 16 日 目前知道的是 webView.loadUrl 加载 http 页面的时候,webView.onPageFinished 后才能注入 js,这个对应页面的 window.load。 |