使用场景
Activity 中或者 Fragment 中放置一个 webview,然后使用这个 webview 来加载本地或者网络上的网页文件。如果网页文件比较复杂,那么不可避免的就要使用 js,如果 js 在他之内使用还好,如果 js 要通过 Android 程序处理数据之后再获得 Android 的返回值,该怎么办?
需求说明
webview 加载一个 html,html 里面是一个百度地图,页面上有一个按钮,点击之后通过 js 请求 Android 端进行定位,定位之后将经纬度返回到 js 中,js 将调用地图 api 将经纬度显示到当前的百度地图上面。
如何对 webview 进行设置
webview.getSettings().setJavaScriptEnabled(true); webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 当返回 true 时,你点任何链接都是失效的,需要你自己跳转。return false 时 webview 会自己跳转。 view.loadUrl(url); //我们自己加载了地址(自己进行了跳转) return true; //返回 true } });
最重要的两行代码,第一行之后设置可以和 js 进行交互了。
第二行设置网页内的内容点击后在当前应用内打开,而不是使用第三方浏览器打开。
如何去和 js 进行交互
在了解了需求之后,首先要知道的就是如何和 js 进行交互。
js 调用 Android 方法
一个按钮,点击后想要调用 Android 中的方法,按钮的代码如下
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="file:///android_asset/out2.js"></script> </head> <body> 1: <input type="text" id="field1" value="Hello World!"><br> 2: <input type="text" id="field2"><br><br> <button onclick="copyText()"> 复制文本</button> <p> 当按钮被单击时触发函数。此函数把文本从 Field1 复制到 Field2 中。</p> </body> </html>
当按钮被单击时触发函数。此函数把文本从 Field1 复制到 Field2 中。
JS 代码:
function copyText() { Android.getLocation("88"); }
发现了和其他 js 不同的地方了吧,在 js 中使用了这样的代码:
Android.getLocation("88");
注意开头的 Android 这个单词(这个单词是可以换的,而且,随便换)
当然现在还不能开始调用 Android 中的代码。
我们还需要对 webview 进行一下设置:
JavaScriptInterface jsif=new JavaScriptInterface(); webview.addJavascriptInterface(jsif, "Android");//这句话里面的第二个参数那里写什么,在 js 里面就写什么,可以理解为这里给 js 传递了一个 java 对象
JavaScriptInterface 的代码(这个类的名称也可以随便换)
public class JavaScriptInterface{ @JavascriptInterface public void getLocation(String str){ System.out.println(str); } }
所以,可以看到,在 js 里面调用的
Android.getLocation("88");
其中,Android 来自于
webview.addJavascriptInterface(jsif, "Android");
的第二个参数,而它后面的方法名,来自于 JavaScriptInterface 类中的方法名,至于参数的传递,类似于 java。
写完这些之后,就可以运行了。(如果不能运行,看看是不是忘了给 JavaScriptInterface 中的方法添加这个 “@JavascriptInterface”【添加原因详解】)
测试代码下载JS 调用 Android 的第二种方法
在 JS 中常用的方法有 alert,Prompt 等(可能是我别的不会,觉得这两个常用。。)
通过第一种方法当然可行。
下面来看看针对 alert,prompt 等的第二种方法。
首先来看 js 中的代码
function copyText() { document.getElementById("field2").value=prompt(document.getElementById("field1").value); }
html 中的代码没有变,再看看 Android 中添加的代码
webview.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // 本方法于 2015 年 8 月 17 日 下午 9:21:51 由 sumile 建立 System.out.println(message + "--->" + url); result.confirm(message + "添加了东西"); return true; } });
代码运行之后的结果:
08-21 09:33:53.160: I/System.out(31749): Hello World!—>file:///
第二个输入框中的内容变成了”Hello World! 添加了东西”
就是说,在 webview 中,某些特定名称的 js 方法可以在 Android 中被截获,如 prompt,alert 等。
prompt 在 js 中的定义是这样的:
prompt() 方法用于显示可提示用户进行输入的对话框。
语法
prompt(text,defaultText)
参数 描述 text 可选。要在对话框中显示的纯文本(而不是 HTML 格式的文本)。 defaultText 可选。默认的输入文本。 说明
如果用户单击提示框的取消按钮,则返回 null。如果用户单击确认按钮,则返回输入字段当前显示的文本。
在用户点击确定按钮或取消按钮把对话框关闭之前,它将阻止用户对浏览器的所有输入。在调用 prompt() 时,将暂停对 JavaScript 代码的执行,在用户作出响应之前,不会执行下一条语句。
那么这样,我们在 Android 中通过 onJsPrompt 方法,实际上就实现了一个 js 与 Android 的同步交互。
在这句话中
document.getElementById("field2").value=prompt(document.getElementById("field1").value);
js 执行了等号后面的话之后,会等待 Android 的返回结果,然后将这个返回结果赋值给等号前面。
其次,我们还看到 Android 的 onJsPrompt 方法有一个返回值,那么这个返回值的含义是什么?
prompt 这个东东本来是会弹出一个对话框的,可以点击确定或者取消
返回 true,不会继续弹出对话框
返回 false,你猜。
这种方法中关于 alert 的请看测试代码,其他的,请自行 google
测试代码下载Android 调用 js 中的” 方法”。
看代码,一看你就懂了,记得 js 里面的方法名字么?忘了就回去看看。
button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { webview.loadUrl("javascript:copyText()"); } });
JS 调用 Android 方法,Android 去执行异步操作,JS 等待到返回值后才可以继续运行
这是一个新问题,其实也是我写这篇文章要解决的问题。
从上面的 JS 调用 Android 以及 Android 调用 js 来看,我们可以将这两种方法进行组合来完成这个功能:
首先通过 js 调用 Android 中的方法,Android 去执行异步任务,异步任务执行完后主动调用 js 中的方法将值传递给 js。
我不想用。。。
换!
然后我就想了,js 调用 Android 方法可以得到返回值,可以通过 prompt 去做,但是在 onJsPrompt 中是同步去做的,就是不等我异步处理完结果,就得返回值了,那该怎么做。
让 js 去频繁的请求当前的这个方法,知道获得返回值。
下面是我的例子:
///////////////////////////////////////////////////////// JS 代码 ///////////////////////////////////////////////////////// function getLocation() { var j=prompt("x","start"); var lan="false"; if(j=="false"){ do{ lan=prompt("y","get"); }while(lan=="false"); } var arr=lan.split(","); document.getElementById("latitude").value=arr[0]; document.getElementById("longitude").value=arr[1]; theLocation(); } ///////////////////////////////////////////////////////// Android 代码 ///////////////////////////////////////////////////////// public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) { System.out.println(message + "--->" + url + "--->" + defaultValue); if ("start".equals(defaultValue)) { initLocation(); mLocationClient.start();// 定位 SDK // start 之后会默认发起一次定位请求,开发者无须判断 isstart 并主动调用 request mLocationClient.requestLocation(); result.confirm("false"); } if ("get".equals(defaultValue)) { ((LocationApplication) getApplication()).setLocationInterface(new BDLocationInterface() { @Override public void getLocation(BDLocation location) { newStr = String.valueOf(location.getLatitude()) + "," + String.valueOf(location.getLongitude()); System.out.println(newStr); } }); if (!"".equals(newStr)) { result.confirm(newStr); mLocationClient.stop(); System.out.println("confirm"); return true; } } result.confirm("false"); return true; }上面的代码是 js 中点击按钮后的代码以及 Android 中 onJsPrompt 中的代码,首先会先请求一次 prompt 方法,可以看到它的第二个参数是”start”,这时候 Android 开始执行,但是因为是异步执行,并没有返回值,所以我主动给他返回了 false 字符串。然后开始循环调用 prompt 方法,这时候的第二个参数现在是 get。通过 start,get 来在 Android 中区分是应该开启异步任务还是检测异步任务是否已有结果。最后,终于等到你(返回值),还好我不放弃(while)。
下面的代码请在测试之前首先在 AndroidManifest 和 assets 下面的 baidujs.html 中将 key 替换掉。否则不能调用百度地图,但可能不影响测试。
测试代码下载Mission completed
转载请注明:热爱改变生活.cn » Android 中 Webview 与 JS 交互
本博客只要没有注明“转”,那么均为原创。 转载请注明链接:sumile.cn » Android 中 Webview 与 JS 交互