Android 代码优化
有这样一个话题在 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的工程师们,并不推荐这么做。
主要有以下原因:
- Configuration改变以后,UI需要和Device的Configuration同步,如果你一个不小心,那么UI就会出现问题。因为此时Device的Configuration改变了,而你的UI却尚未同步改变。
- 一些Android开发人员错误认为设置android:configChanges=”orientation” 会确保禁用销毁和重建Activity,而实际上并非如此,因为到时Configuration改变可能有许多原因,不仅仅局限在旋转屏幕,还有可能是当你改变程序的默认语言,或者将设备和其他显示设备相连的时候,这些都可能引起Configuration的改变。
基于以上原因,在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
/**
* 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.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.
*/
@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();
}
}
}
}
对上面的简单Demo说明一下:
当MainActivity第一次创建时,将一并创建Fragment实例,并加入到MainActivity状态中去,Fragment创建AsyncTask,并通过CallBack回调将结果通知到MainActivity中去,而当COnfiguration改变以后,MainActivity经过正常的生命周期,而新的MainActivity实例会在OnCreate()方法中得到Fragment之前的引用,而在Fragment中,onAttach(Activity activity)方法会得到最新的Activity引用,二者之间都同步更新,有效避免了资源浪费和解决了因Configuration改变带来的问题。
这个简单的Demo是可靠的,从此不用再担心无法预料的Configuration改变带来的问题。