淺談移動架構
幾十億的裝置都在用 Android 系統,從高階手機到飛機上的影音娛樂系統,應有盡有,不一而足。而 Android OS 則為這幾十億的裝置保駕護航,高效管理資源,保障執行流暢,較高的相容性, 然而有時候卻增加了開發卓越 App 的難度。 我一直堅持“框架能解決的事 最好從源頭解決”,團隊每位同事的開發習慣與技術儲備都不一樣,在生命週期與邏輯控制都截然不同,元件為空,缺少判斷,記憶體洩漏不關心,資料庫連線不釋放,view的命名千差萬別,mvc邏輯冗餘,mvp介面太多,資料重新整理與ui重新整理不同步, 怎樣做到獨一無二的體驗呢?怎麼做到行業的標杆(微信)呢? 這是我一直深思的問題! 當你看到這篇部落格的同時,想必你也同樣困惱許久,繼續看,這篇部落格可能改變你的思維方式:iCourt_Android_MVVM; 移動端mvc->mvp->mvvm,不斷演變,我們推陳出新,適應時代,完善了這款mvvm框架, 關於這款框架,我有三個詞來總結:"樣板","生命週期","響應式mvvm", 為什麼這麼說呢?以前我們在重新整理控制的時候, 會寫很多很多程式碼:比如:
public void getData(final boolean isRefresh) { //資料為空 展示載入佈局 if (isRefresh && approveAdapter.isDataEmpty()) { alphaStateLayout.setViewState(ViewState.VIEW_STATE_LOADING); } RetrofitServiceFactory .getApproveApiService().approvesQueryMyApplyObservable(0, 20, 1) .map(new ResEntitySimpleFunction<ItemPageEntity<ApproveEntity>>()) .compose(this.<ItemPageEntity<ApproveEntity>>bindToLifecycle()) .observeOn(AndroidSchedulers.mainThread())//不習慣寫 資料流在主執行緒接收->引發UI崩潰 .subscribe(new Consumer<ItemPageEntity<ApproveEntity>>() { @Override public void accept(ItemPageEntity<ApproveEntity> approveEntityItemPageEntity) throws Exception { approveAdapter.bindData(isRefresh, approveEntityItemPageEntity.items); refreshLayout.finishRefresh(); refreshLayout.finishLoadmore(); alphaStateLayout.setViewState(approveAdapter.isDataEmpty() ? ViewState.VIEW_STATE_EMPTY : ViewState.VIEW_STATE_CONTENT); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { //不習慣寫 錯誤ui接收 refreshLayout.finishRefresh(); refreshLayout.finishLoadmore(); alphaStateLayout.setViewState(approveAdapter.isDataEmpty() ? ViewState.VIEW_STATE_ERROR : ViewState.VIEW_STATE_CONTENT); } }); }
上面的程式碼 邏輯看著複雜 團隊成員會引發很多問題:
1. 不繫結生命週期(非同步請求),記憶體洩漏,引發崩潰,記憶體佔用高,卡頓...
2. 忘記在主執行緒接收資料流來更新ui(引發崩潰)
3. 忘記寫錯誤ui展示與toast提示(互動不完善)
4. 網路邏輯與ui繫結得太厲害了(是展示loading佈局,還是loading dialog呢?),複用性太低
.....
先透個底:最新方式:
優勢getApproves(approveQueryType, selectedApproveTypes, pageIndex) .compose(this.<ItemPageEntity<ApproveEntity>>bindToLifecycle()) .compose(this.<ItemPageEntity<ApproveEntity>>bindToStateRecyclerview(refreshStateRecyclerviewVM)) .subscribe(new Consumer<ItemPageEntity<ApproveEntity>>() { @Override public void accept(ItemPageEntity<ApproveEntity> approveEntityItemPageEntity) throws Exception { pageIndex++; approveAdapter.bindData(isRefresh, approveEntityItemPageEntity.items); refreshStateRecyclerviewVM.setLoadMoreEnable(approveEntityItemPageEntity.hasMoreHttpData()); } });
1: 具體請求邏輯不關心
2: 完全跟view 沒有建立任何訪問關係
3: 即使記憶體洩漏,也不會引發崩潰(這裡已經繫結生命週期啦)
4: 多狀態佈局(先生載入loading佈局,顯示error錯誤佈局,顯示頁面資料為空的佈局) 控制十分簡潔
5: 重新整理控制(重新整理結束方法呢?)無須呼叫(這尼瑪這麼做到的?)
業界 app架構設計,五花八門,沒有最好的架構設計,脫離業務的架構設計是沒有意義的,適合業務的才是好架構,而架構也不是一成不變的,隨著業務的發展,當初的架構已經不適合,這對團隊提出了更高的要求
元件化
元件化的核心思想就是:分而治之,降低耦合,如何實現元件化?我們需要考慮3個問題:
1: 元件單獨執行
2: 元件間通訊
3: 元件拆分粒度
元件化實施步驟:
as 自帶兩種module外掛方式
1: apply plugin: ‘com.android.application’(作為app獨立執行)
2: apply plugin: ‘com.android.library’(作為依賴庫存在)
我們在開發階段可以採用application獨立執行的方式,到提測的時候 合併到主專案中去
mvvm的思想和目標與mvp類似:view 和model 不直接互動
但是mvvm 比mvp 有更好的優勢:
1: 資料驅動
在MVVM中,以前的開發模式是必須先梳理業務資料,然後根據資料去重新整理UI,提交資料也是通過view來獲取,然而在mvvm中,資料和業務邏輯處於一個獨立的ViewModel中,ViewModel只關係資料和業務邏輯,不需要和UI或者控制元件打交到,同時移動開發可以拆分前端和後端開發,由資料去驅動UI自動更新,UI的改變同時有反饋到資料的改變,這就是雙向繫結的思想,讓資料成為主導因素,這樣使得業務邏輯處理只關係資料,簡單,隔離,最重要的是再也不會出現更新UI空指標問題,與再自執行緒更新UI導致崩潰的問題!
2: 低耦合度
MVVM模式中,資料是獨立於UI的,ViewModel只負責處理和提供資料,UI想怎麼處理資料都由UI自己決定,ViewModel 不涉及任何和UI相關的事也不持有UI控制元件的引用,即使控制元件改變(TextView 換成 EditText)ViewModel 幾乎不需要更改任何程式碼,專注自己的資料處理就可以了,如果是MVP遇到UI更改,就可能需要改變獲取UI的方式,改變更新UI的介面,改變從UI上獲取輸入的程式碼,可能還需要更改訪問UI物件的屬性程式碼等等
3: 動態增量重新整理UI
在MVVM中,我們可以在工作執行緒中直接修改View Model的資料(只要資料是執行緒安全的),剩下的資料繫結框架幫你搞定,很多事情都不需要你去關心。
4: 團隊協作
MVVM的分工是非常明顯的,由於View和View Model之間是鬆散耦合的。一個是處理業務和資料,一個是專門的UI處理。完全有兩個人分工來做,一個做UI(xml 和 Activity)一個寫ViewModel,效率更高。
5: 可複用性
一個View Model複用到多個View中,同樣的一份資料,用不同的UI去做展示,對於版本迭代頻繁的UI改動,只要更換View層就行,對於如果想在UI上的做AbTest 更是方便的多。
6: 單元測試
View Model裡面是資料和業務邏輯,View中關注的是UI,這樣的做測試是很方便的,完全沒有彼此的依賴,不管是UI的單元測試還是業務邏輯的單元測試,都是低耦合的。
特性之一 模版
這套框架能幫你解決很多問題,如重新整理,顯示loading對話方塊,展示空狀態或者錯誤佈局
package com.icourt.alpha.module.approve.source;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.icourt.alpha.R;
import com.icourt.alpha.utils.ActionConstants;
import com.icourt.architecture.util.JsonUtils;
import com.icourt.alpha.utils.StringUtils;
import com.icourt.api.RequestUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.functions.Function;
/**
* @author youxuan E-mail:[email protected]
* @version 2.2.2
* @Description 審批模組資料倉庫
* @Company Beijing icourt
* @date createTime:2017/12/6
*/
public class ApproveRepository
extends AlphaBaseViewModel
implements
ApproveDataSource {
......
/**
* 輸入專案編號
*
* @param projectId
* @param projectNumber
* @return
*/
@Override
public Observable<JsonElement> approveProjectNumberInputHUD(String projectId, String projectNumber) {
return RetrofitServiceFactory
.getProjectApiService()
.projectFillProjectNumber(projectId, projectNumber)
.map(new ResEntitySimpleFunction<JsonElement>())
.compose(this.<JsonElement>bindToProgressHUD())// 載入進度對話方塊
.compose(this.<JsonElement>bindToLifecycle());//繫結生命週期 防止記憶體洩漏
}
......
}
上面的程式碼 既實現了網路請求,又在資料上游處理了loading對話方塊的互動很關鍵的一句:
.compose(this.<JsonElement>bindToProgressHUD())
我們知道RxJava 有變換一說,可以資料變換,可以流呼叫變換,還可以執行緒變換,那麼我們怎麼將loading的載入,消失等狀態與網路請求繫結呢?package com.icourt.ui.binding;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.icourt.architecture.mvvm.viewmodel.ICourtViewModel;
import com.icourt.architecture.util.LogUtils;
import com.icourt.ui.AlphaActionHelper;
/**
* @author youxuan E-mail:[email protected]
* @version 2.3.0
* @Description
* @Company Beijing icourt
* @date createTime:2017/12/13
*/
public class AlphaBaseViewModel extends ICourtViewModel {
@Override
public <T> SubscribeLifeTransformer<T> bindToProgressHUD() {
return new AlphaProgressHUDTransformer<>(this, null, null, null);
}
@Override
public <T> SubscribeLifeTransformer<T> bindToProgressHUD(String loadingNotice) {
return new AlphaProgressHUDTransformer<>(this, loadingNotice, null, null);
}
@Override
public <T> SubscribeLifeTransformer<T> bindToProgressHUD(@Nullable String loadingNotice, @Nullable String successNotice) {
return new AlphaProgressHUDTransformer<>(this, loadingNotice, null, successNotice);
}
@Override
public <T> SubscribeLifeTransformer<T> bindToProgressHUD(@Nullable String loadingNotice, @Nullable String errorNotice, @Nullable String successNotice) {
return new AlphaProgressHUDTransformer<>(this, loadingNotice, errorNotice, successNotice);
}
}
可以看到 我綁定了一個transformer,原始碼如下:
package com.icourt.architecture.rxjava;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.icourt.architecture.binding.IICourtProgressHUD;
import org.reactivestreams.Publisher;
import java.util.concurrent.TimeUnit;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.MaybeSource;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.Single;
import io.reactivex.SingleSource;
/**
* @author youxuan E-mail:[email protected]
* @version 2.3.0
* @Description 載入與loading提示結合
* @Company Beijing icourt
* @date createTime:2018/1/4
*/
public class ProgressHUDTransformer<T> extends SubscribeLifeTransformer<T> {
protected IICourtProgressHUD iiCourtProgressHUD;
protected String loadingNotice;
protected String errorNotice;
protected String successNotice;
/**
* @param iiCourtProgressHUD
* @param loadingNotice 可空
* @param errorNotice 如果為空,直接展示預設的error,否則展示errorNotice
* @param successNotice 空 不展示正確對勾符號
*/
public ProgressHUDTransformer(IICourtProgressHUD iiCourtProgressHUD,
@Nullable String loadingNotice,
@Nullable String errorNotice,
@Nullable String successNotice) {
this.iiCourtProgressHUD = iiCourtProgressHUD;
this.loadingNotice = loadingNotice;
this.errorNotice = errorNotice;
this.successNotice = successNotice;
}
@Override
protected void onSubscribe() {
if (iiCourtProgressHUD != null) {
iiCourtProgressHUD.showICourtLoadingDialog(loadingNotice);
}
}
@Override
protected void onComplete() {
if (iiCourtProgressHUD != null) {
iiCourtProgressHUD.dismissICourtLoadingDialogWithSuccess(successNotice);
}
}
@Override
protected void onError(Throwable throwable) {
if (iiCourtProgressHUD != null) {
if (TextUtils.isEmpty(errorNotice)) {
iiCourtProgressHUD.dismissICourtLoadingDialogWithFail(throwable.getMessage());
} else {
iiCourtProgressHUD.dismissICourtLoadingDialogWithFail(errorNotice);
}
}
}
@Override
protected void onCancel() {
if (iiCourtProgressHUD != null) {
iiCourtProgressHUD.dismissICourtLoadingDialog();
}
}
}
在這個轉換器中 實現了三個方法
1.onSubscribe() 流式響應執行開始
2. onComplete() 動作執行完成
3. onError(Throwable t) 流動作執行錯誤,如網路請求伺服器500
4. onCancel() 流動作執行取消,如網路請求取消
我們繼續看一下基類:
package com.icourt.architecture.rxjava;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.Maybe;
import io.reactivex.MaybeSource;
import io.reactivex.MaybeTransformer;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.Single;
import io.reactivex.SingleSource;
import io.reactivex.SingleTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
/**
* @author youxuan E-mail:[email protected]
* @version 2.3.0
* @Description <p>
* <p>
* <p>
* 特性:
* 1: 處理UI操作
* 2:接收處理預設分發在主執行緒
* @Company Beijing icourt
* @date createTime:2017/12/24
* </p>
*/
public abstract class SubscribeLifeTransformer<T>
implements
ObservableTransformer<T, T>,
FlowableTransformer<T, T>,
SingleTransformer<T, T>,
MaybeTransformer<T, T> {
/**
* 開始執行
*/
protected abstract void onSubscribe();
/**
* 執行結束
*/
protected abstract void onComplete();
/**
* 執行失敗
*
* @param throwable
*/
protected abstract void onError(Throwable throwable);
/**
* 執行取消
*/
protected void onCancel() {
}
@Override
public Publisher<T> apply(Flowable<T> upstream) {
return upstream
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer<Subscription>() {
@Override
public void accept(Subscription subscription) throws Exception {
onSubscribe();
}
}).doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
onError(throwable);
}
}).doOnComplete(new Action() {
@Override
public void run() throws Exception {
onComplete();
}
}).doOnCancel(new Action() {
@Override
public void run() throws Exception {
onCancel();
}
});
}
@Override
public MaybeSource<T> apply(Maybe<T> upstream) {
return upstream
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
onSubscribe();
}
}).doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
onError(throwable);
}
}).doOnComplete(new Action() {
@Override
public void run() throws Exception {
onComplete();
}
}).doOnDispose(new Action() {
@Override
public void run() throws Exception {
onCancel();
}
});
}
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream
.observeOn(AndroidSchedulers.mainThread())//流下游接收資料 在主執行緒執行
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
onSubscribe();
}
}).doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
onError(throwable);
}
}).doOnComplete(new Action() {
@Override
public void run() throws Exception {
onComplete();
}
}).doOnDispose(new Action() {
@Override
public void run() throws Exception {
onCancel();
}
});
}
@Override
public SingleSource<T> apply(Single<T> upstream) {
return upstream
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
onSubscribe();
}
}).doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
onError(throwable);
}
}).doOnSuccess(new Consumer<T>() {
@Override
public void accept(T t) throws Exception {
onComplete();
}
}).doOnDispose(new Action() {
@Override
public void run() throws Exception {
onCancel();
}
});
}
}
subscribeLifeTransformer 監聽了流的生命週期,並且將資料下游的接收放到主執行緒(需要與UI元件互動),不僅支援Observable,還支援Flowable 等流式響應物件重新整理控制 我們需要關注四個問題:
1. 重新整理開始
2. 重新整理結束
3.能不能重新整理
4.能不能上拉載入
5. 是手勢觸發的重新整理,還是自動重新整理呢?package com.icourt.ui.binding.alphamodulevm;
import android.databinding.ObservableBoolean;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadmoreListener;
/**
* @author youxuan E-mail:[email protected]
* @version 2.3.0
* @Description 包括多狀態佈局 與重新整理控制
* @Company Beijing icourt
* @date createTime:2017/12/24
*/
public interface IRefreshStateRecyclerviewVM
extends OnRefreshLoadmoreListener, IAlphaStateRecyclerviewVM {
/**
* 是否觸發的
*
* @return
*/
ObservableBoolean isFromGesture();
/**
* 設定手勢觸發
*
* @param isFromGesture
*/
void setFromGesture(boolean isFromGesture);
/**
* 是否可以重新整理
*
* @return
*/
ObservableBoolean getIsRefreshEnable();
/**
* 是否可以重新整理
*
* @param refreshEnable
*/
void setRefreshEnable(boolean refreshEnable);
/**
* 是否正在重新整理
*
* @return
*/
ObservableBoolean getIsRefreshing();
/**
* 是否在重新整理
*
* @param refreshing
*/
void setRefreshing(boolean refreshing);
/**
* 是否結束重新整理
*
* @return
*/
ObservableBoolean getIsLoadMoreEnable();
/**
* 是否能夠重新整理
*
* @param loadMoreEnable
*/
void setLoadMoreEnable(boolean loadMoreEnable);
}
區域性VM 控制著重新整理 我們只需要改變其中的值 就可以控制重新整理過程,這個跟資料驅動的理念非常貼合,我們實現了重新整理控制的ViewModel 那怎麼繫結呢?
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools">
<!-- 標準模版佈局 -->
<data>
<variable
name="vm"
type="com.icourt.ui.binding.alphamodulevm.IRefreshStateRecyclerviewVM" />
</data>
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:srlEnableLoadmore="false"
bind:isRefreshEnable="@{vm.getIsRefreshEnable()}"
bind:isRefreshing="@{vm.getIsRefreshing()}"
bind:loadMoreEnable="@{vm.getIsLoadMoreEnable()}"
bind:refreshListener="@{vm}">
<com.icourt.loading.AlphaStateLayout
style="@style/style_alpha_state"
android:layout_width="match_parent"
android:layout_height="match_parent"
bind:emptyDesc="@{vm.getEmptyDesc()}"
bind:emptyIcon="@{vm.getEmptyIcon()}"
bind:errorDesc="@{vm.getErrorDesc()}"
bind:loadingViewState="@{vm.getLoadingViewState()}"
bind:retryListener="@{vm}">
<!-- 預設白色 -->
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
bind:adapter="@{vm.getRecyclerAdapter()}"
bind:itemDecoration="@{vm.getItemDecorationFactory()}"
bind:layoutManager="@{vm.getLayoutManagerFactory()}" />
</com.icourt.loading.AlphaStateLayout>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</layout>
在XML 中我們綁定了View與ViewModel 也就是資料改變View自動增量重新整理,對於binding標籤語法比較陌生的朋友可以參考這裡特性之二:生命週期
在移動開發中,很大一個特性需要知道元件的生命週期,場景(鎖屏的時候同步資料,螢幕可見的時候拉取資料...),有生命週期的元件只是限於系統原生元件,在mvp中會寫很多介面方法(onStart,onDestroy...)然後在活動或者碎片的基類裡面來控制,不太靈活,這時候有一個新東西:Lifecycle
Lifecycle
Lifecycle 是一個持有元件(比如 activity 或者 fragment)生命週期狀態資訊的類,並且允許其它物件觀察這個狀態。
Lifecycle 主要使用兩個列舉來跟蹤相關元件的生命週期狀態。
Event
由framework和Lifecycle類發出的生命週期事件。這些事件對應Activity和Fragment中的回撥事件。
正因為lifecycle 基於註解,比較靈活,所以我這樣封裝了,避免方法名字在不同成員間產生命名差異
package com.icourt.architecture.binding;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
/**
* @author youxuan E-mail:[email protected]
* @version 2.2.2
* @Description 元件的生命週期
* @Company Beijing icourt
* @date createTime:2017/12/6
*/
public class ICourtLifecycleAllObserver implements ICourtLifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onComponentLifecycleCreate(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onComponentLifecycleStart(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onComponentLifecycleResume(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onComponentLifecyclePuase(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onComponentLifecycleStop(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onComponentLifecycleDestroy(LifecycleOwner owner) {
}
@Override
public void onComponentLifecycleAny(LifecycleOwner owner, Lifecycle.Event event) {
}
}
這樣的模版方法 能建立團隊成員的共識,找尋程式碼更加迅速欲知更多,請聽下回分解