? ? ? ?當(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)