? ? ? ?當我們開發(fā)一個新項目,常常需要先搭建一個項目結構,就好像建房子,你把地基和骨架搭建好了,搭建完善了,你在根據(jù)不同的業(yè)務需求,大部分情況下只是在基本的架構上面做開發(fā)而已,這樣大大的提高的開發(fā)效率,同時如果前期搭建了一個好的架構,會對項目的持續(xù)迭代更新和項目的代碼質量有著至關重要的作用,所以了解并知道如何搭建一個通用的項目架構是非常必要了。
?知識點匯總:
一:項目簡介
二:MVVMHabitComponent的項目框架疑問與解答
三:MVVMHabit的項目框架疑問與解答
四:項目中可以優(yōu)化的點匯總
五:項目知識點舉一反三
六: 擴展閱讀
一:項目簡介
? ? ? ?MVVMHabit是以谷歌DataBinding+LiveData+ViewModel框架為基礎,整合Okhttp+RxJava+Retrofit+Glide等流行模塊,加上各種原生控件自定義的BindingAdapter,讓事件與數(shù)據(jù)源完美綁定的一款容易上癮的實用性MVVM快速開發(fā)框架,而MVVMHabitComponent是基于MVVMHabit的基礎上實現(xiàn)的組件化方案架構,高內聚,低耦合,代碼邊界清晰,每一個組件都可以拆分出來獨立運行。所有組件寄托于宿主App,加載分離的各個組件,各自編譯自己的模塊,有利于多人團隊協(xié)作開發(fā)。
項目地址:
1、https://github.com/goldze/MVVMHabit
2、https://github.com/goldze/MVVMHabitComponent
二:MVVMHabitComponent的項目框架疑問與解答
2.1、組件化項目的架構圖
2.2、組件化項目的優(yōu)點
2.3、如何單獨運行Module項目和整體運行項目,單獨運行項目時build.gradle需要配置什么?
2.4、如何執(zhí)行單個Module項目和完整項目的初始化(Application初始化模塊)
2.5、互不依賴的Module之間,相互跳轉Activity的方式有哪些,都有什么優(yōu)缺點
2.6、LiveData 與 ObservableField的區(qū)別
2.7、路由框架ARouter如何實現(xiàn)不同組件Activity和Fragment的項目跳轉
2.8、路由框架Arouter如何實現(xiàn)不同組件間函數(shù)的相互調用
2.1、組件化項目的架構圖

解析:組件化的架構一般分為三層:應用層,組件層,基礎層。
應用層:一般實現(xiàn)很少的業(yè)務邏輯,通常被稱為殼應用。
組件層:隨著項目迭代和業(yè)務擴展,組件層的組件模塊會越來越多,各個組件Module的代碼也會越來越多。
基礎層:如果剛開始搭建項目的架構時,最需要完善的就是基礎層的代碼,基礎層的功能模塊越完善,組件層的開發(fā)
效率就會越高,后續(xù)改的也相對較少。
2.2、組件化項目的優(yōu)點
優(yōu)點一:業(yè)務解耦
各個業(yè)務模塊通過組件化架構,實現(xiàn)了解耦,不同的模塊相互獨立。
優(yōu)點二:并行開發(fā)
在開發(fā)項目時,各個不同的組件模塊可以分開開發(fā),并且每個模塊可以獨立運行。
優(yōu)點三:提高開發(fā)效率
由于項目模塊的基礎模塊已經(jīng)實現(xiàn),各個組件的開發(fā)人員只需要針對自己的模塊開發(fā),并獨立運行自己模塊的代碼,
,通過路由和服務外發(fā)接口,實現(xiàn)與其他組件的聯(lián)系,大大提高了開發(fā)效率。
2.3、如何單獨運行Module項目和整體運行項目,單獨運行項目時build.gradle需要配置什么?
1、在gradle.properties中,重新設置isBuildModule=false。
2、if (isBuildModule.toBoolean()) {
????//作為獨立App應用運行
????apply plugin: 'com.android.application'
} else {
????//作為組件運行
????apply plugin: 'com.android.library'
}
3、defaultConfig {
????????//如果是獨立模塊,則使用當前組件的包名
????????if (isBuildModule.toBoolean()) {
????????????applicationId 組件的包名
????????}
????}
4、sourceSets {
????????main {
????????????if (isBuildModule.toBoolean()) {
????????????????manifest.srcFile 'src/main/alone/AndroidManifest.xml'
????????????} else {
????????????????manifest.srcFile 'src/main/AndroidManifest.xml'
????????????????resources {
????????????????????exclude 'src/main/alone/*'
????????????????}
????????????}
????????}
????}
2.4、如何執(zhí)行單個Module項目和完整項目的初始化(Application初始化模塊)
解析:項目中主要實現(xiàn)思路是:接口定義、接口實現(xiàn)、反射調用不同組件的接口實現(xiàn)類,執(zhí)行初始化工作。
第一步:在基礎層定義初始化接口
第二步:各個組件實現(xiàn)接口的實現(xiàn)類
第三部:在應用層通過反射調用各個組件的實現(xiàn)類函數(shù)。(通過單例+靜態(tài)類路徑封裝)
2.5、互不依賴的Module之間,相互跳轉Activity的方式有哪些,都有什么優(yōu)缺點
方式一:隱式跳轉
這是一種解決方法,但是一個項目中不可能所有的跳轉都是隱式的,這樣Manifest文件會有很多過濾配置,而且非常不利于后期維護。
方式二:反射
用反射拿到Activity的class文件也可以實現(xiàn)跳轉,但是大量的使用反射跳轉對性能會有影響。
方式三:路由框架(ARouter原理)
1、在每個需要對其他module提供調用的Activity中,都會聲明類似下面@Route注解,我們稱之為路由地址。

2、編譯期時生成路由映射,路由框架會在項目的編譯期通過注解處理器apt掃描所有添加@Route注解的Activity類,然后將Route注解中的path地址和Activity.class文件映射關系保存到它自己生成的java文件中,只要拿到了映射關系便能拿到Activity.class。

3、不同module之間啟動Activity

4、ARouter使用

備注:因為沒有相互引用,startActivity()是實現(xiàn)不了的,必須需要一個協(xié)定的通信方式。
2.6、LiveData 與 ObservableField的區(qū)別
一:ObservableField只有在數(shù)據(jù)發(fā)生改變時UI才會收到通知,而LiveData不同,只要你postValue或者setValue,UI都會收到通知,不管數(shù)據(jù)有無變化。
二:LiveData能感知Activity的生命周期,在Activity不活動的時候不會觸發(fā),例如一個Activity不在任務棧頂部。
三:支持Transformations,而且可以與許多架構組件 (如Room、WorkManager) 相互配合使用。
2.7、路由框架ARouter如何實現(xiàn)不同組件Activity和Fragment的項目跳轉
解析:Arouter通過編譯時動態(tài)生成路由表的方式,實現(xiàn)不同界面的跳轉,底層實現(xiàn)思路:apt注解解析器、javapoat
動態(tài)生成代碼、startActivity實現(xiàn)界面跳轉的,而Fragment的跳轉更多只是通過其他模塊的服務外發(fā)獲取不同模塊
的Fragment實例,從而實現(xiàn)Fragment的跳轉。
2.8、路由框架Arouter如何實現(xiàn)不同組件間函數(shù)的相互調用
實現(xiàn)思路主要是:
第一步:基礎層的通信接口定義(提供服務接口)
第二步:組件層的接口實現(xiàn)
第三步:路由實現(xiàn)函數(shù)調用。
三:MVVMHabit的項目框架疑問與解答
3.1、BaseActivity、BaseFragment、BaseViewModel類中分別封裝了什么
3.2、使用DataBinging時,會遇到四種無法編譯通過的場景,分別是什么,應該如何處理
3.3、BR自動生成的類中,有四個常量,分別代表什么意思
3.4、如何使用DataBinging定義自定義控件屬性
3.5、RxAppCompatActivity和RxFragment類的作用是什么
3.6、用于ViewModel與xml之間的數(shù)據(jù)綁定,執(zhí)行的命令回調的實現(xiàn)原理
3.7、Messager類在項目中的作用是什么,實現(xiàn)原理是怎么樣的
3.8、CaoConfig類在項目中的作用是什么,實現(xiàn)原理是怎么樣的
3.9、Rxbus的作用是什么,實現(xiàn)原理是怎么樣的
3.10、ContainerActivity類作用是什么,實現(xiàn)原理是怎么樣的
3.11、項目中是如何實現(xiàn)Activity和Fragment的堆棧管理的(任務棧管理),Task任務棧的作用是什么
3.12、如何給不同的界面設置不同的Repository(數(shù)據(jù)獲取模塊Model),實現(xiàn)不同界面數(shù)據(jù)獲取的區(qū)分
3.13、項目中是如何封裝網(wǎng)絡請求模塊的
3.14、項目中是如何封裝Https相關網(wǎng)絡請求的
3.15、項目中是如何通過Rxjava實現(xiàn)文件的下載和下載進度的回調
3.16、項目中是如何對網(wǎng)絡請求中常見異常進行處理的
3.17、項目中對Cookie的處理是如何實現(xiàn)的
3.18、項目中引入了開源框架BindingCollectionAdapter的作用是什么,實現(xiàn)原理是什么
3.1、BaseActivity、BaseFragment、BaseViewModel類中分別封裝了什么
一:BaseActivity
1.1、在ActivityLifecycleCallbacks中,通過AppManager管理ActivityTask任務,這里我們可以發(fā)揮一下自己的
想象,可以實現(xiàn)更多不同界面的封裝。
1.2、通過對類的泛型,實現(xiàn)不同界面的DataBinging和ViewModel的初始化適配,各個界面的DataBinging需要繼承
ViewDataBinding,各個不同界面的ViewModel需要實現(xiàn)不同的AndroidViewModel,
DataBinding的初始化代碼:
binding = DataBindingUtil.setContentView(this, initContentView(savedInstanceState));
binding.setVariable(viewModelId, viewModel); //關聯(lián)ViewModel
binding.setLifecycleOwner(this);?????????????//支持LiveData綁定xml,數(shù)據(jù)改變,UI自動會更新
ViewModel的初始化代碼:
viewModel = initViewModel();
????????if (viewModel == null) {
????????????Class modelClass;
????????????Type type = getClass().getGenericSuperclass();
????????????if (type instanceof ParameterizedType) {
????????????????modelClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
????????????} else {
????????????????//如果沒有指定泛型參數(shù),則默認使用BaseViewModel
????????????????modelClass = BaseViewModel.class;
????????????}
????????????viewModel = (VM) createViewModel(this, modelClass);
????????}
????????binding.setVariable(viewModelId, viewModel);????//關聯(lián)ViewModel
????????getLifecycle().addObserver(viewModel);??????????//讓ViewModel擁有View的生命周期感應
????????viewModel.injectLifecycleProvider(this);????????//注入RxLifecycle生命周期
????從上面的代碼中,我們看到viewModel如果為空,里面有一堆陌生的函數(shù)調用,最后也實現(xiàn)了viewModel的初始化,具體我們需要了解相關函數(shù)的作用。
1、getClass().getGenericSuperclass()
????返回表示此 Class 所表示的實體(類、接口、基本類型或 void)的直接超類的 Type,然后將其轉換。ParameterizedType。
2、getActualTypeArguments()
????返回表示此類型實際類型參數(shù)的 Type 對象的數(shù)組。[0]就是這個數(shù)組中第一個了。簡而言之就是獲得超類的泛型參數(shù)的實際類型。
????通過對這兩個主要函數(shù)的解析,應該就能很好的理解上面的代碼了吧。
public <T extends ViewModel> T createViewModel(FragmentActivity activity, Class<T> cls) {
????????return ViewModelProviders.of(activity).get(cls);
????}
注意:
????public void refreshLayout() {??????????????????//??刷新布局通過
????????if (viewModel != null) {
????????????binding.setVariable(viewModelId, viewModel);
????????}
????}
1.3、注意到BaseActivity繼承RxAppCompatActivity,這是Rxjava的家庭成員rxlifecycle提供的類,方便的實現(xiàn)Rxjava的異步任務的解綁,防止內存泄漏,有興趣可以看看類的源碼,代碼并不復雜。
1.4、通過registorUIChangeLiveDataCallBack函數(shù),注冊ViewModel與View的契約UI回調事件,把不同界面中常用的一些操作封裝在ViewModel中,例如:啟動對話框、Toast提示、界面跳轉等操作,這里我們可以根據(jù)項目進行擴展,例如添加加載,空數(shù)據(jù),網(wǎng)絡異常等界面。
1.5、回調函數(shù)onDestroy中,實現(xiàn)常規(guī)的反注冊相關函數(shù),代碼如下:?????????????????Messenger.getDefault().unregister(viewModel);??//解除Messenger注冊
????????if (viewModel != null) {
????????????viewModel.removeRxBus();
????????}
????????if(binding != null){
????????????binding.unbind();
????????}
1.6、我們注意到在onDestroy()中解除Messenger注冊了。
二:BaseFragment
2.1、大體上BaseFragment的初始化的東西都和BaseActivity中保持一致,只是在相關的生命周期中有一點區(qū)別和調用的初始化函數(shù)稍微有點不同。
2.2、界面的Databinding界面初始化:
????public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
????????binding = DataBindingUtil.inflate(inflater, initContentView(inflater, container, savedInstanceState), container, false);
????????return binding.getRoot();
????}
2.3、在回調函數(shù)public void onViewCreated(View view, @Nullable Bundle savedInstanceState)中執(zhí)行主要的界面初始化行為。
2.4、RxFragment與RxAppCompatActivity有著異曲同工的效果。
三:BaseViewModel
3.1、通過定義對象CompositeDisposable,管理RxJava,解決異步操作造成的內存泄漏。
3.2、通過弱應用對象lifecycle,注入RxLifecycle生命周期。
3.3、通過類的泛型初始化Model層的對象初始化,實現(xiàn)MVVM的Model層,統(tǒng)一模塊的數(shù)據(jù)倉庫,包含網(wǎng)絡數(shù)據(jù)和本地數(shù)據(jù)。
3.4、內部類UIChangeLiveData實現(xiàn)BaseAcitivty和BaseFragment的通用功能的監(jiān)聽數(shù)據(jù)定義,代碼如下:
????public final class UIChangeLiveData extends SingleLiveEvent {
????????private SingleLiveEvent<String> showDialogEvent;
????????private SingleLiveEvent<Void> dismissDialogEvent;
????????private SingleLiveEvent<Map<String, Object>> startActivityEvent;
????????private SingleLiveEvent<Map<String, Object>> startContainerActivityEvent;
????????private SingleLiveEvent<Void> finishEvent;
????????private SingleLiveEvent<Void> onBackPressedEvent;
這里需要補充一下:SingleLiveEvent類也是自定義類,繼承MutableLiveData類。
3.5、BaseViewModel實現(xiàn)了接口Consumer<Disposable>,從而使得在非ViewModel的異步任務也可以解除異步綁定事件,防止內存泄露。
這里順便附上MVVM的架構圖:

3.2、使用DataBinging時,會遇到五種無法編譯通過的場景,分別是什么,應該如何處理?
解析:使用databinding其實有個缺點,就是會遇到一些編譯錯誤,而AS不能很好的定位到錯誤的位置,這對于剛開始使用databinding的開發(fā)者來說是一個比較郁悶的事,下面就匯總出相關的問題。
1、綁定錯誤
?????綁定錯誤是一個很常見的錯誤,基本都會犯。比如TextView的 android:text="" ,本來要綁定的是一個String類型,結果你不小心,可能綁了一個Boolean上去,或者變量名寫錯了,這時候編輯器不會報紅錯,而是在點編譯運行的時候,在AS的Messages中會出現(xiàn)錯誤提示,如下圖:

解決方法:把錯誤提示拉到最下面 (上面的提示找不到BR類這個不要管它),看最后一個錯誤 ,這里會提示是哪個xml出了錯,并且會定位到行數(shù),按照提示找到對應位置,即可解決該編譯錯誤的問題。
注意: 行數(shù)要+1,意思是上面報出第33行錯誤,實際是第34行錯誤,AS定位的不準確 (這可能是它的一個bug)。
2、xml導包錯誤
????在xml中需要導入ViewModel或者一些業(yè)務相關的類,假如在xml中導錯了類,那一行則會報紅,但是res/layout卻沒有錯誤提示,有一種場景,非常特殊,不容易找出錯誤位置。就是你寫了一個xml,導入了一個類,比如XXXUtils,后來因為業(yè)務需求,把那個XXXUtils刪了,這時候res/layout下不會出現(xiàn)任何錯誤,而你在編譯運行的時候,才會出現(xiàn)錯誤日志??啾频氖?,不會像上面那樣提示哪一個xml文件,哪一行出錯了,最后一個錯誤只是一大片的報錯報告。如下圖:

解決方法:同樣找到最后一個錯誤提示,找到Cannot resolve type for xxx這一句 (xxx是類名),然后使用全局搜索 (Ctrl+H) ,搜索哪個xml引用了這個類,跟蹤點擊進去,在xml就會出現(xiàn)一個紅錯,看到錯誤你就會明白了,這樣就可解決該編譯錯誤的問題。
3、build錯誤
???構建多module工程時,如出現(xiàn)【4.1.1、綁定錯誤】,且你能確定這個綁定是沒有問題的,經(jīng)過修改后出現(xiàn)下圖錯誤:

解決方法:這種是databinding比較大的坑,清理、重構和刪build都不起作用,網(wǎng)上很難找到方法。經(jīng)過試驗,解決辦法是手動創(chuàng)建異常中提到的文件夾,或者拷貝上一個沒有報錯的版本中對應的文件夾,可以解決這個異常。
4、自動生成類錯誤
????有時候在寫完xml時,databinding沒有自動生成對應的Binding類及屬性。比如新建了一個activity_login.xml,按照databinding的寫法加入<layout> <variable>后,理論上會自動對應生成ActivityLoginBinding.java類和variable的屬性,可能是as對databding的支持還不夠吧,有時候偏偏就不生成,導致BR.xxx報紅等一些莫名的錯誤。
解決方法:其實確保自己的寫法沒有問題,是可以直接運行的,報紅不一定是你寫的有問題,也有可能是編譯器抽風了。或者使用下面的辦法
第一招:Build->Clean Project;
第二招:Build->Rebuild Project;
第三招:重啟大法。
5、gradle錯誤
? ? ? ?如果遇到以下編譯問題:
錯誤: 無法將類 BindingRecyclerViewAdapters中的方法 setAdapter應用到給定類型; 需要: RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds<? super T>,ViewHolderFactory 找到: RecyclerView,ItemBinding,ObservableList,BindingRecyclerViewAdapter<CAP#1>,ItemIds,ViewHolderFactory 原因: 推斷類型不符合等式約束條件 推斷: CAP#1 等式約束條件: CAP#1,NetWorkItemViewModel 其中, T是類型變量: T擴展已在方法 setAdapter(RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds<? super T>,ViewHolderFactory)中聲明的Object 其中, CAP#1是新類型變量: CAP#1從?的捕獲擴展Object。
????一般是由于gradle plugin版本3.5.1造成的,請換成gradle plugin 3.5.0以下版本。
3.3、BR自動生成的類中,有四個常量,分別代表什么意思
解析:項目中生成的BR類代碼如下:
public class BR {
??public static final int _all = 0;
??public static final int adapter = 1;
??public static final int toolbarViewModel = 2;
??public static final int viewModel = 3;
}
解析:BR類似Android R文件,DataBinderMapperImpl 提供了布局文件layoutid到ViewDataBinding類對象的映射,主要用于加載layout返回對應的ViewDataBinding對象。
3.4、如何使用DataBinging定義自定義控件屬性
第一步:在項目中的attrs.xml文件中,定義了一些通用View控件和定義不同自定義控件或系統(tǒng)控件的相關自定義屬性,部分定義如下:
????<attr name="requestFocus" format="boolean" />
????<attr name="itemView" format="reference" />
????<attr name="items" format="reference" />
????<attr name="adapter" format="reference" />
????<attr name="onScrollChangeCommand" format="reference" />
????<attr name="url" format="string" />
????<attr name="onTouchCommand" format="reference" />
????<attr name="onClickCommand" format="reference" />
第二步:然后為不同的控件,定義其相關的自定義綁定適配器,需要重點了解BindingAdapter注解的使用,參考代碼如下,圖片控件的設置。
????@BindingAdapter(value = {"url", "placeholderRes"}, requireAll = false)
????public static void setImageUri(ImageView imageView, String url, int placeholderRes) {
????????if (!TextUtils.isEmpty(url)) {
????????????//使用Glide框架加載圖片
????????????Glide.with(imageView.getContext())
????????????????????.load(url)
????????????????????.apply(new RequestOptions().placeholder(placeholderRes))
????????????????????.into(imageView);
????????}
????}
view的焦點發(fā)生變化的事件綁定:
????@BindingAdapter({"onFocusChangeCommand"})
????public static void onFocusChangeCommand(View view, final BindingCommand<Boolean> onFocusChangeCommand) {
????????view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
????????????@Override
????????????public void onFocusChange(View v, boolean hasFocus) {
????????????????if (onFocusChangeCommand != null) {
????????????????????onFocusChangeCommand.execute(hasFocus);
????????????????}
????????????}
????????});
????}
????這里涉及的的自定義類:BindingCommand,用于執(zhí)行的命令回調, 用于ViewModel與xml之間的數(shù)據(jù)綁定,由于需要綁定的命令回調的類型和場景比較多,項目中實現(xiàn)了相關類來適配不同的情況,相關具體實現(xiàn)解析,請繼續(xù)查看后文,實現(xiàn)類如下:

備注:代碼實現(xiàn)參考開源項目https://github.com/Kelin-Hong/MVVMLight。
BindingAdapter的官方概述:
BindingAdapter is applied to methods that are used to manipulate how values with expressions are set to views. The simplest example is to have a public static method that takes the view and the value to set。
BindingAdapter注解的官方代碼實現(xiàn):
@Target(ElementType.METHOD)
public @interface BindingAdapter {
????/**
?????* @return The attributes associated with this binding adapter.
?????*/
????String[] value();
????/**
?????* Whether every attribute must be assigned a binding expression or if some
?????* can be absent. When this is false, the BindingAdapter will be called
?????* when at least one associated attribute has a binding expression. The attributes
?????* for which there was no binding expression (even a normal XML value) will
?????* cause the associated parameter receive the Java default value. Care must be
?????* taken to ensure that a default value is not confused with a valid XML value.
?????*
?????* @return whether or not every attribute must be assigned a binding expression. The default
?????*?????????value is true.
?????*/
????boolean requireAll() default true;
}
第三步:在布局文件中,由于之前Databinding與ViewModel就已經(jīng)綁定關聯(lián)上了,所以數(shù)據(jù)源可以直接從ViewModel中獲取,參考代碼如下:
<ImageView
????????????android:layout_width="50dp"
????????????android:layout_height="50dp"
????????????android:src="@{viewModel.drawableImg}"
????????????binding:url="@{viewModel.entity.img}" />
<EditText
???????????android:layout_width="0dp"
???????????android:layout_height="match_parent"
???????????android:layout_weight="1"
???????????android:background="@null"
???????????android:hint="請輸入用戶名"
???????????android:text="@={viewModel.userName}"
???????????android:textColor="@color/textColor"
???????????android:textColorHint="@color/textColorHint"
???????????android:textSize="16sp"
???????????binding:onFocusChangeCommand="@{viewModel.onFocusChangeCommand}" />
@BindingAdapter
這個注解用于支持自定義屬性,或者是修改原有屬性。注解值可以是已有的 xml 屬性,例如 android:src、android:text等,也可以自定義屬性然后在 xml 中使用。
3.5、RxAppCompatActivity和RxFragment類的作用是什么?
解析:Rxjava的家庭成員rxlifecycle,該開源框架可以在Rxjava實現(xiàn)異步任務時,防止退出界面導致的內存泄漏問題,該開源框架的實現(xiàn)類如下:



3.6、用于ViewModel與xml之間的數(shù)據(jù)綁定,執(zhí)行的命令回調的實現(xiàn)原理
項目中通過定義通用的命令相關類,實現(xiàn)控件的操作事件以命令的形式進行傳遞,主要實現(xiàn)類代碼如下:
public class BindingCommand<T> {
????private BindingAction execute;
????private BindingConsumer<T> consumer;
????private BindingFunction<Boolean> canExecute0;
????public BindingCommand(BindingAction execute) {
????????this.execute = execute;
????}
????public BindingCommand(BindingConsumer<T> execute) {????//帶泛型參數(shù)的命令綁定
????????this.consumer = execute;
????}
????public BindingCommand(BindingAction execute, BindingFunction<Boolean> canExecute0) {
????????this.execute = execute;????????????????????????????//觸發(fā)命令
????????this.canExecute0 = canExecute0;????????????????????//true則執(zhí)行,反之不執(zhí)行
????}
????public BindingCommand(BindingConsumer<T> execute, BindingFunction<Boolean> canExecute0) {
????????this.consumer = execute;??????????????????????????//帶泛型參數(shù)觸發(fā)命令
????????this.canExecute0 = canExecute0;???????????????????//true則執(zhí)行,反之不執(zhí)行
????}
????public void execute() {
????????if (execute != null && canExecute0()) {???????????//執(zhí)行BindingAction命令
????????????execute.call();
????????}
????}
????public void execute(T parameter) {????????????????????//執(zhí)行帶泛型參數(shù)的命令
????????if (consumer != null && canExecute0()) {
????????????consumer.call(parameter);
????????}
????}
????private boolean canExecute0() {???????????????????????//是否需要執(zhí)行,true則執(zhí)行, 反之不執(zhí)行
????????if (canExecute0 == null) {
????????????return true;
????????}
????????return canExecute0.call();
????}
}
項目中通過定義的類如下所示:

備注:如果發(fā)現(xiàn)控件需要的綁定操作命令需要在當前的綁定指令類中無法滿足相關控件的實現(xiàn),可以根據(jù)項目的實際需要,定義相關的命令相關類,實現(xiàn)相關功能,這里的思考一下觸摸事件的命令相關類應該如何實現(xiàn)。
3.7、Messager類在項目中的作用是什么,實現(xiàn)原理是怎么樣的?
解析:Messenger是一個輕量級全局的消息通信工具,在我們的復雜業(yè)務中,難免會出現(xiàn)一些交叉的業(yè)務,比如ViewModel與ViewModel之間需要有數(shù)據(jù)交換,這時候可以輕松地使用Messenger發(fā)送一個實體或一個空消息,將事件從一個ViewModel回調到另一個ViewModel中。
使用方法:
public static final String TOKEN_LOGINVIEWMODEL_REFRESH = "token_loginviewmodel_refresh";???//定義一個靜態(tài)String類型的字符串token
在ViewModel中注冊消息監(jiān)聽:
//參數(shù)1:接受人(上下文)
//參數(shù)2:定義的token
//參數(shù)3:執(zhí)行的回調監(jiān)聽
Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, new BindingAction() {????//注冊一個空消息監(jiān)聽
????@Override
????public void call() {????}
});
//參數(shù)1:接受人(上下文)
//參數(shù)2:定義的token
//參數(shù)3:實體的泛型約束
//參數(shù)4:執(zhí)行的回調監(jiān)聽
Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, String.class, new BindingConsumer<String>() {?????//注冊一個帶數(shù)據(jù)回調的消息監(jiān)聽
????@Override
????public void call(String s) {????}
});
//發(fā)送一個空消息,在需要回調的地方使用token發(fā)送消息
//參數(shù)1:定義的token
Messenger.getDefault().sendNoMsg(LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH);
//參數(shù)1:回調的實體
//參數(shù)2:定義的token
Messenger.getDefault().send("refresh",LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH);???//發(fā)送一個帶數(shù)據(jù)回調消息
?????token最好不要重名,不然可能就會出現(xiàn)邏輯上的bug,為了更好的維護和清晰邏輯,建議以aa_bb_cc的格式來定義token。aa:TOKEN,bb:ViewModel的類名,cc:動作名(功能名)。
????為了避免大量使用Messenger,建議只在ViewModel與ViewModel之間使用,View與ViewModel之間采用ObservableField去監(jiān)聽UI上的邏輯,可在繼承了BaseActivity或BaseFragment中重寫initViewObservable()方法來初始化UI的監(jiān)聽注冊了監(jiān)聽,當然也要解除它。在BaseActivity、BaseFragment的onDestroy()方法里已經(jīng)調用????Messenger.getDefault().unregister(viewModel);解除注冊,所以不用擔心忘記解除導致的邏輯錯誤和內存泄漏。
具體代碼實現(xiàn)關鍵字:
1、單例模式
2、散列表存儲與讀取
3、函數(shù)的重載
4、散列表的清除
備注:Messager的實現(xiàn)參考開源項目https://github.com/Kelin-Hong/MVVMLight。
3.8、CaoConfig類在項目中的作用是什么,實現(xiàn)原理是怎么樣的
解析:如果你的程序出現(xiàn)崩潰,它會檢測到(各種崩潰,比如空指針),會彈出一個頁面,提示你程序崩潰,你是否要關閉程序,還是重新啟動程序。
實現(xiàn)是基于開源項目:https://github.com/Ereza/CustomActivityOnCrash
????官方的概述:This library allows launching a custom activity when the app crashes, instead of showing the hated "Unfortunately, X has stopped" dialog。

項目中的實現(xiàn)思路關鍵字:
1、建造者模式
2、Thread.setDefaultUncaughtExceptionHandler()函數(shù)的使用
3、ActivityLifecycleCallbacks函數(shù)的使用
3.9、Rxbus的作用是什么,實現(xiàn)原理是怎么樣的?
解析:RxBus并不是一個庫,而是一種模式。相信大多數(shù)開發(fā)者都使用過EventBus,對RxBus也是很熟悉。由于MVVMabit中已經(jīng)加入RxJava,所以采用了RxBus代替EventBus作為事件總線通信,以減少庫的依賴。
使用方法:
在ViewModel中重寫registerRxBus()方法來注冊RxBus,重寫removeRxBus()方法來移除RxBus
private Disposable mSubscription;?????//訂閱者
@Override
public void registerRxBus() {???????//注冊RxBus
????super.registerRxBus();
????mSubscription = RxBus.getDefault().toObservable(String.class)
????????.subscribe(new Consumer<String>() {
????????????@Override
????????????public void accept(String s) throws Exception {
????????????}
????????});
????RxSubscriptions.add(mSubscription);????????????????//將訂閱者加入管理站
}
@Override
public void removeRxBus() {?????//移除RxBus
????super.removeRxBus();
????//將訂閱者從管理站中移除
????RxSubscriptions.remove(mSubscription);
}
RxBus.getDefault().post(object);??????????????????????//在需要執(zhí)行回調的地方發(fā)送
3.10、ContainerActivity類作用是什么,實現(xiàn)原理是怎么樣的
解析:一個盛裝Fragment的一個容器(代理)Activity,普通界面只需要編寫Fragment,使用此Activity盛裝,這樣就不需要每個界面都在AndroidManifest中注冊一遍。
使用方法:
????在ViewModel中調用BaseViewModel的方法開一個Fragment
startContainerActivity(你的Fragment類名.class.getCanonicalName())
????在ViewModel中調用BaseViewModel的方法,攜帶一個序列化實體打開一個Fragment
Bundle mBundle = new Bundle();
mBundle.putParcelable("entity", entity);
startContainerActivity(你的Fragment類名.class.getCanonicalName(), mBundle);
????在你的Fragment中取出實體:
Bundle mBundle = getArguments();
if (mBundle != null) {
????entity = mBundle.getParcelable("entity");
}
3.11、項目中是如何實現(xiàn)Activity和Fragment的堆棧管理的(任務棧管理),Task任務棧的作用是什么
解析:項目中通過AppManager類實現(xiàn)了Activity和Fragment類的任務棧管理。
代碼實現(xiàn)關鍵字:
1、單例模式
2、ActivityLifecycleCallbacks與FragmentLifecycleCallbacks的回調監(jiān)聽
3、Stack數(shù)據(jù)結構的基本使用:增、刪、查、改、遍歷等操作。
3.12、如何給不同的界面設置不同的Repository(數(shù)據(jù)獲取模塊Model),實現(xiàn)不同界面數(shù)據(jù)獲取的區(qū)分
解析:通過AppViewModelFactory類來初始化各個ViewModel的Model實例,不同的界面可以實現(xiàn)不同HttpDataSourceImpl和LocalDataSourceImpl,從而實現(xiàn)界面數(shù)據(jù)獲取的區(qū)分。
????如果不同的界面需要設置不同的Model,并且各界面的網(wǎng)絡數(shù)據(jù)模塊和本地數(shù)據(jù)模塊的邏輯相互分離,可以考慮各個界面單獨實現(xiàn)ViewModel的初始化。
代碼實現(xiàn):
????@Override
????public LoginViewModel initViewModel() {
????????return ViewModelProviders.of(this, new ViewModelProvider.Factory() {
????????????@NonNull
????????????@Override
????????????public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
????????????????if (modelClass.isAssignableFrom(LoginViewModel.class)) {
????????????????????HttpDataSource httpDataSource = HttpDataSourceImpl.getInstance(RetrofitClient.getInstance().create(DemoApiService.class));
????????????????????LocalDataSource localDataSource = LocalDataSourceImpl.getInstance();
????????????????????return (T) new LoginViewModel(getApplication(), DemoRepository.getInstance(httpDataSource, localDataSource));
????????????????}
????????????????throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName());
????????????}
????????}).get(LoginViewModel.class);
????}
3.13、項目中是如何封裝網(wǎng)絡請求模塊的
解析:項目中通過RetrofitClient類封裝網(wǎng)絡請求,通過建造者模式初始化對象OkHttpClient okHttpClient,
Retrofit retrofit的實現(xiàn)網(wǎng)絡請求參數(shù)配置,例如:攔截器的配置、Https的配置、Gson數(shù)據(jù)格式的轉換配置、cookie的相關的配置等,項目在請求返回時,返回對象Observable兼容Rxjava實例。
3.14、項目中是如何封裝Https相關網(wǎng)絡請求的
解析:項目中通過HttpsUtils類,實現(xiàn)對對象SSLSocketFactory sSLSocketFactory和X509TrustManager trustManager的初始化,從而實現(xiàn)對Https網(wǎng)路請求的設置,而在設置這兩個對象時,主要需要設置四個參數(shù):X509TrustManager trustManager、InputStream bksFile、String password、InputStream... certificates,所以我們需要根據(jù)項目的實際情況設置實現(xiàn)相關參數(shù)的設置。
3.15、項目中是如何通過Rxjava實現(xiàn)文件的下載和下載進度的回調
解析:項目中主要通過三個類組合實現(xiàn)文件的下載,三個類分別是:DownLoadManager、ProgressCallBack、
DownLoadSubscriber實現(xiàn),而這里面的進度回調主要通過如下代碼實現(xiàn):
????private Source source(Source source) {
????????return new ForwardingSource(source) {
????????????long bytesReaded = 0;
????????????@Override
????????????public long read(Buffer sink, long byteCount) throws IOException {
????????????????long bytesRead = super.read(sink, byteCount);
????????????????bytesReaded += bytesRead == -1 ? 0 : bytesRead;
????????????????//使用RxBus的方式,實時發(fā)送當前已讀取(上傳/下載)的字節(jié)數(shù)據(jù)
????????????????RxBus.getDefault().post(new DownLoadStateBean(contentLength(), bytesReaded, tag));
????????????????return bytesRead;
????????????}
????????};
????}
mSubscription = RxBus.getDefault().toObservable(DownLoadStateBean.class)
????????????????.observeOn(AndroidSchedulers.mainThread()) //回調到主線程更新UI
????????????????.subscribe(new Consumer<DownLoadStateBean>() {
????????????????????@Override
????????????????????public void accept(final DownLoadStateBean progressLoadBean) throws Exception {
????????????????????????progress(progressLoadBean.getBytesLoaded(), progressLoadBean.getTotal());
????????????????????}
????????????????});
實現(xiàn)思路:通過OkHttpClient設置ProgressInterceptor()攔截器實現(xiàn)對下載進度的監(jiān)聽,并通過RxBus把進度情況發(fā)送到ProgressCallBack實現(xiàn)類中。
3.16、項目中是如何對網(wǎng)絡請求中常見異常進行處理的?
解析:在工具類RxUtils中,封裝函數(shù)exceptionTransformer(),繼而通過observable.onErrorResumeNext(new HttpResponseFunc())和Observable.error()函數(shù)獲取異常信息,然后通過自定義類ExceptionHandle的靜態(tài)函數(shù)
ResponseThrowable handleException(Throwable e),實現(xiàn)對不同的網(wǎng)絡錯誤進行處理。
代碼示例:
model.demoGet()
.compose(RxUtils.schedulersTransformer())??//線程調度
.compose(RxUtils.exceptionTransformer())???//網(wǎng)絡錯誤的異常轉換, 這里可以換成自己的ExceptionHandle
.doOnSubscribe(this)...????????????????????//請求與ViewModel周期同步
3.17、項目中對Cookie的處理是如何實現(xiàn)的
解析:通過在okHttpClient中設置cookieJar的實現(xiàn)類,設置cookie的常見操作接口,并實現(xiàn)持久化存儲和內存存儲的兩種方式實現(xiàn)對連接cookie的處理。
3.18、項目中引入了開源框架BindingCollectionAdapter的作用是什么,實現(xiàn)原理是什么
解析:
官方說明:Easy way to bind collections to listviews and recyclerviews with the new Android Data Binding framework。
框架解析:其實根據(jù)前面的知識,如果想要一個控件在xml配置相應的屬性去支配ViewModel,立即可以想到@BindingAdapter,而改開源框架也是這么實現(xiàn)的,通過在類BindingCollectionAdapters中,配置List與RecyclerView中靜態(tài)初始化函數(shù),在通過xml的屬性配置和ViewModel的變量傳參,從而實現(xiàn)對ListView與RecyclerView控件在xml布局中進行相關的初始化,項目初始化的代碼就在BindingCollectionAdapters類的各個今天方法中,但是,如果我們想要添加控件的靈活性,并且適配更多的使用場景,我們需要引入泛型,抽象類,接口定義等方式,實現(xiàn)對復雜的控件(PageView、RecyclerView等)實現(xiàn)靈活性,擴展性更高的屬性配置。
代碼的接入實例:
<android.support.v7.widget.RecyclerView
????????????????android:layout_width="match_parent"
????????????????android:layout_height="match_parent"
????????????????binding:adapter="@{adapter}"
????????????????binding:itemBinding="@{viewModel.itemBinding}"
????????????????binding:items="@{viewModel.observableList}"
????????????????binding:layoutManager="@{LayoutManagers.linear()}"
????????????????binding:lineManager="@{LineManagers.horizontal()}" />
框架核心代碼實現(xiàn):
public class BindingCollectionAdapters {
????// AdapterView
????@SuppressWarnings("unchecked")
????@BindingAdapter(value = {"itemBinding", "itemTypeCount", "items", "adapter", "itemDropDownLayout", "itemIds", "itemIsEnabled"}, requireAll = false)
????public static <T> void setAdapter(AdapterView adapterView, ItemBinding<T> itemBinding, Integer itemTypeCount, List items, BindingListViewAdapter<T> adapter, @LayoutRes int itemDropDownLayout, BindingListViewAdapter.ItemIds<? super T> itemIds, BindingListViewAdapter.ItemIsEnabled<? super T> itemIsEnabled) {
????????if (itemBinding == null) {
????????????throw new IllegalArgumentException("onItemBind must not be null");
????????}
????????BindingListViewAdapter<T> oldAdapter = (BindingListViewAdapter<T>) unwrapAdapter(adapterView.getAdapter());
????????if (adapter == null) {
????????????if (oldAdapter == null) {
????????????????int count = itemTypeCount != null ? itemTypeCount : 1;
????????????????adapter = new BindingListViewAdapter<>(count);
????????????} else {
????????????????adapter = oldAdapter;
????????????}
????????}
????????adapter.setItemBinding(itemBinding);
????????adapter.setDropDownItemLayout(itemDropDownLayout);
????????adapter.setItems(items);
????????adapter.setItemIds(itemIds);
????????adapter.setItemIsEnabled(itemIsEnabled);
????????if (oldAdapter != adapter) {
????????????adapterView.setAdapter(adapter);
????????}
????}
........
}
ViewModel中的核心代碼:
????//給RecyclerView添加ObservableList
????public ObservableList<MultiItemViewModel> observableList = new ObservableArrayList<>();
????//RecyclerView多布局添加ItemBinding
????public ItemBinding<MultiItemViewModel> itemBinding = ItemBinding.of(new OnItemBind<MultiItemViewModel>() {
????????@Override
????????public void onItemBind(ItemBinding itemBinding, int position, MultiItemViewModel item) {
????????????//通過item的類型, 動態(tài)設置Item加載的布局
????????????String itemType = (String) item.getItemType();
????????????if (MultiRecycleType_Head.equals(itemType)) {
????????????????//設置頭布局
????????????????itemBinding.set(BR.viewModel, R.layout.item_multi_head);
????????????} else if (MultiRecycleType_Left.equals(itemType)) {
????????????????//設置左布局
????????????????itemBinding.set(BR.viewModel, R.layout.item_multi_rv_left);
????????????} else if (MultiRecycleType_Right.equals(itemType)) {
????????????????//設置右布局
????????????????itemBinding.set(BR.viewModel, R.layout.item_multi_rv_right);
????????????}
????????}
????});
開源項目:https://github.com/evant/binding-collection-adapter(MVVM之列表綁定神器BindingCollectionAdapter)
四:項目中可以優(yōu)化的點匯總
1、在UIChangeLiveData中,引入loadsir開源框架,實現(xiàn)項目通用的加載、請求異常、空數(shù)據(jù)、正常數(shù)據(jù)界面等。
2、考慮在ActivityLifecycleCallbacks中封裝更多Acitivity中通用的東西(toolbar、沉浸式等)
3、BaseFragment添加懶加載邏輯
4、各個界面的Model設置模塊,應該根據(jù)不同界面實現(xiàn)各個不同的model。
5、網(wǎng)絡請求模塊修改為RxHttp實現(xiàn)網(wǎng)絡請求的進一步封裝。
6、參考使用RxErrorHandler實現(xiàn)對網(wǎng)絡請求異常的處理。
五:項目知識點舉一反三
知識點一:MutableLiveData與MediatorLiveData的區(qū)別
解析:MutableLiveData是LiveData的子類,它公開setValue和postValue方法(第二個方法是線程安全的),因此您可以將值分配給任何活動的觀察者,MediatorLiveData是MutableLiveData的子類,MediatorLiveData可以觀察其他LiveData對象(源)并對它們的onChange事件作出反應,這將使您可以控制何時傳播事件或進行特定操作。
知識點二:項目中的自定義SingleLiveEvent的作用是什么
背景:只要使用過一段時間的LiveData就會發(fā)現(xiàn),LiveData會經(jīng)常多次回調數(shù)據(jù)。我們經(jīng)常碰到的這個問題,我們的ViewModel里是給Activity持有的,并且里面有一個LiveData數(shù)據(jù),我們A_Fragment現(xiàn)在獲得Activity的ViewModel并且注冊LiveData數(shù)據(jù)成為觀察者,這個時候我們setValue()就會讓前臺的A_Fragment得到一次LiveData數(shù)據(jù),接下來操作A_Fragment啟動B_Fragment,在返回到A_Fragment。你會發(fā)現(xiàn)只要再次注冊LiveData的observe(this, new Observer ...),那么A_Fragment里面又會接收到一次LiveData的數(shù)據(jù)。
問題出現(xiàn)原因:
1、一部分原因是LiveData的機制,就是向所有前臺Fragment或者Activity發(fā)送數(shù)據(jù)。只要注冊的觀察者在前臺就必定會收到這個數(shù)據(jù)。
2、另一部分的原因是對ViewModel理解不深刻,理論上只有在Activity保存的ViewModel它沒被銷毀過就會一直給新的前臺Fragment觀察者發(fā)送數(shù)據(jù)。我們需要管理好ViewModel的使用范圍。 比如只需要在Fragment里使用的ViewModel就不要給Activity保管。而根Activity的ViewModel只需要做一下數(shù)據(jù)共享與看情況使用LiveData。
解決方法:
1、就是管理好ViewModel的范圍,如果業(yè)務范圍只跟某個Fragment有關,那么最好就只給這個Fragment使用。這樣Fragment在銷毀或者創(chuàng)建的時候,也會銷毀ViewModel與創(chuàng)建ViewModel,ViewModel攜帶的LiveData就是全新的不會在發(fā)送之前設置的數(shù)據(jù)。
2、實現(xiàn)類 SingleLiveEvent,其中的機制是用一個原子AtomicBoolean記錄一次setValue。在發(fā)送一次后在將AtomicBoolean設置為false,阻止后續(xù)前臺重新觸發(fā)時的數(shù)據(jù)發(fā)送。
六:擴展閱讀
1、https://github.com/goldze/MVVMHabitComponent(組件化項目結構)
2、https://github.com/goldze/MVVMHabit(DataBinding+LiveData+ViewModel項目架構)
3、https://blog.csdn.net/ican87/article/details/86612733(阿里路由框架ARouter原理分析總結)
4、https://blog.csdn.net/yu75567218/article/details/86706290(自己實現(xiàn)全局通信messenger)
5、https://github.com/Kelin-Hong/MVVMLight(MVVM Light Toolkit使用指南)
6、http://www.itdecent.cn/p/2fc41a310f79(如何構建Android MVVM應用程序)
7、https://zhuanlan.zhihu.com/p/133949967(兩步使用 LiveData 替換 Observable Field)
8、https://github.com/Ereza/CustomActivityOnCrash(Android檢測程序崩潰框架)
9、https://github.com/JessYanCoding/RxErrorHandler(RxJava的錯誤處理庫)
10、https://github.com/KunMinX/Jetpack-MVVM-Best-Practice(難得一見的Jetpack MVVM最佳實踐)
11、https://www.cnblogs.com/guanxinjing/p/12669506.html(SingleLiveEvent解決LiveData或者MutableLiveData多次回調的問題)
12、https://blog.csdn.net/yu75567218/article/details/87860020(MVVM之列表綁定神器BindingCollectionAdapter)