Android主线程阻塞WebView线程 [英] Android main thread blocking WebView thread

查看:236
本文介绍了Android主线程阻塞WebView线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在研究在 WebView (带有返回值)中对JavaScript进行同步调用的问题,并尝试缩小其中的位置和原因为什么它不起作用。似乎是 WebView 线程在主线程正在等待来自它的响应时阻塞 - 自从 WebView 在一个单独的线程上运行。

I've been working on a problem with doing a synchronous call to JavaScript in a WebView (with a return value) and trying to narrow down the where and why of why it's not working. It seems to be that the WebView thread is blocking while the main thread is waiting for a response from it -- which shouldn't be the case since theWebView runs on a separate thread.

我把这个小样本放在一起,相当清楚地展示了它(我希望):

I've put together this small sample that demonstrates it (I hope) fairly clearly:

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:weightSum="1">

    <WebView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:id="@+id/webView"/>
</LinearLayout>

MyActivity.java:

package com.example.myapp;

import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.JavascriptInterface;
import android.webkit.WebViewClient;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class MyActivity extends Activity {

    public final static String TAG = "MyActivity";

    private WebView webView;
    private JSInterface JS;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        webView = (WebView)findViewById(R.id.webView);
        JS = new JSInterface();

        webView.addJavascriptInterface(JS, JS.getInterfaceName());

        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);

        webView.setWebViewClient(new WebViewClient() {
             public void onPageFinished(WebView view, String url) {
                 Log.d(TAG, JS.getEval("test()"));
             }
         });

        webView.loadData("<script>function test() {JSInterface.log(\"returning Success\"); return 'Success';}</script>Test", "text/html", "UTF-8");
    }


    private class JSInterface {

        private static final String TAG = "JSInterface";

        private final String interfaceName = "JSInterface";
        private CountDownLatch latch;
        private String returnValue;

        public JSInterface() {
        }

        public String getInterfaceName() {
            return interfaceName;
        }

        // JS-side functions can call JSInterface.log() to log to logcat

        @JavascriptInterface
        public void log(String str) {
            // log() gets called from Javascript
            Log.i(TAG, str);
        }

        // JS-side functions will indirectly call setValue() via getEval()'s try block, below

        @JavascriptInterface
        public void setValue(String value) {
            // setValue() receives the value from Javascript
            Log.d(TAG, "setValue(): " + value);
            returnValue = value;
            latch.countDown();
        }

        // getEval() is for when you need to evaluate JS code and get the return value back

        public String getEval(String js) {
            Log.d(TAG, "getEval(): " + js);
            returnValue = null;
            latch = new CountDownLatch(1);
            final String code = interfaceName
                    + ".setValue(function(){try{return " + js
                    + "+\"\";}catch(js_eval_err){return '';}}());";
            Log.d(TAG, "getEval(): " + code);

            // It doesn't actually matter which one we use; neither works:
            if (Build.VERSION.SDK_INT >= 19)
                webView.evaluateJavascript(code, null);
            else
                webView.loadUrl("javascript:" + code);

            // The problem is that latch.await() appears to block, not allowing the JavaBridge
            // thread to run -- i.e., to call setValue() and therefore latch.countDown() --
            // so latch.await() always runs until it times out and getEval() returns ""

            try {
                // Set a 4 second timeout for the worst/longest possible case
                latch.await(4, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Log.e(TAG, "InterruptedException");
            }
            if (returnValue == null) {
                Log.i(TAG, "getEval(): Timed out waiting for response");
                returnValue = "";
            }
            Log.d(TAG, "getEval() = " + returnValue);
            return returnValue;
        }

        // eval() is for when you need to run some JS code and don't care about any return value

        public void eval(String js) {
            // No return value
            Log.d(TAG, "eval(): " + js);
            if (Build.VERSION.SDK_INT >= 19)
                webView.evaluateJavascript(js, null);
            else
                webView.loadUrl("javascript:" + js);
        }
    }
}

运行时,结果如下:

Emulator Nexus 5 API 23:

05-25 13:34:46.222 16073-16073/com.example.myapp D/JSInterface: getEval(): test()
05-25 13:34:50.224 16073-16073/com.example.myapp I/JSInterface: getEval(): Timed out waiting for response
05-25 13:34:50.224 16073-16073/com.example.myapp D/JSInterface: getEval() = 
05-25 13:34:50.225 16073-16073/com.example.myapp I/Choreographer: Skipped 239 frames!  The application may be doing too much work on its main thread.
05-25 13:34:50.235 16073-16150/com.example.myapp I/JSInterface: returning Success
05-25 13:34:50.237 16073-16150/com.example.myapp D/JSInterface: setValue(): Success

(16073是'main'; 16150是'JavaBridge ')

如您所见,主线程超时等待 WebView 调用 setValue(),直到 latch.await()超时且主线程执行仍在继续。

As you can see, the main thread times out waiting for theWebView to call setValue(), which it doesn't until latch.await() has timed out and main thread execution has continued.

有趣的是,尝试使用较早的API级别:

Interestingly, trying with an earlier API level:

Emulator Nexus S API 14:

05-25 13:37:15.225 19458-19458/com.example.myapp D/JSInterface: getEval(): test()
05-25 13:37:15.235 19458-19543/com.example.myapp I/JSInterface: returning Success
05-25 13:37:15.235 19458-19543/com.example.myapp D/JSInterface: setValue(): Success
05-25 13:37:15.235 19458-19458/com.example.myapp D/JSInterface: getEval() = Success
05-25 13:37:15.235 19458-19458/com.example.myapp D/MyActivity: Success

(19458是'主要'; 19543是'JavaBridge')

事情按顺序正常工作, getEval()导致 WebView 调用 setValue(),然后退出 latch.await()在它超时之前(正如你期望/希望的那样)。

Things work correctly in sequence, with getEval() causing the WebView to call setValue(), which then exits latch.await() before it times out (as you'd expect/hope).

(我也试过更早的API级别,但事情崩溃了根据我的理解,这可能是2.3.3中一个从未得到修复的模拟器错误。)

(I've also tried with an even earlier API level, but things crash out due to what may be, as I understand it, an emulator-only bug in 2.3.3 that never got fixed.)

所以我有点像失利。在挖掘中,这似乎是正确的做事方法。它当然似乎正确的方法,因为它在API级别14上正常工作。但是后来的版本失败了 - 我在5.1和6.0上测试没有成功。

So I'm at a bit of a loss. In digging around, this seems like the correct approach to doing things. It certainly seems like the correct approach because it works properly on API level 14. But then it's failing on later versions — and I've tested on 5.1 and 6.0 without success.

推荐答案

了解有关使用Android 4.4迁移WebView的更多信息。
查看Android文档说明我认为您需要使用其他方法用于娱乐你的JS动作。

Look more about migration WebView with Android 4.4. See description on Android Docs I think you need to use another method for funning your JS action.

例如,基于该文档 - 运行JS异步在当前显示的页面的上下文中异步评估JavaScript。如果非null,| resultCallback |将使用该执行返回的任何结果调用。必须在UI线程上调用此方法,并且将在UI线程上进行回调。

For example, base on that doc - Running JS Async 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.

这篇关于Android主线程阻塞WebView线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆