Reputation: 3444
My app allows users to take pictures by using the device's default camera app, like so:
private void takePhoto(Uri outputUri) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
startActivityForResult(intent, TAKE_PHOTO_REQUEST_CODE);
}
Looking at crash logs, I'm seeing lots of app crashes happening around what seems to be the app resuming after taking a photo.
The exception and stack trace:
Fatal Exception: android.view.ViewRootImpl$CalledFromWrongThreadException
Only the original thread that created a view hierarchy can touch its views.
android.view.ViewRootImpl.checkThread (ViewRootImpl.java:11379)
android.view.ViewRootImpl.requestLayout (ViewRootImpl.java:2562)
android.view.ViewRootImpl.updateConfiguration (ViewRootImpl.java:6324)
android.app.ActivityThread.handleActivityConfigurationChanged (ActivityThread.java:6925)
android.app.servertransaction.ActivityConfigurationChangeItem.execute (ActivityConfigurationChangeItem.java:53)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2571)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8741)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)
There are a few different variants of the stack trace, but they all go through some configuration change which led me to believe the user must be rotating the device while taking the picture and the app crashes when it's returning to the foreground in a different orientation.
My app's activity has android:configChanges="orientation|screenSize"
in its manifest because it's containing a WebView, but it's not doing anything during onConfigurationChanged
.
While investigating the crash I added logs to print the thread name and ID to the main lifecycle methods as well as to onConfigurationChanged
. I found that onConfigurationChanged
called right before the app crashes on the same main thread as onCreate
is called. onStart
and onResume
aren't called before the crash.
To add more mystery to the whole thing, the crash happens only on Samsung devices (but on a verity of phones/tables models).
Even though I don't think my app is doing anything unique or unusual I couldn't find any similar reports online.
Any ideas/suggestions would be appreciated!
Edit
Adding more stack trace variants:
Fatal Exception: android.view.ViewRootImpl$CalledFromWrongThreadException
Only the original thread that created a view hierarchy can touch its views.
android.view.ViewRootImpl.checkThread (ViewRootImpl.java:11586)
android.view.ViewRootImpl.requestLayout (ViewRootImpl.java:2648)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.widget.TextView.onConfigurationChanged (TextView.java:4706)
android.view.View.dispatchConfigurationChanged (View.java:16145)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewRootImpl.updateConfiguration (ViewRootImpl.java:6502)
android.app.ActivityThread.handleActivityConfigurationChanged (ActivityThread.java:6941)
android.app.servertransaction.ActivityConfigurationChangeItem.execute (ActivityConfigurationChangeItem.java:53)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2574)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8757)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)
Fatal Exception: android.view.ViewRootImpl$CalledFromWrongThreadException
Only the original thread that created a view hierarchy can touch its views.
android.view.ViewRootImpl.checkThread (ViewRootImpl.java:11586)
android.view.ViewRootImpl.requestLayout (ViewRootImpl.java:2648)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.view.View.requestLayout (View.java:27612)
android.widget.TextView.onConfigurationChanged (TextView.java:4706)
android.view.View.dispatchConfigurationChanged (View.java:16145)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewGroup.dispatchConfigurationChanged (ViewGroup.java:1654)
android.view.ViewRootImpl.updateConfiguration (ViewRootImpl.java:6502)
android.app.ActivityThread.handleActivityConfigurationChanged (ActivityThread.java:6941)
android.app.ActivityThread$ActivityClientRecord$1.onConfigurationChanged (ActivityThread.java:797)
android.view.ViewRootImpl.performConfigurationChange (ViewRootImpl.java:6462)
android.view.ViewRootImpl.handleResized (ViewRootImpl.java:2424)
android.view.ViewRootImpl.-$$Nest$mhandleResized
android.view.ViewRootImpl$ViewRootHandler.handleMessageImpl (ViewRootImpl.java:6728)
android.view.ViewRootImpl$ViewRootHandler.handleMessage (ViewRootImpl.java:6697)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8757)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)
Upvotes: 4
Views: 2372
Reputation: 3317
Yes. It does happen with only samsung devices. I faced that issue once upon a time.
Here is what happens when you capture a picture
Open a camara intent to capture a picture
Take picture
Return to your activity or fragment
Get captured image & use it
This is a usual lifecycle of an image capture using device camera.
Now Samsung devices running on android 13 (Don't know about lower versions, I don't have one with lower versions, so) have a problem that if you take a picture using camera intent in portrait and then return to the activity or fragment with landscape orientation then it destroys the previous objects on configuration change.
It also happens even if you capture picture in portrait, then rotate device in landscape mode and then again rotate it to the portrait mode and come back to the activity, because of configuration change as I mentioned above.
So, here is my take on that which is working just fine. I now apply it to all my apps containing such functionality.
1. Fix activity orientation in manifest with traditional method providing fixed screen orientation either portrait
or landscape
according to your need
<activity
android:name=".activity.MainActivity"
android:screenOrientation="portrait" />
2. If you have responsive screen which can either be portrait or landscape on device orientation changes, then add this config
to the activity tag
in Manifest
<activity
android:name=".activity.MainActivity"
android:configChanges="orientation|screenSize"/>
So now, you will have the objects created before launching camera intent undestroyed when you return back to your activity after taking picture.
EDIT:
If you've already put the configurations in the Manifest
, then you can go one step ahead as I did in some of my apps.
When user launches camera, set screen orientation to portrait
programmatically,
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
Then, when user returns to the activity, then release the portrait lock by setting the orientation you've already set.
So, it won't rotate your device orientation while in camera screen.
Upvotes: 2
Reputation: 1415
If you need full control over Image and camera just follow this official android documentation : https://developer.android.com/training/camerax/take-photo Otherwise follow this steps
1 - Request permission
private val requestSinglePermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// permission granted , ok
} else {
// Permission Denied , follow google recommendation for this cases...
}
}
2 - Code invocation be like this :
requestSinglePermissionLauncher.launch(Manifest.permission.CAMERA)
3- use the pre-built "TakePicture" contract :
private val getCameraImage =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
//get image uri : $uri
}
}
Full code :
private val getCameraImage =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
//get image uri : $uri
}
}
private val requestSinglePermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// you should provide an uri : where to store your image
getCameraImage.launch(uri)
} else {
// Permission Denied , follow google recommendation for this cases...
}
}
// some where in code
requestSinglePermissionLauncher.launch(Manifest.permission.CAMERA)
Upvotes: 0
Reputation: 475
You can try debugging. For some reason the mThread field in the ViewRootImpl instance is not the same as the thread the config change is notified on. This is the method where the exception is thrown from:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
It could be that the field was set to null or changed, or maybe the thread did change. Maybe Samsung has modified said class.
Also try triggering a config change without leaving the app and see what happens.
Upvotes: 0
Reputation: 92
Use the below code. It should work fine. Anyway, startActivityForResult() is deprecated.
ActivityResultLauncher<Intent> cameraActivityResultLauncher;
cameraActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
Bitmap img = (Bitmap)data.getExtras().get("data");
imageView.setImageBitmap(img);
}
}
});
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraActivityResultLauncher.launch(cameraIntent);
Upvotes: 0
Reputation: 1116
Usually this error happens with background tasks like network processing, so they use different threads. So to fix this, use Activity.runOnUiThread
method from views original activity.
Upvotes: 0