可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
In the manifest , In each activity I use configChanges:
<activity
android:name=".MainActivity"
android:configChanges="screenLayout|orientation|screenSize">
I hope that this help you.
回答2:
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.
回答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?
回答4:
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.
回答5:
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.
回答6:
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>
回答7:
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 ) .