[关闭]
@linux1s1s 2017-01-22T16:04:38.000000Z 字数 5117 阅读 2384

Android 代码优化

AndroidRefine 2015-05


有这样一个话题在 StackOverflow: 上提起过:

What is the best way to retain active objects—such as running Threads, Sockets, and AsyncTasks—across device configuration changes?

简单的说: AnsyncTask & Socket & Thread 当Configuration改变的时候该如何处理?

回答这个问题前,先来讨论一下Android开发如何在Activity生命周期中处理长时间后台任务,然后再提出两种处理方法的缺陷,最后给出简单的推荐处理方式。

假定,Activity启动一个后台AnsyncTask,当用户旋转屏幕以后,Activity会销毁和重建,当 AsyncTask 最终完成其工作时,它将错误地将结果返回到旧的Activity实例,完全没有意识到已创建一个新的Activity。好像这并不是神马问题,因为新的Activity可以从新发起AsyncTask,即使这被认为是浪费资源,因为新的Activity完全没有意识到已经有AsyncTask实例在运行。

基于以上原因,当Configuration改变的时候,正确有效的保存Activity实例可以节约必要的资源。

保存Activity实例-不推荐

通过修改Android manifest的configChanges 属性可以禁止销毁和重建Activity当Configuration改变的时候,这个是解决这类问题最普遍的做法。这个方法看似相当简单,所以大部分Android开发人员特别热衷这么做。然而Google的工程师们,并不推荐这么做。
主要有以下原因:

基于以上原因,在Android manifest设置configChanges 属性并非明智之举。

重写 onRetainNonConfigurationInstance()-不推荐

在Activity实例之间传递对象比较推荐的方法是通过重写onRetainNonConfigurationInstance()getLastNonConfigurationInstance() 方法,然而这在API13以后废弃了,因为有更好的方法可以替代上面的重写方法。那么该如何处理上面的问题呢?

保持Activity内部的Fragment实例-推荐

自从Android 3.0引入Fragment以后,在Activity实例之间传递对象的推荐方法变成了保持Activity内部的Fragment实例,因为默认情况下,Fragment的生命周期是和它依附的Activity生命周期同步的。通过调用** Fragment#setRetainInstance(true)** 可以帮助我们绕过销毁-重建实例的周期,直接得到例如AnsyncTask & Socket & Thread等的引用。
下面是个简单的例子:

MainActivity.java

  1. /**
  2. * This Activity displays the screen's UI, creates a TaskFragment
  3. * to manage the task, and receives progress updates and results
  4. * from the TaskFragment when they occur.
  5. */
  6. public class MainActivity extends Activity implements TaskFragment.TaskCallbacks {
  7. private static final String TAG_TASK_FRAGMENT = "task_fragment";
  8. private TaskFragment mTaskFragment;
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.main);
  13. FragmentManager fm = getFragmentManager();
  14. mTaskFragment = (TaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT);
  15. // If the Fragment is non-null, then it is currently being
  16. // retained across a configuration change.
  17. if (mTaskFragment == null) {
  18. mTaskFragment = new TaskFragment();
  19. fm.beginTransaction().add(mTaskFragment, TAG_TASK_FRAGMENT).commit();
  20. }
  21. // TODO: initialize views, restore saved state, etc.
  22. }
  23. // The four methods below are called by the TaskFragment when new
  24. // progress updates or results are available. The MainActivity
  25. // should respond by updating its UI to indicate the change.
  26. @Override
  27. public void onPreExecute() { ... }
  28. @Override
  29. public void onProgressUpdate(int percent) { ... }
  30. @Override
  31. public void onCancelled() { ... }
  32. @Override
  33. public void onPostExecute() { ... }
  34. }

TaskFragment.java

  1. /**
  2. * This Fragment manages a single background task and retains
  3. * itself across configuration changes.
  4. */
  5. public class TaskFragment extends Fragment {
  6. /**
  7. * Callback interface through which the fragment will report the
  8. * task's progress and results back to the Activity.
  9. */
  10. interface TaskCallbacks {
  11. void onPreExecute();
  12. void onProgressUpdate(int percent);
  13. void onCancelled();
  14. void onPostExecute();
  15. }
  16. private TaskCallbacks mCallbacks;
  17. private DummyTask mTask;
  18. /**
  19. * Hold a reference to the parent Activity so we can report the
  20. * task's current progress and results. The Android framework
  21. * will pass us a reference to the newly created Activity after
  22. * each configuration change.
  23. */
  24. @Override
  25. public void onAttach(Activity activity) {
  26. super.onAttach(activity);
  27. mCallbacks = (TaskCallbacks) activity;
  28. }
  29. /**
  30. * This method will only be called once when the retained
  31. * Fragment is first created.
  32. */
  33. @Override
  34. public void onCreate(Bundle savedInstanceState) {
  35. super.onCreate(savedInstanceState);
  36. // Retain this fragment across configuration changes.
  37. setRetainInstance(true);
  38. // Create and execute the background task.
  39. mTask = new DummyTask();
  40. mTask.execute();
  41. }
  42. /**
  43. * Set the callback to null so we don't accidentally leak the
  44. * Activity instance.
  45. */
  46. @Override
  47. public void onDetach() {
  48. super.onDetach();
  49. mCallbacks = null;
  50. }
  51. /**
  52. * A dummy task that performs some (dumb) background work and
  53. * proxies progress updates and results back to the Activity.
  54. *
  55. * Note that we need to check if the callbacks are null in each
  56. * method in case they are invoked after the Activity's and
  57. * Fragment's onDestroy() method have been called.
  58. */
  59. private class DummyTask extends AsyncTask<Void, Integer, Void> {
  60. @Override
  61. protected void onPreExecute() {
  62. if (mCallbacks != null) {
  63. mCallbacks.onPreExecute();
  64. }
  65. }
  66. /**
  67. * Note that we do NOT call the callback object's methods
  68. * directly from the background thread, as this could result
  69. * in a race condition.
  70. */
  71. @Override
  72. protected Void doInBackground(Void... ignore) {
  73. for (int i = 0; !isCancelled() && i < 100; i++) {
  74. SystemClock.sleep(100);
  75. publishProgress(i);
  76. }
  77. return null;
  78. }
  79. @Override
  80. protected void onProgressUpdate(Integer... percent) {
  81. if (mCallbacks != null) {
  82. mCallbacks.onProgressUpdate(percent[0]);
  83. }
  84. }
  85. @Override
  86. protected void onCancelled() {
  87. if (mCallbacks != null) {
  88. mCallbacks.onCancelled();
  89. }
  90. }
  91. @Override
  92. protected void onPostExecute(Void ignore) {
  93. if (mCallbacks != null) {
  94. mCallbacks.onPostExecute();
  95. }
  96. }
  97. }
  98. }

对上面的简单Demo说明一下:
当MainActivity第一次创建时,将一并创建Fragment实例,并加入到MainActivity状态中去,Fragment创建AsyncTask,并通过CallBack回调将结果通知到MainActivity中去,而当COnfiguration改变以后,MainActivity经过正常的生命周期,而新的MainActivity实例会在OnCreate()方法中得到Fragment之前的引用,而在Fragment中,onAttach(Activity activity)方法会得到最新的Activity引用,二者之间都同步更新,有效避免了资源浪费和解决了因Configuration改变带来的问题。

这个简单的Demo是可靠的,从此不用再担心无法预料的Configuration改变带来的问题。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注