WebView-线程

时间:2018-06-22 13:27:36

标签: android multithreading webview

[最后编辑。仍不确定应如何执行给出的答案。]

我无法理解Android应用中的线程问题。该应用程序基本上是一个html网页,我需要在webview和android应用程序之间来回移动以进行加载和保存。我希望网页处理所有消息(因为我有一个统一的CSS-为了简单起见,我在下面仅使用Alert)。

当我单击诸如保存之类的按钮时的主要错误:

W/WebView: java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {527e3354} called on Looper (JavaBridge, tid 109) {527ece88}, FYI main Looper is Looper (main, tid 1) {527e3354})
           at android.webkit.WebView.checkThread(WebView.java:2072)
           at android.webkit.WebView.evaluateJavascript(WebView.java:903)
           at com.example.jon.androidhtmltemplate.WebAppInterface.save(WebAppInterface.java:116)
           at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
           at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:24)
           at android.os.Handler.dispatchMessage(Handler.java:102)
           at android.os.Looper.loop(Looper.java:136)
           at android.os.HandlerThread.run(HandlerThread.java:61)
W/System.err: java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {527e3354} called on Looper (JavaBridge, tid 109) {527ece88}, FYI main Looper is Looper (main, tid 1) {527e3354})
W/System.err:     at android.webkit.WebView.checkThread(WebView.java:2082)
              at android.webkit.WebView.evaluateJavascript(WebView.java:903)
W/System.err:     at com.example.jon.androidhtmltemplate.WebAppInterface.save(WebAppInterface.java:116)
              at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
              at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:24)
              at android.os.Handler.dispatchMessage(Handler.java:102)
              at android.os.Looper.loop(Looper.java:136)
              at android.os.HandlerThread.run(HandlerThread.java:61)
          Caused by: java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {527e3354} called on Looper (JavaBridge, tid 109) {527ece88}, FYI main Looper is Looper (main, tid 1) {527e3354})
              at android.webkit.WebView.checkThread(WebView.java:2072)
W/System.err:   ... 7 more
I/chromium: [INFO:CONSOLE(21)] "Uncaught Error: Error calling method on NPObject.", source: file:///android_asset/www/index.html (21)
I/Choreographer: Skipped 135 frames!  The application may be doing too much work on its main thread.
I/Choreographer: Skipped 58 frames!  The application may be doing too much work on its main thread.

我在启动时会收到类似这样的奇怪警告(他们应该关心我)吗?:

I/chromium: [INFO:async_pixel_transfer_manager_android.cc(60)] Async pixel transfers not supported
E/dalvikvm: Could not find class 'android.support.v4.view.ViewCompat$OnUnhandledKeyEventListenerWrapper', referenced from method android.support.v4.view.ViewCompat.addOnUnhandledKeyEventListener
E/dalvikvm: Could not find class 'android.view.WindowInsets', referenced from method android.support.v4.view.ViewCompat.dispatchApplyWindowInsets
E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method android.support.v7.widget.AppCompatImageHelper.hasOverlappingRendering
I/chromium: [INFO:async_pixel_transfer_manager_android.cc(60)] Async pixel transfers not supported
I/Choreographer: Skipped 396 frames!  The application may be doing too much work on its main thread.

这是我的代码。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>

mainactivity.java

package com.example.jon.androidhtmltemplate;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebChromeClient;
import android.webkit.WebView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView webview = (WebView)findViewById(R.id.webView);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.setWebChromeClient(new WebChromeClient());
        webview.loadUrl("file:///android_asset/www/index.html");

        webview.addJavascriptInterface(new WebAppInterface(this, webview), "Android");
    }
}

webappinterface.java

package com.example.jon.androidhtmltemplate;

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.webkit.WebView;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

// This is my class to handle interaction between my webpage and the Android app
public class WebAppInterface {
    Context mContext;
    WebView webview;

    /** Instantiate the interface and set the context */
    WebAppInterface(Context c, WebView w) {
        mContext = c;
        webview = w;
    }

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    /* Checks if external storage is available to at least read */
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    @android.webkit.JavascriptInterface
    public void load(boolean isLoadExternal){
        String ret, script;

        ret = loadLocalExternal(isLoadExternal);

        script = "javascript:CallbackAndroidLoad(\"" + ret + "\")";
        if (Build.VERSION.SDK_INT >= 19 /*Might need 21*/) {
            webview.evaluateJavascript(script, null);
        }else {
            webview.loadUrl(script);
        }
    }

    @android.webkit.JavascriptInterface
    public String loadLocalExternal(boolean isExternal) {
        int length;
        String ret = "";
        File path = isExternal ? mContext.getExternalFilesDir(null) : mContext.getFilesDir();
        String filename = "myappsavefile.glf";
        File file = new File(path, filename);
        FileInputStream in;
        byte[] bytes;

        try {
            length = (int) file.length();
            bytes = new byte[length];
            in = new FileInputStream(file);
            in.read(bytes);
            in.close();
            ret = new String(bytes);
        } catch (Exception e) {
            ret = "";
        }

        return ret;
    }

    @android.webkit.JavascriptInterface
    public void save(boolean saveLocal, boolean saveExternal, String fileContent){
        boolean ret = true;
        String script;

        if(ret && saveLocal && !saveLocalExternal(false, fileContent + " local")) ret = false;
        if(ret && saveExternal && !saveLocalExternal(true, fileContent + " external")) ret = false;

        script = "javascript: CallbackAndroidSave(\"" + (ret ? "true" : "false") + "\")";
        if (Build.VERSION.SDK_INT >= 19 /*Might need 21*/) {
            webview.evaluateJavascript(script, null);
        }else {
            webview.loadUrl(script);
        }
    }

    @android.webkit.JavascriptInterface
    public boolean saveLocalExternal(boolean isExternal, String fileContent) {
        boolean ret = true;
        File path = isExternal ? mContext.getExternalFilesDir(null) : mContext.getFilesDir();
        String filename = "chartmygolf.glf";
        File file = new File(path, filename);
        FileOutputStream outputStream;

        try {
            outputStream = mContext.openFileOutput(filename, Context.MODE_PRIVATE);
            outputStream.write(fileContent.getBytes());
            outputStream.close();
        } catch (Exception e) {
            //e.printStackTrace();
            ret = false;
        }
        return ret;
    }
}

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Page Title</title>
</head>
<body>

<h1>This is a Heading</h1>
<p>This is a paragraph with very interesting content.</p>
<p id="id">This is another paragraph.</p>
<button onclick="save()">Save</button>
<button onclick="loadLocal()">Load Local</button>
<button onclick="loadExternal()">Load External</button>

</body>
<script>
var isAndroid = /Android/i.test(navigator.userAgent);
document.getElementById("id").innerHTML = "This is on Android";

function save(){
    Android.save(true, true, "File Contents 1");
};

function loadLocal(){
    Android.load(false);
};

function loadExternal(){
    Android.load(true);
};

// ---

function CallbackAndroidLoad(fileContents){
    document.getElementById("id").innerHTML = fileContents.length > 0 ? "Error: Load Fail" : ("Contents: " + fileContents);
};

function CallbackAndroidSave(isSuccess){
    alert(isSuccess ? "Success" : "Failure");
};

</script>
</html>

编辑(如Sagar所建议):

我是Android开发的新手(我是Html / Javascript程序员),所以对您的帖子感到有些困惑。您给出2条建议。我不确定Callback是否属于您的第二个建议,因此目前我将坚持您的第一个建议。

问::对于您的 第一 建议,我需要implements Callback课程上的MainActivity吗?

问:,当您提出第一个建议时,似乎您是说只要有回HTML页的调用,我就需要重写public class WebAppInterface函数。是吗?

所以我猜我的新load函数将变为:

@android.webkit.JavascriptInterface
public void load(boolean isLoadExternal){
    String ret, script;

    ret = loadLocalExternal(isLoadExternal);

    script = "javascript:CallbackAndroidLoad(\"" + ret + "\")";

    // This is the only bit of the function with a call back in it
    // so I need to do the special stuff on this bit alone
    activityObj.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (Build.VERSION.SDK_INT >= 19 /*Might need 21*/) {
                 webview.evaluateJavascript(script, null);
            }else {
                webview.loadUrl(script);
        }
    }
    });
}

问:activityObj是什么?它从何而来?如果必须通过调用函数传递它,该怎么办?

问::我在代码中的注释正确吗:This is the only bit of the function with a call back in it so I need to do the special stuff on this bit alone

1 个答案:

答案 0 :(得分:1)

JavascriptInterface方法是在后台线程而不是UI线程上调用的。在处理UI时,您需要使用主UI线程。

由于要在单独的类中执行它,因此需要传递Activity对象,然后传递所有JavascriptInterface方法:

按如下所示更改WebAppInterface类:

public class WebAppInterface {
    Activity activityObj;
    WebView webview;

    /** Instantiate the interface and set the context */
    WebAppInterface(Activity activityObj, WebView w) {
        mContext = c;
        this.activityObj = activityObj;
    }   
    ...
}

和runOnUiThread如下:

activityObj.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        if (Build.VERSION.SDK_INT >= 19 /*Might need 21*/) {
                webview.evaluateJavascript(script, null);
        }else {
            webview.loadUrl(script);
        }
    }
});

或者,您可以实现一些回调接口,并在Activity本身中执行以下操作:

public interface Callback {
    void loadLocalExternal(boolean isExternal);
    //define other methods.
}

MainActivity.java

public class MainActivity extends AppCompatActivity implements Callback{

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView webview = (WebView)findViewById(R.id.webView);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.setWebChromeClient(new WebChromeClient());
        webview.loadUrl("file:///android_asset/www/index.html");

        webview.addJavascriptInterface(new WebAppInterface(this, this), "Android");
    }

    @Override
    public void loadLocalExternal(boolean isExternal){
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //Do your action here
            }
        });
    }

}

WebAppInterface.java

public class WebAppInterface {
    Context mContext;
    private final Callback callback;

    /** Instantiate the interface and set the context */
    WebAppInterface(Context c, Callback callback) {
        mContext = c;
        this.callback = callback;
    }
    @android.webkit.JavascriptInterface
    public String loadLocalExternal(boolean isExternal) {
        callback.loadLocalExternal(isExternal);
    }
    ...
}