闩锁(用于等待异步响应)冻结 WebView(和 UI)

Posted

技术标签:

【中文标题】闩锁(用于等待异步响应)冻结 WebView(和 UI)【英文标题】:latch(used for awaiting async response) freezes the WebView (and the UI) 【发布时间】:2017-08-22 19:05:17 【问题描述】:

我有一个在整个布局上显示 web 视图的应用程序。有时我需要调用一个异步方法,该异步操作是由一个 3 方 sdk 完成的,然后我等待它一段时间,直到我得到指定侦听器的响应。我已经用一个锁存器解决了它——一旦在侦听器中收到响应,我就倒计时锁存器,然后启动方法可以继续这个响应。 不幸的是,当我这样做时,WebView 卡住了。我以为原生 UI 会卡住,但没想到 webview 也会被冻结。 如何克服?

为了更清楚,这里有一个例子。 我需要 ajaxFunc 等到 MyAsyncListener 得到某个响应,然后返回这个确切的响应。

我注入到 webview 的 JS 的一部分:

var response = jsHandler.ajaxFunc(request.data);

我有一个名为 response 的变量全局变量。

public class javascriptInterface

     @JavascriptInterface
     public String ajaxFunc(String data)
     
         return MyUtils.handleData( data );
     

handleData 方法:

 public String handleData( String data )
 
     SomeClass.startAsyncRequest(); // starts the async request, response is to come at the listener's callbacks .

     latch = new CountDownLatch(1);
     try 
         latch.await(30, TimeUnit.SECONDS);
     
     catch (InterruptedException e) 
         e.printStackTrace();
     

     return response;
   

现在,一旦handleData 函数调用了一个异步执行某些操作的函数,异步函数就会在某个监听器中返回一些答案:

myAsyncListener = new MyAsyncListener() 
    @Override
        public Response handleEvent(CustomEvent event) 
            //Now I need to return this 'event' back to the handData/ajaxFunc function <-- 

            response = event.getName();
            latch.countDown();

        
    );

【问题讨论】:

"我需要 ajaxFunc 等待..." 为什么? 因为我在 javascript 中有这一行:var response = jsHandler.ajaxFunc(request.data); 在您的代码示例中,哪一个是第 3 方呼叫? 而且,正如我在回答中所说,webview 会冻结因为你正在让它等待一个闩锁 【参考方案1】:

我需要 ajaxFunc 等到 MyAsyncListener 得到某个响应,然后返回这个确切的响应。

由于您正在分派异步请求,因此您不应期望响应以与启动异步代码相同的方法调用到达。如果这样做,调用者线程将阻塞,直到异步响应到达,也就是说,它根本不会是异步的。

您可以将代码重新设计成这样:

public class JavaScriptInterface 
    @JavascriptInterface
    public void ajaxFunc(String data) 
        // draw a waiting animation or something
        MyUtils.handleData(data);
    

public void handleData(String data) 
    SomeClass.startAsyncRequest();

myAsyncListener = new MyAsyncListener() 
    @Override
    public void handleEvent(CustomEvent event) 
        // Do what you need to do
        // (update UI, call javascript etc.).
        //
        // Mind the thread that will execute this callback
        // to prevent multithreading issues.
    

【讨论】:

【参考方案2】:

您已将CountDownLatch 放入您的代码中,这将导致您的主线程阻塞。这就是 UI 滞后的原因。

为了更好地你应该在做异步任务的主要工作之前显示一些ProgressDialog,通常用异步任务的doInBackground()方法编写并在完成任务后隐藏它,Async为此提供了onPostExecute()方法。

如果您需要像 CountDownLatch 那样设置一些超时,您可以在 Async 的 doInBackground() 中编写计时器。该计时器将停止您当前的异步分配时间。

更新,您可以将 EventBus 用于您的目的 -

EventBus 适用于发布/订阅模式。您可以在Activity 订阅并从JavaScriptInterface 发布结果。

使用 - 将其添加到 gradle 中

compile 'org.greenrobot:eventbus:3.0.0'

在你的Activity开始订阅,一般是在onCreateActivity方法中完成订阅-

EventBus.getDefault().register(this);

还可以删除ActivityonDestroy 方法中的订阅,或者如果您的工作已完成。

EventBus.getDefault().unregister(this);

要收听订阅,请使用Activity 中的以下代码 -

@Subscribe(threadMode = ThreadMode.MAIN)  
     public void onMessageEvent(MyMessageEvent event) 
     // Do your work after getting result from ajaxFunc method
 ;

您现在只需从您的方法中发布消息通知,如果其他地方不需要它,请从您的代码中删除 CountDown 闩锁。

myAsyncListener = new MyAsyncListener() 
    @Override
        public Response handleEvent(CustomEvent event) 
        response = event.getName();
        // Create object of message and post it
        final MyMessageEvent msgEvent = new MessageEvent(response);
        EventBus.getDefault().post(msgEvent);
    
);

MyMessageEvent.class

import android.support.annotation.NonNull;

public class MyMessageEvent 

    private String mMessage;

    public MyMessageEvent(@NonNull final String message) 
        mMessage = message;
    

    public String getMessage() 
        return mMessage;
    


【讨论】:

一旦我使用 JavacriptInterface 方法,加载/进度对话框就会在 WebView 中显示(嗯,应该显示),这对我不使用本机进度对话框至关重要。关于计时器,是的,我必须使用某个计时器,但据我了解,您是说我应该使用 AsyncTask 类?不幸的是,我不明白如何在这里使用它?异步操作发生在 3 方 sdk 中,并且仅返回对我拥有的此侦听器的响应。.. 所以要么在开始后台工作之前禁用你的 webview。 那有什么帮助?即使使用了闩锁,webview 也不会卡住吗? 但是 javaInterface 方法 handleData 是从 Javascript 调用的,而 AnyCallBackListener 是本机 android 接口。这意味着我不能从 javascript 传递它 哦,是的,抱歉,您可以为此目的使用 EventBus 而不是接口。您的活动将成为订阅者,您必须在从 ajax 函数获得响应后发布消息/通知。 EventBus 适用于发布/订阅模式。有关更多信息,请查看 - github.com/greenrobot/EventBus【参考方案3】:

Android 线程模型

正如JCIP 所介绍的,很多UI 框架都是单线程的。他们使用线程限制来避免死锁和数据完整性。 Android 也使用这种线程模型。

默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程(称为“主”线程)中。

因此,如果您使用 lath 在主线程上等待,UI 将不会更新或响应您的操作。

后台任务

但有时,我们想要执行一些长时间的任务,比如使用网络下载一些东西并从数据库中读取。为了使 UI 响应,我们应该在另一个线程中执行这些作业(注意:不在服务中,默认仍在主线程上运行)。

Java 方式

所以您可以使用threadexecutor 来获取您的数据,然后在某些事件发生时像以下方法一样将数据读回主线程:

Activity.runOnUiThread(Runnable)

安卓方式

或者您可以使用 Android 原生 AsyncTask,这很方便。 例如,当您要检查用户的用户名/密码时,您必须连接到您的服务器进行验证。在这种情况下,您可以将请求和验证过程放入 AsyncTask 中,UI 线程可以在没有响应时制作一些动画。以下是一些代码:

public class UserLoginTask extends AsyncTask<Void, Void, Boolean> 

    private final String mName;
    private final String mPassword;

    UserLoginTask(String name, String password) 
        mName = name;
        mPassword = password;
    

    // backgroud thread
    @Override
    protected Boolean doInBackground(Void... params) 
        // network access.

        for (String credential : CREDENTIALS) 
            String[] pieces = credential.split(":");
            if (pieces[0].equals(mName)) 
                // Account exists, return true if the password matches.
                return pieces[1].equals(mPassword);
            
        

        // register the new account here.
        return true;
    

    // UI thread
    @Override
    protected void onPostExecute(final Boolean success) 
        mAuthTask = null;
        showProgress(false);

        if (success) 
            // start next
            finish();
            Intent intent = new Intent(LoginActivity.this, Drawer.class);
            startActivity(intent);
         else 
            mPasswordView.setError(getString( R.string.error_incorrect_password ));
            mPasswordView.requestFocus();
        
    

    @Override
    protected void onCancelled() 
        mAuthTask = null;
        showProgress(false);
    

参考:

My blog related with Android Thread

【讨论】:

我发现问题不在Android。它在我加载到 webview 的 JavaScript 中:var response = jsHandler.ajaxFunc(request.data); 因为 JS 是单线程的,这一行使 webview 冻结。 (至少根据我的理解和阅读).. @BVtp 很高兴您解决了您的问题。抱歉没有完全理解您的问题。

以上是关于闩锁(用于等待异步响应)冻结 WebView(和 UI)的主要内容,如果未能解决你的问题,请参考以下文章

加载 webview 时加载动画卡住/冻结

EF 6 中的异步等待问题

Cordova IOS webview 界面随机无响应

在异步任务中并行使用 WebView2

如何正确使用等待和通知方法?

消息队列和异步/等待