Android Runnable не выполняется MainLooper

1

Краткое описание приложения:

  • У меня есть приложение Cordova/Ionic и пользовательский плагин Cordova для выполнения собственного кода.
  • Плагин содержит отдельную CameraActivity (расширяет FragmentActivity) для работы с камерой (части кода, основанные на примере Camera2Basic).
  • При запуске Activity отображает AnaliseFragment, где приложение захватывает каждый кадр камеры и передает изображение в анализатор в фоновом потоке.

Шаги выполнения:

  1. Пользователь нажимает кнопку на Cordova UI
  2. Cordova выполняет собственный метод плагина с помощью cordova.exec(..)
  3. Собственный плагин запускает CameraActivity для результата через cordova.startActivityForResult(..)
  4. CameraActivity отображает AnaliseFragment
  5. AnaliseFragment начинает сеанс захвата камеры с двух поверхностей: первая отображается в TextureView, а вторая - в ImageAnaliser.

Проблема:

Редко и случайно пользовательский интерфейс перестает реагировать на пользователя и запускаемые элементы не выполняются в потоке пользовательского интерфейса. В то же время фоновые потоки продолжают работать в обычном режиме: выходные данные камеры видны в 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);
}
}
Теги:
cordova
cordova-plugins
android-looper

2 ответа

0
Лучший ответ

После нескольких часов профилирования, отладки и проверки кода я обнаружил, что

проблема была вызвана неправильным просмотром недействительности в фоновом потоке

Должен использоваться метод View.postInvalidate() - этот метод проверяет, все ли прикреплено View к окну, а затем делает его недействительным. Вместо этого я неправильно использовал View.invalidate(), когда обрабатывал мое пользовательское сообщение из MainLooper, что редко вызывало сбои и заставляло 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);
};
1

В UI-потоке есть длительная операция. Попробуйте профилировать свое приложение, чтобы выяснить, что блокирует ваш основной поток.

  • 1
    Это определенно хороший совет, я как-то забыл про профилирование. Спасибо. Попытаюсь.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню