Android 旋转屏幕--处理Activity与AsyncTask的最佳解决方案,androidasynctask
一、概述
运行时变更就是设备在运行时发生变化(例如屏幕旋转、键盘可用性及语言)。发生这些变化,Android会重启Activity,这时就需要保存activity的状态及与activity相关的任务,以便恢复activity的状态。
为此,google提供了三种解决方案:
下面会逐一介绍三种情况,其实保存一些变量对象很简单,难的是当Activity创建异步线程去加载数据时,旋转屏幕时,怎么保存线程的状态。比如,在线程的加载过程中,旋转屏幕,就会存在问题:此时数据没有完成加载,onCreate重新启动时,会再次启动线程;而上个线程可能还在运行,并且可能会更新已经不存在的控件,造成错误。下面会一一解决这些问题。本文较长,主要是代码多,可以先下载demo,源码下载:http://download.csdn.net/detail/jycboy/9720486对比着看。
二、使用onSaveInstanceState,onRestoreInstanceState?
代码如下:
/** * 使用onSaveInstanceState,onRestoreInstanceState; * 在这里不考虑没有加载完毕,就旋转屏幕的情况。 * @author 超超boy * */ public class SavedInstanceStateActivity extends ListActivity { private static final String TAG = "MainActivity"; private ListAdapter mAdapter; private ArrayList<String> mDatas; private DialogFragment mLoadingDialog; private LoadDataAsyncTask mLoadDataAsyncTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG, "onCreate"); initData(savedInstanceState); } /** * 初始化数据 */ private void initData(Bundle savedInstanceState) { if (savedInstanceState != null) mDatas = savedInstanceState.getStringArrayList("mDatas"); if (mDatas == null) { mLoadingDialog = new LoadingDialog(); mLoadingDialog.show(getFragmentManager(), "LoadingDialog"); mLoadDataAsyncTask = new LoadDataAsyncTask(); mLoadDataAsyncTask.execute(); //mLoadDataAsyncTas } else { initAdapter(); } } /** * 初始化适配器 */ private void initAdapter() { mAdapter = new ArrayAdapter<String>( SavedInstanceStateActivity.this, android.R.layout.simple_list_item_1, mDatas); setListAdapter(mAdapter); } @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); Log.e(TAG, "onRestoreInstanceState"); } @Override //在这里保存数据,好用于返回 protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.e(TAG, "onSaveInstanceState"); outState.putSerializable("mDatas", mDatas); } /** * 模拟耗时操作 * * @return */ private ArrayList<String> generateTimeConsumingDatas() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据", "onSaveInstanceState保存数据", "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop", "Spark")); } private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { mDatas = generateTimeConsumingDatas(); return null; } @Override protected void onPostExecute(Void result) { mLoadingDialog.dismiss(); initAdapter(); } } @Override protected void onDestroy() { Log.e(TAG, "onDestroy"); super.onDestroy(); } }
界面为一个ListView,onCreate中启动一个异步任务去加载数据,这里使用Thread.sleep模拟了一个耗时操作;当用户旋转屏幕发生重新启动时,会onSaveInstanceState中进行数据的存储,在onCreate中对数据进行恢复,免去了不必要的再加载一遍。
运行结果:
12-24 20:13:41.814 1994-1994/? E/MainActivity: onCreate
12-24 20:13:46.124 1994-1994/? E/MainActivity: onSaveInstanceState
12-24 20:13:46.124 1994-1994/? E/MainActivity: onDestroy
12-24 20:13:46.154 1994-1994/? E/MainActivity: onCreate
12-24 20:13:46.164 1994-1994/? E/MainActivity: onRestoreInstanceState
当正常加载数据完成之后,用户不断进行旋转屏幕,log会不断打出:onSaveInstanceState->onDestroy->onCreate->onRestoreInstanceState,验证Activity重新启动,但是我们没有再次去进行数据加载。
如果在加载的时候,进行旋转,则会发生错误,异常退出(退出原因:dialog.dismiss()时发生NullPointException,因为与当前对话框绑定的FragmentManager为null,在这里这个不是关键)。
效果图:
三、使用Fragment保留对象,恢复数据
果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,依靠系统通过onSaveInstanceState()
回调为您保存的 Bundle
,可能无法完全恢复 Activity 状态,因为它并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化,这可能会消耗大量内存并使得配置变更速度缓慢。 在这种情况下,如果 Activity 因配置变更而重启,则可通过保留 Fragment
来减轻重新初始化 Activity 的负担。此片段可能包含对您要保留的有状态对象的引用。
当 Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。 您可以将此类片段添加到 Activity 以保留有状态的对象。
要在运行时配置变更期间将有状态的对象保留在片段中,请执行以下操作:
例如,按如下方式定义片段:
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; } }
注意:尽管您可以存储任何对象,但是切勿传递与 Activity
绑定的对象,例如,Drawable
、Adapter
、View
或其他任何与 Context
关联的对象。否则,它将使Activity无法被回收造成内存泄漏。(泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存)
下面举一个实际的例子:
1.RetainedFragment
public class RetainedFragment extends Fragment { // data object we want to retain private Bitmap 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(Bitmap data) { this.data = data; } public Bitmap getData() { return data; } }
只是保持Bitmap对象的引用,你可以用Fragment保存多个对象。
2.FragmentRetainDataActivity:
public class FragmentRetainDataActivity extends Activity { private static final String TAG = "FragmentRetainData"; private RetainedFragment dataFragment; private DialogFragment mLoadingDialog; private ImageView mImageView; private Bitmap mBitmap; BitmapWorkerTask bitmapWorkerTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.e(TAG, "onCreate"); // find the retained fragment on activity restarts FragmentManager fm = getFragmentManager(); dataFragment = (RetainedFragment) fm.findFragmentByTag("data"); // create the fragment and data the first time if (dataFragment == null) { // add the fragment dataFragment = new RetainedFragment(); fm.beginTransaction().add(dataFragment, "data").commit(); } // the data is available in dataFragment.getData() m