【问题标题】:Android Runnable not executed by MainLooperMainLooper 未执行 Android Runnable
【发布时间】:2019-04-12 08:11:46
【问题描述】:

申请简介:

  • 我有用于执行本机代码的 Cordova/Ionic 应用程序和自定义 Cordova 插件。
  • 插件包含单独的 CameraActivity(扩展 FragmentActivity)以使用 Camera(部分代码基于 Camera2Basic 示例)。
  • 在启动 Activity 时会显示 AnaliseFragment,应用程序会在其中捕获每个 Camera 帧并将图像通过后台线程传递给分析器。

执行步骤为:

  1. 用户在 Cordova 用户界面上按下按钮
  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 活动 = 没有帮助
  • 记录(跟踪) 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);
}
}

【问题讨论】:

  • 你释放图片资源吗?我没有看到任何致电 https://developer.android.com/reference/android/media/Image.html#close()
  • 是的,我在需要的地方在 finally 块中调用 close() 方法。如果不调用,我将无法获得比相机捕获会话缓冲区更多的图像。我更新了代码示例,看看如何调用 close() 方法
  • 也许你的结果永远不可用..
  • 我在 onResultAvailable 和 reportToActivityAndUpdateUI 的第一行添加了日志记录,在 Logcat 中看到,调用了 onResultAvailable,但没有调用 reportToActivityAndUpdateUI。
  • 您只能通过mImageAnalyser.onResultAvailable 在 onViewCreated 中分派一次结果。您应该在处理完每个图像后立即发送结果。

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


【解决方案1】:

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

【讨论】:

  • 这绝对是个好建议,我不知何故忘记了分析。谢谢你。会试试的。
【解决方案2】:

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

问题是由后台线程的错误视图失效引起的

必须使用 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);
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-02
    • 1970-01-01
    相关资源
    最近更新 更多