Handling Orientation Change in Android

默认情况下,横竖屏切换时 Android 会销毁当前 Activity 然后重启它。这时某些运行时状态可能会丢失,因此我们要根据我们的需求防止这些状态的丢失。Android 给我们提供了两种选择:

  1. 将这些状态保存到 Activity 实例之外的地方:Activity 依然会重启,在重启时将这些状态值传给新的 Activity
  2. 我们自己处理横竖屏切换:Activity 不会重启,我们可以根据需要在回调函数中自己处理状态变化。

1. Retaining an Object During a Orientation Change

横竖屏切换时,Activity 会重启,因此需要一个不依赖 Activity 实例的对象保存状态值。Android 官方文档推荐了两种方式,简单数据可保存到一个 Bundle 对象中;而复杂状态可保存在一个特殊的 Fragment 中。

1.1 Restore with Bundle

在横竖屏切换销毁 Activity 时,系统会将之前的状态以键值对的方式存储到一个 Bundle 对象中,默认只会保存布局中每个 View 的一些信息,要储存我们自己的状态信息,只用在相关的回调函数中去做就可以。

1.1.1 Save activity state

系统准备开始销毁 Activity 时会调用 onSaveInstanceState() 方法,因此我们要存储更多的状态就需要重写此方法。

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

1.1.2 Restore activity state

Activity 被重新创建时,可以在 onCreate()onRestoreInstanceState() 方法中读取之前存储的状态。onCreate() 在每次创建时都会被调用,非重启情况下 Bundle 为空。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        // Restore value of members from saved state
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance
    }
    ...
}

onRestoreInstanceState() 方法会在 onStart() 之后被调用,并且只有在有存储状态时才会被调用。

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

1.2 Manage complex Object inside a Retained Fragment

一般情况下,Fragment 和会 Activity 一起被销毁,但是在 API level 11 之后,可以标记 Fragment 在其父 Activity 销毁时保留下来。利用这一特性,我们就可以将状态保存在一个特殊的 Fragment 中,需要做以下几步:

  1. 扩展 Fragment 类用于保存我们需要的状态值;
  2. 创建 Fragment 时调用 setRetainInstance(boolean)方法;
  3. Fragment 添加到 Activity 中;
  4. Activity 重启时使用 FragmentManager 获取 Fragment

定义 Fragment 如下:

public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}

然后在 Activity 中使用 Fragment 保存状态, 重启时使用 FragmentManager 获取 Fragment,如下:

public class MyActivity extends Activity {

    private static final String TAG_RETAINED_FRAGMENT = "RetainedFragment";

    private RetainedFragment mRetainedFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT);

        // create the fragment and data the first time
        if (mRetainedFragment == null) {
            // add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment, TAG_RETAINED_FRAGMENT).commit();
            // load data from a data source or perform any calculation
            mRetainedFragment.setData(loadMyData());
        }

        // the data is available in mRetainedFragment.getData() even after 
        // subsequent configuration change restarts.
        ...
    }
}

当我们不再需要保存的状态时,可在 onPause() 中检查 isFinishing() 并移除 Fragment

    @Override
    public void onPause() {
        // perform other onPause related actions
        ...
        // this means that this activity will not be recreated now, user is leaving it
        // or the activity is otherwise finishing
        if(isFinishing()) {
            FragmentManager fm = getFragmentManager();
            // we will not need this fragment anymore, this may also be a good place to signal
            // to the retained fragment object to perform its own cleanup.
            fm.beginTransaction().remove(mDataFragment).commit();
        }
    }

2. Handling the Orientation Change Yourself

如果由于性能等因素重启 Activity 太慢,并且应用没有资源依赖横竖屏状态,那么可以选择在 AndroidManifest.xml 中为 Activity 设置 android:configChanges
属性为 "orientation|screenSize"。例如:

<activity android:name=".MyActivity"
          android:configChanges="orientation|screenSize" />

现在,切换横竖屏不会再导致 Activity 重启,取而代之,系统会调用 onConfigurationChanged() 方法以便我们自己去处理横竖屏切换需要做的改变。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

3. Summary

每种方法各有优缺,我们要根据场景选择合适的方法,如无必要,尽量不要选择最后一种方法。因为还有一些其它会导致 Activity 重启的情况,在这些情况下状态信息没有保存,也就无法恢复到之前的状态。

4. Reference

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

推荐阅读更多精彩内容