JS交互中的线程间协作浅析

引言

当我们希望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("<!DOCTYPE html><title></title>", "text/html", null);
 * webView.loadUrl("javascript:alert(injectedObject.toString())");

出于安全性考虑,在API 16及以上的系统中,只有带有@JavascriptInterface注解的public方法才能被JS调用。

简单使用

下面我们通过示例实现交互,在JS中调用APP上的toLogin()方法,获取登录信息。

  • 设置WebView
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());,之后新线程等待对象锁。
  • 3s后执行到Log.i("Thread_3", Thread.currentThread().toString());,之后主线程唤醒新线程。
  • 再执行到Log.i("Thread_2", Thread.currentThread().toString());,新线程继续执行代码,返回结果给网页。

根据输出结果,可以看到真的实现了刚才的需求,并且JS交互时新开了线程执行,新线程名为JavaBridge

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值