Android中运行时配备环境的改变

Android中运行时配置环境的改变
原文:http://developer.android.com/guide/topics/resources/runtime-changes.html
一、简介

Some device configurations can change during runtime (such as screen orientation, keyboard availability, and language). When such a change occurs, Android restarts the running Activity (onDestroy()is called, followed by onCreate()). The restart behavior is designed to help your application adapt to new configurations by automatically reloading your application with alternative resources that match the new device configuration.

Android中,当应用程序运行的时,设备的配置环境也许会发生变化(比如屏幕的旋转,键盘可用性的改变,语言的改变)。但是这些变化发生时,Android系统会重启Activity(onDestroy()将被调用,随后再调用onCreate()来创建Activity).之所以设计为重启,主要是想通过重新加载你的应用程序来加载和新的配置环境相对应的资源,以便你的应用程序能很好的适配新的配置环境。

To properly handle a restart, it is important that your activity restores its previous state through the normal Activity lifecycle, in which Android calls onSaveInstanceState() before it destroys your activity so that you can save data about the application state. You can then restore the state during onCreate() oronRestoreInstanceState().

为了很好的处理Activity的重启,我们应该在Activity销毁前的系统回调函数中onSaveInstanceState() 保存其状态,然后在其重新被构建时的系统回调函数 onCreate() oronRestoreInstanceState()中,根据前面保存的状态来进行相应的设置,以便还原到重启前的相应状态。关于onSaveInstanceState() 和onRestoreInstanceState()更多的内容可以参考《关于onSaveInstanceState(Bundle)和onRestoreInstanceState(Bundle)

To test that your application restarts itself with the application state intact, you should invoke configuration changes (such as changing the screen orientation) while performing various tasks in your application. Your application should be able to restart at any time without loss of user data or state in order to handle events such as configuration changes or when the user receives an incoming phone call and then returns to your application much later after your application process may have been destroyed. To learn how you can restore your activity state, read about the Activity lifecycle.

为了测试你的应用程序的Activity是否能够很好的进行重启,你应该在你的应用程式执行各类任务时都尝试改变配置环境,检查Activity是否能很好的进行重生。为了很好的处理运行时配置环境的变化或像用户有个来电然后在的应用程序的进程已经销毁后试图回到的你应用程序这种场景,你的应用程序应该在任何时候都能够很好的被重启,而不丢失任何用户数据和用户状态。

However, you might encounter a situation in which restarting your application and restoring significant amounts of data can be costly and create a poor user experience. In such a situation, you have two other options:

然而你可能面临如下问题:重启一个Activity并重新恢复其相关的数据非常的昂贵,这样可能造成用户体验非常的不好。这时你有以下两个选择:

  1. Retain an object during a configuration change

    Allow your activity to restart when a configuration changes, but carry a stateful Object to the new instance of your activity.在运行配置环境发生变化时,允许Activity进行重启,但是携带一个能表明Activity此时状态的对象到新的Activity对象

  2. Handle the configuration change yourself

    Prevent the system from restarting your activity during certain configuration changes, but receive a callback when the configurations do change, so that you can manuall.但某些配置发生变化时,阻止系统重启你的activity,而且只是在回调函数onConfigurationChanged() i中相应的处理

Retaining an Object During a Configuration Change

If restarting your activity requires that you recover large sets of data, re-establish a network connection, or perform other intensive operations, then a full restart due to a configuration change might be a slow user experience. Also, it might not be possible for you to completely restore your activity state with the Bundle that the system saves for you with the onSaveInstanceState() callback—it is not designed to carry large objects (such as bitmaps) and the data within it must be serialized then deserialized, which can consume a lot of memory and make the configuration change slow. In such a situation, you can alleviate the burden of reinitializing your activity by retaining a stateful Object when your activity is restarted due to a configuration change.

如果重启一个activity需要恢复大量的数据,或需要重新进行网络链接操作,或其他一些敏感的操作,这时因为运行时配置环境变化而重启activity就可能降低用户体验。另外可能完全通过的回调函数onSaveInstanceState() 的Bundle可能也无法完全保存activity此时的状态。Bundle 本身并不是设计来携带大型对象(比如 bitmaps)的,它携带的数据必须是可序列化和反序列化;如果携带大型对象的话,将消耗大量的内存并降低应用程序对运行时环境变化的相应速度。在这种情形下,可以在你的应用程序因为运行时环境发生变化而需重启时,请求系统retian一个代表activity状态的对象以便activity重生时使用,这样可以达到优化activity重生的目的。

To retain an object during a runtime configuration change:

为了在运行时环境发生变化时,retain一个对象需要做以下两步操作。

  1. Override the onRetainNonConfigurationInstance() method to return the object you would like to retain.重写onRetainNonConfigurationInstance()  方法,在里面返回一个你想retain的对象
  2. When your activity is created again, call getLastNonConfigurationInstance() to recover your object.在activity重生时,通过方法 getLastNonConfigurationInstance()  得到retian的对象,通过该对象恢复activity的状态。

When the Android system shuts down your activity due to a configuration change, it callsonRetainNonConfigurationInstance() between the onStop() and onDestroy() callbacks. In your implementation of onRetainNonConfigurationInstance(), you can return any Object that you need in order to efficiently restore your state after the configuration change.当系统因为运行时环境发生变化而关闭你的activity时,系统会在activity的生命周期函数onStop() onDestroy()之间调用函数onRetainNonConfigurationInstance() ,你可以重写该函数,在里面返回一个你想retain的对象。在activity重生时,通过方法 getLastNonConfigurationInstance()  得到retian的对象,通过该对象恢复activity的状态。

A scenario in which this can be valuable is if your application loads a lot of data from the web. If the user changes the orientation of the device and the activity restarts, your application must re-fetch the data, which could be slow. What you can do instead is implement onRetainNonConfigurationInstance() to return an object carrying your data and then retrieve the data when your activity starts again withgetLastNonConfigurationInstance(). For example:

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

Caution: While you can return any object, you should never pass an object that is tied to the Activity, such as a Drawable, an Adapter, a View or any other object that's associated with a Context. If you do, it will leak all the views and resources of the original activity instance. (Leaking resources means that your application maintains a hold on them and they cannot be garbage-collected, so lots of memory can be lost.)

注意:你通过onRetainNonConfigurationInstance() 返回的对象不能有对当前Activity的引用,比如:a Drawable, an Adapter, a View或其他对当前Activity有引用的对象。否则会造成当前Activity无法被回收,造成内存泄露。

Then retrieve the data when your activity starts again:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();
    if (data == null) {
        data = loadMyData();
    }
    ...
}

In this case, getLastNonConfigurationInstance() returns the data saved byonRetainNonConfigurationInstance(). If data is null (which happens when the activity starts due to any reason other than a configuration change) then this code loads the data object from the original source.

public Object getLastNonConfigurationInstance ()

Since: API Level 1

This method is deprecated.
Use the new Fragment API setRetainInstance(boolean) instead; this is also available on older platforms through the Android compatibility package.

Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initialonCreate(Bundle) and onStart() calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.

该方法将返回onRetainNonConfigurationInstance()中返回给系统用于retain的对象,该方法在Activity重生时的onCreate(Bundle) onStart() 生命周期之间有效。因为onRestoreInstanceState()在之后onStart() 调用,所以 getLastNonConfigurationInstance() 在方法onRestoreInstanceState()中使用无效。

Note that the data you retrieve here should only be used as an optimization for handling configuration changes. You should always be able to handle getting a null pointer back, and an activity must still be able to restore itself to its previous state (through the normal onSaveInstanceState(Bundle) mechanism) even if this function returns null.

Returns
  • Returns the object previously returned by onRetainNonConfigurationInstance().
三、Handling the Configuration Change Yourself

If your application doesn't need to update resources during a specific configuration change and you have a performance limitation that requires you to avoid the activity restart, then you can declare that your activity handles the configuration change itself, which prevents the system from restarting your activity.

当某个运行时环境发生变化,如果你并不需要更新资源,或者因为性能的制约要求你避免重启Activity,你可以声明你自己处理这个运行时环境的变化,以避免系统restart你的activity.

Note: Handling the configuration change yourself can make it much more difficult to use alternative resources, because the system does not automatically apply them for you. This technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications.

To declare that your activity handles a configuration change, edit the appropriate <activity> element in your manifest file to include the android:configChanges attribute with a value that represents the configuration you want to handle. Possible values are listed in the documentation for the android:configChanges attribute (the most commonly used values are "orientation" to prevent restarts when the screen orientation changes and "keyboardHidden" to prevent restarts when the keyboard availability changes). You can declare multiple configuration values in the attribute by separating them with a pipe | character.

为了声明你将自己处理运行时环境的变化,你需要在mainfest.xml文件中设置<activity> 元素的android:configChanges属性,该属性所有可能的值都在android:configChanges的文档说明中(其中最常用的是关于屏幕旋转的"orientation"和键盘availability"keyboardHidden")。你可以声明多个configuration值,它们之间用|进行分割

For example, the following manifest code declares an activity that handles both the screen orientation change and keyboard availability change:

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

Now, when one of these configurations change, MyActivity does not restart. Instead, the MyActivity receives a call to onConfigurationChanged(). This method is passed a Configuration object that specifies the new device configuration. By reading fields in the Configuration, you can determine the new configuration and make appropriate changes by updating the resources used in your interface. At the time this method is called, your activity's Resources object is updated to return resources based on the new configuration, so you can easily reset elements of your UI without the system restarting your activity.

这样的话当屏幕旋转或键盘availability发生变变化时,系统将不再重启Activity;取而代之的是这个Activity的函数onConfigurationChanged()将被系统调用。该函数传递一个Configuration 对象,以说明运行时配置环境的变化。通过读该Configuration 对象,你就能确定新的运行时配置环境,通过更新你的用户界面的资源来做合适的改变。当该方法被调用时,Activity的Resources对象已经是根据新的运行时配置环境而返回资源了。

Caution: Beginning with Android 3.2 (API level 13), the "screen size" also changes when the device switches between portrait and landscape orientation. Thus, if you want to prevent runtime restarts due to orientation change when developing for API level 13 or higher (as declared by the minSdkVersion andtargetSdkVersion attributes), you must include the "screenSize" value in addition to the"orientation" value. That is, you must decalareandroid:configChanges="orientation|screenSize". However, if your application targets API level 12 or lower, then your activity always handles this configuration change itself (this configuration change does not restart your activity, even when running on an Android 3.2 or higher device).

注意:从Android 3.2 (API level 13)开始,当设备的屏幕在横屏和竖屏之间进行切换时,"screen size"这个配置属性也会发生变化。因此从API level 13 or higher  (as declared by the minSdkVersion andtargetSdkVersion attributes)开始,如果你想屏幕旋转时,系统不重启Activity的话,你应该在android:configChanges=的属性值"orientation" 上再加上"screenSize",比如android:configChanges="orientation|screenSize"。然而如果你的应用程序的targets API level 12 或更低的,即使设备的是Android 3.2或更高,你不加"screenSize"的话,系统也不会重启Activity,而是调用函数onConfigurationChanged()让你自己处理,屏幕改变这个运行时配置环境的变化。

For example, the following onConfigurationChanged() implementation checks the current device orientation:

以下是一个在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();
    }
}

The Configuration object represents all of the current configurations, not just the ones that have changed. Most of the time, you won't care exactly how the configuration has changed and can simply re-assign all your resources that provide alternatives to the configuration that you're handling. For example, because theResources object is now updated, you can reset any ImageViews with setImageResource() and the appropriate resource for the new configuration is used (as described in Providing Resources).

onConfigurationChanged()中的传人的参数Configuration 对象代表的是当前运行时的所有配置,而不仅仅是改变了的运行时配置。大多数时,你并不用关心配置环境是如果变化的,你仅仅需要的是re-assign所有的resources。比如因为Resources对象已经是根据新的运行时配置环境而返回资源了,对于ImageViews 你只可以直接调用setImageResource()来进行资源的更新

Notice that the values from the Configuration fields are integers that are matched to specific constants from the Configuration class. For documentation about which constants to use with each field, refer to the appropriate field in the Configuration reference.

Remember: When you declare your activity to handle a configuration change, you are responsible for resetting any elements for which you provide alternatives. If you declare your activity to handle the orientation change and have images that should change between landscape and portrait, you must re-assign each resource to each element during onConfigurationChanged().

If you don't need to update your application based on these configuration changes, you can instead not implement onConfigurationChanged(). In which case, all of the resources used before the configuration change are still used and you've only avoided the restart of your activity. However, your application should always be able to shutdown and restart with its previous state intact, so you should not consider this technique an escape from retaining your state during normal activity lifecycle. Not only because there are other configuration changes that you cannot prevent from restarting your application, but also because you should handle events such as when the user leaves your application and it gets destroyed before the user returns to it.

对于所有的运行时配置环境变化,你如果都不想你的应用程序因此而做任何的改变, 你根本就可以不用实现onConfigurationChanged().这样的话,当任何运行时配置环境发生时,你的Activity就不会做任何改变。然而你的应用程序应该能被关闭然后重启回到和以前一样的状态。因此前面介绍的技术并不是用来逃避Activity被关闭然后重启回到以前状态的。这并不仅仅是因为还没有阻止因其他的配置变化而重启Activity;这也是因你在用户离开你的应用程序然后该应用程序结束后又试图回到你的应用程序这种场景下你其实是不得不处理Activity关闭然后重启回到和以前一样的状态这么一个event的

For more about which configuration changes you can handle in your activity, see the android:configChangesdocumentation and the Configuration class.

结束