Android架构组件

本文就“谷歌开发者”最近推出的Android 架构组件做简单介绍和使用,如果需要更好的理解架构组件的原理,可以到官网《Guide to App Architecture》

(一)Lifecycle

Lifecycle组件的引入

一项新的技术的提出肯定是为了解决痛点问题,如果使用过MVP模式的话,有个问题:Presenter感知Activity或者Fragment的生命周期?你可能会这样做,Presenter中定义多个和Activity或者Fragment相应的生命周期方法,然后在Activity或者Fragment中调用Presenter中定义的方法。以下暂且用MVP模式场景举例:

public class MainPresenter implements IPresenter {

    public MainPresenter(Context context){
    }

    public void onCreate() {
    }

    public void onStart() {
    }

    ...

    public void onDestroy() {
    }
}

   然后,Activity中的生命周期回调主动调用Presenter的public函数,如下:

public class MainActivity extends AppCompatActivity {
    private IPresenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPresenter = new MainPresenter(this);
        mPresenter.onCreate();
    }

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

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

   上面简单的例子很明显暴露出代码冗余的短板,实际开发中业务如果稍加复杂,这个问题体现的就更加明显,对于追求代码结构完美的程序员最不能接受。因此,Lifecycle组件就诞生了。

Lifecycle组件的简单使用

public interface IPresenter extends DefaultLifecycleObserver{
 //....
}
public class MainActivity extends AppCompatActivity {
    private IPresenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPresenter = new MainPresenter(this);
        mPresenter.onCreate();
        //将mPresenter加入宿主生命周期观察者队列
        getLifecycle().addObserver(mPresenter);
    }

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

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

  作为观察者Presenter即可选择性的重写相关生命周期回调

public class MainPresenter implements IPresenter {

    public MainPresenter(Context context){

    }

   @Override
    public void onCreate(LifecycleOwner owner) {
    }
    
    @Override
    public void onStart(LifecycleOwner owner) {
    }

    ...
    @Override
    public void onDestroy(LifecycleOwner owner) {
    }
}

   所谓没有对比就没有伤害,关于Lifecycle组件原理简单的由下面的图来诠释:

高清无码图
高清无码图

   我们以V4包中的Fragment(AppCompatActivity类似)为例,看下Fragment和LifecycleOwner、LifecycleObserver、Lifecycle之间的类关系图。

  • Lifecycle组件成员Lifecycle被定义成了抽象类,LifecycleOwner、LifecycleObserver被定义成了接口;

  • Fragment实现了LifecycleOwner接口,该只有一个返回Lifecycle对象的方法getLifecyle();

  • Fragment中getLifecycle()方法返回的是继承了抽象类Lifecycle的LifecycleRegistry。

  • LifecycleRegistry中定义嵌套类ObserverWithState,该类持有GenericLifecycleObserver对象,而GenericLifecycleObserver是继承了LifecycleObserver的接口。

 
  最后,建议明白Lifecycle组件的框架和结构后,亲自实践慢慢体会。


(二)LiveData

LiveData组件的引入

从LiveData具有的特点,我们就能联想到它能够解决我们遇到的什么问题。LiveData具有以下优点:

  • 能够保证数据和UI统一
  • 减少内存泄漏
  • 当Activity停止时不会引起崩溃
  • 不需要额外的手动处理来响应生命周期的变化
  • 组件和数据相关的内容能实时更新
  • 针对configuration change(比如语言、屏幕方向变化)时,不需要额外的处理来保存数据
  • 资源共享

LiveData组件的简单使用

LiveData常见的几种使用方式:

  • 使用LiveData对象
  • 继承LiveData类

方式一:使用LiveData对象

主要有一下步骤:

  1. 创建保存特定数据类型的LiveData实例;
  2. 创建Observer对象,作为参数传入LiveData.observe(...)方法添加观察者;
  3. 更新LiveData对象存储的数据;

创建LiveData实例

   Android文档中建议LiveData配合ViewModel使用更佳,其实,你也可以不使用ViewModel,但是一定要做到LiveData中保存的数据和组件分离,原因前面我们已经提到过了。下面是在ViewModel中创建LiveData实例的例子,至于ViewModel后面会做详细介绍:

public class NameViewModel extends ViewModel{
    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;
    // Create a LiveData with a String list
    private MutableLiveData<List<String>> mNameListData;

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

    public MutableLiveData<List<String>> getNameList(){
        if (mNameListData == null) {
            mNameListData = new MutableLiveData<>();
        }
        return mNameListData;
    }
}

   在NameViewModel中创建了两个MutableLiveData(MutableLiveData是LiveData的子类)实例,分别存储当前姓名、姓名列表;两个实例通过NameViewModel中的Getter方法得到。

创建Observer对象,添加观察者

public class LiveDataFragment extends Fragment {
    private static final String TAG = "LiveDataFragment";
    private NameViewModel mNameViewModel;
    @BindView(R.id.tv_name)
    TextView mTvName;

    public static LiveDataFragment getInstance(){
        return new LiveDataFragment();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mNameViewModel = ViewModelProviders.of(this).get(NameViewModel.class);
        mNameViewModel.getCurrentName().observe(this,(String name) -> {
            mTvName.setText(name);
            Log.d(TAG, "currentName: " + name);
        }); // 订阅LiveData中当前Name数据变化
        mNameViewModel.getNameList().observe(this, (List<String> nameList) -> {
            for (String item : nameList) {
                Log.d(TAG, "name: " + item);
            }
        }); // 订阅LiveData中Name列表数据变化
    }


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.layout_livedata, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

}

   在onCreate()方法中通过LiveData.observe()方法添加观察者,当数据变化时会通过回调方法通知观察者,在Observer中更新当前姓名和打印姓名列表。

更新LiveData中的数据

   在上面我们已经订阅了LiveData数据变化,现在我们看下如果LiveData数据变化时,上面的Observer中是否会受到更新的通知。我们在LiveDataFragment中增加两个按钮来改变LiveData中的数据。

@OnClick({R.id.btn_change_name, R.id.btn_update_list})
void onClicked(View view){
    switch (view.getId()){
        case R.id.btn_change_name:
            mNameViewModel.getCurrentName().setValue("Uzi");
            break;
        case R.id.btn_update_list:
            List<String> nameList = new ArrayList<>();
            for (int i = 0; i < 10; i++){
                nameList.add("Mlxg<" + i + ">");
            }
            mNameViewModel.getNameList().setValue(nameList);
            break;
    }
}

   代码很简单,在两个按钮的点击事件中通过LiveData.setValue()方法来改变LiveData中保存的数据。当点击这两个按钮的时候,我们会发现在onCreate()方法中会收相应到数据改变的回调。

方式二:继承LiveData类

    除了直接使用LiveData对象外,还可以通过集成LiveData类来定义适合特定需求的LiveData。下面举个继承LiveData类的例子, 来验证下LiveData的其中一个优点——资源共享

public class MyLiveData extends LiveData<Integer> {
    private static final String TAG = "MyLiveData";
    private static MyLiveData sData;
    private WeakReference<Context> mContextWeakReference;

    public static MyLiveData getInstance(Context context){
        if (sData == null){
            sData = new MyLiveData(context);
        }
        return sData;
    }

    private MyLiveData(Context context){
        mContextWeakReference = new WeakReference<>(context);
    }

    @Override
    protected void onActive() {
        super.onActive();
        registerReceiver();
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        unregisterReceiver();
    }

    private void registerReceiver() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
        mContextWeakReference.get().registerReceiver(mReceiver, intentFilter);
    }

    private void unregisterReceiver() {
        mContextWeakReference.get().unregisterReceiver(mReceiver);
    }


    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.d(TAG, "action = " + action);
            if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
                int wifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
                int wifiLevel = WifiManager.calculateSignalLevel(wifiRssi, 4);
                sData.setValue(wifiLevel);
            }
        }
    };
}

   MyLiveData是个继承了LiveData的单例类,在onActive()和onInactive()方法中分别注册和反注册Wifi信号强度的广播。然后在广播接收器中更新MyLiveData对象。在使用的时候就可以通过MyLiveData.getInstance()方法,然后通过调用observe()方法来添加观察者对象,订阅Wifi信息强度变化。

  • onActive(),此方法是当处于激活状态的observer个数从0到1时,该方法会被调用。
  • onInactive() ,此方法是当处于激活状态的observer个数从1变为0时,该方法会被调用。

   关于LiveData的原理,从以下图理解:

类关系图
类关系图

    LiveData的类关系图相对比较简单,从上面的类图我们就能看到。和LiveData组件相关的类和接口有:LiveData类、Observer接口、GenericLifecycleObserver接口。LiveData类是个抽象类,但是它没有抽象方法,抽象类有个特点是:不能在抽象类中实例化自己。为什么LiveData会被定义成abstract而又没有抽象方法呢,这个…我也不知道,看了下LiveData的提交记录,是在将hasObservers()替换getObserverCount()方法时将LiveData改成了abstract,在此之前它是被定义为public,可以翻墙的可以看下这里的修改记录。

  • MediatorLiveData继承自MutableLiveData,MutableLiveData继承自LiveData。MediatorLiveData可以看成是多个LiveData的代理,当将多个LiveData添加到MediatorLiveData,任何一个LiveData数据发生变化时,MediatorLiveData都会收到通知。
  • LiveData有个内部类LifecycleBoundObserver,它实现了GenericLifecycleObserver,而GenericLifecycleObserver继承了LifecycleObserver接口。在这里可以回顾下Lifecycle组件相关的内容。当组件(Fragment、Activity)生命周期变化时会通过onStateChanged()方法回调过来。
  • Observer接口就是观察者,其中定义了LiveData数据变化的回调方法onChanged()。

 
当然,LiveData还有更多的使用方法,这里仅做大致的理解并简单使用。


(三)ViewModel

ViewModel组件的引入

ViewModel,它是负责准备及管理UI组件(Fragment/Activity)相关的数据类,也就是说ViewModel是用来管理UI相关的数据的。同时ViewModel还可以用来负责UI组件间的通信。既然ViewModel是用来管理和UI组件有关的数据的,而LiveData又是这些数据的持有类,所以在使用LiveData的时候,就自然想到要使用ViewModel了。另外,ViewModel还可以用于UI组件间的通信。

ViewModel的基本使用

前面在讲解LiveData时,我们已经使用了ViewModel,所以它的基本使用,在这里就不在赘述了。

ViewModel的生命周期

ViewModel的生命周期,在官方文档中是用下面这张图来描述ViewModel的生命周期。


ViewModel生命周期
ViewModel生命周期

   上图是用Activity作为例子,左侧表示Activity的生命周期状态,右侧绿色部分表示ViewModel的生命周期范围。当屏幕旋转的时候,Activity会被recreate,Activity会经过几个生命周期方法,但是这个时候ViewModel还是之前的对象,并没有被重新创建,只有当Activity的finish()方法被调用时,ViewModel.onCleared()方法会被调用,对象才会被销毁。这张图很好的描述了是当Activity被recreate时,ViewModel的生命周期。

    另外,有个注意的地方:在ViewModel中不要持有Activity的引用。为什么要注意这一点呢?从上面的图我们看到,当Activity被recreate时,ViewModel对象并没有被销毁,如果Model持有Activity的引用时就可能会导致内存泄漏。那如果要使用到Context对象怎么办呢,那就使用ViewModel的子类AndroidViewModel吧。

ViewModel的组件间通信

下面看下ViewModel用于Fragment之间通信的例子:

public class CommunicateViewModel extends ViewModel {
    private MutableLiveData<String> mNameLiveData;

    public LiveData<String> getName(){
        if (mNameLiveData == null) {
            mNameLiveData = new MutableLiveData<>();
        }
        return mNameLiveData;
    }

    public void setName(String name){
        if (mNameLiveData != null) {
            mNameLiveData.setValue(name);
        }
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        mNameLiveData = null;
    }
}

 

public class FragmentOne extends Fragment {
    private CommunicateViewModel mCommunicateViewModel;

    public static FragmentOne getInstance(){
        return new FragmentOne();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCommunicateViewModel = ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @OnClick(R.id.btn_set_name)
    void onViewClicked(View v){
        switch (v.getId()){
            case R.id.btn_set_name:
                mCommunicateViewModel.setName("Jane");
                break;
        }
    }
}

 

public class FragmentTwo extends Fragment {
    @BindView(R.id.tv_name)
    TextView mTvName;
    private CommunicateViewModel mCommunicateViewModel;

    public static FragmentTwo getInstance(){
        return new FragmentTwo();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCommunicateViewModel =      ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);
        mCommunicateViewModel.getName().observe(this, name -> mTvName.setText(name));
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_two, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

}

代码很简单,在FragmentOne中改变CommunicateViewModel中LiveData保存的数据,然后在FragmentTwo中会收到数据改变的通知。但是这个前提两个Fragment是共享的同一个LiveData数据对象。如何保证两个Fragment里的ViewModel是同一个对象呢,ViewModelProviders.of(Activity/Fragment)中的对象一致,获得的ViewModel就是同一个对象,源码如下:

   /** 
     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
     * is alive. More detailed explanation is in {@link ViewModel}.
     * <p>
     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
     *
     * @param activity an activity, in whose scope ViewModels should be retained
     * @return a ViewModelProvider instance
     */
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
   /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this {@code ViewModelProvider}.
     * <p>
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     *                   present.
     * @param <T>        The type parameter for the ViewModel.
     * @return A ViewModel that is an instance of the given type {@code T}.
     */
    @NonNull
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this {@code ViewModelProvider}.
     * <p>
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param key        The key to use to identify the ViewModel.
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     *                   present.
     * @param <T>        The type parameter for the ViewModel.
     * @return A ViewModel that is an instance of the given type {@code T}.
     */
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

以下是原理图:


ViewModel相关类图
ViewModel相关类图
  • ViewModelProviders是ViewModel工具类,该类提供了通过Fragment和Activity得到ViewModel的方法,而具体实现又是有ViewModelProvider实现的。ViewModelProvider是实现ViewModel创建、获取的工具类。在ViewModelProvider中定义了一个创建ViewModel的接口类——Factory。ViewModelProvider中有个ViewModelStore对象,用于存储ViewModel对象。
  • ViewModelStore是存储ViewModel的类,具体实现是通过HashMap来保存ViewModle对象。
  • ViewModel是个抽象类,里面只定义了一个onCleared()方法,该方法在ViewModel不在被使用时调用。ViewModel有一个子类AndroidViewModel,这个类是便于要在ViewModel中使用Context对象,因为我们前面提到是不能在ViewModel中持有Activity的引用。
  • ViewModelStores是ViewModelStore的工厂方法类,它会关联HolderFragment,HolderFragment有个嵌套类——HolderFragmentManager。
     
    最后,Lifecycle,LiveData,ViewModel结合起来使用,优化项目整体架构。

 
 
 
 
 

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

推荐阅读更多精彩内容