開源項(xiàng)目MVVMHabitComponent詳解

? ? ? ?當(dāng)我們開發(fā)一個(gè)新項(xiàng)目拾碌,常常需要先搭建一個(gè)項(xiàng)目結(jié)構(gòu),就好像建房子街望,你把地基和骨架搭建好了校翔,搭建完善了,你在根據(jù)不同的業(yè)務(wù)需求灾前,大部分情況下只是在基本的架構(gòu)上面做開發(fā)而已防症,這樣大大的提高的開發(fā)效率,同時(shí)如果前期搭建了一個(gè)好的架構(gòu)哎甲,會(huì)對(duì)項(xiàng)目的持續(xù)迭代更新和項(xiàng)目的代碼質(zhì)量有著至關(guān)重要的作用蔫敲,所以了解并知道如何搭建一個(gè)通用的項(xiàng)目架構(gòu)是非常必要了。


?知識(shí)點(diǎn)匯總:

一:項(xiàng)目簡(jiǎn)介

二:MVVMHabitComponent的項(xiàng)目框架疑問(wèn)與解答

三:MVVMHabit的項(xiàng)目框架疑問(wèn)與解答

四:項(xiàng)目中可以優(yōu)化的點(diǎn)匯總

五:項(xiàng)目知識(shí)點(diǎn)舉一反三

六: 擴(kuò)展閱讀


一:項(xiàng)目簡(jiǎn)介

? ? ? ?MVVMHabit是以谷歌DataBinding+LiveData+ViewModel框架為基礎(chǔ)炭玫,整合Okhttp+RxJava+Retrofit+Glide等流行模塊奈嘿,加上各種原生控件自定義的BindingAdapter,讓事件與數(shù)據(jù)源完美綁定的一款容易上癮的實(shí)用性MVVM快速開發(fā)框架吞加,而MVVMHabitComponent是基于MVVMHabit的基礎(chǔ)上實(shí)現(xiàn)的組件化方案架構(gòu)裙犹,高內(nèi)聚,低耦合榴鼎,代碼邊界清晰伯诬,每一個(gè)組件都可以拆分出來(lái)獨(dú)立運(yùn)行。所有組件寄托于宿主App巫财,加載分離的各個(gè)組件盗似,各自編譯自己的模塊,有利于多人團(tuán)隊(duì)協(xié)作開發(fā)平项。

項(xiàng)目地址:

1赫舒、https://github.com/goldze/MVVMHabit

2、https://github.com/goldze/MVVMHabitComponent


二:MVVMHabitComponent的項(xiàng)目框架疑問(wèn)與解答

2.1闽瓢、組件化項(xiàng)目的架構(gòu)圖

2.2接癌、組件化項(xiàng)目的優(yōu)點(diǎn)

2.3、如何單獨(dú)運(yùn)行Module項(xiàng)目和整體運(yùn)行項(xiàng)目扣讼,單獨(dú)運(yùn)行項(xiàng)目時(shí)build.gradle需要配置什么?

2.4缺猛、如何執(zhí)行單個(gè)Module項(xiàng)目和完整項(xiàng)目的初始化(Application初始化模塊)

2.5、互不依賴的Module之間,相互跳轉(zhuǎn)Activity的方式有哪些椭符,都有什么優(yōu)缺點(diǎn)

2.6荔燎、LiveData 與 ObservableField的區(qū)別

2.7、路由框架ARouter如何實(shí)現(xiàn)不同組件Activity和Fragment的項(xiàng)目跳轉(zhuǎn)

2.8销钝、路由框架Arouter如何實(shí)現(xiàn)不同組件間函數(shù)的相互調(diào)用


2.1有咨、組件化項(xiàng)目的架構(gòu)圖

解析:組件化的架構(gòu)一般分為三層:應(yīng)用層,組件層蒸健,基礎(chǔ)層座享。

應(yīng)用層:一般實(shí)現(xiàn)很少的業(yè)務(wù)邏輯婉商,通常被稱為殼應(yīng)用。

組件層:隨著項(xiàng)目迭代和業(yè)務(wù)擴(kuò)展渣叛,組件層的組件模塊會(huì)越來(lái)越多丈秩,各個(gè)組件Module的代碼也會(huì)越來(lái)越多。

基礎(chǔ)層:如果剛開始搭建項(xiàng)目的架構(gòu)時(shí)淳衙,最需要完善的就是基礎(chǔ)層的代碼癣籽,基礎(chǔ)層的功能模塊越完善,組件層的開發(fā)

效率就會(huì)越高滤祖,后續(xù)改的也相對(duì)較少。


2.2瓶籽、組件化項(xiàng)目的優(yōu)點(diǎn)

優(yōu)點(diǎn)一:業(yè)務(wù)解耦

各個(gè)業(yè)務(wù)模塊通過(guò)組件化架構(gòu)匠童,實(shí)現(xiàn)了解耦,不同的模塊相互獨(dú)立塑顺。

優(yōu)點(diǎn)二:并行開發(fā)

在開發(fā)項(xiàng)目時(shí)汤求,各個(gè)不同的組件模塊可以分開開發(fā),并且每個(gè)模塊可以獨(dú)立運(yùn)行严拒。

優(yōu)點(diǎn)三:提高開發(fā)效率

由于項(xiàng)目模塊的基礎(chǔ)模塊已經(jīng)實(shí)現(xiàn)扬绪,各個(gè)組件的開發(fā)人員只需要針對(duì)自己的模塊開發(fā),并獨(dú)立運(yùn)行自己模塊的代碼裤唠,

挤牛,通過(guò)路由和服務(wù)外發(fā)接口,實(shí)現(xiàn)與其他組件的聯(lián)系种蘸,大大提高了開發(fā)效率墓赴。


2.3、如何單獨(dú)運(yùn)行Module項(xiàng)目和整體運(yùn)行項(xiàng)目航瞭,單獨(dú)運(yùn)行項(xiàng)目時(shí)build.gradle需要配置什么?

1诫硕、在gradle.properties中,重新設(shè)置isBuildModule=false刊侯。

2章办、if (isBuildModule.toBoolean()) {

????//作為獨(dú)立App應(yīng)用運(yùn)行

????apply plugin: 'com.android.application'

} else {

????//作為組件運(yùn)行

????apply plugin: 'com.android.library'

}

3、defaultConfig {

????????//如果是獨(dú)立模塊滨彻,則使用當(dāng)前組件的包名

????????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í)行單個(gè)Module項(xiàng)目和完整項(xiàng)目的初始化(Application初始化模塊)

解析:項(xiàng)目中主要實(shí)現(xiàn)思路是:接口定義疮绷、接口實(shí)現(xiàn)翰舌、反射調(diào)用不同組件的接口實(shí)現(xiàn)類,執(zhí)行初始化工作冬骚。

第一步:在基礎(chǔ)層定義初始化接口

第二步:各個(gè)組件實(shí)現(xiàn)接口的實(shí)現(xiàn)類

第三部:在應(yīng)用層通過(guò)反射調(diào)用各個(gè)組件的實(shí)現(xiàn)類函數(shù)椅贱。(通過(guò)單例+靜態(tài)類路徑封裝)


2.5懂算、互不依賴的Module之間,相互跳轉(zhuǎn)Activity的方式有哪些,都有什么優(yōu)缺點(diǎn)

方式一:隱式跳轉(zhuǎn)

這是一種解決方法庇麦,但是一個(gè)項(xiàng)目中不可能所有的跳轉(zhuǎn)都是隱式的计技,這樣Manifest文件會(huì)有很多過(guò)濾配置,而且非常不利于后期維護(hù)山橄。

方式二:反射

用反射拿到Activity的class文件也可以實(shí)現(xiàn)跳轉(zhuǎn)垮媒,但是大量的使用反射跳轉(zhuǎn)對(duì)性能會(huì)有影響。

方式三:路由框架(ARouter原理)

1航棱、在每個(gè)需要對(duì)其他module提供調(diào)用的Activity中睡雇,都會(huì)聲明類似下面@Route注解,我們稱之為路由地址饮醇。


2它抱、編譯期時(shí)生成路由映射,路由框架會(huì)在項(xiàng)目的編譯期通過(guò)注解處理器apt掃描所有添加@Route注解的Activity類朴艰,然后將Route注解中的path地址和Activity.class文件映射關(guān)系保存到它自己生成的java文件中观蓄,只要拿到了映射關(guān)系便能拿到Activity.class。

3祠墅、不同module之間啟動(dòng)Activity

4侮穿、ARouter使用

備注:因?yàn)闆]有相互引用,startActivity()是實(shí)現(xiàn)不了的毁嗦,必須需要一個(gè)協(xié)定的通信方式亲茅。


2.6、LiveData 與 ObservableField的區(qū)別

一:ObservableField只有在數(shù)據(jù)發(fā)生改變時(shí)UI才會(huì)收到通知金矛,而LiveData不同芯急,只要你postValue或者setValue,UI都會(huì)收到通知驶俊,不管數(shù)據(jù)有無(wú)變化娶耍。

二:LiveData能感知Activity的生命周期,在Activity不活動(dòng)的時(shí)候不會(huì)觸發(fā)饼酿,例如一個(gè)Activity不在任務(wù)棧頂部榕酒。

三:支持Transformations,而且可以與許多架構(gòu)組件 (如Room故俐、WorkManager) 相互配合使用想鹰。


2.7、路由框架ARouter如何實(shí)現(xiàn)不同組件Activity和Fragment的項(xiàng)目跳轉(zhuǎn)

解析:Arouter通過(guò)編譯時(shí)動(dòng)態(tài)生成路由表的方式药版,實(shí)現(xiàn)不同界面的跳轉(zhuǎn)辑舷,底層實(shí)現(xiàn)思路:apt注解解析器、javapoat

動(dòng)態(tài)生成代碼槽片、startActivity實(shí)現(xiàn)界面跳轉(zhuǎn)的何缓,而Fragment的跳轉(zhuǎn)更多只是通過(guò)其他模塊的服務(wù)外發(fā)獲取不同模塊

的Fragment實(shí)例肢础,從而實(shí)現(xiàn)Fragment的跳轉(zhuǎn)。


2.8碌廓、路由框架Arouter如何實(shí)現(xiàn)不同組件間函數(shù)的相互調(diào)用

實(shí)現(xiàn)思路主要是:

第一步:基礎(chǔ)層的通信接口定義(提供服務(wù)接口)

第二步:組件層的接口實(shí)現(xiàn)

第三步:路由實(shí)現(xiàn)函數(shù)調(diào)用传轰。


三:MVVMHabit的項(xiàng)目框架疑問(wèn)與解答

3.1、BaseActivity谷婆、BaseFragment慨蛙、BaseViewModel類中分別封裝了什么

3.2、使用DataBinging時(shí)纪挎,會(huì)遇到四種無(wú)法編譯通過(guò)的場(chǎng)景期贫,分別是什么,應(yīng)該如何處理

3.3异袄、BR自動(dòng)生成的類中唯灵,有四個(gè)常量,分別代表什么意思

3.4隙轻、如何使用DataBinging定義自定義控件屬性

3.5、RxAppCompatActivity和RxFragment類的作用是什么

3.6垢揩、用于ViewModel與xml之間的數(shù)據(jù)綁定玖绿,執(zhí)行的命令回調(diào)的實(shí)現(xiàn)原理

3.7、Messager類在項(xiàng)目中的作用是什么叁巨,實(shí)現(xiàn)原理是怎么樣的

3.8斑匪、CaoConfig類在項(xiàng)目中的作用是什么,實(shí)現(xiàn)原理是怎么樣的

3.9锋勺、Rxbus的作用是什么蚀瘸,實(shí)現(xiàn)原理是怎么樣的

3.10、ContainerActivity類作用是什么庶橱,實(shí)現(xiàn)原理是怎么樣的

3.11贮勃、項(xiàng)目中是如何實(shí)現(xiàn)Activity和Fragment的堆棧管理的(任務(wù)棧管理),Task任務(wù)棧的作用是什么

3.12苏章、如何給不同的界面設(shè)置不同的Repository(數(shù)據(jù)獲取模塊Model)寂嘉,實(shí)現(xiàn)不同界面數(shù)據(jù)獲取的區(qū)分

3.13、項(xiàng)目中是如何封裝網(wǎng)絡(luò)請(qǐng)求模塊的

3.14枫绅、項(xiàng)目中是如何封裝Https相關(guān)網(wǎng)絡(luò)請(qǐng)求的

3.15泉孩、項(xiàng)目中是如何通過(guò)Rxjava實(shí)現(xiàn)文件的下載和下載進(jìn)度的回調(diào)

3.16、項(xiàng)目中是如何對(duì)網(wǎng)絡(luò)請(qǐng)求中常見異常進(jìn)行處理的

3.17并淋、項(xiàng)目中對(duì)Cookie的處理是如何實(shí)現(xiàn)的

3.18寓搬、項(xiàng)目中引入了開源框架BindingCollectionAdapter的作用是什么,實(shí)現(xiàn)原理是什么


3.1县耽、BaseActivity句喷、BaseFragment镣典、BaseViewModel類中分別封裝了什么

一:BaseActivity

1.1、在ActivityLifecycleCallbacks中脏嚷,通過(guò)AppManager管理ActivityTask任務(wù)骆撇,這里我們可以發(fā)揮一下自己的

想象,可以實(shí)現(xiàn)更多不同界面的封裝父叙。

1.2神郊、通過(guò)對(duì)類的泛型,實(shí)現(xiàn)不同界面的DataBinging和ViewModel的初始化適配趾唱,各個(gè)界面的DataBinging需要繼承

ViewDataBinding涌乳,各個(gè)不同界面的ViewModel需要實(shí)現(xiàn)不同的AndroidViewModel,

DataBinding的初始化代碼:

binding = DataBindingUtil.setContentView(this, initContentView(savedInstanceState));

binding.setVariable(viewModelId, viewModel); //關(guān)聯(lián)ViewModel

binding.setLifecycleOwner(this);?????????????//支持LiveData綁定xml甜癞,數(shù)據(jù)改變夕晓,UI自動(dòng)會(huì)更新

ViewModel的初始化代碼:

viewModel = initViewModel();

????????if (viewModel == null) {

????????????Class modelClass;

????????????Type type = getClass().getGenericSuperclass();

????????????if (type instanceof ParameterizedType) {

????????????????modelClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];

????????????} else {

????????????????//如果沒有指定泛型參數(shù),則默認(rèn)使用BaseViewModel

????????????????modelClass = BaseViewModel.class;

????????????}

????????????viewModel = (VM) createViewModel(this, modelClass);

????????}

????????binding.setVariable(viewModelId, viewModel);????//關(guān)聯(lián)ViewModel

????????getLifecycle().addObserver(viewModel);??????????//讓ViewModel擁有View的生命周期感應(yīng)

????????viewModel.injectLifecycleProvider(this);????????//注入RxLifecycle生命周期

????從上面的代碼中悠咱,我們看到viewModel如果為空蒸辆,里面有一堆陌生的函數(shù)調(diào)用,最后也實(shí)現(xiàn)了viewModel的初始化析既,具體我們需要了解相關(guān)函數(shù)的作用躬贡。

1、getClass().getGenericSuperclass()

????返回表示此 Class 所表示的實(shí)體(類眼坏、接口拂玻、基本類型或 void)的直接超類的 Type,然后將其轉(zhuǎn)換宰译。ParameterizedType檐蚜。

2、getActualTypeArguments()

????返回表示此類型實(shí)際類型參數(shù)的 Type 對(duì)象的數(shù)組沿侈。[0]就是這個(gè)數(shù)組中第一個(gè)了闯第。簡(jiǎn)而言之就是獲得超類的泛型參數(shù)的實(shí)際類型。

????通過(guò)對(duì)這兩個(gè)主要函數(shù)的解析缀拭,應(yīng)該就能很好的理解上面的代碼了吧乡括。

public <T extends ViewModel> T createViewModel(FragmentActivity activity, Class<T> cls) {

????????return ViewModelProviders.of(activity).get(cls);

????}

注意:

????public void refreshLayout() {??????????????????//??刷新布局通過(guò)

????????if (viewModel != null) {

????????????binding.setVariable(viewModelId, viewModel);

????????}

????}

1.3、注意到BaseActivity繼承RxAppCompatActivity智厌,這是Rxjava的家庭成員rxlifecycle提供的類诲泌,方便的實(shí)現(xiàn)Rxjava的異步任務(wù)的解綁,防止內(nèi)存泄漏铣鹏,有興趣可以看看類的源碼敷扫,代碼并不復(fù)雜。

1.4、通過(guò)registorUIChangeLiveDataCallBack函數(shù)葵第,注冊(cè)ViewModel與View的契約UI回調(diào)事件绘迁,把不同界面中常用的一些操作封裝在ViewModel中,例如:?jiǎn)?dòng)對(duì)話框卒密、Toast提示缀台、界面跳轉(zhuǎn)等操作,這里我們可以根據(jù)項(xiàng)目進(jìn)行擴(kuò)展哮奇,例如添加加載膛腐,空數(shù)據(jù),網(wǎng)絡(luò)異常等界面鼎俘。

1.5哲身、回調(diào)函數(shù)onDestroy中,實(shí)現(xiàn)常規(guī)的反注冊(cè)相關(guān)函數(shù)贸伐,代碼如下:?????????????????Messenger.getDefault().unregister(viewModel);??//解除Messenger注冊(cè)

????????if (viewModel != null) {

????????????viewModel.removeRxBus();

????????}

????????if(binding != null){

????????????binding.unbind();

????????}

1.6勘天、我們注意到在onDestroy()中解除Messenger注冊(cè)了。


二:BaseFragment

2.1捉邢、大體上BaseFragment的初始化的東西都和BaseActivity中保持一致脯丝,只是在相關(guān)的生命周期中有一點(diǎn)區(qū)別和調(diào)用的初始化函數(shù)稍微有點(diǎn)不同。

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巾钉、在回調(diào)函數(shù)public void onViewCreated(View view, @Nullable Bundle savedInstanceState)中執(zhí)行主要的界面初始化行為。

2.4秘案、RxFragment與RxAppCompatActivity有著異曲同工的效果。


三:BaseViewModel

3.1潦匈、通過(guò)定義對(duì)象CompositeDisposable阱高,管理RxJava,解決異步操作造成的內(nèi)存泄漏茬缩。

3.2赤惊、通過(guò)弱應(yīng)用對(duì)象lifecycle,注入RxLifecycle生命周期凰锡。

3.3未舟、通過(guò)類的泛型初始化Model層的對(duì)象初始化,實(shí)現(xiàn)MVVM的Model層掂为,統(tǒng)一模塊的數(shù)據(jù)倉(cāng)庫(kù)裕膀,包含網(wǎng)絡(luò)數(shù)據(jù)和本地?cái)?shù)據(jù)。

3.4勇哗、內(nèi)部類UIChangeLiveData實(shí)現(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;

這里需要補(bǔ)充一下:SingleLiveEvent類也是自定義類,繼承MutableLiveData類欲诺。

3.5抄谐、BaseViewModel實(shí)現(xiàn)了接口Consumer<Disposable>渺鹦,從而使得在非ViewModel的異步任務(wù)也可以解除異步綁定事件,防止內(nèi)存泄露蛹含。

這里順便附上MVVM的架構(gòu)圖:


3.2毅厚、使用DataBinging時(shí),會(huì)遇到五種無(wú)法編譯通過(guò)的場(chǎng)景浦箱,分別是什么吸耿,應(yīng)該如何處理?

解析:使用databinding其實(shí)有個(gè)缺點(diǎn)憎茂,就是會(huì)遇到一些編譯錯(cuò)誤珍语,而AS不能很好的定位到錯(cuò)誤的位置,這對(duì)于剛開始使用databinding的開發(fā)者來(lái)說(shuō)是一個(gè)比較郁悶的事竖幔,下面就匯總出相關(guān)的問(wèn)題板乙。

1、綁定錯(cuò)誤

?????綁定錯(cuò)誤是一個(gè)很常見的錯(cuò)誤拳氢,基本都會(huì)犯募逞。比如TextView的 android:text="" ,本來(lái)要綁定的是一個(gè)String類型馋评,結(jié)果你不小心放接,可能綁了一個(gè)Boolean上去,或者變量名寫錯(cuò)了留特,這時(shí)候編輯器不會(huì)報(bào)紅錯(cuò)纠脾,而是在點(diǎn)編譯運(yùn)行的時(shí)候,在AS的Messages中會(huì)出現(xiàn)錯(cuò)誤提示蜕青,如下圖:

解決方法:把錯(cuò)誤提示拉到最下面 (上面的提示找不到BR類這個(gè)不要管它)苟蹈,看最后一個(gè)錯(cuò)誤 ,這里會(huì)提示是哪個(gè)xml出了錯(cuò)右核,并且會(huì)定位到行數(shù)慧脱,按照提示找到對(duì)應(yīng)位置,即可解決該編譯錯(cuò)誤的問(wèn)題贺喝。

注意: 行數(shù)要+1菱鸥,意思是上面報(bào)出第33行錯(cuò)誤,實(shí)際是第34行錯(cuò)誤躏鱼,AS定位的不準(zhǔn)確 (這可能是它的一個(gè)bug)氮采。


2、xml導(dǎo)包錯(cuò)誤

????在xml中需要導(dǎo)入ViewModel或者一些業(yè)務(wù)相關(guān)的類染苛,假如在xml中導(dǎo)錯(cuò)了類扳抽,那一行則會(huì)報(bào)紅,但是res/layout卻沒有錯(cuò)誤提示,有一種場(chǎng)景贸呢,非常特殊镰烧,不容易找出錯(cuò)誤位置。就是你寫了一個(gè)xml楞陷,導(dǎo)入了一個(gè)類怔鳖,比如XXXUtils,后來(lái)因?yàn)闃I(yè)務(wù)需求固蛾,把那個(gè)XXXUtils刪了结执,這時(shí)候res/layout下不會(huì)出現(xiàn)任何錯(cuò)誤,而你在編譯運(yùn)行的時(shí)候艾凯,才會(huì)出現(xiàn)錯(cuò)誤日志献幔。苦逼的是趾诗,不會(huì)像上面那樣提示哪一個(gè)xml文件蜡感,哪一行出錯(cuò)了,最后一個(gè)錯(cuò)誤只是一大片的報(bào)錯(cuò)報(bào)告恃泪。如下圖:

解決方法:同樣找到最后一個(gè)錯(cuò)誤提示郑兴,找到Cannot resolve type for xxx這一句 (xxx是類名)递宅,然后使用全局搜索 (Ctrl+H) 伯襟,搜索哪個(gè)xml引用了這個(gè)類,跟蹤點(diǎn)擊進(jìn)去炭菌,在xml就會(huì)出現(xiàn)一個(gè)紅錯(cuò)览效,看到錯(cuò)誤你就會(huì)明白了却舀,這樣就可解決該編譯錯(cuò)誤的問(wèn)題。


3锤灿、build錯(cuò)誤

???構(gòu)建多module工程時(shí)挽拔,如出現(xiàn)【4.1.1、綁定錯(cuò)誤】衡招,且你能確定這個(gè)綁定是沒有問(wèn)題的,經(jīng)過(guò)修改后出現(xiàn)下圖錯(cuò)誤:

解決方法:這種是databinding比較大的坑每强,清理始腾、重構(gòu)和刪build都不起作用,網(wǎng)上很難找到方法空执。經(jīng)過(guò)試驗(yàn)浪箭,解決辦法是手動(dòng)創(chuàng)建異常中提到的文件夾,或者拷貝上一個(gè)沒有報(bào)錯(cuò)的版本中對(duì)應(yīng)的文件夾辨绊,可以解決這個(gè)異常奶栖。


4、自動(dòng)生成類錯(cuò)誤

????有時(shí)候在寫完xml時(shí),databinding沒有自動(dòng)生成對(duì)應(yīng)的Binding類及屬性宣鄙。比如新建了一個(gè)activity_login.xml袍镀,按照databinding的寫法加入<layout> <variable>后,理論上會(huì)自動(dòng)對(duì)應(yīng)生成ActivityLoginBinding.java類和variable的屬性冻晤,可能是as對(duì)databding的支持還不夠吧苇羡,有時(shí)候偏偏就不生成,導(dǎo)致BR.xxx報(bào)紅等一些莫名的錯(cuò)誤鼻弧。

解決方法:其實(shí)確保自己的寫法沒有問(wèn)題设江,是可以直接運(yùn)行的,報(bào)紅不一定是你寫的有問(wèn)題攘轩,也有可能是編譯器抽風(fēng)了叉存。或者使用下面的辦法

第一招:Build->Clean Project度帮;

第二招:Build->Rebuild Project歼捏;

第三招:重啟大法。


5够傍、gradle錯(cuò)誤

? ? ? ?如果遇到以下編譯問(wèn)題:

錯(cuò)誤: 無(wú)法將類 BindingRecyclerViewAdapters中的方法 setAdapter應(yīng)用到給定類型; 需要: RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds<? super T>,ViewHolderFactory 找到: RecyclerView,ItemBinding,ObservableList,BindingRecyclerViewAdapter<CAP#1>,ItemIds,ViewHolderFactory 原因: 推斷類型不符合等式約束條件 推斷: CAP#1 等式約束條件: CAP#1,NetWorkItemViewModel 其中, T是類型變量: T擴(kuò)展已在方法 setAdapter(RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds<? super T>,ViewHolderFactory)中聲明的Object 其中, CAP#1是新類型變量: CAP#1從?的捕獲擴(kuò)展Object甫菠。

????一般是由于gradle plugin版本3.5.1造成的,請(qǐng)換成gradle plugin 3.5.0以下版本冕屯。


3.3寂诱、BR自動(dòng)生成的類中,有四個(gè)常量安聘,分別代表什么意思

解析:項(xiàng)目中生成的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類對(duì)象的映射,主要用于加載layout返回對(duì)應(yīng)的ViewDataBinding對(duì)象浴韭。


3.4丘喻、如何使用DataBinging定義自定義控件屬性

第一步:在項(xiàng)目中的attrs.xml文件中,定義了一些通用View控件和定義不同自定義控件或系統(tǒng)控件的相關(guān)自定義屬性念颈,部分定義如下:

????<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" />

第二步:然后為不同的控件泉粉,定義其相關(guān)的自定義綁定適配器,需要重點(diǎn)了解BindingAdapter注解的使用榴芳,參考代碼如下嗡靡,圖片控件的設(shè)置。

????@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的焦點(diǎn)發(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í)行的命令回調(diào), 用于ViewModel與xml之間的數(shù)據(jù)綁定讨彼,由于需要綁定的命令回調(diào)的類型和場(chǎng)景比較多,項(xiàng)目中實(shí)現(xiàn)了相關(guān)類來(lái)適配不同的情況柿祈,相關(guān)具體實(shí)現(xiàn)解析哈误,請(qǐng)繼續(xù)查看后文哩至,實(shí)現(xiàn)類如下:

備注:代碼實(shí)現(xiàn)參考開源項(xiàng)目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注解的官方代碼實(shí)現(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)綁定關(guān)聯(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="請(qǐng)輸入用戶名"

???????????android:text="@={viewModel.userName}"

???????????android:textColor="@color/textColor"

???????????android:textColorHint="@color/textColorHint"

???????????android:textSize="16sp"

???????????binding:onFocusChangeCommand="@{viewModel.onFocusChangeCommand}" />

@BindingAdapter

這個(gè)注解用于支持自定義屬性菜谣,或者是修改原有屬性。注解值可以是已有的 xml 屬性晚缩,例如 android:src尾膊、android:text等,也可以自定義屬性然后在 xml 中使用荞彼。


3.5冈敛、RxAppCompatActivity和RxFragment類的作用是什么?

解析:Rxjava的家庭成員rxlifecycle鸣皂,該開源框架可以在Rxjava實(shí)現(xiàn)異步任務(wù)時(shí)抓谴,防止退出界面導(dǎo)致的內(nèi)存泄漏問(wèn)題,該開源框架的實(shí)現(xiàn)類如下:


3.6寞缝、用于ViewModel與xml之間的數(shù)據(jù)綁定癌压,執(zhí)行的命令回調(diào)的實(shí)現(xiàn)原理

項(xiàng)目中通過(guò)定義通用的命令相關(guān)類,實(shí)現(xiàn)控件的操作事件以命令的形式進(jìn)行傳遞荆陆,主要實(shí)現(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();

????}

}

項(xiàng)目中通過(guò)定義的類如下所示:

備注:如果發(fā)現(xiàn)控件需要的綁定操作命令需要在當(dāng)前的綁定指令類中無(wú)法滿足相關(guān)控件的實(shí)現(xiàn)滩届,可以根據(jù)項(xiàng)目的實(shí)際需要,定義相關(guān)的命令相關(guān)類被啼,實(shí)現(xiàn)相關(guān)功能帜消,這里的思考一下觸摸事件的命令相關(guān)類應(yīng)該如何實(shí)現(xiàn)。


3.7浓体、Messager類在項(xiàng)目中的作用是什么泡挺,實(shí)現(xiàn)原理是怎么樣的?

解析:Messenger是一個(gè)輕量級(jí)全局的消息通信工具命浴,在我們的復(fù)雜業(yè)務(wù)中娄猫,難免會(huì)出現(xiàn)一些交叉的業(yè)務(wù),比如ViewModel與ViewModel之間需要有數(shù)據(jù)交換生闲,這時(shí)候可以輕松地使用Messenger發(fā)送一個(gè)實(shí)體或一個(gè)空消息媳溺,將事件從一個(gè)ViewModel回調(diào)到另一個(gè)ViewModel中。

使用方法:

public static final String TOKEN_LOGINVIEWMODEL_REFRESH = "token_loginviewmodel_refresh";???//定義一個(gè)靜態(tài)String類型的字符串token

在ViewModel中注冊(cè)消息監(jiān)聽:

//參數(shù)1:接受人(上下文)

//參數(shù)2:定義的token

//參數(shù)3:執(zhí)行的回調(diào)監(jiān)聽

Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, new BindingAction() {????//注冊(cè)一個(gè)空消息監(jiān)聽

????@Override

????public void call() {????}

});

//參數(shù)1:接受人(上下文)

//參數(shù)2:定義的token

//參數(shù)3:實(shí)體的泛型約束

//參數(shù)4:執(zhí)行的回調(diào)監(jiān)聽

Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, String.class, new BindingConsumer<String>() {?????//注冊(cè)一個(gè)帶數(shù)據(jù)回調(diào)的消息監(jiān)聽

????@Override

????public void call(String s) {????}

});

//發(fā)送一個(gè)空消息跪腹,在需要回調(diào)的地方使用token發(fā)送消息

//參數(shù)1:定義的token

Messenger.getDefault().sendNoMsg(LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH);

//參數(shù)1:回調(diào)的實(shí)體

//參數(shù)2:定義的token

Messenger.getDefault().send("refresh",LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH);???//發(fā)送一個(gè)帶數(shù)據(jù)回調(diào)消息

?????token最好不要重名褂删,不然可能就會(huì)出現(xiàn)邏輯上的bug飞醉,為了更好的維護(hù)和清晰邏輯冲茸,建議以aa_bb_cc的格式來(lái)定義token屯阀。aa:TOKEN,bb:ViewModel的類名轴术,cc:動(dòng)作名(功能名)难衰。

????為了避免大量使用Messenger,建議只在ViewModel與ViewModel之間使用逗栽,View與ViewModel之間采用ObservableField去監(jiān)聽UI上的邏輯盖袭,可在繼承了BaseActivity或BaseFragment中重寫initViewObservable()方法來(lái)初始化UI的監(jiān)聽注冊(cè)了監(jiān)聽,當(dāng)然也要解除它彼宠。在BaseActivity鳄虱、BaseFragment的onDestroy()方法里已經(jīng)調(diào)用????Messenger.getDefault().unregister(viewModel);解除注冊(cè),所以不用擔(dān)心忘記解除導(dǎo)致的邏輯錯(cuò)誤和內(nèi)存泄漏凭峡。

具體代碼實(shí)現(xiàn)關(guān)鍵字:

1拙已、單例模式

2、散列表存儲(chǔ)與讀取

3摧冀、函數(shù)的重載

4倍踪、散列表的清除

備注:Messager的實(shí)現(xiàn)參考開源項(xiàng)目https://github.com/Kelin-Hong/MVVMLight


3.8索昂、CaoConfig類在項(xiàng)目中的作用是什么建车,實(shí)現(xiàn)原理是怎么樣的

解析:如果你的程序出現(xiàn)崩潰,它會(huì)檢測(cè)到(各種崩潰椒惨,比如空指針)缤至,會(huì)彈出一個(gè)頁(yè)面,提示你程序崩潰框产,你是否要關(guān)閉程序凄杯,還是重新啟動(dòng)程序。

實(shí)現(xiàn)是基于開源項(xiàng)目: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àng)目中的實(shí)現(xiàn)思路關(guān)鍵字:

1戒突、建造者模式

2、Thread.setDefaultUncaughtExceptionHandler()函數(shù)的使用

3描睦、ActivityLifecycleCallbacks函數(shù)的使用


3.9膊存、Rxbus的作用是什么,實(shí)現(xiàn)原理是怎么樣的忱叭?

解析:RxBus并不是一個(gè)庫(kù)隔崎,而是一種模式。相信大多數(shù)開發(fā)者都使用過(guò)EventBus韵丑,對(duì)RxBus也是很熟悉爵卒。由于MVVMabit中已經(jīng)加入RxJava,所以采用了RxBus代替EventBus作為事件總線通信撵彻,以減少庫(kù)的依賴钓株。

使用方法:

在ViewModel中重寫registerRxBus()方法來(lái)注冊(cè)RxBus实牡,重寫removeRxBus()方法來(lái)移除RxBus

private Disposable mSubscription;?????//訂閱者

@Override

public void registerRxBus() {???????//注冊(cè)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í)行回調(diào)的地方發(fā)送


3.10、ContainerActivity類作用是什么轴合,實(shí)現(xiàn)原理是怎么樣的

解析:一個(gè)盛裝Fragment的一個(gè)容器(代理)Activity创坞,普通界面只需要編寫Fragment,使用此Activity盛裝受葛,這樣就不需要每個(gè)界面都在AndroidManifest中注冊(cè)一遍题涨。

使用方法:

????在ViewModel中調(diào)用BaseViewModel的方法開一個(gè)Fragment

startContainerActivity(你的Fragment類名.class.getCanonicalName())

????在ViewModel中調(diào)用BaseViewModel的方法,攜帶一個(gè)序列化實(shí)體打開一個(gè)Fragment

Bundle mBundle = new Bundle();

mBundle.putParcelable("entity", entity);

startContainerActivity(你的Fragment類名.class.getCanonicalName(), mBundle);

????在你的Fragment中取出實(shí)體:

Bundle mBundle = getArguments();

if (mBundle != null) {

????entity = mBundle.getParcelable("entity");

}


3.11总滩、項(xiàng)目中是如何實(shí)現(xiàn)Activity和Fragment的堆棧管理的(任務(wù)棧管理)纲堵,Task任務(wù)棧的作用是什么

解析:項(xiàng)目中通過(guò)AppManager類實(shí)現(xiàn)了Activity和Fragment類的任務(wù)棧管理。

代碼實(shí)現(xiàn)關(guān)鍵字:

1闰渔、單例模式

2婉支、ActivityLifecycleCallbacks與FragmentLifecycleCallbacks的回調(diào)監(jiān)聽

3、Stack數(shù)據(jù)結(jié)構(gòu)的基本使用:增澜建、刪向挖、查、改炕舵、遍歷等操作何之。


3.12、如何給不同的界面設(shè)置不同的Repository(數(shù)據(jù)獲取模塊Model)咽筋,實(shí)現(xiàn)不同界面數(shù)據(jù)獲取的區(qū)分

解析:通過(guò)AppViewModelFactory類來(lái)初始化各個(gè)ViewModel的Model實(shí)例溶推,不同的界面可以實(shí)現(xiàn)不同HttpDataSourceImpl和LocalDataSourceImpl,從而實(shí)現(xiàn)界面數(shù)據(jù)獲取的區(qū)分奸攻。

????如果不同的界面需要設(shè)置不同的Model蒜危,并且各界面的網(wǎng)絡(luò)數(shù)據(jù)模塊和本地?cái)?shù)據(jù)模塊的邏輯相互分離,可以考慮各個(gè)界面單獨(dú)實(shí)現(xiàn)ViewModel的初始化睹耐。

代碼實(shí)現(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辐赞、項(xiàng)目中是如何封裝網(wǎng)絡(luò)請(qǐng)求模塊的

解析:項(xiàng)目中通過(guò)RetrofitClient類封裝網(wǎng)絡(luò)請(qǐng)求,通過(guò)建造者模式初始化對(duì)象OkHttpClient okHttpClient硝训,

Retrofit retrofit的實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求參數(shù)配置响委,例如:攔截器的配置、Https的配置窖梁、Gson數(shù)據(jù)格式的轉(zhuǎn)換配置赘风、cookie的相關(guān)的配置等,項(xiàng)目在請(qǐng)求返回時(shí)纵刘,返回對(duì)象Observable兼容Rxjava實(shí)例邀窃。


3.14、項(xiàng)目中是如何封裝Https相關(guān)網(wǎng)絡(luò)請(qǐng)求的

解析:項(xiàng)目中通過(guò)HttpsUtils類假哎,實(shí)現(xiàn)對(duì)對(duì)象SSLSocketFactory sSLSocketFactory和X509TrustManager trustManager的初始化瞬捕,從而實(shí)現(xiàn)對(duì)Https網(wǎng)路請(qǐng)求的設(shè)置敲茄,而在設(shè)置這兩個(gè)對(duì)象時(shí),主要需要設(shè)置四個(gè)參數(shù):X509TrustManager trustManager山析、InputStream bksFile、String password掏父、InputStream... certificates笋轨,所以我們需要根據(jù)項(xiàng)目的實(shí)際情況設(shè)置實(shí)現(xiàn)相關(guān)參數(shù)的設(shè)置。


3.15赊淑、項(xiàng)目中是如何通過(guò)Rxjava實(shí)現(xiàn)文件的下載和下載進(jìn)度的回調(diào)

解析:項(xiàng)目中主要通過(guò)三個(gè)類組合實(shí)現(xiàn)文件的下載爵政,三個(gè)類分別是:DownLoadManager、ProgressCallBack陶缺、

DownLoadSubscriber實(shí)現(xiàn)钾挟,而這里面的進(jìn)度回調(diào)主要通過(guò)如下代碼實(shí)現(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的方式,實(shí)時(shí)發(fā)送當(dāng)前已讀取(上傳/下載)的字節(jié)數(shù)據(jù)

????????????????RxBus.getDefault().post(new DownLoadStateBean(contentLength(), bytesReaded, tag));

????????????????return bytesRead;

????????????}

????????};

????}

mSubscription = RxBus.getDefault().toObservable(DownLoadStateBean.class)

????????????????.observeOn(AndroidSchedulers.mainThread()) //回調(diào)到主線程更新UI

????????????????.subscribe(new Consumer<DownLoadStateBean>() {

????????????????????@Override

????????????????????public void accept(final DownLoadStateBean progressLoadBean) throws Exception {

????????????????????????progress(progressLoadBean.getBytesLoaded(), progressLoadBean.getTotal());

????????????????????}

????????????????});

實(shí)現(xiàn)思路:通過(guò)OkHttpClient設(shè)置ProgressInterceptor()攔截器實(shí)現(xiàn)對(duì)下載進(jìn)度的監(jiān)聽饱岸,并通過(guò)RxBus把進(jìn)度情況發(fā)送到ProgressCallBack實(shí)現(xiàn)類中掺出。


3.16、項(xiàng)目中是如何對(duì)網(wǎng)絡(luò)請(qǐng)求中常見異常進(jìn)行處理的苫费?

解析:在工具類RxUtils中汤锨,封裝函數(shù)exceptionTransformer(),繼而通過(guò)observable.onErrorResumeNext(new HttpResponseFunc())和Observable.error()函數(shù)獲取異常信息百框,然后通過(guò)自定義類ExceptionHandle的靜態(tài)函數(shù)

ResponseThrowable handleException(Throwable e)闲礼,實(shí)現(xiàn)對(duì)不同的網(wǎng)絡(luò)錯(cuò)誤進(jìn)行處理。

代碼示例:

model.demoGet()

.compose(RxUtils.schedulersTransformer())??//線程調(diào)度

.compose(RxUtils.exceptionTransformer())???//網(wǎng)絡(luò)錯(cuò)誤的異常轉(zhuǎn)換, 這里可以換成自己的ExceptionHandle

.doOnSubscribe(this)...????????????????????//請(qǐng)求與ViewModel周期同步


3.17铐维、項(xiàng)目中對(duì)Cookie的處理是如何實(shí)現(xiàn)的

解析:通過(guò)在okHttpClient中設(shè)置cookieJar的實(shí)現(xiàn)類柬泽,設(shè)置cookie的常見操作接口,并實(shí)現(xiàn)持久化存儲(chǔ)和內(nèi)存存儲(chǔ)的兩種方式實(shí)現(xiàn)對(duì)連接cookie的處理嫁蛇。


3.18锨并、項(xiàng)目中引入了開源框架BindingCollectionAdapter的作用是什么,實(shí)現(xiàn)原理是什么

解析:

官方說(shuō)明:Easy way to bind collections to listviews and recyclerviews with the new Android Data Binding framework睬棚。

框架解析:其實(shí)根據(jù)前面的知識(shí)琳疏,如果想要一個(gè)控件在xml配置相應(yīng)的屬性去支配ViewModel,立即可以想到@BindingAdapter闸拿,而改開源框架也是這么實(shí)現(xiàn)的空盼,通過(guò)在類BindingCollectionAdapters中,配置List與RecyclerView中靜態(tài)初始化函數(shù)新荤,在通過(guò)xml的屬性配置和ViewModel的變量傳參揽趾,從而實(shí)現(xiàn)對(duì)ListView與RecyclerView控件在xml布局中進(jìn)行相關(guān)的初始化,項(xiàng)目初始化的代碼就在BindingCollectionAdapters類的各個(gè)今天方法中苛骨,但是篱瞎,如果我們想要添加控件的靈活性苟呐,并且適配更多的使用場(chǎng)景,我們需要引入泛型俐筋,抽象類牵素,接口定義等方式,實(shí)現(xiàn)對(duì)復(fù)雜的控件(PageView澄者、RecyclerView等)實(shí)現(xiàn)靈活性笆呆,擴(kuò)展性更高的屬性配置。

代碼的接入實(shí)例:

<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()}" />

框架核心代碼實(shí)現(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) {

????????????//通過(guò)item的類型, 動(dòng)態(tài)設(shè)置Item加載的布局

????????????String itemType = (String) item.getItemType();

????????????if (MultiRecycleType_Head.equals(itemType)) {

????????????????//設(shè)置頭布局

????????????????itemBinding.set(BR.viewModel, R.layout.item_multi_head);

????????????} else if (MultiRecycleType_Left.equals(itemType)) {

????????????????//設(shè)置左布局

????????????????itemBinding.set(BR.viewModel, R.layout.item_multi_rv_left);

????????????} else if (MultiRecycleType_Right.equals(itemType)) {

????????????????//設(shè)置右布局

????????????????itemBinding.set(BR.viewModel, R.layout.item_multi_rv_right);

????????????}

????????}

????});

開源項(xiàng)目:https://github.com/evant/binding-collection-adapter(MVVM之列表綁定神器BindingCollectionAdapter)


四:項(xiàng)目中可以優(yōu)化的點(diǎn)匯總

1粱挡、在UIChangeLiveData中赠幕,引入loadsir開源框架,實(shí)現(xiàn)項(xiàng)目通用的加載询筏、請(qǐng)求異常榕堰、空數(shù)據(jù)、正常數(shù)據(jù)界面等嫌套。

2逆屡、考慮在ActivityLifecycleCallbacks中封裝更多Acitivity中通用的東西(toolbar、沉浸式等)

3踱讨、BaseFragment添加懶加載邏輯

4康二、各個(gè)界面的Model設(shè)置模塊,應(yīng)該根據(jù)不同界面實(shí)現(xiàn)各個(gè)不同的model勇蝙。

5沫勿、網(wǎng)絡(luò)請(qǐng)求模塊修改為RxHttp實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的進(jìn)一步封裝。

6味混、參考使用RxErrorHandler實(shí)現(xiàn)對(duì)網(wǎng)絡(luò)請(qǐng)求異常的處理产雹。


五:項(xiàng)目知識(shí)點(diǎn)舉一反三

知識(shí)點(diǎn)一:MutableLiveData與MediatorLiveData的區(qū)別

解析:MutableLiveData是LiveData的子類,它公開setValue和postValue方法(第二個(gè)方法是線程安全的)翁锡,因此您可以將值分配給任何活動(dòng)的觀察者蔓挖,MediatorLiveData是MutableLiveData的子類,MediatorLiveData可以觀察其他LiveData對(duì)象(源)并對(duì)它們的onChange事件作出反應(yīng)馆衔,這將使您可以控制何時(shí)傳播事件或進(jìn)行特定操作瘟判。


知識(shí)點(diǎn)二:項(xiàng)目中的自定義SingleLiveEvent的作用是什么

背景:只要使用過(guò)一段時(shí)間的LiveData就會(huì)發(fā)現(xiàn),LiveData會(huì)經(jīng)常多次回調(diào)數(shù)據(jù)角溃。我們經(jīng)常碰到的這個(gè)問(wèn)題拷获,我們的ViewModel里是給Activity持有的,并且里面有一個(gè)LiveData數(shù)據(jù)减细,我們A_Fragment現(xiàn)在獲得Activity的ViewModel并且注冊(cè)LiveData數(shù)據(jù)成為觀察者匆瓜,這個(gè)時(shí)候我們setValue()就會(huì)讓前臺(tái)的A_Fragment得到一次LiveData數(shù)據(jù),接下來(lái)操作A_Fragment啟動(dòng)B_Fragment,在返回到A_Fragment驮吱。你會(huì)發(fā)現(xiàn)只要再次注冊(cè)LiveData的observe(this, new Observer ...)茧妒,那么A_Fragment里面又會(huì)接收到一次LiveData的數(shù)據(jù)。

問(wèn)題出現(xiàn)原因:

1左冬、一部分原因是LiveData的機(jī)制桐筏,就是向所有前臺(tái)Fragment或者Activity發(fā)送數(shù)據(jù)。只要注冊(cè)的觀察者在前臺(tái)就必定會(huì)收到這個(gè)數(shù)據(jù)拇砰。

2梅忌、另一部分的原因是對(duì)ViewModel理解不深刻,理論上只有在Activity保存的ViewModel它沒被銷毀過(guò)就會(huì)一直給新的前臺(tái)Fragment觀察者發(fā)送數(shù)據(jù)毕匀。我們需要管理好ViewModel的使用范圍。 比如只需要在Fragment里使用的ViewModel就不要給Activity保管癌别。而根Activity的ViewModel只需要做一下數(shù)據(jù)共享與看情況使用LiveData皂岔。

解決方法:

1、就是管理好ViewModel的范圍展姐,如果業(yè)務(wù)范圍只跟某個(gè)Fragment有關(guān)躁垛,那么最好就只給這個(gè)Fragment使用。這樣Fragment在銷毀或者創(chuàng)建的時(shí)候圾笨,也會(huì)銷毀ViewModel與創(chuàng)建ViewModel教馆,ViewModel攜帶的LiveData就是全新的不會(huì)在發(fā)送之前設(shè)置的數(shù)據(jù)。

2擂达、實(shí)現(xiàn)類 SingleLiveEvent土铺,其中的機(jī)制是用一個(gè)原子AtomicBoolean記錄一次setValue。在發(fā)送一次后在將AtomicBoolean設(shè)置為false板鬓,阻止后續(xù)前臺(tái)重新觸發(fā)時(shí)的數(shù)據(jù)發(fā)送悲敷。


六:擴(kuò)展閱讀

1、https://github.com/goldze/MVVMHabitComponent(組件化項(xiàng)目結(jié)構(gòu))

2俭令、https://github.com/goldze/MVVMHabit(DataBinding+LiveData+ViewModel項(xiàng)目架構(gòu))

3后德、https://blog.csdn.net/ican87/article/details/86612733(阿里路由框架ARouter原理分析總結(jié))

4、https://blog.csdn.net/yu75567218/article/details/86706290(自己實(shí)現(xiàn)全局通信messenger)

5抄腔、https://github.com/Kelin-Hong/MVVMLight(MVVM Light Toolkit使用指南)

6瓢湃、http://www.reibang.com/p/2fc41a310f79(如何構(gòu)建Android MVVM應(yīng)用程序)

7、https://zhuanlan.zhihu.com/p/133949967(兩步使用 LiveData 替換 Observable Field)

8赫蛇、https://github.com/Ereza/CustomActivityOnCrash(Android檢測(cè)程序崩潰框架)

9绵患、https://github.com/JessYanCoding/RxErrorHandler(RxJava的錯(cuò)誤處理庫(kù))

10、https://github.com/KunMinX/Jetpack-MVVM-Best-Practice(難得一見的Jetpack MVVM最佳實(shí)踐)

11悟耘、https://www.cnblogs.com/guanxinjing/p/12669506.html(SingleLiveEvent解決LiveData或者M(jìn)utableLiveData多次回調(diào)的問(wèn)題)

12藏雏、https://blog.csdn.net/yu75567218/article/details/87860020(MVVM之列表綁定神器BindingCollectionAdapter)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掘殴,更是在濱河造成了極大的恐慌赚瘦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奏寨,死亡現(xiàn)場(chǎng)離奇詭異起意,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)病瞳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門揽咕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人套菜,你說(shuō)我怎么就攤上這事亲善。” “怎么了逗柴?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵蛹头,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我戏溺,道長(zhǎng)渣蜗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任旷祸,我火速辦了婚禮耕拷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘托享。我一直安慰自己骚烧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布闰围。 她就那樣靜靜地躺著止潘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辫诅。 梳的紋絲不亂的頭發(fā)上凭戴,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音炕矮,去河邊找鬼么夫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肤视,可吹牛的內(nèi)容都是我干的档痪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼邢滑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腐螟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乐纸,失蹤者是張志新(化名)和其女友劉穎衬廷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汽绢,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吗跋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宁昭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跌宛。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖积仗,靈堂內(nèi)的尸體忽然破棺而出疆拘,到底是詐尸還是另有隱情,我是刑警寧澤寂曹,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布哎迄,位于F島的核電站,受9級(jí)特大地震影響稀颁,放射性物質(zhì)發(fā)生泄漏芬失。R本人自食惡果不足惜楣黍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一匾灶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧租漂,春花似錦阶女、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至业筏,卻和暖如春憔杨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒜胖。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工消别, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人台谢。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓寻狂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朋沮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛇券,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容