3

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)
Tako
  • 3,364
  • 2
  • 14
  • 21
  • Without adding necessary snippets it would be hard to understand the cause! – Rajan Kali May 08 '23 at 20:58
  • Thanks for your comment, added a snippet of the code calling the camera. I didn't include the `onActivityResult` part because the app crashes prior to this method. – Tako May 08 '23 at 21:51
  • Are you using `new Runnable()` or thread executed `run(){}` anywhere in your code? The error you're getting is *almost* always caused by not properly using `runOnUiThread(new Runnable(){` – G. Putnam May 13 '23 at 17:01
  • Yes, I have Runnables in my code (for things that should happen off the main thread), but not for anything UI related. Also, if the app was crashing in one of my Runnables, it should have shown in the stack trace, wouldn't it? – Tako May 15 '23 at 16:32
  • add code snippet for better clarification. – Ali Azaz Alam May 17 '23 at 12:19
  • " onResume isn't called before the crash" onResume() is called after onActivityResult() method, though not necessarily immediately after – Vivek Gupta May 17 '23 at 18:19
  • Usually this happened when you process something using background thread and at the same time you accessing the UI. UI running on separate thread called main thread. To access UI, you need to use `runOnUIThread` or if you use `AsyncTask` to process the photo, you need to put UI logic in `onPostExecute`. Can you share your onActivityResult? – Amad Yus May 18 '23 at 03:04
  • @VivekGupta, you're right, onActivityResult() is called before onResume() (for some reason I thought it's the other way). However, onStart() isn't called before the crash either so it's safe to assume onActivityResult() isn't called either. Plus I can see from the logs that onActivityResult() hasn't ran before the crash. – Tako May 18 '23 at 18:03
  • can you show code where you consumed the returned uri? – – Vivek Gupta May 18 '23 at 18:48
  • @Tako All what you want in one place :) https://developer.android.com/training/camerax/take-photo – issamux May 19 '23 at 15:16
  • @issamux do you suggest I implement a camera activity instead of using the device's camera app? – Tako May 19 '23 at 21:23

5 Answers5

1

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

  1. Open a camara intent to capture a picture
  2. Take picture
  3. Return to your activity or fragment
  4. 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.

Dev4Life
  • 2,328
  • 8
  • 22
  • 1
    I'm so glad to hear some else is have encountered a similar crash on Samsung devices only, I feel much less crazy now, lol. But if the problem is an object that was claimed by the OS, would you expect to see a NullReferenceException or something? I know my app handles well cases where MainActivity is destroyed when while the camera is open. This happens quite often. Also, I already have configChanges="orientation|screenSize" set for my activity. – Tako May 19 '23 at 05:04
  • @Tako Hi, check my edited answer. It's one more step to do if things don't work with the previous solution. Since there is some problem by OEM handling the intent of camera capture, we can't do anything except trying different solutions and finding the perfect one, lol. – Dev4Life May 19 '23 at 07:28
  • That's an interesting suggestion. I'll give it a try and report back! – Tako May 19 '23 at 20:56
  • @Tako Yes, sure. Let me know if it's working or not. – Dev4Life May 20 '23 at 04:59
0

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.

utrucceh
  • 1,076
  • 6
  • 11
  • Thanks for your answer, but where? Non of my code is being called after the camera is closed and before the crash. The activity haven't even resumed yet. – Tako May 15 '23 at 18:02
  • you need to fix implement of onActivityResult method. For quick test, remove this method and test it, if there is no problem implement your code with Activity.runOnUiThread – utrucceh May 17 '23 at 08:33
  • onActivityResult is not executed before the app crashes. Also, isn't onActivityResult called on the main thread anyway? – Tako May 17 '23 at 14:31
0

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);
Lakshmi
  • 92
  • 1
  • 7
  • That's interesting. I want to start ActivityResultLaunchers, but they are only implemented in AppCompatActivity while my app uses plain old Activity. I should migrate to AppCompat I guess. Although, I wonder if you can rationalize why would it have anything to do with the thread issue? Or is it just a gut feeling? – Tako May 15 '23 at 17:55
0

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.

  • Thanks for your suggestions. I wasn't able to directly debug because it never crashes on my device, only on users devices (sigh). What I can do is add logs, redeploy to the play store and look at the new logs when the app crashes. That's how I figured that onConfigurationChanged is called on the same thread the created the view. Unfortunately I can't use this method to add logs to checkThread(). Config changes while the app is in the foreground doesn't cause any problems – Tako May 17 '23 at 04:43
  • @Tako You are welcome. First of all, you should try your best to replicate the crash, that's almost mandatory so you can undertsand it and then validate your fix. You said thare are other stack traces similar to this one. Can you share them? Maybe there is more info we can get from them. Do you keep any reference to a view in the activity or any other class? This could be caused by a memory leak. It's just a guess. – Isidro Rodriguez May 17 '23 at 18:08
  • Believe me, I tried replicating it for many hours with no luck. I only ask stuff on SO when I'm desperate. I added 2 more stack trace variants I'm seeing. I keep refs to views inside the Activity, but nowhere outside the Activity, that shouldn't cause any problem, right? – Tako May 18 '23 at 17:51
0

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) 
issamux
  • 1,336
  • 1
  • 19
  • 35