在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 – 使用 – 注册。
先看源码:setValue
—dispatchingValue
----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布局文件时,通过LayoutInflater
的inflate
方法,调用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;
}
如何是没有注册,就是通过这两个参数完成的mLastVersion
和mVersion
。
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。