通過Lifecycle-Aware 組件處理生命周期[翻譯]

引入概念

  • Lifecycle解決的問題:

    • 用于響應(yīng)送讲、管理其他應(yīng)用組件(如ActivityFragment)的改變狀態(tài)蚕泽,相對于我們自己寫事件監(jiān)聽回調(diào)接口轿秧,Lifecycle會更加簡潔、易于管理宦焦。
    • 大部分應(yīng)用組件都存在于Android Framework发钝,生命周期綁定在此之上顿涣,并且直接由系統(tǒng)或者由應(yīng)用進程框架管理,因此必須遵循它們的規(guī)則笼平,避免內(nèi)存泄露和應(yīng)用崩潰园骆。
  • 實際場景: 我們需要在Activity中顯示設(shè)備的位置舔痪,通常會這樣實現(xiàn):

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
} 

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    } 
    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

  • 貌似看起來很不錯寓调,但是在實際應(yīng)用中,最終會存在太多用于管理其他組件生命周期狀態(tài)的調(diào)用锄码,管理多個組件時會在生命周期方法中放置大量代碼夺英,例如 onStart()onStop(),這使得它們難以維護滋捶。
  • 此外痛悯,無法保證組件在ActivityFragment停止之前啟動,這在我們需要執(zhí)行耗時長的操作時尤為真實,比如我們在onStart()中檢查某些配置重窟,這就可能在當(dāng) onStop()onStart()之間完成的情況下 產(chǎn)生競爭條件载萌,最終導(dǎo)致組件存活的時間比實際需要長。如下示例:
class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}
  • 針對以上問題巡扇,android.arch.lifecycle包提供了可彈性扭仁、解耦地解決這些問題的類和接口。

Lifecycle的概念

  • lifecycle是一個持有組件生命周期(Activity厅翔,Fragment)狀態(tài)的類乖坠,并且允許其他對象觀察這一狀態(tài)。

  • lifecycle 使用兩個主要枚舉類來處理與之綁定的組件的生命周期狀態(tài)刀闷。

    • Event: 這個事件由系統(tǒng)框架和Lifecyle類分發(fā)熊泵,并且會映射到ActivitFragment的回調(diào)事件上。
    • State: 代表當(dāng)前LIfecycle對象正在處理的組件的狀態(tài)甸昏。
      思考`States`節(jié)點以及`Events`事件
  • 通過給方法添加注解的方式可以使這個類具備監(jiān)聽組件生命周期的能力顽分,然后通過Lifecycle#addObserver()添加觀察者即可賦予其他對象這個觀察能力,如下示例:

    // 作為觀察者施蜜,我們需要實現(xiàn) LifecycleObserver 接口
    public class MyObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) // 在onResume時執(zhí)行
        public void connectListener() {
            ...
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) // 在onPause時執(zhí)行
        public void disconnectListener() {
            ...
        }
    }
    // 添加一個觀察者卒蘸,使得這個觀察者也可以監(jiān)聽組件狀態(tài)變化
    myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
    

LifeOwner的概念

  • LifeOwner只包含一個getLifecycle()方法,用于獲取Lifecycle花墩,使用時必須實現(xiàn)這個方法悬秉。如果想要管理整個應(yīng)用進程的生命周期,可以使用ProcessLifecycleOwner代替冰蘑。

  • 這個接口從ActivityFragment等中抽取了Lifecycle的所有權(quán)和泌,并且允許編寫組件來與之配合,任何自定義的應(yīng)用類都可以實現(xiàn)LifecOwner接口祠肥。

  • 實現(xiàn)了LifecycleOwner的組件與實現(xiàn)了LifecycleObserver的組件運作方式是無縫銜接的的武氓,因為Owner用于提供事件,而Observer用于注冊、監(jiān)聽事件县恕。

  • 在前面我們定義了一個實現(xiàn)了LifecycleObserver接口的MyLocationLIstener類东羹,我們可以如下面代碼這樣在onCreate中初始化,這意味響應(yīng)生命周期變化的邏輯都提取到了MyLocationLIstener忠烛,而不是全部擠在Activity中属提,可見這樣可以極大簡化ActivityFragment的代碼邏輯。

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;
    
        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
                // update UI
            });
            Util.checkUserStatus(result -> {
                if (result) {
                    myLocationListener.enable();
                }
            });
      }
    }
    
    • 為了避免在Lifecycle的不合適狀態(tài)下執(zhí)行回調(diào)美尸,比如如果這個回調(diào)用于在Activity保存狀態(tài)后執(zhí)行Fragment的轉(zhuǎn)場切換冤议,就會觸發(fā)崩潰,因此我們千萬不要執(zhí)行這個回調(diào)师坎。為了簡單處理這個問題恕酸,Lifecycle允許其他對象查看當(dāng)前狀態(tài)。
    class MyLocationListener implements LifecycleObserver {
        private boolean enabled = false;
        public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
           ...
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void start() {
            if (enabled) {
               // connect
            }
        }
    
        public void enable() {
            enabled = true;
            // 查看Lifecycle的當(dāng)前狀態(tài)
            if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
                // connect if not connected
            }
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void stop() {
            // disconnect if connected
        }
    }
    
    • 通過上面實現(xiàn)胯陋,我們的MyLocationListener就完全可以管理生命周期了蕊温,如果想要在其他ActivityFragment中使用它,那么只需要初始化一下就行了遏乔,其他處理操作都會在它內(nèi)部處理义矛。
    • 如果一個類庫提供需要結(jié)合Android生命周期的處理類,那么建議使用Lifecycle-aware組件按灶,這樣的話類庫客戶端就可以輕易地整合這些組件而不需要手動地在客戶端處理生命周期管理工作症革。
  • 實現(xiàn)自定義的 LifecycleOwner
    • Support Library 26.1.0以及上版本中,FragmentActivity已經(jīng)實現(xiàn)了LifecycleOwner接口鸯旁。
    • 如果需要自定義實現(xiàn)一個LifecycleOwner噪矛,那么可以使用LifecycleRegistry類,但是你需要發(fā)送事件到LifecycleRegistry類中铺罢,如下示例:
    public class MyActivity extends Activity implements LifecycleOwner {
        private LifecycleRegistry mLifecycleRegistry;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mLifecycleRegistry = new LifecycleRegistry(this);
            mLifecycleRegistry.markState(Lifecycle.State.CREATED);
        }
    
        @Override
        public void onStart() {
            super.onStart();
            mLifecycleRegistry.markState(Lifecycle.State.STARTED);
        }
    
        @NonNull
        @Override
        public Lifecycle getLifecycle() {
            return mLifecycleRegistry;
        }
    }
    

lifecycle-aware 組件最佳實踐

  • 盡可能保證UI控制器艇挨,如ActivityFragment的簡潔性,它們不應(yīng)該請求它們自身的數(shù)據(jù)韭赘,而應(yīng)交給ViewModel去做缩滨,并觀察一個LiveData對象,用來將變化返回給UI視圖泉瞻。

  • 盡量編寫數(shù)據(jù)驅(qū)動型(data-drivenUI脉漏,這種形式下,UI控制器只需要負責(zé)在數(shù)據(jù)改變時更新視圖袖牙,或者通知用戶動作給ViewModel

  • 將數(shù)據(jù)邏輯放到ViewModel類侧巨,ViewModel應(yīng)當(dāng)用作UI控制器和應(yīng)用其他部分的連接器,但是注意鞭达,ViewModel不負責(zé)請求數(shù)據(jù)(比如網(wǎng)絡(luò)請求等)司忱,相反皇忿,它只是調(diào)用數(shù)據(jù)請求模塊去請求數(shù)據(jù),然后將數(shù)據(jù)結(jié)果返回給UI控制器坦仍。

  • 使用DataBinding來維持視圖與UI控制器間的簡潔性鳍烁。它可以可簡化視圖的聲明和視圖更新時所需在UI控制器中編寫的代碼,如果喜歡使用Java繁扎,那么建議使用類似于ButterKnife之類的類庫來避免編寫無聊的聲明代碼幔荒,并且它可以實現(xiàn)更好的抽象。

  • 如果UI很復(fù)雜锻离,可以考慮創(chuàng)建一個Presenter類來處理UI更改操作铺峭,這可能很費事,但可以使UI組件更易于測試汽纠。

  • 禁止在ViewModel中引用View或者Activity上下文(context震束,否則如果ViewModel生命周期比Activity長時(比如configuration change的情況)陶舞,Activity就會內(nèi)存泄露而不被GC了。


lifecycle-aware 組件使用場景

lifecycle-aware組件可以在各種場景中讓生命周期的管理更簡單豌拙,比如以下場景:

  • 粗略定位(coarse-grained)與高精度定位(fine-grained)之間的更新狀態(tài)切換钓账。使用lifecycle-aware組件在應(yīng)用處于前臺時開啟高精度定位碴犬,而在后臺時開啟粗略定位,可以結(jié)合LiveData來實現(xiàn)狀態(tài)改變時更新UI的操作梆暮。
  • 開啟和關(guān)閉視頻緩沖服协。 比如使用lifecycle-aware組件盡快開啟視頻緩沖,而延遲到應(yīng)用完全啟動后才真正播放視頻啦粹,同樣也可以在應(yīng)用關(guān)閉時終止緩沖動作偿荷。
  • 開啟和關(guān)閉網(wǎng)絡(luò)連接。 使用lifecycle-aware組件進行動態(tài)更新網(wǎng)絡(luò)數(shù)據(jù)唠椭,如應(yīng)用處于前臺時自動加載數(shù)據(jù)跳纳,而應(yīng)用切換至后臺時自動暫停加載。
  • 啟動和暫停Drawable動畫贪嫂。 前臺時播放動畫寺庄,后臺是暫停動畫。

處理 onStop 事件

當(dāng)Lifecycle關(guān)聯(lián)到AppCompatActivityFragment時力崇,它的狀態(tài)會切換到CREATED斗塘,而ON_STOP狀態(tài)則是會在AppCompatActivityFragmentonSaveInstanceState()被調(diào)用是觸發(fā)。

如果AppCompatActivityFragment是通過onSaveInstanceState()中保存狀態(tài)的亮靴,那么在ON_START被調(diào)用之前馍盟,它們的UI狀態(tài)都會被認定為不可變的(immutable)。這時如果嘗試在UI狀態(tài)保存后修改UI的話台猴,就會導(dǎo)致應(yīng)用導(dǎo)航狀態(tài)不一致朽合,這也就是為什么在狀態(tài)保存后執(zhí)行FragmentTransaction俱两,FragmentManager會拋異常的原因了,具體看 commit()方法曹步。

如果LiveData的已經(jīng)關(guān)聯(lián)到LifecycleObserver還沒到到達STARTED狀態(tài)的話, LiveData可以通過終止observer的調(diào)用來避免上述邊角情況的發(fā)生宪彩,這是因為LiveData會在執(zhí)行Observer之前先調(diào)用isAtLeast()確定狀態(tài),然后再決定是否執(zhí)行讲婚。

然而不幸的是尿孔,AppCompatActivityonStop()方法實在onSaveInstanceState()之后調(diào)用的,這種情況就導(dǎo)致已經(jīng)保存的UI狀態(tài)不允許改變筹麸,而Lifecycle又還沒有到達STARTED狀態(tài)活合。

為了避免這個問題的發(fā)生,在版本beta2及之前的Lifecycle類都會將這一狀態(tài)標記為CREATED物赶,而不分發(fā)這一事件白指,這樣,任何檢查當(dāng)前狀態(tài)的代碼都能拿到真實狀態(tài)值酵紫,即使這一事件還沒有被分發(fā)告嘲,直到系統(tǒng)調(diào)用onStop()方法。

然而又不幸的是奖地,這個解決方案有兩大問題:

  • API 23及之前的版本橄唬,Android系統(tǒng)確實會保存Activity的狀態(tài),即使是由其他AActivity轉(zhuǎn)換的部分参歹,也就是說仰楚,系統(tǒng)調(diào)用onSaveInstanceState(),但是確實沒有調(diào)用onStop()必要犬庇。這造成了一個潛在的長間隔期僧界,而在這個間隔期之間,observer一直會認為Lifecycle是活動的械筛,即使UI狀態(tài)已經(jīng)不能被改變了捎泻。
  • 任何想要暴露給LiveData類似行為的類都必須實現(xiàn)Lifecyclebeta2及之前版本所提供的解決方案。

Note: 為了簡化流程并兼容老版本埋哟,請直接從版本1.0.0-rc1開始使用笆豁,Lifecycle對象會被標記為CREATED,并且會在onSaveInstanceState()被調(diào)用是標記為ON_STOP狀態(tài)赤赊,而無需等待onStop()的調(diào)用闯狱。雖然這并不會影響我們的代碼,但是確是我們需要注意的抛计,因為它沒有遵循API 26及以前版本中Activity的生命周期調(diào)用次序

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哄孤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吹截,更是在濱河造成了極大的恐慌瘦陈,老刑警劉巖凝危,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晨逝,居然都是意外死亡蛾默,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門捉貌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來支鸡,“玉大人,你說我怎么就攤上這事趁窃∧琳酰” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵醒陆,是天一觀的道長瀑构。 經(jīng)常有香客問我,道長统求,這世上最難降的妖魔是什么检碗? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮码邻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘另假。我一直安慰自己像屋,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布边篮。 她就那樣靜靜地躺著己莺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戈轿。 梳的紋絲不亂的頭發(fā)上凌受,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音思杯,去河邊找鬼胜蛉。 笑死,一個胖子當(dāng)著我的面吹牛色乾,可吹牛的內(nèi)容都是我干的誊册。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼暖璧,長吁一口氣:“原來是場噩夢啊……” “哼案怯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起澎办,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嘲碱,失蹤者是張志新(化名)和其女友劉穎金砍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麦锯,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡恕稠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了离咐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谱俭。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宵蛀,靈堂內(nèi)的尸體忽然破棺而出昆著,到底是詐尸還是另有隱情,我是刑警寧澤术陶,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布凑懂,位于F島的核電站,受9級特大地震影響梧宫,放射性物質(zhì)發(fā)生泄漏接谨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一塘匣、第九天 我趴在偏房一處隱蔽的房頂上張望脓豪。 院中可真熱鬧,春花似錦忌卤、人聲如沸扫夜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笤闯。三九已至,卻和暖如春棍厂,著一層夾襖步出監(jiān)牢的瞬間颗味,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工牺弹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浦马,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓例驹,卻偏偏與公主長得像捐韩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鹃锈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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