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等,都不能作為資料的來源。
-
明確定義各模組的職責。
-
儘可能少地暴露每個模組的資訊。
-
定義模組間的互動時,考慮如何讓他們易於測試。
-
將資料持久化,讓程式在離線時更加可用。
-
資料儲存庫應該指定一個數據源作為單一的資料來源。