Основной поток, блокирующий поток веб-приложений Android

Я работаю над проблемой синхронного вызова JavaScript в WebView (с возвращаемым значением) и пытается сузить место и почему он не работает. Похоже, что поток WebView блокируется, в то время как основной поток ожидает ответа от него, что не должно иметь место, поскольку WebView работает в отдельном потоке.

Я собрал этот небольшой образец, который демонстрирует его (я надеюсь) довольно четко:

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 -- ie, 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 является «основным», 16150 – «JavaBridge»)

Как вы можете видеть, основной поток истекает, ожидая, пока WebView вызовет setValue() , который он не делает до latch.await() пор, пока latch.await() не latch.await() , и продолжение основного потока продолжается.

Интересно, что с более ранним уровнем API:

 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() прежде чем latch.await() время ожидания (как вы ожидали / надеетесь).

(Я также пробовал с еще более ранним уровнем API, но все из-за того, что может быть, как я понимаю, ошибкой эмулятора в версии 2.3.3, которая никогда не исправлялась).

Поэтому я немного потерял. При копании это похоже на правильный подход к тому, чтобы делать вещи. Это, безусловно, похоже на правильный подход, потому что он работает правильно на уровне API 14. Но потом он терпит неудачу в более поздних версиях – и я успешно тестировал 5.1 и 6.0 без успеха.

Узнайте больше о миграции WebView с Android 4.4. См. Описание на Android-документах. Думаю, вам нужно использовать другой метод для усиления вашего действия JS.

Например, база на этом документе – Запуск JS Async Асинхронно оценивает JavaScript в контексте отображаемой на данный момент страницы. Если не null, | resultCallback | Будет вызываться с любым результатом, возвращенным из этого выполнения. Этот метод должен быть вызван в потоке пользовательского интерфейса, и обратный вызов будет выполняться в потоке пользовательского интерфейса.

Intereting Posts
Как непрерывно запускать эффект тени через тексты в виде андроидного текста? Как выбрать конкретное приложение для обработки намерений Заголовок и нижний колонтитул RecyclerView CSS Media Query – Soft-клавиатура нарушает правила ориентации CSS – альтернативное решение? Как создать пользовательское уведомление Push для моего собственного приложения? Android localStorage исчезает после стирания приложения из ОЗУ Как передать параметр в подкласс BroadcastReceiver? Исключение: обратные вызовы должны устанавливать границы родительских объектов в populateNodeForVirtualViewId () Как разместить мои библиотеки перед android.jar, отредактировав build.gradle в Android-Studio Время работы приложения Android: может быть, оно бесконечно? Как обновить ListView динамически? Android Gradle Apache HttpClient не существует? Связь между действиями: намерение или услуга: что быстрее? Как рисовать изображение на поверхностиview android Хранение растрового изображения на SD-карте, потерявшее качество изображения