7

我有一个从教程中复制的应用程序,该教程使用MediaStore.ACTION_IMAGE_CAPTURE. 当我在手机上运行该应用程序时,我遇到了一些奇怪的事情。

即使我没有移动手机,相机应用程序本身也会在操作过程中翻转其方向几次。在返回教程应用程序之前,它会短暂进入横向模式。因此,教程应用程序在将控制权返回给它后会翻转回纵向模式,并且图像会丢失。我尝试将相机活动的方向设置为横向,并且图像没有丢失。

但是应用程序的布局是为纵向模式设计的。或者,如果我在拍摄照片时将相机保持在横向,我可以在我的应用程序重新聚焦后转动手机,而不会丢失图像。

我在网上做了一些闲逛。Stackoverflow 上有人提到方向的变化导致额外调用onCreate. “之所以onCreate()被调用,是因为当你在纵向时调用相机活动时,它会改变方向并破坏你之前的活动。” 我在调试模式下运行应用程序,并在 onCreate 和onActivityResult方法中设置了断点。onCreate当我以纵向模式拍照时,确实会被调用。调用顺序是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="相机活动"
                android:configChanges="方向"
                安卓:screenOrientation="人像"

我做了相当多的搜索,但我发现的大部分内容都缺乏具体的例子。我需要知道代码是什么样的,而不仅仅是要使用什么功能。

我的手机是 LG Motion。有没有其他人遇到过这个问题?如何修复?

4

7 回答 7

15

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

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

我希望这对你有帮助。

于 2015-07-20T12:04:20.257 回答
8

您必须覆盖onRetainNonConfigurationInstance并使用getLastNonConfigurationInstance来保存/恢复位图。

像这样:

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

然后在您的活动中覆盖:

@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 特殊类型的序列化,可以处理实现 Parcelable 的原语、字符串和类(这些类实际上只打包它们的原语) . 即使您的位图确实实现了 Parcelable,将位图上的每个字节复制到捆绑包中也将花费大量时间,并且将使位图已经很大的内存消耗增加一倍。

现在让我们看看使用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);
        }
    }

}

并确保android:configChanges="orientation"从您的清单中删除该行,因为它可能造成的伤害大于好处,关于它的一个小引用:

注意:应避免使用此属性,并且仅将其用作最后的手段。有关如何正确处理由于配置更改而重新启动的更多信息,请阅读处理运行时更改。

于 2013-02-23T17:35:47.957 回答
3

我周一参加了一个移动开发小组会议并得到了提示。一位为一家大公司为各种平台编写应用程序的程序员建议将位图附加到应用程序对象上。当我研究如何做到这一点时,我终于根据以下博客文章中的建议提出了一个可行的解决方案(帖子末尾的新问题): http ://android-er.blogspot.com/2011/ 10/share-bitmap-between-activities-as.html

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

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

然后我将CameraActivity.java 中对bmp 的所有引用更改为CommonResources.cameraBmp。

在初始化过程中,如果 CommonResources.cameraBmp 不为空,那么我用它来设置 ImageView 中的位图。

onCreate 和 initialize 现在看起来像这样:

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

然后,为了做一点清理工作,我添加了 onPause 如下:

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

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

于 2013-03-01T23:13:13.523 回答
1

在网上搜索了很长一段时间的简单解决方案后,我查看了一些代码并想出了这个,我希望它对将来查看此线程的任何人有所帮助!

我的 Activity 类中有一个全局变量public Uri imageURI = null;,用于设置我正在使用的 ImaveView 的图像 URI;onCreate()和下面的代码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);
}

我希望有人觉得这很有用 - 只要询问您是否需要更多详细信息。

于 2014-05-05T08:59:37.400 回答
1

您应该处理方向更改的方式是保存您的实例状态。如果您填写onSaveInstanceState方法,您可以在 onCreate 期间将保存到该捆绑包中的数据取回。这是为您完成的视图,但您必须自己保存其他数据。任何原始的、可分割的或可序列化的对象都可以通过这种方式保存,包括位图。

您应该这样做不仅是为了在配置更改中幸存下来,而且还要使您的状态保持在低内存条件下。

于 2013-02-23T17:19:38.290 回答
1

您是否尝试过将 android:launchMode="singleTop" 添加到 android 清单中的活动中,对我有用。问题是当方向改变并且您的数据丢失时,android会重新启动活动,如果您的活动已经在运行,它不会创建新实例。

        <activity 
            android:name=".FacebookActivity" 
            android:label="@string/facebook_app_name"
            android:launchMode="singleTop"
            android:configChanges="keyboardHidden|orientation"
            >
        </activity>
于 2013-02-23T17:23:10.617 回答
0

要么使用onSaveInstanceState,然后使用您通过 onCreate 获得的参数,或者在清单上设置您将通过使用configChanges来处理方向更改并设置您希望自己处理的情况(例如orientation|screenSize|screenLayout )。

于 2013-02-23T17:25:10.553 回答