智能座舱进阶-应用框架层-Jetpack主要组件

Jetpack的分类

复制代码
1. DataBinding:以声明方式将可观察数据绑定到界面元素,通常和ViewModel配合使用。
2. Lifecycle:用于管理Activity和Fragment的生命周期,可帮助开发者生成更易于维护的轻量级代码。
3. LiveData: 在底层数据库更改时通知视图。它是一个可观察的数据持有者,与常规observable不同,LiveData是生命周期感知的。
4. Navigation:处理应用内导航。
5. Paging:可以帮助开发者一次加载和显示小块数据,按需加载部分数据可减少网络带宽和系统资源的使用。
6. Room:友好、流畅的访问SQLite数据库。它在SQLite的基础上提供了一个抽象层,允许更强大的数据库访问。
7. ViewModel: 以生命周期的方式管理界面相关的数据,通常和DataBinding配合使用,为开发者实现MVVM架构提供了强有力的支持。
8. WorkManager: 管理Android的后台的作业,即使应用程序退出或设备重新启动也可以运行可延迟的异步任务。

Android 标准架构框架图:

Android官方架构部分的知识 https://developer.android.google.cn/topic/architecture/intro?hl=zh-cn

ViewBinding&DataBinding篇

ViewBinding介绍和使用

大家还是不要把ViewBinding和DataBinding这两个不要混淆了.

ViewBindling 这是一个负责绑定View到代码,减少 findViewId降低空引用资源ID错误。使用起来也较为简单, 流程是:

第一、 在 app模块下的build.gradle.kts下,打开使用ViewBinder的开关:

复制代码
Android{
...
buildFeatures {
        ViewBinding = true
    }
}

第二就是需要在使用的Activity、fragment、view里面去初始化一下,inflate"

复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}

第三步就可以直接引用使用了

复制代码
binding.button.text="helloworld"

注意: viewBinding 目前能够支持所有Xml的控件进行自动绑定, 包含Activity、Fragment以及其他View等。

ViewBinding原理

开启后会自动生成【xml的文件名 自动大驼峰】+Binding.kt 文件。例如ActivityMainBinding.kt。可以看一下自动生成文件代码,我只看inflate最后执行的部分:

复制代码
public static ActivityMainBinding bind(@NonNull View rootView) { 
int id; 
id = R.id.tv1; 
TextView tv1 = ViewBindings.findChildViewById(rootView, id);
 if (tv1 == null) { 
break missingId; 
} 
id = R.id.tv2; 
TextView tv2 = ViewBindings.findChildViewById(rootView, id);
if (tv2 == null) { 
break missingId; 
} 
return new ActivityMainBinding((ConstraintLayout) rootView, tv1, tv2);
 } 
}

可以看到自动生成的代码里,会把XML里控件的每个id都会塞进去,并且调findViewById进行绑定。

DataBinding的介绍和使用

Android 开发中体现 MVVM 架构思想的 Data Binding,其核心是 观察者模式 的特定实现。首先,它有三个主要的实体:

  1. Data:与 View 相关的数据,它可以是 View 的可观察者对象;
  2. View:展示给用户的视图,如果有交互功能且能更新数据,它可以是 Data 的可观察者对象;
  3. ViewDataBinding:连接 Data 和 View 的中介,当 Data 或 View 作为可观察者对象时,它充当可观察者对象的代理。假如当我们写了一个名为 demo.xml 的 Data Binding 的 layout 文件后,编译工具会生成一个相应的类------DemoBinding,它的原型就是 ViewDataBinding。我们通常通过 DataBindingUtil.inflate(inflater, R.layout.demo, container, false) 来实例化的 DemoBinding 对象,即 ViewDataBinding。

主要是三个方面的功能:

• 将特定的 View 与特定的 Data 进行绑定,便于模块化;

• View 自动感知和响应 Data 的变化,使得处理数据的业务层不必关心 View 的状态,便于解耦;

• Data 也可以自动同步带有交互功能的 View 对数据的修改,使得 UI 层的交互不必担心数据是否能同步 View 状态的问题,仍然便于解耦

使用类似ViewBinding:

第一是,在 app模块下的build.gradle.kts下,打开使用ViewBinder的开关:

复制代码
Android{
...
buildFeatures {
        DataBinding = true
    }
}

第二就可以直接在需要使用Xml文件里使用就行了,但是前提现有已经新简的Data类:

复制代码
class User(var firstName: String, var lastName: String) {}
<?xml version="1.0" encoding="utf-8"?> 
<layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data> <variable name="user" type="com.example.User"/>
  </data> 
  <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> 
  <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/> 
  <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}"/> 
  </LinearLayout>
   </layout>

基础用法很简单, 但是这只是单项View-Data绑定。

比较好的功能是 它还能帮助我们做到通过感知Data的局部变量来进行局部刷新,还有可以可以绑定View,进行双向绑定。使用方法就是将Data转化成observe 对象,并且添加@Bindable 的竹节,通过notifyPropertyChanged()方法来达成局部刷新,如下:

复制代码
public class DemoData extends BaseObservable {
    private int element;
// 定义其它成员,省略
@Bindable
    public int getElement() {
        return this.element;
    }
public void setElement(int e) {
        this.element = e;
        notifyPropertyChanged(BR.element);
    }
// 省略其它成员操作
}

ViewModel篇

viewModel 的使用, viewModel继承子类使用, 数据存储在内存中,可以和acitivty 生命周期进行绑定,也独立于Activity;onSaveInstanceState(Bundle) 存储的数据,仅在当前应用进程哪有效, 当退出重新进入, Bundle重新恢复成初始状态。或者在 OnStop中保存永久性数据

存储作用域

实例化 ViewModel 时,您会向其传递实现 ViewModelStoreOwner 接口的对象。它可能是 Navigation 目的地、Navigation 图表、activity、fragment 或实现接口的任何其他类型。然后,ViewModel 的作用域将限定为 ViewModelStoreOwner 的 Lifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失。

有一系列类是 ViewModelStoreOwner 接口的直接或间接子类。直接子类为 ComponentActivity、Fragment 和 NavBackStackEntry。如需查看间接子类的完整列表,请参阅 ViewModelStoreOwner 参考文档。

当 ViewModel 的作用域 fragment 或 activity 被销毁时,异步工作会在作用域限定到该 fragment 或 activity 的 ViewModel 中继续进行。这是持久性的关键。

如需了解详情,请参阅下文有关 ViewModel 生命周期的部分

我这里不去赘述 如何使用它, 我曾在上一家公司中对的Alios系统平台, 使用TS语言设计了一个跟viewModel类似的 组件库DataManager .主要的设计原则是观察者绑定和职责分离原则。 本质上来讲ViewModel 就是一个独立的数据储存类, 设计者在里面做了跟Activity的destory来消亡的【非因为配置改变导致的destory】,其实也可以做到完全独立,消亡由开发者自己决定, 这样就会很有趣了,多个activty可以共一个ViewModel实例.只是里面做了一些对象传递的封装,就可以和LiveData进行配合使用。

  • ViewModelStore:用于存储ViewModel实例的类,内部持有一个HashMap保存实例,ViewModelProvider会将创建好的ViewModel实例保存到ViewModelStore中,之后再需要此类ViewModel的实例时就直接从中读取。
  • ViewModelProvider.Factory:前文已经提到,这是用于创建ViewModel实例的工厂,ViewModelProvider当需要ViewModel的实例又在ViewModelStore中没有找到对应实例时就会调用工厂的create方法创建。
  • CreationExtras:前文也已提到,它用于在创建ViewModel实例时从外界向构造过程传递参数,内部持有一个MutableMap,以key-value的形式存储和查找参数

ViewModelStore中的HashMap 设计应该是至少两层以上的,在当初我设计的空间中,这里用的两层, 并且因为是量产项目的原因, 加了一个些状态机key值进行了耦合。 Android官方是直接保存的ViewModel的对象,这样就只有一层了。

每个Acitivty都会有一个ViewModelStoreOwner ,这样在开发者做项目的时候,就不需要关系每个Activity的获取ViewModel的时候,就是绑定的哪一个。

复制代码

public class ComponentActivity extends androidx.core.app.ComponentActivity implements

...

// 实现了 ViewModelStoreOwner 接口

ViewModelStoreOwner,

...{

private ViewModelStore mViewModelStore;

// 重写了 ViewModelStoreOwner 接口的唯一的方法 getViewModelStore()

@NonNull

@Override

public ViewModelStore getViewModelStore() {

if (getApplication() == null) {

throw new IllegalStateException("Your activity is not yet attached to the "

  • "Application instance. You can't request ViewModel before onCreate call.");

}

ensureViewModelStore();

return mViewModelStore;

}

ViewModelProvider 的构造方法时 ,获取 ViewModelStore 对象时,实际调用了 MainActivity#getViewModelStore() ,而 getViewModelStore() 实现在 MainActivity 的父类 ComponentActivity 中。

在返回 mViewModelStore 对象之前调用了 ensureViewModelStore()

void ensureViewModelStore() {

if (mViewModelStore == null) {

NonConfigurationInstances nc =

(NonConfigurationInstances) getLastNonConfigurationInstance();

if (nc != null) {

// Restore the ViewModelStore from NonConfigurationInstances

mViewModelStore = nc.viewModelStore;

}

if (mViewModelStore == null) {

mViewModelStore = new ViewModelStore();

}

}

}

复制代码
- onRetainNonConfigurationInstance 方法和 getLastNonConfigurationInstance 是成对出现的,跟 onSaveInstanceState 机制类似,只不过它是仅用作处理配置更改的优化。
- 返回的是 onRetainNonConfigurationInstance 返回的对象


Activity 在因配置更改而销毁重建过程中会先调用 onRetainNonConfigurationInstance 保存 viewModelStore 实例。 在重建后可以通过 getLastNonConfigurationInstance 方法获取之前的 viewModelStore 实例。
另外,我們可以看一下 mViewModelStore ,销毁的时机,其实是在生命周期的判断Lifecycle.Event.ON_DESTROY 被摧毁并且 判断是否是配置改变引起的isChangingConfigurations()来进行区分:
getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
        ...
    }

ViewModel 出现之前,Activity 可以使用 onSaveInstanceState() 方法保存,然后从 onCreate() 中的 Bundle 恢复数据,但此方法仅适合可以序列化再反序列化的少量数据(IPC 对 Bundle 有 1M 的限制),而不适合数量可能较大的数据,如用户信息列表或位图。 ViewModel 的出现完美解决这个问题。

LiveData篇

通常情况下LiveData都是配合viewModel使用,在某个具体的ViewModel类中定义LiveData数据,然后在对应的Activity或Fragment中观察LiveData数据的变化,LiveData的使用使得我们不再将数据保存在Activity或Fragment中,减轻了Activity或Fragment的工作量,使得Activity或Fragment只负责界面的管理和显示,而不在保存数据也不会受到数据的影响。但是也可以使用oberveForever()不绑定生命周期, 那就需要自己手动管理它的消亡。

核心方法

通过 observe(owner,observer) 向 LiveData 注册观察者

• 通过 observe(owner,observer) 向 LiveData 注册观察者,并且把 observer 包装成一个 LifecycleBoundObserver,它是一个具有生命周期边界的观察者,因为这个观察者只有当宿主处于 STARTED 或者 RESUMED 状态的它才会接收数据,其他时候它是不会接收数据的。

• 把包装好的 Observer 注册到 Lifecycle 当中,handlerLifecycleEvent(event) 利用 Lifecycle 能力,它能感知宿主生命周期能力的关键地方。注册时和宿主每次生命周期变化都会回调 onStateChanged() 方法,刚进去的时候会触发方法的同步。

• 会判断这个事件宿主是否被销毁了,从而主动地把 Observer 从 LiveData 中移除掉,流程结束。如果不是 DESTORY,说明宿主当前的状态发生了变化,它会触发 activeStateChanged(boolean newActive) 方法,它会判断当前 Observer 是否处于活跃的状态,如果宿主的状态为 STARTED,RESUMED 则会分发最新数据到每个观察者。

• 进而调用 dispatchingValue(ObserverWrapper) 分发数据,如果 ObserverWrapper 为空则分发数据给 liveData 中存储的所有观察者,如果不为空,则分发数据给该 Observer。

• considerNotify(ObserverWrapper) 中先判断观察者所在的宿主不活跃,则不分发;接着如果 observer 的 mLastVersion 大于或等于 LiveData 的 mVersion 则不分发,防止重复发送数据;最后通过 observer.mObserver.onChanged((T) mData) 分发数据,同步 mVersion 数据。

• 那么 LiveData 先发送数据,后注册的 Observer 能接收到数据吗? 答案是可以的。

普通消息的发送

• postValue() 发送一条数据,它可以在任意线程使用的,里面实际使用了 Handler.post 先把这个事件发送到主线程,然后在调用 setValue() 发送数据;

• setValue() 代表着 LiveData 发送数据,每发送一次 mVersion++,另外 LifecycleBoundObserver 中也有一个,它代表这个 Observer 接收了几次数据,在分发数据的时候,这两个 version 会进行比对,防止数据重复发送;

• setValue() 里面也会触发 dispatchingValue(ObserverWrapper),ObserverWrapper 为 null,dispatchingValue() 它会遍历 Observer 集合里面所有观察者,然后逐一调用 considerNotify(ObserverWrapper) 去做消息的分发。

此外,LiveData和 Room配合使用 时候,

LiveData 就是为了简化room 的Dao查询对象到 viewModel的过程, 返回LiveData对象, 查询数据的操作就不需要自己去创建后台线程 。 LiveData能够自动完成。至于为什么Room的查询使用LiveData对象就不需要自己起子线程。 是因为 当liveData做返回对象的时候,room的 查询方法,会多使用一个ComputableLiveData类, 这里面会判断是否有子线程,如果没有,自己会自动起一个子线程去查询,普通类型就会直接走SupportSQLiteDatabase#query(SupportSQLiteQuery)方法,看下面代码:

并且使用LiveData.observe.能够添加观察者, 绑定fragment周期和数据的更新,只要LiveData的数据发生变化, 就会实时刷新:

普通数据类型的查询:

复制代码
/**
     * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
     *
     * @param query The Query which includes the SQL and a bind callback for bind arguments.
     * @return Result of the query.
     */
    public Cursor query(SupportSQLiteQuery query) {
        assertNotMainThread();
        return mOpenHelper.getWritableDatabase().query(query);
    }

使用LiveData作为返回值时用到了ComputableLiveData类,此类在构造的时候就将RoomDatabase中的mQueryExecutor传入了。如果在构造的时候没有传入自定义的Executor,那么会自动生成一个。会走这个方法:

复制代码
 if (mQueryExecutor == null) {
                mQueryExecutor = ArchTaskExecutor.getIOThreadExecutor();
            }
相关推荐
安东尼肉店6 小时前
Android compose屏幕适配终极解决方案
android
2501_916007476 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun7 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户20187928316711 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子11 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822711 小时前
安卓接入Max广告源
android
齊家治國平天下11 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO11 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel11 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢11 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱