当我们希望APP和网页有交互时,可以利用WebView中的addJavascriptInterface(Object object, String name)
方法来实现。按照该方法的源码说明,它可以将Java对象注入到JavaScript context
,这样JavaScript
就可以使用这个Java对象的方法。
参数object
表示注入到JS的对象。
参数name
表示在JS中调用该对象时所使用的名字。
下面的代码是该方法源码说明中的用法示例:
Note that injected objects will not appear in JavaScript until the page is next (re)loaded. For example:
* class JsObject {
* {@literal @}JavascriptInterface
* public String toString() { return "injectedObject"; }
* }
* webView.addJavascriptInterface(new JsObject(), "injectedObject");
* webView.loadData(" ", "text/html", null);
* webView.loadUrl("javascript:alert(injectedObject.toString())");
出于安全性考虑,在API 16及以上的系统中,只有带有@JavascriptInterface
注解的public
方法才能被JS调用。
下面我们通过示例实现交互,在JS中调用APP上的toLogin()
方法,获取登录信息。
webView.addJavascriptInterface(new JsObject(), "app");
webView.loadUrl(url);
JsObject
类,并添加toLogin()
方法 public class JsObject {
@JavascriptInterface
public String toLogin() {
String result = "just test";
return result;
}
}
这样写好之后JS中就可以调用了。
JS中的调用代码略略略,不在APP讨论之列。
addJavascriptInterface(Object object, String name)
方法的源码说明中有这么一段:
JavaScript interacts with Java object on a private, background thread of this WebView. Care is therefore required to maintain thread safety.
从中我们可以得知,JS交互中的代码会运行在一个新线程中,因此需要注意线程安全。
上面的例子中,调用toLogin()
方法直接就返回结果了。
情况如果比这复杂点:需要新开一个界面,用户在新界面中进行一系列的操作(操作时间不可预知)才会产生所需结果,重新返回WebView界面才将结果返回给JS。该怎么实现呢?
因为WebView界面需要等待用户操作完成,等待时间是未知的,所以需要想办法将这个界面的代码阻塞住,返回结果后再继续执行。
刚才已经得知,JS交互中的代码会新开线程执行,所以我们可以利用线程协作来实现该需求。
修改上面的代码,其中还添加了一些打印信息,用以观察执行顺序,还能验证是否真的新开了线程:
Object obj = new Object();
public class JsObject {
@JavascriptInterface
public String toLogin() {
String result = "";
//跳转至登录页面
Intent intent = new Intent(WebViewActivity.this, LoginActivity.class);
WebViewActivity.this.startActivityForResult(intent, 0);
try {
synchronized (obj) {
Log.i("Thread_1", Thread.currentThread().toString());
//使该线程进入等待
obj.wait();
Log.i("Thread_2", Thread.currentThread().toString());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//该线程获取到了对象锁,继续执行
// 假设已经从别处获取到了结果
result = "got the result somewhere else";
//返回结果给H5页面
return result;
}
}
新界面LoginActivity
的代码如下,进入3秒后关闭,返回到WebView界面:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 3000);
}
从新界面LoginActivity
返回时,唤醒刚才在等待的线程:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 0:
synchronized (obj) {
Log.i("Thread_3", Thread.currentThread().toString());
//唤醒等待的线程
obj.notify();
}
break;
default:
}
}
执行后,我们可以看到如下的输出结果:
08-31 14:43:57.870 30511-30972/com.*** I/Thread_1: Thread[JavaBridge,5,main]
08-31 14:44:00.910 30511-30511/com.*** I/Thread_3: Thread[main,5,main]
08-31 14:44:00.910 30511-30972/com.*** I/Thread_2: Thread[JavaBridge,5,main]
分析一下整个流程:
Log.i("Thread_1", Thread.currentThread().toString());
,之后新线程等待对象锁。Log.i("Thread_3", Thread.currentThread().toString());
,之后主线程唤醒新线程。Log.i("Thread_2", Thread.currentThread().toString());
,新线程继续执行代码,返回结果给网页。根据输出结果,可以看到真的实现了刚才的需求,并且JS交互时新开了线程执行,新线程名为JavaBridge
。