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

引入概念

  • Lifecycle解決的問題:

    • 用於響應、管理其他應用組件(如ActivityFragment)的改變狀態,相對於我們自己寫事件監聽回調接口,Lifecycle會更加簡潔、易於管理。
    • 大部分應用組件都存在於Android Framework,生命週期綁定在此之上,並且直接由系統或者由應用進程框架管理,因此必須遵循它們的規則,避免內存泄露和應用崩潰。
  • 實際場景: 我們需要在Activity中顯示設備的位置,通常會這樣實現:

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

  • 貌似看起來很不錯,但是在實際應用中,最終會存在太多用於管理其他組件生命週期狀態的調用,管理多個組件時會在生命週期方法中放置大量代碼,例如 onStart()onStop(),這使得它們難以維護。
  • 此外,無法保證組件在ActivityFragment停止之前啓動,這在我們需要執行耗時長的操作時尤爲真實,比如我們在onStart()中檢查某些配置,這就可能在當 onStop()onStart()之間完成的情況下 產生競爭條件,最終導致組件存活的時間比實際需要長。如下示例:
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是一個持有組件生命週期(ActivityFragment)狀態的類,並且允許其他對象觀察這一狀態。

  • lifecycle 使用兩個主要枚舉類來處理與之綁定的組件的生命週期狀態。

    • Event: 這個事件由系統框架和Lifecyle類分發,並且會映射到ActivitFragment的回調事件上。
    • State: 代表當前LIfecycle對象正在處理的組件的狀態。
  • 通過給方法添加註解的方式可以使這個類具備監聽組件生命週期的能力,然後通過Lifecycle#addObserver()添加觀察者即可賦予其他對象這個觀察能力,如下示例:

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

LifeOwner的概念

  • LifeOwner只包含一個getLifecycle()方法,用於獲取Lifecycle,使用時必須實現這個方法。如果想要管理整個應用進程的生命週期,可以使用ProcessLifecycleOwner代替

  • 這個接口從ActivityFragment等中抽取了Lifecycle的所有權,並且允許編寫組件來與之配合,任何自定義的應用類都可以實現LifecOwner接口

  • 實現了LifecycleOwner的組件與實現了LifecycleObserver的組件運作方式是無縫銜接的的,因爲Owner用於提供事件,而Observer用於註冊、監聽事件

  • 在前面我們定義了一個實現了LifecycleObserver接口的MyLocationLIstener類,我們可以如下面代碼這樣在onCreate中初始化,這意味響應生命週期變化的邏輯都提取到了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的不合適狀態下執行回調,比如如果這個回調用於在Activity保存狀態後執行Fragment的轉場切換,就會觸發崩潰,因此我們千萬不要執行這個回調。爲了簡單處理這個問題,Lifecycle允許其他對象查看當前狀態。
    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的當前狀態
            if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
                // connect if not connected
            }
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void stop() {
            // disconnect if connected
        }
    }
    
    • 通過上面實現,我們的MyLocationListener就完全可以管理生命週期了,如果想要在其他ActivityFragment中使用它,那麼只需要初始化一下就行了,其他處理操作都會在它內部處理。
    • 如果一個類庫提供需要結合Android生命週期的處理類,那麼建議使用Lifecycle-aware組件,這樣的話類庫客戶端就可以輕易地整合這些組件而不需要手動地在客戶端處理生命週期管理工作。
  • 實現自定義的 LifecycleOwner
    • Support Library 26.1.0以及上版本中,FragmentActivity已經實現了LifecycleOwner接口。
    • 如果需要自定義實現一個LifecycleOwner,那麼可以使用LifecycleRegistry類,但是你需要發送事件到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的簡潔性,它們不應該請求它們自身的數據,而應交給ViewModel去做,並觀察一個LiveData對象,用來將變化返回給UI視圖。

  • 儘量編寫數據驅動型(data-drivenUI,這種形式下,UI控制器只需要負責在數據改變時更新視圖,或者通知用戶動作給ViewModel

  • 將數據邏輯放到ViewModel類,ViewModel應當用作UI控制器和應用其他部分的連接器,但是注意,ViewModel不負責請求數據(比如網絡請求等),相反,它只是調用數據請求模塊去請求數據,然後將數據結果返回給UI控制器。

  • 使用DataBinding來維持視圖與UI控制器間的簡潔性。它可以可簡化視圖的聲明和視圖更新時所需在UI控制器中編寫的代碼,如果喜歡使用Java,那麼建議使用類似於ButterKnife之類的類庫來避免編寫無聊的聲明代碼,並且它可以實現更好的抽象。

  • 如果UI很複雜,可以考慮創建一個Presenter類來處理UI更改操作,這可能很費事,但可以使UI組件更易於測試。

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


lifecycle-aware 組件使用場景

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

  • 粗略定位(coarse-grained)與高精度定位(fine-grained)之間的更新狀態切換。使用lifecycle-aware組件在應用處於前臺時開啓高精度定位,而在後臺時開啓粗略定位,可以結合LiveData來實現狀態改變時更新UI的操作。
  • 開啓和關閉視頻緩衝。 比如使用lifecycle-aware組件儘快開啓視頻緩衝,而延遲到應用完全啓動後才真正播放視頻,同樣也可以在應用關閉時終止緩衝動作。
  • 開啓和關閉網絡連接。 使用lifecycle-aware組件進行動態更新網絡數據,如應用處於前臺時自動加載數據,而應用切換至後臺時自動暫停加載。
  • 啓動和暫停Drawable動畫。 前臺時播放動畫,後臺是暫停動畫。

處理 onStop 事件

Lifecycle關聯到AppCompatActivityFragment時,它的狀態會切換到CREATED,而ON_STOP狀態則是會在AppCompatActivityFragmentonSaveInstanceState()被調用是觸發。

如果AppCompatActivityFragment是通過onSaveInstanceState()中保存狀態的,那麼在ON_START被調用之前,它們的UI狀態都會被認定爲不可變的(immutable)。這時如果嘗試在UI狀態保存後修改UI的話,就會導致應用導航狀態不一致,這也就是爲什麼在狀態保存後執行FragmentTransactionFragmentManager會拋異常的原因了,具體看 commit()方法

如果LiveData的已經關聯到LifecycleObserver還沒到到達STARTED狀態的話, LiveData可以通過終止observer的調用來避免上述邊角情況的發生,這是因爲LiveData會在執行Observer之前先調用isAtLeast()確定狀態,然後再決定是否執行。

然而不幸的是,AppCompatActivityonStop()方法實在onSaveInstanceState()之後調用的,這種情況就導致已經保存的UI狀態不允許改變,而Lifecycle又還沒有到達STARTED狀態。

爲了避免這個問題的發生,在版本beta2及之前的Lifecycle類都會將這一狀態標記爲CREATED,而不分發這一事件,這樣,任何檢查當前狀態的代碼都能拿到真實狀態值,即使這一事件還沒有被分發,直到系統調用onStop()方法。

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

  • API 23及之前的版本,Android系統確實會保存Activity的狀態,即使是由其他AActivity轉換的部分,也就是說,系統調用onSaveInstanceState(),但是確實沒有調用onStop()必要。這造成了一個潛在的長間隔期,而在這個間隔期之間,observer一直會認爲Lifecycle是活動的,即使UI狀態已經不能被改變了。
  • 任何想要暴露給LiveData類似行爲的類都必須實現Lifecyclebeta2及之前版本所提供的解決方案。

Note: 爲了簡化流程併兼容老版本,請直接從版本1.0.0-rc1開始使用Lifecycle對象會被標記爲CREATED,並且會在onSaveInstanceState()被調用是標記爲ON_STOP狀態,而無需等待onStop()的調用。雖然這並不會影響我們的代碼,但是確是我們需要注意的,因爲它沒有遵循API 26及以前版本中Activity的生命週期調用次序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章