Androidデータ取得中に画面を回転させる問題
12786 ワード
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コード:
レイアウト:
ボタンをクリックしてデータの取得を開始すると、間もなくデータが更新されるのが見えます.
しかし、ボタンをクリックして画面を回転させると、presenterがバインドしている古いactivityオブジェクトなので、データは新しいactivityに更新されません.
解決方法:1.AndroidManifestでActivityを再作成しないように画面を回転する設定します.xmlでactivityコンポーネント定義でandroid:configChangesプロパティを設定する
画面を回転させるとActivityのonDestroyメソッドは呼び出されません.
データの更新に成功したことがわかります.これは比較的簡単な方法です.
2.Presenterオブジェクトを新しいActivityオブジェクトにバインドアプリケーションオブジェクトをブリッジとして使用し、新しいactivityを作成する前にpresenterオブジェクトをキャッシュし、新しいactivityのonCreateメソッドでpresenterオブジェクトを再バインドします.
アプリケーションコード:
AndroidManifestでxmlでこのアプリケーションオブジェクトを設定します.
Presenterに再バインドviewインタフェースを追加するには、次の手順に従います.
Activityに次のコードを追加します.
onCreateメソッドで追加:
これにより、データの更新に成功したことがわかります.
古いactivityオブジェクトは引用されていないので、楽しく回収できます.
しかし、ここではまだ終わっていないので、presenterオブジェクトを楽しく回収させることも考えなければなりません.
画面が何度回転してもキャッシュにはpresenterオブジェクトが1つしかなく、最後のactivityが終了するとキャッシュが空になり、presenterオブジェクトも回収されます.
まず例を見てみましょう.
文字列リストを表示する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 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(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 userList) {
// TODO Auto-generated method stub
if (userList == null) {
return;
}
Log.d(TAG,"viewUserList,mAdapter:" + mAdapter + ",mListView:" + mListView);
List users = (List)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("
");
sb.append("desc:");
sb.append(user.getDescription());
data[i] = sb.toString();
}
mAdapter.notifyDataSetChanged();
}
}
レイアウト:
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 usersCollection) {
//transform datas ...
//show view
this.userListView.viewUserList(usersCollection);
}
private void showErrorMessage(String errorMessage) {
userListView.showError(errorMessage);
}
private class GetUserListTask extends AsyncTask {
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.AndroidManifestでActivityを再作成しないように画面を回転する設定します.xmlでactivityコンポーネント定義でandroid:configChangesプロパティを設定する
画面を回転させるとActivityのonDestroyメソッドは呼び出されません.
データの更新に成功したことがわかります.これは比較的簡単な方法です.
2.Presenterオブジェクトを新しいActivityオブジェクトにバインドアプリケーションオブジェクトをブリッジとして使用し、新しいactivityを作成する前にpresenterオブジェクトをキャッシュし、新しいactivityのonCreateメソッドでpresenterオブジェクトを再バインドします.
アプリケーションコード:
public class MyApplication extends Application {
public static final String PRESENTER_CACHE_KEY = "presenter_cache_key";
private int mPresenterCacheIndex = 0;
private HashMap mPresenterCacheMap = new HashMap();
/**
* 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でこのアプリケーションオブジェクトを設定します.
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オブジェクトが1つしかなく、最後のactivityが終了するとキャッシュが空になり、presenterオブジェクトも回収されます.