MainLooper未执行Android Runnable

时间:2019-04-12 08:11:46

标签: android cordova cordova-plugins android-looper android-threading

应用简要说明:

  • 我有用于本机代码执行的Cordova / Ionic应用程序和Custom Cordova插件。
  • 插件包含单独的CameraActivity(扩展了FragmentActivity)以与Camera一起使用(基于Camera2Basic示例的部分代码)。
  • 启动时,活动显示AnaliseFragment,应用程序在其中捕获每个Camera帧并将图像传递到背景线程上。

执行步骤为:

  1. 用户在Cordova UI上按下按钮
  2. Cordova通过cordova.exec(..)执行本机插件方法
  3. 本地插件通过cordova.startActivityForResult(..)启动CameraActivity获取结果
  4. CameraActivity显示AnaliseFragment
  5. AnaliseFragment从两个表面开始相机捕获会话:第一个显示在TextureView上,第二个由ImageAnaliser进行分析

问题:

  

很少和随机的UI会停止对未在UI线程上执行的用户和可运行对象作出反应。同时,后台线程继续正常运行:相机输出在TextureView上可见,ImageAnaliser继续从相机接收图像。

有人对如何查找/调试此类行为的原因有任何建议吗?或有什么想法会导致这种情况?

我已经尝试过:

  • 记录CameraActivity / AnaliseFragment的每个生命周期事件=在应用正常状态和ANR之间没有调用
  • 添加WAKELOCK以使Cordova MainActivity保持活动状态=没有帮助
  • log(trace)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);
}
}

2 个答案:

答案 0 :(得分:1)

UI线程中有一些长时间运行的操作。尝试profile您的应用找出导致阻塞主线程的原因。

答案 1 :(得分:0)

经过数小时的分析,调试和代码审查,我发现

  

问题是由于后台线程的视图无效导致的

必须使用View.postInvalidate()方法-此方法检查View是否仍附加到窗口,然后进行无效化。相反,当我处理来自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);
};