对android:screenOrientation及android:configChanges的思考

对屏幕旋转而引发的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也没有销毁。

带给我们的思考:
  • 如何可以保证Activity不重建?
    1. 简单处理可以配置参数如下:
      android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
      但是这种方案在配置的参数之外的参数发生改变时同样会交给系统处理导致重建,所以这种方案需要考虑很多情况,如果没有特殊情况还是可以满足需求的
    2. 配置参数
      android:screenOrientation="portrait"
      固定屏幕方向,但是损失了横屏的体验(视实际情况酌情处理)
  • 当有可能发生重建情况时如果正确的保存重要数据
    做个简单测试,新建A和B两个Activity。
    1. 从A进入B,旋转屏幕改变B的方向,保持屏幕方向不变按返回键回到A观察日志输出会发现在A进入B的时候onSaveInstanceState方法被调用,从B返回A时onRestoreInstanceState被调用。
      这种现象完全可以理解,因为在B旋转屏幕时屏幕大小以及发生改变,保持屏幕方向不变再返回A时,A是需要重建的
    2. 测试步骤和1相同,但是在进入B后不旋转屏幕,直接返回A,发现onRestoreInstanceState并没有调用。
      这种现象是因为回到A时屏幕大小并没有发生改变,所以并不需要调用onRestoreInstanceState来恢复数据

所以最终我们还是需要配合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将不能被回收,所以最好不要这样来解决问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容