android12——Jetpack
作者:互联网
Jetpack
Jetpack 是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。
Jetpack中的组件有一共特点,它们大部分依赖任何android系统版本,这意味着这些组件通常是定义在androidX库当中的,并且拥有非常好的向下兼容性。另外jetpack中的许多构架组件是专门为MVVM架构量身打造的。
ViewModel
在传统的开发模式下,activity的任务实在是太重了,既要负责逻辑处理,又要控制UI展示,甚至还要处理网络回调等等。而 ViewModel作为Jetpack最重要的组件之一,他可以帮助activity分担一部分工作,他是专门用于存放和界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是activity中,这样可以在一定程度上减少activity中的逻辑。
另外,viewmodel还有一个非常重要的特性。我们都知道,当手机发生横竖屏旋转的时候,activity会被重新粗行间,同时存放在activity中的数据也会丢失。而viewmodel的声明周期和activity不同,他可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当activity退出的时候才会跟着activity一起销毁。因此,将与界面相关的变量存放在viewmode当中,这样即使旋转手机屏幕,界面上的数据也不会丢失。viewmodel的生命周期如下图所示:
ViewModel的基本用法
-
创建JetpackTest项目
-
jetpack的组件通常是以androidX库的形式发布的,但是viewmodel组织还是需要添加依赖:
implementation 'androidx.lifecycle:liftcycle:lifecycle-extenions:2.1.0'
-
按照编程规范给每一个activity和fragment都创建一个对应的ViewModel,MainActivity=>MainViewModel,并集成viewmodel。添加计数器变量
counter
:import androidx.lifecycle.ViewModel; public class MainViewModel extends ViewModel { private int counter = 0; }
-
在layout上添加计数器。(
activity_main.xml
)<TextView android:id="@+id/info_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textSize="32sp"/> <Button android:id="@+id/plus_one_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Plus One"/>
-
MainActiviy中实现计数器逻辑:
注意:这个地方不可以是直接去创建ViewModel实例,而是通过Provider获取的,因为 ViewModel有独立的生命周期,并且其生命周期要长于Activiy。如果我们在onCreate()中创建ViewModel的实例,那么每次执行onCreate()都会创建一个新的实例,这样当手机旋转的时候,就无法保留其中的数据了。
另外,即使旋转,数据也不会丢失!
public class MainActivity extends AppCompatActivity { private TextView infoText; private Button plusOneBtn; private MainViewModel mainViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); infoText = findViewById(R.id.info_text); plusOneBtn = findViewById(R.id.plus_one_btn); // 下面这个方法过时了:https://blog.csdn.net/sinat_33150417/article/details/104323897 // MainViewModel mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class); mainViewModel = new ViewModelProvider(this).get(MainViewModel.class); plusOneBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mainViewModel.counter++; // public 但是不符合开闭原则 refreshCounter(); } }); refreshCounter(); } private void refreshCounter() { // 注意setText必须是String类型不能是int,不然会报错 infoText.setText(String.valueOf(mainViewModel.counter)); } }
向ViewModel传递参数
上一节创建的MainViewModel的构造函数中没有任何参数,如果我们确实需要通过苟傲函数来传递一些参数的时候,则需要借助ViewModelProvider.Factory
就可以实现了。
创建一个factory类:
public class MainViewFactory implements ViewModelProvider.Factory {
private final int couterReserved;
public MainViewFactory(){
couterReserved = 0;
}
public MainViewFactory(int counterReserved){
this.couterReserved = counterReserved;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MainViewModel(this.couterReserved);
}
}
在创建ViewModel的时候增加factory的参数:
mainViewModel = new ViewModelProvider(this, new MainViewFactory(count_reserved)).get(MainViewModel.class);
关于持久化计数器:
private static final String TAG = "MainActivity";
private TextView infoText;
private Button plusOneBtn;
private Button clearBtn;
private MainViewModel mainViewModel;
private SharedPreferences sp;
private SharedPreferences.Editor editor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
infoText = findViewById(R.id.info_text);
plusOneBtn = findViewById(R.id.plus_one_btn);
clearBtn = findViewById(R.id.clear_btn);
// sp = PreferenceManager.getDefaultSharedPreferences(this); // getPreferences(Context.MODE_PRIVATE)的区别?
sp = getSharedPreferences("counter",MODE_PRIVATE);
editor = sp.edit();
int count_reserved = sp.getInt("count_reserved", 0);
// 下面这个方法过时了:https://blog.csdn.net/sinat_33150417/article/details/104323897
// MainViewModel mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
mainViewModel = new ViewModelProvider(this,new MainViewFactory(count_reserved)).get(MainViewModel.class);
plusOneBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mainViewModel.counter++; // public 但是不符合开闭原则
refreshCounter();
}
});
clearBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mainViewModel.counter = 0;
refreshCounter();
}
});
refreshCounter();
}
private void refreshCounter() {
// 注意setText必须是String类型不能是int,不然会报错
infoText.setText(String.valueOf(mainViewModel.counter));
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: counter:" + mainViewModel.counter);
editor.putInt( "count_reserved", mainViewModel.counter);
if(editor.commit()){// 和apply的区别是有返回值 如果不commit是进不去的!!!
Log.d(TAG, "onPause: count_reserved: "+ String.valueOf(sp.getInt("count_reserved",-1)));
}else {
Log.d(TAG, "onPause: Error commit");
}
}
Lifecycles
在编写android应用层序的时候,可能会经常遇到需要感知activity生命周期的情况,以便在适当的时候进行相应的逻辑控制。
在一个activity中去感知它的生命周期非常简单,而如果要在一个非activity的类中去感知activity的生命周期就可能需要借助监听器等方式来完成。但是这种方式就需要自己编写大量的逻辑代码。而Lifecycles组件可以让任何一个类都能够轻松感知到activity的生命周期,同时又不需要在activity中编写太多额外的逻辑。
Observer的用法非常简单: 实现接口,并使用注解:
import android.util.Log;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
public class MyObserver implements LifecycleObserver {
private static final String TAG = "MyObserver";
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void activityStart(){
Log.d(TAG, "activityStart");
}
}
借助LifecycleOwner,在生命周期发生变化的时候通知MyObserver,我们可以实现一个LifecycleOwner接口的实现类。首先调用LifecycleOwner的getLifeOwner的getLifecycle()方法,得到一个Lifecycle对象,然后调用它的addObserver()方法来观察LifecycleOwner的生命周期,再把MyObserver的实例传进去就可以了。
那么LifecycleOwner是什么呢?如何才能获取一个LifecycleOwner的实例。其实没有必要去自己实现一个LifecycleOwner,因为只要你的Activity是继承自AppCompatActivity的,或者你的Frgment是继承自androidx.fragment.app.Fragment的,那么他妈本身就是一个LifecycleOwner的实例。
所以在MainActiviy中的代码可以这么写:
Lifecycle lifecycle = getLifecycle();
lifecycle.addObserver(new MyObserver());
lifecycle.currentState返回的生命周期状态是一个枚举类型,一共有INITALIZED、DESTORYED、CREATED、STARTED、RESUMED这5种状态,他们与activity的生命周期回调所对应的关系如下图:
LiveData
LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。LiveData特别适合于ViewModel结合在一起使用。
LiveData的基本用法
我们一直使用的都是在activity中手动获取viewmodel中的数据这种交互方式,但是viewmodel却无法将数据的变化主动通知给activity。虽然,把activity的实例传给viewmodel,这样viewmodel就能主动对activity进行通知。但是,由于viewmodel的声明周期是长于activity的,如果吧activity的实例传递给viewmodel,就很有可能会因为activity无法释放而造成内存泄漏,这是一种非常错误的做法。
而LiveData可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。也就是说,如果我们将计数器使用livedata来包装,然后在activity中去观察他,就可以主动将数据变化通知给activity了。
我们可以修改MainViewModel
的代码,具体如下:
public class MainViewModel extends ViewModel {
private MutableLiveData<Integer> counter;
public MainViewModel(){
counter = new MutableLiveData<>();
counter.setValue(0);
}
public MainViewModel(int counter){
this.counter = new MutableLiveData<>();
this.counter.setValue(counter);
}
public void plusOne(){
counter.setValue((counter.getValue() == null ? 0 : counter.getValue()) + 1);
}
public void clear(){
counter.setValue(0);
}
public int getCounter(){
return this.counter.getValue() == null ? 0 : this.counter.getValue();
}
}
}
这里我们将counter变量修改成了MutableLiveData
对象,并指定他的泛型为Integer。MutableLiveData是一种可变的LiveData,用法很简单,主要有3种读写数据的方法,分别是getValue()
、setValue()
和postValue()
方法,用于获取数据,设置数据(但是只能在主线程中调用),在非主线程线程中给LiveData设置数据。
最后,上面还有有点不够规范,应该只暴露不可变的LiveData给外部。这样在非ViewModel中就稚嫩观察LiveData的数据变化,而不能给LiveData设置数据。因此改造如下:
public class MainViewModel extends ViewModel {
private final MutableLiveData<Integer> counter;
public MainViewModel(){
counter = new MutableLiveData<>();
counter.setValue(0);
}
public MainViewModel(int counter){
this.counter = new MutableLiveData<>();
this.counter.setValue(counter);
}
public void plusOne(){
counter.setValue((counter.getValue() == null ? 0 : counter.getValue()) + 1);
}
/**
* 最终返回的时候是一个LiveData<Integer> 他是不可变的
*/
public LiveData<Integer> getCounter(){
return counter;
}
}
map和switchMap
map
LiveData的基本用法可以满足大部分的开发需求,但是当项目变得复杂之后,可能会出现一些更加特殊的需求,比如一个POJO类User,如果我们只关心用户的姓名,而实际上将整个User类型的LiveData暴露给外部就不合适了。整个时候就可以使用map()方法,他可以将User类型的LiveData自由地转型成任意其他类型的LiveData。具体代码如下:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class MainViewModel extends ViewModel {
private final MutableLiveData<Integer> counter;
private final MutableLiveData<User> userLiveData = new MutableLiveData<>();
LiveData<String> userName = Transformations.map(userLiveData, User::getName);
}
上面我们调用了Transformations的map()方法来对LiveData的数据类型进行转换。第一个参数是原始的LiveData对象,第二个参数是转换函数,逻辑就是将User对象转换成一个只包含用户姓名的字符串。
switchMap
他的使用场景非常固定,但是可能比map方法要更加常用。上面的所有的内容都有一个前提:LiveData对象的实例都是在ViewModel中创建的。而实际的项目中,不可能一直是这种情况,很可能是ViewModel中的某个LiveData对象是调用另外的方法获取的。
假设存在一个单例类Repository,代码如下:
public class Repository {
public static LiveData<User> getUser(String userId){
MutableLiveData<User> liveData = new MutableLiveData<>();
liveData.setValue(new User(userId,"ssozh",0));
return liveData;
}
}
这里我们在Repository类中添加了一个getUser()方法,这个方法接受一个userId参数。我们在MainViewModel中也是获取LiveData对象。
public LiveData<User> getUser(String userId){
return Repository.getUser(userId);
}
接下来的问题是,在activity中如何观察LiveData的数据变化呢?
- 首先通过写一个getUser()方法来调用肯定是错误的,因为你这样调用观察的是捞的LiveData实例,根本无法观察到数据的变化。
- 使用switchMap()方法【下面代码是对着kotlin写的可能有问题】
private MutableLiveData<String> userIdLiveData = new MutableLiveData<>();
LiveData<User> user = Transformations.switchMap(userIdLiveData,userId ->{
return Repository.getUser(userId);
});
public LiveData<User> getUser(String userId){
return Repository.getUser(userId);
}
switchMap()方法同样接受两个参数:
- 第一个参数用来传入我们新增的userIdLiveData,switchMap()方法对他进行观察。
- 第二个参数是一个转换函数,我们必须在这个转换函数中返回一个LiveData对象,因为switchMap()方法的工作原理就是要将转换函数中返回的LiveData对象转换成另一个可观察的LiveData对象。
switchMap()
的整体工作流程:当外部调用MainViewModel的getUser()
方法来获取用户数据时,并不会发起任何请求或者函数调用,只会将传入的userId
值设置到userIdLiveData
当中。一旦uerIdLiveData
的数据发生变化,那么观察userIdLiveData
的switchMap()
方法就会执行。并且调用我们编写的转换函数,然后在转换函数中调用Repository.getUser()
方法返回的LiveData对象转换成一个可观察的LiveData对象,对于activity而言,只要去观察这个LiveData对象就可以了。
Room【概述】
Room是android官方推出的一个ORM框架,并将他加入到了jetpack当中。
使用Room进行增删改查
Room是由Entity、Dao和Database这三个部分组成,每个部分都有明确的职责,具体说明如下:
- Entity:用于定义封装实际数据的实体类,每个实体类都会子啊数据库中有一张丢应的表,并且表中的列是根据实体类中的字段自动生成的。
- Dao:Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层打交道交互即可。
- Database:用于定义数据库中的关键信息,包括数据库的版本号,包含哪些实体类以及提供的Dao层访问实例。
‘使用:
- 添加依赖
- 定义实体类Entity:添加
@Entity
注解 - 定义Dao【最关键部分】:
- 创建一个UserDao接口,并添加
@Dao
注解。 - 接口中的方法添加
@Insert
等注解。
- 创建一个UserDao接口,并添加
- 定义Database【写法固定】:
- 其包含三部分内容:数据库的版本号,包含哪些实体类,Dao层的访问实例。
- 添加
@Database
注解,继承RoomDatabase
类,并且声明成抽象类。【这个地方只需要声明就行,具体的方法是由Room在底层自动完成的】 - kotlin在companion object结构体中编写一个单例模式。【Java就是静态方法】
- 测试【略】
Room的数据库升级【略】
Room在数据库升级方面设计得非常繁琐,基本上没有比SQLiteDatabase简单到哪去,每次升级都需要手动编写升级逻辑才行。
WorkManager
android的后台机制是一个很复杂的话题。在很早之前,android系统的后台系统是非常开发的,service的优先级也很高,仅次于activity,那个时候可以在service中做很多事情。但是由于后台功能太过于开放,每个应用都想无限地占用后台资源,导致手机的内存越来越紧张,好点越来越快,也变得越来越卡。为了解决这些情况,基本上android系统每发布一个新版本,后台权限都会被进一步收紧。
从4.4系统的AlarmManager开始到5.0的JobScheduler来处理后台任务,再到6.0的Doze和App Standby模式,再到8.0直接禁用了后台功能只允许使用前台service。一直在修改与后台相关的API。这么频繁的功能和API变更,让开发者更难受了,为了解决这个问题,google退出了workmanager组件。
Workmanager很适合用于处理一些要求定时执行的任务,它可以根据OS的版本自动选择底层是使用alarmManager实现还是JobScheduler实现,从而降低了我们的使用成本。另外,他还支持周期性任务、链式任务处理等功能,是一个非常强大的工具。
不过,WorkManager和Service并不相同,也没有直接的联系。Service是android系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。而WorkManager只是一个处理定时任务的工具,它可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务仍然将得到执行,因此workmanager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据等等。
WorkManager的基本用法
基本用法:
- 添加依赖
- 定义一个后台任务,并实现具体的任务逻辑
- 配置该后台任务的运行条件和约束信息,并构建后台任务请求;
- 将该后台任务请求传入WorkManager的
enqueue()
方法中,系统会在合适的时间运行。
添加依赖
implementation 'androidx.work:work-runtime:2.4.0'
定义后台任务
后台任务的写法非常固定:
- 首先每个后台任务必须继承自Worker类,并调用它唯一的构造函数。
- 然后重写父类的
doWork()
这个方法,在这个方法中编写具体id后台任务逻辑即可。doWork()
方法不会运行在主线程中,因此你可以放心的在这里执行耗时逻辑。doWork()
方法要求返回一个Result对象,用于表示任务的运行结果。- 还有一个
Result.retry()
方法,他代失败,只是可以结合WorkRequest.Buidler的setBackoffCriteria()
方法来重新执行任务。
// 创建一个SimpleWorker类
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class SimpleWorker extends Worker{
private static final String TAG = "SimpleWork";
public SimpleWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Log.d(TAG, "doWork: do work in SimpleWorker");
return Result.success();
}
}
配置该后台任务的运行条件和约束信息
在MainActivity中,把上面创建的后台任务的Class对象传入OneTimeWorkRequest.Builder的构造函数中,然后调用build()方法即可完成构建。
- OneTimeWorkRequest.Builder是WorkRequest.Builder的子类,用于构建单词运行的后台任务请求。
- PeriodicWorkRequest.Builder也是WorkRequest.Builder的子类,可用于构建周期性运行的后台任务,但是为了降低设备性能消耗,PeriodicWorkRequest.Buidler构建函数中传递的运行周期间隔不能低于15分钟。
// WorkManager的基本用法
// OneTimeWorkRequest.Builder是WorkRequest.Builder的子类,用于构建单词运行的后台任务请求。
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(SimpleWorker.class).build();
// PeriodicWorkRequest.Builder也是WorkRequest.Builder的子类,可用于构建周期性运行的后台任务
PeriodicWorkRequest request1 = new PeriodicWorkRequest.Builder(SimpleWorker.class, 15, TimeUnit.MINUTES).build();
Button doWorkBtn = findViewById(R.id.do_work_btn);
doWorkBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Operation enqueue = WorkManager.getInstance(MainActivity.this).enqueue(request);
// WorkManager.getInstance(this).enqueue(request1);
}
});
使用WorkManager处理复杂的任务
虽然成功运行了一个后台任务,但是由于不能控制它的具体运行时间,因此并没有什么太大的实际用处。实际上WorkManager还允许我们控制许多其他方面的东西,包括:
-
让后台任务再指定的延时时间后运行 => 借助etInitilaDelay()方法
-
给后台任务请求添加标签 =>
OneTimeWorkRequest.Builder()
后面addTag()
- 可以通过标签来取消后台任务请求,当然也可以通过request.id来取消。
-
一次性取消所有后台任务请求 =>
WorkManager.getInstance(Context).cancelAllWork()
-
Result.retry()结合
setBackoffCriteria()
方法来重新执行任务 =>WorkManager.getInstance(Context)
后面setBackoffCriteria()
,其中接受三个参数:- 第一个参数用于指定如果任务再次执行失败,下次重试的时间应该以什么样的形式延迟(BackoffPolicy)。有两个策略,分别是线性和指数。
- 第二个参数(数字)和第三个参数(单位)用于指定在多久之后重新执行任务,不能短于10分钟
-
Result.success和Result.failure的监听任务:
WorkManager.getInstance(MainActivity.this).getWorkInfoByIdLiveData(request1.getId()) .observe(MainActivity.this, workInfo -> { if(workInfo.getState() == WorkInfo.State.SUCCEEDED) Log.d(TAG, "onClick: do work succeeded"); });
其中
getWorkInfoByIdLiveData
传入后台请求任务的id,会返回一个LiveData对象,然后就可以调用LiveData对象的observe()
方法来观察数据变化了。 -
链式任务:
Operation enqueue = WorkManager.getInstance(MainActivity.this) .beginWith(work1) .then(work2) .then(work3) .enqueue();
注意,一旦某个任务运行失败了,或者被取消了,那么接下来的后台任务就都得不到运行了。
标签:android12,counter,LiveData,private,activity,后台任务,Jetpack,public 来源: https://www.cnblogs.com/SsoZhNO-1/p/14197424.html