【声明:】本文是作者(蘑菇v5)原创,版权归作者 蘑菇v5所有,侵权必究。本文首发在简书。如若转发,请注明作者和来源地址!未经授权,严禁私自转载!
解决方案:
线程在异步加载数据时,屏幕旋转不会中断任务线程,等待加载框在加载完成之前一直正常显示,并且使用Fragment
进行数据保存,因为这是官方推荐的,适用于比较大的数据的存储与恢复(如 bitmap
)。
RetainedFragment:
/**
* 保存对象的Fragment
*
* @author zsnlh
*
*/
public class RetainedFragment extends Fragment {
// data object we want to retain
// 保存一个异步的任务
private MyAsyncTask 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(MyAsyncTask data) {
this.data = data;
}
public MyAsyncTask getData() {
return data;
}
}
关键思想:保存一个异步任务,在重启时,继续这个任务。
MyAsyncTask:
/**
* MyAsyncTask
* @author zsnlh
*/
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
//保存外部activity的弱引用
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
private FixProblemsActivity activity;
/**
* 是否完成
*/
private boolean isCompleted;
/**
* 进度框
*/
private LoadingDialog mLoadingDialog;
private List<String> items;
public MyAsyncTask(FixProblemsActivity activity) {
this.activity = activity;
}
/**
* 开始时,显示加载框
*/
@Override
protected void onPreExecute() {
mLoadingDialog = new LoadingDialog();
activity = (FixProblemsActivity) weakReference.get();
if(activity != null){
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}
}
/**
* 加载数据
*/
@Override
protected Void doInBackground(Void... params) {
items = loadingData();
return null;
}
/**
* 加载完成回调当前的Activity
*/
@Override
protected void onPostExecute(Void unused) {
isCompleted = true;
notifyActivityTaskCompleted();
if (mLoadingDialog != null)
mLoadingDialog.dismiss();
}
public List<String> getItems() {
return items;
}
private List<String> loadingData() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
"onSaveInstanceState保存数据",
"getLastNonConfigurationInstance已经被弃用", "穿越火线", "英雄联盟",
"王者荣耀"));
}
/**
* 设置Activity,因为Activity会一直变化
*
* @param activity
*/
public void setActivity(FixProblemsActivity activity) {
weakReference = new WeakReference<>(activity);
// 设置为当前的Activity
this.activity = (FixProblemsActivity) activity;
// 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁
if (activity == null) {
dialogDismiss();
}
// 开启一个与当前Activity绑定的等待框
if (activity != null && !isCompleted) {
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}
// 如果完成,通知Activity
if (isCompleted) {
notifyActivityTaskCompleted();
}
}
private void notifyActivityTaskCompleted() {
if (null != activity) {
activity.onTaskCompleted();
}
}
/**
* 在Activity不可见时,关闭dialog
*/
public void dialogDismiss(){
if(mLoadingDialog != null){
mLoadingDialog.dismiss();
}
}
}
和之前一样保留Activity
的虚引用,防止内存泄漏。
setActivity()
方法用于任务未完成时,重启activity
相应的创建一个新的dialog
。
dialogDismiss
用于在在Activity
不可见时,关闭dialog
,防止以前的dialog
造成内存泄漏。
当任务完成时,调用activity
的回调方法onTaskCompleted
更新UI
。
异步任务中,管理一个对话框,当开始下载前,进度框显示,下载结束进度框消失,并为Activity
提供回调。当然了,运行过程中Activity
不断的重启,我们也提供了setActivity
方法,onDestory
时,会setActivity(null)
防止内存泄漏,同时我们也会关闭与其绑定的加载框;当onCreate
传入新的Activity
时,我们会在再次打开一个加载框,当然了因为屏幕的旋转并不影响加载的数据,所有后台的数据一直继续在加载。
主
Activity
:
public class FixProblemsActivity extends ListActivity {
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private List<String> mDatas;
private RetainedFragment dataFragment;
private MyAsyncTask mMyTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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 OtherRetainedFragment();
fm.beginTransaction().add(dataFragment, "data").commit();
}
mMyTask = dataFragment.getData();
if (mMyTask != null) {
//与新的Activity进行绑定
mMyTask.setActivity(this);
} else {
//启动一个新的任务
mMyTask = new MyAsyncTask(this);
dataFragment.setData(mMyTask);
mMyTask.execute();
}
// the data is available in dataFragment.getData()
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
Log.e(TAG, "onRestoreInstanceState");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mMyTask.setActivity(null);
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState");
}
@Override
protected void onDestroy() {
Log.e(TAG, "onDestroy");
super.onDestroy();
}
@Override
//在这里关闭Dialog,否则容易造成内存泄漏
protected void onPause() {
super.onPause();
mMyTask.dialogDismiss();
}
/**
* 回调方法,更新UI
* 这里如果在加载的过程中按下返回键返回主Activity时,会出现异常,
*setAdapter on a null object reference。因为activity被销毁,
* 要解决这个问题,可以监听返回键事件做相应处理。
*/
public void onTaskCompleted() {
mDatas = mMyTask.getItems();
mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this, android.R.layout.simple_list_item_1, mDatas);
setListAdapter(mAdapter);
}
}
在onCreate
中,如果没有开启任务(第一次进入),开启任务;如果已经开启了,调用setActivity(this)
;在onSaveInstanceState
把当前任务加入Fragment
,我设置了等待5
秒,足够旋转三四个来回了,可以看到虽然在不断的重启,但是丝毫不影响加载数据任务的运行和加载框的显示
效果图:
可以看到我在加载的时候就丧心病狂的旋转屏幕,但是丝毫不影响显示效果与任务的加载。
最后,说明一下,其实不仅是屏幕旋转需要保存数据,当用户在使用你的app
时,忽然接到一个来电,长时间没有回到你的app
界面也会造成Activity
的销毁与重建,所以一个行为良好的App
,是有必要拥有恢复数据的能力的。