乐趣区

关于android:GitHub标星46K手写一款基于MVVM模式开发框架完美实现事件与数据源绑定

前言

目前,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 中注册一遍。
  • 全局操作

    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

1.1、启用 databinding

在主工程 app 的 build.gradle 的 android {}中退出:

dataBinding {enabled true}

1.2、依赖 Library

从近程依赖:
在根目录的 build.gradle 中退出

allprojects {
    repositories {
        ...
        google()
        jcenter()
        maven {url 'https://jitpack.io'}
    }
}

在主我的项目 app 的 build.gradle 中依赖

dependencies {
    ...
    implementation 'com.github.goldze:MVVMHabit:3.1.4'
}

或下载例子程序,在主我的项目 app 的 build.gradle 中依赖例子程序中的 mvvmhabit:

dependencies {  
    ...
    implementation project(':mvvmhabit')
}

1.3、配置 config.gradle

如果不是近程依赖,而是下载的例子程序,那么还须要将例子程序中的 config.gradle 放入你的主我的项目根目录中,而后在根目录 build.gradle 的第一行退出:

apply from: "config.gradle"

留神: config.gradle 中的
android = [] 是你的开发相干版本配置,可自行批改
support = [] 是你的 support 相干配置,可自行批改
dependencies = [] 是依赖第三方库的配置,能够加新库,但不要去批改原有第三方库的版本号,不然可能会编译不过

1.4、配置 AndroidManifest

增加权限:

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

配置 Application:

继承 mvvmhabit 中的 BaseApplication,或者调用

BaseApplication.setApplication(this);

来初始化你的 Application

能够在你的本人 AppApplication 中配置

// 是否开启日志打印
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.javaLoginViewModel.javaactivity_login.xml

2.1.1、关联 ViewModel

在 activity_login.xml 中关联 LoginViewModel。

<layout>
    <data>
        <variable
            type="com.goldze.mvvmhabit.ui.login.LoginViewModel"
            name="viewModel"
        />
    </data>
    .....

</layout>

variable – type:类的全门路
variable – name:变量名

2.1.2、继承 BaseActivity

LoginActivity 继承 BaseActivity

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);
    }
}

保留 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 对象

@Override
public LoginViewModel initViewModel() {
    //View 持有 ViewModel 的援用,如果没有非凡业务解决,这个办法能够不重写
    return ViewModelProviders.of(this).get(LoginViewModel.class);
}

留神: 不重写 initViewModel(),默认会创立 LoginActivity 中第二个泛型束缚的 LoginViewModel,如果没有指定第二个泛型,则会创立 BaseViewModel

2.1.3、继承 BaseViewModel

LoginViewModel 继承 BaseViewModel

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 中定义

// 用户名的绑定
public ObservableField<String> userName = new ObservableField<>("");

在用户名 EditText 标签中绑定

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

这样一来,输入框中输出了什么,userName.get()的内容就是什么,userName.set(“”)设置什么,输入框中就显示什么。留神: @符号前面须要加 = 号能力达到双向绑定成果;userName 须要是 public 的,不然 viewModel 无奈找到它。
点击事件绑定:
在 LoginViewModel 中定义

// 登录按钮的点击事件
public View.OnClickListener loginOnClick = new View.OnClickListener() {
    @Override
    public void onClick(View v) {}};

在登录按钮标签中绑定

android:onClick="@{viewModel.loginOnClick}"

这样一来,用户的点击事件间接被回调到 ViewModel 层了,更好的保护了业务逻辑
这就是弱小的 databinding 框架双向绑定的个性,不必再给控件定义 id,setText(),setOnClickListener()。
然而,光有这些,齐全满足不了咱们简单业务的需要啊!MVVMHabit 闪亮退场:它有一套自定义的绑定规定,能够满足大部分的场景需要,请持续往下看。

2.2.2、自定义绑定

还拿点击事件说吧,不必传统的绑定形式,应用自定义的点击事件绑定。
在 LoginViewModel 中定义

// 登录按钮的点击事件
public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() {
    @Override
    public void call() {}
});

在 activity_login 中定义命名空间

xmlns:binding="http://schemas.android.com/apk/res-auto"

在登录按钮标签中绑定

binding:onClickCommand="@{viewModel.loginOnClickCommand}"

这和本来传统的绑定不是一样吗?不,这其实是有差异的。应用这种模式的绑定,在本来事件绑定的根底之上,带有防反复点击的性能,1 秒内屡次点击也只会执行一次操作。如果不须要防反复点击,能够退出这条属性

binding:isThrottleFirst="@{Boolean.TRUE}"

那这性能是在哪里做的呢?答案在上面的代码中。

// 防反复点击距离(秒)
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();
                }
            }
        });
    }
}

onClickCommand 办法是自定义的,应用 @BindingAdapter 注解来表明这是一个绑定办法。在办法中应用了 RxView 来加强 view 的 clicks 事件,.throttleFirst()限度订阅者在指定的工夫内反复执行,最初通过 BindingCommand 将事件回调进来,就好比有一种拦截器,在点击时先做一下判断,而后再把事件沿着他原有的方向传递。
是不是感觉有点意思,好戏还在后头呢!

2.2.3、自定义 ImageView 图片加载

绑定图片门路:
在 ViewModel 中定义

public String imgUrl = "http://img0.imgtn.bdimg.com/it/u=2183314203,562241301&fm=26&gp=0.jpg";

在 ImageView 标签中

binding:url="@{viewModel.imgUrl}"

url 是图片门路,这样绑定后,这个 ImageView 就会去显示这张图片,不限网络图片还是本地图片。
如果须要给一个默认加载中的图片,能够加这一句

binding:placeholderRes="@{R.mipmap.ic_launcher_round}"

R 文件须要在 data 标签中导入应用,如:<import type="com.goldze.mvvmhabit.R" />
BindingAdapter 中的实现

@BindingAdapter(value = {"url", "placeholderRes"}, requireAll = false)
public static void setImageUri(ImageView imageView, String url, int placeholderRes) {if (!TextUtils.isEmpty(url)) {
        // 应用 Glide 框架加载图片
        Glide.with(imageView.getContext())
            .load(url)
            .placeholder(placeholderRes)
            .into(imageView);
    }
}

很简略就自定义了一个 ImageView 图片加载的绑定,学会这种形式,可自定义扩大。

如果你对这些感兴趣,能够下载源码,在 binding 包中能够看到各类控件的绑定实现形式

2.2.4、RecyclerView 绑定

RecyclerView 也是很罕用的一种控件,传统的形式须要针对各种业务要写各种 Adapter,如果你应用了 mvvmhabit,则可大大简化这种工作量,从此辞别 setAdapter()。
在 ViewModel 中定义:

/ 给 RecyclerView 增加 items
public final ObservableList<NetWorkItemViewModel> observableList = new ObservableArrayList<>();
// 给 RecyclerView 增加 ItemBinding
public final ItemBinding<NetWorkItemViewModel> itemBinding = ItemBinding.of(BR.viewModel, R.layout.item_network);

ObservableList<> 和 ItemBinding<> 的泛型是 Item 布局所对应的 ItemViewModel
在 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()}" />

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中引入了

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 时退出

Retrofit retrofit = new Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();

或者间接应用例子程序中封装好的 RetrofitClient

2.3.2、网络拦截器

LoggingInterceptor: 全局拦挡申请信息,格式化打印 Request、Response,能够清晰的看到与后盾接口对接的数据,

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 时退出

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .addInterceptor(mLoggingInterceptor)
    .build();

CacheInterceptor: 缓存拦截器,当没有网络连接的时候主动读取缓存中的数据,缓存寄存工夫默认为 3 天。
创立缓存对象

// 缓存工夫
int CACHE_TIMEOUT = 10 * 1024 * 1024
// 缓存寄存的文件
File httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache");
// 缓存对象
Cache cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT);

构建 okhttp 时退出

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor(new CacheInterceptor(mContext))
    .build();

2.3.3、Cookie 治理

MVVMHabit提供两种 CookieStore:PersistentCookieStore (SharedPreferences 治理)和MemoryCookieStore (内存治理),能够依据本人的业务需要,在构建 okhttp 时退出相应的 cookieJar

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext)))
    .build();
kHttpClient okHttpClient = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new MemoryCookieStore()))
    .build();

2.3.4、绑定生命周期

申请在 ViewModel 层。默认在 BaseActivity 中注入了 LifecycleProvider 对象到 ViewModel,用于绑定申请的生命周期,View 与申请共存亡。

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 {}});

在申请时要害须要退出组合操作符 .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

// 订阅者
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);
}

在须要执行回调的中央发送

RxBus.getDefault().post(object);

3.3.2、Messenger

Messenger 是一个轻量级全局的音讯通信工具,在咱们的简单业务中,难免会呈现一些穿插的业务,比方 ViewModel 与 ViewModel 之间须要有数据交换,这时候能够轻松地应用 Messenger 发送一个实体或一个空音讯,将事件从一个 ViewModel 回调到另一个 ViewModel 中。
应用办法:
定义一个动态 String 类型的字符串 token

public static final String TOKEN_LOGINVIEWMODEL_REFRESH = "token_loginviewmodel_refresh";

在 ViewModel 中注册音讯监听

// 注册一个空音讯监听 
// 参数 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) {}});

在须要回调的中央应用 token 发送音讯

// 发送一个空音讯
// 参数 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 实现一行代码监听带进度的文件下载。
下载文件

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) {// 下载谬误回调}
});

在 ProgressResponseBody 中应用了 RxBus,发送下载进度信息到 ProgressCallBack 中,继承 ProgressCallBack 就能够监听到下载状态。回调办法全副执行在主线程,不便 UI 的更新,详情请参考例子程序。

3.3、ContainerActivity

一个盛装 Fragment 的一个容器 (代理)Activity,一般界面只须要编写 Fragment,应用此 Activity 盛装,这样就不须要每个界面都在 AndroidManifest 中注册一遍
应用办法:
在 ViewModel 中调用 BaseViewModel 的办法开一个 Fragment

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

在 ViewModel 中调用 BaseViewModel 的办法,携带一个序列化实体关上一个 Fragment

Bundle mBundle = new Bundle();
mBundle.putParcelable("entity", entity);
startContainerActivity(你的 Fragment 类名.class.getCanonicalName(), mBundle);

在你的 Fragment 中取出实体

Bundle mBundle = getArguments();
if (mBundle != null) {entity = mBundle.getParcelable("entity");
}

3.4、6.0 权限申请

对 RxPermissions 曾经相熟的敌人能够跳过。
应用办法:
例如申请相机权限,在 ViewModel 中调用

// 申请关上相机权限
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("权限被回绝");
            }
        }
    });

3.5、图片压缩

为了节约用户流量和放慢图片上传的速度,某些场景将图片在本地压缩后再传给后盾,所以特此提供一个图片压缩的辅助性能。
应用办法:
RxJava 的形式压缩单张图片,失去一个压缩后的图片文件对象

String filePath = "mnt/sdcard/1.png";
ImageUtils.compressWithRx(filePath, new Consumer<File>() {
    @Override
    public void accept(File file) throws Exception {
        // 将文件放入 RequestBody
        ...
    }
});

RxJava 的形式压缩多张图片,按汇合程序每压缩胜利一张,都将在 onNext 办法中失去一个压缩后的图片文件对象

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) {}});

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 以下版本

混同

例子程序中给出了最新的【MVVMHabit 混同规定】,蕴含 MVVMHabit 中依赖的所有第三方 library,能够将规定间接拷贝到本人 app 的混同规定中。在此基础上你只须要关注本人业务代码以及本人引入第三方的混同,【MVVMHabit 混同规定】请参考 app 目录下的 proguard-rules.pro 文件。
一点题外话:
https://shimo.im/docs/TG8PDh9…

退出移动版