前言
目前,Android风行的MVC、MVP模式的开发框架很多,然而一款基于MVVM模式开发框架却很少。MVVMHabit是以谷歌DataBinding+LiveData+ViewModel框架为根底,整合Okhttp+RxJava+Retrofit+Glide等风行模块,加上各种原生控件自定义的BindingAdapter,让事件与数据源完满绑定的一款容易上瘾的实用性MVVM疾速开发框架。从此辞别findViewById(),辞别setText(),辞别setOnClickListener()…
框架流程
框架特点
- 疾速开发
只须要写我的项目的业务逻辑,不必再去关怀网络申请、权限申请、View的生命周期等问题,撸起袖子就是干。 - 保护不便
MVVM开发模式,低耦合,逻辑明显。Model层负责将申请的数据交给ViewModel;ViewModel层负责将申请到的数据做业务逻辑解决,最初交给View层去展现,与View一一对应;View层只负责界面绘制刷新,不解决业务逻辑,非常适合调配独立模块开发。 - 风行框架
retrofit+okhttp+rxJava负责网络申请;gson负责解析json数据;glide负责加载图片;rxlifecycle负责管理view的生命周期;与网络申请共存亡;rxbinding联合databinding扩大UI事件;rxpermissions负责Android 6.0权限申请;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中注册一遍。 -
全局操作
- 全局的Activity堆栈式治理,在程序任何中央能够关上、完结指定的Activity,一键退出应用程序。
- LoggingInterceptor全局拦挡网络申请日志,打印Request和Response,格式化json、xml数据显示,不便与后盾调试接口。
- 全局Cookie,反对SharedPreferences和内存两种管理模式。
- 通用的网络申请异样监听,依据不同的状态码或异样设置相应的message。
- 全局的异样捕捉,程序产生异样时不会解体,可跳入异样界面重启利用。
- 全局事件回调,提供RxBus、Messenger两种回调形式。
- 全局任意地位一行代码实现文件下载进度监听(暂不反对多文件进度监听)。
- 全局点击事件防抖动解决,避免点击过快。
1、筹备工作
网上的很多无关MVVM的材料,在此就不再论述什么是MVVM了,不分明的敌人能够先去理解一下。todo-mvvm-live
1.1、启用databinding
在主工程app的build.gradle的android {}中退出:
<code class="bash">dataBinding { enabled true }</code>
1.2、依赖Library
从近程依赖:
在根目录的build.gradle中退出
<code class="rust">allprojects { repositories { ... google() jcenter() maven { url 'https://jitpack.io' } } }</code>
在主我的项目app的build.gradle中依赖
<code class="bash">dependencies { ... implementation 'com.github.goldze:MVVMHabit:3.1.4' }</code>
或下载例子程序,在主我的项目app的build.gradle中依赖例子程序中的mvvmhabit:
<code class="bash">dependencies { ... implementation project(':mvvmhabit') }</code>
1.3、配置config.gradle
如果不是近程依赖,而是下载的例子程序,那么还须要将例子程序中的config.gradle放入你的主我的项目根目录中,而后在根目录build.gradle的第一行退出:
<code class="csharp">apply from: "config.gradle" </code>
留神: config.gradle中的
android = [] 是你的开发相干版本配置,可自行批改
support = [] 是你的support相干配置,可自行批改
dependencies = [] 是依赖第三方库的配置,能够加新库,但不要去批改原有第三方库的版本号,不然可能会编译不过
1.4、配置AndroidManifest
增加权限:
<code class="xml"><uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /></code>
配置Application:
继承mvvmhabit中的BaseApplication,或者调用
<code class="kotlin">BaseApplication.setApplication(this); </code>
来初始化你的Application
能够在你的本人AppApplication中配置
<code class="kotlin">//是否开启日志打印 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();</code>
2、疾速上手
2.1、第一个Activity
以大家都相熟的登录操作为例:三个文件LoginActivty.java、LoginViewModel.java、activity_login.xml
2.1.1、关联ViewModel
在activity_login.xml中关联LoginViewModel。
<code class="xml"><layout> <data> <variable type="com.goldze.mvvmhabit.ui.login.LoginViewModel" name="viewModel" /> </data> ..... </layout></code>
variable – type:类的全门路
variable – name:变量名
2.1.2、继承BaseActivity
LoginActivity继承BaseActivity
<code class="java">public class LoginActivity extends BaseActivity<ActivityLoginBinding, LoginViewModel> { //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); } }</code>
保留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对象
<code class="kotlin">@Override public LoginViewModel initViewModel() { //View持有ViewModel的援用,如果没有非凡业务解决,这个办法能够不重写 return ViewModelProviders.of(this).get(LoginViewModel.class); }</code>
留神: 不重写initViewModel(),默认会创立LoginActivity中第二个泛型束缚的LoginViewModel,如果没有指定第二个泛型,则会创立BaseViewModel
2.1.3、继承BaseViewModel
LoginViewModel继承BaseViewModel
<code class="java">public class LoginViewModel extends BaseViewModel { public LoginViewModel(@NonNull Application application) { super(application); } .... }</code>
BaseViewModel与BaseActivity通过LiveData来解决罕用UI逻辑,即可在ViewModel中应用父类的showDialog()、startActivity()等办法。在这个LoginViewModel中就能够纵情的写你的逻辑了!
BaseFragment的应用和BaseActivity一样,详情参考Demo。
2.2、数据绑定
领有databinding框架自带的双向绑定,也有扩大
2.2.1、传统绑定
绑定用户名:
在LoginViewModel中定义
<code class="tsx">//用户名的绑定 public ObservableField<String> userName = new ObservableField<>(""); </code>
在用户名EditText标签中绑定
<code class="bash">android:text="@={viewModel.userName}" </code>
这样一来,输入框中输出了什么,userName.get()的内容就是什么,userName.set(“”)设置什么,输入框中就显示什么。 留神: @符号前面须要加=号能力达到双向绑定成果;userName须要是public的,不然viewModel无奈找到它。
点击事件绑定:
在LoginViewModel中定义
<code class="java">//登录按钮的点击事件 public View.OnClickListener loginOnClick = new View.OnClickListener() { @Override public void onClick(View v) { } }; </code>
在登录按钮标签中绑定
<code class="bash">android:onClick="@{viewModel.loginOnClick}" </code>
这样一来,用户的点击事件间接被回调到ViewModel层了,更好的保护了业务逻辑
这就是弱小的databinding框架双向绑定的个性,不必再给控件定义id,setText(),setOnClickListener()。
然而,光有这些,齐全满足不了咱们简单业务的需要啊!MVVMHabit闪亮退场:它有一套自定义的绑定规定,能够满足大部分的场景需要,请持续往下看。
2.2.2、自定义绑定
还拿点击事件说吧,不必传统的绑定形式,应用自定义的点击事件绑定。
在LoginViewModel中定义
<code class="java">//登录按钮的点击事件 public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() { @Override public void call() { } });</code>
在activity_login中定义命名空间
<code class="bash">xmlns:binding="http://schemas.android.com/apk/res-auto" </code>
在登录按钮标签中绑定
<code class="bash">binding:onClickCommand="@{viewModel.loginOnClickCommand}" </code>
这和本来传统的绑定不是一样吗?不,这其实是有差异的。应用这种模式的绑定,在本来事件绑定的根底之上,带有防反复点击的性能,1秒内屡次点击也只会执行一次操作。如果不须要防反复点击,能够退出这条属性
<code class="bash">binding:isThrottleFirst="@{Boolean.TRUE}"</code>
那这性能是在哪里做的呢?答案在上面的代码中。
<code class="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<Object>() { @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<Object>() { @Override public void accept(Object object) throws Exception { if (clickCommand != null) { clickCommand.execute(); } } }); } }</code>
onClickCommand办法是自定义的,应用@BindingAdapter注解来表明这是一个绑定办法。在办法中应用了RxView来加强view的clicks事件,.throttleFirst()限度订阅者在指定的工夫内反复执行,最初通过BindingCommand将事件回调进来,就好比有一种拦截器,在点击时先做一下判断,而后再把事件沿着他原有的方向传递。
是不是感觉有点意思,好戏还在后头呢!
2.2.3、自定义ImageView图片加载
绑定图片门路:
在ViewModel中定义
<code class="tsx">public String imgUrl = "http://img0.imgtn.bdimg.com/it/u=2183314203,562241301&fm=26&gp=0.jpg";</code>
在ImageView标签中
<code class="bash">binding:url="@{viewModel.imgUrl}" </code>
url是图片门路,这样绑定后,这个ImageView就会去显示这张图片,不限网络图片还是本地图片。
如果须要给一个默认加载中的图片,能够加这一句
<code class="bash">binding:placeholderRes="@{R.mipmap.ic_launcher_round}" </code>
R文件须要在data标签中导入应用,如:
<import type="com.goldze.mvvmhabit.R" />
BindingAdapter中的实现
<code class="csharp">@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); } }</code>
很简略就自定义了一个ImageView图片加载的绑定,学会这种形式,可自定义扩大。
如果你对这些感兴趣,能够下载源码,在binding包中能够看到各类控件的绑定实现形式
2.2.4、RecyclerView绑定
RecyclerView也是很罕用的一种控件,传统的形式须要针对各种业务要写各种Adapter,如果你应用了mvvmhabit,则可大大简化这种工作量,从此辞别setAdapter()。
在ViewModel中定义:
<code class="java">/给RecyclerView增加items public final ObservableList<NetWorkItemViewModel> observableList = new ObservableArrayList<>(); //给RecyclerView增加ItemBinding public final ItemBinding<NetWorkItemViewModel> itemBinding = ItemBinding.of(BR.viewModel, R.layout.item_network); </code>
ObservableList<>和ItemBinding<>的泛型是Item布局所对应的ItemViewModel
在xml中绑定
<code class="xml"><android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" binding:itemBinding="@{viewModel.itemBinding}" binding:items="@{viewModel.observableList}" binding:layoutManager="@{LayoutManagers.linear()}" binding:lineManager="@{LineManagers.horizontal()}" /> </code>
layoutManager管制是线性(蕴含程度和垂直)排列还是网格排列,lineManager是设置分割线
网格布局的写法:binding:layoutManager="@{LayoutManagers.grid(3)}
程度布局的写法:binding:layoutManager="@{LayoutManagers.linear(LinearLayoutManager.HORIZONTAL,Boolean.FALSE)}"
应用到相干类,则须要导入该类能力应用,和导入Java类类似
<import type="me.tatarka.bindingcollectionadapter2.LayoutManagers" />
<import type="me.goldze.mvvmhabit.binding.viewadapter.recyclerview.LineManagers" />
<import type="android.support.v7.widget.LinearLayoutManager" />
这样绑定后,在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
2.3、网络申请
网络申请始终都是一个我的项目的外围,当初的我的项目根本都离不开网络,一个好用网络申请框架能够让开发事倍功半。
2.3.1、Retrofit+Okhttp+RxJava
现今,这三个组合根本是网络申请的标配,如果你对这三个框架不理解,倡议先去查阅相干材料。
square出品的框架,用起来的确十分不便。MVVMHabit中引入了
<code class="css">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" </code>
构建Retrofit时退出
<code class="cpp">Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); </code>
或者间接应用例子程序中封装好的RetrofitClient
2.3.2、网络拦截器
LoggingInterceptor: 全局拦挡申请信息,格式化打印Request、Response,能够清晰的看到与后盾接口对接的数据,
<code class="cpp">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() </code>
构建okhttp时退出
<code class="cpp">OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(mLoggingInterceptor) .build(); </code>
CacheInterceptor: 缓存拦截器,当没有网络连接的时候主动读取缓存中的数据,缓存寄存工夫默认为3天。
创立缓存对象
<code class="cpp">//缓存工夫 int CACHE_TIMEOUT = 10 * 1024 * 1024 //缓存寄存的文件 File httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache"); //缓存对象 Cache cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT); </code>
构建okhttp时退出
<code class="cpp">OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache) .addInterceptor(new CacheInterceptor(mContext)) .build(); </code>
2.3.3、Cookie治理
MVVMHabit提供两种CookieStore:PersistentCookieStore (SharedPreferences治理)和MemoryCookieStore (内存治理),能够依据本人的业务需要,在构建okhttp时退出相应的cookieJar
<code class="cpp">OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext))) .build(); </code>
<code class="cpp">kHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJarImpl(new MemoryCookieStore())) .build(); </code>
2.3.4、绑定生命周期
申请在ViewModel层。默认在BaseActivity中注入了LifecycleProvider对象到ViewModel,用于绑定申请的生命周期,View与申请共存亡。
<code class="java">RetrofitClient.getInstance().create(DemoApiService.class) .demoGet() .compose(RxUtils.bindToLifecycle(getLifecycleProvider())) // 申请与View周期同步 .compose(RxUtils.schedulersTransformer()) // 线程调度 .compose(RxUtils.exceptionTransformer()) // 网络谬误的异样转换 .subscribe(new Consumer<BaseResponse<DemoEntity>>() { @Override public void accept(BaseResponse<DemoEntity> response) throws Exception { } }, new Consumer<ResponseThrowable>() { @Override public void accept(ResponseThrowable throwable) throws Exception { } });</code>
在申请时要害须要退出组合操作符.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,已为你实现了大部分网络异样的判断,也可自行依据我的项目的具体需要调整逻辑。
留神: 这里的网络异样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
<code class="java">//订阅者 private Disposable mSubscription; //注册RxBus @Override public void registerRxBus() { super.registerRxBus(); mSubscription = RxBus.getDefault().toObservable(String.class) .subscribe(new Consumer<String>() { @Override public void accept(String s) throws Exception { } }); //将订阅者退出管理站 RxSubscriptions.add(mSubscription); } //移除RxBus @Override public void removeRxBus() { super.removeRxBus(); //将订阅者从管理站中移除 RxSubscriptions.remove(mSubscription); }</code>
在须要执行回调的中央发送
<code class="css">RxBus.getDefault().post(object);</code>
3.3.2、Messenger
Messenger是一个轻量级全局的音讯通信工具,在咱们的简单业务中,难免会呈现一些穿插的业务,比方ViewModel与ViewModel之间须要有数据交换,这时候能够轻松地应用Messenger发送一个实体或一个空音讯,将事件从一个ViewModel回调到另一个ViewModel中。
应用办法:
定义一个动态String类型的字符串token
<code class="dart">public static final String TOKEN_LOGINVIEWMODEL_REFRESH = "token_loginviewmodel_refresh"; </code>
在ViewModel中注册音讯监听
<code class="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<String>() { @Override public void call(String s) { } });</code>
在须要回调的中央应用token发送音讯
<code class="cpp">//发送一个空音讯 //参数1:定义的token Messenger.getDefault().sendNoMsg(LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH); //发送一个带数据回调音讯 //参数1:回调的实体 //参数2:定义的token Messenger.getDefault().send("refresh",LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH); </code>
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实现一行代码监听带进度的文件下载。
下载文件
<code class="java">String loadUrl = "你的文件下载门路"; String destFileDir = context.getCacheDir().getPath(); //文件寄存的门路 String destFileName = System.currentTimeMillis() + ".apk";//文件寄存的名称 DownLoadManager.getInstance().load(loadUrl, new ProgressCallBack<ResponseBody>(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) { //下载谬误回调 } });</code>
在ProgressResponseBody中应用了RxBus,发送下载进度信息到ProgressCallBack中,继承ProgressCallBack就能够监听到下载状态。回调办法全副执行在主线程,不便UI的更新,详情请参考例子程序。
3.3、ContainerActivity
一个盛装Fragment的一个容器(代理)Activity,一般界面只须要编写Fragment,应用此Activity盛装,这样就不须要每个界面都在AndroidManifest中注册一遍
应用办法:
在ViewModel中调用BaseViewModel的办法开一个Fragment
<code class="css">startContainerActivity(你的Fragment类名.class.getCanonicalName()) </code>
在ViewModel中调用BaseViewModel的办法,携带一个序列化实体关上一个Fragment
<code class="java">Bundle mBundle = new Bundle(); mBundle.putParcelable("entity", entity); startContainerActivity(你的Fragment类名.class.getCanonicalName(), mBundle); </code>
在你的Fragment中取出实体
<code class="csharp">Bundle mBundle = getArguments(); if (mBundle != null) { entity = mBundle.getParcelable("entity"); } </code>
3.4、6.0权限申请
对RxPermissions曾经相熟的敌人能够跳过。
应用办法:
例如申请相机权限,在ViewModel中调用
<code class="java">//申请关上相机权限 RxPermissions rxPermissions = new RxPermissions((Activity) context); rxPermissions.request(Manifest.permission.CAMERA) .subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean aBoolean) throws Exception { if (aBoolean) { ToastUtils.showShort("权限曾经关上,间接跳入相机"); } else { ToastUtils.showShort("权限被回绝"); } } });</code>
3.5、图片压缩
为了节约用户流量和放慢图片上传的速度,某些场景将图片在本地压缩后再传给后盾,所以特此提供一个图片压缩的辅助性能。
应用办法:
RxJava的形式压缩单张图片,失去一个压缩后的图片文件对象
<code class="java">String filePath = "mnt/sdcard/1.png"; ImageUtils.compressWithRx(filePath, new Consumer<File>() { @Override public void accept(File file) throws Exception { //将文件放入RequestBody ... } });</code>
RxJava的形式压缩多张图片,按汇合程序每压缩胜利一张,都将在onNext办法中失去一个压缩后的图片文件对象
<code class="java">List<String> 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) { } });</code>
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的写法退出<layout> <variable>
后,实践上会主动对应生成ActivityLoginBinding.java类和variable的属性,可能是as对databding的反对还不够吧,有时候偏偏就不生成,导致BR.xxx报红等一些莫名的谬误。
解决办法:其实确保本人的写法没有问题,是能够间接运行的,报红不肯定是你写的有问题,也有可能是编译器抽风了。或者应用上面的方法
第一招:Build->Clean Project;
第二招:Build->Rebuild Project;
第三招:重启大法。
4.1.5、gradle谬误
如果遇到以下编译问题:
谬误: 无奈将类 BindingRecyclerViewAdapters中的办法 setAdapter利用到给定类型; 须要: RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds<? super T>,ViewHolderFactory 找到: RecyclerView,ItemBinding,ObservableList,BindingRecyclerViewAdapter<CAP#1>,ItemIds,ViewHolderFactory 起因: 推断类型不合乎等式约束条件 推断: CAP#1 等式约束条件: CAP#1,NetWorkItemViewModel 其中, T是类型变量: T扩大已在办法 setAdapter(RecyclerView,ItemBinding,List,BindingRecyclerViewAdapter,ItemIds<? super T>,ViewHolderFactory)中申明的Object 其中, CAP#1是新类型变量: CAP#1从?的捕捉扩大Object
个别是因为gradle plugin版本3.5.1造成的,请换成gradle plugin 3.5.0以下版本