1. 程式人生 > >Android Architecture Components

Android Architecture Components

開發者經常面臨的問題

Android應用由四大元件構成,各元件可以被獨立且無序的調起,使用者會在各個App之間來回切換。元件啟動後,生命週期會受使用者的操作和系統影響,不完全受開發者控制。而由於裝置記憶體問題,程序隨時可能被系統強殺,所以不要將資料和狀態直接儲存在元件中,也不要讓元件互相依賴。

問題例項

  • 記憶體洩漏:在Activity中發起網路請求,在網路請求返回之前退出Activity, 那麼Activity就被洩漏了。

  • 崩潰:Activity destroy後,還被其他類操作,從而引發崩潰,例如Glide圖片庫;

  • Activity類臃腫:資料,邏輯,控制元件程式碼都堆積在Activity中,導致Activity臃腫,不易維護和測試。

  • Fragment通訊困難:開發中經常會遇到Activity中含有多個Fragment的情況, 並且Fragment之間需要通訊。通常會利用Activity轉發Fragment之間的通訊,而且Fragment之間還需要依賴對方的生命週期和通訊細節。

  • 資料易銷燬:如果將記憶體中的資料儲存在Activity中,由於Activity很容易被銷燬重建(配置改變,記憶體不夠),那麼資料也就很容易被銷燬。

通用的框架原則

  • 關注點分離:不要在Activity/Fragment中新增非UI控制、非系統介面呼叫的程式碼。儘量讓他們保持精煉,以免引起生命週期相關的問題。這些類是系統建立和管理的,並不完全受開發者控制。

  • 模型驅動UI:應該用資料模型驅動UI展示,最好是持久模型,因為當系統強殺程序或者網路不穩定時,持久模型能讓程式繼續工作。模型獨立於元件之外,不會受到生命週期的影響。

應用框架元件

Android官方在17年IO大會上釋出了一套框架元件,幫助開發者開發優質的App.

LifeCycle

將Activity/Fragment的生命週期剝離到其他類中,減少元件類中的程式碼。

LifeCycle用兩個列舉類追蹤元件的狀態。

  • Event:從Framework層分發的,匹配Activity和Fragment中的生命週期方法回撥。
  • State:當前元件的狀態。

使用方式
public class
LearnLifeCycleObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) public void onCreate() { } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onStop() { } } public class LearnLifeCycleActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLifecycle().addObserver(new LearnLifeCycleObserver()); } }
實踐
  • 將UI和資料分離,UI層不需要持有資料。

  • 構建資料驅動UI的應用,UI控制器負責根據資料更新UI以及將使用者的操作反饋給資料層。

  • 將資料邏輯放在ViewModel中,將資料拉取邏輯放在資料倉庫(Data Repository)中.

  • 使用MVP模式,引入Presenter, 讓程式更加容易被測試。

LiveData

一個自帶觀察者模式的資料封裝類,不同的是LiveData有生命週期,會利用LifeCycle自動監聽與它繫結的元件的生命週期。當資料發生改變時,Activity或者Fragment要處於活動狀態,觀察者才會接收到資料改變的回撥,此時介面就可以安全的進行渲染,而不會出現Activity銷燬後還更改介面的情況。

優點
  • 不會造成洩漏記憶體。
  • 不會由於操作銷燬的Activity而發生崩潰。
  • 不需要手動處理生命週期。
  • 資料總能實時更新。
  • 資料不會受configuration更改的影響。
  • 易於共享資料。
使用

final MutableLiveData<String> userNameLiveData = new MutableLiveData<>();

userNameLiveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {
        //Activity的lifecycle state處於STARTED或RESUMED時才會回撥
        mUserNameTV.setText(s);
    }
});

mUserNameSetBtn.setOnClickListener(new View.OnClickListener(){

    @Override
    public void onClick(View v) {
        //更新LiveData的資料,以嘗試觸發觀察者的通知
        userNameLiveData.setValue("jayden");
    }
});
兩個擴充套件類
MutableLiveData

開放了setValue和postValue方法,用於主動改變LiveData的值,並通知觀察者。

MediatorLiveData

可以同時觀察多個LiveData, 當被觀察的LiveData發生改變時,可以對LiveData的資料進行加工後再通知MediatorLiveData.

轉換LiveData
Transformations#map

利用MediatorLiveData將LiveData的資料加工後再通知給觀察者。

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});
Transformations#switchMap

和map方法類似,而且也是用MediatorLiveData實現。但是switchMap方法的第二個形參介面的返回值是LiveData. 如下程式碼塊中的例子,當userId發生改變後,getUser(id)返回另一個LiveData: liveData1, user的觀察者開始觀察liveData1的變化。

舉個例子:id為1的使用者切換成id為2的使用者後,getUser(id)方法返回的LiveData就是id為2的使用者的使用者資訊,以後如果id為2的使用者資訊發生改變,user的觀察者就會接收到通知。

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

ViewModel

用來儲存和管理和UI相關的有生命週期的資料。當Configuration改變,例如螢幕旋轉,語言切換時,ViewModel中的資料不會被銷燬,是一個不會被濫用的單例。

建立ViewModel

利用LiveData持有資料

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> mObservableUsers;
    
    public MyViewModel(){
        mObservableUsers = new MutableLiveData<List<Users>>();
    }
    
    public LiveData<List<User>> getUsers() {
        return mObservableUsers;
    }

    private void loadUsers() {
        // 非同步載入資料後通知到users中。
        ...
        mObservableUsers.setValue(users)
    }
}

在Activity中監聽資料改變

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        //監聽LiveData
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

如果由於螢幕旋轉或語言切換導致Activity重建,ViewModelProviders.of(this).get(MyViewModel.class);獲取的ViewModel還是Activity首次建立時所構建的,只有當Activity銷燬後,ViewModel才會被清除。

ViewModel的生命週期

在多個Fragment中共享資料
public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

兩個Fragment拿到的ViewModel物件是同一個。

這種方式的優點:

  • Activity不需要了解Fragment之間的通訊,完全鬆耦合。
  • 兩個Fragment都不需要依賴對方的生命週期和通訊細節,即使一個Fragment被銷燬,也不會影響另一個Fragment.

最終的框架

推薦的框架原則

  • 在Manifest檔案中定義的程式入口:Activities, Services, BroadcastReceiver等,都不能作為資料的來源。

  • 明確定義各模組的職責。

  • 儘可能少地暴露每個模組的資訊。

  • 定義模組間的互動時,考慮如何讓他們易於測試。

  • 將資料持久化,讓程式在離線時更加可用。

  • 資料儲存庫應該指定一個數據源作為單一的資料來源。

推薦閱讀