[翻譯]使用Fragment處理配置更改(Handling Configuration Changes With Fragments)

原文地址

StackOverflow上這類問(wèn)題很常見(jiàn)

What is the best way to retain active objects—such as runningThreads栏尚、Sockets鳄厌、 andAsyncTasks—across device configuration changes?

回答問(wèn)題之前硕糊,我們先討論開(kāi)發(fā)者通常在處理與Activity生命周期相關(guān)的耗時(shí)任務(wù)會(huì)遇到的困難,接著,我們會(huì)討論兩種常見(jiàn)解決方法的缺陷仲吏,最后占调,我們會(huì)使用持久化Framgnet作為實(shí)例代碼,給出值得推薦解決方案法梯。

屏幕旋轉(zhuǎn) & 后臺(tái)任務(wù)

屏幕旋轉(zhuǎn)時(shí)苔货,Activity必須經(jīng)歷生命周期的重構(gòu),而事件的發(fā)生卻是不可預(yù)測(cè)的立哑。后臺(tái)并發(fā)任務(wù)的處理無(wú)異加劇了這個(gè)難題夜惭。

比如,Activity啟動(dòng)了AsyncTask之后铛绰,用戶旋轉(zhuǎn)手機(jī)屏幕诈茧,導(dǎo)致Activity被銷毀和重構(gòu)。AsyncTask完成任務(wù)后捂掰,并不知道存在新Activity敢会,錯(cuò)誤地把結(jié)果轉(zhuǎn)交給舊Activity。另一方面这嚣,新Activity并不知道AsyncTask的存在和處理結(jié)果鸥昏,會(huì)重新啟動(dòng)AsyncTask,導(dǎo)致資源浪費(fèi)姐帚。因此吏垮,在屏幕旋轉(zhuǎn)的過(guò)程中,正確有效地保存Activity信息就顯得尤為重要罐旗。

壞方法:固定Activity的方向

世界上最取巧膳汪,最被濫用的方法就是通過(guò)固定Activity方向,阻止Activity的重構(gòu)九秀。
在AndroidManifest.xml文件中設(shè)置android:configChanges
這個(gè)簡(jiǎn)單的方法非常吸引開(kāi)發(fā)者遗嗽。谷歌工程師并不推薦這種做法。

首當(dāng)其沖需要使用代碼處理屏幕旋轉(zhuǎn)鼓蜒,意味著花更多的精力確保每個(gè)字符串(string),布局(layout),尺寸(dimen)等與當(dāng)前屏幕方向保持同步痹换,處理不當(dāng)很容易會(huì)造成一系列的資源特定bug征字。

谷歌另一個(gè)不鼓勵(lì)使用該方法的原因,很多開(kāi)發(fā)者錯(cuò)誤地設(shè)置android:configChanges="orientation"(舉例)會(huì)意外地阻止底層Activity摧毀和重構(gòu)晴音。不單止屏幕旋轉(zhuǎn)柔纵,還有各種各樣的原因會(huì)導(dǎo)致配置改變,把設(shè)備接到顯示器上锤躁、改變默認(rèn)語(yǔ)言搁料、改變默認(rèn)字體大小只是三個(gè)會(huì)改變配置的觸發(fā)事件。所以系羞,設(shè)置android:configChanges并不是一個(gè)好方法郭计。

過(guò)時(shí),重寫onRetainNonConfigurationInstance()

在Android Honeycomb(Android 3.1系統(tǒng)椒振,譯者注)版本之前昭伸,推薦重寫onRetainNonConfigurationInstance()getLastNonConfigurationInstance()在多個(gè)Activity實(shí)例間轉(zhuǎn)移對(duì)象。onRetainNonConfigurationInstance()用于傳遞對(duì)象而getLastNonConfigurationInstance()用于獲取對(duì)象澎迎。在API 13(Android 3.2系統(tǒng)庐杨,譯者注)這些方法過(guò)時(shí),支持使用更方便的模塊化方法Fragment中setRetainInstance(boolean)來(lái)保存對(duì)象夹供。下一章節(jié)我們會(huì)討論這種方法灵份。

推薦:在持久化Fragment中管理對(duì)象

從Android 3.0開(kāi)始引入Fragment的概念,在Activity中持久化對(duì)象的方法哮洽,是通過(guò)持久化Fragment包裝和管理這些對(duì)象填渠。默認(rèn)情況下,在配置發(fā)生改變時(shí)Fragment的重構(gòu)是跟隨父Activity的鸟辅。通過(guò)調(diào)用Fragment#setRetainInstance(true),跳過(guò)銷毀重構(gòu)的過(guò)程氛什,告訴系統(tǒng)在Acitivity重構(gòu)時(shí)保持當(dāng)前Fragment實(shí)例的狀態(tài)。這在我們運(yùn)行Thread,AsyncTask,Socket匪凉,使用持久化Fragment就變得相當(dāng)有利枪眉。

下面的樣例代碼示范,在配置改變的情況下再层,怎么去使用持久化Fragment來(lái)保存AsyncTask贸铜。代碼保證了進(jìn)度更新和正確傳遞結(jié)果到Activity,在配置改變時(shí)不會(huì)泄露AsyncTask的引用。
代碼包括兩個(gè)類树绩,第一個(gè)是MainActivity

 * This Activity displays the screen's UI, creates a TaskFragment
 * to manage the task, and receives progress updates and results 
 * from the TaskFragment when they occur.
 */
public class MainActivity extends Activity implements TaskFragment.TaskCallbacks {

  private static final String TAG_TASK_FRAGMENT = "task_fragment";

  private TaskFragment mTaskFragment;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    FragmentManager fm = getFragmentManager();
    mTaskFragment = (TaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT);

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (mTaskFragment == null) {
      mTaskFragment = new TaskFragment();
      fm.beginTransaction().add(mTaskFragment, TAG_TASK_FRAGMENT).commit();
    }

    // TODO: initialize views, restore saved state, etc.
  }

  // The four methods below are called by the TaskFragment when new
  // progress updates or results are available. The MainActivity 
  // should respond by updating its UI to indicate the change.

  @Override
  public void onPreExecute() { ... }

  @Override
  public void onProgressUpdate(int percent) { ... }

  @Override
  public void onCancelled() { ... }

  @Override
  public void onPostExecute() { ... }
}

還有TaskFragment

 * This Fragment manages a single background task and retains 
 * itself across configuration changes.
 */
public class TaskFragment extends Fragment {

  /**
   * Callback interface through which the fragment will report the
   * task's progress and results back to the Activity.
   */
  interface TaskCallbacks {
    void onPreExecute();
    void onProgressUpdate(int percent);
    void onCancelled();
    void onPostExecute();
  }

  private TaskCallbacks mCallbacks;
  private DummyTask mTask;

  /**
   * Hold a reference to the parent Activity so we can report the
   * task's current progress and results. The Android framework 
   * will pass us a reference to the newly created Activity after 
   * each configuration change.
   */
  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    mCallbacks = (TaskCallbacks) activity;
  }

  /**
   * This method will only be called once when the retained
   * Fragment is first created.
   */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Retain this fragment across configuration changes.
    setRetainInstance(true);

    // Create and execute the background task.
    mTask = new DummyTask();
    mTask.execute();
  }

  /**
   * Set the callback to null so we don't accidentally leak the 
   * Activity instance.
   */
  @Override
  public void onDetach() {
    super.onDetach();
    mCallbacks = null;
  }

  /**
   * A dummy task that performs some (dumb) background work and
   * proxies progress updates and results back to the Activity.
   *
   * Note that we need to check if the callbacks are null in each
   * method in case they are invoked after the Activity's and
   * Fragment's onDestroy() method have been called.
   */
  private class DummyTask extends AsyncTask<Void, Integer, Void> {

    @Override
    protected void onPreExecute() {
      if (mCallbacks != null) {
        mCallbacks.onPreExecute();
      }
    }

    /**
     * Note that we do NOT call the callback object's methods
     * directly from the background thread, as this could result 
     * in a race condition.
     */
    @Override
    protected Void doInBackground(Void... ignore) {
      for (int i = 0; !isCancelled() && i < 100; i++) {
        SystemClock.sleep(100);
        publishProgress(i);
      }
      return null;
    }

    @Override
    protected void onProgressUpdate(Integer... percent) {
      if (mCallbacks != null) {
        mCallbacks.onProgressUpdate(percent[0]);
      }
    }

    @Override
    protected void onCancelled() {
      if (mCallbacks != null) {
        mCallbacks.onCancelled();
      }
    }

    @Override
    protected void onPostExecute(Void ignore) {
      if (mCallbacks != null) {
        mCallbacks.onPostExecute();
      }
    }
  }
}

事件流

當(dāng)MainActivity第一次啟動(dòng)時(shí),實(shí)例化同時(shí)添加TaskFragment到Activity隐轩。TaskFragment創(chuàng)建并執(zhí)行AsyncTask,將更新結(jié)果傳遞回MainActivity通過(guò)TaskCallbacks接口饺饭。

當(dāng)配置發(fā)生改變時(shí),MainActivity正常走生命周期的重構(gòu)方法职车,一旦新的Activity創(chuàng)建成功后會(huì)回調(diào)Fragmentd的onAttach(Activity)方法瘫俊,即使在配置改變的情況下鹊杖,保證Fragment當(dāng)前持有的是最新的Activity的引用。

代碼運(yùn)行的結(jié)果是簡(jiǎn)單且可靠的扛芽;應(yīng)用程序框架會(huì)處理Activity重建后的實(shí)例骂蓖,TaskFragmentAsyncTask無(wú)需關(guān)注配置的改變。onPostExecute()可以在onDetach()onAttach()方法回調(diào)之間執(zhí)行川尖。
參考在StackOverFlow上的回答和在Google+回答Doug Stevenson的問(wèn)題登下。

結(jié)論

與Activity生命周期相關(guān)的同步后臺(tái)任務(wù)的處理是很有技巧的,配置改變也容易令人迷惑叮喳。幸運(yùn)的是被芳,通過(guò)長(zhǎng)期持有父Activity的引用,即使在被重構(gòu)的情況下馍悟,持久化Fragment使得這些事件的處理變得簡(jiǎn)單畔濒。
你可以在Play Store上下載到代碼,源碼在github上開(kāi)源了锣咒,下載侵状,import到Eclipse,隨心所欲地改吧;)

Demo視圖

譯者注

屏幕旋轉(zhuǎn)總結(jié)

  • 不設(shè)置Activity的android:configChanges時(shí),切屏?xí)匦抡{(diào)用各個(gè)生命周期毅整,切橫屏?xí)r會(huì)執(zhí)行一次趣兄,切豎屏?xí)r會(huì)執(zhí)行兩次
  • 設(shè)置Activity的android:configChanges="orientation"時(shí),切屏還是會(huì)重新調(diào)用各個(gè)生命周期毛嫉,切橫诽俯、豎屏?xí)r只會(huì)執(zhí)行一次
  • 設(shè)置Activity的android:configChanges="orientation|keyboardHidden"時(shí),切屏不會(huì)重新調(diào)用各個(gè)生命周期承粤,只會(huì)執(zhí)行onConfigurationChanged方法

意見(jiàn)修改

  • 歡迎指出翻譯有誤的地方
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暴区,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辛臊,更是在濱河造成了極大的恐慌仙粱,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彻舰,死亡現(xiàn)場(chǎng)離奇詭異伐割,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)刃唤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門隔心,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人尚胞,你說(shuō)我怎么就攤上這事硬霍。” “怎么了笼裳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵唯卖,是天一觀的道長(zhǎng)粱玲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拜轨,這世上最難降的妖魔是什么抽减? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮橄碾,結(jié)果婚禮上卵沉,老公的妹妹穿的比我還像新娘。我一直安慰自己堪嫂,他們只是感情好偎箫,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著皆串,像睡著了一般淹办。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恶复,一...
    開(kāi)封第一講書(shū)人閱讀 52,895評(píng)論 1 314
  • 那天怜森,我揣著相機(jī)與錄音,去河邊找鬼谤牡。 笑死副硅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翅萤。 我是一名探鬼主播恐疲,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼套么!你這毒婦竟也來(lái)了培己?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤胚泌,失蹤者是張志新(化名)和其女友劉穎省咨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體玷室,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡零蓉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了穷缤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敌蜂。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖津肛,靈堂內(nèi)的尸體忽然破棺而出章喉,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布囊陡,位于F島的核電站,受9級(jí)特大地震影響掀亥,放射性物質(zhì)發(fā)生泄漏撞反。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一搪花、第九天 我趴在偏房一處隱蔽的房頂上張望遏片。 院中可真熱鬧,春花似錦撮竿、人聲如沸吮便。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)髓需。三九已至,卻和暖如春房蝉,著一層夾襖步出監(jiān)牢的瞬間僚匆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工搭幻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咧擂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓檀蹋,卻偏偏與公主長(zhǎng)得像松申,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俯逾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容