javascript - examples - Hilo principal de Android bloqueando hilo de WebView
web view android (1)
Mira más sobre la migración de WebView con Android 4.4. Consulte la descripción en Android Docs . Creo que necesita usar otro método para organizar su acción JS.
Por ejemplo, base en ese documento: la ejecución de JS Async evalúa de forma asíncrona JavaScript en el contexto de la página que se muestra actualmente. Si no es nulo, | resultCallback | se invocará con cualquier resultado devuelto de esa ejecución. Este método debe llamarse en el subproceso de la interfaz de usuario y la devolución de llamada se realizará en el subproceso de la interfaz de usuario.
He estado trabajando en un problema al hacer una llamada sincrónica a JavaScript en una vista WebView
(con un valor de retorno) y al intentar reducir el por qué y por qué no funciona. Parece ser que el subproceso de WebView
está bloqueando mientras que el subproceso principal está esperando una respuesta, lo que no debería ser así, ya que el WebView
ejecuta en un subproceso separado.
He reunido esta pequeña muestra que lo demuestra (espero) con bastante claridad:
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);
}
}
}
Cuando se ejecuta, los siguientes resultados:
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 es ''main''; 16150 es ''JavaBridge'')
Como puede ver, el hilo principal agota el tiempo de espera para que WebView
llame a setValue()
, lo que no ocurre hasta que latch.await()
y la ejecución del hilo principal continúa.
Curiosamente, probando con un nivel de API anterior:
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 es ''main''; 19543 es ''JavaBridge'')
Las cosas funcionan correctamente en secuencia, con getEval()
hace que WebView
llame a setValue()
, que luego sale de latch.await()
antes de que se agote (como era de esperar / esperar).
(También lo he intentado con un nivel de API incluso anterior, pero las cosas se caen debido a lo que podría ser, según tengo entendido, un error solo para el emulador en 2.3.3 que nunca se solucionó).
Así que estoy en una pequeña pérdida. Al excavar, este parece ser el enfoque correcto para hacer las cosas. Ciertamente, parece ser el enfoque correcto porque funciona correctamente en el nivel de API 14. Pero luego falla en versiones posteriores, y he probado en 5.1 y 6.0 sin éxito.