Android MainLooper 未执行 Runnable
Android Runnable not executed by MainLooper
应用简要说明:
- 我有 Cordova/Ionic 应用程序和用于本机代码执行的自定义 Cordova 插件。
- 插件包含单独的 CameraActivity(扩展 FragmentActivity)以与 Camera 一起使用(部分代码基于 Camera2Basic 示例)。
- 启动时 Activity 显示 AnaliseFragment,其中应用程序捕获每个相机帧并在后台线程上将图像传递给分析器。
执行步骤为:
- 用户按下 Cordova 上的按钮 UI
- Cordova 通过 cordova.exec(..)
执行原生插件方法
- 本机插件通过 cordova.startActivityForResult(..)
启动相机Activity 获取结果
- 相机Activity 显示 AnaliseFragment
- AnaliseFragment 使用两个表面启动相机捕获会话:第一个显示在 TextureView 上,第二个由 ImageAnaliser 分析
问题:
Rarely and randomly UI stops reacting on user and runnables not executed on UI thread. At the same time background threads continue working as normal: camera output is visible on TextureView and ImageAnaliser continue receive images from Camera.
有人对如何 find/debug 这种行为的原因有什么建议吗?或者有什么想法会导致这种情况?
我已经试过了:
- 记录每个生命周期事件 CameraActivity/AnaliseFragment = 应用程序正常状态和 ANR 之间没有调用
- 添加 WAKELOCK 以保持 Cordova MainActivity 存活 = 没有帮助
- 记录(跟踪)AnalilseFragment 和 ImageAnaliser 中的每个方法 = 没有可疑之处
这里是AnaliseFragment的简化代码:
public class AnaliseFragment extends Fragment {
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private ImageAnalyser mImageAnalyser;
// listener is attached to camera capture session and receives every frame
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image nextImage = reader.acquireLatestImage();
mBackgroundHandler.post(() ->
try {
mImageAnalyser.AnalizeNextImage(mImage);
}
finally {
mImage.close();
}
);
}
};
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
mImageAnalyser = new ImageAnalyser();
mImageAnalyser.onResultAvailable(boolResult -> {
// Runnable posted, but never executed
new Handler(Looper.getMainLooper()).post(() -> reportToActivityAndUpdateUI(boolResult));
});
}
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
}
@Override
public void onPause() {
stopBackgroundThread();
super.onPause();
}
private void startBackgroundThread() {
if (mBackgroundThread == null) {
mBackgroundThread = new HandlerThread("MyBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ImageAnalyser 的简化代码:
public class ImageAnalyser {
public interface ResultAvailableListener {
void onResult(bool boolResult);
}
private ResultAvailableListener mResultAvailableListener;
public void onResultAvailable(ResultAvailableListener listener) { mResultAvailableListener = listener; }
public void AnalizeNextImage(Image image) {
// Do heavy analysis and put result into theResult
mResultAvailableListener.onResult(theResult);
}
}
UI线程中有一些长运行操作。尝试 profile 您的应用,找出是什么阻塞了您的主线程。
经过数小时的性能分析、调试和代码审查,我发现
issue was caused by incorrect View invalidation from background thread
必须使用 View.postInvalidate() 方法 - 此方法检查 View 是否仍附加到 window 然后进行失效。相反,我在处理来自 MainLooper 的自定义消息时错误地使用了 View.invalidate(),这很少导致失败并使 MainLooper 停止处理更多消息。
对于那些可能有同样问题的人,我添加了正确和错误的代码。
正确:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private void invalidateGraphicOverlayViewFromBackgroundThread(){
mGraphicOverlayView.postInvalidate();
};
错误:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private final int MSG_INVALIDATE_GRAPHICS_OVERLAY = 1;
private Handler mUIHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE_GRAPHICS_OVERLAY:{
GraphicOverlayView overlay = (GraphicOverlayView)msg.obj;
// Next line can cause MainLooper stop processing other messages
overlay.invalidate();
break;
}
default:
super.handleMessage(msg);
}
}
};
private void invalidateGraphicOverlayViewFromBackgroundThread(){
Message msg = new Message();
msg.obj = mGraphicOverlayView;
msg.what = MSG_INVALIDATE_GRAPHICS_OVERLAY;
mUIHandler.dispatchMessage(msg);
};
应用简要说明:
- 我有 Cordova/Ionic 应用程序和用于本机代码执行的自定义 Cordova 插件。
- 插件包含单独的 CameraActivity(扩展 FragmentActivity)以与 Camera 一起使用(部分代码基于 Camera2Basic 示例)。
- 启动时 Activity 显示 AnaliseFragment,其中应用程序捕获每个相机帧并在后台线程上将图像传递给分析器。
执行步骤为:
- 用户按下 Cordova 上的按钮 UI
- Cordova 通过 cordova.exec(..) 执行原生插件方法
- 本机插件通过 cordova.startActivityForResult(..) 启动相机Activity 获取结果
- 相机Activity 显示 AnaliseFragment
- AnaliseFragment 使用两个表面启动相机捕获会话:第一个显示在 TextureView 上,第二个由 ImageAnaliser 分析
问题:
Rarely and randomly UI stops reacting on user and runnables not executed on UI thread. At the same time background threads continue working as normal: camera output is visible on TextureView and ImageAnaliser continue receive images from Camera.
有人对如何 find/debug 这种行为的原因有什么建议吗?或者有什么想法会导致这种情况?
我已经试过了:
- 记录每个生命周期事件 CameraActivity/AnaliseFragment = 应用程序正常状态和 ANR 之间没有调用
- 添加 WAKELOCK 以保持 Cordova MainActivity 存活 = 没有帮助
- 记录(跟踪)AnalilseFragment 和 ImageAnaliser 中的每个方法 = 没有可疑之处
这里是AnaliseFragment的简化代码:
public class AnaliseFragment extends Fragment {
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private ImageAnalyser mImageAnalyser;
// listener is attached to camera capture session and receives every frame
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image nextImage = reader.acquireLatestImage();
mBackgroundHandler.post(() ->
try {
mImageAnalyser.AnalizeNextImage(mImage);
}
finally {
mImage.close();
}
);
}
};
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
mImageAnalyser = new ImageAnalyser();
mImageAnalyser.onResultAvailable(boolResult -> {
// Runnable posted, but never executed
new Handler(Looper.getMainLooper()).post(() -> reportToActivityAndUpdateUI(boolResult));
});
}
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
}
@Override
public void onPause() {
stopBackgroundThread();
super.onPause();
}
private void startBackgroundThread() {
if (mBackgroundThread == null) {
mBackgroundThread = new HandlerThread("MyBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ImageAnalyser 的简化代码:
public class ImageAnalyser {
public interface ResultAvailableListener {
void onResult(bool boolResult);
}
private ResultAvailableListener mResultAvailableListener;
public void onResultAvailable(ResultAvailableListener listener) { mResultAvailableListener = listener; }
public void AnalizeNextImage(Image image) {
// Do heavy analysis and put result into theResult
mResultAvailableListener.onResult(theResult);
}
}
UI线程中有一些长运行操作。尝试 profile 您的应用,找出是什么阻塞了您的主线程。
经过数小时的性能分析、调试和代码审查,我发现
必须使用issue was caused by incorrect View invalidation from background thread
View.postInvalidate() 方法 - 此方法检查 View 是否仍附加到 window 然后进行失效。相反,我在处理来自 MainLooper 的自定义消息时错误地使用了 View.invalidate(),这很少导致失败并使 MainLooper 停止处理更多消息。
对于那些可能有同样问题的人,我添加了正确和错误的代码。
正确:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private void invalidateGraphicOverlayViewFromBackgroundThread(){
mGraphicOverlayView.postInvalidate();
};
错误:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private final int MSG_INVALIDATE_GRAPHICS_OVERLAY = 1;
private Handler mUIHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE_GRAPHICS_OVERLAY:{
GraphicOverlayView overlay = (GraphicOverlayView)msg.obj;
// Next line can cause MainLooper stop processing other messages
overlay.invalidate();
break;
}
default:
super.handleMessage(msg);
}
}
};
private void invalidateGraphicOverlayViewFromBackgroundThread(){
Message msg = new Message();
msg.obj = mGraphicOverlayView;
msg.what = MSG_INVALIDATE_GRAPHICS_OVERLAY;
mUIHandler.dispatchMessage(msg);
};