处理运行时更改
某些设备配置可能会在运行时更改(例如屏幕方向,键盘可用性和语言)。当发生这样的更改时,Android会重新启动正在运行的Activity(调用onDestroy(),然后调用onCreate())。重新启动行为旨在通过使用与新设备配置匹配的备用资源自动重新加载应用程序,帮助您的应用程序适应新配置。
要正确处理重新启动,您的活动必须通过正常的Activity生命周期恢复其先前的状态,在此生命周期中Android会在销毁活动之前调用onSaveInstanceState(),以便您可以保存有关应用程序状态的数据。然后,您可以在onCreate()或onRestoreInstanceState()期间恢复状态。
要测试应用程序在应用程序状态不变的情况下重新启动自身,您应该在应用程序中执行各种任务时调用配置更改(例如更改屏幕方向)。您的应用程序应该能够在不丢失用户数据或状态的情况下随时重新启动,以便处理诸如配置更改之类的事件,或者当用户收到来电时,然后在您的申请过程之后很久就返回到您的应用程序销毁。要了解如何恢复活动状态,请阅读活动生命周期。
但是,您可能会遇到这样的情况:重新启动应用程序并恢复大量数据可能成本高昂并且会导致糟糕的用户体验。在这种情况下,您还有两个选择:
- 在配置更改期间保留对象
允许您的活动在配置更改时重新启动,但将有状态对象带到活动的新实例。
- 自己处理配置更改
在某些配置更改期间阻止系统重新启动您的活动,但在配置更改时接收回调,以便您可以根据需要手动更新活动。
在配置更改期间保留对象
如果重新启动活动需要恢复大量数据,重新建立网络连接或执行其他密集操作,则由于配置更改而导致的完全重新启动可能会降低用户体验。此外,您可能无法使用系统为onSaveInstanceState()回调为您保存的Bundle完全恢复活动状态 - 它不是为承载大型对象(如位图)而设计的,其中的数据必须序列化然后反序列化,这可能会消耗大量内存并使配置变化缓慢。在这种情况下,您可以通过在配置更改时重新启动活动时保留Fragment来减轻重新初始化活动的负担。此片段可以包含对要保留的有状态对象的引用。
当Android系统因配置更改而关闭您的活动时,您标记为要保留的活动片段不会被销毁。您可以将此类片段添加到活动中以保留有状态对象。
- 扩展Fragment类并声明对有状态对象的引用。
- 创建片段时调用setRetainInstance(boolean)。
- 将片段添加到您的活动中。
- 重新启动活动时,使用FragmentManager检索片段。
例如,按如下方式定义片段:
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关联的对象,例如Drawable,Adapter,View或与Context关联的任何其他对象。如果这样做,它将泄漏原始活动实例的所有视图和资源。 (资源泄漏意味着您的应用程序保持对它们的保持并且它们不能被垃圾收集,因此可能会丢失大量内存。)
然后使用FragmentManager将片段添加到活动中。在运行时配置更改期间,当活动再次启动时,您可以从片段中获取数据对象。例如,按如下方式定义您的活动:
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}
// the data is available in dataFragment.getData()
...
}
@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}
在此示例中,onCreate()添加片段或恢复对它的引用。 onCreate()还将有状态对象存储在片段实例中。 onDestroy()更新保留的片段实例中的有状态对象。
自行处理配置改变
如果您的应用程序在特定配置更改期间不需要更新资源,并且您有性能限制要求您避免重新启动活动,那么您可以声明您的活动自己处理配置更改,这会阻止系统重新启动活动。
注意:自行处理配置更改会使使用备用资源变得更加困难,因为系统不会自动为您应用它们。当您必须避免由于配置更改而重新启动时,此技术应被视为最后的手段,并且不建议用于大多数应用程序。
要声明您的活动处理配置更改,请编辑清单文件中相应的<activity>元素,以包含android:configChanges属性,其中包含表示要处理的配置的值。 android:configChanges属性的文档中列出了可能的值(最常用的值是“orientation”以防止在屏幕方向更改时重新启动,“keyboardHidden”以防止在键盘可用性更改时重新启动)。您可以通过用管道分隔属性来声明属性中的多个配置值字符。
例如,以下清单代码声明了一个处理屏幕方向更改和键盘可用性更改的活动:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
现在,当其中一个配置发生更改时,MyActivity不会重新启动。相反,MyActivity接收对onConfigurationChanged()的调用。此方法传递一个Configuration对象,该对象指定新设备配置。通过读取“配置”中的字段,您可以确定新配置并通过更新界面中使用的资源进行适当的更改。在调用此方法时,您的活动的Resources对象将更新为基于新配置返回资源,因此您可以轻松重置UI的元素,而无需系统重新启动您的活动。
警告:从Android 3.2(API级别13)开始,当设备在纵向和横向之间切换时,“屏幕大小”也会更改。因此,如果要在开发API级别13或更高级别(由minSdkVersion和targetSdkVersion属性声明)时由于方向更改而阻止运行时重新启动,则除了“orientation”值之外,还必须包含“screenSize”值。也就是说,你必须decalare android:configChanges =“orientation | screenSize”。但是,如果您的应用程序的目标是API级别12或更低,那么您的活动始终会自行处理此配置更改(即使在Android 3.2或更高版本的设备上运行,此配置更改也不会重新启动您的活动)。
例如,以下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();
}
}
Configuration对象表示所有当前配置,而不仅仅是已更改的配置。大多数情况下,您不会完全关心配置的更改方式,只需重新分配所有资源,这些资源可提供您正在处理的配置的替代方案。例如,因为现在更新了Resources对象,所以可以使用setImageResource()重置任何ImageView,并使用适当的新配置资源(如提供资源中所述)。
请注意,“Configuration
”字段中的值是与“Configuration
”类中的特定常量匹配的整数。有关每个字段使用哪些常量的文档,请参阅“Configuration
”参考中的相应字段
请记住:当您声明活动以处理配置更改时,您有责任重置您提供备选方案的任何元素。如果声明活动以处理方向更改并且图像应在横向和纵向之间更改,则必须在onConfigurationChanged()期间将每个资源重新分配给每个元素。
如果您不需要根据这些配置更改来更新应用程序,则可以不实现onConfigurationChanged()。在这种情况下,仍然使用配置更改之前使用的所有资源,并且您只避免重新启动活动。但是,您的应用程序应该始终能够关闭并重新启动其先前的状态,因此您不应该认为这种技术可以避免在正常活动生命周期中保留您的状态。这不仅是因为还有其他配置更改无法阻止重新启动应用程序,还因为您应该处理事件,例如用户离开应用程序时它会在用户返回之前被销毁。
有关您可以在活动中处理哪些配置更改的更多信息,请参阅android:configChanges文档和Configuration类。