安卓应用在改变方向时丢失数据[英] Android app losing data during orientation change

本文是小编为大家收集整理的关于安卓应用在改变方向时丢失数据的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我有一个应用程序,我从一个教程复制的应用程序,该教程捕获了MediaStore.ACTION_IMAGE_CAPTURE的图像.当我在手机上运行应用程序时,我有一些奇怪的奇怪.

相机应用程序本身在操作期间正在翻转其取向即使我没有移动手机,也可以在运行中进行延续几次.在返回教程应用程序之前,它简要进入横向模式.因此,教程应用程序在返回它后返回纵向模式,并且图像丢失.我尝试将相机活动的方向设置为横向,并且图像不会丢失.

但应用程序的布局适用于纵向模式.或者,如果我在捕获照片时在横向举行我的相机,我可以在我的应用程序返回焦点后打机,而不会丢失图像.

我在网上做了一些嘲笑. Stackoverflow上的某人提到了方向的变化导致了对onCreate的额外呼叫. "onCreate()称为onCreate()的原因是因为在纵向期间调用相机活动时,它将改变方向并销毁您之前的活动."我在调试模式下运行了应用程序,其中包含在oncreate和onActivityResult方法中设置的断点.当我以纵向模式拍摄照片时,它确实如此.呼叫顺序是oncreate,onActivityResult,oncreate.如果我在横向模式下拍摄照片(这是我的相机应用程序的位置无论哪种方式),OnCreate不会被调用.现在我有一些想法发生了什么,我如何保留那个问题?这是应用程序的样子:

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);
        }
    }
}

而这是我在此活动的清单中所拥有的:

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

我已经完成了相当大的搜索,但我发现的大部分缺乏具体的例子.我需要知道代码的样子,不仅仅是什么功能.

我的手机是一个lg运动.还有其他人遇到这个问题吗?如何修复?

在清单中,在每个活动中,我使用configchanges:

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

我希望这可以帮助你.

其他推荐答案

您必须覆盖 onretainnonconfigurationInstance 和使用 getlastnonconfigurationInstance 保存/恢复位图.

如下:

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

然后在您的活动中,U覆盖:

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

将在旋转期间"保存"位图.

编辑:

使用建议的onsaveinstancestate,它将工作,但不可建议,因为它会使用很多内存并非常慢

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);
    }
}
如你所看到的,这个解决方案对你的案件不利的原因是 Android Bundle 用于此目的是Android特殊类型的序列化,可以处理实现可包裹的基元,字符串和类(这些类真的只只有包裹他们的基元).甚至你能够实现包裹物,它会花费大量时间来做位图上的每个字节的副本到捆绑包,并将为位图的已经大的存储器消耗加倍.

现在让我们使用setRetainInstance(从您可以在\sdk\extras\android\support\samples\Support4Demos\src\com\example\android\supportv4\app\FragmentRetainInstanceSupport.Java上找到的示例略微复制)解决方案.

确保还要检查示例,因为它显示了一些其他花哨的技巧.

// 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);
        }
    }

}

并确保从清单,因为它可能做得更多伤害,然后是一个关于它的小报价:

注意:应避免使用此属性并仅作为a 最后一招.有关更多信息,请阅读处理运行时更改 关于如何正确处理因配置更改而重新启动.

其他推荐答案

我星期一去了一个移动开发小组会议,并有一个提示.为主要公司提供各种平台的程序员,建议将位图与应用程序对象附加到应用程序对象.正如我看待如何做到的那样,我终于提出了一种解决方法(帖子结束时的新问题)根据以下博客文章的建议: http://android-er.blogspot.com/2011/10/share-bitmap-between-Attivities-as.html

基本上,这需要将位图设置为在应用程序中的任何活动可以访问它的位置中的公共资源.我创建了一个新的课程,如下所示:

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

我然后将CameraItity.java更改为BMP的所有引用到CommonResources.Camerabmp.

在初始化期间,如果commonresources.camerabmp不为null,则我使用它在imageview中设置位图.

oncreate并初始化现在看起来像这样:

@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);
}

然后,要做一点清理,我添加了如下:

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

我现在的大问题是:我是否正确处理了内存或我创建了垃圾收集问题?

本文地址:https://www.itbaoku.cn/post/102587.html

问题描述

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?

推荐答案

In the manifest , In each activity I use configChanges:

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

I hope that this help you.

其他推荐答案

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.

其他推荐答案

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?