android开发文集Android开发经验谈Android知识

对android:screenOrientation及andro

2016-05-18  本文已影响1115人  laogui

对屏幕旋转而引发的Activity重新创建的问题想必所有从事android开发的人来说再熟悉不过了,大家可以通过测试来了解这整个过程。比如我的测试过程如下:

  1. 新建BaseAcitivity作为父类(方便添加测试类)
    BaseAcitivity.java
    public class BaseAcitivity extends AppCompatActivity {
    protected final String TAG ;
    public BaseAcitivity() {
    TAG = this.getClass().getSimpleName();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    }
    @Override
    protected void onStart() {
    super.onStart();
    Log.d(TAG,"onStart");
    }
    @Override
    protected void onResume() {
    super.onResume();
    Log.d(TAG, "onResume");
    }
    @Override
    protected void onPause() {
    super.onPause();
    Log.d(TAG, "onPause");
    }
    @Override
    protected void onStop() {
    super.onStop();
    Log.d(TAG, "onStop");
    }
    @Override
    protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy");
    }
    }
    代码略多,只是为了演示activity整个生命周期的回调,如果大家比较熟悉了可以略过这段代码。

  2. 建立子类
    ScreenChangeActivity.java
    public class ScreenChangeActivity extends BaseAcitivity {
    private StringBuffer text = new StringBuffer();
    private TextView textView;

         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_screen_change);
             textView = (TextView)findViewById(R.id.text);
         }
    
         @Override
         protected void onSaveInstanceState(Bundle outState) {
             super.onSaveInstanceState(outState);
             updateTextView("onSaveInstanceState\n");
             Log.d(TAG,"onSaveInstanceState");
         }
    
         @Override
         protected void onRestoreInstanceState(Bundle savedInstanceState) {
             super.onRestoreInstanceState(savedInstanceState);
             updateTextView("onRestoreInstanceState\n");
             Log.d(TAG,"onRestoreInstanceState");
         }
    
         @Override
         public void onConfigurationChanged(Configuration newConfig) {
             super.onConfigurationChanged(newConfig);
             updateTextView("onConfigurationChanged\n");
             updateTextView("newConfig:" + newConfig.toString());
             Log.d(TAG,"onConfigurationChanged");
         }
    
         private void updateTextView(String str){
             text.append(str);
             textView.setText(text.toString());
         }
     }
    

运行程序观察打印日志如下:
ScreenChangeActivity: onCreate
ScreenChangeActivity: onStart
ScreenChangeActivity: onResume
接着旋转屏幕观察打印日志如下:
ScreenChangeActivity: onPause
ScreenChangeActivity: onSaveInstanceState
ScreenChangeActivity: onStop
ScreenChangeActivity: onDestroy
ScreenChangeActivity: onCreate
ScreenChangeActivity: onStart
ScreenChangeActivity: onRestoreInstanceState

正常运行程序的流程不多讲了,通过日志可以看出如果屏幕旋转了确实发生Activity销毁并重新创建的情况,销毁的流程告诉我们必然会调用onPause, onStoponDestroy。大家仔细观察旋转后的日志输出可以发现onSaveInstanceState,onRestoreInstanceState会在销毁之前的onPause以及重建后的onStart方法之后调用,说明在销毁之前你可以在onSaveInstanceState方法中做些数据保存等操作,在销毁之后需要恢复数据的操作放在在onSaveInstanceState方法中。

在日志中没有看到onConfigurationChanged方法被调用过,这是我们在AndroidManifest.xml文件中没有对activity的android:configChanges=""配置参数。现在配置上参数如下:
<activity android:name=".ScreenChangeActivity" android:configChanges="screenSize"></activity>表示当屏幕大小变化时Activity自己来处理这种情况而不是交给系统来处理(默认就是销毁再重建)
运行并旋转屏幕再次测试观察日志输出:
ScreenChangeActivity: onCreate
ScreenChangeActivity: onStart
ScreenChangeActivity: onResume
ScreenChangeActivity: onConfigurationChanged
发现确实如我们所料onSaveInstanceState,onRestoreInstanceState并没有被调用并且Activity也没有销毁。

带给我们的思考:

所以最终我们还是需要配合onSaveInstanceStateonRestoreInstanceState来保持和恢复数据的

实际应用:

想必大家都处理过如下应用场景:
ActivityA用来展示相册中的图片,有个入口可以调用系统相机用来拍照片,调起系统相机进入拍照界面(暂时假定是ActivityB),拍摄完毕后回到ActivityA,我们需要扫描制定的文件路径来更新ActivityA实时展示新拍的照片。有些机型比如典型的三星机器,在进入系统相机界面后会强制横屏,如果不做任何处理的典型代码如下:
TestActivity.java

private File file;//成员变量file用来保存拍照后的图片文件
private void openCamera() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    String dirPath = Environment.getExternalStorageDirectory().getAbsolutePath();
    String dateStr = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
    String imageFileName = dateStr + "wenwan_.jpg";
    file = null;
    file = new File(dirPath, imageFileName); 
    Uri imageUri = Uri.fromFile(file);
    intent.putExtra(MediaStore.Images.Media.ORIENTATION, 0);
    //指定拍照完成后的照片存放位置
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intent, CAMERA_RESULT);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK && estCodreque == CAMERA_RESULT) {
        //扫描制定位置的文件
        String scanPath = file.getAbsolutePath();
        MediaScannerConnection.scanFile(this,
                new String[]{scanPath}, null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    public void onScanCompleted(String path, Uri uri) {
                        //扫描完毕后通知可以刷新UI了
                        Message message = scanfileHandler.obtainMessage(100);
                        scanfileHandler.sendMessage(message);
                    }
                });
    }
}

运行程序进入拍照界面,横屏拍完照片返回讲发生NullPointerException的异常。原因很简单,因为ActivityA重建了,成员变量file将重新初始化为默认值null,而onActivityResult方法中使用了file.getAbsolutePath()。当然有人认为是不是做个判空的安全操作就完事了呢,当然不可,如果判空不执行扫描代码,则虽然不会发生异常,但是新拍的照片将不能事实展示出来。

解决方案就是加入如下代码:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if(file == null)
        return;
    outState.putSerializable("file",file);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    file = null;
    file = (File) savedInstanceState.getSerializable("file");
}

在切换Activity的时候在onSaveInstanceState中保持file到Bundle中,在需要恢复的时候从Bundle中获取。

还有一种暴力解决办法应该是将file定义为static成员,这样对象重新创建时static成员将不会重新初始化而是保留上次的值,但是这无意中延长了file的生命周期,在Activity结束后file将不能被回收,所以最好不要这样来解决问题

上一篇下一篇

猜你喜欢

热点阅读