Android之Handling Runtime Changes(处理运行时更改)

一些设备配置在运行过程中可能会发生改变(例如屏幕横向布局、键盘可用性和语言)。当这样的变化发生时,Android会重新启动这个正在运行的Activity(onDestroy()方法会被调用,然后调用onCreate()方法)。这个重启的动作是为了通过自动往你的应用程序中载入可替代资源,从而使你的应用适应新的配置。

为了正确执行一次重启,你的Activity在整个平凡的生命周期中重新保存它之前的状态是很重要的,Android是通过在销毁你的Activity之前调用onSaveInstanceState()方法来保存关于应用之前状态的数据。然后你就可以在onCreate()方法或者onRestoreInstanceState()方法中重新保存应用的状态了。为了测试你的应用可以通过应用的状态原封不动地重启自己,你应该给你的应用授权当程序在执行不同的任务时应用的配置可以改变(例如屏幕的方向变化)。

为了处理一些事件,例如当用户接听一个打入的电话然后返回到你的应用程序中,在没有丢失用户数据或者状态信息的情况下,你的应用应该具备在任何时候重启自己的能力(更多参见Activity lifecycle)。

然而,你可能要面对这样一个情景,重启你的应用程序并重新保存大量有价值的数据会导致很差的用户体验。在这样的情景面前,你有两种选择:

a     在配置改变期间维持一个对象

当配置发生改变时允许你的Activity重启,但让其携带一个有状态的对象到你的新Activity实例中。

b     你自己来处理配置的变化

当某些配置发生变化的时候阻止系统重启你的Activity,并且当配置改变时要接收一个回调,这样你就可以根据需要来手动更新你的Activity。

在配置改变期间维持一个对象

如果重启你的Activity,你需要恢复大量的数据,重新执行网络连接,或者其他深入的操作,这样由配置改变引起的一次完全启动就会引起不好的用户体验。而且,仅有Activity生命周期中为你保存的的Bundle对象,你是不可能完全维护你的Activity的状态的—不能传递很大的对象(如bitmap对象),并且这些对象里面的数据必须序列化,然后解序列化,这些都需要消耗很多内存从而使配置改变得很慢。在这样的情境下,当你的Activity由于配置发生改变而重启时,你可以通过重新预置一个有状态的对象来减缓你程序的负担。

在运行期间配置改变时维护一个对象:

1.      重写 onRetainNonConfigurationInstance() 方法来返回你想要维护的对象

2.      当你的Activity再次创建时,调用getLastNonConfigurationInstance()方法恢复你的对象

当你的Activity由于配置发生改变要关闭的时候,Android会在执行onStop()方法与onDestroy()方法之间调用onRetainNonConfigurationInstance()方法。为了在配置改变后更有效地保存状态,在实现onRetainNonConfigurationInstance() 方法时你应该返回你所需要的一个对象。

这个场景的可贵之处在于当你的应用程序需要从网上下载很多数据的时候。如果用户更改设备的方向并且Activity重启,你的应用程序必须要重新载入数据,那就会很慢了。你需要做的就是实现onRetainNonConfigurationInstance() 方法并返回带有你的数据的对象,然后当你的 Activity通过getLastNonConfigurationInstance()方法重启时就能获取数据。例如:

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

特别提醒:当你要返回任何对象的时候,你应该不要传递一个跟Activity有关联的对象,例如一个Drawable对象,一个Adapter对象,一个View对象或者任何其他跟Context相关的对象 。如果你这样做,它会泄漏原来Activity实例的所有视图和资源。(泄漏资源意味着您的应用程序保持对他们的持有,他们不能被当做垃圾收集,因此内存就丢失了)

然后当你的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()方法中保存的数据。如果数据为空,(这种情况发生在,当Activity重启是由其他原因而不是配置改变引起的)那么程序将从原来的数据源载入数据对象 。

 

 

 

你自己来处理配置的变化

如果在某个特殊的配置发生改变的期间你的应用程序不需要更新资源,而且你有个操作限制需要你避免Activity的重启,那么你可以声明使你自己的Activity来处理配置的变化,从而阻止系统重启你的Activity。

特别提醒: 选择自己来处理配置的变化会使得可替代资源的使用变得更困难,因为系统不会为你来自动调用这些资源。这种技术应该被视为最后的手段,对于大多数应用程序不建议使用。

为了声明你的Activity来处理配置的变化,在清单文件中编辑正确的<activity>元素,包括赋好值的android:configChanges属性,代表你要处理的配置。android:configChanges属性所有可能的值都要在文档中列出(最常用的值是:orientation来处理当屏幕的方向变化时,keyboardHidden来处理键盘可用性改变时)。你可以在属性中声明多个配置的值,通过“|”符号将它们分隔开。

例如,以下清单片段声明了Activity中将同时处理屏幕的方向变化和键盘的可用性变化:

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

当这些配置中的一个发生改变时,MyActivity不会重新启动。相反,这个 Activity会接收onConfigurationChanged()方法的调用。这个方法传递一个Configuration类的对象来标识新的设备配置。通过读取配置字段,你可以确定新的配置信息并通过更新你界面中使用的资源来正确应用这些改变。任何时候这个方法被调用,你的Activity的Resources对象会被更新并返回一个基于新配置的Resources对象,因此你可以在不用系统重启你的Activity的情况下很容易地重置你的UI元素。

例如,接下来的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();
    }
    // Checks whether a hardware keyboard is available
    if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
    } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
    }
}

这个Configuration类的对象代表着当前所有的配置信息,不仅仅那些改变的配置信息。在很多时候,你不会确切地在乎这些配置是怎么改变的,并且可以简单地重新分配所有资源,提供您正在处理的配置的可替代资源。例如,因为这个Resources对象现在被更新,你可以通过setImageResource(int)方法重置任何ImageView,并重置恰当的资源给当前配置使用。(详见:Providing Resources

请注意,配置字段的值是一些匹配Configuration类里特定的常量的整数。对于文档中的每个字段使用那个常量,请在Configuration类中参阅相应的字段。

记住:当你声明你的Activity来处理配置的变化时,你负责重置所有你提供可替代资源的元素 。如果你声明你的Activity来处理屏幕方向的改变并具有在横向和纵向之间切换的图像,你必须在onConfigurationChanged()方法中给每个元素重新指定一个资源。

如果你不需要根据配置的变化来更新你的程序,你可以不实现onConfigurationChanged()方法。在这种情况下,所有在配置改变之前使用的资源仍然会被使用,并且你只需要避免你的Activity被重启。然而,您的应用程序应该始终能够关闭并从其之前的状态完好地重新启动。这不仅是因为存在有一些配置发生改变时你不能防止它重新启动您的应用程序,而且为了处理一些事件,例如当用户接收了电话然后返回到应用程序。

更多关于哪些配置变化时你可以在你的Activity中处理,参见android:configChanges文档和Configuration类。

 

 

 

欢迎转载,转载请注明出处http://www.cnblogs.com/CodeGuy/

posted @ 2011-08-25 23:52  黄明恩  阅读(519)  评论(0编辑  收藏  举报