Handling Runtime Changes(retain Fragment)

  一些设置在发生 configurations change 的时候(如屏幕旋转、语言改变等),会重新启动当前处于运行太的 activity(即先调用 onDestory(),再调用 onCreate())。重新启动 activity 是为了让它能够适应新的 configuration,它能够根据新的 configuration 加载合适的资源。
  要处理好 restart 事件,就有必要在 activity 中存储 configuration change 前一刻的状态,可以在 onSaveInstanceState() 中存储相关状态,因为 activity 在 destory 之前,该方法会被回调。然后可以在 onCreate() 或 onRestoreInstanceState() 中恢复 activity 的状态。为了恢复 activity 的状态,你需要自己存储/恢复大量的字符串,界面状态等,这是相当繁琐的事情。为了省去这个麻烦,你可以采用下面这两种方法:
(1)在 configuration change 的时候 retain 一个 object:允许 configuration change 的时候 restart activity,但是把相应的 object 存储起来,再传递给新创建的 activity。
(2)自己处理 configuration change:也可以阻止系统在 configuration change 的时候 restart activity,你可以自己捕获 configuration change 事件,然后根据需要更新你的 activity。

一、retaning an object during a configuration change

  如果 restart activity 需要恢复很多数据,重新连接网络等,那么 restart 所需要的时间开销就会比较大,会给客户造成应用反应缓慢的感觉。而且,Bundle 并不能存储所有的大 object,要存储大的 object,必须 serialized 和 deserialize,但是 serialized 操作是很耗内存很耗时的。所以最好的办法就是利用 Fragment 保存 Activity 来省去这种麻烦:通过 fragment 引用 Activity 对应的 objects 对象(此对象会存储着 Activity 的所有状态),这样在 configuration change 的时候,这个 objects 就不会丢失。之所以可以这样做是因为在 configuration change 的时候,调用了 setRetainInstance(true) 的 Fragment 是不会被销毁重建的,所以你可以在 activity 中加上这样的 fragment 来存储 activity 的状态。
  具体步骤如下:
(1)继承 Fragment 类,并声明对代表状态的 objects 的引用;
(2)在 Fragment 的 onCreate() 中调用 setRetainInstance(true);
(3)在 activity 中添加该 Fragment;
(4)在 activity restarted 的时候,通过 FragmentManager 获取这个 Fragment。
如下代码片段所示:
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;
    }
}
注意:虽然你可以存储所有的 object,但是千万不要存储那些和 Activity 绑定的 objects,如 Drawable、Adapter、View 或者那些和 Context 相关联的 objects。因为这样会使得这些 views 和 resources 无法被回收(如果这些 objects 一直被引用着,那么系统在进行垃圾回收的时候,就无法回收它们)。

  然后通过 FragmentManager 把该 Fragment 添加到 Activity 中,如下所示:
public class MyActivity extends Activity{

    private RetainedFragment dataFragment;

    @Override
    publicvoid 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
    publicvoid onDestroy(){
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}
Demo 下载链接


二、Handling the Configuration Change Yourself

  如果在 configuration change 的时候,不需要更新资源,而且你想要避免 activity restart,那么你就得禁止系统 restart activity,然后自己处理 configuration change 事件了。注意:禁止系统 restart activity 会使系统在 configuration change 的时候,无法自动选择加载合适的资源,所以要慎重考虑是否禁止。禁止系统 restart activity 的通常办法是在配置文件中做如下声明来实现:
<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

这样,当 configuration change 的时候,MyActivity 就不会 restart 了,但是它的 onConfigurationChanged() 方法还是会被系统调用的,所以你可以在这个方法里根据新的 configuration 来更新你的 UI(在这个方法被调用的时候,activity 的 resources object 会被刷新,这个刷新是系统根据新的 configuration 来做的,所以你可以根据需要来更新 UI)。

注意:从 Android 3.2(API 13) 开始,横竖屏的切换也会导致 screen size 的改变,所以如果你想禁止系统因横竖屏切换而 restart activity,那么就还要在 activity 的属性声明中加入 screenSize,即 android:configChanges="orientation|screenSize"。但是,如果你用的是 API 12 及以前的版本,那么你就得自己处理这个问题了(这些版本的 API 不会在 configuration change 的时候 restart activity,不管运行在哪种类型的设备上)。如下这段代码检查设备是横屏还是竖屏
@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 对象包含了当前所有的 configurations,而不仅仅是发生改变了的那些 configuration。但是你不必关心所有的 configurations,只需要更新那些所有的 resources 就行了,如通过 setImagesResource() 更新 ImageView 等。

你可能感兴趣的:(android,configuration,Activity,Fragment)