懶得處理樣式了, 將就著看吧.
官網(wǎng)地址: https://developer.android.com/topic/libraries/architecture/guide.html#common_problems_faced_by_app_developers
Guide相比于電腦端應(yīng)用, 移動端有很多額外的需求, 比如應(yīng)用會被后臺殺死, 四大組件短暫的生命周期, 跳到相機(jī)獲取照片之類的應(yīng)用間跳轉(zhuǎn), 玩一半來個電話. 以上情況都很普遍, 這就需要碼農(nóng)們管理好數(shù)據(jù)的持久化和加載時機(jī).總結(jié)一下就是, 各組件的啟動順序是不確定的, 死亡時間也是不確定的. 這些都是不可控的, 所以不要把數(shù)據(jù)存到組件里!!!, 組件間也不應(yīng)該互相依賴.架構(gòu)原則一: 模塊分離Activity & Fragment 只應(yīng)該保留 UI處理 和 與操作系統(tǒng)交互 的邏輯.要明白, 這倆貨不歸你管, 丫只是個glue classes, 用于把你的應(yīng)用套進(jìn)Android操作系統(tǒng)里.架構(gòu)原則二: Model 核心實現(xiàn)寫進(jìn)Model模塊, 因為Model 因為沒有生命周期問題和配置問題(比如橫屏轉(zhuǎn)豎屏). 最好加上持久化, 這可以避免App被殺死時丟失當(dāng)前數(shù)據(jù), 和丟失網(wǎng)絡(luò)連接時直接傻逼.官方推薦架構(gòu)煮個栗子, 比如要實現(xiàn)用戶信息模塊, 數(shù)據(jù)用 REST API 從后臺獲取.UI部分是常規(guī)的 UserProfileFragment.java和 user_profile_layout.xml UI要初始化頁面, 則需要將 User ID 傳給Model 模塊, 并接收返回的 User object (pojo) . id 推薦通過 fragment.arguments 傳入, 這可以保證程序被殺死重啟后, 數(shù)據(jù)仍可以保留. Model 模塊, UserProfileViewModel :?ViewModel?.這貨相當(dāng)于一個中間層, 用于給視圖組件提供數(shù)據(jù), 與數(shù)據(jù)處理層交互(比如通知處理層加載數(shù)據(jù)或是提交用戶做出的數(shù)據(jù)變更).public class UserProfileViewModel extends ViewModel {? ? private String userId;? ? private User user;? ? public void init(String userId) { this.userId = userId; }? ? public User getUser() { return user; }}public class UserProfileFragment extends Fragment {? ? private static final String UID_KEY = "uid";? ? private UserProfileViewModel viewModel;? ? @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {? ? ? ? super.onActivityCreated(savedInstanceState);? ? ? ? String userId = getArguments().getString(UID_KEY);? ? ? ? viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);? ? ? ? viewModel.init(userId);? ? }? ? @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {? ? ? ? return inflater.inflate(R.layout.user_profile, container, false);? ? }} 現(xiàn)在需要解決的是交互問題, 在Model模塊的User被賦值時, 我們需要通知UI更新頁面. 這就引入了下一個要講的模塊 LiveData?LiveData 是個數(shù)據(jù)容器, 類似于 Observable. 丫使你的組件可以在不添加外部依賴的情況下觀察到容器中數(shù)據(jù)的變動. 丫也會替你關(guān)注生命周期變化, 避免內(nèi)存泄漏.引入庫 android.arch.lifecycle:reactivestreams 可以使 LiveData 與 RxJava2 同用.以下是加入LiveData的代碼:public class UserProfileViewModel extends ViewModel {? ? ...? ? private User user;? ? private LiveDatauser;? ? public LiveDatagetUser() { return user; }}@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {? ? super.onActivityCreated(savedInstanceState);? ? viewModel.getUser().observe(this, user -> { // update UI });}每次Pojo數(shù)據(jù)變更的時候, 都會觸發(fā)這個回調(diào), 更新UI界面.如果你熟悉其他類似的觀察者回調(diào), 你就該知道, 我們沒必要重載onStop() 方法來停止觀察. 對于LiveData來說, 這貨是看生命周期的, 只在UI頁面活躍時(onStart() 之后, onStop() 之前)觸發(fā)回調(diào), 并且會在onDestroy() 后自動移除觀察者.開發(fā)者在諸如屏幕旋轉(zhuǎn)之類的配置變更時也不需要做額外的處理. ViewModel會自覺的存儲數(shù)據(jù), 并在變更完成后觸發(fā)回調(diào).這也是ViewModel不該放進(jìn)UI視圖的原因: 使得ViewModel可以無視View的生命周期.數(shù)據(jù)獲取我們已經(jīng)把ViewModel和Fragment關(guān)聯(lián)在一起了, 那么ViewModel是怎么讀取數(shù)據(jù)的呢? 假定我們使用Retrofit 庫來訪問后臺提供的REST API.public interface Webservice {? ? /**? ? ?* @GET declares an HTTP GET request? ? ?* @Path("user") annotation on the userId parameter marks it as a? ? ?* replacement for the {user} placeholder in the @GET path? ? ?*/? ? @GET("/users/{user}")? ? CallgetUser(@Path("user") String userId);}ViewModel 的本地實現(xiàn)可以直接調(diào)用Webservice 獲取數(shù)據(jù), 并存入Pojo. 但這個活不應(yīng)該由ViewModel來做, 秉承職能分離的原則, ViewModel 需要找一個代理模塊: RepositoryRepository(數(shù)據(jù)倉庫)模塊用于處理數(shù)據(jù)操作. 并對外提供簡潔的API調(diào)用. 這貨負(fù)責(zé)獲取數(shù)據(jù), 并在有外部API調(diào)用時處理數(shù)據(jù)并輸出. 你可以把丫當(dāng)做不同數(shù)據(jù)源(本地, 網(wǎng)絡(luò), 緩存, 等等)的轉(zhuǎn)接器.UserRepository 使用示例如下:public class UserRepository {? ? private Webservice webservice;? ? // ...? ? public LiveDatagetUser(int userId) {? ? ? ? // This is not an optimal implementation, we'll fix it below? ? ? ? final MutableLiveDatadata = new MutableLiveData<>();? ? ? ? webservice.getUser(userId).enqueue(new Callback() {? ? ? ? ? ? @Override public void onResponse(Callcall, Responseresponse) {? ? ? ? ? ? ? ? // error case is left out for brevity? ? ? ? ? ? ? ? data.setValue(response.body());? ? ? ? ? ? }? ? ? ? });? ? ? ? return data;? ? }}盡管這貨看起來沒什么卵用, 但丫的存在還是很有意義的, 丫負(fù)責(zé)抽離數(shù)據(jù)源, ViewModel不需要知道數(shù)據(jù)是怎么來的, 而我們可以在必要的時候切換數(shù)據(jù)源.* 為了簡化代碼, 這里沒有處理網(wǎng)絡(luò)異常, 處理邏輯可以看Addendum: exposing network status.組件依賴上面的代碼中, UserRepository 需要一個Webservice 實例才能干活. 而如果直接New一個對象出來, 就需要管理Webservice 的依賴. 所以你懂的, 解決方案有兩個, 依賴注入和服務(wù)定位.Dependency Injection: 依賴注入(DI)可以從外部構(gòu)造實例, 在調(diào)用時直接獲取, 在運行時, 有專門的類負(fù)責(zé)提供依賴. Android中我們推薦使用自家的Dagger 2 .這貨可以遍歷依賴樹, 還有編譯時檢測.Service Locator: 服務(wù)定位可以提供一個注冊類, 這貨比上面那個簡單點, 所以你要是不會用DI, 就拿丫頂上.這倆貨還有一個好處是方便測試, 這個點夠吹一年. 當(dāng)前Demo中將使用Dagger2.關(guān)聯(lián)ViewModel和Repository我們來改一下UserProfileViewModel 的代碼, 加上數(shù)據(jù)倉庫的邏輯.public class UserProfileViewModel extends ViewModel {? ? private LiveDatauser;? ? private UserRepository userRepo;? ? @Inject // UserRepository parameter is provided by Dagger 2? ? public UserProfileViewModel(UserRepository userRepo) {? ? ? ? this.userRepo = userRepo;? ? }? ? public void init(String userId) {? ? ? ? if (this.user != null) {? ? ? ? ? ? // ViewModel is created per Fragment so? ? ? ? ? ? // we know the userId won't change? ? ? ? ? ? return;? ? ? ? }? ? ? ? user = userRepo.getUser(userId);? ? }? ? public LiveDatagetUser() { return this.user; }}數(shù)據(jù)緩存上面的數(shù)據(jù)倉庫已經(jīng)完美抽離了網(wǎng)絡(luò)請求, 但這還不夠?qū)? 現(xiàn)在的問題是, 丫在獲取數(shù)據(jù)后, 使用一次就廢了. 比如用戶離開了UserProfileFragment 頁面又回來, 還需要重新請求數(shù)據(jù).這既浪費了帶寬, 還增加了等待時間. 所以我們來增加一個數(shù)據(jù)源, 把數(shù)據(jù)保留在內(nèi)存中.@Singleton ?// informs Dagger that this class should be constructed oncepublic class UserRepository {? ? private Webservice webservice;? ? // simple in memory cache, details omitted for brevity? ? private UserCache userCache;? ? public LiveDatagetUser(String userId) {? ? ? ? LiveDatacached = userCache.get(userId);? ? ? ? if (cached != null) { return cached; }? ? ? ? final MutableLiveDatadata = new MutableLiveData<>();? ? ? ? userCache.put(userId, data);? ? ? ? // this is still suboptimal but better than before.? ? ? ? // a complete implementation must also handle the error cases.? ? ? ? webservice.getUser(userId).enqueue(new Callback() {? ? ? ? ? ? @Override public void onResponse(Callcall, Responseresponse) { data.setValue(response.body()); }? ? ? ? });? ? ? ? return data;? ? }}數(shù)據(jù)持久化現(xiàn)在, 在旋轉(zhuǎn)屏幕或是跳轉(zhuǎn)后返回時, UI視圖會實時刷新, 因為數(shù)據(jù)可以從內(nèi)存中直接讀取了. 但如果應(yīng)用被殺了還是一樣傻逼.用戶還需要進(jìn)行一次網(wǎng)絡(luò)請求, 經(jīng)歷一次網(wǎng)絡(luò)等待, 來獲取相同的數(shù)據(jù). 你可以簡單的緩存網(wǎng)絡(luò)請求, 但這又會產(chǎn)生新的問題.如果是同一個Pojo的其他請求, 比如獲取當(dāng)前用戶的好友列表. 邏輯上就生無可戀了. 所以你應(yīng)該把它們合并一下.我的意思是, 你應(yīng)該加上持久化層. 我們推薦RoomRoom 是一個簡化過的本地持久化的對象映射庫. 在編譯時, 丫會檢查所有的請求語句, 所以如果有問題會直接崩給你看. 丫內(nèi)部封裝了SQL細(xì)節(jié), 還能監(jiān)控數(shù)據(jù)庫中的數(shù)據(jù)變化, 并觸發(fā)LiveData的回調(diào). 這貨還附帶線程監(jiān)測, 不讓你在主線程干數(shù)據(jù)存儲的活.要使用Room, 先按規(guī)范刷一遍代碼, 加上注解. 第一步, Pojo類需要加上@Entity 來標(biāo)識數(shù)據(jù)表.@Entityclass User {? @PrimaryKey? private int id;? private String name;? private String lastName;? // getters and setters for fields}第二步, 創(chuàng)建數(shù)據(jù)庫類, 繼承?RoomDatabase@Database(entities = {User.class}, version = 1)public abstract class MyDatabase extends RoomDatabase { }要注意, 這貨是抽象的, Room會幫你提供實現(xiàn)類的.第三步, 創(chuàng)建DAO類, 提供數(shù)據(jù)操作函數(shù)@Daopublic interface UserDao {? ? @Insert(onConflict = REPLACE)? ? void save(User user);? ? @Query("SELECT * FROM user WHERE id = :userId")? ? LiveDataload(String userId);}要注意, load 方法返回的是LiveData. Room會在數(shù)據(jù)庫變更時自覺觸發(fā)觀察者回調(diào), 且只在至少還有一個觀察者活躍時更新數(shù)據(jù).最后, 在第二步里面引用第三步的對象@Database(entities = {User.class}, version = 1)public abstract class MyDatabase extends RoomDatabase {? ? public abstract UserDao userDao();}要注意, Room是根據(jù)數(shù)據(jù)表被修改觸發(fā)通知的, 所以如果發(fā)錯了通知, 這個鍋我不背.我們現(xiàn)在可以給UserRepository 加上Room的邏輯了.@Singletonpublic class UserRepository {? ? private final Webservice webservice;? ? private final UserDao userDao;? ? private final Executor executor;? ? @Inject? ? public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {? ? ? ? this.webservice = webservice;? ? ? ? this.userDao = userDao;? ? ? ? this.executor = executor;? ? }? ? public LiveDatagetUser(String userId) {? ? ? ? refreshUser(userId);? ? ? ? // return a LiveData directly from the database.? ? ? ? return userDao.load(userId);? ? }? ? private void refreshUser(final String userId) {? ? ? ? executor.execute(() -> {? ? ? ? ? ? // running in a background thread? ? ? ? ? ? // check if user was fetched recently? ? ? ? ? ? boolean userExists = userDao.hasUser(FRESH_TIMEOUT);? ? ? ? ? ? if (!userExists) {? ? ? ? ? ? ? ? // refresh the data? ? ? ? ? ? ? ? Response response = webservice.getUser(userId).execute();? ? ? ? ? ? ? ? // TODO check for error etc.? ? ? ? ? ? ? ? // Update the database.The LiveData will automatically refresh so? ? ? ? ? ? ? ? // we don't need to do anything else here besides updating the database? ? ? ? ? ? ? ? userDao.save(response.body());? ? ? ? ? ? }? ? ? ? });? ? }}要注意, 盡管我們改變了UserRepository 的數(shù)據(jù)源, 但UserProfileViewModel 和UserProfileFragment 并沒有受到影響. 大家說模塊分離吼木吼呀. 在測試UserProfileViewModel時, 我們也可以直接提供一個偽數(shù)據(jù)源來干活.現(xiàn)在, 這個Demo就已經(jīng)完結(jié)了. 用戶過幾天再登錄也能直接刷出頁面, 同時, 我們的數(shù)據(jù)倉庫也會依據(jù)更新邏輯更新數(shù)據(jù)(本例中使用FRESH_TIMEOUT 來判定, 現(xiàn)實項目里可以使用更復(fù)雜的邏輯). 因為LiveData是回調(diào)更新UI的, 所以數(shù)據(jù)更新之后會自動二次刷新頁面, 或者你也可以選擇在需要更新數(shù)據(jù)時, 不顯示數(shù)據(jù)庫中的過時數(shù)據(jù).在諸如下拉刷新之類的使用場景中, 通常需要提供說明網(wǎng)絡(luò)處理情況的進(jìn)度組件. 我們建議將UI與數(shù)據(jù)分離, 一個很重要的理由是, 這個進(jìn)度組件可能因為多種原因而被更新.比如獲取好友列表時, 可能會因為分頁觸發(fā)多次User數(shù)據(jù)更新. 而對于UI來說, 網(wǎng)絡(luò)進(jìn)度也只是一個數(shù)據(jù)節(jié)點, 你給我數(shù)據(jù)我展示就完了.這里也有兩種常規(guī)解決方案:讓getUser 的返回值攜帶網(wǎng)絡(luò)狀況的數(shù)據(jù).給數(shù)據(jù)倉庫添加一個返回數(shù)據(jù)刷新狀態(tài)的函數(shù). 如果你只想在特定場景顯示網(wǎng)絡(luò)進(jìn)度, 比如下拉刷新, 推薦這種解決方案.唯一標(biāo)準(zhǔn)數(shù)據(jù)源對于REST API來說, 不同的端點返回相同的數(shù)據(jù), 這種情況相當(dāng)普遍. 比如獲取用戶數(shù)據(jù), 和獲取好友列表, 返回的都是同一個User對象, 只是它們的粒度不同.所以如果我們直接顯示, 而不是先存入數(shù)據(jù)庫的話, 顯示出來的數(shù)據(jù)可能就亂套了.所以我們的數(shù)據(jù)庫就相當(dāng)于官方唯一指定旗艦數(shù)據(jù)源, 其他數(shù)據(jù)必須經(jīng)過數(shù)據(jù)庫統(tǒng)籌之后才能被傳入UI使用. 我們同樣也建議開發(fā)者指定一個數(shù)據(jù)總源.測試我們之前提到過, 這個框架的一個顯著優(yōu)點就是便于測試, 下面就來看看每個模塊的測試方式.User Interface & Interactions: 現(xiàn)在, 開發(fā)者只需要在這里使用?Android UI Instrumentation test. 而測試UI我們推薦使用Espresso. 依賴方面, 你只需要mock一個ViewModel, 就可以開始你的測試了.ViewModel: 這貨可以使用JUnit test來測試, 只需要mock一個UserRepository.UserRepository: 同樣使用JUnit test來測試, mock Webservice和Dao. 測試一下服務(wù)器調(diào)用, 數(shù)據(jù)存入數(shù)據(jù)庫, 以及數(shù)據(jù)過期時的處理. 因為這倆貨都是接口, 所以你可以直接提供偽實現(xiàn)做更深度的測試.UserDao: 這貨更推薦使用Instrumentation test來測試, 而且在不涉及UI的情況下仍可以保持相當(dāng)?shù)臏y試速度. 我們推薦在每個測試用例中, 創(chuàng)建一個只保留在內(nèi)存中的數(shù)據(jù)庫, 以避免外部影響.Webservice: 我們建議開發(fā)者在測試時盡量隔離外部環(huán)境, 所以請盡量不要在測這個模塊時連接你們的后端. 我們建議使用MockWebServer 來創(chuàng)建一個本地服務(wù)用于測試.Testing Artifacts 我們還提供了一個Maven artifact來管理后臺線程. 導(dǎo)入android.arch.core:core-testing, 這里有兩個JUnit Rules用于測試:InstantTaskExecutorRule: 在調(diào)用線程執(zhí)行指定操作.CountingTaskExecutorRule: 等待后臺操作, 或是作為idling resource扔進(jìn)Espresso架構(gòu)圖設(shè)計思想不同Activity或Fragment間的數(shù)據(jù)共享, 數(shù)據(jù)遠(yuǎn)程查詢與本地持久化.以下是我們的建議.Manifest中定義的程序入口, activities, services, broadcast receivers, etc, 并不是數(shù)據(jù)源, 它們的生命周期不可控, 它們只需要一部分?jǐn)?shù)據(jù)子集用于交互. 定義模塊的時候, 要堅定的遵循單一職能原則. 比如數(shù)據(jù)緩存和數(shù)據(jù)綁定, 請分成兩個模塊.模塊入口越少越好. 不要有我就只提供這個實現(xiàn)調(diào)用的想法, 畢竟出來混早晚要還的.在定義模塊間交互的時候, 先想想測試時能不能分別單獨測試. 比如封裝流弊的網(wǎng)絡(luò)請求框架可以讓你單獨測試本地數(shù)據(jù)持久化模塊. 如果滿地圖都是網(wǎng)絡(luò)請求, 你就傻逼了.App的核心在于與眾不同, 不要天天敲代碼就為了搭個模板.是數(shù)據(jù)就要持久化, 不要指望用戶的網(wǎng)絡(luò)和你測試時一樣好.指定一個標(biāo)準(zhǔn)數(shù)據(jù)源, 然后只跟丫交互.附錄: 網(wǎng)絡(luò)狀態(tài)處理前面的例子中, 為了簡化代碼, 我們省略了網(wǎng)絡(luò)異常處理. 在這里會展示如何用Resource 類來封裝數(shù)據(jù)和網(wǎng)絡(luò)狀態(tài).//a generic class that describes a data with a statuspublic class Resource{? ? @NonNull public final Status status;? ? @Nullable public final T data;? ? @Nullable public final String message;? ? private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {? ? ? ? this.status = status;? ? ? ? this.data = data;? ? ? ? this.message = message;? ? }? ? public staticResourcesuccess(@NonNull T data) { return new Resource<>(SUCCESS, data, null); }? ? public staticResourceerror(String msg, @Nullable T data) { return new Resource<>(ERROR, data, msg); }? ? public staticResourceloading(@Nullable T data) { return new Resource<>(LOADING, data, null); }}網(wǎng)絡(luò)加載本地顯示, 程序員基本上都要處理這種問題. 為了處理這個倒霉問題, 我們設(shè)計了NetworkBoundResource, 用于規(guī)范化處理. 下面是丫的決策樹.丫先綁定資源(Pojo), 然后開始觀察數(shù)據(jù)庫. 數(shù)據(jù)請求到來時, 丫判定現(xiàn)有數(shù)據(jù)是直接分發(fā)還是先請求后臺更新, 還是同時發(fā)生: 先展示本地數(shù)據(jù), 在網(wǎng)絡(luò)請求完成后進(jìn)行二次刷新.如果網(wǎng)絡(luò)請求成功, 更新數(shù)據(jù)庫, 手動觸發(fā)更新UI. 如果失敗, 發(fā)一個失敗通知出去.* 數(shù)據(jù)更新后, 手動觸發(fā)UI更新, 這個多數(shù)情況下沒什么卵用, 因為數(shù)據(jù)庫自己會這活. 但數(shù)據(jù)庫干的活有時候不太靠譜, 比如有時數(shù)據(jù)沒變化丫就不會觸發(fā)回調(diào)(這特么不是好事么, 不對, 網(wǎng)絡(luò)狀態(tài)似乎需要刷新). 網(wǎng)絡(luò)更新的數(shù)據(jù)原則上也不應(yīng)該直接拿來分發(fā), 因為違反了唯一標(biāo)準(zhǔn)數(shù)據(jù)源的原則(有可能數(shù)據(jù)在存入數(shù)據(jù)庫時, 觸發(fā)什么事務(wù)被更新了). 或是請求成功SUCCESS但是沒有新數(shù)據(jù)產(chǎn)生時, 可能會發(fā)送錯誤的通知.// ResultType: Type for the Resource data// RequestType: Type for the API responsepublic abstract class NetworkBoundResource{? ? // Called to save the result of the API response into the database? ? @WorkerThread? ? protected abstract void saveCallResult(@NonNull RequestType item);? ? // Called with the data in the database to decide whether it should be? ? // fetched from the network.? ? @MainThread? ? protected abstract boolean shouldFetch(@Nullable ResultType data);? ? // Called to get the cached data from the database? ? @NonNull @MainThread? ? protected abstract LiveDataloadFromDb();? ? // Called to create the API call.? ? @NonNull @MainThread? ? protected abstract LiveData> createCall();? ? // Called when the fetch fails. The child class may want to reset components? ? // like rate limiter.? ? @MainThread? ? protected void onFetchFailed() {? ? }? ? // returns a LiveData that represents the resource, implemented? ? // in the base class.? ? public final LiveData> getAsLiveData();}要注意, 上面這貨定義了兩個類型參數(shù)?(ResultType,?RequestType), 原因是API返回的數(shù)據(jù)可能跟本地使用的數(shù)據(jù)有區(qū)別.還要注意, 上面網(wǎng)絡(luò)請求用的是ApiResponse, 這玩意就是把Retrofit2.Call返回的響應(yīng)數(shù)據(jù)轉(zhuǎn)換成LiveData. 下面是NetworkBoundResource 的后續(xù)實現(xiàn).public abstract class NetworkBoundResource{? ? private final MediatorLiveData> result = new MediatorLiveData<>();? ? @MainThread? ? NetworkBoundResource() {? ? ? ? result.setValue(Resource.loading(null));? ? ? ? LiveDatadbSource = loadFromDb();? ? ? ? result.addSource(dbSource, data -> {? ? ? ? ? ? result.removeSource(dbSource);? ? ? ? ? ? if (shouldFetch(data)) fetchFromNetwork(dbSource);? ? ? ? ? ? else result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));? ? ? ? });? ? }? ? private void fetchFromNetwork(final LiveDatadbSource) {? ? ? ? LiveData> apiResponse = createCall();? ? ? ? // we re-attach dbSource as a new source,? ? ? ? // it will dispatch its latest value quickly? ? ? ? result.addSource(dbSource, newData -> result.setValue(Resource.loading(newData)));? ? ? ? result.addSource(apiResponse, response -> {? ? ? ? ? ? result.removeSource(apiResponse);? ? ? ? ? ? result.removeSource(dbSource);? ? ? ? ? ? //noinspection ConstantConditions? ? ? ? ? ? if (response.isSuccessful()) saveResultAndReInit(response);? ? ? ? ? ? else {? ? ? ? ? ? ? ? onFetchFailed();? ? ? ? ? ? ? ? result.addSource(dbSource, newData -> result.setValue( Resource.error(response.errorMessage, newData)));? ? ? ? ? ? }? ? ? ? });? ? }? ? @MainThread? ? private void saveResultAndReInit(ApiResponseresponse) {? ? ? ? new AsyncTask() {? ? ? ? ? ? @Override protected Void doInBackground(Void... voids) {? ? ? ? ? ? ? ? saveCallResult(response.body);? ? ? ? ? ? ? ? return null;? ? ? ? ? ? }? ? ? ? ? ? @Override protected void onPostExecute(Void aVoid) {? ? ? ? ? ? ? ? // we specially request a new live data,? ? ? ? ? ? ? ? // otherwise we will get immediately last cached value,? ? ? ? ? ? ? ? // which may not be updated with latest results received from network.? ? ? ? ? ? ? ? result.addSource(loadFromDb(), newData -> result.setValue(Resource.success(newData)));? ? ? ? ? ? }? ? ? ? }.execute();? ? }? ? public final LiveData> getAsLiveData() { return result; }}現(xiàn)在我們可以把這貨用在之前的Demo中了.class UserRepository {? ? Webservice webservice;? ? UserDao userDao;? ? public LiveData> loadUser(final String userId) {? ? ? ? return new NetworkBoundResource() {? ? ? ? ? ? @Override protected void saveCallResult(@NonNull User item) { userDao.insert(item); }? ? ? ? ? ? @Override protected boolean shouldFetch(@Nullable User data) { return rateLimiter.canFetch(userId) && (data == null || !isFresh(data)); }? ? ? ? ? ? @NonNull @Override protected LiveDataloadFromDb() { return userDao.load(userId); }? ? ? ? ? ? @NonNull @Override protected LiveData> createCall() { return webservice.getUser(userId); }? ? ? ? }.getAsLiveData();? ? }}引入項目添加Google Maven倉庫project build.gradle 添加高亮代碼allprojects {? ? repositories {? ? ? ? jcenter()? ? ? ? google()? ? }}app build.gradle 添加依賴, kotlin中, 需要引入 kapt & kotlin-apt添加主要依賴模塊, 包括 ?Lifecycles,?LiveData,?ViewModel,?Room, and?Paging. 以及測試模塊 testing Room migrations.dependencies {? ? // ViewModel and LiveData? ? implementation "android.arch.lifecycle:extensions:1.0.0"? ? annotationProcessor "android.arch.lifecycle:compiler:1.0.0" // 如果使用Java8, 把這一行替換成下面那個// Java8 support for Lifecycles? ? implementation "android.arch.lifecycle:common-java8:1.0.0"? ? // Room? ? implementation "android.arch.persistence.room:runtime:1.0.0"? ? annotationProcessor "android.arch.persistence.room:compiler:1.0.0"? ? // Paging? ? implementation "android.arch.paging:runtime:1.0.0-alpha4-1"? ? // Test helpers for LiveData? ? testImplementation "android.arch.core:core-testing:1.0.0"? ? // Test helpers for Room? ? testImplementation "android.arch.persistence.room:testing:1.0.0"http:// RxJava support for Room? ? implementation "android.arch.persistence.room:rxjava2:1.0.0" // 如果要使用響應(yīng)式編程庫, 可以引入這兩個庫? ? // ReactiveStreams support for LiveData? ? implementation "android.arch.lifecycle:reactivestreams:1.0.0"}生命周期生命周期處理組件用于專門處理Activity&Fragment的生命周期狀態(tài)改變事件, 丫可以使你的代碼具有更好的組織邏輯, 更輕量級, 也更容易維護(hù).之前, 常規(guī)處理方式是實現(xiàn)生命周期方法, 而這通常會造成代碼難以維護(hù)并產(chǎn)生不必要的異常. 現(xiàn)在, 你可以直接針對組件本身而非某個生命周期方法進(jìn)行處理.android.arch.lifecycle包允許開發(fā)者構(gòu)建無需處理生命周期問題的組件, 我們已經(jīng)替你處理好了.絕大多數(shù)Android自帶組件都有生命周期, 丫本身是由操作系統(tǒng)進(jìn)行管理的. 如果開發(fā)者不按我們的套路出牌, 由此產(chǎn)生的各種內(nèi)存泄漏和崩潰我們不背鍋.假設(shè)有一個Activity用于顯示當(dāng)前設(shè)備的位置信息. 常規(guī)代碼如下: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()?and?onStop()里面. 并且, Android并不保證onStart跑完才會跑onStop, 參考上面的代碼, 有可能你在listener.start() 方法中處理一些耗時操作, 比如檢查配置信息, 然后onStop觸發(fā)了, 操作就被中斷了. 從邏輯上看, LocationListener可能需要活的比Activity更久一點, 好歹榨干了再滾.?android.arch.lifecycle 能幫你處理這些問題.class MyActivity extends AppCompatActivity {? ? private MyLocationListener myLocationListener;? ? @Override 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();? ? }}LifecycleLifecycle, 這里指的是android.arch.lifecycle 庫中的一個類, 丫有兩個主要的枚舉(Event&State)用于保存組件生命周期狀態(tài)的信息, 開發(fā)者可以通過它觀察對應(yīng)組件的生命周期狀態(tài), Event 這貨對應(yīng)于我們常用的onCreate這些回調(diào)函數(shù). State 這貨對應(yīng)于Event事件完成后的組件狀態(tài), 比如created.這貨通過方法注解來監(jiān)視生命周期狀態(tài). 你可以調(diào)用Lifecycle類的addObserver()方法來添加一個觀察者, 如下:public class MyObserver implements LifecycleObserver {? ? @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)? ? public void connectListener() { ... }? ? @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)? ? public void disconnectListener() { ... }}myLifecycleOwner.getLifecycle().addObserver(new MyObserver());在這里, myLifecycleOwner對象實現(xiàn)了LifecycleOwner接口.LifecycleOwnerLifecycleOwner是一個單方法接口, 代表這貨有一個Lifecycle, 需要實現(xiàn)getLifecycle()方法. 如果你想管理整個App進(jìn)程的生命周期, 可以使用ProcessLifecycleOwner.丫從Fragment和AppCompatActivity中抽離出了生命周期邏輯, 并允許其他控件與之交互. 這貨可以由任意類來實現(xiàn).實現(xiàn)了LifecycleObserver的組件與實現(xiàn)了LifecycleOwner的組件可以無縫對接: 后者提供生命周期, 前者注冊并開始觀察.在下面的例子中, MyLocationListener實現(xiàn)了LifecycleObserver, 并用Lifecycle的onCreate()方法做初始化. 這樣, 響應(yīng)生命周期變化的邏輯就完整了. 把這個邏輯封入Listener而不是Activity使得代碼更方便管理.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(); } });? }}一個常規(guī)使用情境是, 在生命周期處于抑制狀態(tài)時, 不觸發(fā)監(jiān)聽. 比如, 如果回調(diào)中是一個FragmentTransaction, 那么如果在Activity狀態(tài)保存之后運行, 會使項目崩潰.為了簡化例子, Lifecycle類允許其他對象查詢當(dāng)前生命周期狀態(tài).class MyLocationListener implements LifecycleObserver {? ? private boolean enabled = false;? ? public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... }? ? public void enable() {? ? ? ? enabled = true;? ? ? ? if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected }? ? }? ? @OnLifecycleEvent(Lifecycle.Event.ON_START)? ? void start() { if (enabled) { // connect } }? ? @OnLifecycleEvent(Lifecycle.Event.ON_STOP)? ? void stop() { // disconnect if connected }}如此如此, LocationListener類就完全不懼生命周期問題了. 我們在外部調(diào)用丫時, 只需要初始化一下就好了. 其他的操作都已經(jīng)在內(nèi)部封裝好了.如果一個庫需要處理生命周期問題, 我們建議你使用這套生命周期組件. 這可以避免庫的使用者編寫額外的生命周期管理代碼.實現(xiàn)自定義LifecycleOwnerSupport Library 26.1.0 之后, Fragments和Activities默認(rèn)實現(xiàn)了LifecycleOwner接口.如果你想把一個自定義的類做成LifecycleOwner, 可以使用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;? ? }}Best PracticesUI Controller干的活越少越好. 丫不需要存儲數(shù)據(jù), 只需要定義數(shù)據(jù)變更后的邏輯, 然后直接找ViewModel要就好.改用數(shù)據(jù)驅(qū)動的UI, Controller只負(fù)責(zé)在數(shù)據(jù)變化時更新UI, 以及通知ViewModel數(shù)據(jù)變動.把數(shù)據(jù)邏輯放入ViewModel. 把這貨當(dāng)成UI Controller與其他部分的中間層. 要注意, 這貨不負(fù)責(zé)獲取數(shù)據(jù), 丫應(yīng)該把這個活委托給其他組件.使用Data Binding來保持View和Controller的簡潔. 推薦使用Butter Knife來簡化代碼.如果UI層比較復(fù)雜, 推薦加一個presenter層. 雖然有點麻煩, 但易于測試.避免在ViewModel中引用Context. 有引用就沒法回收了.Use Cases不同粒度的實時更新. 比如定位, 應(yīng)用在前臺時, 使用細(xì)粒度的更新, 在后臺時, 使用粗粒度的更新.?LiveData能保證在數(shù)據(jù)更新時實時變更UI.開始和停止數(shù)據(jù)緩存. 盡量早的開始視頻緩沖, 在App完全啟動后才開始播放, 在App銷毀時停止緩沖.開始和停止網(wǎng)絡(luò)連接. App在前臺時實時更新網(wǎng)絡(luò)數(shù)據(jù), 切換到后臺時暫停網(wǎng)絡(luò)更新.暫停和恢復(fù)動圖. 切換到后臺時暫停動圖, 切換到前臺時恢復(fù)動圖.Stop 事件的處理AppCompatActivity?or?Fragment的onSaveInstanceState()被觸發(fā)時, 會開始ON_STOP事件的分發(fā), 這個時候, UI被視為處于不可變動狀態(tài). 這也是為什么這時候運行FragmentTransaction會觸發(fā)異常的原因.LiveData會自覺避免這個問題的發(fā)生, 丫會先調(diào)用來isAtLeast()確認(rèn)一波.這里有一個問題, 就是AppCompatActivity的狀態(tài)保存時, 還沒有調(diào)用onStop()方法. 這個時間段里, 生命周期狀態(tài)還沒有被變更, 但UI是不可變動的. (也就是不能用State判定UI是否可變)Lifecycle的解決方式是無視onStop方法, 直接改變狀態(tài). 這種蛋疼的歷史遺留問題, 目前有兩個大坑, 目測幾年之內(nèi)都沒法解決, 不過基本上不影響使用. LiveData這貨通常應(yīng)該在onCreate方法中被綁定. 因為關(guān)聯(lián)操作只應(yīng)該進(jìn)行一次, 并且確保視圖在活躍后可以立即加載最新的數(shù)據(jù).這貨通常只會在數(shù)據(jù)改變時發(fā)送通知, 且給發(fā)送給處于活躍狀態(tài)的觀察者. 比較特殊的情況是, 觀察者被激活時, 也會收到通知. 而如果觀察者第二次被激活, 則會先判斷當(dāng)前值與丫保存的值是否一致.以下是如何綁定LiveData的示例:public class NameActivity extends AppCompatActivity {? ? private NameViewModel mModel;? ? @Override protected void onCreate(Bundle savedInstanceState) {? ? ? ? super.onCreate(savedInstanceState);? ? ? ? // Other code to setup the activity...? ? ? ? // Get the ViewModel.? ? ? ? mModel = ViewModelProviders.of(this).get(NameViewModel.class);? ? ? ? // Create the observer which updates the UI.? ? ? ? final ObservernameObserver = new Observer() {? ? ? ? ? ? @Override public void onChanged(@Nullable final String newName) {? ? ? ? ? ? ? ? // Update the UI, in this case, a TextView.? ? ? ? ? ? ? ? mNameTextView.setText(newName);? ? ? ? ? ? }? ? ? ? };? ? ? ? // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.? ? ? ? mModel.getCurrentName().observe(this, nameObserver);? ? }}observe(nameObserver) 方法被調(diào)用后, ?onChanged() 被立即觸發(fā), 返回最新的name(LiveData中的 mCurrentName). 如果LiveData中本身沒有數(shù)據(jù), 就不會觸發(fā)onChanged.LiveData數(shù)據(jù)更新這貨本身是只讀的, 如果有更新數(shù)據(jù)的需求, 請用MutableLiveData , 這廝有?setValue(T)?和?postValue(T) 方法用于寫入數(shù)據(jù). 標(biāo)準(zhǔn)架構(gòu)是ViewModel 持有MutableLiveData 的引用負(fù)責(zé)讀寫, 而向觀察者提供的是 只讀的LiveData .架子搭起來之后, 就可以實現(xiàn)點擊一個按鈕, 觸發(fā)數(shù)據(jù)更新, 更新所有關(guān)聯(lián)的UI.mButton.setOnClickListener(new OnClickListener() {? ? @Override public void onClick(View v) {? ? ? ? String anotherName = "John Doe";? ? ? ? mModel.getCurrentName().setValue(anotherName);? ? }});點擊事件調(diào)用setValue(T) 方法, 所有觀察者的onChanged(John Doe) 方法被觸發(fā). setValue()[用于主線程調(diào)用]和 postValue()[用于其他線程調(diào)用]其他的應(yīng)用場景還包括: 網(wǎng)絡(luò)請求的響應(yīng), 數(shù)據(jù)庫讀取完成, 等等.RoomRoom 是個持久化庫, 可以在查詢后返回LiveData包裝的數(shù)據(jù), 查詢操作本身是用DAO實現(xiàn)的.這貨實現(xiàn)了在數(shù)據(jù)庫異步查詢和更新時同步更新LiveData 對象, 相關(guān)文檔 ?Room persistent library guide.LiveData擴(kuò)展這貨在STARTED?和?RESUMED 狀態(tài)下被視為是活躍的. 以下是擴(kuò)展代碼:public class StockLiveData extends LiveData{? ? private StockManager mStockManager;? ? private SimplePriceListener mListener = new SimplePriceListener() {? ? ? ? @Override public void onPriceChanged(BigDecimal price) { setValue(price); }? ? };? ? public StockLiveData(String symbol) { mStockManager = new StockManager(symbol); }? ? @Override protected void onActive() { mStockManager.requestPriceUpdates(mListener); }? ? @Override protected void onInactive() { mStockManager.removeUpdates(mListener); }}?onActive() 方法在LiveData 有一個以上的觀察者時被調(diào)用, 表明開始觀察股票價格.?onInactive() 方法在LiveData 所有觀察者都傻逼了之后被調(diào)用, 表明可以和StockManager 說拜拜了.?setValue(T) 方法在LiveData 數(shù)據(jù)更新時被調(diào)用, 通知觀察者該干活了.StockLiveData 的使用方法如下:public class MyFragment extends Fragment {? ? @Override public void onActivityCreated(Bundle savedInstanceState) {? ? ? ? super.onActivityCreated(savedInstanceState);? ? ? ? LiveDatamyPriceListener = ...;? ? ? ? myPriceListener.observe(this, price -> { // Update the UI. });? ? }}?observe()?中的 this, 也就是當(dāng)前Fragment, 是個LifecycleOwner 實例, 也就是說, 這個對象的生命周期已經(jīng)被納入考察范圍了.這意味著, 如果該對象不活躍了, 就不能接收通知了. 如果該對象被銷毀了, 這個觀察者也就被移除了.這進(jìn)一步意味著, LiveData 可以被設(shè)計成一個單例, 被不同的視圖一起使用. 比如:public class StockLiveData extends LiveData{? ? private static StockLiveData sInstance;? ? private StockManager mStockManager;? ? private SimplePriceListener mListener = new SimplePriceListener() {? ? ? ? @Override public void onPriceChanged(BigDecimal price) {? ? ? ? ? ? setValue(price);? ? ? ? }? ? };? ? @MainThread public static StockLiveData get(String symbol) {? ? ? ? if (sInstance == null) { sInstance = new StockLiveData(symbol); }? ? ? ? return sInstance;? ? }? ? private StockLiveData(String symbol) { mStockManager = new StockManager(symbol); }? ? @Override protected void onActive() { mStockManager.requestPriceUpdates(mListener); }? ? @Override protected void onInactive() { mStockManager.removeUpdates(mListener); }}使用方式如下:public class MyFragment extends Fragment {? ? @Override public void onActivityCreated(Bundle savedInstanceState) {? ? ? ? StockLiveData.get(getActivity()).observe(this, price -> { // Update the UI. });? ? }}這樣, MyPriceListener 就可以被多個視圖觀察到, 且只有在至少一個觀察者處于活躍狀態(tài)時才處理事務(wù).LiveData 的變形開發(fā)者可能存在以下的需求: 改變LiveData 中的值, 但不希望被發(fā)送給觀察者. 或是希望返回一個基于其他LiveData 對象而有所調(diào)整的值.這就引出了 Lifecycle 包下的 Transformations 類.Transformations.map(source, func) map方法, 將數(shù)據(jù)源轉(zhuǎn)換后傳遞給觀察者.LiveDatauserLiveData = ...;LiveDatauserName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });Transformations.switchMap(trigger, func)與map方法類似, 不同的是, 這貨的func 返回的是一個LiveData 對象.private LiveDatagetUser(String id) { ...; }LiveDatauserId = ...;LiveDatauser = Transformations.switchMap(userId, id -> getUser(id) );變形后的數(shù)據(jù)可以略過生命周期檢測, 在有觀察者關(guān)注變形后的數(shù)據(jù)之前, 整個變形都不會被觸發(fā). 正是由于這貨的延遲計算的特性, 與生命周期相關(guān)的行為如果需要在?ViewModel 對象中存儲一個Lifecycle 對象, 這貨將是一個合適的解決方案. 比如, 一個UI組件需要接收地址輸入, 返回該地址的郵政編碼.class MyViewModel extends ViewModel {? ? private final PostalCodeRepository repository;? ? public MyViewModel(PostalCodeRepository repository) { this.repository = repository; }? ? private LiveDatagetPostalCode(String address) {? ? ? ?// DON'T DO THIS? ? ? ?return repository.getPostCode(address);? ? }}如果這么寫的話, 每次調(diào)用getPostalCode() 方法時, 都會重新創(chuàng)建一個LiveData 對象, 丫還需要注銷之前的然后關(guān)注新創(chuàng)建的LiveData.如果UI組件被銷毀重建了, 也會觸發(fā)getPostalCode() 方法, 使之前的LiveData 被廢棄.所以開發(fā)者應(yīng)該轉(zhuǎn)而使用變形作為解決方案, 如下.class MyViewModel extends ViewModel {? ? private final PostalCodeRepository repository;? ? private final MutableLiveDataaddressInput = new MutableLiveData();? ? public final LiveDatapostalCode = Transformations.switchMap(addressInput, (address) -> { return repository.getPostCode(address); });? public MyViewModel(PostalCodeRepository repository) { this.repository = repository }? private void setInput(String address) { addressInput.setValue(address); }}這里的postalCode就是public?final的, 意味著丫可以直接讀取且不做變化. 丫被定義成是addressInput的變種, 會隨著repository.getPostCode()被調(diào)用而變化.丫只在有活躍的觀察者時被觸發(fā), 否則在引入新的觀察者之前, 不會進(jìn)行計算.這個機(jī)制在底層上實現(xiàn)了延遲計算. 使得ViewModel對象可以直接實現(xiàn)對LiveData對象的引用并且變形.創(chuàng)建新的變形各種亂七八糟的變形方法并不是默認(rèn)提供的. 要實現(xiàn)自定義變形方法, 你需要使用MediatorLiveData類, 監(jiān)聽其他的LiveData類, 并處理它們發(fā)出的事件. MediatorLiveData會將它的狀態(tài)傳遞給數(shù)據(jù)源對象, 詳細(xì)信息可以戳這里Transformations.合并多個數(shù)據(jù)源MediatorLiveData是LiveData的子類, 用于合并多個數(shù)據(jù)源. 這貨的觀察者們會在其中任意一個數(shù)據(jù)源變動時收到通知.比如UI里有一個MediatorLiveData對象, 丫有兩個數(shù)據(jù)源, 一個本地數(shù)據(jù)庫LiveData, 一個網(wǎng)絡(luò)LiveData.那么, 只要任意一個數(shù)據(jù)源有變動, 都會觸發(fā)UI更新. 具體例子戳這里: Addendum: exposing network status. ViewModel這貨被設(shè)計為存儲并管理UI相關(guān)的數(shù)據(jù), 以及處理生命周期問題. 丫保證數(shù)據(jù)可以不受諸如屏幕旋轉(zhuǎn)之類的配置信息變更的影響.Activity & Fragment 這些 UI Controller, 在處理生命周期問題時, 并不受開發(fā)者的控制. 所以如果操作系統(tǒng)決定要銷毀或重啟一個頁面時, 存入其中的數(shù)據(jù)都會被清除. 少量數(shù)據(jù)可以通過onSaveInstanceState()方法存儲并在?onCreate()中使用Bundle恢復(fù), 但數(shù)據(jù)量上去之后就傻逼了.另一個問題是, 項目里經(jīng)常需要進(jìn)行異步調(diào)用, UI Controller需要管理這些異步調(diào)用, 并且在銷毀時清理這些調(diào)用, 以防止可能的內(nèi)存泄漏. 這特么就惡心了.而且在諸如頁面重啟動的時候, 你可能需要終止并重新開始一個調(diào)用, 而這通常是不必要的.UI Controller的設(shè)計本意是用來展示數(shù)據(jù), 響應(yīng)用戶操作, 與操作系統(tǒng)交互(比如權(quán)限請求). 加載數(shù)據(jù)本身就不該是丫干的活, 要不你大可以一個Activity寫完所有邏輯代碼.Implement a ViewModel架構(gòu)組件提供了幫助類ViewModel來為UI提供數(shù)據(jù). 這貨會在配置信息變更時自動保存數(shù)據(jù), 并在頁面重新可用時直接加載. 比如假設(shè)你需要展示用戶列表:public class MyViewModel extends ViewModel {? ? private MutableLiveData> users;? ? public LiveData> getUsers() {? ? ? ? if (users == null) {? ? ? ? ? ? users = new MutableLiveData>();? ? ? ? ? ? loadUsers();? ? ? ? }? ? ? ? return users;? ? }? ? private void loadUsers() {? ? ? ? // Do an asyncronous operation to fetch users.? ? }}然后在Activity里這么用:public class MyActivity extends AppCompatActivity {? ? public void onCreate(Bundle savedInstanceState) {? ? ? ? // Create a ViewModel the first time the system calls an activity's onCreate() method.? ? ? ? // Re-created activities receive the same MyViewModel instance created by the first activity.? ? ? ? MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);? ? ? ? model.getUsers().observe(this, users -> {? ? ? ? ? ? // update UI? ? ? ? });? ? }}那么Activity重啟時, 會收到之前的MyViewModel實例. 徹底銷毀時, 會調(diào)用MyViewModel的onCleared()方法清理資源.* 注意, ViewModel萬萬不要持有View, Lifecycle, 或加了中間層的Context引用. ViewModel被設(shè)計為凌駕于生命周期持有者之上的存在, 這使得丫更容易測試. 丫可以持有生命周期觀察者的實例, 比如LiveData. 但丫本身不應(yīng)該關(guān)注生命周期變化. 如果ViewModel中的業(yè)務(wù)邏輯需要Context, 比如調(diào)用系統(tǒng)服務(wù), 你可以繼承AndroidViewModel類, 在構(gòu)造方法中傳入Application.ViewModel 的生命周期這貨在創(chuàng)建時, 需要指定Lifecycle對象并傳入ViewModelProvider. 在這個玩意進(jìn)墳之后, 丫就躺著等死了. 這個Lifecycle如果是個Activity, 就死在finish(), 如果是個Fragment, 就死在detach().下圖講的是一個Activity在手機(jī)旋轉(zhuǎn)和自殺時的生命周期變化情況, 以及相關(guān)聯(lián)的ViewModel的存活狀態(tài). 換成Fragment也是這個套路.ViewModel通常會在系統(tǒng)第一次調(diào)用onCreate()時被獲取. 之后會一直存在, 直到finish()Fragments 之間的數(shù)據(jù)共享一個Activity鐘的多個Fragment之間互相交互是個很常規(guī)的使用情境. 比如你有一個Fragment提供選擇列表, 另一個Fragment展示選中項的詳細(xì)內(nèi)容. 你不僅要處理各自的業(yè)務(wù)邏輯, 還需要在Activity中將兩者進(jìn)行綁定, 還有另一個Fragment未初始化之類的容錯處理.ViewModel可以拯救你, 只需要讓他倆共享一個ViewModel, 就妥了.public class SharedViewModel extends ViewModel {? ? private final MutableLiveDataselected = new MutableLiveData(); ? ? public void select(Item item) { selected.setValue(item); } ? ? public LiveDatagetSelected() { 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.? ? ? ? });? ? }}這倆貨在調(diào)用ViewModelProvider時, 都是通過getActivity()來獲取實例的, 這使得他們獲取的是同一個, 作用于當(dāng)前Activity的SharedViewModel實例. 這么做的好處如下:Activity中不需要處理任何事情.Fragment之間不需要直接交互. 不受另一個Fragment的生命周期影響, 互相取代也不會造成任何問題.ViewModel 取代 Loaders加載類, 比如CursorLoader, 常用于保持UI與數(shù)據(jù)庫的數(shù)據(jù)一致性. 丫現(xiàn)在已經(jīng)可以退休了. ViewModel可以將UI從數(shù)據(jù)加載操作中分離出來, 減少類之間的強(qiáng)引用.以前, 一個常見的使用情境是, 使用CursorLoader來觀察數(shù)據(jù)庫. 數(shù)據(jù)庫內(nèi)容變更時, Loader自動觸發(fā)重新加載方法, 更新UI, 如下: ViewModel使用Room和LiveData來達(dá)成同樣的效果. ViewModel保證數(shù)據(jù)不受頁面生命周期影響, Room用于在數(shù)據(jù)庫變動時通知LiveData, 而LiveData負(fù)責(zé)更新UI.這里有一篇博客, 講述了如何用這套邏輯替換AsyncTaskLoader. 要看的話戳這里: Blog.重復(fù)一遍, ViewModel是用來保護(hù)數(shù)據(jù)免受生命周期影響的. Saving UI StatesUI狀態(tài)的保存和恢復(fù), 是一個相當(dāng)影響用戶體驗的活兒. 旋轉(zhuǎn)設(shè)備, 重啟App, 后臺被殺時, 你都應(yīng)該將應(yīng)用恢復(fù)到用戶期望的頁面狀態(tài).對于簡單的, 輕量化的數(shù)據(jù), 通常會使用onSaveInstanceState()來保存.對于復(fù)雜的數(shù)據(jù), 你可以使用ViewModel, onSaveInstanceState(), 與本地持久化相結(jié)合的方式來保存.本篇的主題就是這三者.簡單難度: onSaveInstanceState()onSaveInstanceState()方法的設(shè)計初衷就是保存少量的數(shù)據(jù), 用于重新加載頁面. 丫有兩個處理場景:App處于后臺時, 由于內(nèi)存原因而被終結(jié). 配置信息變更, 比如屏幕旋轉(zhuǎn).兩種情形下, Activity都處于Stopped狀態(tài), 而非Finished. 比如, 用戶幾個小時沒開你的應(yīng)用, 后臺已經(jīng)殺死了你的進(jìn)程, 系統(tǒng)會調(diào)用onSaveInstanceState()的默認(rèn)實現(xiàn), 前提是你的UI Controller擁有一個ID. 然后用戶打開你的應(yīng)用, 系統(tǒng)會恢復(fù)之前保存的狀態(tài).* 注意, 如果用戶主動殺死你的應(yīng)用, 或者Activity的finish()方法被調(diào)用, 丫就是廢的.系統(tǒng)會自動保存很多UI數(shù)據(jù), 比如EditText中已輸入的文本, 或是ListView滾動條的位置. 你也可以重寫該方法以手動保存數(shù)據(jù). 請不要用這貨來保存大容量的數(shù)據(jù), 比如Bitmap, 或是能讓系統(tǒng)原地讀秒的序列化數(shù)據(jù)結(jié)構(gòu). 因為序列化會消耗大量的系統(tǒng)內(nèi)存, 而這個操作是在主線程干的, 所以會卡到用戶懷疑人生.管理復(fù)雜狀態(tài): divide and conquerDivide, 如果數(shù)據(jù)結(jié)構(gòu)復(fù)雜, 你可以試著將保存工作拆分, 然后使用不同的保存機(jī)制實現(xiàn). 用戶通常會在兩種情況下離開一個Activity, 并期待對應(yīng)的結(jié)果:用戶完全關(guān)閉這個Activity. 比如, 最近打開項目中滑掉, 項目中返回上一級. 這種情況下, 用戶期望再次打開時, 該Activity處于初始狀態(tài).用戶旋轉(zhuǎn)了屏幕或是從后臺進(jìn)程中返回. 比如, 用戶在用搜索功能, 然后按下了Home鍵或是接聽了一個電話. 然后用戶返回搜索頁面, 并期望之前的搜索關(guān)鍵字和結(jié)果列表仍然健在.要實現(xiàn)上述情況下的數(shù)據(jù)保存, 請同時使用本地持久化, ViewModel類, 以及onSaveInstanceState()方法. 他們仨用于存儲不同復(fù)雜度的數(shù)據(jù).本地持久化: 在打開或關(guān)閉Activity時, 存儲不應(yīng)該被丟棄的數(shù)據(jù). 比如, 歌曲對象的列表, 包括音頻文件和元數(shù)據(jù).ViewModel類: 在內(nèi)存中存儲用于展示UI的數(shù)據(jù). 比如, 最近搜索的歌曲對象列表, 以及最近的搜索語句(Search Query, 關(guān)鍵字).onSaveInstanceState()方法: 存儲少量數(shù)據(jù), 用于在系統(tǒng)重新創(chuàng)建Activity時, 簡單初始化UI.對于復(fù)雜的對象, 請存入本地數(shù)據(jù)庫, 然后用本方法存儲對應(yīng)的數(shù)據(jù)ID. 比如, 存儲最近的搜索語句.比如一個用于搜索歌曲的Activity. 處理方式大致如下:用戶添加歌曲時, ViewModel通過代理實現(xiàn)本地數(shù)據(jù)持久化. 如果這首新增的歌曲需要在UI中展示出來, 你還需要更新ViewModel對象. 切記不要在主線程進(jìn)行數(shù)據(jù)庫操作.用戶搜索歌曲時, 從數(shù)據(jù)庫讀取的歌曲的復(fù)雜數(shù)據(jù)需要直接存入ViewModel對象. 搜索語句也需要存入ViewModel對象.Activity被切入后臺時, 系統(tǒng)調(diào)用onSaveInstanceState()方法. 你應(yīng)該在此存入搜索語句. 這貨夠輕量, 也足夠載入所有需要的數(shù)據(jù).恢復(fù)復(fù)雜狀態(tài): 組裝數(shù)據(jù)片段用戶返回Activity時, 有兩種重新創(chuàng)建的常見情境:Activity已被系統(tǒng)關(guān)閉. onSaveInstanceState()方法中保存了搜索語句, 將語句傳入ViewModel對象. ViewModel發(fā)現(xiàn)木有查詢結(jié)果的緩存, 然后呼喚代理開始干活.配置信息變更, ViewModel對象發(fā)現(xiàn)有緩存, 直接更新UI.* 注意, Activity第一次初始化時, 木有搜索語句, 在創(chuàng)建ViewModel的時候, 傳入空語句來告訴丫保持無數(shù)據(jù)狀態(tài).基于你自己的Activity實現(xiàn), 你可能根本不需要onSaveInstanceState()方法. 比如, 瀏覽器會在退出前完成跳轉(zhuǎn), 所以丫更應(yīng)該直接進(jìn)行本地持久化. 之前的例子中, 意味著存入Shared Preferences.如果是從Intent打開一個Activity, extras bundle會自覺實現(xiàn)本地持久化, 不受那兩種情況影響. 如果查詢語句是這么來的, 你也不需要onSaveInstanceState()方法了.但ViewModel這貨還是必須的, 丫可以省去不必要的數(shù)據(jù)重加載. Room Persistence Library這貨就是一個管理SQLite的抽象層, 丫會在你的App運行時, 創(chuàng)建相關(guān)數(shù)據(jù)的緩存. 這個緩存, 就是之前提到的標(biāo)準(zhǔn)數(shù)據(jù)源, 使UI層在獲取數(shù)據(jù)時無需關(guān)注是否有網(wǎng)絡(luò)連接. 查看更多: Room. Paging Library分頁嘛, 誰用誰知道.Overview很多應(yīng)用都需要分批次展示大量的數(shù)據(jù), 如果玩不明白, 會影響程序的性能表現(xiàn), 還可能浪費流量.現(xiàn)有的用于分頁的Android API, 都是垃圾.CursorAdapter可以將映射數(shù)據(jù)庫查詢結(jié)果映射到ListView的items, Cursor本身就慢成狗, 這貨還腦殘的只知道跑UI線程. 全套槽點在此: ?Large Database Queries on Android.AsyncListUtil可以在將基于位置(position-based)的數(shù)據(jù)分頁后扔進(jìn)RecyclerView, 但丫也只能基于位置, 并且在可計數(shù)(Countable)數(shù)據(jù)集中, 強(qiáng)制使用null作為占位符.我們這個產(chǎn)品就解決了這些問題. 我們用了幾個專門的類來簡化數(shù)據(jù)請求的過程. 并且與Room無縫銜接.Classes這貨有下面這些類, 附加功能請參考支持庫的Api.DataSource.用于定義要獲取分頁數(shù)據(jù)的數(shù)據(jù)源. 你可以基于業(yè)務(wù)邏輯選擇丫的子類.PageKeyedDataSource用于獲取下一頁(Page). 比如你在瀏覽社交媒體, 你可能會在網(wǎng)絡(luò)請求時, 基于當(dāng)前頁, 發(fā)送請求下一頁的Token.ItemKeyedDataSource用于獲取下一項(Item). 比如評論類的App, 大眾點評之流, 你可能會基于當(dāng)前評論的ID, 獲取下一條評論.PositionalDataSource用于基于任意位置獲取分頁數(shù)據(jù). 比如, 從ID-1200開始, 拿20條數(shù)據(jù)給我.如果你是配套使用Room的話, 丫會自動生成?DataSource.Factory來作為PositionalDataSources使用. 比如: @Query("select * from users WHERE age > :age order by name DESC, id ASC")DataSource.FactoryusersOlderThan(int age);PagedList.這貨負(fù)責(zé)從上面那貨(DataSource)中讀取數(shù)據(jù). 你可以設(shè)置一次讀取多少數(shù)據(jù), 預(yù)讀多少數(shù)據(jù), 以盡量減少用戶的等待時間. 這個類會向其他類發(fā)送數(shù)據(jù)更新的信號, 比如RecyclerView.Adapter, 以更新其內(nèi)容.PagedListAdapter.這貨是RecyclerView.Adapter接口的一個實現(xiàn), 為?PagedList提供數(shù)據(jù). 比如, 一個新分頁被加載時, 這貨通知RecyclerView新數(shù)據(jù)來了, 然后RecyclerView更新items, 并觸發(fā)相關(guān)動畫.PagedListAdapter會啟用一個后臺線程來計算PagedList變更時的更新計數(shù), 比如, 數(shù)據(jù)庫更新時生成的新數(shù)據(jù)頁, 并調(diào)用對應(yīng)的notifyItem…()方法以更新列表內(nèi)容, 然后RecyclerView做出相應(yīng)的變動.比如, 一個item在?PagedList變更時改變了位置, RecyclerView會觸發(fā)item移動的動畫.LivePagedListBuilder.這貨會用你提供的DataSource.Factory生成一個LiveData對象. 并且, 如果你是配套使用Room的話, DAO會自動生成DataSource.Factory來作為PositionalDataSource使用, 比如:@Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC")public abstract LivePagedListProviderusersOlderThan(int age);這個整形的參數(shù)告訴用戶使用PositionalDataSource來進(jìn)行基于位置的數(shù)據(jù)加載. (目測丫說的是那個泛型…)這些類加在一起, 組成了整個數(shù)據(jù)流, 后臺線程提供數(shù)據(jù), 主線程更新UI. 比如, 向數(shù)據(jù)庫插入一個新數(shù)據(jù)時, DataSource被凍結(jié), LiveData會在后臺線程中生產(chǎn)出一個新的PagedList.Figure: 大部分的工作都在后臺線程完成, 不會阻塞UI線程.* 使用流程看這里新生成的PagedList會被發(fā)送給主線程的PagedListAdapter. PagedListAdapter會隨后使用后臺的DiffUtil來計算需要變更的item的計數(shù). 比對完成時, PagedListAdapter會利用得到的信息調(diào)用?RecyclerView.Adapter.notifyItemInserted()方法, 標(biāo)記當(dāng)前操作是插入一個新的item.UI線程的RecyclerView就知道丫只需要綁定一個新的item就完了, 然后開始播放插入動畫.Database Sample以下是完整示例代碼. 展示了用戶添加, 移除, 以及變更數(shù)據(jù)庫數(shù)據(jù)時, RecyclerView內(nèi)容會自動更新.@Daointerface UserDao {? ? @Query("SELECT * FROM user ORDER BY lastName ASC")? ? public abstract DataSource.FactoryusersByLastName();}class MyViewModel extends ViewModel {? ? public final LiveData> usersList;? ? public MyViewModel(UserDao userDao) {? ? ? ? usersList = new LivePagedListBuilder<>(? ? ? ? ? ? ? ? userDao.usersByLastName(), /* page size */ 20).build();? ? }}class MyActivity extends AppCompatActivity {? ? @Override? ? public void onCreate(Bundle savedState) {? ? ? ? super.onCreate(savedState);? ? ? ? MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);? ? ? ? RecyclerView recyclerView = findViewById(R.id.user_list);? ? ? ? UserAdapteradapter = new UserAdapter();? ? ? ? viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));? ? ? ? recyclerView.setAdapter(adapter);? ? }}class UserAdapter extends PagedListAdapter{? ? public UserAdapter() {? ? ? ? super(DIFF_CALLBACK);? ? }? ? @Override? ? public void onBindViewHolder(UserViewHolder holder, int position) {? ? ? ? User user = getItem(position);? ? ? ? if (user != null) {? ? ? ? ? ? holder.bindTo(user);? ? ? ? } else {? ? ? ? ? ? // Null defines a placeholder item - PagedListAdapter will automatically invalidate? ? ? ? ? ? // this row when the actual object is loaded from the database? ? ? ? ? ? holder.clear();? ? ? ? }? ? }? ? public static final DiffCallbackDIFF_CALLBACK = new DiffCallback() {? ? ? ? @Override? ? ? ? public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {? ? ? ? ? ? // User properties may have changed if reloaded from the DB, but ID is fixed? ? ? ? ? ? return oldUser.getId() == newUser.getId();? ? ? ? }? ? ? ? @Override? ? ? ? public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {? ? ? ? ? ? // NOTE: if you use equals, your object must properly override Object#equals()? ? ? ? ? ? // Incorrectly returning false here will result in too many animations.? ? ? ? ? ? return oldUser.equals(newUser);? ? ? ? }? ? }}Loading Data數(shù)據(jù)來源通常有兩種情況: 網(wǎng)絡(luò)與數(shù)據(jù)庫, 其一, 或是合在一起. 兩種情況下的, 用Retrofit來實現(xiàn)處理下拉刷新, 網(wǎng)絡(luò)異常, 網(wǎng)絡(luò)重試的示例, 統(tǒng)一戳這里: PagingWithNetworkSample.如果是單一數(shù)據(jù)源, 網(wǎng)絡(luò) or 數(shù)據(jù)庫, 可以使用LiveData給UI喂數(shù)據(jù), 做法是: 指定數(shù)據(jù)源, 向LivePagedListBuilder傳入DataSource.Factory.
Figure 2.?Single source of data provides DataSource.Factory to load content.
數(shù)據(jù)庫在被觀察時, 若有數(shù)據(jù)變動發(fā)生, 會'push'一個新的PagedList出來.
假設(shè)是當(dāng)前數(shù)據(jù)源是網(wǎng)絡(luò), 后臺沒有發(fā)出更新通知, 諸如下拉刷新之類的信號會凍結(jié)當(dāng)前數(shù)據(jù)源, 并'pull'一個新的PagedList出來. 這會異步更新所有數(shù)據(jù).
如果是多數(shù)據(jù)源, 網(wǎng)絡(luò) and 數(shù)據(jù)庫, 你可以使用本地存儲的分頁數(shù)據(jù), 然后本地存儲默默的從網(wǎng)絡(luò)獲取新的分頁數(shù)據(jù).
這可以減輕網(wǎng)絡(luò)負(fù)載, 并在渣網(wǎng)速時提供更好的用戶體驗, 畢竟在數(shù)據(jù)庫后臺和用戶中間加了一層緩沖.
Figure 3.?Database is cache of network data - UI loads data from Database, and sends signals when out of data to load from network to database.
看圖說話, 數(shù)據(jù)邊界的回調(diào)方法(BoundaryCallback), 用來觸發(fā)網(wǎng)絡(luò)請求, 并將響應(yīng)的數(shù)據(jù)直接存入數(shù)據(jù)庫. 然后UI會關(guān)注數(shù)據(jù)庫, 在更新時通知所有的UI觀察者.
Room Persistence Library
需要幫助的話, 請到Stack Overflow, 并使用對應(yīng)的問題標(biāo)簽:
android-arch, 基礎(chǔ)庫相關(guān)的問題
android-room, 數(shù)據(jù)庫相關(guān)的問題
android-arch-lifecycle, 生命周期庫相關(guān)的問題
也歡迎到?G+ community來提問題.
要報告Bug的話, 先戳這里讀一下: https://developer.android.com/topic/libraries/architecture/feedback.html#issues.