9

I've got an app that I've copied from a tutorial that captures an image with MediaStore.ACTION_IMAGE_CAPTURE. I've got some kind of weirdness going on when I run the app on my phone.

The camera app itself is flipping its orientation a couple times during operation even though I am not moving the phone. It briefly goes into landscape mode before returning to the tutorial app. Consequently, the tutorial app is flipping back to portrait mode after control is returned to it, and the image is lost. I tried setting the camera activity's orientation to landscape, and the image is not lost.

But the layout of the app is intended for portrait mode. Or, if I hold my camera in landscape orientation while capturing the photo, I can turn the phone after my app has returned to focus, and not lose the image.

I did some poking around on the web. Someone on Stackoverflow mentioned that the change in orientation caused additional calls to onCreate. "The reason that onCreate() is called is because when you do call the camera activity during the portrait orientation, it will change the orientation and destroy your previous activity." I ran the app in debugging mode with breakpoints set in onCreate and in the onActivityResult methods. It is indeed true that onCreate is getting called when I take the photo in portrait mode. The order of calls is onCreate, onActivityResult, onCreate. If I take the photo in landscape mode (which is where my camera app ends up either way), onCreate does not get called. Now that I have some idea what is going on, how do I keep that from being a problem? Here's what the app looks like now:

package com.example.testapp;

import java.io.IOException;
import java.io.InputStream;

import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;

public class CameraActivity extends Activity implements View.OnClickListener {

    ImageButton ib;
    Button b;
    ImageView iv;
    Intent i;
    final static int cameraData = 0;
    Bitmap bmp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.photo_activity);
        initialize();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        // TODO Auto-generated method stub
        super.onConfigurationChanged(newConfig);
        setContentView(R.layout.photo_activity);
        initialize();
    }

    private void initialize() {
        iv = (ImageView)findViewById(R.id.imageViewReturnedPicture);
        ib = (ImageButton)findViewById(R.id.imageButtonTakePicture);
        b = (Button)findViewById(R.id.buttonSetWallpaper);
        b.setOnClickListener(this);
        ib.setOnClickListener(this);
    }

    @Override
    public void onClick(View arg0) {
        switch (arg0.getId()) {

        case R.id.buttonSetWallpaper:
            try {
                WallpaperManager wm = WallpaperManager.getInstance(getApplicationContext());
                wm.setBitmap(bmp);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            break;

        case R.id.imageButtonTakePicture:
            i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
            startActivityForResult(i, cameraData);
            break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            Bundle extras = data.getExtras();
            bmp = (Bitmap)extras.get("data");
            iv.setImageBitmap(bmp);
        }
    }
}

And here's what I have in the manifest for this activity:

                android:name="com.example.testapp.CameraActivity"
                android:label="Camera Activity"
                android:configChanges="orientation"
                android:screenOrientation="portrait" 

I've done considerable searching, but much of what I find lacks concrete examples. I need to know what the code looks like, not just what feature to use.

My phone is an LG Motion. Has anyone else run into this problem? How can it be fixed?

Vikalp Patel
  • 10,669
  • 6
  • 61
  • 96
Roxann Higuera
  • 121
  • 1
  • 1
  • 4

7 Answers7

17

In the manifest , In each activity I use configChanges:

 <activity
        android:name=".MainActivity"
        android:configChanges="screenLayout|orientation|screenSize">

I hope that this help you.

Cabezas
  • 9,329
  • 7
  • 67
  • 69
  • 1
    Please note that the `orientation` flag is the most important in the answer and saved my day... – JamesC Nov 07 '17 at 11:20
8

You'll have to override onRetainNonConfigurationInstance and use getLastNonConfigurationInstance to save/restore the bitmap.

Like this:

// during onCreate(Bundle), after initialise()
bmp = getLastNonConfigurationInstance();
if(bmp!=null){ iv.setImageBitmap(bmp); }
else { /* the image was not taken yet */ }

then on your activity u override:

@Override
public Object onRetainNonConfigurationInstance (){
     return bmp;
}

That will 'save' the bitmap during rotation.

edit:

example using suggested onSaveInstanceState that will work but is not advisable because it will use a lot of memory and be very slow but you'll need for other situations soon:

public class SomethingSomething extends Activity{

    String name="";
    int page=0;

    // This is called when the activity is been created
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

            // if you saved something on outState you can recover them here
            if(savedInstanceState!=null){
                name = savedInstanceState.getString("name");
                page = savedInstanceState.getInt("page");
            }
    }

    // This is called before the activity is destroyed
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

            outState.putString("name", name);
            outState.putInt("page", page);
    }
}

as you can see, the reason this solution is not good for your case is because the Android Bundle used on for this is Android special type of serialization that can handle primitives, Strings and Classes that implements Parcelable (those classes really only parcel their primitives). And even thou Bitmaps do implement Parcelable, it will be taking a lot of time to do a copy of every byte on the Bitmap to the bundle and will be doubling the already big memory consumption of a Bitmap.

Now let's see at a solution using the setRetainInstance (slightly copied from the example you can find on \sdk\extras\android\support\samples\Support4Demos\src\com\example\android\supportv4\app\FragmentRetainInstanceSupport.Java.

Make sure to also check the examples as it shows some other fancy tricks.

// This fragment will be managed by the framework but we won't built a UI for it.
public class FragRetained extends Fragment{
   public static final String TAG = "TAG.FragRetained";
   private Bitmap bitmap;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Tell the framework to try to keep this fragment around
        // during a configuration change.
        setRetainInstance(true);
    }
    public Bitmap getBitmap() { return bitmap; }
    public void setBitmap(Bitmap bmp) { bitmap = bmp; }
}

public class MyActivity extends Activity{

    private FragRetained myFragRetained;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
            // set the content view
            img = (ImageView)findViewById(R.id.byImgV);


            myFragRetained = getFragmentManger.findFragmentByTag(FragRetained.TAG);
            if(myFragRetained == null){
                myFragRetained = new FragRetained ();
            }else{
               Bitmap b = myFragRetained.getBitmap();
               if(b==null){
                  // the user still did not choose the photo
               }else{
                  img.setImageBitmap(b);
               }
            }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            Bundle extras = data.getExtras();
            bmp = (Bitmap)extras.get("data");
            iv.setImageBitmap(bmp);
            myFragRetained.setBitmap(bmp);
        }
    }

}

and make sure to remove the line android:configChanges="orientation" from your manifest because it's probably doing more harm then good, a small quote about it:

Note: Using this attribute should be avoided and used only as a last-resort. Please read Handling Runtime Changes for more information about how to properly handle a restart due to a configuration change.

Community
  • 1
  • 1
Budius
  • 39,391
  • 16
  • 102
  • 144
  • 1
    Apparently, onRetainNonConfigurationInstance is deprecated. The documentation suggests using the Fragment API setRetainInstance(boolean) instead. Does anyone know how to use that? – Roxann Higuera Feb 23 '13 at 21:23
  • 1
    It is indeed but nothing stops you from using now that you're still learning. To use the fragment version you would have a UI-less fragment that will ask to retain it's instance with setRetainInstance(true) during onCreate() and you can create a setBitmap() getBitmap() methods on it. So you after you get the image you can init the fragment and setBitmap(). After rotation (during activity onCreate()) you can get the fragment with getFragmentManager().findFragmentByTag(String) and call getBitmap() on it. But I just think it's too much for someone that is just learning the platform. – Budius Feb 24 '13 at 00:26
  • 1
    apparently (http://stackoverflow.com/a/7677326/906362) there's an example using UI-less Fragment + setRetainInstance() on the examples you downloaded with the SDK, FragmentRetainInstanceSupport or something like that. – Budius Feb 24 '13 at 00:36
  • 1
    OK, maybe I'm new to Android and Java, but I have been programming since 1980. If I've got a good example, I'm pretty good at picking up on what is needed. But when there are no examples, the documentation tends to look like gobbledygook to me. That's been my problem in trying to figure this out. People say to use onSaveInstanceState and the like, but I have not found an example of how to use onSaveInstanceState anywhere I've looked. The Android documentation is sorely lacking in examples. – Roxann Higuera Feb 24 '13 at 01:30
  • 1
    I didn't mean to be condescending but you only have 1 reputation and I could see you're starting on Android so I tried simplify. But anyway, I'll edit my answer with both examples on how to use onSaveInstanceState (which as per my comments will NOT work on your situtation, but will probably be useful in a near future) and for the setRetainInstance(). Please check it again. – Budius Feb 24 '13 at 11:10
  • 1
    How did this not get +1? Not an Android programmer here, but good effort (ping @Roxann). – halfer Mar 24 '13 at 22:01
  • +1 to correct answer using the method before edit ;) hope the answer is accepted someday – Victor Oliveira Jun 11 '14 at 12:22
3

I went to a mobile development group meeting on Monday and got a hint. A programmer who writes apps for a variety of platforms for a major company suggested attaching the bitmap to the application object. As I looked at how I might do that, I finally came up with a solution that works (new question at end of post) based on suggestions in the following blog post: http://android-er.blogspot.com/2011/10/share-bitmap-between-activities-as.html

Basically, this entailed setting up the bitmap as a common resource in a location where any activity in the app can access it. I created a new class as follows:

package com.example.testapp;
 
import android.graphics.Bitmap;
 
public class CommonResources {
        public static Bitmap cameraBmp = null;
}

I then changed all references to bmp in CameraActivity.java to CommonResources.cameraBmp.

During initialization, if CommonResources.cameraBmp is not null, then I use it to set the bitmap in the ImageView.

onCreate and initialize now look like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
              super.onCreate(savedInstanceState);
        setContentView(R.layout.photo_activity);
        initialize();
}

private void initialize() {
        iv = (ImageView)findViewById(R.id.imageViewReturnedPicture);
        ib = (ImageButton)findViewById(R.id.imageButtonTakePicture);
        b = (Button)findViewById(R.id.buttonSetWallpaper);
        if (CommonResources.cameraBmp != null) {
                   iv.setImageBitmap(CommonResources.cameraBmp);
           }
        b.setOnClickListener(this);
        ib.setOnClickListener(this);
}

And then, to do a little cleanup, I added onPause as follows:

@Override
protected void onPause() {
        // TODO Auto-generated method stub
              super.onPause();
        if (isFinishing()) {
                   // Save the bitmap.
                        CommonResources.cameraBmp = null;
           }
}

My big question now is this: Have I handled the memory properly or have I created garbage collection issues?

Roxann Higuera
  • 121
  • 1
  • 1
  • 4
1

The way you're supposed to handle orientation changes is by saving your instance state. If you fill in the onSaveInstanceState method, you can get the data you save into that bundle back out during onCreate. This is done for you for views, but other data you have to save yourself. Any primitive, parcellable, or serializable object can be saved this way, including BitMaps.

You should do this not just to survive configuration changes, but to keep your state in low memory conditions too.

Charles Munger
  • 1,417
  • 12
  • 11
  • onSaveInstanceState can't save bitmaps – Budius Feb 23 '13 at 17:36
  • @Budius yes it can - http://it-ride.blogspot.com/2010/08/save-image-in-instance-state-android.html – Charles Munger Feb 27 '13 at 02:43
  • 1
    Although your link doesn't work, after my comment I noticed I was wrong, I apologise for that, and I would be happy to un-downvote you, but I can't unless you edit your answer. But I insist that although Bitmaps are parcelable, it's not advisable to use it for during SavedInstanceState because it takes time and memory to complete such transactions and might make the rotation very laggy. – Budius Feb 27 '13 at 09:54
  • Depends on the bitmap - if it's small, it may not be an issue. Also, for rotation the process isn't killed, and the objects in the intent shouldn't actually be parcelled so it should just keep a reference. – Charles Munger Feb 27 '13 at 21:09
1

Have you tried adding android:launchMode="singleTop" to activity in android menifest, worked out for me. The problem is android restarts the activity when orientation changes and your data is lost, with this if your activity is already running it doesn't create new instance.

        <activity 
            android:name=".FacebookActivity" 
            android:label="@string/facebook_app_name"
            android:launchMode="singleTop"
            android:configChanges="keyboardHidden|orientation"
            >
        </activity>
Amol
  • 285
  • 1
  • 5
  • 15
1

After searching the web quite a while for a simple solution, I looked at some code and came up with this, I hope it helps anyone who views this thread in future!

I have a global variable public Uri imageURI = null; in my Activity class, which is used to set the image URI of the ImaveView I am using; and the following code in onCreate() and onSaveInstanceState()

@Override
protected void onCreate(Bundle savedInstanceState) 
{   
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_thisactivity);

    //restore the bitmap for the image
    if (savedInstanceState!=null)
    {
        imageURI=savedInstanceState.getParcelable("imageURI");
        ImageView photo=(ImageView)findViewById(R.id.image);
        photo.setImageURI(imageURI);
    }
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putParcelable("imageURI", imageURI);
}

I hope someone finds this useful - just ask if you want more details.

Jayps
  • 109
  • 1
  • 7
0

Either use the onSaveInstanceState and then the argument you get via onCreate , or set on the manifest that you would handle orientation changes by using configChanges and setting on which cases you wish to handle by yourself (for example orientation|screenSize|screenLayout ) .

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • onSaveInstanceState can't save bitmaps – Budius Feb 23 '13 at 17:31
  • ok , i'm confused about the problem . Can you please explain some more about it? Also , have you also tried setting the configChanges ? – android developer Feb 23 '13 at 17:38
  • I'm not the one who asked the question. I'm just explaining you that you suggest him to use onSavedInstanceState but Bitmaps are not Parcelable, hence, can't be saved with onSavedInstanceState. – Budius Feb 23 '13 at 17:43
  • so he can save it to his internal/external storage folder. I still don't understand the question though. – android developer Feb 23 '13 at 18:13
  • Yes. I've got configChanges set in my manifest as shown above. I've also got an override on onConfigurationChanged. The main problem is that applications on my phone lose the data in their views when the phone is turned. The program works just fine in the emulator. So something needs to be done to explicitly save view data. – Roxann Higuera Feb 23 '13 at 21:19
  • but you've called setContentView() and initialize() in the onConfigurationChanged ... please remove them so that they won't re-initialize everything. This way , they should have the same content as before. also , please set all of the flags for the configChanges i've written about , and remove the orientation since you wish to handle both landscape and portrait modes . – android developer Feb 23 '13 at 21:21
  • and why do people keep downvoting without telling why ? please have the dignity to write why as we all try to help each other . if i'm mistaken i can fix the answer or even delete it , but why downvote? – android developer Feb 23 '13 at 23:57
  • @Budius but you didn't consider the other solution, and you didn't consider that before onSaveInstanceState , you can save the bitmap into a file. you just downvoted without letting me explain what you can do . – android developer Feb 24 '13 at 06:47