Android获取数据过程中旋转屏幕问题

Android开发默认情况下旋转屏幕会重新创建Activity对象,这个过程中会先调用旧的Activity对象的onSaveInstanceState和onDestroy方法,然后调用新Activity的onCreate和onRestoreInstanceState方法。如果启动AsyncTask后台获取数据时旋转屏幕,由于没有绑定新的Activity对象,获取到的数据不会显示出来,另外,由于旧的Activity对象依然被引用,不会被垃圾回收,存在内存泄露。


先看一个例子:
创建一个Activity,用于显示一个字符串列表
创建一个Presenter,用于处理数据
PS:MVP结构,Activity做为view专门负责显示UI逻辑,Model则负责处理业务和数据,Presenter作为view和model的桥梁
这里只用到view和presenter,模型部分忽略。

Activity代码:

public class SampleActivity extends BaseActivity implements UserListView,OnItemClickListener {
	private static final String TAG = SampleActivity.class.getName();
	
	private UserListPresenter mPresenter;
	private ArrayAdapter<String> mAdapter;
	
	private String[] data = new String[3];
	
	@Bind(R.id.main_list_view) ListView mListView;
	@Bind(R.id.btn_get_data_by_asynctask) Button mBtnAsyncTask;
	@Bind(R.id.view_loading) TextView mTextViewLoading;
	@Bind(R.id.view_loading_progress) TextView mTextViewLoadingProgress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_sample);
		
		Log.d(TAG,"onCreate,savedInstanceState:" + savedInstanceState);
		
		mPresenter = new UserListPresenter(this);
		
		ButterKnife.bind(this);
		
		data[0] = "first line";
		data[1] = "second line";
		data[2] = "third line";
		mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data);
		mListView.setAdapter(mAdapter);
		mListView.setOnItemClickListener(this);
	}
	
	@OnClick(R.id.btn_get_data_by_asynctask) void getDataByAsyncTask(){
		Log.d(TAG,"getData");
		mPresenter.getUserListByAsyncTask();
	}
	
	@Override
	public void onItemClick(AdapterView<?> arg0, View v, int position, long id) {
		// TODO Auto-generated method stub
    	Log.d(TAG,"onItemClick,position:" + position);
		
	}
	
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onRestoreInstanceState(savedInstanceState);
		Log.d(TAG,"onRestoreInstanceState");
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		// TODO Auto-generated method stub
		super.onSaveInstanceState(outState);
		Log.d(TAG,"onSaveInstanceState");
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(TAG,"onDestroy");
		mPresenter.destroy();
		mPresenter = null;
	}
	
	@Override
	public void showLoading() {
		// TODO Auto-generated method stub
		mTextViewLoading.setVisibility(View.VISIBLE);
	}

	@Override
	public void hideLoading() {
		// TODO Auto-generated method stub
		mTextViewLoading.setVisibility(View.GONE);
	}
	
	@Override
	public void showLoadingProgress(Integer... values) {
		// TODO Auto-generated method stub
		mTextViewLoadingProgress.setVisibility(View.VISIBLE);
		mTextViewLoadingProgress.setText("loading progress:" + values[0]);
	}

	@Override
	public void hideLoadingProgress() {
		// TODO Auto-generated method stub
		mTextViewLoadingProgress.setVisibility(View.GONE);
	}

	@Override
	public void showRetry() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void hideRetry() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void showError(String message) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Context getContext() {
		// TODO Auto-generated method stub
		return this;
	}

	@Override
	public void viewUserList(Collection<UserEntity> userList) {
		// TODO Auto-generated method stub
		if (userList == null) {
			return;
		}
		
		Log.d(TAG,"viewUserList,mAdapter:" + mAdapter + ",mListView:" + mListView);
		List<UserEntity> users = (List<UserEntity>)userList;
		int len = userList.size();
		StringBuffer sb = new StringBuffer();
		for (int i = 1; i < len+1; i++) {
			sb.setLength(0);
			UserEntity user = users.get(i-1);
			sb.append("name:");
			sb.append(user.getFullName());
			sb.append("\n");
			sb.append("desc:");
			sb.append(user.getDescription());
			data[i] = sb.toString();
		}
		
		mAdapter.notifyDataSetChanged();
	}
}

布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.mytestapp.view.SampleActivity" >

    <Button android:id="@+id/btn_get_data_by_asynctask"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="get data by AsyncTask" />
    
    <TextView android:id="@+id/view_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_get_data_by_asynctask"
        android:text="loading..."
        android:visibility="gone" />
    
    <TextView android:id="@+id/view_loading_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_get_data_by_asynctask"
        android:layout_toRightOf="@id/view_loading"
        android:text="loading progress:"
        android:visibility="gone" />
    
    <TextView android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/view_loading_progress"
        android:text="user list:" />
    
    <ListView android:id="@+id/main_list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/text_view"
        android:layout_centerHorizontal="true" />

</RelativeLayout>


Presenter代码:

public class UserListPresenter extends BasePresenter implements Presenter {
	private static final String TAG = "UserListPresenter";
	
	private UserListView userListView;
	
	public UserListPresenter(UserListView userListView){
		this.userListView = userListView;
	}
	
	public void getUserListByAsyncTask(){
		Log.d(TAG,"getUserListByAsyncTask");
		new GetUserListTask(userListView.getContext()).execute();
	}

	@Override
	public void resume() {
		// TODO Auto-generated method stub

	}

	@Override
	public void pause() {
		// TODO Auto-generated method stub

	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
	}
	
	private void showViewLoading() {
	    userListView.showLoading();
	}
	
	private void hideViewLoading() {
	    userListView.hideLoading();
	}
	
	private void showViewLoadingProgress(Integer...values) {
	    userListView.showLoadingProgress(values[0]);
	}
	
	private void hideViewLoadingProgress() {
	    userListView.hideLoadingProgress();
	}
	
	private void showViewRetry() {
		userListView.showRetry();
	}
	
	private void hideViewRetry() {
		userListView.hideRetry();
	}
	
	private void showUsersCollectionInView(Collection<UserEntity> usersCollection) {
	    
		//transform datas ...
		
		//show view
	    this.userListView.viewUserList(usersCollection);
	}
	private void showErrorMessage(String errorMessage) {
	    userListView.showError(errorMessage);
	}
	
	private class GetUserListTask extends AsyncTask<Void, Integer, UserListEntity> {
		
		private Context mContext;
		
		public GetUserListTask(Context context){
			mContext = context;
		}

		@Override
		protected UserListEntity doInBackground(Void... arg0) {
			// TODO Auto-generated method stub
			Log.d(TAG,"GetUserListTask doInBackground");
			int level = 0;
			while(level < 100){  //模拟进度条
				publishProgress(level++);
				
				try {
					Thread.sleep(20);
				} catch (Exception e) {
					// TODO: handle exception
				}
			}
			return new DataManager(mContext).getUsers(null);
		}

		@Override
		protected void onPostExecute(UserListEntity users) {
			// TODO Auto-generated method stub
			super.onPostExecute(users);
			Log.d(TAG,"GetUserListTask onPostExecute,userListView:" + userListView);
			hideViewRetry();
			hideViewLoading();
			userListView.viewUserList(users.getUserList());
		}

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			super.onPreExecute();
			Log.d(TAG,"GetUserListTask onPreExecute,userListView:" + userListView);
			hideViewRetry();
			showViewLoading();
			showViewLoadingProgress(0);
		}

		@Override
		protected void onCancelled() {
			// TODO Auto-generated method stub
			super.onCancelled();
			hideViewRetry();
			hideViewLoading();
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			// TODO Auto-generated method stub
			super.onProgressUpdate(values);
			showViewLoadingProgress(values[0]);
			Log.d(TAG,"GetUserListTask onProgressUpdate,value:" + values[0]);
		}
	}
}

点击按钮开始获取数据,可以看到不久后数据更新了。
但是如果在点击按钮后旋转屏幕,则看不到数据更新,因为presenter绑定的是旧的activity对象,数据并没有更新到新的activity上。


解决办法:
1.设置旋转屏幕时不重新创建Activity
在AndroidManifest.xml中activity组件定义中设置android:configChanges属性

<activity android:name=".view.activity.SampleActivity" android:label="SampleActivity"
            android:configChanges="orientation|screenSize|keyboardHidden" />

旋转屏幕后不会调用Activity的onDestroy方法。
可以看到数据更新成功,这是比较简单的方法。


2.将Presenter对象绑定到新的Activity对象
使用Application对象作为桥梁,在创建新activity前将presenter对象缓存,在新activity的onCreate方法中将presenter对象重新绑定。


Application代码:

public class MyApplication extends Application {
	
	public static final String PRESENTER_CACHE_KEY = "presenter_cache_key";
	private int mPresenterCacheIndex = 0;

	private HashMap<String,Presenter> mPresenterCacheMap = new HashMap<String,Presenter>();
	
	/**
	 * add presenter to cache map
	 * @param presenter
	 * @return cache key of the presenter
	 */
	public synchronized String addPresenterToCache(Presenter presenter){
		String key = Integer.toString(mPresenterCacheIndex);
		mPresenterCacheMap.put(key, presenter);
		mPresenterCacheIndex++;
		return key;
	}
	
	/**
	 * get presenter from cache according to given key
	 * @param key
	 * @return
	 */
	public synchronized Presenter getPresenterFromCache(String key){
		return mPresenterCacheMap.get(key);
	}
	
	public synchronized void removePresenterFromCache(String key){
		mPresenterCacheMap.remove(key);
	}
	
	public synchronized int getPresenterCacheSize(){
		return mPresenterCacheMap.size();
	}
}


记得在AndroidManifest.xml中设置这个application对象:

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name=".app.MyApplication" >

Presenter中添加重新绑定view接口:

public void resetView(UserListView userListView){
	this.userListView = userListView;
}

Activity中添加如下代码:

@Override
protected void onSaveInstanceState(Bundle outState) {
	// TODO Auto-generated method stub
	super.onSaveInstanceState(outState);
	Log.d(TAG,"onSaveInstanceState");
	
	//缓存presenter对象
	String key = ((MyApplication)getApplication()).addPresenterToCache(mPresenter);
	outState.putString(MyApplication.PRESENTER_CACHE_KEY, key);
}

在onCreate方法添加:

if (savedInstanceState != null) { //获取缓存的presenter对象,绑定到当前activity上
	mPresenterCacheKey = savedInstanceState.getString(MyApplication.PRESENTER_CACHE_KEY);
	mPresenter = (UserListPresenter)((MyApplication)getApplication()).getPresenterFromCache(mPresenterCacheKey);
	mPresenter.resetView(this);
}else {
	mPresenter = new UserListPresenter(this);
}

这样就可以看到数据更新成功了。
由于旧的activity对象没有被引用,可以愉快地被回收。
不过这里还没结束,还要考虑让presenter对象愉快地被回收。

@Override
protected void onDestroy() {
	// TODO Auto-generated method stub
	super.onDestroy();
	Log.d(TAG,"onDestroy");
	mPresenter.destroy();
	
	if (mPresenterCacheKey != null) {
		((MyApplication)getApplication()).removePresenterFromCache(mPresenterCacheKey);
	}
	
	mPresenter = null;
	
	//查看缓存对象数量
	int size = ((MyApplication)getApplication()).getPresenterCacheSize();
	Log.d(TAG,"onDestroy,presenter cache size:" + size);
}

不管屏幕旋转多少次,缓存里面只有一个presenter对象,当最后一次activity退出时,缓存被清空,presenter对象也就能被回收。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值