@linux1s1s
2017-01-22T08:04:38.000000Z
字数 5117
阅读 2713
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实例可以节约必要的资源。
通过修改Android manifest的configChanges 属性可以禁止销毁和重建Activity当Configuration改变的时候,这个是解决这类问题最普遍的做法。这个方法看似相当简单,所以大部分Android开发人员特别热衷这么做。然而Google的工程师们,并不推荐这么做。
主要有以下原因:
基于以上原因,在Android manifest设置configChanges 属性并非明智之举。
在Activity实例之间传递对象比较推荐的方法是通过重写onRetainNonConfigurationInstance() 和 getLastNonConfigurationInstance() 方法,然而这在API13以后废弃了,因为有更好的方法可以替代上面的重写方法。那么该如何处理上面的问题呢?
自从Android 3.0引入Fragment以后,在Activity实例之间传递对象的推荐方法变成了保持Activity内部的Fragment实例,因为默认情况下,Fragment的生命周期是和它依附的Activity生命周期同步的。通过调用** Fragment#setRetainInstance(true)** 可以帮助我们绕过销毁-重建实例的周期,直接得到例如AnsyncTask & Socket & Thread等的引用。
下面是个简单的例子:
MainActivity.java
/*** 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;@Overrideprotected 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.@Overridepublic void onPreExecute() { ... }@Overridepublic void onProgressUpdate(int percent) { ... }@Overridepublic void onCancelled() { ... }@Overridepublic void onPostExecute() { ... }}
TaskFragment.java
/*** 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.*/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.*/@Overridepublic void onAttach(Activity activity) {super.onAttach(activity);mCallbacks = (TaskCallbacks) activity;}/*** This method will only be called once when the retained* Fragment is first created.*/@Overridepublic 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.*/@Overridepublic 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> {@Overrideprotected 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.*/@Overrideprotected Void doInBackground(Void... ignore) {for (int i = 0; !isCancelled() && i < 100; i++) {SystemClock.sleep(100);publishProgress(i);}return null;}@Overrideprotected void onProgressUpdate(Integer... percent) {if (mCallbacks != null) {mCallbacks.onProgressUpdate(percent[0]);}}@Overrideprotected void onCancelled() {if (mCallbacks != null) {mCallbacks.onCancelled();}}@Overrideprotected void onPostExecute(Void ignore) {if (mCallbacks != null) {mCallbacks.onPostExecute();}}}}
对上面的简单Demo说明一下:
当MainActivity第一次创建时,将一并创建Fragment实例,并加入到MainActivity状态中去,Fragment创建AsyncTask,并通过CallBack回调将结果通知到MainActivity中去,而当COnfiguration改变以后,MainActivity经过正常的生命周期,而新的MainActivity实例会在OnCreate()方法中得到Fragment之前的引用,而在Fragment中,onAttach(Activity activity)方法会得到最新的Activity引用,二者之间都同步更新,有效避免了资源浪费和解决了因Configuration改变带来的问题。
这个简单的Demo是可靠的,从此不用再担心无法预料的Configuration改变带来的问题。
