處理運行時狀態的改變

轉載請註明出處:http://blog.csdn.net/u013687632

英文原文地址:http://developer.android.com/guide/topics/resources/runtime-changes.html

翻譯正文:

    一些設備配置會在運行時改變(比如屏幕方向、鍵盤可用性和語言)。當發生這些改變時,Android會重新啓動正在運行的Activity(onDestroy()會被調用,之後是onCreate())。設計重新啓動行爲是爲了通過自動重新加載你的應用從而適應新的配置,重新加載的應用會採用適應新配置的替代資源。

    要正確地處理重新啓動,非常重要的一點是你的activity在正常的Activity活動週期中存儲下來它之前的數據,在活動週期中,Android在銷燬你的activity之前會調用onSaveInstanceState()從而你可以保存有關應用狀態的數據。你可以在onCreate()或者onRestoreInstanceState()中恢復數據。

    爲了測試你的應用是否狀態完好地重新自啓動,你應該在應用進行各種任務時改變配置狀態(比如修改屏幕方向)。爲了能夠處理諸如配置改變或者用戶接了一個電話,之後在你的應用進程被銷燬之後回到應用之類的情況,你的應用需要能夠在保證用戶數據或狀態無損的情況下隨時重啓。如果你想知道如何恢復activity的狀態,請閱讀Activity lifecycle.

    但是你可能會遇到這樣的情況:重新啓動應用並恢復大量的數據開銷非常大,而且會導致糟糕的用戶體驗。在這種情況下,你有另外兩個選擇:
a.在配置改變期間保留一個數據對象
    允許你的activity在配置改變時重啓,但是要爲你的activity保留一個有狀態的對象(object)。
b.你自己來處理配置的改變
    在特定的配置改變時阻止系統重啓應用,但是當配置改變時要接收回調函數,從而你可以按照需求手動地更新的你的activity。

在配置改變期間保留一個數據對象

    如果重啓應用需要你恢復大量的數據,重新建立網絡連接或者進行其他密集型操作,那麼由於配置改變而造成的一次完整的重啓可能會是一次非常慢的用戶體驗。除此之外,你可能也無法使用系統通過onSaveInstanceState()回調函數爲你保留的Bundle完整地恢復你的activity狀態——它並非是被設計用來承載比較大的對象(比如bitmaps)的,而且其中的數據必須被序列化和反序列化,這些操作會佔用非常多的內存並且使得配置切換緩慢。在這種情況下,你可以通過持有一個Fragment來減輕因爲配置改變而重啓activity時的負擔。這個fragment將會保存你想要留存的有狀態的對象的引用。

    當安卓系統因爲配置改變而關閉你的activity時,你標記爲保留的fragment不會被銷燬。你可以在你的activity中添加這樣的fragment來保存有狀態對象。

    要想在運行配置改變期間在fragment中持有有狀態對象:

    1.繼承Fragment類並聲明對你的有狀態對象的引用。
    2.當fragment創建時調用setRetainInstance(boolean)
    3.把這個fragment添加到你的activity中。
    4.當activity重啓之後通過FragmentManager獲取這個fragment。

舉個例子,像下邊這樣定義你的fragment:

public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject 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(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}

小心:雖然你可以存儲任何對象,但是永遠也不要傳遞一個綁定在Activity上的對象,比如Drawable,一個Adapter,一個View或者任何一個關聯到Context上的對象。如果你這麼做的話,會導致原有activity實例所有視圖和資源的泄露。(資源泄漏是指你的應用一直掛起這些資源使他們無法被垃圾回收,因而很多內存會流失掉。)

    然後使用FragmentManager把fragment添加到activity。當activity在配置改變期間重新啓動後你可以從這個fragment獲取數據對象。例如,像下邊這樣定義你的activity:

public class MyActivity extends Activity {

    private RetainedFragment dataFragment;

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

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new DataFragment();
            fm.beginTransaction().add(dataFragment, “data”).commit();
            // load the data from the web
            dataFragment.setData(loadMyData());
        }

        // the data is available in dataFragment.getData()
        ...
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}

    在這個例子中onCreate()添加了一個fragment或者恢復了對它的引用。onCreate()也在fragment實例中存儲了有狀態對象。onDestroy()更新了持有的fragment中的有狀態對象。

你自己來處理配置的改變

    如果你的應用在特定的配置改變時不需要更新資源並且由於性能限制不允許重啓activity,那麼你可以聲明你的activity自己來處理配置改變,這樣可以阻止系統重啓你的activity。

標註:親自處理配置改變將會比使用可選資源困難得多,因爲系統不會自動爲你應用這些資源。當你一定要禁用由於配置改變而造成的重新啓動時,這項技術可以被看做最後的手段,對於大多數應用並不推薦。

    爲了聲明你的activity要處理一項配置更改,在你的manifest文件中編輯合適的<activity>標籤來修改android:configChanges屬性,填入合適的值以代表你要處理的配置。可選的值列在了android:configChanges屬性的文檔中(最常用的值是”orientation”來避免屏幕方向改變時activity的重啓,以及”keyboardHidden”來避免鍵盤可用性發生改變時activity的重啓)。你可以通過使用 | 把值隔開的方式聲明多個值。

    舉個例子,下邊的manifest代碼聲明瞭一個同時處理屏幕方向改變和鍵盤可用性改變的activity。

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

    現在,當這些配置的其中一個發生改變時,MyActivity不會重啓,而是接收到了一個對onConfigurationChanged()的調用。這個方法傳遞了一個Configuration對象,指定了新的配置。通過讀取Configuration中的字段,你可以定義新的配置並且通過更新你的界面所用資源來做出合適的修改。當這個方法被調用時,你的activity的Resources對象會被更新並返回基於新配置的資源,從而你可以在系統不重啓你的activity的情況下重置你的UI的元素。

注意:從Android3.2(API 版本13)開始,當設備在橫屏和豎屏之間進行切換時”screen size”也會發生變化。因此在做API版本13或者更高(在屬性minSdkVersiontargetSdkVersion中聲明)的開發時,如果你想避免因爲屏幕方向改變而引起的重啓,你必須在”orientation”的基礎上包含”screenSize”屬性。也就是說,你必須聲明android:configChanges=”orientation|screenSize”。但是如果你的應用是面向API版本12或者更低的,那麼你的activity總能自己處理這種配置改變。(即使運行在Android版本3.2或者更高的設備上,配置改變也不會引起activity重啓)

    例如,下邊的onConfigurationChanged()實現檢查了當前設備的屏幕方向:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

    Configuration對象表示出了目前所有的配置,不僅僅是已經改變了的。大部分情況下,你並不關心你的配置是如何改變的以及它是如何簡單地重新分配你的資源以達到爲你正在處理的配置提供備選的目的。例如,因爲Resources對象現在已經更新,你可以通過setImageResource()重新設置ImageView,你也可以設置其他對已經使用的配置來說合適的資源(就像Providing Resources說的那樣)。

    注意Configuration中的字段都是整型,並且他們都與Configuration類中的常量相匹配。你可以參考Configuration中具體的部分來獲取每個字段使用的常量。

記住:當你聲明你的activity以處理配置改變時,你有責任重新設置你爲之提供備選的每個元素。如果你聲明你的activity來處理屏幕方向變化並且有需要在橫屏和豎屏之間切換的圖片,你必須在onConfigurationChanged()中爲每個元素重新分配新的資源。

    如果你不需要基於這些配置修改做activity的更新,你可以不去實現onConfigurationChanged()。在這種情況下,配置更改前後使用的資源是相同的,你僅僅是避免了activity的重新啓動。但是,你的應用應該總是能夠關閉後再打開時原樣保留之前的狀態。因此,在正常的activity生命週期內,你不應該把這項技術當成保留狀態的選擇。不僅是因爲你不能阻止一些其他的配置改變引起的應用重啓,而且你還應該處理諸如用戶離開應用,在用戶再次開啓應用之前應用就被銷燬的情況。

    想了解更多關於你在activity中可以處理的配置改變,請查閱android:configChanges文檔和Configuration類。

第一次翻譯文章,一定有很多不好的地方,懇請大家指出。(不要噴,求)

發佈了60 篇原創文章 · 獲贊 29 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章