Android应用资源—处理运行时改变(Handing Runtime Changes)

某些设备配置能够在运行期间进行改变(如屏幕的方向、键盘可用性、语言等)。当这样的改变发生时,Android会重启正在运行的Activity(onDestroy()回调之后,紧跟着调用onCreate()回调方法)。设计这种重启行为有助于应用程序通过重启,重新载入跟新设备配置相匹配的可选资源。

要正确的处理重启,重要的是要恢复Activity之前的生存周期状态,因此在Activity被销毁之前,Android会调用onSaveInstanceState()回调方法来保存应用程序相关的状态数据。这样在Activity的onCreate()或onRestoreInstanceState()被调用期间就可以恢复之前的状态了。

要测试应用程序重启后的状态的完整性,就应该在程序运行时改变设备配置(如改变屏幕方向)。应用程序应该在处理配置改变、电话接入等事件的任何时候,都能够重启而且不丢失用户数据和状态。要了解Activity状态是如何被恢复的,请阅读Activity的生存周期。

但是,可能会遇到在重启应用程序时要恢复大量数据的情况,这时可能会给用户带来不好的用户体验。这样情况,有两个选择:

1.  在配置改变期间保留一个对象。

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

2. 自行处理配置的变化

在某个配置改变期间,防止系统重启Activity,但是要在配置改变时接收一个回调方法,以便在必要时手动的更新Activity。

 

在配置变化期间保留一个对象

如果在重启Activity时,要求恢复大数据集、重建网络连接、或执行其他的敏感操作,那么由于配置的改变完全重启会降低用户体验。此外,系统也不可能用onSaveInstanceState()回调方法的Bundle对象来完整的保存要恢复的数据,因为Bundle对象没有被设计用来携带大对象(如位图)和大数据,这样导致Bundle对象在系列化和随后的反系列化时要占用大量的内存,从而导致配置改变缓慢。这种情况下,可以选择保留一个状态对象来减轻恢复Activity重启时的负担。

保留运行期间配置改变对象的方法如下:

1.  重写onRetainNonConfigurationInstance()回调方法,它会返回希望保留的对象。

2.  在Activity被重建时,调用getLastNonConfigurationInstance()方法来恢复这个对象。

当Android系统由于配置的变化关掉Activity时,它会在onStop()回调和onDestroy()之间调用onRetainNonConfigurationInstance()回调方法。在onRetainNonConfigurationInstance()回调的实现中,为了在配置变化之后能够有效的恢复状态,它可以返回任意任何需要的对象。

如果应用程序要从网络上加载数据,这样做就很有价值。如果用户改变了设备的方向,并且Activity重启了,应用程序就必须重新获取数据,这样会很慢。因此可以实现onRetainNonConfigurationInstance()方法,让它返回一个带有数据的对象,然后再Activity被重启时,再用getLastNonConfigurationInstance()方法来获取这个被保留的对象,例如:

@Override
public Object onRetainNonConfigurationInstance() {
final MyDataObject data = collectMyLoadedData();
return data;
}

警告:在返回对象时,不要带有跟Activity绑定的对象,如Drawable、Adapter、View或其他跟Context关联的对象。如果这样做了,会导致最初的Activity实例的所有View对象和资源泄漏(泄漏资源意味着应用程序占用了这些资源,因而不能被作为垃圾来回收,这样就会导致大量的内存占用)。

Activity重启时,恢复数据的方法:

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

final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();
if (data == null) {
data = loadMyData();
}

}

这个例子中,getLastNonConfigurationInstance()方法返回被onRetainNonConfigurationInstance()方法保存的数据。如果data对象是null(在由于配置改变以外的其他原因而导致的Activity重启时,会发生这种情况),那么代码就会从初始资源中装载数据对象。

自行处理配置变化

如果在特定的配置变化期间,应用程序不需要更新资源,并且由于性能的限制,要求避免Activity的重启,那么可以声明让Activity自行处理配置的变化,从而防止系统重启Activity。

注意:自行处理配置的变化使得使用可选资源更加困难,因为系统不能自动的应用它们。这种技术应该在必须避免Activity重启(由于配置的变化)时,最后才考虑使用,并且大数应用程序不推荐使用。

要让Activity处理配置的变化,就要编辑清单文件中相应的<activity>元素,让它包含android:configChanges属性,这个属性值代表了要处理的配置。可能的值在android:configChanges属性文档中被列出(最常使用的值时“orientation”和“keyboardHidden”,orientation用于在屏幕方向变化时,防止Activity的重启;keyboardHidden用于在键盘的可用性发生变化时,防止Activity重启)。通过用管道符“|”分离,可以给这个属性声明多个配置值。

例如,下列清单代码就声明了一个自行处理屏幕方向变化和键盘可用性变化的Activity:

<activityandroid:name=”.MyActivity”
android:configChanges=”orientation|keyboardHidden”
android:label=”@string/app_name”>

通过上述声明,当这些配置中其中之一发生变化时,MyActivity就不会重启了,相反,MyActivity会接收onConfigurationChanged()方法的回调。这个方法被传入了一个Configuration对象,它指定了新的设备配置。通过读取Configuration对象中的字段,就能够判断新的配置,并通过更新界面中使用的资源来进行对应的改变。在调用这个方法时,Activity的Resources对象会被基于新配置所返回的资源更新,因此不用系统重启Activity,就能够容易的重设UI元素。

警告:从Android3.2(API级别13)开始,设备在纵向和横向之间切换时,“屏幕尺寸”也会发生改变。因此在使用API级别13或更高版本开发时,如果想要在运行时防止由于方向的变化而导致的重启,就必须在android:configChanges的属性值中包含“screenSize”。也就是说,必须声明android:configChanges=”orientation|screenSize”。但是,如果应用的目标平台是API级别12或更低版本,那么Activity就会始终自行处理配置的变化(这样配置的变化就不会重启Activity,即使是在Android3.2或更高版本的设备上运行,也不会重启Activity)。

例如,下面的onConfigurationChanged()方法实现了对当前设备方向的检查:

@Override
publicvoid 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();
}elseif(newConfig.orientation ==Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this,”portrait”,Toast.LENGTH_SHORT).show();
}
}

Configuration对象代表了所有的当前配置,不仅仅针对改变的那个配置。大多数时候,不必关注配置是如何变化的,只需把提供的可选资源重新分配给正在处理的配置就可以了。例如,因为Resources对象的更新,就要用setImageResource()方法重设所有ImageView对象,并且给新的配置使用适当的资源。

我们注意到,来自Configuration字段的值是整数,它要跟Configuration类中指定的常量进行匹配。关于这些常量的说明,请参考Configuration类说明。

记住:当声明了要Activity自行处理配置变化时,就要手动的负责重设Activity中的任何UI元素。如果声明了Activity要处理方向的变化,并且在横向和纵向之间切换时有图标的变化,那么在onConfigurationChanged()执行期间,就应该重新分配每个资源。

如果不需要基于这些配置变化来更新应用程序,就不必实现onConfigurationChanged()方法。这样,所有的在配置变化之前使用的资源会在变化之后依然使用,仅仅是避免了Activity的重启。但是,应用程序使用能够关闭并用之前完整的状态来重启,因此,在普通Activity的生存期间不应该考虑使用这种技术,这不仅是因为这样不能阻止其他配置变化所导致的应用程序重启,而且也因为应该处理诸如用户离开应用程序、用户返回应用之前获取被销毁的状态等事件。

关于在Activity中能够处理的配置变化的更多信息,请看android:configChanges文档和Configuration类。

 

声明:本文采用 BY-NC-SA 协议进行授权,本文链接:Android应用资源—处理运行时改变(Handing Runtime Changes)

发表评论