Android MVVM 开发新姿势 一

news2025/1/11 8:11:44

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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快速入门pandas进行数据挖掘数据分析[多维度排序、数据筛选、分组计算、透视表](一)

1. 快速入门python&#xff0c;python基本语法 Python使用缩进(tab或者空格)来组织代码&#xff0c;而不是像其 他语言比如R、C、Java和Perl那样用大括号。考虑使用for循 环来实现排序算法: for x in list_values:if x < 10:small.append(x)else:bigger.append(x)标量类型 …

uni-app中vant-Weapp组件库的使用

先创建一个基础的uni-app目录从github下载vant包&#xff0c;zip格式的https://github.com/youzan/vant-weapp/releases项目根目录下创建wxcomponents文件夹把我们下好的文件vant-weapp里面只留下dist其余的可以全部删掉&#xff0c;然后把vant-weapp放到 wxcomponents里面App.…

在CentOS-6.9配置apache服务(2)---虚拟目录配置

文章目录一 需求二 系统环境三 基于Alias普通别名3.1 配置个人主页3.2 编写虚拟目录配置文件3.3 测试四 基于ScriptAlias脚本别名4.1 编写主配置文件4.2 创建测试主页4.3 测试一 需求 基于用户个人主页的身份验证&#xff0c;在浏览器输入 10.0.0.100/~a 可以得到用户a的个人网…

Linux:CPU频率调节模式以及降频方法简介

概述 cpufreq的核心功能&#xff0c;是通过调整CPU的电压和频率&#xff0c;来兼顾系统的性能和功耗。在不需要高性能时&#xff0c;降低电压和频率&#xff0c;以降低功耗&#xff1b;在需要高性能时&#xff0c;提高电压和频率&#xff0c;以提高性能。 cpufreq 是一个动态调…

拉伯证券|机构看好中国经济 人民币资产吸引力持续增强

2023年人民币汇率以及A股强势开局。1月以来人民币对美元中心价已累计增值超3%&#xff0c;接连3个月增值。到1月末&#xff0c;北向资金累计净买入额达1311.46亿元&#xff0c;刷新了沪深股通单月净买入新高。 在“真金白银”加仓布局人民币财物的一起&#xff0c;外资组织也纷…

CSS实现9宫格布局的4种方法:flex、float、grid、table布局

一、实现效果及html代码 1、实现效果 2、html代码 <body><div class"container"><div style"background-color: red">1</div><div style"background-color: blue">2</div><div style"background-…

十二、树结构的实际应用—赫夫曼树

1、赫夫曼树 1.1 基本介绍 给定 n 个权值作为 n 个叶子节点&#xff0c;构造一棵二叉树&#xff0c;若该树的带权路径长度&#xff08;wpl&#xff09;达到最小&#xff0c;称这样的二叉树为最优二叉树&#xff0c;也称哈夫曼树&#xff08;Huffman Tree&#xff09;&#xf…

Java工厂模式

定义&#xff1a;将创建对象的权利交给工厂类实现&#xff0c;解耦对象使用者和对象创建过程。 工厂模式有三种&#xff1a; 1、简单工厂模式 2、工厂方法模式 3、抽象工厂模式 使用工厂模式作用&#xff1a; 1、客户类和对象之间的耦合关系转移到了工厂方法和对象之间 …

pl/sql篇之變量的定義

簡述本篇文章主要介紹pl/sql的變量的簡單數據類型&#xff0c;複雜數據類型定義和調用方法&#xff0c;希望能對讀者有些許作用數據類型介紹變量的定義和調用在pl/sql中&#xff0c;定義的變量在聲明之後&#xff0c;可以直接在後續的sql調用&#xff0c;使用上非常方便簡單數據…

图解 MySQL MVCC 实现原理

文章目录MVCC 产生背景InnoDB 引擎表的隐藏列Undo 回滚版本链一致性视图MVCC 实现原理举例说明 MVCC 实现过程MVCC 产生背景 最早的数据库系统,只有读读之间可以并发,读写,写读,写写之间都要阻塞。而 MVCC (Muti Version Concurrency Control) , 是一种多版本并发控制机制。在…

Pandas+Pyecharts | 全国吃穿住行消费排行榜,最‘抠门’的地区居然是北京!!!

文章目录&#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 计算各项占比&#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 全国各地区人均收入、消费支出排行榜3.2 全国各地区人均可支配收入地图3.3 全国…

HCIA之ARP协议

ARP协议1、原理2、ARP工作过程3、ARP分类1、原理 根据已知的地址来获取与其对应的另一种地址 2、ARP工作过程 目标MAC全F&#xff0c;对于交换机&#xff0c;会洪泛&#xff1b;对于所有主机&#xff0c;都会以为是找自己的。 发送者PC1&#xff1a;发出广播帧&#xff0c;源I…

大型CRM客户管理系统带小程序、H5 java源码(spring boot 后台 前端vue)

功能介绍 1、系统管理&#xff1a;员工管理、角色管理、菜单管理、部门管理、岗位管理、字典管理、参数设置、日志管理 2、系统监控&#xff1a;在线用户、定时任务、数据监控、服务监控 3、系统工具&#xff1a;表单构建、代码生成、系统接口 4、平台配置&#xff1a;配置…

python - 密码加密与解密

Python之密码加密与解密 - 对称算法一、对称加密1.1 安装第三方库 - PyCrypto1.2 加密实现二、非对称加密三、摘要算法3.1 md5加密3.2 sha1加密3.3 sha256加密3.4 sha384加密3.5 sha512加密3.6 “加盐”加密由于计算机软件的非法复制&#xff0c;通信的泄密、数据安全受到威胁。…

车载音频系统方案的组合设计

现代数学可以分为两大类:一类是研究连续对象的,如分析学、方程等,另一类就是研究离散对象的数学。 有人认为广义的组合数学就是离散数学,也有人认为离散数学是狭义的组合数学和图论、代数结构、数理逻辑等的总称。但这只是不同学者在叫法上的区别,随着计算机科学的日益发…

实现支付宝网站登录

不推荐使用沙箱环境&#xff0c;因为问题太多&#xff0c;如果使用沙箱环境请注意一下几点 alipay.user.info.auth&#xff08;用户登录授权接口&#xff09;的 return_url 必传&#xff0c;建议检查是否设置 return_url。return_url 与应用中的授权回调地址一致。再换沙箱环境…

图解最常用的 10 个机器学习算法

在机器学习领域&#xff0c;有种说法叫做“世上没有免费的午餐”&#xff0c;简而言之&#xff0c;它是指没有任何一种算法能在每个问题上都能有最好的效果&#xff0c;这个理论在监督学习方面体现得尤为重要。 举个例子来说&#xff0c;你不能说神经网络永远比决策树好&#…

5.4 单管放大电路的频率响应

一、单管共射放大电路的频率响应 考虑到耦合电容和结电容的影响&#xff0c;图5.4.1(a)所示电路的等效电路如图(b)所示。在分析放大电路的频率响应时&#xff0c;为了方便起见&#xff0c;一般将输入信号的频率范围分为中频、低频和高频三个频段。在中频段&#xff0c;极间电容…

Win10系统打开控制面板出现闪退怎么回事?

Win10系统打开控制面板出现闪退怎么回事&#xff1f;有用户开启自己电脑的控制面板时&#xff0c;突然间页面初选了闪退的情况&#xff0c;导致无法进行相关设置的操作。那么我们怎么去进行控制面板闪退问题的解决呢&#xff1f;一起来看看以下的解决方法吧。 解决方法 1、更换…

Python | 文件操作和异常处理

博主简介&#x1f647;&#xff1a;&#x1f393;本科大二学生&#x1f393;&#xff0c;立志成为一名全栈开发工程师&#x1f38f;&#x1f38f;分类专栏&#x1f4d8;&#xff1a;Python从入门到精通&#x1f33b;&#x1f33b; 知识目录一、文件操作1.1 打开和关闭文件1.2 读…