Android Architecture Components 之 Lifecycle、LiveData、ViewModel

本文仅是阅读Android Architecture Components的个人总结。

  • Android App开发的痛点

在开发AndroidApp时,由于系统的UI Controller(Activity、Fragment)Component能够被独立启动并且是无顺序的,他们的生命周期不受开发者的控制,因此当我们的数据依赖这些组件的生命周期时就会发生许多问题。

Android官方指出一个重要的开发原则是:我们不应该把不是处理UI和系统事件的代码写在UI Controller中。

Any code that does not handle a UI or operating system interaction should not be in these classes. Keeping them as lean as possible will allow you to avoid many lifecycle related problems. Don't forget that you don't own those classes, they are just glue classes that embody the contract between the OS and your app.

官方指出第二个重要的原则是drive your UI from a model

Architecture Components就是遵守上面两个原则的框架,它保证我们app可以很好的响应系统组件的生命周期,不出现混乱的情况。同时drive your UI from a model

Handling Lifecycles with Lifecycle-Aware Components

这个其实原理是很简单的:UI Controller(Activity、Fragment)是一个LifecycleOwner,它的生命周期时可以被观察的。我们可以向它们上面注册LifecycleObserver,观察生命周期事件。简单代码如下:

创建一个LifecycleObserver:

public class MainPresenter implements LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    void initView() {
        Log.e(TAG, "init View");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    void initData() {
        Log.e(TAG, "initData");
    }

}

LifecycleOwner注册LifecycleObserver:

public class MainActivity extends AppCompatActivity implements MainView {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)
        getLifecycle().addObserver(new MainPresenter());
    }

note: 在Support Library 26.1.0, AppCompatActivityFragment 都已经实现LifecycleOwner

对于上面这个sample,可以说简单的把UI Controller的生命周期相关方法dispatch给其他组件处理,使代码看起来非常清晰。

LiveData

先看一下使用LiveData编码的优势:

  • 确保UI状态和数据状态是同步的。
  • 没有内存泄漏
  • 不会由于Activity的Stop而引起crash
  • 不需要手动处理组件生命周期
  • 不会由于系统config改变而保持数据,比如屏幕旋转

LiveData是一个包裹数据并且可以被观察的对象,它可以感知App组件的生命周期(Activity Fragment Service)。这个特性使:LiveData可以在组件活跃的状态(在生命周期内)下更新数据

活跃状态是指STARTorRESUMED。即LiveData在数据发生改变时只把这个改变事件通知给活跃的观察者,对于不活跃的观察者是不会通知的。

在给LiveData注册Observer时,可以把LifecycleOwnerLiveData组成一个配对关系。这样在LifecycleOwner不活跃时,Observer会被移除。这样就保证像Activity Fragment在观察LiveData变化时是非常安全的,即在不活跃时,是不会响应LiveData的变化的。

下面看一下如何使用:

public class NameViewModel extends ViewModel {

    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;
    
    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }
}

MutableLiveData是一个LiveData,是一个可被观察的对象。官方是推荐把LiveData写在ViewModel中的:

  • 可以避免Activity和Fragment的臃肿,这些UI Controller只是响应UI的变化,而不会持有UI的状态
  • 解耦LiveData和UIController
    对于ViewModel将会在下面介绍。

通过observer()方法将LifecycleOwner和LiveData建立联系:


public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

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

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the 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);
            }
        };
});

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

上面把一个Observer加入到LiveData的观察者列表,如果Oberver对应的LifeOwner(UI Controller)不活跃了,这个Observer将会被移除。

数据变化,触发活跃的LifeOwner更新UI,即调用ObserveronChanged()方法。

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

ViewModel

这个类被设计用来管理和UI相关的数据(即LiveData),它允许数据感知系统的配置变化,比如屏幕旋转。

ViewModel在发生屏幕旋转事件是,这个对象会被保留,如果此时UI Controller销毁了(Activity),在Activity实例重建的时候,这个对象是可以被重新获得的,即我们可以不用在onSaveInstanceState()保存数据,然后在onCreate()中恢复。

可以这样获得一个ViewModel

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
    
    @Override
    protected void onCleared() {
        super.onCleared();
    }
}

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

ViewModel的生命周期如下:

viewmodel-lifecycle.png

即这个对象作用域直到Activity destroy。当框架层面Activity被销毁时,ViewModelonCleared方法会被调用。

官方建议:ViewModel不应该引用任何UI Controller相关对象。它也不应该关心任何UI Controller的生命周期变化。

如果你需要一个 Application Context,可以使用 AndroidViewModel

在多个Fragment中的应用

在上面可以看出一个ViewModel其实适合LifecycleOwner绑定的,因此Activity下的Fragment可以获得同一个ViewModel,即可以交流。

比如下面的代码:

Notice that both fragments use getActivity() when getting the ViewModelProvider. As a result, both fragments receive the same SharedViewModel instance, which is scoped to the activity.

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

总结

通过上面的介绍,我们可以通过Lifecycle、LiveData、ViewModel这三个对象写出一个比较稳定的app架构:代码逻辑清晰,生命周期管理方便等优点。

Demo地址:https://github.com/SusionSuc/ArchitectureComponentSmale

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