处理Fragment配置变更

        StackOerflow上经常看到类似如下的问题: 在设备配置变更时保持激活对象比如运行中的Threads,Sochets还有AsyncTasks的最好办法是什么?

        本文讨论这个问题。首先讨论对于开发者来讲比较难的在Activity生命周期中的长运行后台线程问题。然后讨论两种解决方案。最终给出实例代码。

配置变化和后台任务

        首先一个问题是配置改变和Activity建立摧毁这些事件是不可预知随时都有可能发生的。同时发生的后台进程也会出现问题。比如,一个Activity启动了一个AsyncTask然后突然用户旋转了屏幕,这会造成Activity摧毁和重建。当AsyncTask完成时会把返回的结果传递给旧的Activity实例,完全不知到新的Activity已经被建立起来了。就像这不是个问题一样,新的线程可能会再次浪费客观的资源重新开启后台任务,并不知道旧的AsyncTask还在运行。因此,当配置变化时我们需要正确的有效率的维持不同Activity间的激活的对象。

坏的实践:维持Activity

        可能最常见和滥用的方法通过更改AndroidManifest的android:configChanges属性是去禁用摧毁重建流程。这种方法明显很简单,让开发者很容易实现。但是谷歌的工程师不鼓励使用这种方法。首要问题是它需要你手动修改代码来控制设备的配置变化。控制设备变更需要你去做许多额外的步骤来保证每一个string字符串、layout,drawable,dimension等等保持和当前设备的配置同步,如果一不小心就会出来一系列的资源相关的bug。

        另一个反对的原因是许多开发者不正确的假设比如修改android:configChanges=“orientation”会神奇的保证他们的应用不会被未知的情景摧毁重构Activity。这是不行的。配置变化可能有一系列的原因导致,不仅仅是屏幕方向变化。比如把设备插到显示架、改变设备默认语言、改变默认字体大小等事件都会改变配置信息。也都会造成当前运行的Acitivity摧毁重建。因此改这个属性是不好的实践。

Deprecated:Override

onRetainNonConfigurationInstance()

        在Honeycomb出来前,比较好的办法是override重写onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()方法。用这种方法,在activity中改变激活的对象就是在onRetainNonConfigurationInstance()方法里返回激活的对象然后getLastNonConfigurationInstance()里取回它。API13之后这种方法被deprecated,因为Fragment的更多的setRetainInstance(boolean)能力,提供了更干净模块化的方法来维持设备配置变化时的对象。下章讨论基于Fragment的方法。

推荐:在Retained Fragment里管理对象

        自从安卓3.0之后,最好的办法就是在保持的工人fragment里保持激活的对象。配置变化时,默认的情况是Fragment随着它的Activity一起摧毁重建。调用Frament的setRetainInstance(True)方法可以避免这个过程,通知系统在activity重建的工程中保持当前的fragment实例。这对保持的running Treads,AsyncTask,Sockets对象非常有用。

        下边的例子是在配置变化时在Fragment里维持一个AsyncTask任务,保证异步任务更新返回的结果回到当前的Activity实例里,保证不会造成异步任务的意外泄漏。例子包含两个类,一个MainActivity:

/**
 * This Activity displays the screen's UI, creates a TaskFragment
 * to manage the task, and receives progress updates and results 
 * from the TaskFragment when they occur.
 */
public class MainActivity extends Activity implements TaskFragment.TaskCallbacks {

  private static final String TAG_TASK_FRAGMENT = "task_fragment";

  private TaskFragment mTaskFragment;

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

    FragmentManager fm = getFragmentManager();
    mTaskFragment = (TaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT);

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (mTaskFragment == null) {
      mTaskFragment = new TaskFragment();
      fm.beginTransaction().add(mTaskFragment, TAG_TASK_FRAGMENT).commit();
    }

    // TODO: initialize views, restore saved state, etc.
  }

  // The four methods below are called by the TaskFragment when new
  // progress updates or results are available. The MainActivity 
  // should respond by updating its UI to indicate the change.

  @Override
  public void onPreExecute() { ... }

  @Override
  public void onProgressUpdate(int percent) { ... }

  @Override
  public void onCancelled() { ... }

  @Override
  public void onPostExecute() { ... }
}
和一个TaskFragment:

/**
 * This Fragment manages a single background task and retains 
 * itself across configuration changes.
 */
public class TaskFragment extends Fragment {

  /**
   * Callback interface through which the fragment will report the
   * task's progress and results back to the Activity.
   */
  static interface TaskCallbacks {
    void onPreExecute();
    void onProgressUpdate(int percent);
    void onCancelled();
    void onPostExecute();
  }

  private TaskCallbacks mCallbacks;
  private DummyTask mTask;

  /**
   * Hold a reference to the parent Activity so we can report the
   * task's current progress and results. The Android framework 
   * will pass us a reference to the newly created Activity after 
   * each configuration change.
   */
  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    mCallbacks = (TaskCallbacks) activity;
  }

  /**
   * This method will only be called once when the retained
   * Fragment is first created.
   */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Retain this fragment across configuration changes.
    setRetainInstance(true);

    // Create and execute the background task.
    mTask = new DummyTask();
    mTask.execute();
  }

  /**
   * Set the callback to null so we don't accidentally leak the 
   * Activity instance.
   */
  @Override
  public void onDetach() {
    super.onDetach();
    mCallbacks = null;
  }

  /**
   * A dummy task that performs some (dumb) background work and
   * proxies progress updates and results back to the Activity.
   *
   * Note that we need to check if the callbacks are null in each
   * method in case they are invoked after the Activity's and
   * Fragment's onDestroy() method have been called.
   */
  private class DummyTask extends AsyncTask<Void, Integer, Void> {

    @Override
    protected void onPreExecute() {
      if (mCallbacks != null) {
        mCallbacks.onPreExecute();
      }
    }

    /**
     * Note that we do NOT call the callback object's methods
     * directly from the background thread, as this could result 
     * in a race condition.
     */
    @Override
    protected Void doInBackground(Void... ignore) {
      for (int i = 0; !isCancelled() && i < 100; i++) {
        SystemClock.sleep(100);
        publishProgress(i);
      }
      return null;
    }

    @Override
    protected void onProgressUpdate(Integer... percent) {
      if (mCallbacks != null) {
        mCallbacks.onProgressUpdate(percent[0]);
      }
    }

    @Override
    protected void onCancelled() {
      if (mCallbacks != null) {
        mCallbacks.onCancelled();
      }
    }

    @Override
    protected void onPostExecute(Void ignore) {
      if (mCallbacks != null) {
        mCallbacks.onPostExecute();
      }
    }
  }
}
事件流:

MainActivity启动后建立TaskFragment然后执行AsyncTask,AsyncTask把结果返回给MainActivity并且刷新UI。当配置发生变化时,MainActivity按照正常的生命周期走,新建里的activity走到 onAttach方法,保证TaskFragment能持有当前显示中的Activity实例。设计很简单但很可可靠,应用框架将会处理新的Activity实例然后TaskFragment和它的异步任务也不用担心配置变更。

总结

同步后台任务和Activity生命周期一起会很麻烦,配置变更也会怎加困惑。幸运的事保持的Fragment可以简单的处理这些事件,保持当前的父Activity实例就算它被摧毁重建。


Ref:http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值