Brief description of application:
- I have Cordova/Ionic application and Custom Cordova plugin for native code execution.
- Plugin contains separate CameraActivity (extends FragmentActivity) to work with Camera (parts of code based on Camera2Basic example).
- On launch Activity displays AnaliseFragment, where application captures every Camera frame and passes image to the analyser on backround thread.
Execution steps are:
- User presses button on Cordova UI
- Cordova executes native plugin method via cordova.exec(..)
- Native plugin starts CameraActivity for result via cordova.startActivityForResult(..)
- CameraActivity displays AnaliseFragment
- AnaliseFragment starts Camera capture session with two surfaces: first is displayed on TextureView and second analised by ImageAnaliser
Problem:
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.
Does anybody have any suggestion how to find/debug reason of such behavior? Or any ideas what can cause this?
I already tried:
- log every lifecycle event of CameraActivity/AnaliseFragment = no calls between app normal state and ANR
- add WAKELOCK to keep Cordova MainActivity alive = didn't help
- log(trace) every method in AnalilseFragment and ImageAnaliser = nothing suspicious
Here is simplified code of 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();
}
}
}
Simplified code for 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);
}
}