Repository: goldze/MVVMHabit Branch: master Commit: ab2bf079815e Files: 190 Total size: 566.8 KB Directory structure: gitextract_gppaw7p9/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── UpdateLog.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── goldze/ │ │ └── mvvmhabit/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── goldze/ │ │ │ └── mvvmhabit/ │ │ │ ├── app/ │ │ │ │ ├── AppApplication.java │ │ │ │ ├── AppViewModelFactory.java │ │ │ │ └── Injection.java │ │ │ ├── binding/ │ │ │ │ └── twinklingrefreshlayout/ │ │ │ │ └── ViewAdapter.java │ │ │ ├── data/ │ │ │ │ ├── DemoRepository.java │ │ │ │ └── source/ │ │ │ │ ├── HttpDataSource.java │ │ │ │ ├── LocalDataSource.java │ │ │ │ ├── http/ │ │ │ │ │ ├── HttpDataSourceImpl.java │ │ │ │ │ └── service/ │ │ │ │ │ └── DemoApiService.java │ │ │ │ └── local/ │ │ │ │ └── LocalDataSourceImpl.java │ │ │ ├── entity/ │ │ │ │ ├── DemoEntity.java │ │ │ │ ├── FormEntity.java │ │ │ │ └── SpinnerItemData.java │ │ │ ├── ui/ │ │ │ │ ├── base/ │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── BaseFragmentPagerAdapter.java │ │ │ │ │ ├── fragment/ │ │ │ │ │ │ └── BasePagerFragment.java │ │ │ │ │ └── viewmodel/ │ │ │ │ │ └── ToolbarViewModel.java │ │ │ │ ├── form/ │ │ │ │ │ ├── FormFragment.java │ │ │ │ │ └── FormViewModel.java │ │ │ │ ├── login/ │ │ │ │ │ ├── LoginActivity.java │ │ │ │ │ └── LoginViewModel.java │ │ │ │ ├── main/ │ │ │ │ │ ├── DemoActivity.java │ │ │ │ │ └── DemoViewModel.java │ │ │ │ ├── network/ │ │ │ │ │ ├── NetWorkFragment.java │ │ │ │ │ ├── NetWorkItemViewModel.java │ │ │ │ │ ├── NetWorkViewModel.java │ │ │ │ │ └── detail/ │ │ │ │ │ ├── DetailFragment.java │ │ │ │ │ └── DetailViewModel.java │ │ │ │ ├── rv_multi/ │ │ │ │ │ ├── MultiRecycleHeadViewModel.java │ │ │ │ │ ├── MultiRecycleLeftItemViewModel.java │ │ │ │ │ ├── MultiRecycleRightItemViewModel.java │ │ │ │ │ ├── MultiRecycleViewFragment.java │ │ │ │ │ └── MultiRecycleViewModel.java │ │ │ │ ├── tab_bar/ │ │ │ │ │ ├── activity/ │ │ │ │ │ │ └── TabBarActivity.java │ │ │ │ │ └── fragment/ │ │ │ │ │ ├── TabBar1Fragment.java │ │ │ │ │ ├── TabBar2Fragment.java │ │ │ │ │ ├── TabBar3Fragment.java │ │ │ │ │ └── TabBar4Fragment.java │ │ │ │ ├── viewpager/ │ │ │ │ │ ├── activity/ │ │ │ │ │ │ └── ViewPagerActivity.java │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── ViewPagerBindingAdapter.java │ │ │ │ │ └── vm/ │ │ │ │ │ ├── ViewPagerItemViewModel.java │ │ │ │ │ └── ViewPagerViewModel.java │ │ │ │ └── vp_frg/ │ │ │ │ └── ViewPagerGroupFragment.java │ │ │ └── utils/ │ │ │ ├── HttpsUtils.java │ │ │ └── RetrofitClient.java │ │ └── res/ │ │ ├── drawable/ │ │ │ └── login_clear_input.xml │ │ ├── layout/ │ │ │ ├── activity_demo.xml │ │ │ ├── activity_login.xml │ │ │ ├── activity_tab_bar.xml │ │ │ ├── fragment_base_pager.xml │ │ │ ├── fragment_detail.xml │ │ │ ├── fragment_form.xml │ │ │ ├── fragment_multi_rv.xml │ │ │ ├── fragment_network.xml │ │ │ ├── fragment_tab_bar_1.xml │ │ │ ├── fragment_tab_bar_2.xml │ │ │ ├── fragment_tab_bar_3.xml │ │ │ ├── fragment_tab_bar_4.xml │ │ │ ├── fragment_viewpager.xml │ │ │ ├── item_multi_head.xml │ │ │ ├── item_multi_rv_left.xml │ │ │ ├── item_multi_rv_right.xml │ │ │ ├── item_network.xml │ │ │ ├── item_viewpager.xml │ │ │ └── layout_toolbar.xml │ │ ├── values/ │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml/ │ │ └── network_security_config.xml │ └── test/ │ └── java/ │ └── com/ │ └── goldze/ │ └── mvvmhabit/ │ └── ExampleUnitTest.java ├── build.gradle ├── config.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── mvvmhabit/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── goldze/ │ │ └── mvvmhabit/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── me/ │ │ │ └── goldze/ │ │ │ └── mvvmhabit/ │ │ │ ├── base/ │ │ │ │ ├── AppManager.java │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── BaseApplication.java │ │ │ │ ├── BaseFragment.java │ │ │ │ ├── BaseModel.java │ │ │ │ ├── BaseViewModel.java │ │ │ │ ├── ContainerActivity.java │ │ │ │ ├── IBaseView.java │ │ │ │ ├── IBaseViewModel.java │ │ │ │ ├── IModel.java │ │ │ │ ├── ItemViewModel.java │ │ │ │ ├── MultiItemViewModel.java │ │ │ │ └── ViewModelFactory.java │ │ │ ├── binding/ │ │ │ │ ├── command/ │ │ │ │ │ ├── BindingAction.java │ │ │ │ │ ├── BindingCommand.java │ │ │ │ │ ├── BindingConsumer.java │ │ │ │ │ ├── BindingFunction.java │ │ │ │ │ └── ResponseCommand.java │ │ │ │ └── viewadapter/ │ │ │ │ ├── checkbox/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── edittext/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── image/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── listview/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── mswitch/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── radiogroup/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── recyclerview/ │ │ │ │ │ ├── DividerLine.java │ │ │ │ │ ├── LayoutManagers.java │ │ │ │ │ ├── LineManagers.java │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── scrollview/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── spinner/ │ │ │ │ │ ├── IKeyAndValue.java │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── swiperefresh/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── view/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── viewgroup/ │ │ │ │ │ ├── IBindingItemViewModel.java │ │ │ │ │ └── ViewAdapter.java │ │ │ │ ├── viewpager/ │ │ │ │ │ └── ViewAdapter.java │ │ │ │ └── webview/ │ │ │ │ └── ViewAdapter.java │ │ │ ├── bus/ │ │ │ │ ├── Messenger.java │ │ │ │ ├── RxBus.java │ │ │ │ ├── RxBusSubscriber.java │ │ │ │ ├── RxSubscriptions.java │ │ │ │ ├── WeakAction.java │ │ │ │ └── event/ │ │ │ │ ├── SingleLiveEvent.java │ │ │ │ └── SnackbarMessage.java │ │ │ ├── crash/ │ │ │ │ ├── CaocConfig.java │ │ │ │ ├── CaocInitProvider.java │ │ │ │ ├── CustomActivityOnCrash.java │ │ │ │ └── DefaultErrorActivity.java │ │ │ ├── http/ │ │ │ │ ├── ApiDisposableObserver.java │ │ │ │ ├── BaseResponse.java │ │ │ │ ├── DownLoadManager.java │ │ │ │ ├── ExceptionHandle.java │ │ │ │ ├── NetworkUtil.java │ │ │ │ ├── ResponseThrowable.java │ │ │ │ ├── cookie/ │ │ │ │ │ ├── CookieJarImpl.java │ │ │ │ │ └── store/ │ │ │ │ │ ├── CookieStore.java │ │ │ │ │ ├── MemoryCookieStore.java │ │ │ │ │ ├── PersistentCookieStore.java │ │ │ │ │ └── SerializableHttpCookie.java │ │ │ │ ├── download/ │ │ │ │ │ ├── DownLoadStateBean.java │ │ │ │ │ ├── DownLoadSubscriber.java │ │ │ │ │ ├── ProgressCallBack.java │ │ │ │ │ └── ProgressResponseBody.java │ │ │ │ └── interceptor/ │ │ │ │ ├── BaseInterceptor.java │ │ │ │ ├── CacheInterceptor.java │ │ │ │ ├── ProgressInterceptor.java │ │ │ │ └── logging/ │ │ │ │ ├── I.java │ │ │ │ ├── Level.java │ │ │ │ ├── Logger.java │ │ │ │ ├── LoggingInterceptor.java │ │ │ │ └── Printer.java │ │ │ ├── utils/ │ │ │ │ ├── CloseUtils.java │ │ │ │ ├── ConvertUtils.java │ │ │ │ ├── ImageUtils.java │ │ │ │ ├── KLog.java │ │ │ │ ├── MaterialDialogUtils.java │ │ │ │ ├── RegexUtils.java │ │ │ │ ├── RxUtils.java │ │ │ │ ├── SDCardUtils.java │ │ │ │ ├── SPUtils.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── ToastUtils.java │ │ │ │ ├── Utils.java │ │ │ │ ├── compression/ │ │ │ │ │ ├── Luban.java │ │ │ │ │ ├── OnCompressListener.java │ │ │ │ │ └── Preconditions.java │ │ │ │ └── constant/ │ │ │ │ ├── MemoryConstants.java │ │ │ │ ├── RegexConstants.java │ │ │ │ └── TimeConstants.java │ │ │ └── widget/ │ │ │ └── ControlDistributeLinearLayout.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_container.xml │ │ │ └── customactivityoncrash_default_error_activity.xml │ │ └── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── me/ │ └── goldze/ │ └── mvvmhabit/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ *.iml .gradle key.jks /local.properties /.idea/workspace.xml /.idea/libraries /.idea .DS_Store /build /captures .externalNativeBuild /app/release /mvvmhabit/bintray.gradle ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 goldze(曾宪泽) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ ## 最新日志 **v4.0.0:2021年07月16日** - 迁移AndroidX分支作为主线分支; - 升级第三方框架依赖版本; - 升级gradle插件版本支持; - 优化框架代码,解决已知Bug; - 修改文档说明。 #### [更多日志](./UpdateLog.md) *** **注:** [3.x:Support版(最后版本:3.1.6)](https://github.com/goldze/MVVMHabit/tree/20210716_v3.1.6_android) [4.x:AndroidX版(最后版本:4.0.0)](https://github.com/goldze/MVVMHabit) 建议使用当前版本 > **原文地址:** [https://github.com/goldze/MVVMHabit](https://github.com/goldze/MVVMHabit) ②群 MVVMHabit-Family2 ①群 MVVMHabit-Family(已满) # MVVMHabit ## 目前,android流行的MVC、MVP模式的开发框架很多,然而一款基于MVVM模式开发框架却很少。**MVVMHabit是以谷歌DataBinding+LiveData+ViewModel框架为基础,整合Okhttp+RxJava+Retrofit+Glide等流行模块,加上各种原生控件自定义的BindingAdapter,让事件与数据源完美绑定的一款容易上瘾的实用性MVVM快速开发框架**。从此告别findViewById(),告别setText(),告别setOnClickListener()... ## 框架流程 ![](./img/fc.png) ## 框架特点 - **快速开发** 只需要写项目的业务逻辑,不用再去关心网络请求、权限申请、View的生命周期等问题,撸起袖子就是干。 - **维护方便** MVVM开发模式,低耦合,逻辑分明。Model层负责将请求的数据交给ViewModel;ViewModel层负责将请求到的数据做业务逻辑处理,最后交给View层去展示,与View一一对应;View层只负责界面绘制刷新,不处理业务逻辑,非常适合分配独立模块开发。 - **流行框架** [retrofit](https://github.com/square/retrofit)+[okhttp](https://github.com/square/okhttp)+[rxJava](https://github.com/ReactiveX/RxJava)负责网络请求;[gson](https://github.com/google/gson)负责解析json数据;[glide](https://github.com/bumptech/glide)负责加载图片;[rxlifecycle](https://github.com/trello/RxLifecycle)负责管理view的生命周期;与网络请求共存亡;[rxbinding](https://github.com/JakeWharton/RxBinding)结合databinding扩展UI事件;[rxpermissions](https://github.com/tbruyelle/RxPermissions)负责Android 6.0权限申请;[material-dialogs](https://github.com/afollestad/material-dialogs)一个漂亮的、流畅的、可定制的material design风格的对话框。 - **数据绑定** 满足google目前控件支持的databinding双向绑定,并扩展原控件一些不支持的数据绑定。例如将图片的url路径绑定到ImageView控件中,在BindingAdapter方法里面则使用Glide加载图片;View的OnClick事件在BindingAdapter中方法使用RxView防重复点击,再把事件回调到ViewModel层,实现xml与ViewModel之间数据和事件的绑定(框架里面部分扩展控件和回调命令使用的是@kelin原创的)。 - **基类封装** 专门针对MVVM模式打造的BaseActivity、BaseFragment、BaseViewModel,在View层中不再需要定义ViewDataBinding和ViewModel,直接在BaseActivity、BaseFragment上限定泛型即可使用。普通界面只需要编写Fragment,然后使用ContainerActivity盛装(代理),这样就不需要每个界面都在AndroidManifest中注册一遍。 - **全局操作** 1. 全局的Activity堆栈式管理,在程序任何地方可以打开、结束指定的Activity,一键退出应用程序。 2. LoggingInterceptor全局拦截网络请求日志,打印Request和Response,格式化json、xml数据显示,方便与后台调试接口。 3. 全局Cookie,支持SharedPreferences和内存两种管理模式。 4. 通用的网络请求异常监听,根据不同的状态码或异常设置相应的message。 5. 全局的异常捕获,程序发生异常时不会崩溃,可跳入异常界面重启应用。 6. 全局事件回调,提供RxBus、Messenger两种回调方式。 7. 全局任意位置一行代码实现文件下载进度监听(暂不支持多文件进度监听)。 8. 全局点击事件防抖动处理,防止点击过快。 ## 1、准备工作 > 网上的很多有关MVVM的资料,在此就不再阐述什么是MVVM了,不清楚的朋友可以先去了解一下。[todo-mvvm-live](https://github.com/googlesamples/android-architecture/tree/todo-mvvm-live) ### 1.1、启用databinding 在主工程app的build.gradle的android {}中加入: ```gradle dataBinding { enabled true } ``` ### 1.2、依赖Library 从远程依赖: 在根目录的build.gradle中加入 ```gradle allprojects { repositories { ... google() jcenter() maven { url 'https://jitpack.io' } } } ``` 在主项目app的build.gradle中依赖 ```gradle dependencies { ... implementation 'com.github.goldze:MVVMHabit:4.0.0' } ``` 或 下载例子程序,在主项目app的build.gradle中依赖例子程序中的**mvvmhabit**: ```gradle dependencies { ... implementation project(':mvvmhabit') } ``` ### 1.3、配置config.gradle 如果不是远程依赖,而是下载的例子程序,那么还需要将例子程序中的config.gradle放入你的主项目根目录中,然后在根目录build.gradle的第一行加入: ```gradle apply from: "config.gradle" ``` **注意:** config.gradle中的 android = [] 是你的开发相关版本配置,可自行修改 support = [] 是你的support相关配置,可自行修改 dependencies = [] 是依赖第三方库的配置,可以加新库,但不要去修改原有第三方库的版本号,不然可能会编译不过 ### 1.4、配置AndroidManifest 添加权限: ```xml ``` 配置Application: 继承**mvvmhabit**中的BaseApplication,或者调用 ```java BaseApplication.setApplication(this); ``` 来初始化你的Application 可以在你的自己AppApplication中配置 ```java //是否开启日志打印 KLog.init(true); //配置全局异常崩溃操作 CaocConfig.Builder.create() .backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT) //背景模式,开启沉浸式 .enabled(true) //是否启动全局异常捕获 .showErrorDetails(true) //是否显示错误详细信息 .showRestartButton(true) //是否显示重启按钮 .trackActivities(true) //是否跟踪Activity .minTimeBetweenCrashesMs(2000) //崩溃的间隔时间(毫秒) .errorDrawable(R.mipmap.ic_launcher) //错误图标 .restartActivity(LoginActivity.class) //重新启动后的activity //.errorActivity(YourCustomErrorActivity.class) //崩溃后的错误activity //.eventListener(new YourCustomEventListener()) //崩溃后的错误监听 .apply(); ``` ## 2、快速上手 ### 2.1、第一个Activity > 以大家都熟悉的登录操作为例:三个文件**LoginActivty.java**、**LoginViewModel.java**、**activity_login.xml** ##### 2.1.1、关联ViewModel 在activity_login.xml中关联LoginViewModel。 ```xml ..... ``` > variable - type:类的全路径
variable - name:变量名 ##### 2.1.2、继承BaseActivity LoginActivity继承BaseActivity ```java public class LoginActivity extends BaseActivity { //ActivityLoginBinding类是databinding框架自定生成的,对activity_login.xml @Override public int initContentView(Bundle savedInstanceState) { return R.layout.activity_login; } @Override public int initVariableId() { return BR.viewModel; } @Override public LoginViewModel initViewModel() { //View持有ViewModel的引用,如果没有特殊业务处理,这个方法可以不重写 return ViewModelProviders.of(this).get(LoginViewModel.class); } } ``` > 保存activity_login.xml后databinding会生成一个ActivityLoginBinding类。(如果没有生成,试着点击Build->Clean Project) BaseActivity是一个抽象类,有两个泛型参数,一个是ViewDataBinding,另一个是BaseViewModel,上面的ActivityLoginBinding则是继承的ViewDataBinding作为第一个泛型约束,LoginViewModel继承BaseViewModel作为第二个泛型约束。 重写BaseActivity的二个抽象方法 initContentView() 返回界面layout的id
initVariableId() 返回变量的id,对应activity_login中name="viewModel",就像一个控件的id,可以使用R.id.xxx,这里的BR跟R文件一样,由系统生成,使用BR.xxx找到这个ViewModel的id。
选择性重写initViewModel()方法,返回ViewModel对象 ```java @Override public LoginViewModel initViewModel() { //View持有ViewModel的引用,如果没有特殊业务处理,这个方法可以不重写 return ViewModelProviders.of(this).get(LoginViewModel.class); } ``` **注意:** 不重写initViewModel(),默认会创建LoginActivity中第二个泛型约束的LoginViewModel,如果没有指定第二个泛型,则会创建BaseViewModel ##### 2.1.3、继承BaseViewModel LoginViewModel继承BaseViewModel ```java public class LoginViewModel extends BaseViewModel { public LoginViewModel(@NonNull Application application) { super(application); } .... } ``` BaseViewModel与BaseActivity通过LiveData来处理常用UI逻辑,即可在ViewModel中使用父类的showDialog()、startActivity()等方法。在这个LoginViewModel中就可以尽情的写你的逻辑了! > BaseFragment的使用和BaseActivity一样,详情参考Demo。 ### 2.2、数据绑定 > 拥有databinding框架自带的双向绑定,也有扩展 ##### 2.2.1、传统绑定 绑定用户名: 在LoginViewModel中定义 ```java //用户名的绑定 public ObservableField userName = new ObservableField<>(""); ``` 在用户名EditText标签中绑定 ```xml android:text="@={viewModel.userName}" ``` 这样一来,输入框中输入了什么,userName.get()的内容就是什么,userName.set("")设置什么,输入框中就显示什么。 **注意:** @符号后面需要加=号才能达到双向绑定效果;userName需要是public的,不然viewModel无法找到它。 点击事件绑定: 在LoginViewModel中定义 ```java //登录按钮的点击事件 public View.OnClickListener loginOnClick = new View.OnClickListener() { @Override public void onClick(View v) { } }; ``` 在登录按钮标签中绑定 ```xml android:onClick="@{viewModel.loginOnClick}" ``` 这样一来,用户的点击事件直接被回调到ViewModel层了,更好的维护了业务逻辑 这就是强大的databinding框架双向绑定的特性,不用再给控件定义id,setText(),setOnClickListener()。 **但是,光有这些,完全满足不了我们复杂业务的需求啊!MVVMHabit闪亮登场:它有一套自定义的绑定规则,可以满足大部分的场景需求,请继续往下看。** ##### 2.2.2、自定义绑定 还拿点击事件说吧,不用传统的绑定方式,使用自定义的点击事件绑定。 在LoginViewModel中定义 ```java //登录按钮的点击事件 public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { } }); ``` 在activity_login中定义命名空间 ```xml xmlns:binding="http://schemas.android.com/apk/res-auto" ``` 在登录按钮标签中绑定 ```xml binding:onClickCommand="@{viewModel.loginOnClickCommand}" ``` 这和原本传统的绑定不是一样吗?不,这其实是有差别的。使用这种形式的绑定,在原本事件绑定的基础之上,带有防重复点击的功能,1秒内多次点击也只会执行一次操作。如果不需要防重复点击,可以加入这条属性 ```xml binding:isThrottleFirst="@{Boolean.TRUE}" ``` 那这功能是在哪里做的呢?答案在下面的代码中。 ```java //防重复点击间隔(秒) public static final int CLICK_INTERVAL = 1; /** * requireAll 是意思是是否需要绑定全部参数, false为否 * View的onClick事件绑定 * onClickCommand 绑定的命令, * isThrottleFirst 是否开启防止过快点击 */ @BindingAdapter(value = {"onClickCommand", "isThrottleFirst"}, requireAll = false) public static void onClickCommand(View view, final BindingCommand clickCommand, final boolean isThrottleFirst) { if (isThrottleFirst) { RxView.clicks(view) .subscribe(new Consumer() { @Override public void accept(Object object) throws Exception { if (clickCommand != null) { clickCommand.execute(); } } }); } else { RxView.clicks(view) .throttleFirst(CLICK_INTERVAL, TimeUnit.SECONDS)//1秒钟内只允许点击1次 .subscribe(new Consumer() { @Override public void accept(Object object) throws Exception { if (clickCommand != null) { clickCommand.execute(); } } }); } } ``` onClickCommand方法是自定义的,使用@BindingAdapter注解来标明这是一个绑定方法。在方法中使用了RxView来增强view的clicks事件,.throttleFirst()限制订阅者在指定的时间内重复执行,最后通过BindingCommand将事件回调出去,就好比有一种拦截器,在点击时先做一下判断,然后再把事件沿着他原有的方向传递。 是不是觉得有点意思,好戏还在后头呢! ##### 2.2.3、自定义ImageView图片加载 绑定图片路径: 在ViewModel中定义 ```java public String imgUrl = "http://img0.imgtn.bdimg.com/it/u=2183314203,562241301&fm=26&gp=0.jpg"; ``` 在ImageView标签中 ```xml binding:url="@{viewModel.imgUrl}" ``` url是图片路径,这样绑定后,这个ImageView就会去显示这张图片,不限网络图片还是本地图片。 如果需要给一个默认加载中的图片,可以加这一句 ```xml binding:placeholderRes="@{R.mipmap.ic_launcher_round}" ``` > R文件需要在data标签中导入使用,如:`` BindingAdapter中的实现 ```java @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) .placeholder(placeholderRes) .into(imageView); } } ``` 很简单就自定义了一个ImageView图片加载的绑定,学会这种方式,可自定义扩展。 > 如果你对这些感兴趣,可以下载源码,在binding包中可以看到各类控件的绑定实现方式 ##### 2.2.4、RecyclerView绑定 > RecyclerView也是很常用的一种控件,传统的方式需要针对各种业务要写各种Adapter,如果你使用了mvvmhabit,则可大大简化这种工作量,从此告别setAdapter()。 在ViewModel中定义: ```java //给RecyclerView添加items public final ObservableList observableList = new ObservableArrayList<>(); //给RecyclerView添加ItemBinding public final ItemBinding itemBinding = ItemBinding.of(BR.viewModel, R.layout.item_network); ``` ObservableList<>和ItemBinding<>的泛型是Item布局所对应的ItemViewModel 在xml中绑定 ```xml ``` layoutManager控制是线性(包含水平和垂直)排列还是网格排列,lineManager是设置分割线 网格布局的写法:`binding:layoutManager="@{LayoutManagers.grid(3)}`
水平布局的写法:`binding:layoutManager="@{LayoutManagers.linear(LinearLayoutManager.HORIZONTAL,Boolean.FALSE)}"`
使用到相关类,则需要导入该类才能使用,和导入Java类相似 > ``
> ``
> `` 这样绑定后,在ViewModel中调用ObservableList的add()方法,添加一个ItemViewModel,界面上就会实时绘制出一个Item。在Item对应的ViewModel中,同样可以以绑定的形式完成逻辑 > 可以在请求到数据后,循环添加`observableList.add(new NetWorkItemViewModel(NetWorkViewModel.this, entity));`详细可以参考例子程序中NetWorkViewModel类。 **注意:** 在以前的版本中,ItemViewModel是继承BaseViewModel,传入Context,新版本3.x中可继承ItemViewModel,传入当前页面的ViewModel 更多RecyclerView、ListView、ViewPager等绑定方式,请参考 [https://github.com/evant/binding-collection-adapter](https://github.com/evant/binding-collection-adapter) ### 2.3、网络请求 > 网络请求一直都是一个项目的核心,现在的项目基本都离不开网络,一个好用网络请求框架可以让开发事半功倍。 #### 2.3.1、Retrofit+Okhttp+RxJava > 现今,这三个组合基本是网络请求的标配,如果你对这三个框架不了解,建议先去查阅相关资料。 square出品的框架,用起来确实非常方便。**MVVMHabit**中引入了 ```gradle api "com.squareup.okhttp3:okhttp:3.10.0" api "com.squareup.retrofit2:retrofit:2.4.0" api "com.squareup.retrofit2:converter-gson:2.4.0" api "com.squareup.retrofit2:adapter-rxjava2:2.4.0" ``` 构建Retrofit时加入 ```java Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); ``` 或者直接使用例子程序中封装好的RetrofitClient #### 2.3.2、网络拦截器 **LoggingInterceptor:** 全局拦截请求信息,格式化打印Request、Response,可以清晰的看到与后台接口对接的数据, ```java LoggingInterceptor mLoggingInterceptor = new LoggingInterceptor .Builder()//构建者模式 .loggable(true) //是否开启日志打印 .setLevel(Level.BODY) //打印的等级 .log(Platform.INFO) // 打印类型 .request("Request") // request的Tag .response("Response")// Response的Tag .addHeader("version", BuildConfig.VERSION_NAME)//打印版本 .build() ``` 构建okhttp时加入 ```java OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(mLoggingInterceptor) .build(); ``` **CacheInterceptor:** 缓存拦截器,当没有网络连接的时候自动读取缓存中的数据,缓存存放时间默认为3天。
创建缓存对象 ```java //缓存时间 int CACHE_TIMEOUT = 10 * 1024 * 1024 //缓存存放的文件 File httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache"); //缓存对象 Cache cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT); ``` 构建okhttp时加入 ```java OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache) .addInterceptor(new CacheInterceptor(mContext)) .build(); ``` #### 2.3.3、Cookie管理 **MVVMHabit**提供两种CookieStore:**PersistentCookieStore** (SharedPreferences管理)和**MemoryCookieStore** (内存管理),可以根据自己的业务需求,在构建okhttp时加入相应的cookieJar ```java OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext))) .build(); ``` 或者 ```java OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJarImpl(new MemoryCookieStore())) .build(); ``` #### 2.3.4、绑定生命周期 请求在ViewModel层。默认在BaseActivity中注入了LifecycleProvider对象到ViewModel,用于绑定请求的生命周期,View与请求共存亡。 ```java RetrofitClient.getInstance().create(DemoApiService.class) .demoGet() .compose(RxUtils.bindToLifecycle(getLifecycleProvider())) // 请求与View周期同步 .compose(RxUtils.schedulersTransformer()) // 线程调度 .compose(RxUtils.exceptionTransformer()) // 网络错误的异常转换 .subscribe(new Consumer>() { @Override public void accept(BaseResponse response) throws Exception { } }, new Consumer() { @Override public void accept(ResponseThrowable throwable) throws Exception { } }); ``` 在请求时关键需要加入组合操作符`.compose(RxUtils.bindToLifecycle(getLifecycleProvider()))`
**注意:** 由于BaseActivity/BaseFragment都实现了LifecycleProvider接口,并且默认注入到ViewModel中,所以在调用请求方法时可以直接调用getLifecycleProvider()拿到生命周期接口。如果你没有使用 **mvvmabit** 里面的BaseActivity或BaseFragment,使用自己定义的Base,那么需要让你自己的Activity继承RxAppCompatActivity、Fragment继承RxFragment才能用`RxUtils.bindToLifecycle(lifecycle)`方法。 #### 2.3.5、网络异常处理 网络异常在网络请求中非常常见,比如请求超时、解析错误、资源不存在、服务器内部错误等,在客户端则需要做相应的处理(当然,你可以把一部分异常甩锅给网络,比如当出现code 500时,提示:请求超时,请检查网络连接,此时偷偷将异常信息发送至后台(手动滑稽))。
在使用Retrofit请求时,加入组合操作符`.compose(RxUtils.exceptionTransformer())`,当发生网络异常时,回调onError(ResponseThrowable)方法,可以拿到异常的code和message,做相应处理。
> mvvmhabit中自定义了一个[ExceptionHandle](./mvvmhabit/src/main/java/me/goldze/mvvmhabit/http/ExceptionHandle.java),已为你完成了大部分网络异常的判断,也可自行根据项目的具体需求调整逻辑。
**注意:** 这里的网络异常code,并非是与服务端协议约定的code。网络异常可以分为两部分,一部分是协议异常,即出现code = 404、500等,属于HttpException,另一部分为请求异常,即出现:连接超时、解析错误、证书验证失等。而与服务端约定的code规则,它不属于网络异常,它是属于一种业务异常。在请求中可以使用RxJava的filter(过滤器),也可以自定义BaseSubscriber统一处理网络请求的业务逻辑异常。由于每个公司的业务协议不一样,所以具体需要你自己来处理该类异常。 ## 3、辅助功能 > 一个完整的快速开发框架,当然也少不了常用的辅助类。下面来介绍一下**MVVMabit**中有哪些辅助功能。 ### 3.1、事件总线 > 事件总线存在的优点想必大家都很清楚了,android自带的广播机制对于组件间的通信而言,使用非常繁琐,通信组件彼此之间的订阅和发布的耦合也比较严重,特别是对于事件的定义,广播机制局限于序列化的类(通过Intent传递),不够灵活。 #### 3.3.1、RxBus RxBus并不是一个库,而是一种模式。相信大多数开发者都使用过EventBus,对RxBus也是很熟悉。由于**MVVMabit**中已经加入RxJava,所以采用了RxBus代替EventBus作为事件总线通信,以减少库的依赖。 使用方法: 在ViewModel中重写registerRxBus()方法来注册RxBus,重写removeRxBus()方法来移除RxBus ```java //订阅者 private Disposable mSubscription; //注册RxBus @Override public void registerRxBus() { super.registerRxBus(); mSubscription = RxBus.getDefault().toObservable(String.class) .subscribe(new Consumer() { @Override public void accept(String s) throws Exception { } }); //将订阅者加入管理站 RxSubscriptions.add(mSubscription); } //移除RxBus @Override public void removeRxBus() { super.removeRxBus(); //将订阅者从管理站中移除 RxSubscriptions.remove(mSubscription); } ``` 在需要执行回调的地方发送 ```java RxBus.getDefault().post(object); ``` #### 3.3.2、Messenger Messenger是一个轻量级全局的消息通信工具,在我们的复杂业务中,难免会出现一些交叉的业务,比如ViewModel与ViewModel之间需要有数据交换,这时候可以轻松地使用Messenger发送一个实体或一个空消息,将事件从一个ViewModel回调到另一个ViewModel中。 使用方法: 定义一个静态String类型的字符串token ```java public static final String TOKEN_LOGINVIEWMODEL_REFRESH = "token_loginviewmodel_refresh"; ``` 在ViewModel中注册消息监听 ```java //注册一个空消息监听 //参数1:接受人(上下文) //参数2:定义的token //参数3:执行的回调监听 Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, new BindingAction() { @Override public void call() { } }); //注册一个带数据回调的消息监听 //参数1:接受人(上下文) //参数2:定义的token //参数3:实体的泛型约束 //参数4:执行的回调监听 Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, String.class, new BindingConsumer() { @Override public void call(String s) { } }); ``` 在需要回调的地方使用token发送消息 ```java //发送一个空消息 //参数1:定义的token Messenger.getDefault().sendNoMsg(LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH); //发送一个带数据回调消息 //参数1:回调的实体 //参数2:定义的token Messenger.getDefault().send("refresh",LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH); ``` > token最好不要重名,不然可能就会出现逻辑上的bug,为了更好的维护和清晰逻辑,建议以`aa_bb_cc`的格式来定义token。aa:TOKEN,bb:ViewModel的类名,cc:动作名(功能名)。 > 为了避免大量使用Messenger,建议只在ViewModel与ViewModel之间使用,View与ViewModel之间采用ObservableField去监听UI上的逻辑,可在继承了Base的Activity或Fragment中重写initViewObservable()方法来初始化UI的监听 注册了监听,当然也要解除它。在BaseActivity、BaseFragment的onDestroy()方法里已经调用`Messenger.getDefault().unregister(viewModel);`解除注册,所以不用担心忘记解除导致的逻辑错误和内存泄漏。 ### 3.2、文件下载 文件下载几乎是每个app必备的功能,图文的下载,软件的升级等都要用到,mvvmhabit使用Retrofit+Okhttp+RxJava+RxBus实现一行代码监听带进度的文件下载。 下载文件 ```java String loadUrl = "你的文件下载路径"; String destFileDir = context.getCacheDir().getPath(); //文件存放的路径 String destFileName = System.currentTimeMillis() + ".apk";//文件存放的名称 DownLoadManager.getInstance().load(loadUrl, new ProgressCallBack(destFileDir, destFileName) { @Override public void onStart() { //RxJava的onStart() } @Override public void onCompleted() { //RxJava的onCompleted() } @Override public void onSuccess(ResponseBody responseBody) { //下载成功的回调 } @Override public void progress(final long progress, final long total) { //下载中的回调 progress:当前进度 ,total:文件总大小 } @Override public void onError(Throwable e) { //下载错误回调 } }); ``` > 在ProgressResponseBody中使用了RxBus,发送下载进度信息到ProgressCallBack中,继承ProgressCallBack就可以监听到下载状态。回调方法全部执行在主线程,方便UI的更新,详情请参考例子程序。 ### 3.3、ContainerActivity 一个盛装Fragment的一个容器(代理)Activity,普通界面只需要编写Fragment,使用此Activity盛装,这样就不需要每个界面都在AndroidManifest中注册一遍 使用方法: 在ViewModel中调用BaseViewModel的方法开一个Fragment ```java startContainerActivity(你的Fragment类名.class.getCanonicalName()) ``` 在ViewModel中调用BaseViewModel的方法,携带一个序列化实体打开一个Fragment ```java Bundle mBundle = new Bundle(); mBundle.putParcelable("entity", entity); startContainerActivity(你的Fragment类名.class.getCanonicalName(), mBundle); ``` 在你的Fragment中取出实体 ```java Bundle mBundle = getArguments(); if (mBundle != null) { entity = mBundle.getParcelable("entity"); } ``` ### 3.4、6.0权限申请 > 对RxPermissions已经熟悉的朋友可以跳过。 使用方法: 例如请求相机权限,在ViewModel中调用 ```java //请求打开相机权限 RxPermissions rxPermissions = new RxPermissions((Activity) context); rxPermissions.request(Manifest.permission.CAMERA) .subscribe(new Consumer() { @Override public void accept(Boolean aBoolean) throws Exception { if (aBoolean) { ToastUtils.showShort("权限已经打开,直接跳入相机"); } else { ToastUtils.showShort("权限被拒绝"); } } }); ``` 更多权限申请方式请参考[RxPermissions原项目地址](https://github.com/tbruyelle/RxPermissions) ### 3.5、图片压缩 > 为了节约用户流量和加快图片上传的速度,某些场景将图片在本地压缩后再传给后台,所以特此提供一个图片压缩的辅助功能。 使用方法: RxJava的方式压缩单张图片,得到一个压缩后的图片文件对象 ```java String filePath = "mnt/sdcard/1.png"; ImageUtils.compressWithRx(filePath, new Consumer() { @Override public void accept(File file) throws Exception { //将文件放入RequestBody ... } }); ``` RxJava的方式压缩多张图片,按集合顺序每压缩成功一张,都将在onNext方法中得到一个压缩后的图片文件对象 ```java List filePaths = new ArrayList<>(); filePaths.add("mnt/sdcard/1.png"); filePaths.add("mnt/sdcard/2.png"); ImageUtils.compressWithRx(filePaths, new Subscriber() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(File file) { } }); ``` ### 3.6、其他辅助类 **ToastUtils:** 吐司工具类 **MaterialDialogUtils:** Material风格对话框工具类 **SPUtils:** SharedPreferences工具类 **SDCardUtils:** SD卡相关工具类 **ConvertUtils:** 转换相关工具类 **StringUtils:** 字符串相关工具类 **RegexUtils:** 正则相关工具类 **KLog:** 日志打印,含json格式打印 ## 4、附加 ### 4.1、编译错误解决方法 > 使用databinding其实有个缺点,就是会遇到一些编译错误,而AS不能很好的定位到错误的位置,这对于刚开始使用databinding的开发者来说是一个比较郁闷的事。那么我在此把我自己在开发中遇到的各种编译问题的解决方法分享给大家,希望这对你会有所帮助。 ##### 4.1.1、绑定错误 绑定错误是一个很常见的错误,基本都会犯。比如TextView的 `android:text=""` ,本来要绑定的是一个String类型,结果你不小心,可能绑了一个Boolean上去,或者变量名写错了,这时候编辑器不会报红错,而是在点编译运行的时候,在AS的Messages中会出现错误提示,如下图: 解决方法:把错误提示拉到最下面 (上面的提示找不到BR类这个不要管它),看最后一个错误 ,这里会提示是哪个xml出了错,并且会定位到行数,按照提示找到对应位置,即可解决该编译错误的问题。 **注意:** 行数要+1,意思是上面报出第33行错误,实际是第34行错误,AS定位的不准确 (这可能是它的一个bug) ##### 4.1.2、xml导包错误 在xml中需要导入ViewModel或者一些业务相关的类,假如在xml中导错了类,那一行则会报红,但是res/layout却没有错误提示,有一种场景,非常特殊,不容易找出错误位置。就是你写了一个xml,导入了一个类,比如XXXUtils,后来因为业务需求,把那个XXXUtils删了,这时候res/layout下不会出现任何错误,而你在编译运行的时候,才会出现错误日志。苦逼的是,不会像上面那样提示哪一个xml文件,哪一行出错了,最后一个错误只是一大片的报错报告。如下图: 解决方法:同样找到最后一个错误提示,找到Cannot resolve type for **xxx**这一句 (xxx是类名),然后使用全局搜索 (Ctrl+H) ,搜索哪个xml引用了这个类,跟踪点击进去,在xml就会出现一个红错,看到错误你就会明白了,这样就可解决该编译错误的问题。 ##### 4.1.3、build错误 构建多module工程时,如出现【4.1.1、绑定错误】,且你能确定这个绑定是没有问题的,经过修改后出现下图错误: 解决方法: 这种是databinding比较大的坑,清理、重构和删build都不起作用,网上很难找到方法。经过试验,解决办法是手动创建异常中提到的文件夹,或者拷贝上一个没有报错的版本中对应的文件夹,可以解决这个异常 ##### 4.1.4、自动生成类错误 有时候在写完xml时,databinding没有自动生成对应的Binding类及属性。比如新建了一个activity_login.xml,按照databinding的写法加入``` ```后,理论上会自动对应生成ActivityLoginBinding.java类和variable的属性,可能是as对databding的支持还不够吧,有时候偏偏就不生成,导致BR.xxx报红等一些莫名的错误。 解决方法:其实确保自己的写法没有问题,是可以直接运行的,报红不一定是你写的有问题,也有可能是编译器抽风了。或者使用下面的办法
第一招:Build->Clean Project;
第二招:Build->Rebuild Project;
第三招:重启大法。 ##### 4.1.5、gradle错误 如果遇到以下编译问题: 错误: 无法将类 BindingRecyclerViewAdapters中的方法 setAdapter应用到给定类型; 需要: RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds,ViewHolderFactory 找到: RecyclerView,ItemBinding,ObservableList,BindingRecyclerViewAdapter,ItemIds,ViewHolderFactory 原因: 推断类型不符合等式约束条件 推断: CAP#1 等式约束条件: CAP#1,NetWorkItemViewModel 其中, T是类型变量: T扩展已在方法 setAdapter(RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds,ViewHolderFactory)中声明的Object 其中, CAP#1是新类型变量: CAP#1从?的捕获扩展Object 一般是由于gradle plugin版本3.5.1造成的,请换成gradle plugin 3.5.0以下版本 ## 混淆 例子程序中给出了最新的【MVVMHabit混淆规则】,包含MVVMHabit中依赖的所有第三方library,可以将规则直接拷贝到自己app的混淆规则中。在此基础上你只需要关注自己业务代码以及自己引入第三方的混淆,【MVVMHabit混淆规则】请参考app目录下的[proguard-rules.pro](./app/proguard-rules.pro)文件。 ## 组件化 进阶Android组件化方案,请移步:[MVVMHabitComponent](https://github.com/goldze/MVVMHabitComponent) ## About **goldze:** 本人喜欢尝试新的技术,以后发现有好用的东西,我将会在企业项目中实战,没有问题了就会把它引入到**MVVMHabit**中,一直维护着这套框架,谢谢各位朋友的支持。如果觉得这套框架不错的话,麻烦点个 **star**,你的支持则是我前进的动力! **QQ群**:84692105 ## Thank 感谢[【zhangxiaoxiao】](https://github.com/zhanghacker)小伙伴长期提供的技术支持与帮助,为项目开源做出了很多的贡献。 ## License Copyright 2017 goldze(曾宪泽) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: UpdateLog.md ================================================ ## 更新日志 **v4.0.0:2021年07月16日** - 迁移AndroidX分支作为主线分支; - 升级第三方框架依赖版本; - 升级gradle插件版本支持; - 优化框架代码,解决已知Bug; - 修改文档说明。 *** **v3.0.7:2019年1月25日** - 优化框架代码,解决已知Bug; - 新增ViewPager+Fragment例子; - 新增RecycleView多布局例子; - 升级第三方依赖库; - 修改文档说明。 *** **v3.0.0:2018年10月8日** - 全面升级AAC,引入谷歌lifecycle组件; - 修改Base基类,满足新一套模式; - 升级第三方依赖库; - 修改例子程序; - 修改文档说明。 *** **v2.0.6:2018年7月19日** - 优化框架性能、基类逻辑,新增绑定命令; - 补充例子程序及注释; - 升级/修改第三方依赖库; - 补充文档说明。 *** **v2.0.0:2018年4月10日** - 全面升级RxJava2; - 优化绑定回调方式; - 升级第三方依赖库; - 微调例子程序。 ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion rootProject.ext.android.compileSdkVersion defaultConfig { applicationId rootProject.ext.android.applicationId minSdkVersion rootProject.ext.android.minSdkVersion targetSdkVersion rootProject.ext.android.targetSdkVersion versionCode rootProject.ext.android.versionCode versionName rootProject.ext.android.versionName } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } dataBinding { enabled true } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //support implementation rootProject.ext.support["design"] //下拉刷新,上拉加载 implementation 'com.lcodecorex:tkrefreshlayout:1.0.7' //底部tabBar implementation('me.majiajie:pager-bottom-tab-strip:2.2.5') { exclude group: 'com.android.support' } //MVVMHabit implementation project(':mvvmhabit') // implementation rootProject.ext.dependencies.MVVMHabit //内存泄漏测试 debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in D:\AndroidSDK/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile #------------------------------------------主项目混淆规则---------------------------------------------- #实体类不参与混淆 -keep class com.goldze.mvvmhabit.entity.** { *; } #tkrefreshlayout -keep class com.lcodecore.tkrefreshlayout.** { *; } -dontwarn com.lcodecore.tkrefreshlayout.** #-------------------------------------------MVVMHabit混淆规则---------------------------------------------- #---------------------------------1.实体类--------------------------------- -keep class me.goldze.mvvmhabit.http.BaseResponse { *; } #------------------------------------------------------------------------- #--------------------------------2.第三方包------------------------------- #support -keep class android.support.** { *; } -keep interface android.support.** { *; } -dontwarn android.support.** #databinding -keep class android.databinding.** { *; } -dontwarn android.databinding.** #annotation -keep class android.support.annotation.** { *; } -keep interface android.support.annotation.** { *; } #retrofit -dontwarn retrofit2.** -keep class retrofit2.** { *; } -keepattributes Signature -keepattributes Exceptions #gson -keepattributes Signature -keepattributes *Annotation* -keep class sun.misc.Unsafe { *; } -keep class com.google.gson.stream.** { *; } -keep class com.sunloto.shandong.bean.** { *; } #glide -keep public class * implements com.bumptech.glide.module.AppGlideModule -keep public class * implements com.bumptech.glide.module.LibraryGlideModule -keep class com.bumptech.glide.** { *; } -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { **[] $VALUES; public *; } #glide-transformations -keep class jp.wasabeef.glide.transformations.** {*;} -dontwarn jp.wasabeef.glide.transformations.** #okhttp -keepattributes Signature -keepattributes *Annotation* -keep class com.squareup.okhttp.** { *; } -keep interface com.squareup.okhttp.** { *; } -keep class okhttp3.** { *; } -keep interface okhttp3.** { *; } -dontwarn com.squareup.okhttp.** -dontwarn okhttp3.** -dontwarn okio.** #RxJava RxAndroid -dontwarn rx.* -dontwarn sun.misc.** -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { long producerIndex; long consumerIndex; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { rx.internal.util.atomic.LinkedQueueNode producerNode; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { rx.internal.util.atomic.LinkedQueueNode consumerNode; } #RxLifecycle -keep class com.trello.rxlifecycle2.** { *; } -keep interface com.trello.rxlifecycle2.** { *; } -dontwarn com.trello.rxlifecycle2.** #RxPermissions -keep class com.tbruyelle.rxpermissions2.** { *; } -keep interface com.tbruyelle.rxpermissions2.** { *; } #material-dialogs -keep class com.afollestad.materialdialogs.** { *; } -dontwarn om.afollestad.materialdialogs.** #=====================bindingcollectionadapter===================== -keep class me.tatarka.bindingcollectionadapter.** { *; } -dontwarn me.tatarka.bindingcollectionadapter.** #--------------------------------------------------------------------------- #---------------------------------3.与js互相调用的类------------------------ #无 #---------------------------------------------------------------------------- #---------------------------------4.反射相关的类和方法----------------------- -keep public class * extends me.goldze.mvvmhabit.base.BaseActivity{ *; } -keep public class * extends me.goldze.mvvmhabit.base.BaseFragment{ *; } -keep public class * extends me.goldze.mvvmhabit.binding.command.BindingCommand{ *; } -keep public class * extends me.goldze.mvvmhabit.binding.command.ResponseCommand{ *; } #---------------------------------------------------------------------------- #---------------------------------5.自定义控件------------------------------ -keep class me.goldze.mvvmhabit.widget.** { *; } #---------------------------------------------------------------------------- #---------------------------------6.其他定制区------------------------------- #native方法不被混淆 -keepclasseswithmembernames class * { native ; } #Parcelable 不被混淆 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } #Serializable 不被混淆 -keepnames class * implements java.io.Serializable #Serializable 不被混淆并且enum 类也不被混淆 -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient ; !private ; !private ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } #保持枚举 enum 类不被混淆 如果混淆报错,建议直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * { public void *ButtonClicked(android.view.View); } #不混淆资源类 -keepclassmembers class **.R$* { public static ; } #保持类中的所有方法名 -keepclassmembers class * { public ; private ; } #---------------------------------------------------------------------------- #---------------------------------基本指令区--------------------------------- # 抑制警告 -ignorewarnings #指定代码的压缩级别 -optimizationpasses 5 #包名不混合大小写 -dontusemixedcaseclassnames #不去忽略非公共的库类 -dontskipnonpubliclibraryclasses #指定不去忽略非公共库的类成员 -dontskipnonpubliclibraryclassmembers #优化 不优化输入的类文件 -dontoptimize #预校验 -dontpreverify #混淆时是否记录日志 -verbose # 混淆时所采用的算法 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #混淆包路径 -repackageclasses '' -flattenpackagehierarchy '' #保护注解 -keepattributes *Annotation* #避免混淆泛型 如果混淆报错建议关掉 -keepattributes Signature #保留SourceFile和LineNumber属性 -keepattributes SourceFile,LineNumberTable #忽略警告 #-ignorewarning #----------记录生成的日志数据,gradle build时在本项目根目录输出--------- #apk 包内所有 class 的内部结构 -dump class_files.txt #未混淆的类和成员 -printseeds seeds.txt #列出从 apk 中删除的代码 -printusage unused.txt #混淆前后的映射 -printmapping mapping.txt #---------------------------------------------------------------------------- #---------------------------------默认保留区--------------------------------- -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class * extends android.view.View -keep public class com.android.vending.licensing.ILicensingService -keep class android.support.** {*;} -keep public class * extends android.view.View{ *** get*(); void set*(***); public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -keep class **.R$* { *; } -keepclassmembers class * { void *(**On*Event); } #---------------------------------------------------------------------------- #---------------------------------webview------------------------------------ -keepclassmembers class fqcn.of.javascript.interface.for.Webview { public *; } -keepclassmembers class * extends android.webkit.WebViewClient { public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } -keepclassmembers class * extends android.webkit.WebViewClient { public void *(android.webkit.WebView, jav.lang.String); } #---------------------------------------------------------------------------- #---------------------------------------------------------------------------- ================================================ FILE: app/src/androidTest/java/com/goldze/mvvmhabit/ExampleInstrumentedTest.java ================================================ package com.goldze.mvvmhabit; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.goldze.mvvmhabit", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/app/AppApplication.java ================================================ package com.goldze.mvvmhabit.app; import com.goldze.mvvmhabit.BuildConfig; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.ui.login.LoginActivity; import com.squareup.leakcanary.LeakCanary; import me.goldze.mvvmhabit.base.BaseApplication; import me.goldze.mvvmhabit.crash.CaocConfig; import me.goldze.mvvmhabit.utils.KLog; /** * Created by goldze on 2017/7/16. */ public class AppApplication extends BaseApplication { @Override public void onCreate() { super.onCreate(); //是否开启打印日志 KLog.init(BuildConfig.DEBUG); //初始化全局异常崩溃 initCrash(); //内存泄漏检测 if (!LeakCanary.isInAnalyzerProcess(this)) { LeakCanary.install(this); } } private void initCrash() { CaocConfig.Builder.create() .backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT) //背景模式,开启沉浸式 .enabled(true) //是否启动全局异常捕获 .showErrorDetails(true) //是否显示错误详细信息 .showRestartButton(true) //是否显示重启按钮 .trackActivities(true) //是否跟踪Activity .minTimeBetweenCrashesMs(2000) //崩溃的间隔时间(毫秒) .errorDrawable(R.mipmap.ic_launcher) //错误图标 .restartActivity(LoginActivity.class) //重新启动后的activity // .errorActivity(YourCustomErrorActivity.class) //崩溃后的错误activity // .eventListener(new YourCustomEventListener()) //崩溃后的错误监听 .apply(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/app/AppViewModelFactory.java ================================================ package com.goldze.mvvmhabit.app; import android.annotation.SuppressLint; import android.app.Application; import com.goldze.mvvmhabit.data.DemoRepository; import com.goldze.mvvmhabit.ui.login.LoginViewModel; import com.goldze.mvvmhabit.ui.network.NetWorkViewModel; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; /** * Created by goldze on 2019/3/26. */ public class AppViewModelFactory extends ViewModelProvider.NewInstanceFactory { @SuppressLint("StaticFieldLeak") private static volatile AppViewModelFactory INSTANCE; private final Application mApplication; private final DemoRepository mRepository; public static AppViewModelFactory getInstance(Application application) { if (INSTANCE == null) { synchronized (AppViewModelFactory.class) { if (INSTANCE == null) { INSTANCE = new AppViewModelFactory(application, Injection.provideDemoRepository()); } } } return INSTANCE; } @VisibleForTesting public static void destroyInstance() { INSTANCE = null; } private AppViewModelFactory(Application application, DemoRepository repository) { this.mApplication = application; this.mRepository = repository; } @NonNull @Override public T create(@NonNull Class modelClass) { if (modelClass.isAssignableFrom(NetWorkViewModel.class)) { return (T) new NetWorkViewModel(mApplication, mRepository); } else if (modelClass.isAssignableFrom(LoginViewModel.class)) { return (T) new LoginViewModel(mApplication, mRepository); } throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName()); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/app/Injection.java ================================================ package com.goldze.mvvmhabit.app; import com.goldze.mvvmhabit.data.DemoRepository; import com.goldze.mvvmhabit.data.source.HttpDataSource; import com.goldze.mvvmhabit.data.source.LocalDataSource; import com.goldze.mvvmhabit.data.source.http.HttpDataSourceImpl; import com.goldze.mvvmhabit.data.source.http.service.DemoApiService; import com.goldze.mvvmhabit.data.source.local.LocalDataSourceImpl; import com.goldze.mvvmhabit.utils.RetrofitClient; /** * 注入全局的数据仓库,可以考虑使用Dagger2。(根据项目实际情况搭建,千万不要为了架构而架构) * Created by goldze on 2019/3/26. */ public class Injection { public static DemoRepository provideDemoRepository() { //网络API服务 DemoApiService apiService = RetrofitClient.getInstance().create(DemoApiService.class); //网络数据源 HttpDataSource httpDataSource = HttpDataSourceImpl.getInstance(apiService); //本地数据源 LocalDataSource localDataSource = LocalDataSourceImpl.getInstance(); //两条分支组成一个数据仓库 return DemoRepository.getInstance(httpDataSource, localDataSource); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/binding/twinklingrefreshlayout/ViewAdapter.java ================================================ package com.goldze.mvvmhabit.binding.twinklingrefreshlayout; import com.lcodecore.tkrefreshlayout.RefreshListenerAdapter; import com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout; import androidx.databinding.BindingAdapter; import me.goldze.mvvmhabit.binding.command.BindingCommand; /** * Created by goldze on 2017/6/16. * TwinklingRefreshLayout列表刷新的绑定适配器 */ public class ViewAdapter { @BindingAdapter(value = {"onRefreshCommand", "onLoadMoreCommand"}, requireAll = false) public static void onRefreshAndLoadMoreCommand(TwinklingRefreshLayout layout, final BindingCommand onRefreshCommand, final BindingCommand onLoadMoreCommand) { layout.setOnRefreshListener(new RefreshListenerAdapter() { @Override public void onRefresh(TwinklingRefreshLayout refreshLayout) { super.onRefresh(refreshLayout); if (onRefreshCommand != null) { onRefreshCommand.execute(); } } @Override public void onLoadMore(TwinklingRefreshLayout refreshLayout) { super.onLoadMore(refreshLayout); if (onLoadMoreCommand != null) { onLoadMoreCommand.execute(); } } }); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/data/DemoRepository.java ================================================ package com.goldze.mvvmhabit.data; import com.goldze.mvvmhabit.data.source.HttpDataSource; import com.goldze.mvvmhabit.data.source.LocalDataSource; import com.goldze.mvvmhabit.entity.DemoEntity; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import io.reactivex.Observable; import me.goldze.mvvmhabit.base.BaseModel; import me.goldze.mvvmhabit.http.BaseResponse; /** * MVVM的Model层,统一模块的数据仓库,包含网络数据和本地数据(一个应用可以有多个Repositor) * Created by goldze on 2019/3/26. */ public class DemoRepository extends BaseModel implements HttpDataSource, LocalDataSource { private volatile static DemoRepository INSTANCE = null; private final HttpDataSource mHttpDataSource; private final LocalDataSource mLocalDataSource; private DemoRepository(@NonNull HttpDataSource httpDataSource, @NonNull LocalDataSource localDataSource) { this.mHttpDataSource = httpDataSource; this.mLocalDataSource = localDataSource; } public static DemoRepository getInstance(HttpDataSource httpDataSource, LocalDataSource localDataSource) { if (INSTANCE == null) { synchronized (DemoRepository.class) { if (INSTANCE == null) { INSTANCE = new DemoRepository(httpDataSource, localDataSource); } } } return INSTANCE; } @VisibleForTesting public static void destroyInstance() { INSTANCE = null; } @Override public Observable login() { return mHttpDataSource.login(); } @Override public Observable loadMore() { return mHttpDataSource.loadMore(); } @Override public Observable> demoGet() { return mHttpDataSource.demoGet(); } @Override public Observable> demoPost(String catalog) { return mHttpDataSource.demoPost(catalog); } @Override public void saveUserName(String userName) { mLocalDataSource.saveUserName(userName); } @Override public void savePassword(String password) { mLocalDataSource.savePassword(password); } @Override public String getUserName() { return mLocalDataSource.getUserName(); } @Override public String getPassword() { return mLocalDataSource.getPassword(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/data/source/HttpDataSource.java ================================================ package com.goldze.mvvmhabit.data.source; import com.goldze.mvvmhabit.entity.DemoEntity; import io.reactivex.Observable; import me.goldze.mvvmhabit.http.BaseResponse; /** * Created by goldze on 2019/3/26. */ public interface HttpDataSource { //模拟登录 Observable login(); //模拟上拉加载 Observable loadMore(); Observable> demoGet(); Observable> demoPost(String catalog); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/data/source/LocalDataSource.java ================================================ package com.goldze.mvvmhabit.data.source; /** * Created by goldze on 2019/3/26. */ public interface LocalDataSource { /** * 保存用户名 */ void saveUserName(String userName); /** * 保存用户密码 */ void savePassword(String password); /** * 获取用户名 */ String getUserName(); /** * 获取用户密码 */ String getPassword(); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/data/source/http/HttpDataSourceImpl.java ================================================ package com.goldze.mvvmhabit.data.source.http; import com.goldze.mvvmhabit.data.source.HttpDataSource; import com.goldze.mvvmhabit.data.source.http.service.DemoApiService; import com.goldze.mvvmhabit.entity.DemoEntity; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import me.goldze.mvvmhabit.http.BaseResponse; /** * Created by goldze on 2019/3/26. */ public class HttpDataSourceImpl implements HttpDataSource { private DemoApiService apiService; private volatile static HttpDataSourceImpl INSTANCE = null; public static HttpDataSourceImpl getInstance(DemoApiService apiService) { if (INSTANCE == null) { synchronized (HttpDataSourceImpl.class) { if (INSTANCE == null) { INSTANCE = new HttpDataSourceImpl(apiService); } } } return INSTANCE; } public static void destroyInstance() { INSTANCE = null; } private HttpDataSourceImpl(DemoApiService apiService) { this.apiService = apiService; } @Override public Observable login() { return Observable.just(new Object()).delay(3, TimeUnit.SECONDS); //延迟3秒 } @Override public Observable loadMore() { return Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(ObservableEmitter observableEmitter) throws Exception { DemoEntity entity = new DemoEntity(); List itemsEntities = new ArrayList<>(); //模拟一部分假数据 for (int i = 0; i < 10; i++) { DemoEntity.ItemsEntity item = new DemoEntity.ItemsEntity(); item.setId(-1); item.setName("模拟条目"); itemsEntities.add(item); } entity.setItems(itemsEntities); observableEmitter.onNext(entity); } }).delay(3, TimeUnit.SECONDS); //延迟3秒 } @Override public Observable> demoGet() { return apiService.demoGet(); } @Override public Observable> demoPost(String catalog) { return apiService.demoPost(catalog); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/data/source/http/service/DemoApiService.java ================================================ package com.goldze.mvvmhabit.data.source.http.service; import com.goldze.mvvmhabit.entity.DemoEntity; import io.reactivex.Observable; import me.goldze.mvvmhabit.http.BaseResponse; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; /** * Created by goldze on 2017/6/15. */ public interface DemoApiService { @GET("action/apiv2/banner?catalog=1") Observable> demoGet(); @FormUrlEncoded @POST("action/apiv2/banner") Observable> demoPost(@Field("catalog") String catalog); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/data/source/local/LocalDataSourceImpl.java ================================================ package com.goldze.mvvmhabit.data.source.local; import com.goldze.mvvmhabit.data.source.LocalDataSource; import me.goldze.mvvmhabit.utils.SPUtils; /** * 本地数据源,可配合Room框架使用 * Created by goldze on 2019/3/26. */ public class LocalDataSourceImpl implements LocalDataSource { private volatile static LocalDataSourceImpl INSTANCE = null; public static LocalDataSourceImpl getInstance() { if (INSTANCE == null) { synchronized (LocalDataSourceImpl.class) { if (INSTANCE == null) { INSTANCE = new LocalDataSourceImpl(); } } } return INSTANCE; } public static void destroyInstance() { INSTANCE = null; } private LocalDataSourceImpl() { //数据库Helper构建 } @Override public void saveUserName(String userName) { SPUtils.getInstance().put("UserName", userName); } @Override public void savePassword(String password) { SPUtils.getInstance().put("password", password); } @Override public String getUserName() { return SPUtils.getInstance().getString("UserName"); } @Override public String getPassword() { return SPUtils.getInstance().getString("password"); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/entity/DemoEntity.java ================================================ package com.goldze.mvvmhabit.entity; import android.os.Parcel; import android.os.Parcelable; import java.util.List; /** * Created by goldze on 2017/7/17. */ public class DemoEntity { private String nextPageToken; private String prevPageToken; private int requestCount; private int responseCount; private int totalResults; private List items; public String getNextPageToken() { return nextPageToken; } public void setNextPageToken(String nextPageToken) { this.nextPageToken = nextPageToken; } public String getPrevPageToken() { return prevPageToken; } public void setPrevPageToken(String prevPageToken) { this.prevPageToken = prevPageToken; } public int getRequestCount() { return requestCount; } public void setRequestCount(int requestCount) { this.requestCount = requestCount; } public int getResponseCount() { return responseCount; } public void setResponseCount(int responseCount) { this.responseCount = responseCount; } public int getTotalResults() { return totalResults; } public void setTotalResults(int totalResults) { this.totalResults = totalResults; } public List getItems() { return items; } public void setItems(List items) { this.items = items; } public static class ItemsEntity implements Parcelable{ private String detail; private String href; private int id; private String img; private String name; private String pubDate; private int type; public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } public String getHref() { return href; } public void setHref(String href) { this.href = href; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPubDate() { return pubDate; } public void setPubDate(String pubDate) { this.pubDate = pubDate; } public int getType() { return type; } public void setType(int type) { this.type = type; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.detail); dest.writeString(this.href); dest.writeInt(this.id); dest.writeString(this.img); dest.writeString(this.name); dest.writeString(this.pubDate); dest.writeInt(this.type); } public ItemsEntity() { } protected ItemsEntity(Parcel in) { this.detail = in.readString(); this.href = in.readString(); this.id = in.readInt(); this.img = in.readString(); this.name = in.readString(); this.pubDate = in.readString(); this.type = in.readInt(); } public static final Creator CREATOR = new Creator() { @Override public ItemsEntity createFromParcel(Parcel source) { return new ItemsEntity(source); } @Override public ItemsEntity[] newArray(int size) { return new ItemsEntity[size]; } }; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/entity/FormEntity.java ================================================ package com.goldze.mvvmhabit.entity; import android.os.Parcel; import android.os.Parcelable; import androidx.databinding.BaseObservable; /** * Created by goldze on 2017/7/17. */ public class FormEntity extends BaseObservable implements Parcelable { private String id; private String name; private String sex; private String Bir; private String hobby; private Boolean isMarry; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getBir() { return Bir; } public void setBir(String bir) { Bir = bir; } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } public FormEntity() { } public Boolean getMarry() { return isMarry; } public void setMarry(Boolean marry) { isMarry = marry; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.id); dest.writeString(this.name); dest.writeString(this.sex); dest.writeString(this.Bir); dest.writeString(this.hobby); dest.writeValue(this.isMarry); } protected FormEntity(Parcel in) { this.id = in.readString(); this.name = in.readString(); this.sex = in.readString(); this.Bir = in.readString(); this.hobby = in.readString(); this.isMarry = (Boolean) in.readValue(Boolean.class.getClassLoader()); } public static final Creator CREATOR = new Creator() { @Override public FormEntity createFromParcel(Parcel source) { return new FormEntity(source); } @Override public FormEntity[] newArray(int size) { return new FormEntity[size]; } }; } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/entity/SpinnerItemData.java ================================================ package com.goldze.mvvmhabit.entity; import me.goldze.mvvmhabit.binding.viewadapter.spinner.IKeyAndValue; /** * Created by goldze on 2017/7/17. * Spinner条目数据实体 * 该实体类可以自定义,比如该类是数据库实体类. 或者是数据字典实体类, 但需要实现IKeyAndValue接口, 返回key和value两个值就可以在Spinner中绑定使用了 */ public class SpinnerItemData implements IKeyAndValue { //key是下拉显示的文字 private String key; //value是对应需要上传给后台的值, 这个可以根据具体业务具体定义 private String value; public SpinnerItemData(String key, String value) { this.key = key; this.value = value; } @Override public String getKey() { return key; } @Override public String getValue() { return value; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/base/adapter/BaseFragmentPagerAdapter.java ================================================ package com.goldze.mvvmhabit.ui.base.adapter; import java.util.List; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; /** * Created by goldze on 2017/7/17. * FragmentPager适配器 */ public class BaseFragmentPagerAdapter extends FragmentPagerAdapter { private List list;//ViewPager要填充的fragment列表 private List title;//tab中的title文字列表 //使用构造方法来将数据传进去 public BaseFragmentPagerAdapter(FragmentManager fm, List list, List title) { super(fm); this.list = list; this.title = title; } @Override public Fragment getItem(int position) {//获得position中的fragment来填充 return list.get(position); } @Override public int getCount() {//返回FragmentPager的个数 return list.size(); } //FragmentPager的标题,如果重写这个方法就显示不出tab的标题内容 @Override public CharSequence getPageTitle(int position) { return title.get(position); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/base/fragment/BasePagerFragment.java ================================================ package com.goldze.mvvmhabit.ui.base.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.FragmentBasePagerBinding; import com.goldze.mvvmhabit.ui.base.adapter.BaseFragmentPagerAdapter; import com.google.android.material.tabs.TabLayout; import java.util.List; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import me.goldze.mvvmhabit.base.BaseFragment; import me.goldze.mvvmhabit.base.BaseViewModel; /** * Created by goldze on 2017/7/17. * 抽取的二级BasePagerFragment */ public abstract class BasePagerFragment extends BaseFragment { private List mFragments; private List titlePager; protected abstract List pagerFragment(); protected abstract List pagerTitleString(); @Override public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return R.layout.fragment_base_pager; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initData() { mFragments = pagerFragment(); titlePager = pagerTitleString(); //设置Adapter BaseFragmentPagerAdapter pagerAdapter = new BaseFragmentPagerAdapter(getChildFragmentManager(), mFragments, titlePager); binding.viewPager.setAdapter(pagerAdapter); binding.tabs.setupWithViewPager(binding.viewPager); binding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabs)); } @Override public void initViewObservable() { } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/base/viewmodel/ToolbarViewModel.java ================================================ package com.goldze.mvvmhabit.ui.base.viewmodel; import android.app.Application; import android.view.View; import androidx.annotation.NonNull; import androidx.databinding.ObservableField; import androidx.databinding.ObservableInt; import me.goldze.mvvmhabit.base.BaseModel; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; /** * Create Author:goldze * Create Date:2019/01/03 * Description: 对应include标题的ToolbarViewModel * Toolbar的封装方式有很多种,具体封装需根据项目实际业务和习惯来编写 * 所有例子仅做参考,业务多种多样,可能我这里写的例子和你的需求不同,理解如何使用才最重要。 */ public class ToolbarViewModel extends BaseViewModel { //标题文字 public ObservableField titleText = new ObservableField<>(""); //右边文字 public ObservableField rightText = new ObservableField<>("更多"); //右边文字的观察者 public ObservableInt rightTextVisibleObservable = new ObservableInt(View.GONE); //右边图标的观察者 public ObservableInt rightIconVisibleObservable = new ObservableInt(View.GONE); //兼容databinding,去泛型化 public ToolbarViewModel toolbarViewModel; public ToolbarViewModel(@NonNull Application application) { this(application, null); } public ToolbarViewModel(@NonNull Application application, M model) { super(application, model); toolbarViewModel = this; } /** * 设置标题 * * @param text 标题文字 */ public void setTitleText(String text) { titleText.set(text); } /** * 设置右边文字 * * @param text 右边文字 */ public void setRightText(String text) { rightText.set(text); } /** * 设置右边文字的显示和隐藏 * * @param visibility */ public void setRightTextVisible(int visibility) { rightTextVisibleObservable.set(visibility); } /** * 设置右边图标的显示和隐藏 * * @param visibility */ public void setRightIconVisible(int visibility) { rightIconVisibleObservable.set(visibility); } /** * 返回按钮的点击事件 */ public final BindingCommand backOnClick = new BindingCommand(new BindingAction() { @Override public void call() { finish(); } }); public BindingCommand rightTextOnClick = new BindingCommand(new BindingAction() { @Override public void call() { rightTextOnClick(); } }); public BindingCommand rightIconOnClick = new BindingCommand(new BindingAction() { @Override public void call() { rightIconOnClick(); } }); /** * 右边文字的点击事件,子类可重写 */ protected void rightTextOnClick() { } /** * 右边图标的点击事件,子类可重写 */ protected void rightIconOnClick() { } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/form/FormFragment.java ================================================ package com.goldze.mvvmhabit.ui.form; import android.app.DatePickerDialog; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.DatePicker; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.FragmentFormBinding; import com.goldze.mvvmhabit.entity.FormEntity; import java.util.Calendar; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.Observable; import androidx.lifecycle.Observer; import me.goldze.mvvmhabit.base.BaseFragment; import me.goldze.mvvmhabit.utils.MaterialDialogUtils; /** * Created by goldze on 2017/7/17. * 表单提交/编辑界面 */ public class FormFragment extends BaseFragment { private FormEntity entity = new FormEntity(); @Override public void initParam() { //获取列表传入的实体 Bundle mBundle = getArguments(); if (mBundle != null) { entity = mBundle.getParcelable("entity"); } } @Override public int initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return R.layout.fragment_form; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initData() { //通过binding拿到toolbar控件, 设置给Activity ((AppCompatActivity) getActivity()).setSupportActionBar(binding.include.toolbar); //View层传参到ViewModel层 viewModel.setFormEntity(entity); //初始化标题 viewModel.initToolbar(); } @Override public void initViewObservable() { //监听日期选择 viewModel.uc.showDateDialogObservable.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable sender, int propertyId) { final Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); DatePickerDialog datePickerDialog = new DatePickerDialog(getContext(), new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { viewModel.setBir(year, month, dayOfMonth); } }, year, month, day); datePickerDialog.setMessage("生日选择"); datePickerDialog.show(); } }); viewModel.entityJsonLiveData.observe(this, new Observer() { @Override public void onChanged(@Nullable String submitJson) { MaterialDialogUtils.showBasicDialog(getContext(), "提交的json实体数据:\r\n" + submitJson).show(); } }); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/form/FormViewModel.java ================================================ package com.goldze.mvvmhabit.ui.form; import android.app.Application; import android.text.TextUtils; import android.view.View; import com.goldze.mvvmhabit.entity.FormEntity; import com.goldze.mvvmhabit.entity.SpinnerItemData; import com.goldze.mvvmhabit.ui.base.viewmodel.ToolbarViewModel; import com.google.gson.Gson; import java.util.ArrayList; import java.util.List; import androidx.annotation.NonNull; import androidx.databinding.ObservableBoolean; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.binding.command.BindingConsumer; import me.goldze.mvvmhabit.binding.viewadapter.spinner.IKeyAndValue; import me.goldze.mvvmhabit.bus.event.SingleLiveEvent; import me.goldze.mvvmhabit.utils.ToastUtils; /** * Created by goldze on 2017/7/17. */ public class FormViewModel extends ToolbarViewModel { public FormEntity entity; public List sexItemDatas; public SingleLiveEvent entityJsonLiveData = new SingleLiveEvent<>(); //封装一个界面发生改变的观察者 public UIChangeObservable uc; public class UIChangeObservable { //显示日期对话框 public ObservableBoolean showDateDialogObservable; public UIChangeObservable() { showDateDialogObservable = new ObservableBoolean(false); } } public FormViewModel(@NonNull Application application) { super(application); } @Override public void onCreate() { super.onCreate(); uc = new UIChangeObservable(); //sexItemDatas 一般可以从本地Sqlite数据库中取出数据字典对象集合,让该对象实现IKeyAndValue接口 sexItemDatas = new ArrayList<>(); sexItemDatas.add(new SpinnerItemData("男", "1")); sexItemDatas.add(new SpinnerItemData("女", "2")); } /** * 初始化Toolbar */ public void initToolbar() { //初始化标题栏 setRightTextVisible(View.VISIBLE); if (TextUtils.isEmpty(entity.getId())) { //ID为空是新增 setTitleText("表单提交"); } else { //ID不为空是修改 setTitleText("表单编辑"); } } @Override public void rightTextOnClick() { ToastUtils.showShort("更多"); } public void setFormEntity(FormEntity entity) { if (this.entity == null) { this.entity = entity; } } //性别选择的监听 public BindingCommand onSexSelectorCommand = new BindingCommand<>(new BindingConsumer() { @Override public void call(IKeyAndValue iKeyAndValue) { entity.setSex(iKeyAndValue.getValue()); } }); //生日选择的监听 public BindingCommand onBirClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { //回调到view层(Fragment)中显示日期对话框 uc.showDateDialogObservable.set(!uc.showDateDialogObservable.get()); } }); //是否已婚Switch点状态改变回调 public BindingCommand onMarryCheckedChangeCommand = new BindingCommand<>(new BindingConsumer() { @Override public void call(Boolean isChecked) { entity.setMarry(isChecked); } }); //提交按钮点击事件 public BindingCommand onCmtClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { String submitJson = new Gson().toJson(entity); entityJsonLiveData.setValue(submitJson); } }); public void setBir(int year, int month, int dayOfMonth) { //设置数据到实体中,自动刷新界面 entity.setBir(year + "年" + (month + 1) + "月" + dayOfMonth + "日"); //刷新实体,驱动界面更新 entity.notifyChange(); } @Override public void onDestroy() { super.onDestroy(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/login/LoginActivity.java ================================================ package com.goldze.mvvmhabit.ui.login; import android.os.Bundle; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.app.AppViewModelFactory; import com.goldze.mvvmhabit.databinding.ActivityLoginBinding; import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import me.goldze.mvvmhabit.base.BaseActivity; /** * 一个MVVM模式的登陆界面 */ public class LoginActivity extends BaseActivity { //ActivityLoginBinding类是databinding框架自定生成的,对应activity_login.xml @Override public int initContentView(Bundle savedInstanceState) { return R.layout.activity_login; } @Override public int initVariableId() { return BR.viewModel; } @Override public LoginViewModel initViewModel() { //使用自定义的ViewModelFactory来创建ViewModel,如果不重写该方法,则默认会调用LoginViewModel(@NonNull Application application)构造方法 AppViewModelFactory factory = AppViewModelFactory.getInstance(getApplication()); return ViewModelProviders.of(this, factory).get(LoginViewModel.class); } @Override public void initViewObservable() { //监听ViewModel中pSwitchObservable的变化, 当ViewModel中执行【uc.pSwitchObservable.set(!uc.pSwitchObservable.get());】时会回调该方法 viewModel.uc.pSwitchEvent.observe(this, new Observer() { @Override public void onChanged(@Nullable Boolean aBoolean) { //pSwitchObservable是boolean类型的观察者,所以可以直接使用它的值改变密码开关的图标 if (viewModel.uc.pSwitchEvent.getValue()) { //密码可见 //在xml中定义id后,使用binding可以直接拿到这个view的引用,不再需要findViewById去找控件了 binding.ivSwichPasswrod.setImageResource(R.mipmap.show_psw); binding.etPassword.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); } else { //密码不可见 binding.ivSwichPasswrod.setImageResource(R.mipmap.show_psw_press); binding.etPassword.setTransformationMethod(PasswordTransformationMethod.getInstance()); } } }); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/login/LoginViewModel.java ================================================ package com.goldze.mvvmhabit.ui.login; import android.app.Application; import android.text.TextUtils; import android.view.View; import com.goldze.mvvmhabit.data.DemoRepository; import com.goldze.mvvmhabit.ui.main.DemoActivity; import androidx.annotation.NonNull; import androidx.databinding.ObservableField; import androidx.databinding.ObservableInt; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.binding.command.BindingConsumer; import me.goldze.mvvmhabit.bus.event.SingleLiveEvent; import me.goldze.mvvmhabit.utils.RxUtils; import me.goldze.mvvmhabit.utils.ToastUtils; /** * Created by goldze on 2017/7/17. */ public class LoginViewModel extends BaseViewModel { //用户名的绑定 public ObservableField userName = new ObservableField<>(""); //密码的绑定 public ObservableField password = new ObservableField<>(""); //用户名清除按钮的显示隐藏绑定 public ObservableInt clearBtnVisibility = new ObservableInt(); //封装一个界面发生改变的观察者 public UIChangeObservable uc = new UIChangeObservable(); public class UIChangeObservable { //密码开关观察者 public SingleLiveEvent pSwitchEvent = new SingleLiveEvent<>(); } public LoginViewModel(@NonNull Application application, DemoRepository repository) { super(application, repository); //从本地取得数据绑定到View层 userName.set(model.getUserName()); password.set(model.getPassword()); } //清除用户名的点击事件, 逻辑从View层转换到ViewModel层 public BindingCommand clearUserNameOnClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { userName.set(""); } }); //密码显示开关 (你可以尝试着狂按这个按钮,会发现它有防多次点击的功能) public BindingCommand passwordShowSwitchOnClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { //让观察者的数据改变,逻辑从ViewModel层转到View层,在View层的监听则会被调用 uc.pSwitchEvent.setValue(uc.pSwitchEvent.getValue() == null || !uc.pSwitchEvent.getValue()); } }); //用户名输入框焦点改变的回调事件 public BindingCommand onFocusChangeCommand = new BindingCommand<>(new BindingConsumer() { @Override public void call(Boolean hasFocus) { if (hasFocus) { clearBtnVisibility.set(View.VISIBLE); } else { clearBtnVisibility.set(View.INVISIBLE); } } }); //登录按钮的点击事件 public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { login(); } }); /** * 网络模拟一个登陆操作 **/ private void login() { if (TextUtils.isEmpty(userName.get())) { ToastUtils.showShort("请输入账号!"); return; } if (TextUtils.isEmpty(password.get())) { ToastUtils.showShort("请输入密码!"); return; } //RaJava模拟登录 addSubscribe(model.login() .compose(RxUtils.schedulersTransformer()) //线程调度 .doOnSubscribe(new Consumer() { @Override public void accept(Disposable disposable) throws Exception { showDialog(); } }) .subscribe(new Consumer() { @Override public void accept(Object o) throws Exception { dismissDialog(); //保存账号密码 model.saveUserName(userName.get()); model.savePassword(password.get()); //进入DemoActivity页面 startActivity(DemoActivity.class); //关闭页面 finish(); } })); } @Override public void onDestroy() { super.onDestroy(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/main/DemoActivity.java ================================================ package com.goldze.mvvmhabit.ui.main; import android.Manifest; import android.app.ProgressDialog; import android.content.pm.ActivityInfo; import android.os.Bundle; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.ActivityDemoBinding; import com.tbruyelle.rxpermissions2.RxPermissions; import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import io.reactivex.functions.Consumer; import me.goldze.mvvmhabit.base.BaseActivity; import me.goldze.mvvmhabit.http.DownLoadManager; import me.goldze.mvvmhabit.http.download.ProgressCallBack; import me.goldze.mvvmhabit.utils.ToastUtils; import okhttp3.ResponseBody; /** * Created by goldze on 2017/7/17. */ public class DemoActivity extends BaseActivity { @Override public void initParam() { super.initParam(); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } @Override public int initContentView(Bundle savedInstanceState) { return R.layout.activity_demo; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initViewObservable() { //注册监听相机权限的请求 viewModel.requestCameraPermissions.observe(this, new Observer() { @Override public void onChanged(@Nullable Boolean aBoolean) { requestCameraPermissions(); } }); //注册文件下载的监听 viewModel.loadUrlEvent.observe(this, new Observer() { @Override public void onChanged(@Nullable String url) { downFile(url); } }); } /** * 请求相机权限 */ private void requestCameraPermissions() { //请求打开相机权限 RxPermissions rxPermissions = new RxPermissions(DemoActivity.this); rxPermissions.request(Manifest.permission.CAMERA) .subscribe(new Consumer() { @Override public void accept(Boolean aBoolean) throws Exception { if (aBoolean) { ToastUtils.showShort("相机权限已经打开,直接跳入相机"); } else { ToastUtils.showShort("权限被拒绝"); } } }); } private void downFile(String url) { String destFileDir = getApplication().getCacheDir().getPath(); String destFileName = System.currentTimeMillis() + ".apk"; final ProgressDialog progressDialog = new ProgressDialog(this); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setTitle("正在下载..."); progressDialog.setCancelable(false); progressDialog.show(); DownLoadManager.getInstance().load(url, new ProgressCallBack(destFileDir, destFileName) { @Override public void onStart() { super.onStart(); } @Override public void onCompleted() { progressDialog.dismiss(); } @Override public void onSuccess(ResponseBody responseBody) { ToastUtils.showShort("文件下载完成!"); } @Override public void progress(final long progress, final long total) { progressDialog.setMax((int) total); progressDialog.setProgress((int) progress); } @Override public void onError(Throwable e) { e.printStackTrace(); ToastUtils.showShort("文件下载失败!"); progressDialog.dismiss(); } }); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/main/DemoViewModel.java ================================================ package com.goldze.mvvmhabit.ui.main; import android.app.Application; import android.os.Bundle; import com.goldze.mvvmhabit.entity.FormEntity; import com.goldze.mvvmhabit.ui.form.FormFragment; import com.goldze.mvvmhabit.ui.network.NetWorkFragment; import com.goldze.mvvmhabit.ui.rv_multi.MultiRecycleViewFragment; import com.goldze.mvvmhabit.ui.tab_bar.activity.TabBarActivity; import com.goldze.mvvmhabit.ui.viewpager.activity.ViewPagerActivity; import com.goldze.mvvmhabit.ui.vp_frg.ViewPagerGroupFragment; import androidx.annotation.NonNull; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.bus.event.SingleLiveEvent; /** * Created by goldze on 2017/7/17. */ public class DemoViewModel extends BaseViewModel { //使用Observable public SingleLiveEvent requestCameraPermissions = new SingleLiveEvent<>(); //使用LiveData public SingleLiveEvent loadUrlEvent = new SingleLiveEvent<>(); public DemoViewModel(@NonNull Application application) { super(application); } //网络访问点击事件 public BindingCommand netWorkClick = new BindingCommand(new BindingAction() { @Override public void call() { startContainerActivity(NetWorkFragment.class.getCanonicalName()); } }); //RecycleView多布局 public BindingCommand rvMultiClick = new BindingCommand(new BindingAction() { @Override public void call() { startContainerActivity(MultiRecycleViewFragment.class.getCanonicalName()); } }); //进入TabBarActivity public BindingCommand startTabBarClick = new BindingCommand(new BindingAction() { @Override public void call() { startActivity(TabBarActivity.class); } }); //ViewPager绑定 public BindingCommand viewPagerBindingClick = new BindingCommand(new BindingAction() { @Override public void call() { startActivity(ViewPagerActivity.class); } }); //ViewPager+Fragment public BindingCommand viewPagerGroupBindingClick = new BindingCommand(new BindingAction() { @Override public void call() { startContainerActivity(ViewPagerGroupFragment.class.getCanonicalName()); } }); //表单提交点击事件 public BindingCommand formSbmClick = new BindingCommand(new BindingAction() { @Override public void call() { startContainerActivity(FormFragment.class.getCanonicalName()); } }); //表单修改点击事件 public BindingCommand formModifyClick = new BindingCommand(new BindingAction() { @Override public void call() { //模拟一个修改的实体数据 FormEntity entity = new FormEntity(); entity.setId("12345678"); entity.setName("goldze"); entity.setSex("1"); entity.setBir("xxxx年xx月xx日"); entity.setMarry(true); //传入实体数据 Bundle mBundle = new Bundle(); mBundle.putParcelable("entity", entity); startContainerActivity(FormFragment.class.getCanonicalName(), mBundle); } }); //权限申请 public BindingCommand permissionsClick = new BindingCommand(new BindingAction() { @Override public void call() { requestCameraPermissions.call(); } }); //全局异常捕获 public BindingCommand exceptionClick = new BindingCommand(new BindingAction() { @Override public void call() { //伪造一个异常 Integer.parseInt("goldze"); } }); //文件下载 public BindingCommand fileDownLoadClick = new BindingCommand(new BindingAction() { @Override public void call() { loadUrlEvent.setValue("http://gdown.baidu.com/data/wisegame/dc8a46540c7960a2/baidushoujizhushou_16798087.apk"); } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/network/NetWorkFragment.java ================================================ package com.goldze.mvvmhabit.ui.network; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.app.AppViewModelFactory; import com.goldze.mvvmhabit.databinding.FragmentNetworkBinding; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import me.goldze.mvvmhabit.base.BaseFragment; import me.goldze.mvvmhabit.utils.MaterialDialogUtils; import me.goldze.mvvmhabit.utils.ToastUtils; import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter; /** * Created by goldze on 2017/7/17. * 网络请求列表界面 */ public class NetWorkFragment extends BaseFragment { @Override public void initParam() { super.initParam(); getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } @Override public int initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return R.layout.fragment_network; } @Override public int initVariableId() { return BR.viewModel; } @Override public NetWorkViewModel initViewModel() { //使用自定义的ViewModelFactory来创建ViewModel,如果不重写该方法,则默认会调用NetWorkViewModel(@NonNull Application application)构造方法 AppViewModelFactory factory = AppViewModelFactory.getInstance(getActivity().getApplication()); return ViewModelProviders.of(this, factory).get(NetWorkViewModel.class); } @Override public void initData() { //请求网络数据 viewModel.requestNetWork(); } @Override public void initViewObservable() { //监听下拉刷新完成 viewModel.uc.finishRefreshing.observe(this, new Observer() { @Override public void onChanged(@Nullable Object o) { //结束刷新 binding.twinklingRefreshLayout.finishRefreshing(); } }); //监听上拉加载完成 viewModel.uc.finishLoadmore.observe(this, new Observer() { @Override public void onChanged(@Nullable Object o) { //结束刷新 binding.twinklingRefreshLayout.finishLoadmore(); } }); //监听删除条目 viewModel.deleteItemLiveData.observe(this, new Observer() { @Override public void onChanged(@Nullable final NetWorkItemViewModel netWorkItemViewModel) { int index = viewModel.getItemPosition(netWorkItemViewModel); //删除选择对话框 MaterialDialogUtils.showBasicDialog(getContext(), "提示", "是否删除【" + netWorkItemViewModel.entity.get().getName() + "】? position:" + index) .onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { ToastUtils.showShort("取消"); } }).onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { viewModel.deleteItem(netWorkItemViewModel); } }).show(); } }); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/network/NetWorkItemViewModel.java ================================================ package com.goldze.mvvmhabit.ui.network; import android.graphics.drawable.Drawable; import android.os.Bundle; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.entity.DemoEntity; import com.goldze.mvvmhabit.ui.network.detail.DetailFragment; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.databinding.ObservableField; import me.goldze.mvvmhabit.base.ItemViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.utils.ToastUtils; /** * Created by goldze on 2017/7/17. */ public class NetWorkItemViewModel extends ItemViewModel { public ObservableField entity = new ObservableField<>(); public Drawable drawableImg; public NetWorkItemViewModel(@NonNull NetWorkViewModel viewModel, DemoEntity.ItemsEntity entity) { super(viewModel); this.entity.set(entity); //ImageView的占位图片,可以解决RecyclerView中图片错误问题 drawableImg = ContextCompat.getDrawable(viewModel.getApplication(), R.mipmap.ic_launcher); } /** * 获取position的方式有很多种,indexOf是其中一种,常见的还有在Adapter中、ItemBinding.of回调里 * * @return */ public int getPosition() { return viewModel.getItemPosition(this); } //条目的点击事件 public BindingCommand itemClick = new BindingCommand(new BindingAction() { @Override public void call() { //这里可以通过一个标识,做出判断,已达到跳入不同界面的逻辑 if (entity.get().getId() == -1) { viewModel.deleteItemLiveData.setValue(NetWorkItemViewModel.this); } else { //跳转到详情界面,传入条目的实体对象 Bundle mBundle = new Bundle(); mBundle.putParcelable("entity", entity.get()); viewModel.startContainerActivity(DetailFragment.class.getCanonicalName(), mBundle); } } }); //条目的长按事件 public BindingCommand itemLongClick = new BindingCommand(new BindingAction() { @Override public void call() { //以前是使用Messenger发送事件,在NetWorkViewModel中完成删除逻辑 // Messenger.getDefault().send(NetWorkItemViewModel.this, NetWorkViewModel.TOKEN_NETWORKVIEWMODEL_DELTE_ITEM); //现在ItemViewModel中存在ViewModel引用,可以直接拿到LiveData去做删除 ToastUtils.showShort(entity.get().getName()); } }); // /** // * 可以在xml中使用binding:currentView="@{viewModel.titleTextView}" 拿到这个控件的引用, 但是强烈不推荐这样做,避免内存泄漏 // **/ // private TextView tv; // //将标题TextView控件回调到ViewModel中 // public BindingCommand titleTextView = new BindingCommand(new BindingConsumer() { // @Override // public void call(TextView tv) { // NetWorkItemViewModel.this.tv = tv; // } // }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/network/NetWorkViewModel.java ================================================ package com.goldze.mvvmhabit.ui.network; import android.app.Application; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.data.DemoRepository; import com.goldze.mvvmhabit.entity.DemoEntity; import androidx.annotation.NonNull; import androidx.databinding.ObservableArrayList; import androidx.databinding.ObservableList; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; import io.reactivex.observers.DisposableObserver; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.bus.event.SingleLiveEvent; import me.goldze.mvvmhabit.http.BaseResponse; import me.goldze.mvvmhabit.http.ResponseThrowable; import me.goldze.mvvmhabit.utils.RxUtils; import me.goldze.mvvmhabit.utils.ToastUtils; import me.tatarka.bindingcollectionadapter2.ItemBinding; /** * Created by goldze on 2017/7/17. */ public class NetWorkViewModel extends BaseViewModel { public SingleLiveEvent deleteItemLiveData = new SingleLiveEvent<>(); //封装一个界面发生改变的观察者 public UIChangeObservable uc = new UIChangeObservable(); public class UIChangeObservable { //下拉刷新完成 public SingleLiveEvent finishRefreshing = new SingleLiveEvent<>(); //上拉加载完成 public SingleLiveEvent finishLoadmore = new SingleLiveEvent<>(); } public NetWorkViewModel(@NonNull Application application, DemoRepository repository) { super(application, repository); } //给RecyclerView添加ObservableList public ObservableList observableList = new ObservableArrayList<>(); //给RecyclerView添加ItemBinding public ItemBinding itemBinding = ItemBinding.of(BR.viewModel, R.layout.item_network); //下拉刷新 public BindingCommand onRefreshCommand = new BindingCommand(new BindingAction() { @Override public void call() { ToastUtils.showShort("下拉刷新"); requestNetWork(); } }); //上拉加载 public BindingCommand onLoadMoreCommand = new BindingCommand(new BindingAction() { @Override public void call() { if (observableList.size() > 50) { ToastUtils.showLong("兄dei,你太无聊啦~崩是不可能的~"); uc.finishLoadmore.call(); return; } //模拟网络上拉加载更多 model.loadMore() .compose(RxUtils.schedulersTransformer()) //线程调度 .doOnSubscribe(NetWorkViewModel.this) //请求与ViewModel周期同步 .doOnSubscribe(new Consumer() { @Override public void accept(Disposable disposable) throws Exception { ToastUtils.showShort("上拉加载"); } }) .subscribe(new Consumer() { @Override public void accept(DemoEntity entity) throws Exception { for (DemoEntity.ItemsEntity itemsEntity : entity.getItems()) { NetWorkItemViewModel itemViewModel = new NetWorkItemViewModel(NetWorkViewModel.this, itemsEntity); //双向绑定动态添加Item observableList.add(itemViewModel); } //刷新完成收回 uc.finishLoadmore.call(); } }); } }); /** * 网络请求方法,在ViewModel中调用Model层,通过Okhttp+Retrofit+RxJava发起请求 */ public void requestNetWork() { //可以调用addSubscribe()添加Disposable,请求与View周期同步 model.demoGet() .compose(RxUtils.schedulersTransformer()) //线程调度 .compose(RxUtils.exceptionTransformer()) // 网络错误的异常转换, 这里可以换成自己的ExceptionHandle .doOnSubscribe(this)//请求与ViewModel周期同步 .doOnSubscribe(new Consumer() { @Override public void accept(Disposable disposable) throws Exception { showDialog("正在请求..."); } }) .subscribe(new DisposableObserver>() { @Override public void onNext(BaseResponse response) { //清除列表 observableList.clear(); //请求成功 if (response.getCode() == 1) { for (DemoEntity.ItemsEntity entity : response.getResult().getItems()) { NetWorkItemViewModel itemViewModel = new NetWorkItemViewModel(NetWorkViewModel.this, entity); //双向绑定动态添加Item observableList.add(itemViewModel); } } else { //code错误时也可以定义Observable回调到View层去处理 ToastUtils.showShort("数据错误"); } } @Override public void onError(Throwable throwable) { //关闭对话框 dismissDialog(); //请求刷新完成收回 uc.finishRefreshing.call(); if (throwable instanceof ResponseThrowable) { ToastUtils.showShort(((ResponseThrowable) throwable).message); } } @Override public void onComplete() { //关闭对话框 dismissDialog(); //请求刷新完成收回 uc.finishRefreshing.call(); } }); } /** * 删除条目 * * @param netWorkItemViewModel */ public void deleteItem(NetWorkItemViewModel netWorkItemViewModel) { //点击确定,在 observableList 绑定中删除,界面立即刷新 observableList.remove(netWorkItemViewModel); } /** * 获取条目下标 * * @param netWorkItemViewModel * @return */ public int getItemPosition(NetWorkItemViewModel netWorkItemViewModel) { return observableList.indexOf(netWorkItemViewModel); } @Override public void onDestroy() { super.onDestroy(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/network/detail/DetailFragment.java ================================================ package com.goldze.mvvmhabit.ui.network.detail; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.FragmentDetailBinding; import com.goldze.mvvmhabit.entity.DemoEntity; import me.goldze.mvvmhabit.base.BaseFragment; /** * Created by goldze on 2017/7/17. * 详情界面 */ public class DetailFragment extends BaseFragment { private DemoEntity.ItemsEntity entity; @Override public void initParam() { //获取列表传入的实体 Bundle mBundle = getArguments(); if (mBundle != null) { entity = mBundle.getParcelable("entity"); } } @Override public int initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return R.layout.fragment_detail; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initData() { viewModel.setDemoEntity(entity); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/network/detail/DetailViewModel.java ================================================ package com.goldze.mvvmhabit.ui.network.detail; import android.app.Application; import com.goldze.mvvmhabit.entity.DemoEntity; import androidx.annotation.NonNull; import androidx.databinding.ObservableField; import me.goldze.mvvmhabit.base.BaseViewModel; /** * Created by goldze on 2017/7/17. */ public class DetailViewModel extends BaseViewModel { public ObservableField entity = new ObservableField<>(); public DetailViewModel(@NonNull Application application) { super(application); } public void setDemoEntity(DemoEntity.ItemsEntity entity) { this.entity.set(entity); } @Override public void onDestroy() { super.onDestroy(); entity = null; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/rv_multi/MultiRecycleHeadViewModel.java ================================================ package com.goldze.mvvmhabit.ui.rv_multi; import androidx.annotation.NonNull; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.base.MultiItemViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.utils.ToastUtils; /** * Create Author:goldze * Create Date:2019/01/25 * Description: */ public class MultiRecycleHeadViewModel extends MultiItemViewModel { public MultiRecycleHeadViewModel(@NonNull BaseViewModel viewModel) { super(viewModel); } //条目的点击事件 public BindingCommand itemClick = new BindingCommand(new BindingAction() { @Override public void call() { ToastUtils.showShort("我是头布局"); } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/rv_multi/MultiRecycleLeftItemViewModel.java ================================================ package com.goldze.mvvmhabit.ui.rv_multi; import androidx.annotation.NonNull; import androidx.databinding.ObservableField; import me.goldze.mvvmhabit.base.MultiItemViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.utils.ToastUtils; /** * Create Author:goldze * Create Date:2019/01/25 * Description: */ public class MultiRecycleLeftItemViewModel extends MultiItemViewModel { public ObservableField text = new ObservableField<>(""); public MultiRecycleLeftItemViewModel(@NonNull MultiRecycleViewModel viewModel, String text) { super(viewModel); this.text.set(text); } //条目的点击事件 public BindingCommand itemClick = new BindingCommand(new BindingAction() { @Override public void call() { //拿到position int position = viewModel.observableList.indexOf(MultiRecycleLeftItemViewModel.this); ToastUtils.showShort("position:" + position); } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/rv_multi/MultiRecycleRightItemViewModel.java ================================================ package com.goldze.mvvmhabit.ui.rv_multi; import androidx.annotation.NonNull; import androidx.databinding.ObservableField; import me.goldze.mvvmhabit.base.MultiItemViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.utils.ToastUtils; /** * Create Author:goldze * Create Date:2019/01/25 * Description: */ public class MultiRecycleRightItemViewModel extends MultiItemViewModel { public ObservableField text = new ObservableField<>(""); public MultiRecycleRightItemViewModel(@NonNull MultiRecycleViewModel viewModel, String text) { super(viewModel); this.text.set(text); } //条目的点击事件 public BindingCommand itemClick = new BindingCommand(new BindingAction() { @Override public void call() { //拿到position int position = viewModel.observableList.indexOf(MultiRecycleRightItemViewModel.this); ToastUtils.showShort("position:" + position); } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/rv_multi/MultiRecycleViewFragment.java ================================================ package com.goldze.mvvmhabit.ui.rv_multi; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.FragmentMultiRvBinding; import androidx.annotation.Nullable; import me.goldze.mvvmhabit.base.BaseFragment; import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter; /** * Create Author:goldze * Create Date:2019/01/25 * Description:RecycleView多布局实现 */ public class MultiRecycleViewFragment extends BaseFragment { @Override public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return R.layout.fragment_multi_rv; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initData() { super.initData(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/rv_multi/MultiRecycleViewModel.java ================================================ package com.goldze.mvvmhabit.ui.rv_multi; import android.app.Application; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import androidx.annotation.NonNull; import androidx.databinding.ObservableArrayList; import androidx.databinding.ObservableList; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.base.MultiItemViewModel; import me.tatarka.bindingcollectionadapter2.ItemBinding; import me.tatarka.bindingcollectionadapter2.OnItemBind; /** * Create Author:goldze * Create Date:2019/01/25 * Description: */ public class MultiRecycleViewModel extends BaseViewModel { private static final String MultiRecycleType_Head = "head"; private static final String MultiRecycleType_Left = "left"; private static final String MultiRecycleType_Right = "right"; public MultiRecycleViewModel(@NonNull Application application) { super(application); //模拟10个条目,数据源可以来自网络 for (int i = 0; i < 20; i++) { if (i == 0) { MultiItemViewModel item = new MultiRecycleHeadViewModel(this); //条目类型为头布局 item.multiItemType(MultiRecycleType_Head); observableList.add(item); } else { String text = "我是第" + i + "条"; if (i % 2 == 0) { MultiItemViewModel item = new MultiRecycleLeftItemViewModel(this, text); //条目类型为左布局 item.multiItemType(MultiRecycleType_Left); observableList.add(item); } else { MultiItemViewModel item = new MultiRecycleRightItemViewModel(this, text); //条目类型为右布局 item.multiItemType(MultiRecycleType_Right); observableList.add(item); } } } } //给RecyclerView添加ObservableList public ObservableList observableList = new ObservableArrayList<>(); //RecyclerView多布局添加ItemBinding public ItemBinding itemBinding = ItemBinding.of(new OnItemBind() { @Override public void onItemBind(ItemBinding itemBinding, int position, MultiItemViewModel item) { //通过item的类型, 动态设置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); } } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/tab_bar/activity/TabBarActivity.java ================================================ package com.goldze.mvvmhabit.ui.tab_bar.activity; import android.os.Bundle; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.ActivityTabBarBinding; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar1Fragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar2Fragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar3Fragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar4Fragment; import java.util.ArrayList; import java.util.List; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import me.goldze.mvvmhabit.base.BaseActivity; import me.goldze.mvvmhabit.base.BaseViewModel; import me.majiajie.pagerbottomtabstrip.NavigationController; import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener; /** * 底部tab按钮的例子 * 所有例子仅做参考,理解如何使用才最重要。 * Created by goldze on 2018/7/18. */ public class TabBarActivity extends BaseActivity { private List mFragments; @Override public int initContentView(Bundle savedInstanceState) { return R.layout.activity_tab_bar; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initData() { //初始化Fragment initFragment(); //初始化底部Button initBottomTab(); } private void initFragment() { mFragments = new ArrayList<>(); mFragments.add(new TabBar1Fragment()); mFragments.add(new TabBar2Fragment()); mFragments.add(new TabBar3Fragment()); mFragments.add(new TabBar4Fragment()); //默认选中第一个 commitAllowingStateLoss(0); } private void initBottomTab() { NavigationController navigationController = binding.pagerBottomTab.material() .addItem(R.mipmap.yingyong, "应用") .addItem(R.mipmap.huanzhe, "工作") .addItem(R.mipmap.xiaoxi_select, "消息") .addItem(R.mipmap.wode_select, "我的") .setDefaultColor(ContextCompat.getColor(this, R.color.textColorVice)) .build(); //底部按钮的点击事件监听 navigationController.addTabItemSelectedListener(new OnTabItemSelectedListener() { @Override public void onSelected(int index, int old) { // FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // transaction.replace(R.id.frameLayout, mFragments.get(index)); // transaction.commitAllowingStateLoss(); commitAllowingStateLoss(index); } @Override public void onRepeat(int index) { } }); } private void commitAllowingStateLoss(int position) { hideAllFragment(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); Fragment currentFragment = getSupportFragmentManager().findFragmentByTag(position + ""); if (currentFragment != null) { transaction.show(currentFragment); } else { currentFragment = mFragments.get(position); transaction.add(R.id.frameLayout, currentFragment, position + ""); } transaction.commitAllowingStateLoss(); } //隐藏所有Fragment private void hideAllFragment() { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); for (int i = 0; i < mFragments.size(); i++) { Fragment currentFragment = getSupportFragmentManager().findFragmentByTag(i + ""); if (currentFragment != null) { transaction.hide(currentFragment); } } transaction.commitAllowingStateLoss(); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/tab_bar/fragment/TabBar1Fragment.java ================================================ package com.goldze.mvvmhabit.ui.tab_bar.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import androidx.annotation.Nullable; import me.goldze.mvvmhabit.base.BaseFragment; /** * Created by goldze on 2018/7/18. */ public class TabBar1Fragment extends BaseFragment { @Override public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return R.layout.fragment_tab_bar_1; } @Override public int initVariableId() { return BR.viewModel; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/tab_bar/fragment/TabBar2Fragment.java ================================================ package com.goldze.mvvmhabit.ui.tab_bar.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import androidx.annotation.Nullable; import me.goldze.mvvmhabit.base.BaseFragment; /** * Created by goldze on 2018/7/18. */ public class TabBar2Fragment extends BaseFragment { @Override public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return R.layout.fragment_tab_bar_2; } @Override public int initVariableId() { return BR.viewModel; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/tab_bar/fragment/TabBar3Fragment.java ================================================ package com.goldze.mvvmhabit.ui.tab_bar.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import androidx.annotation.Nullable; import me.goldze.mvvmhabit.base.BaseFragment; /** * Created by goldze on 2018/7/18. */ public class TabBar3Fragment extends BaseFragment{ @Override public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return R.layout.fragment_tab_bar_3; } @Override public int initVariableId() { return BR.viewModel; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/tab_bar/fragment/TabBar4Fragment.java ================================================ package com.goldze.mvvmhabit.ui.tab_bar.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import androidx.annotation.Nullable; import me.goldze.mvvmhabit.base.BaseFragment; /** * Created by goldze on 2018/7/18. */ public class TabBar4Fragment extends BaseFragment { @Override public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return R.layout.fragment_tab_bar_4; } @Override public int initVariableId() { return BR.viewModel; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/viewpager/activity/ViewPagerActivity.java ================================================ package com.goldze.mvvmhabit.ui.viewpager.activity; import android.os.Bundle; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import com.goldze.mvvmhabit.databinding.FragmentViewpagerBinding; import com.goldze.mvvmhabit.ui.viewpager.adapter.ViewPagerBindingAdapter; import com.goldze.mvvmhabit.ui.viewpager.vm.ViewPagerViewModel; import com.google.android.material.tabs.TabLayout; import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import me.goldze.mvvmhabit.base.BaseActivity; import me.goldze.mvvmhabit.utils.ToastUtils; /** * ViewPager绑定的例子, 更多绑定方式,请参考 https://github.com/evant/binding-collection-adapter * 所有例子仅做参考,千万不要把它当成一种标准,毕竟主打的不是例子,业务场景繁多,理解如何使用才最重要。 * Created by goldze on 2018/7/18. */ public class ViewPagerActivity extends BaseActivity { @Override public int initContentView(Bundle savedInstanceState) { return R.layout.fragment_viewpager; } @Override public int initVariableId() { return BR.viewModel; } @Override public void initData() { // 使用 TabLayout 和 ViewPager 相关联 binding.tabs.setupWithViewPager(binding.viewPager); binding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabs)); //给ViewPager设置adapter binding.setAdapter(new ViewPagerBindingAdapter()); } @Override public void initViewObservable() { viewModel.itemClickEvent.observe(this, new Observer() { @Override public void onChanged(@Nullable String text) { ToastUtils.showShort("position:" + text); } }); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/viewpager/adapter/ViewPagerBindingAdapter.java ================================================ package com.goldze.mvvmhabit.ui.viewpager.adapter; import android.view.ViewGroup; import com.goldze.mvvmhabit.databinding.ItemViewpagerBinding; import com.goldze.mvvmhabit.ui.viewpager.vm.ViewPagerItemViewModel; import androidx.databinding.ViewDataBinding; import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter; /** * Created by goldze on 2018/6/21. */ public class ViewPagerBindingAdapter extends BindingViewPagerAdapter { @Override public void onBindBinding(final ViewDataBinding binding, int variableId, int layoutRes, final int position, ViewPagerItemViewModel item) { super.onBindBinding(binding, variableId, layoutRes, position, item); //这里可以强转成ViewPagerItemViewModel对应的ViewDataBinding, ItemViewpagerBinding _binding = (ItemViewpagerBinding) binding; } @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container, position, object); } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/viewpager/vm/ViewPagerItemViewModel.java ================================================ package com.goldze.mvvmhabit.ui.viewpager.vm; import androidx.annotation.NonNull; import me.goldze.mvvmhabit.base.ItemViewModel; import me.goldze.mvvmhabit.binding.command.BindingAction; import me.goldze.mvvmhabit.binding.command.BindingCommand; /** * 所有例子仅做参考,千万不要把它当成一种标准,毕竟主打的不是例子,业务场景繁多,理解如何使用才最重要。 * Created by goldze on 2018/7/18. */ public class ViewPagerItemViewModel extends ItemViewModel { public String text; public ViewPagerItemViewModel(@NonNull ViewPagerViewModel viewModel, String text) { super(viewModel); this.text = text; } public BindingCommand onItemClick = new BindingCommand(new BindingAction() { @Override public void call() { //点击之后将逻辑转到activity中处理 viewModel.itemClickEvent.setValue(text); } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/viewpager/vm/ViewPagerViewModel.java ================================================ package com.goldze.mvvmhabit.ui.viewpager.vm; import android.app.Application; import com.goldze.mvvmhabit.BR; import com.goldze.mvvmhabit.R; import androidx.annotation.NonNull; import androidx.databinding.ObservableArrayList; import androidx.databinding.ObservableList; import me.goldze.mvvmhabit.base.BaseViewModel; import me.goldze.mvvmhabit.binding.command.BindingCommand; import me.goldze.mvvmhabit.binding.command.BindingConsumer; import me.goldze.mvvmhabit.bus.event.SingleLiveEvent; import me.goldze.mvvmhabit.utils.ToastUtils; import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter; import me.tatarka.bindingcollectionadapter2.ItemBinding; /** * 所有例子仅做参考,千万不要把它当成一种标准,毕竟主打的不是例子,业务场景繁多,理解如何使用才最重要。 * Created by goldze on 2018/7/18. */ public class ViewPagerViewModel extends BaseViewModel { public SingleLiveEvent itemClickEvent = new SingleLiveEvent<>(); public ViewPagerViewModel(@NonNull Application application) { super(application); //模拟3个ViewPager页面 for (int i = 1; i <= 3; i++) { ViewPagerItemViewModel itemViewModel = new ViewPagerItemViewModel(this, "第" + i + "个页面"); items.add(itemViewModel); } } //给ViewPager添加ObservableList public ObservableList items = new ObservableArrayList<>(); //给ViewPager添加ItemBinding public ItemBinding itemBinding = ItemBinding.of(BR.viewModel, R.layout.item_viewpager); //给ViewPager添加PageTitle public final BindingViewPagerAdapter.PageTitles pageTitles = new BindingViewPagerAdapter.PageTitles() { @Override public CharSequence getPageTitle(int position, ViewPagerItemViewModel item) { return "条目" + position; } }; //ViewPager切换监听 public BindingCommand onPageSelectedCommand = new BindingCommand<>(new BindingConsumer() { @Override public void call(Integer index) { ToastUtils.showShort("ViewPager切换:" + index); } }); } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/ui/vp_frg/ViewPagerGroupFragment.java ================================================ package com.goldze.mvvmhabit.ui.vp_frg; import com.goldze.mvvmhabit.ui.base.fragment.BasePagerFragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar1Fragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar2Fragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar3Fragment; import com.goldze.mvvmhabit.ui.tab_bar.fragment.TabBar4Fragment; import java.util.ArrayList; import java.util.List; import androidx.fragment.app.Fragment; /** * Create Author:goldze * Create Date:2019/01/25 * Description:ViewPager+Fragment的实现 */ public class ViewPagerGroupFragment extends BasePagerFragment { @Override protected List pagerFragment() { List list = new ArrayList<>(); list.add(new TabBar1Fragment()); list.add(new TabBar2Fragment()); list.add(new TabBar3Fragment()); list.add(new TabBar4Fragment()); return list; } @Override protected List pagerTitleString() { List list = new ArrayList<>(); list.add("page1"); list.add("page2"); list.add("page3"); list.add("page4"); return list; } } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/utils/HttpsUtils.java ================================================ /* * Copyright 2016 jeasonlzy(廖子尧) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.goldze.mvvmhabit.utils; import java.io.IOException; import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; public class HttpsUtils { public static class SSLParams { public SSLSocketFactory sSLSocketFactory; public X509TrustManager trustManager; } public static SSLParams getSslSocketFactory() { return getSslSocketFactoryBase(null, null, null); } /** * https单向认证 * 可以额外配置信任服务端的证书策略,否则默认是按CA证书去验证的,若不是CA可信任的证书,则无法通过验证 */ public static SSLParams getSslSocketFactory(X509TrustManager trustManager) { return getSslSocketFactoryBase(trustManager, null, null); } /** * https单向认证 * 用含有服务端公钥的证书校验服务端证书 */ public static SSLParams getSslSocketFactory(InputStream... certificates) { return getSslSocketFactoryBase(null, null, null, certificates); } /** * https双向认证 * bksFile 和 password -> 客户端使用bks证书校验服务端证书 * certificates -> 用含有服务端公钥的证书校验服务端证书 */ public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream... certificates) { return getSslSocketFactoryBase(null, bksFile, password, certificates); } /** * https双向认证 * bksFile 和 password -> 客户端使用bks证书校验服务端证书 * X509TrustManager -> 如果需要自己校验,那么可以自己实现相关校验,如果不需要自己校验,那么传null即可 */ public static SSLParams getSslSocketFactory(InputStream bksFile, String password, X509TrustManager trustManager) { return getSslSocketFactoryBase(trustManager, bksFile, password); } private static SSLParams getSslSocketFactoryBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) { SSLParams sslParams = new SSLParams(); try { KeyManager[] keyManagers = prepareKeyManager(bksFile, password); TrustManager[] trustManagers = prepareTrustManager(certificates); X509TrustManager manager; if (trustManager != null) { //优先使用用户自定义的TrustManager manager = trustManager; } else if (trustManagers != null) { //然后使用默认的TrustManager manager = chooseTrustManager(trustManagers); } else { //否则使用不安全的TrustManager manager = UnSafeTrustManager; } // 创建TLS类型的SSLContext对象, that uses our TrustManager SSLContext sslContext = SSLContext.getInstance("TLS"); // 用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书 // 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书 sslContext.init(keyManagers, new TrustManager[]{manager}, null); // 通过sslContext获取SSLSocketFactory对象 sslParams.sSLSocketFactory = sslContext.getSocketFactory(); sslParams.trustManager = manager; return sslParams; } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } catch (KeyManagementException e) { throw new AssertionError(e); } } private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) { try { if (bksFile == null || password == null) return null; KeyStore clientKeyStore = KeyStore.getInstance("BKS"); clientKeyStore.load(bksFile, password.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(clientKeyStore, password.toCharArray()); return kmf.getKeyManagers(); } catch (Exception e) { e.printStackTrace(); } return null; } private static TrustManager[] prepareTrustManager(InputStream... certificates) { if (certificates == null || certificates.length <= 0) return null; try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); // 创建一个默认类型的KeyStore,存储我们信任的证书 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); int index = 0; for (InputStream certStream : certificates) { String certificateAlias = Integer.toString(index++); // 证书工厂根据证书文件的流生成证书 cert Certificate cert = certificateFactory.generateCertificate(certStream); // 将 cert 作为可信证书放入到keyStore中 keyStore.setCertificateEntry(certificateAlias, cert); try { if (certStream != null) certStream.close(); } catch (IOException e) { e.printStackTrace(); } } //我们创建一个默认类型的TrustManagerFactory TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); //用我们之前的keyStore实例初始化TrustManagerFactory,这样tmf就会信任keyStore中的证书 tmf.init(keyStore); //通过tmf获取TrustManager数组,TrustManager也会信任keyStore中的证书 return tmf.getTrustManagers(); } catch (Exception e) { e.printStackTrace(); } return null; } private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) { for (TrustManager trustManager : trustManagers) { if (trustManager instanceof X509TrustManager) { return (X509TrustManager) trustManager; } } return null; } /** * 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查, * 这是一种有很大安全漏洞的办法 */ public static X509TrustManager UnSafeTrustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } }; /** * 此类是用于主机名验证的基接口。 在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配, * 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。策略可以是基于证书的或依赖于其他验证方案。 * 当验证 URL 主机名使用的默认规则失败时使用这些回调。如果主机名是可接受的,则返回 true */ public static HostnameVerifier UnSafeHostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }; } ================================================ FILE: app/src/main/java/com/goldze/mvvmhabit/utils/RetrofitClient.java ================================================ package com.goldze.mvvmhabit.utils; import android.content.Context; import android.text.TextUtils; import com.goldze.mvvmhabit.BuildConfig; import java.io.File; import java.util.Map; import java.util.concurrent.TimeUnit; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import me.goldze.mvvmhabit.http.cookie.CookieJarImpl; import me.goldze.mvvmhabit.http.cookie.store.PersistentCookieStore; import me.goldze.mvvmhabit.http.interceptor.BaseInterceptor; import me.goldze.mvvmhabit.http.interceptor.CacheInterceptor; import me.goldze.mvvmhabit.http.interceptor.logging.Level; import me.goldze.mvvmhabit.http.interceptor.logging.LoggingInterceptor; import me.goldze.mvvmhabit.utils.KLog; import me.goldze.mvvmhabit.utils.Utils; import okhttp3.Cache; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import okhttp3.internal.platform.Platform; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; /** * Created by goldze on 2017/5/10. * RetrofitClient封装单例类, 实现网络请求 */ public class RetrofitClient { //超时时间 private static final int DEFAULT_TIMEOUT = 20; //缓存时间 private static final int CACHE_TIMEOUT = 10 * 1024 * 1024; //服务端根路径 public static String baseUrl = "https://www.oschina.net/"; private static Context mContext = Utils.getContext(); private static OkHttpClient okHttpClient; private static Retrofit retrofit; private Cache cache = null; private File httpCacheDirectory; private static class SingletonHolder { private static RetrofitClient INSTANCE = new RetrofitClient(); } public static RetrofitClient getInstance() { return SingletonHolder.INSTANCE; } private RetrofitClient() { this(baseUrl, null); } private RetrofitClient(String url, Map headers) { if (TextUtils.isEmpty(url)) { url = baseUrl; } if (httpCacheDirectory == null) { httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache"); } try { if (cache == null) { cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT); } } catch (Exception e) { KLog.e("Could not create http cache", e); } HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(); okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext))) // .cache(cache) .addInterceptor(new BaseInterceptor(headers)) .addInterceptor(new CacheInterceptor(mContext)) .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager) .addInterceptor(new LoggingInterceptor .Builder()//构建者模式 .loggable(BuildConfig.DEBUG) //是否开启日志打印 .setLevel(Level.BASIC) //打印的等级 .log(Platform.INFO) // 打印类型 .request("Request") // request的Tag .response("Response")// Response的Tag .addHeader("log-header", "I am the log request header.") // 添加打印头, 注意 key 和 value 都不能是中文 .build() ) .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .connectionPool(new ConnectionPool(8, 15, TimeUnit.SECONDS)) // 这里你可以根据自己的机型设置同时连接的个数和时间,我这里8个,和每个保持时间为10s .build(); retrofit = new Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(url) .build(); } /** * create you ApiService * Create an implementation of the API endpoints defined by the {@code service} interface. */ public T create(final Class service) { if (service == null) { throw new RuntimeException("Api service is null!"); } return retrofit.create(service); } /** * /** * execute your customer API * For example: * MyApiService service = * RetrofitClient.getInstance(MainActivity.this).create(MyApiService.class); *

* RetrofitClient.getInstance(MainActivity.this) * .execute(service.lgon("name", "password"), subscriber) * * @param subscriber */ public static T execute(Observable observable, Observer subscriber) { observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); return null; } } ================================================ FILE: app/src/main/res/drawable/login_clear_input.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_demo.xml ================================================