Skip to content

Commit ba0e427

Browse files
feat(android): stop/release camera in non-UI thread (react-native-camera#2685)
* stop/release camera in non-UI thread so we prevent ANRs and UI freezing. Some phones may take up to a second to release the camera and preview. * fix for surface destroy and resume events. Co-authored-by: Cristiano Coelho <[email protected]>
1 parent c683076 commit ba0e427

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

android/src/main/java/com/google/android/cameraview/Camera1.java

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
124124
private boolean mIsScanning;
125125

126126
private boolean mustUpdateSurface;
127+
private boolean surfaceWasDestroyed;
127128

128129
private SurfaceTexture mPreviewTexture;
129130

@@ -133,12 +134,56 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
133134
preview.setCallback(new PreviewImpl.Callback() {
134135
@Override
135136
public void onSurfaceChanged() {
136-
updateSurface();
137+
138+
// if we got our surface destroyed
139+
// we must re-start the camera and surface
140+
// otherwise, just update our surface
141+
142+
143+
synchronized(Camera1.this){
144+
if(!surfaceWasDestroyed){
145+
updateSurface();
146+
}
147+
else{
148+
mBgHandler.post(new Runnable() {
149+
@Override
150+
public void run() {
151+
start();
152+
}
153+
});
154+
}
155+
}
137156
}
138157

139158
@Override
140159
public void onSurfaceDestroyed() {
141-
stop();
160+
161+
// need to this early so we don't get buffer errors due to sufrace going away.
162+
// Then call stop in bg thread since it might be quite slow and will freeze
163+
// the UI or cause an ANR while it is happening.
164+
synchronized(Camera1.this){
165+
if(mCamera != null){
166+
167+
// let the instance know our surface was destroyed
168+
// and we might need to re-create it and restart the camera
169+
surfaceWasDestroyed = true;
170+
171+
try{
172+
mCamera.setPreviewCallback(null);
173+
// note: this might give a debug message that can be ignored.
174+
mCamera.setPreviewDisplay(null);
175+
}
176+
catch(Exception e){
177+
Log.e("CAMERA_1::", "onSurfaceDestroyed preview cleanup failed", e);
178+
}
179+
}
180+
}
181+
mBgHandler.post(new Runnable() {
182+
@Override
183+
public void run() {
184+
stop();
185+
}
186+
});
142187
}
143188
});
144189
}
@@ -235,8 +280,13 @@ void stop() {
235280

236281
if (mCamera != null) {
237282
mIsPreviewActive = false;
238-
mCamera.stopPreview();
239-
mCamera.setPreviewCallback(null);
283+
try{
284+
mCamera.stopPreview();
285+
mCamera.setPreviewCallback(null);
286+
}
287+
catch(Exception e){
288+
Log.e("CAMERA_1::", "stop preview cleanup failed", e);
289+
}
240290
}
241291

242292
releaseCamera();
@@ -247,6 +297,8 @@ void stop() {
247297
@SuppressLint("NewApi")
248298
void setUpPreview() {
249299
try {
300+
surfaceWasDestroyed = false;
301+
250302
if(mCamera != null){
251303
if (mPreviewTexture != null) {
252304
mCamera.setPreviewTexture(mPreviewTexture);

android/src/main/java/org/reactnative/camera/RNCameraView.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,10 +522,17 @@ public void onHostDestroy() {
522522
mGoogleBarcodeDetector.release();
523523
}
524524
mMultiFormatReader = null;
525-
stop();
526525
mThemedReactContext.removeLifecycleEventListener(this);
527526

528-
this.cleanup();
527+
// camera release can be quite expensive. Run in on bg handler
528+
// and cleanup last once everything has finished
529+
mBgHandler.post(new Runnable() {
530+
@Override
531+
public void run() {
532+
stop();
533+
cleanup();
534+
}
535+
});
529536
}
530537

531538
private boolean hasCameraPermissions() {

0 commit comments

Comments
 (0)