闩锁(用于等待异步响应)冻结 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
开始订阅,一般是在onCreate
的Activity
方法中完成订阅-
EventBus.getDefault().register(this);
还可以删除Activity
的onDestroy
方法中的订阅,或者如果您的工作已完成。
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 方式
所以您可以使用thread
或executor
来获取您的数据,然后在某些事件发生时像以下方法一样将数据读回主线程:
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)的主要内容,如果未能解决你的问题,请参考以下文章