安卓框架-MVVM的学习

这篇文章我们来学习一下MVVM。之前的项目一直都是用MVP,一直没有了解过MVVM。现在让我们一起初识MVVM。(目的是了解,后续学习并使用)
另外推荐一篇很好的文章给大家:安卓-深入浅出MVVM教程

MVVM介绍

  • Model:bean(实体类)、Data Source(Http请求相关、数据库相关)
  • View:xml、View、Activity、Fragment 等UI相关
  • ViewModel:View的数据模型和Presenter的合体

执行流程

ViewModel 负责调用 Model(可以称之为数据源),拿到结果后,更新自身。而 View 与 ViewModel 双向绑定(后面会讲怎么实现绑定的),所以 View 就会自动更新。这就是 MVVM 大致的思想。


MVVM.png

代码实现

了解了MVVM的工作原理接下来我们看下代码是如何实现的:
首先需要引入 Lifecycles, LiveData and ViewModel 这三个全家桶,而这些库是放在谷歌 maven 上,所以你得在项目根 gradle 文件中加上谷歌 maven

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

然后在 app 模块的 gradle 文件中加上如下 3 个库,为了便于后面正式版发布后修改版本号,我们通过引用变量。

ext {
    lifecycle = '1.0.0-rc1'
    lifecycleRuntime = '1.0.3'
}
dependencies {
// Lifecycles, LiveData and ViewModel
compile "android.arch.lifecycle:runtime:$rootProject.lifecycleRuntime"
compile "android.arch.lifecycle:extensions:$rootProject.lifecycle"
annotationProcessor "android.arch.lifecycle:compiler:$rootProject.lifecycle"
}

Model

这里我们用参考文章的案例:

public class User implements Serializable {
    private int id;
    private String name;
// ...getter setter and toString...
}

View

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:id="@+id/v_root"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_id"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_name"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

然后在 Activity 中初始化并获得引用。
这里需要注意,如果你的 support lib >= 26.0.0,则直接继承 AppCompatActivity 就好。否则需要继承 LifecycleActivity。

public class UserActivity extends AppCompatActivity {
    private TextView tvId;
    private TextView tvName;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.a_activity_user);
        initView();
        initData();
    }

    private void initView() {
        tvId = (TextView) findViewById(R.id.tv_id);
        tvName = (TextView) findViewById(R.id.tv_name);
    }

    private void initData() {
        // todo
    }

}

Lifecycle

上面提到 LifecycleActivity,实际上还有 LifecycleFragment,类似的,在 support lib >= 26.0.0 时,默认的 Activity 和 Fragment 基类里面已经集成了,不需要你做修改了。具体可以看官方文档 Lifecycle
用于管理 Activity / Freagment 生命周期,方便查询当前组件生命周期的状态。对应的关系如下图。

生命周期.png

那到底有什么用?

只要持有 Lifecycle,就等于知道了容器的生命周期。
比如,实现 LifecycleObserver 之后,就可以在状态发生改变时及时响应。也就是等同于让 Model或其他也拥有了生命周期。(还有这种操作?)

定义一个定位监听器,可以在生命周期不同状态下,做对应的操作,再也不用在 Activity 中加生命周期注册或取消操作了。

class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void start() {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void stop() {
        // disconnect if connected
    }
}

而在 Activity 中,只需要传入 lifecycle 就可以了。优雅得不像话。

myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
            // update UI
        });

ViewModel

ViewModel 就类似于 MVP 中的 Presenter 或者 MVC 中的 Controller。(不清楚什么是 MVC MVP 也没关系,就是个概念而已)

现在我们完全没有必要自己定义 ViewModel 来管理 UI 生命周期,谷歌大礼包中就有 ViewModel

所以很自然的我们写出这样的一个类。

public class UserViewModel extends ViewModel {}

你可能好奇 ViewModel,点进去看发现就是只有一个空方法的抽象类。可以,这很谷歌。但从 onCleared 这方法名可以看出,这还是有用处的。

LiveData

接下来就是要去调用 Model 并和 View 绑定,在继续往下讲之前,还需要介绍一下 LiveData,没错,就是它,可以轻松的实现和 View 的绑定。

它能自动响应生命周期方法,可被观察(数据改变时通知观察者),所以可以实现 Model 和 View 的绑定。

具有以下优点:

  • 避免内存泄露:由于 Observer 和 Lifecycle 绑定,当 Lifecycle 被销毁后,Observer 自动被清理。
  • 避免崩溃:避免在 Activity 被销毁后更新数据导致的崩溃情况
  • 数据可共享:数据可在多个 fragment 中共享。甚至可以通过单例的 LiveData 实现全局数据共享
  • 不需要在 Activity 中处理生命周期
  • 数据更新及时:当数据在 UI 不可见的时更新了,在可见时,数据会及时更新到UI上

具体用法

创建一个可变的 LiveData,也就是 MutableLiveData。
并通过一个方法返回。没错,就是这么简单,只需要 new 出来就行了。

// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;

public MutableLiveData<String> getCurrentName() {
    if (mCurrentName == null) {
        mCurrentName = new MutableLiveData<String>();
    }
    return mCurrentName;
}

在 Activity / Fragment 中 观察 LiveData。也就是当 LiveData 数据发生变化时,自动回调方法来更新 UI。

final Observer<String> nameObserver = new Observer<String>() {
    @Override
    public void onChanged(@Nullable final String newName) {
        // Update the UI, in this case, a TextView.
        mNameTextView.setText(newName);
    }
};
getCurrentName().observe(this, nameObserver);

那什么时候会更新数据?

更新数据一般是 UI 这边触发的,比如我们给按钮绑定一个事件,点击之后去更改 name。

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        getCurrentName().setValue(anotherName);
    }
});

是的,你想更新 UI 上的文本,只需要修改 LiveData 中的数据即可。

ViewModel + LiveData

讲完 LiveData,我们回到 ViewModel。
为了显示 id 和 name,我们需要一个 LiveData<User>。
对外公开一个 getUser 方法 和 setUsername 方法。
这里你会发现 getUser 并不是线程安全的,因为这个案例不会有在多线程中 getUser,所以没必要加,实际上还可以把 user 初始化放到成员变量定义的地方。

public class UserViewModel extends ViewModel {
    private MutableLiveData<User> user;

    public LiveData<User> getUser() {
        if (user == null)
            user = new MutableLiveData<>();
        return user;
    }

    public void setUsername(String username) {
        user.setValue(new User(1, username));
    }
}

View 和 ViewModel 绑定

之前提到了 LiveData,它可以方便的被观察。
还有 ViewModel,可以自动响应生命周期。
所以就是这两个东西,方便的实现绑定操作。

之前我已经初始化了 UserActivity 的View,现在来完善 initData。

通常来说,一个 View 只和一个 ViewModel 绑定。所以我们需要一个 Activity 级别的单例 ViewModel,我们可以通过 ViewModelProviders.of(this).get(UserViewModel.class) 来简单的实现。

然后通过 userViewModel 取得 LiveData 并添加监听(绑定操作)。

这个回调方法里面,我们就可以更新与该数据相关的 UI。

没错,绑定就是这么简单便捷。

    private UserViewModel userViewModel;

    private void initData() {
        userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
        userViewModel.getUser().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                updateView(user);
            }
        });

        userViewModel.setUsername("ittianyu");
    }

    private void updateView(User user) {
        tvId.setText(user.getId() + "");
        tvName.setText(user.getName());
    }

总结

有了这样一个构架,以后就不需要为 n 处都要修改 View 导致的复杂的 UI 逻辑而头疼了。

而数据请求也可以更纯粹,不需要管 UI 是怎么显示的。

简单来说就是 请求数据完成并更新数据后,通过通知 UI 去根据数据来渲染界面。

没错,今后安卓也可以像前端那样愉快的写 UI 了。也可以像后端那样层次分明的专心处理数据了,妙不可言。

转自

安卓-深入浅出MVVM教程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容