歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Android 屏幕旋轉 處理 AsyncTask 和 ProgressDialog 的最佳方案

眾所周知,Activity在不明確指定屏幕方向和configChanges時,當用戶旋轉屏幕會重新啟動。當然了,應對這種情況,Android給出了幾種方案:

a、如果是少量數據,可以通過onSaveInstanceState()和onRestoreInstanceState()進行保存與恢復。

Android會在銷毀你的Activity之前調用onSaveInstanceState()方法,於是,你可以在此方法中存儲關於應用狀態的數據。然後你可以在onCreate()或onRestoreInstanceState()方法中恢復。

b、如果是大量數據,使用Fragment保持需要恢復的對象。

c、自已處理配置變化。

注:getLastNonConfigurationInstance()已經被棄用,被上述方法二替代。

2、難點

假設當前Activity在onCreate中啟動一個異步線程去夾在數據,當然為了給用戶一個很好的體驗,會有一個ProgressDialog,當數據加載完成,ProgressDialog消失,設置數據。

這裡,如果在異步數據完成加載之後,旋轉屏幕,使用上述a、b兩種方法都不會很難,無非是保存數據和恢復數據。

但是,如果正在線程加載的時候,進行旋轉,會存在以下問題:

a)此時數據沒有完成加載,onCreate重新啟動時,會再次啟動線程;而上個線程可能還在運行,並且可能會更新已經不存在的控件,造成錯誤。

b)關閉ProgressDialog的代碼在線程的onPostExecutez中,但是上個線程如果已經殺死,無法關閉之前ProgressDialog。

c)谷歌的官方不建議使用ProgressDialog,這裡我們會使用官方推薦的DialogFragment來創建我的加載框,如果你不了解:請看 Android 官方推薦 : DialogFragment 創建對話框(http://www.linuxidc.com/Linux/2015-08/121313.htm)。這樣,其實給我們帶來一個很大的問題,DialogFragment說白了是Fragment,和當前的Activity的生命周期會發生綁定,我們旋轉屏幕會造成Activity的銷毀,當然也會對DialogFragment造成影響。

下面我將使用幾個例子,分別使用上面的3種方式,和如何最好的解決上述的問題。

本文相關源碼下載地址

------------------------------------------分割線------------------------------------------

免費下載地址在 http://linux.linuxidc.com/

用戶名與密碼都是www.linuxidc.com

具體下載目錄在 /2015年資料/8月/8日/Android 屏幕旋轉 處理 AsyncTask 和 ProgressDialog 的最佳方案/

下載方法見 http://www.linuxidc.com/Linux/2013-07/87684.htm

------------------------------------------分割線------------------------------------------

3、使用onSaveInstanceState()和onRestoreInstanceState()進行數據保存與恢復

代碼:

package com.example.zhy_handle_runtime_change;

import java.util.ArrayList;
import java.util.Arrays;

import android.app.DialogFragment;
import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
/**
 * 不考慮加載時,進行旋轉的情況,有意的避開這種情況,後面例子會介紹解決方案
 * @author zhy
 *
 */
public class SavedInstanceStateUsingActivity 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();
   
  } else
  {
   initAdapter();
  }

 }

 /**
  * 初始化適配器
  */
 private void initAdapter()
 {
  mAdapter = new ArrayAdapter<String>(
    SavedInstanceStateUsingActivity.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(2000);
  } catch (InterruptedException e)
  {
  }
  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中對數據進行恢復,免去了不必要的再加載一遍。

運行結果:

當正常加載數據完成之後,用戶不斷進行旋轉屏幕,log會不斷打出:onSaveInstanceState->onDestroy->onCreate->onRestoreInstanceState,驗證我們的確是重新啟動了,但是我們沒有再次去進行數據加載。

如果在加載的時候,進行旋轉,則會發生錯誤,異常退出(退出原因:dialog.dismiss()時發生NullPointException,因為與當前對話框綁定的FragmentManager為null,又有興趣的可以去Debug,這個不是關鍵)。

效果圖:

4、使用Fragment來保存對象,用於恢復數據

如果重新啟動你的Activity需要恢復大量的數據,重新建立網絡連接,或者執行其他的密集型操作,這樣因為配置發生變化而完全重新啟動可能會是一個慢的用戶體驗。並且,使用系統提供的onSaveIntanceState()的回調中,使用Bundle來完全恢復你Activity的狀態是可能是不現實的(Bundle不是設計用來攜帶大量數據的(例如bitmap),並且Bundle中的數據必須能夠被序列化和反序列化),這樣會消耗大量的內存和導致配置變化緩慢。在這樣的情況下,當你的Activity因為配置發生改變而重啟,你可以通過保持一個Fragment來緩解重新啟動帶來的負擔。這個Fragment可以包含你想要保持的有狀態的對象的引用。

當Android系統因為配置變化關閉你的Activity的時候,你的Activity中被標識保持的fragments不會被銷毀。你可以在你的Activity中添加這樣的fragements來保存有狀態的對象。

在運行時配置發生變化時,在Fragment中保存有狀態的對象

a) 繼承Fragment,聲明引用指向你的有狀態的對象

b) 當Fragment創建時調用setRetainInstance(boolean)

c) 把Fragment實例添加到Activity中

d) 當Activity重新啟動後,使用FragmentManager對Fragment進行恢復

代碼:

首先是Fragment:

package com.example.zhy_handle_runtime_change;

import android.app.Fragment;
import android.graphics.Bitmap;
import android.os.Bundle;

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;
 }
}

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-08/121312p2.htm

Copyright © Linux教程網 All Rights Reserved