Android如何去处理运行时配置的改变?

一些设备的配置(比如屏幕方向,键盘的可用性及语言等)可以在运行时改变。当这样的改变发生时,Android会重新启动运行中的Activity(先调用onDestory()方法,然后调用onCreate()方法)。设计这种重新启动的行为是用来帮助你的应用适应新的配置,通过那些匹配新的设备配置的可选的资源自动重载你的应用。

要妥善处理这种重启行为,Activity 必须通过常规的Activity 生命周期方法恢复其之前的状态。在Activity被系统销毁之前(比如由于转动屏幕、切换语言等导致Activity重启)Android会调用onSaveInstanceState()方法,以此来保存关于应用状态的一些数据。然后可以在onCreate() 或者onRestoreInstanceState()方法恢复这些状态。

当然,onSaveInstanceState()方法并不是只有在Activity被系统销毁的时候才会被调用,只要存在潜在的可能性能被系统销毁,都会调用此方法。比如按home键,跳转到其他的Activity,锁屏等等,而且过段时间有可能因为内存的原因被系统销毁,在这种情况下,Android是有义务去保存当前Activity状态的,故onSaveInstanceState()方法也会被调用,尽管Activity在失去交互性的时候并没有立即被系统销毁。当然Activity由于转屏,切换语言而被系统销毁、重启的时候,onSaveInstanceState()肯定会被调用。但是如果Activity是由用户自己销毁的,比如按回退键,Android就没有义务去保存当前状态,onSaveInstanceState()自然也不会被调用。

两种选择

在处理Activity重启的时候,你可能会遇到这样的情况:重启应用和恢复大量数据可能会有高昂的成本开销,比如数据流量增加和用户体验下降等。在这样的情况下,你有两个其他的选择:

1、当配置改变时维持一个对象

当配置改变时允许Activity重启,但是携带一个有状态的对象给新的Activity实例。

2、当配置改变时自己去处理

当配置改变时阻止系统重启你的Activity,但是接收一个回调方法(如果配置确实改变的话),必要的话你可以手动地更新你的Activity。

当配置改变时维持一个对象

如果重启Activity要求你恢复大量的数据集,重新建立一个网络连接,或者执行一些密集型的操作,因为配置的改变而导致这样完整的重启会让用户觉得应用启动过慢,系统onSaveInstanceState()回调方法保存的Bundle对象并不能完全恢复activity的状态——它不是被设计成可以携带大型的对象(比如Bitmap),而且bundle里面的数据必须被序列化和反序列化,可能会消耗很多内存,从而导致配置的改变缓慢。在这样的情况下,当Activity由于配置改变而重启的时候,你可以通过维持一个Fragment碎片来减轻重新初始化Activity的负担。Fragment可以包含一个你想要维持的有状态的对象的引用。

当安卓系统因为配置改变而关闭Activity时,Activity 中被标记要维持的fragment并不会被销毁。你可以在activity里面添加这样的fragments去保存有状态的对象。

当运行配置改变时为了在fragment中维持有状态的对象,你可以这样做:

继承Framgment类和声明有状态对象的引用。

当fragment被创建的时候调用setRetainInstance(boolean)方法。查阅这个方法的API你可以发现,如果参数为true,即setRetainInstance(true),当activity销毁时,fragment的onDestory()方法不会被调用,onDetach()仍然会调用,也就是说fragment不会被销毁,只会和Activity解绑。当这个fragment再次和activity绑定的时候,onCreate()方法就不会再被调用了。系统会一直都维持着这个fragment对象。

将fragment添加到activity中。

当activity重启的时候使用FragmentManager检索这个fragment。

Fragment代码片段如下:

注意:虽然你可以保存任意的对象,但是绝对不能够保存和Activity绑定的对象,比如Drawable,Adapter,View或者任意的和Context有联系的对象。如果这样做,它会泄露原始Activity实例的所有视图和资源(资源泄露意味着应用一直抓着它们不放,也不能被回收,因此会损失很多的内存)。

然后使用FragmentManager将fragment添加到你的Activity中。当Activity因为运行配置改变而再次启动时你可以从fragment中获得这个对象。

Activity代码片段如下:

在上述代码片段中,Activity 的onCreate()方法添加一个fragment或者恢复一个fragment的引用。同时也在fragment中存储了一个有状态的对象。onDestroy()则更新了fragment实例中有状态的对象。

基本涵义

当配置改变时阻止系统重启你的Activity,但是接收一个回调方法(如果配置确实改变的话),必要的话你可以手动地更新你的Activity。

当配置改变时自己去处理

当一个特定的配置改变时,如果你的应用不需要更新资源而且你有一个性能的约束要求你避免Activity重启,那么你可以声明你的Activity自己去处理配置改变,阻止系统重启你的Activity。

应当要注意的是:自己去处理配置改变可能会让资源的选择变得更加困难,因为系统不会自动地应用可选的资源。当因为配置改变而必须避免重启时,这种技术应该被视为最后的手段,对于大多数应用程序不建议使用。

为了声明Activity去处理配置改变,只要在manifest清单文件里面的属性节点中添加android:configChanges= “” 这个属性,配置改变时就可以避免重启Activity.比如最常用的两个:

android:configChanges= “Orientation”表示屏幕方向改变时不会重启Activity

android:configChanges= “keyboardHidden ”表示输入法软键盘可用状态改变时不会重启Activity

若需申明多个配置值用“|”分开即可,如下:

只要配置上述的两个状态,当屏幕方向改变时或者键盘弹出或者消失都不会重启Activity。

更多详细的属性如下:


现在,当其中的一个配置改变时,MyActivity不会重启。MyActivity会收到一个回调onConfigurationChanged()。这个方法会传递一个Configuration对象,这个对象指定了新的设备配置。通过这个Configuration对象,你可以对界面上的资源做出恰当地更新。

当这个方法被调用的时候,Activity的Resources对象已经被新的配置更新,因此你可以很容易去重置UI上的元素而不必去重启Activity。

要注意一点,从3.2(API 13)开始,设备转屏的时候屏幕大小是会发生变化的。如果你的应用要在API13或者更高版本的sdk开发(指定minSdkVersion  >= 13 和targetSdkVersion >=13)且在转屏的时候阻止Activity重启,你就必须这样声明:android:configChanges="orientation|screenSize".

如果你的应用targetSdkVersion在API12或者更低,也就是拿API12或者更低版本的sdk去编译,系统总是会自己处理配置改变(即使将这个应用运行到Android3.2或者更高版本也不会因为转屏而重启Activity)。因为在API13之前,转屏的时候screenSize不会发生改变。

举个例子:下面onConfigurationChanged()方法的实现会去检查当前设备的方向


Configuration代表了当前所有的配置,不仅仅是改变的那些配置,大多数时候,你不必介意配置是如何改变的,只要你提供了对应配置的可选资源(比如提供了横竖屏对应的布局/横竖屏对应的图片资源/不同的语言等等),只需要在onConfigurationChanged()回调方法简单地重新分配就可以了,因为当配置改变的时候,Resources对象已经更新了。

更多关于Configuration的信息请参考:

http://developer.android.com/intl/zh-cn/reference/android/content/res/Configuration.html

而这种方案也是有缺陷的, 因为其他配置的改变也可能导致Activity重启。比如语言的改变等等,而这些改变你未必全部都处理过。

最完美的当然是:不管Activity在什么情况下被销毁,重新启动的时候,还是原来的配方,还是熟悉的味道!


本文作者:项健(点融黑帮),一个热衷于android技术开发的程序猿,曾供职于索尼,主要负责系统app的维护和更新。现任职于点融网北京技术团队。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,458评论 25 707
  • Android Studio JNI流程首先在java代码声明本地方法 用到native关键字 本地方法不用去实现...
    MigrationUK阅读 11,907评论 7 123
  • 这一秒的我比任何时候都要爱你,恐惧,嫉妒,冷漠,锁在最后一颗泪滴里,用手紧握,你踏过我的湖水,涟漪,涟漪,在路的尽...
    方走走阅读 84评论 0 0