Androidx是Support Library 28之后的版本。虽然,Support Library 虽然也独立于SDK,但是其版本号与SDK高度耦合,且所有的Support库的版本必须保持一致,无法单独升级。随着Support子库种类越来越多,这种强一致的版本管理方式越来越不灵活,于是Andoird推出了 AndroidX,其目的是用全新的的包名和版本的组织方式替代Support Library。另外,AAC(Android Architecture Component)中的组件也被并入Androidx中。所以,当使用Jetpack的组件时,经常会看到以“androidx”开头的包名。
Android Jetpack 是一套库、工具和指导,可以帮助开发者更轻松地编写应用程序。Jetpack中的组件可以帮助开发者遵循最佳做法、摆脱编写样板代码的工作并简化复杂的任务,以便他们能将精力集中放在业务所需的代码上。
如何迁移到Androidx:
1.新项目勾选androidx资源
2.已有项目点击迁移到androidx
针对于Android Jetpack的学习主要集中在Architecture组件(AAC)的学习,主要是八大组件,以下针对各个组件做简要描述:
1. LifeCycle
痛点:应用开发中常常涉及到系统组件(四大组件)和普通组件(用户自定义),往往普通组件需要依赖于系统组件的生命周期。于是出现在Activity、Service等系统组件的各个生命周期回调函数多次调用普通组件的特定函数动作,普通组件和系统组件出现强耦合现象。
换言之,在Android应用程序开发中,解耦很大程度上表现为系统组件的生命周期与普通组件之间的解耦。但是普通组件又必须要获取系统组件的生命周期事件。那有没有,既能够让普通组件能够获取系统组件生命周期事件的通知,同时又最小程度的依赖系统组件?答案就是LifeCycle组件。
LifeCycle可以帮助开发者创建可感知系统组件生命周期的普通组件。
原理分析:实际上对于Activity、Service和Application在Androidx之后,都是实现了观察者模式的。为系统组件注册观察者,那么当系统组件生命周期事件发生改变时,便会通知那些注册到系统组件的观察者组件了,普通组件只需要在特定事件通知做出特定响应即可。
- Activity会继承自FragmentActivity,并在ComponentActivity中可以看到实现了LifeCycleOwner接口
public class FragmentActivity extends ComponentActivity implements
ViewModelStoreOwner,
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
public class ComponentActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
其中的LifeCycleRegistry保持了所有的观察者,注意这个是与组件生命周期挂钩的。LifeCycleRegistry其实就是一个记录所有观察者的Map。
public class LifecycleRegistry extends Lifecycle {
private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
new FastSafeIterableMap<>();
//通知组件生命周期事件
private void forwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next();
ObserverWithState observer = entry.getValue();
while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
&& mObserverMap.contains(entry.getKey()))) {
pushParentState(observer.mState);
observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
popParentState();
}
}
}
所以只需要知道,系统组件的生命周期函数中发起通知观察者的逻辑即可,回到FragmentActivity中。
public class FragmentActivity extends ComponentActivity implements
ViewModelStoreOwner,
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
private static boolean markState(FragmentManager manager, Lifecycle.State state) {
boolean hadNotMarked = false;
Collection<Fragment> fragments = manager.getFragments();
for (Fragment fragment : fragments) {
if (fragment == null) {
continue;
}
//通知观察者系统组件生命周期发生改变
if(fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
fragment.mLifecycleRegistry.markState(state);
hadNotMarked = true;
}
FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
if (childFragmentManager != null) {
hadNotMarked |= markState(childFragmentManager, state);
}
}
return hadNotMarked;
}
2.Fragment里面的情况,同样实现了这个接口。
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner, ViewModelStoreOwner {
3.Service里面的情况,注意需要继承实现了观察者模式的LifeCycleService。
/**
* A Service that is also a {@link LifecycleOwner}.
*/
public class LifecycleService extends Service implements LifecycleOwner {
4.Application里面的情况,注意使用ProcessLifeCycleOwner.get().getLifeCycle()来获取被观察者
public class MyApplication extends Application {
}
//LifecycleService和ProcessLifeCycleOwner使用需要引入依赖
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
实践项目:github里面,注意使用kotlin哦!
2.ViewModel
在页码(Activity/Fragment)功能较为简单的情况下,通常会将UI交互、与数据获取等相关的业务逻辑全部写在页码中。但是在页面功能复杂的情况下,这种做法是不合适的,不符合“单一职责原则”。页码只应该负责处理用户与UI控件的交互,并将数据展示到屏幕上。与数据相关的业务逻辑应该单独处理和存放。可以理解为,介于View(视图)和Model(数据模型)之间的一个东西,作为一个桥梁,使视图和数据既能够分离开,又能够保持通信。
ViewModel的生命周期特性,在Activity/Fragment的整个生命周期都不会消失,比如说横竖屏切换时不会导致ViewModel消失。
// viewmodel需要导入的依赖包
implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
使用注意:
TimerViewModel timerViewModel = new ViewModelProvider(this).get(TimeViewModel.class);
原理:ViewModelProvider接受一个ViewModelStoreOwner对象作为参数,而在androidx包中,FragmentActivity默认实现了ViewModelStoreOwener接口。
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware,
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
ActivityResultCaller {
private ViewModelStore mViewModelStore;
@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;
}
}
实际上ViewModelStore存储了Hash表
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
}
ViewModel和Activity并无关联,而是通过ViewModelProvider进行关联的。其实ViewModel只是取出了Activity中的ViewModelStore,相当于在Activity中存取了数据而已。
public class ViewModelProvider {
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
}
ViewModel没有限制保存数据的大小,但是onSaveInstanceState则限制只能够保存少量数据。另外,ViewModel是不支持持久化的,但onSaveInstanceState则支持。
AndroidViewModel相比于ViewModel会传入一个Context作为构造函数,便可以在AndroidViewModel中使用Context接口方法。
实践项目:github里面,注意使用kotlin哦!
3.LiveData
痛点:在ViewModel中存放的数据如果发生变化,则需要定义接口,来实现对于视图页码的通知。这种接口通知的方式,如果数据量多了,则会导致太多回调出现,又会使代码冗余。ViewModel用于存放页面所需要的各种数据,不仅如此还会在其中提供与数据相关的业务逻辑代码,可能会修改数据内容。因此,ViewModel中的数据可能会随着业务的变化而变化。对于页面来说,并不关系ViewModel中业务逻辑,而只关心当ViewModel中的数据发生改变时,能够及时得到通知并作出更新。LiveData的作用就是,在ViewModel中的数据发生变化时通知页面数据发生改变,页面便能够进行相应的UI刷新。
LiveData是一个可被观察的数据容器类。具体来说,可以将LiveData理解为一个数据的容器,它将数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能够获取通知。LiveData取代了ViewModel和视图之间定义的接口,帮助我们完成ViewModel与页面之间的通信。
LiveData是一个抽象类,不能够直接使用,通常使用其直接子类MutableLiveData。并且根据上面的说明,LiveData通常在ViewModel中作为数据源。在Activity/Fragment中,持有ViewModel的同时还有获取ViewModel中的LiveData,然后给这个LiveData注册观察者,并在数据发生改变的回调中进行UI数据刷新动作。注入LiveData中对于数据的修改使用postValue和setValue的差异。
原理分析:LiveData注册观察者,和自定义感知系统组件生命周期的方式一样类似,其实是通过给LifeCycle添加观察者。LiveData的本质是观察者模式,并且能够感知页面的生命周期,只在页面存活的时候才会通知,从而避免内存泄漏。注意,observeForever则忽略了生命周期了。
var data : LiveData<Int> = MutableLiveData<Int>()
data.observe(this, object : Observer<Int> {
override fun onChanged(t: Int?) {
TODO("Not yet implemented")
}
})
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
注意是将LifecycleBoundObserver 来作为系统组件生命周期的观察者注册的。本来作为观察者只会接收到系统组件生命周期的通知,但是不知道什么“反向操作”就是实现了数据变化通知LifecycleOwner发生改变了????
4.DataBinding
通常布局文件只负责UI控件的布局工作,页面通过setContentView方法关联布局文件,再通过UI控件的id找到控件,接着在页面中通过代码对控件进行操作。可以说,页面承担了绝大部分的工作量,为了减轻页面的工作量,于是提出了DataBinding。DataBinding的出现让布局文件承担了部分原本属于页面的工作,也使得页面与布局文件之间的耦合度近一步降低。
DataBinding的优势:
- 项目更简洁,可读性更高。部分与UI控件相关的代码可以在布局文件中完成
- 不需要findViewById方法
- 布局文件可以包含简单的业务逻辑。UI控件能够直接与数据模型中的字段绑定,甚至响应用户的交互
使用方式:
- build.gradle文件中添加databinding { enble = true}
- 转换布局文件为databinding布局
- 实例化布局文件,通过DataBindingUtils给布局文件设置变量,此时build时会生成该布局所需的类,即“Activity+活动类名+Binding类”
- 将数据传递到布局文件,在布局文件中定义接受数据的变量,在页面通过“Activity+活动类名+Binding类”将变量设置到布局中
- 布局文件中给控件绑定布局变量或成员变量
- 如果有必要可以在布局中引入静态工具类,并在布局文件中调用相关的函数
注意,既然可以导入类变量或者静态工具类,那么同样可以设置响应事件对象。
注意,一级页面的绑定,二级页面的绑定
//一级页面布局如下
<data>
<variable
name="book"
type="com.michael.databindingdemo.Book"/>
</data>
<LinearLayout
andriod:orientation="vertical"
...>
<include
layout="@layout/layout_content"
app:book="@{book}"/>
</LinearLayout>
//二级布局如下
<data>
<variable
name="book"
type="com.michael.databindingdemo.Book"/>
</data>
<LinearLayout
andriod:orientation="vertical"
...>
<LinearLayout>
<TextView/>
<LinearLayout/>
</LinearLayout>
</LinearLayout>
DataBinding原理:在gradle中启用DataBinding库,会自动生成绑定所需的各种类,包括大量针对UI控件的、名为XXXBindingAdapter的类,这些类中包含各种静态方法,并且在这些写静态方法前都有@BindingAdapter标签,标签中的别名对应于UI控件在布局文件中的属性。以静态方法的形式为UI控件的各个属性绑定相应的代码。
自定义DataBindingAdapter类
双向绑定和单向绑定:
- BaseObServable、@Bindable、notifyPropertyChanged
- ObservableField
5.Navigation
单Activity多Fragment VS 多Activity:
- Activity占用资源更加多,多次跳转Activity耗费资源更大;Fragment是轻量级Activity,消耗资源较少。
- FragmentManager和FragmentTransaction来管理Fragment的切换和ActionBar,纯代码不友好,复杂
Navigation组件的优势:
- 可视化的页面导航
- 通过destination和action完成页面的导航
- 方便添加页面切换动画
- 页面间类型安全的参数传递
- 通过NavigationUI类,对菜单、底部导航、抽屉式菜单导航键进行统一的管理
- 支持深度链接DeepLink
Navigation的主要元素: - Navigation Graph,新型xml资源文件,其中包含应用程序的所有页面(Fragment),以及页面之间的关系(Action)
- NavHostFragment,特殊的Fragment,可视作其他Fragment的“容器”,Navigation Graph中的Fragment正是通过NavHostFragment进行展示的
- NavController,这是一个Java/Kotlin对象,用于在代码中完成Navigation Graph中具体的页面切换工作
当你想切换Fragment时,使用NavController对象,告诉它你想要去Navigation Graph中的哪个Fragment,NavControler会将你想要去的Fragment展示在NAVHostFragment中。
使用流程:
- 新建Navigation Graph xml资源文件,并导入相关依赖包
def nav_version = "2.3.0-alpha05"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
- 添加NaviHostFragment
- 创建destination,常见是fragment,也可以是activity
- 完成fragment之间的切换
- 使用NaviController完成导航
Navigation.findNaviController(v).navigate(R.id.action_mainFragment_to_secondFragment);
- 添加页面动画效果
- 使用safe args插件传递参数
6.Room
Android采用Sqlite作为数据库存储,但是Sqlite代码写起来比较繁琐,且容易出错。因此,开源社区逐渐出现了ORM(Object Relational Mapping)库。常见的库有ORMLite、GreenDAO等。Google有推出了自家的ORM库,也就是Room。同开源库一样,都是基于Sqlite上提供了一层封装。
相关概念:
- Entity,一个Entity对应于数据库中的一张表。Entity类是Sqlite表结构对Java类的映射,在Java中可以被看作一个Model类。
- DAO,即Data Access Objects,数据访问对象,用户通过这个来访问数据。
操作步骤:
- 添加依赖关系
implementation "androidx.room:rooom-runtime:2.2.2"
annotationProcessor "androidx.room:room-compiler:2.2.2"
- 创建一个Entity的Java类,在类上面添加@Entity,@PrimaryKey指定主键,@ColumnInfo指定数据库字段名称,@Ignore表示是否需要忽略该字段
- 针对定义的Entity,自定义一个DAO接口文件,以便对Entity进行访问。注意,在接口文件的上方,需要加入@Dao标签。常见的增删改查,@Insert、@Delete、@Update、@Queue。
- 定义好Entity和Dao后,接下来就是创建数据库
@Database(entites = {Student.class}, version = 1}
public abstract class MyDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "my_db";
private static MyDatbase databaseInstance;
public static synchronized MyDatabase getInstance(Context context) {
if (databaseInstance == null) {
databaseInstance = Room.databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME).build();
}
return databaseInstance;
}
public abstract StudentDao studentDao();
}