JectPack组件开发1------Lifecycle + LiveData(LiveDataBus)

在Android开发者平台中,对于JectPack的概述

Jetpack 是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。

Jetpack 包含与平台 API 解除捆绑的 androidx.* 软件包库。这意味着,它可以提供向后兼容性,且比 Android
平台的更新频率更高,以此确保您始终可以获取最新且最好的 Jetpack 组件版本。

在Android项目中,引入JectPack组件之后,具有向后兼容性,可以减少崩溃和内存泄漏;而且Android Jetpack 可管理繁琐的 Activity(如后台任务(Work Manager)、导航(Navigation)和生命周期管理(Lifecycles)),以便您可以专注于如何让自己的应用出类拔萃。

在JectPack组件库中,存在多种组件,可以分开使用,主要包括:
(1)Lifecycles:用来管理Activity和Fragment的生命周期;
(2)LiveData:数据通信
(3)Navigation:导航栏
(4)Paging:分页加载
(5)Room:数据库
(6)ViewModel:View和Model的双向绑定
(7)WorkManager:管理后台
(8)CameraX:添加相机功能

在后面的章节中,我会依次介绍这些组件的使用。

1、Lifecycles

管理Activity、Fragment的生命周期;在JectPack之前,我们监听生命周期,通常是采用下面的方式:

public interface IMain {
    void onCreate();
    void onStop();
    void onDestroy();
}
public class Main implements IMain {

    @Override
    public void onCreate() {
        Log.e("TAG","life---onCreate()");
    }

    @Override
    public void onStop() {
        Log.e("TAG","life---onStop()");
    }

    @Override
    public void onDestroy() {
        Log.e("TAG","life---onDestroy()");
    }
}

//监听
    Main main = new Main();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
        mPresenter.attachView((V) this);
        //监听onCreate();
        main.onCreate();
    }

    @Override
    protected void onStop() {
        super.onStop();
        //监听onStop
        main.onStop();
    }

这种方式存在的问题就是:耦合性太高,都是通过new出来的对象,与当前Activity直接绑定。

因此JectPack中的Lifecycles组件,就完美解决这个问题。

public class BasePresenter<V> implements LifecycleObserver {
    //通过弱引用存储View
    private WeakReference<V> views;
    //绑定View
    public void attachView(V view) {
        views = new WeakReference<>(view);
    }
    //解绑View
    public void detachView(V view){
        if(views != null) {
            views.clear();
        }
    }
    //获取绑定的View
    public V getView(){
        return views.get();
    }

}

BasePresenter类中实现了LifecycleObserver 接口,实现了这个接口的类,就有能力监听Activity或者Fragment的生命周期;因为在后续的BaseActivity要继承自这个BasePresenter,因此所有的Activity都有能力监听生命周期。

//监听onCreate方法
    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    void onCreate(LifecycleOwner owner){

    }

LifecycleOwner 也是一个接口,主要用于提供生命周期,通过OnLifecycleEvent注解提供;然后实现了LifecycleObserver 接口的类可以观察Activity或者Fragment的生命周期,因此两者是相互合作的关系。

然后在Presenter的具体实现类中,来重写这些方法:

 @Override
    void onCreate(LifecycleOwner owner) {
        super.onCreate(owner);
        Log.e("TAG","owner===onCreate");
    }

    @Override
    void onStart(LifecycleOwner owner) {
        super.onStart(owner);
        Log.e("TAG","owner===onStart");
    }

    @Override
    void onResume(LifecycleOwner owner) {
        super.onResume(owner);
        Log.e("TAG","owner===onResume");
    }

    @Override
    void onPause(LifecycleOwner owner) {
        super.onPause(owner);
        Log.e("TAG","owner===onPause");
    }

在具体的Presenter类对应的Activity或者Fragment中,实现订阅关系。

 @Override
    protected void init() {
        super.init();
        //BasePresenter实现了观察者接口
        getLifecycle().addObserver(mPresenter);
    }

进入到登录界面的时候,打印的日志:

2020-02-23 13:27:44.290 18010-18010/com.example.wanandroid E/TAG: owner===onCreate
2020-02-23 13:27:44.290 18010-18010/com.example.wanandroid E/TAG: owner===onAny
2020-02-23 13:27:44.291 18010-18010/com.example.wanandroid E/TAG: owner===onStart
2020-02-23 13:27:44.292 18010-18010/com.example.wanandroid E/TAG: owner===onAny
2020-02-23 13:27:44.295 18010-18010/com.example.wanandroid E/TAG: owner===onResume
2020-02-23 13:27:44.295 18010-18010/com.example.wanandroid E/TAG: owner===onAny

在onDestroy()的时候会解绑,不需要自己手动去解绑很方便。

2、LiveData

JectPack没有出来之前,像在MVP架构中,各个模块之前数据通信通常会使用EventBus事件总线来进行数据的传递,但是在JectPack组件出现之后,通过LivaDataBus就可以代替EventBus成为新的事件总线。

在之前MVP架构的设计中,包括在项目开发中,我在Model层做数据解析后,是自己写接口回调:

 @Override
    public void getBannerData(ValueCallback callback) {
        MainService mainService = RetrofitUtils.getInstance().getRetrofit().create(MainService.class);
        Call<BannerBean> call = mainService.getBannerData();
        call.enqueue(new Callback<BannerBean>() {
            @Override
            public void onResponse(Call<BannerBean> call, Response<BannerBean> response) {
                List<BannerBean.DataBean> data = response.body().getData();
                callback.complete(data);
            }

            @Override
            public void onFailure(Call<BannerBean> call, Throwable t) {

            }
        });
    }

ValueCallback 就是自己写的回调接口,在P层中,通过调用这个接口,获取数据的回调,返回到View层,这个过程中,最大的一个问题就是,在不同的数据回调时,要写许多接口,特别多,像每种数据使用的接口也不一样,因此,使用数据总线是最佳选择。

在JectPack的组件中,提供了LiveData。LiveData是一个能够感知生命周期的组件,也就是说,像Activity、Fragment、Service等,具备生命周期的组件,LiveData能够感知其生命周期,仅会更新处于活跃周期的组件。

如果观察者(由 Observer 类表示)的生命周期处于 STARTED 或 RESUMED 状态,则 LiveData 会认为该观察者处于活跃状态。LiveData 只会将更新通知给活跃的观察者。为观察 LiveData 对象而注册的非活跃观察者不会收到更改通知。

在上面我们讲到过的lifecycle,会通过LifecycleOwner提供生命周期,时刻感知当前注册组件的生命周期变化,而LiveData也会根据注册的观察者的生命周期,来决定是否提供数据更新。

所以,为什么LiveData会解决内存泄漏、空指针的问题?就是源于这样一特性,当LiveData感知当前Activity已经退出,那么就不会再为当前Activity提供数据更新,在某些Activity退出之后,部分对象被回收(View视图),如果在这个时候更新,必然会出现空指针异常导致系统崩溃;而且在当前Activity退出之后,因为不会继续提供数据更新,也不会再进行数据请求耗时操作,避免像Handler这样的内存泄漏问题。

通信框架特点
Handler优点:线程间通信 缺点:耦合性高,容易发生内存泄漏和空指针
广播优点:使用简单 缺点:性能差,发送数据有限(intent)
interface优点:速度快 缺点:维护难度大,容易接口爆炸
RxBus优点:效率高,无内存泄漏 缺点:基于Rxjava,依赖包太大
EventBus优点:简单 缺点:无法感知生命周期,实现复杂

以上通信框架,均有可能造成内存泄漏、ANR、NPE,但是通过LiveData就可以解决这个问题。

(1)LiveData的使用

public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

LiveData是一个抽象类,有一个MutableLiveData是它的实现类,实现了两个重要的方法,在主线程通信使用setValue,在子线程使用postValue,见到post就不必说太多线程间切换的问题。

 private MutableLiveData<String> livedata;
 
 livedata = new MutableLiveData<>();

        livedata.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                tv_set.setText(s);
            }
        });

 btn_set.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                livedata.setValue("JectPack");
            }
        });

btn_set.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        livedata.postValue("JectPack--post");
                    }
                }).start();
            }
        });

onChanged方法中,会接收到这个回调的结果,更新UI。而且它跟Handler相比的优势在于,会感知Activity的生命周期,当我们更新UI的时候(延迟5秒更新),如果跳转到其他的Activity,只有当回到这个Activity的时候,才会更新UI,避免了空指针异常。

(2)LiveData的有序性

当我们连续点击Button按钮获取数据,如果是使用Handler,那么就会响应10次单击事件,如果是使用LiveData,那么只会响应最后一次单击事件。

如果是从不同的地方发送过来数据,那么就会按顺序依次响应数据。

(3)LiveData跨页面通信—LiveDataBus

在使用LiveData的时候,如果想要跨页面通信,那么就需要将LiveData设置为静态类变量。

public static MutableLiveData<String> livedata;

在另一个页面中,调用这个LiveData变量,因为是注册了同一个观察者对象,因此在另一个界面,发送数据,同样也可以更新在另一个Activity的页面上。

但是这样使用存在一个问题就是,静态变量会在方法区开辟一块内存,并且静态变量一般很难被回收,会一直占据这块内存,多了之后可能会造成OOM异常。

所以针对这种情况,可以设计LiveDataBus架构,当有Activity注册订阅之后,就可以接收到发布者发布的消息;这样的方式就是将LiveData作为全局的,谁都可以使用。

在这里插入图片描述
这个方式可以通过key、value键值对的方式实现,key可以是String类型,谁也可以拿,value就是LiveData,每个key都对应一个LiveData,比如说两个Activity:1 和 2,1 想拿 2 的LiveData使用,那么就通过这个key获取拿到;

从上面那个例子就知道,2 拿到了 1 的LivaData之后,发送数据,然后在1中更新数据,两者共用这个LiveData,只要两个通过这个key,就可以拿到相同的LiveData,才能进行数据传递。

public class LiveDataBus {
    private HashMap<String, MutableLiveData<Object>> maps;
    private LiveDataBus(){maps = new HashMap<>();}
    private static LiveDataBus dataBus = new LiveDataBus();
    public static LiveDataBus getInstance(){
        return dataBus;
    }

    //获取LiveData对象
    public  <T> MutableLiveData<T> with(String key,Class<T> type){
        if(!maps.containsKey(key)){
            maps.put(key,new MutableLiveData<Object>());
        }
        return (MutableLiveData<T>) maps.get(key);
    }

    //手动移除
    public void clear(String key){
        if(maps.containsKey(key)){
            maps.remove(key);
        }
    }
}

根据这个思想,就完成事件总线的LivaDataBus的构建,通过key得到对应的LiveData,做发送数据和接受数据的准备。

//Activity 1:接收数据更新UI
LiveDataBus.getInstance().with("lay",String.class)
                .observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                tv_set.setText(s);
                Toast.makeText(Android5Activity.this,"更新了UI",Toast.LENGTH_SHORT).show();
            }
        });

//Activity 2:发布数据
btn_update_2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LiveDataBus.getInstance().with("lay",String.class)
                        .setValue("JectPack---OK!!!");
            }
        });

(4)问题处理

上面做的操作是,我们启动Activity之后,进入Activity1,然后进入Activity2界面发送数据,Activity1接收数据更新UI;但是存在一个问题,如果是在启动Activity1页面之后,给Activity2发送数据,此时Activity2还没有启动,那么2会接收到数据吗?

经过测试是能够接收到了。但这就是问题所在,在未启动的Activity2中,需要注册LiveDataBus,但是还没启动还没注册,就能够接收到外界发送的数据,显然不可以。(粘性事件)

那么需要制定一个规则:在订阅之前,不可以接收到消息;造成这个现象的原因就是,一般我们使用LiveData的时候,先创建一个LiveData子类,然后注册,最后再使用;但是LiveDataBus的使用先是new – 使用 – 注册。

先看源码:setValuedispatchingValue----considerNotify----发送的数据到达onChanged

@MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }
void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

所以,在Activity1发送数据的时候,如果发现Activity2没有注册,那么就禁止走到onChanged()方法中,不能进行UI更新,认定是没注册。

在之前换肤的实战中,我介绍过系统加载XML布局文件时,通过LayoutInflaterinflate方法,调用setFactory2()时,会根据Tag在onCreateView方法中,实例化控件,我通过Hook,拦截系统加载布局,做了更改,所以这里也需要使用到Hook技术。

considerNotify方法中,有3个判断条件,只要满足其中一个,直接return,不走下面的observer.mObserver.onChanged((T) mData);,也就是完成了截击。

  if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }

如何是没有注册,就是通过这两个参数完成的mLastVersionmVersion

if (observer.mLastVersion >= mVersion) {
            return;
        }

在进行注册的时候,会调用observe方法,这个方法中会new 一个LifecycleBoundObserver,这个类继承自ObserverWrapper

 private abstract class ObserverWrapper {
        final Observer<? super T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION;

        ObserverWrapper(Observer<? super T> observer) {
            mObserver = observer;
        }
new LiveData的时候,给mVersion 赋值-1;
在observe注册的时候,给mLastVersion  赋值-1
mLastVersion  = -1  mVersion = -1
public abstract class LiveData<T> {
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Object mDataLock = new Object();
    static final int START_VERSION = -1;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static final Object NOT_SET = new Object();

    private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();

    // how many observers are in active state
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    int mActiveCount = 0;
    private volatile Object mData = NOT_SET;
    // when setData is called, we set the pending data and actual data swap happens on the main
    // thread
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    volatile Object mPendingData = NOT_SET;
    private int mVersion = START_VERSION;

如果是先new LiveData,然后再注册,observer.mLastVersion >= mVersion这个是成立的,直接return;
但是如果是new LiveData,然后使用,比如setValue或者postValue

 @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

这个时候mVersion++;已经不成立了,所以就会调用onChanged更新UI,所以根据LiveDataBus:new LiveData—setValue—注册的这个模式,采用hook技术来判断是否注册,没有注册那么就不能更新数据。

所以我们需要做的就是,在第二个Activity启动的时候,正是注册的时候,我们要把mLastVersion == mVersion,这样它就不会走onChanged()方法,这也只限于在粘性事件的时候,使用这个注册方式。

private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();
 protected Entry<K, V> get(K k) {
        Entry<K, V> currentNode = mStart;
        while (currentNode != null) {
            if (currentNode.mKey.equals(k)) {
                break;
            }
            currentNode = currentNode.mNext;
        }
        return currentNode;
    }
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());

新的LiveDataBus:

public class LiveDataBus {
    private HashMap<String, MyMutableLiveData<Object>> maps;
    private LiveDataBus(){maps = new HashMap<>();}
    private static LiveDataBus dataBus = new LiveDataBus();
    public static LiveDataBus getInstance(){
        return dataBus;
    }

    //获取LiveData对象
    public  <T> MyMutableLiveData<T> with(String key,Class<T> type){
        if(!maps.containsKey(key)){
            maps.put(key,new MyMutableLiveData<Object>());
        }
        return (MyMutableLiveData<T>) maps.get(key);
    }

    //粘性事件的注册
    public static class MyMutableLiveData<T> extends MutableLiveData<T>{
        //目的:在第二个Activity启动的时候,正是注册的时候
        // 我们要把`mLastVersion  == mVersion`,这样它就不会走onChanged()方法
        //重写注册方法

        @Override
        public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
            super.observe(owner, observer);
            try {
                hook(observer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void hook(Observer<? super T> observer) throws Exception{
            Class<LiveData> liveDataClass = LiveData.class;
            Field filed = liveDataClass.getDeclaredField("mObservers");
            filed.setAccessible(true);
            Object mObservers = filed.get(this);
            Class<?> map = mObservers.getClass();

            Method methodGet = map.getDeclaredMethod("get", Object.class);
            methodGet.setAccessible(true);
            Object entry = methodGet.invoke(mObservers, observer);
            Object objectWrapper = ((Map.Entry) entry).getValue();
            Class<?> mObserver = objectWrapper.getClass().getSuperclass();

            Field mLastVersion = mObserver.getDeclaredField("mLastVersion");
            mLastVersion.setAccessible(true);
            Field mVersion = liveDataClass.getDeclaredField("mVersion");
            mVersion.setAccessible(true);
            Object mVersionValue = mVersion.get(this);
            mLastVersion.set(objectWrapper,mVersionValue);
        }

    }


    //手动移除
    public void clear(String key){
        if(maps.containsKey(key)){
            maps.remove(key);
        }
    }
}

测试之后,果然,在启动第二个Activity的时候,不会再更新UI。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Awesome_lay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值