Android架构模式之AAC

AAC全称Android Architecture Components,是Android官方推出的MVVM架构指导方案。我们知道Android官方之前为了支持MVVM已经推出了DataBinding方案,AAC与DataBinding之间没有任何关系,但它们可以结合使用。在阅读本文后续内容前可以先看下Android架构模式之MVC、MVP、MVVM这篇文章,本文后面所描述的例子是以该文章的例子为基础的。

使用AAC需要导入如下依赖:

compile "android.arch.lifecycle:runtime:1.1.1"
compile "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"

网上不少文章都是基于1.0.0版本进行讲解,本文则是基于写此文章时最新的1.1.1版本,两个版本之间还是有不少差异性且1.1.1没有完全兼容1.0.0,读者需要注意。

AAC主要包含如下东西:

Lifecycle

Lifecycle一看就是用来管理生命周期的,它负责将Activity/Fragment的生命周期同步给其它模块,主要饱含三种角色:

  1. Lifecycle:生命周期本身,其它模块(LifecycleObserver)可以对其进行观测,以便在状态发生变化时接收通知,同时也可以主动从这里获取当前状态。
  2. LifecycleOwner:Lifecycle的持有者,一般为上下文对象,比如ActivityFragment,因为生命周期就是从它们这里同步出去的。
  3. LifecycleObserver:生命周期观察者,观察者通过向Lifecycle注册来监听生命周期的变化。

一个简单的流程如下:

  1. ActivityFragment实现LifecycleOwner接口,创建并持有Lifecycle对象。
  2. 某模块实现LifecycleObserver接口,并将自身注册到步骤1创建的Lifecycle对象中,以便观察ActivityFragment生命周期的变化。
  3. 生命周期变化时,ActivityFragment将状态同步给Lifecycle对象。
  4. Lifecycle对象dispatch事件给所有LifecycleObserver对象。

在1.1.1版本中,FragmentActivityFragment已经集成了Lifecycle,也就是说需要我们处理的只有流程2。我们来看下代码:

UserActivity.java

public class UserActivity extends FragmentActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        getLifecycle().addObserver(new UserController(getLifecycle()));

        log("onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        log("onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        log("onResume");
    }

    @Override
    protected void onPause() {
        log("onPause");
        super.onPause();
    }

    @Override
    protected void onStop() {
        log("onStop");
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        log("onDestroy");
        super.onDestroy();
    }

    private void log(String msg) {
        Log.i("sean_activity", msg);
    }
}

UserController.java

public class UserController implements LifecycleObserver {
    private Lifecycle mLifecycle;

    public UserController(Lifecycle lifecycle) {
        this.mLifecycle = lifecycle;
    }

    private void log(String msg) {
        Log.i("sean_lifecycle", msg);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    public void onCreate() {
        log("onCreate");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void onStart() {
        log("onStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void onResume() {
        log("onResume");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void onPause() {
        log("onPause");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void onStop() {
        log("onStop");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy() {
        log("onDestroy");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
    public void onAny() {
        log("onAny:" + mLifecycle.getCurrentState());
    }
}

读者可以先运行下上面的例子(xml布局随便搞一个),看下日志输出,接着我们来分析下:

  1. UserController作为控制器是为了分离出原本在UserActivity中处理的逻辑代码,因此它需要监听Activity的生命周期,所以它实现了LifecycleObserver
  2. UserActivity继承了FragmentActivity而不是Activity是因为前者集成了Lifecycle而后者没有,它扮演的是LifecycleOwner的角色,且是Lifecycle的创建者和持有者。
  3. Lifecycle对象的实际类型是LifecycleRegistry
  4. 生命周期的分发我们不需要关心,想了解原理的自行查看源码,我们需要关心的是Observer如何接收生命周期的变化。这里使用OnLifecycleEvent注解,该注解只有一个参数,表示生命周期变化对应的事件,所有事件在代码中都列出来了,看事件名就可以知道对应哪个生命周期了,这里不再多说。当生命周期发生变化时,Observer中对应的方法会被调用,其中ANY是比较特殊的,每次生命周期发生变化时都会在原事件触发后触发。
  5. CREATE/START/RESUME事件由Owner先触发,Observer后触发;PAUSE/STOP/DESTROY由Observer先触发,Owner后触发。

UserController之后会被废弃,因为AAC是MVVM模式的应用,后面会使用ViewModel来代替UserController。之所以本例中使用UserController,是为了排除其它干扰因素,能更直接地理解和掌握Lifecycle

LiveData

LiveData是一个可被观察的数据持有者,即它既是一个Observable(被观察者/发布者),同时持有数据模型(或者本身作为数据模型也可以),它的Observer(观察者)通常都是控制层对象(如ActivityFragment)。与一般的Observable不同,LiveData能知道Observer的生命周期变化,这意味着它能同步到ActivityFragment等组件的生命周期,这确保了LiveData只更新处于活跃状态的Observer

如果一个Observer的生命周期处于非DESTROYED状态时,那么LiveData将认为这个Observer处于活跃状态。LiveData仅通知活跃的Observer去更新UI。非活跃状态的Observer,即使订阅了LiveData,也不会收到更新的通知。

之后为了简化语言和便于直观理解,我们以Activity作为控制层来讲解,即之后提到的Activity同时代表控制层及LiveData的观察者。

我们上面提到LiveData可以观察到Activity的生命周期变化,同时它的数据变化也能够被Activity观测到,因此LiveDataActivity互为观察者。

  1. LiveData作为观察者时:根据前面Lifecycle所掌握到的知识,LiveData要观察Activity就需要实现LifecycleObserver,同时将自身注册到Activity中。
  2. LiveData作为被观察者时:它需要保存观察者的集合,提供注册和反注册的方法。Android中已经提供了两个LiveData相关的类,分别是LiveDataMutableLiveData,提供了作为被观察者需要的方法,同时也提供了第1点提到的注册自身的方法,二者的区别是前者的数据不可变,后者可变。因此,我们在应用LiveData时,只需要根据情况选择继承它们其一即可。

    如没有特别说明,LiveData指概念本身,而非具体的类。

  3. Activity作为被观察者时:需要实现LifecycleOwner接口,根据前面掌握的知识,实际上只需要继承FragmentActivity即可。
  4. Activity作为观察者时:需要实现Observer,并注册到LiveData中。

结合LifecycleLiveData在之前MVC的基础上进行重构,代码如下:

UserLiveData.java

public class UserLiveData extends MutableLiveData<User> {
    
}

UserActivity.java

public class UserActivity extends FragmentActivity implements Observer<User>, UserBusiness.UserListener {
    private TextView mNameView;
    private TextView mAgaView;
    private UserBusiness mUserBusiness = UserBusiness.get();
    private UserLiveData mUserLiveData;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        mNameView = findViewById(R.id.tv_name);
        mAgaView = findViewById(R.id.tv_age);

        findViewById(R.id.btn_refresh).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 向服务器请求最新用户信息
                mUserBusiness.requestUser();
            }
        });

        // 加载数据库缓存中的用户信息
        User user = mUserBusiness.getUser();

        mUserLiveData = new UserLiveData();
        // LiveData注册对Activity的监听,同时Activity注册对LiveData的监听
        mUserLiveData.observe(this, this);
        // 更新LiveData中的数据
        mUserLiveData.postValue(user);

        mUserBusiness.addListener(this);
    }
    
    @Override
    protected void onDestroy() {
        mUserBusiness.removeListener(this);
        super.onDestroy();
    }

    @Override
    public void onChanged(@Nullable User user) {
        // LiveData中的数据更新,在这里刷新UI,这个方法是在主线程中调用的,可放心刷新UI
        mNameView.setText("昵称:" + user.name);
        mAgaView.setText("年龄:" + user.age);
    }

    @Override
    public void onRequestUserResult(int code, User user) {
        if(code == 0) {
            // 更新LiveData中的数据
            mUserLiveData.postValue(user);
        } else {
            Toast.makeText(this, "刷新失败", Toast.LENGTH_SHORT).show();
        }
    }
}

分析一下:

  1. LiveDataobserve方法内部进行了双向注册,Activity观察LiveData的数据变化,数据变化时会触发Activity.onChange方法;LiveData观察Activity生命周期的变化,当生命周期状态变更为DESTROYED时(Activity.onDestroy),移除ActivityLiveData中的注册信息,后续发生数据变化时便不会再通知Activity
  2. LiveData类已经帮我们做了很多事了,所有必要的注册逻辑都封装在里面了,我们只需要调用一个observe方法即可。
  3. LiveData类提供了两个刷新数据的方法,分别是setValuepostValue,前者必须在主线程中调用,后者没有线程限制会自动post到主线程中。
  4. 多个界面可以共享一个LiveData对象,当数据发生变化时,这些界面都可以观测到,适应于全局性的数据(比如用户信息)。

ViewModel

之前已经讲过,VM的作用类似于C、P,这里不再过多描述。Android中提供了两个VM相关的基础类,分别是ViewModelAndroidViewModel,后者比前者多了一个Application上下文对象。查看ViewModel的代码,会发现代码非常简单,就一个空方法onCleared,因此如果是手动new一个ViewModel对象那就没什么意义了。创建ViewModel对象可以使用ViewModelProvider(使用ViewModelProviders创建ViewModelProvider对象),这样创建出来的ViewModel对象便有了管理者,会在适当的时机调用它的onCleared方法以便开发者清理资源。另外,ViewModelProvider会根据key缓存ViewModel对象。

下面来看下使用ViewModel重构后的代码:

UserViewModel.java

public class UserViewModel extends AndroidViewModel implements UserBusiness.UserListener {
    private UserBusiness mUserBusiness = UserBusiness.get();
    private UserLiveData mUserLiveData;

    public UserViewModel(@NonNull Application application) {
        super(application);
    }

    public void observe(LifecycleOwner owner, Observer<User> observer) {
        // 加载数据库缓存中的用户信息
        User user = mUserBusiness.getUser();

        mUserLiveData = new UserLiveData();
        // LiveData注册对Activity的监听,同时Activity注册对LiveData的监听
        mUserLiveData.observe(owner, observer);
        // 更新LiveData中的数据
        mUserLiveData.postValue(user);

        mUserBusiness.addListener(this);
    }

    @Override
    protected void onCleared() {
        mUserBusiness.removeListener(this);
        super.onCleared();
    }

    @Override
    public void onRequestUserResult(int code, User user) {
        if(code == 0) {
            // 更新LiveData中的数据
            mUserLiveData.postValue(user);
        } else {
            Toast.makeText(getApplication(), "刷新失败", Toast.LENGTH_SHORT).show();
        }
    }

    public void refresh() {
        mUserBusiness.requestUser();
    }
}

UserActivity.java

public class UserActivity extends FragmentActivity implements Observer<User> {
    private TextView mNameView;
    private TextView mAgaView;
    private UserViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        mNameView = findViewById(R.id.tv_name);
        mAgaView = findViewById(R.id.tv_age);

        findViewById(R.id.btn_refresh).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewModel.refresh();
            }
        });

        mViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
        mViewModel.observe(this, this);
    }

    @Override
    public void onChanged(@Nullable User user) {
        // LiveData中的数据更新,在这里刷新UI,这个方法是在主线程中调用的,可放心刷新UI
        mNameView.setText("昵称:" + user.name);
        mAgaView.setText("年龄:" + user.age);
    }
}

至此,我们的Activity又成功地从控制层+视图层转变成单纯的视图层了。

AAC结合DataBinding

通过上面的几次重构,我们的代码已经分层得很好了,下面直接贴代码:

activity_user.xml

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

    <data>
        <variable
            name="user"
            type="com.sean.mvvm.model.entity.User" />

        <variable
            name="host"
            type="com.sean.mvvm.UserViewModel"/>
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text='@={user.name}'
            android:id="@+id/tv_name"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text='@{"年龄:" + String.valueOf(user.age)}'/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:text="刷新"
            android:onClick="@{host.onRefresh}"/>

    </LinearLayout>

</layout>

UserViewModel.java

public class UserViewModel extends AndroidViewModel implements UserBusiness.UserListener, Observer<User> {
    private UserBusiness mUserBusiness = UserBusiness.get();
    private UserLiveData mUserLiveData;
    private User mUser;

    public UserViewModel(@NonNull Application application) {
        super(application);
    }

    public void observe(LifecycleOwner owner, ActivityUserBinding binding) {
        // 加载数据库缓存中的用户信息
        mUser = mUserBusiness.getUser();
        if(mUser == null) {
            mUser = new User();
        }

        binding.setUser(mUser);
        binding.setHost(this);

        mUserLiveData = new UserLiveData();
        // LiveData注册对Activity的监听,同时Activity注册对LiveData的监听
        mUserLiveData.observe(owner, this);
        // 更新LiveData中的数据
        mUserLiveData.postValue(mUser);

        mUserBusiness.addListener(this);
    }

    @Override
    protected void onCleared() {
        Log.i("sean_vm", "onCleared");
        mUserBusiness.removeListener(this);
        super.onCleared();
    }

    @Override
    public void onRequestUserResult(int code, User user) {
        if(code == 0) {
            // 更新LiveData中的数据
            mUserLiveData.postValue(user);
        } else {
            Toast.makeText(getApplication(), "刷新失败", Toast.LENGTH_SHORT).show();
        }
    }

    public void onRefresh(View v) {
        mUserBusiness.requestUser();
    }

    @Override
    public void onChanged(@Nullable User user) {
        mUser.setName(user.name);
        mUser.setAge(user.age);
    }
}

UserActivity.java

public class UserActivity extends FragmentActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityUserBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
        ViewModelProviders.of(this).get(UserViewModel.class).observe(this, binding);
    }
}

可以看到Activity已经没什么代码了,它完全成了一个载体,视图部分交给xml布局,而控制逻辑交给ViewModel

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

推荐阅读更多精彩内容