Android MVVM 开发新姿势 一

Android MVVM 开发新姿势 一

1. 前言

网上有不少关于Android架构的讨论,如MVC, MVP,MVVM,最近还有MVI,emmm…不得不感慨时代变化太快。MVVM出来也有一段很长的时间了,接触时间不长,写一篇文章记录一下。同时也作为一个思路,抛砖引玉。

2. MVVM概略

首先,要了解一个架构思想那么首先要看一下相关文章,其中又以谷歌为佳,下面就是MVVM(Model-View-ViewModel)结构的架构图。
相关链接:https://developer.android.google.cn/jetpack
MVVM(Model-View-ViewModel)结构的架构图
简单介绍一下各个模块:

View层 绿色框中的Activity/Fragment,继承至LifecycleActivity\LifecycleFragment,是UI控件的宿主。
核心职责是:
更新UI控件显示,包括状态及数据,由ViewModel驱动
监听UI事件及其生命周期,驱动ViewModel
View层不直接处理任何业务逻辑及数据加工。尽量做到瘦身,代码逻辑简约,减轻UI线程负担。

ViewModel层 蓝色框的ViewModel。只做业务逻辑操作,不持有任何UI控件的引用。那数据的更新如何通知到View层,这就要仰仗LiveData,具体使用后面会提及。

Model层 橘黄色框的Repository及其下都是Model层。Model层就是数据层。数据来源有:
本地存储数据,如数据库,文件,SharedPreferences(本质也是文件)
内存的缓存或临时数据
通过各种网络协议获取的远程数据

Repository是数据仓库,整合各路来源的数据,再统一暴露给ViewModel层使用。官方新框架在这一层只提供给了一个新的SQLite数据库封装类库Room,这是可选的。换言之,Model层,官方只提供指导思想,并未有具体实现方案。也就是可以自由发挥,各种嗨,Emm

各个层次的依赖如图:
依赖

3. 框架搭建

首先解决一下依赖最底层model的问题。
一般而言,model一般用于从数据源获取数据。当然也可以从本地获取数据
数据源一般就是后台服务器。
我们抽象一下,抽出来就是一个抽象类。

public abstract class BaseModel {
    
    

    public void getData(int code, BaseSubscriber subscriber , Object... parameter){
    
    
        //TODO 调用网络请求前的操作
        requestInNet(code, subscriber ,parameter);
    }

    /**
     * 网络请求
     * @param code 请求对应的code用于区分不同的请求
     * @param subscriber 回调
     * @param parameter 可变参数列表 存放请求所需参数
     */
    protected abstract void requestInNet(int code,BaseSubscriber subscriber ,Object... parameter);
}

接下来需要思考一个问题,下层不存在上层依赖,那么viewmodel和model之间该怎么通讯呢?思索一番,决定用rxjava的回调。Emmm,那么肯定要根据需求改造一番。代码如下:

public abstract class BaseSubscriber<T> extends ResourceSubscriber<T> {
    
    
    protected BaseViewModel vm;

    public BaseSubscriber(BaseViewModel vm) {
    
    
        this(vm, null);
    }
    public BaseSubscriber(BaseViewModel vm, @Nullable String requestKey) {
    
    
        this.vm = vm;
        this.vm.subscribe(this, requestKey);
    }

    @Override
    public void onStart() {
    
    
        super.onStart();
        //TODO 此处可以判断网络,无网络调用onStartOffline
        onStartRequest();
    }

    @Override
    public void onNext(T t) {
    
    
        onSuccess(t);
    }

    /**
     * 请求失败
     * 可重写复用
     */
    @Override
    public void onError(Throwable t) {
    
    
        vm.onError(t);
    }

    /**
     * 完成时回调
     */
    @Override
    public abstract void onComplete();

    /**
     * 当开始请求时没有网络
     * 可重写复用
     */
    public void onStartOffline(){
    
    

    }

    /**
     * 当开始请求时时
     * 可重写复用
     */
    public void onStartRequest(){
    
    

    }

    /**
     * 当成功返回时
     * @param t 返回的数据
     */
    public abstract void onSuccess(T t);
}

回调改造完成,那么我们就写一个viewmodel。

public abstract class BaseViewModel<M extends BaseModel> extends AndroidViewModel {
    
    
    /* RX相关注册 */
    /**
     * 一次性容器,可以容纳多个其他一次性用品和提供I/O添加和移除复杂性。
     */
    private CompositeDisposable mCompositeDisposable;
    private ArrayMap<String, Disposable> mDisposableMap;

    protected M model;

    protected final MutableLiveData<Intent> startIntent = new MutableLiveData<>();

    protected final MutableLiveData<ArrayList<Object>> messageToContext = new MutableLiveData<>();

    public BaseViewModel(Application application){
    
    
        super(application);
        model = setModel();
    }

    /**
     * 设置model
     * @return model
     */
    protected abstract M setModel();

    protected void getData(int code , BaseSubscriber subscriber , Object... param){
    
    
        if (model == null){
    
    
            throw new NullPointerException("进行获取数据的操作前,检查Model是否赋值");
        }
        model.getData(code,subscriber,param);
    }

    protected void sendMessageToContext(ArrayList<Object> params){
    
    
        messageToContext.setValue(params);
    }

    /**
     * 注册RX可处理的线程
     * @param disposable 可处理的线程
     * @param requestKey 请求键
     */
    public void subscribe(Disposable disposable, @Nullable String requestKey) {
    
    
        if (null == mCompositeDisposable) {
    
    
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(disposable);

        if (null == mDisposableMap){
    
    
            mDisposableMap = new ArrayMap<>();
        }
        if (!TextUtils.isEmpty(requestKey)){
    
    
            mDisposableMap.put(requestKey, disposable);
        }
    }

    /**
     * 注销所有线程
     */
    public void unsubscribe() {
    
    
        if (null != mCompositeDisposable) {
    
    
            mCompositeDisposable.clear();
        }
    }

    /**
     * 移除线程
     * @param requestKey 请求键
     */
    public void removeDisposable(@NotNull String requestKey) {
    
    
        if (null != mCompositeDisposable && null != mDisposableMap
                && mDisposableMap.containsKey(requestKey)) {
    
    
            mCompositeDisposable.remove(mDisposableMap.get(requestKey));
            mDisposableMap.remove(requestKey);
        }
    }

    /**
     * 请求时客户端错误处理
     */
    @CallSuper
    public void onError(Throwable throwable) {
    
    
        throwable.printStackTrace();
        // 当在主线程时显示异常Toast
        if (Looper.getMainLooper() == Looper.myLooper()) {
    
    
            //TODO 后面添加
        }
    }

}

最后搞定activity

public abstract class BaseMvvmActivity <VM extends BaseViewModel,VDB extends
        ViewDataBinding> extends AppCompatActivity {
    
    
        
    protected VDB binding;
    protected VM viewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        viewModel = setViewModel();
        doBeforeBinding();
        onBinding();
        initUI();
        setDefaultObservers();
    }

    protected void setDefaultObservers() {
    
    
    }

    /**
     * 设置布局文件id
     * @return  layout id
     */
    protected abstract int getLayoutId();

    protected void doBeforeBinding(){
    
    }

    protected void onBinding(){
    
    
        binding = DataBindingUtil.setContentView(this, getLayoutId());
        binding.setLifecycleOwner(this);
        binding.setVariable(BR.viewModel,viewModel);
    }

    /**
     * 设置ViewModel
     * @return viewModel
     */
    protected abstract VM setViewModel();

    /**
     * 初始化ui的方法
     */
    protected abstract void initUI();

    public void changeIntoLightStatusBar(boolean isLight){
    
    
        if (isLight){
    
    
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }else{
    
    
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        }
    }
}

4. 实现简单功能

网络加载一张图片&获取一段数据显示
首先图片加载库glide,
配置glide balabala…此处省略1000字
然后新建一个类,ViewUtils 名字随意呀

public class ViewUtils {
    
    

    @BindingAdapter({
    
    "imageUrl"})
    public static void loadImageOnTheInternet(final ImageView imageView, final String url) {
    
    
        if (null != url && url.startsWith("http")) {
    
    
            imageView.post(() ->
                Glide.with(imageView.getContext()).load(url).into(imageView));
        }
    }
}

图片加载搞定了,下面是网络数据加载
同样省略1000字

public class NetworkClient {
    
    

    private static Retrofit retrofit;
    private static final int TIME_OUT = 60*5;

    /**
     * 私有化构造方法
     */
    private NetworkClient(){
    
    
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> LogUtil.i("HttpLog",message));
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient okHttpClient = new OkHttpClient()
                .newBuilder()
                .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                .addInterceptor(loggingInterceptor)
                .build();
        retrofit = new Retrofit.Builder()
                .baseUrl(Constant.BASE_URL)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    private static class SingletonHolder{
    
    
        private static final NetworkClient INSTANCE = new NetworkClient();
    }

    public static NetworkClient getNetworkClient (){
    
    
        return SingletonHolder.INSTANCE;
    }

    /**
     * 获取api
     * @param service
     * @param <T>
     * @return
     */
    public <T> T createApi(Class<T> service){
    
    
        return retrofit.create(service);
    }

}

public interface UserApi {
    
    

    /**
     * 测试,获取公众号列表
     * @return
     */
    @GET("wxarticle/chapters/json")
    Flowable<ResponseBean<ArrayList<PublicNumBean>>> wxarticle();
}

关键代码如上。
下面前期准备工作都搞定了,我们开始写布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="android.view.View"/>
        <import type="java.lang.String"/>
        <variable
                name="viewModel"
                type="com.tao.androidx_mvvm.viewmodel.ViewModelOfMain"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activity.MainActivity">

        <Button
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.text}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:onClick="@{(view)->viewModel.onClick(view)}"/>

        <ImageView
            app:layout_constraintTop_toBottomOf="@id/text"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:imageUrl="@{viewModel.text}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

一个button一个imageView搞定
ViewModel

public class ViewModelOfMain extends BaseViewModel<ModelOfMain> {
    
    

    public final MutableLiveData<String> text = new MutableLiveData<>();

    public ViewModelOfMain(Application application) {
    
    
        super(application);
        text.setValue("Hello");
    }

    public void getData(){
    
    
        getData(TEXT, new BaseSubscriber(this) {
    
    
            @Override
            public void onComplete() {
    
    

            }

            @Override
            public void onSuccess(Object o) {
    
    
                LogUtil.i("ViewModelOfMain",o.toString());
                text.setValue(o.toString());
            }

            @Override
            public void onError(Throwable t) {
    
    
                super.onError(t);
                LogUtil.i("ViewModelOfMain",t.getMessage());
            }
        });
    }

    @Override
    protected ModelOfMain setModel() {
    
    
        return new ModelOfMain();
    }

    public void onClick(View v){
    
    
        if (v.getId() == R.id.text){
    
    
           getData();
        }
    }
}

Model:

public class ModelOfMain extends BaseModel {
    
    

    public static final int TEXT = 1001;

    @Override
    protected void requestInNet(int code, BaseSubscriber subscriber, Object... parameter) {
    
    
        switch (code) {
    
    
            case TEXT:
                LogUtil.i("ModelOfMain", "requestInNet");
                NetworkClient.getNetworkClient().createApi(UserApi.class)
                        .wxarticle()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(subscriber);
                break;
            default:
                LogUtil.i("ModelOfMain", "default");
                break;
        }
    }
}

Ativity:

class MainActivity : BaseMvvmActivity<ViewModelOfMain,ActivityMainBinding>() {
    
    
    override fun setViewModel(): ViewModelOfMain {
    
    
        return ViewModelOfMain(application)
    }
    
    override fun initUI() {
    
    
        viewModel.text.value = "http://img5.imgtn.bdimg.com/it/u=3300305952,1328708913&fm=26&gp=0.jpg"
    }
    
    override fun getLayoutId(): Int {
    
    
        return R.layout.activity_main
    }

}

简单效果就如上面一样。
关于recyclerview,fragment,dialog的使用就下次再写。

github:https://github.com/huangtaoOO/AndroidXMVVM
接口:https://www.wanandroid.com/

猜你喜欢

转载自blog.csdn.net/tao_789456/article/details/93493649