Jetpack全套
一.Jetpack介绍
Jetpack是一套库、工具和指南,可以帮助开发者更轻松地编写优质应用。这些组件可以帮助开发者遵循最佳做法、
让开发者摆脱编写样板代码的工作并简化复杂任务,以便开发者将精力集中放在所需的代码上。
1.特性:
- 加速开发: 组件可以单独采用(不过这些组件是为协同工作而构建的),同时利用Kotlin语言功能帮助开发者提高工作效率。
- 消除样板代码: Android Jetpack可管理繁琐的Activity(如后台任务、导航和生命周期管理),以便开发者可以专注于如何让自己的应用出类拔萃。
- 构建高质量的强大应用 Android Jetpack组件围绕现代化设计实践构建而成,具有向后兼容性,可以减少崩溃和内存泄漏。
2.分类:
(1)Architecture(架构组件)可帮助您设计稳健、可测试且易维护的应用
- Data Binding: 是一种支持库,借助该库,可以以声明方式将可观察数据绑定到界面元素。
- Lifecycles: 管理Activity 和 Fragment的生命周期,能够帮助开发者轻松的应对Activity/Fragment的生命周期
变化问题,帮助开发者生成更易于维护的轻量级代码。 - LiveData: 在底层数据库更改时通知视图,是可观察的数据持有者类。与常规的可观察对象不同,LiveData具
有生命周期感知功能(例如Activity,Fragment或Service的生命周期)。 - Navigation: 处理应用内导航所需的一切。
- Paging: 逐步从您的数据源按需加载信息,帮助开发者一次加载和显示小块数据。按需加载部分数据可减少网
络带宽和系统资源的使用。 - Room: 流畅地访问 SQLite 数据库。在SQLite上提供了一个抽象层,以在利用SQLite的全部功能的同时允许更
健壮的数据库访问。 - ViewModle: 以注重生命周期的方式管理界面相关的数据。ViewModel类允许数据幸免于配置更改(例如屏幕
旋转)。通常和DataBinding配合使用,为开发者实现MVVM架构提供了强有力的支持。 - WorkManager: 管理 Android 的后台作业,即使应用程序退出或设备重新启动,也可以轻松地调度预期将要
运行的可延迟异步任务。
(2)Foundation(基础组件)可提供横向功能,例如向后兼容性、测试和 Kotlin 语言支持。 - Android KTX: 编写更简洁、惯用的 Kotlin 代码,是一组Kotlin扩展程序。优化了供Kotlin使用的Jetpack和
Android平台API。旨在让开发者利用 Kotlin 语言功能(例如扩展函数/属性、lambda、命名参数和参数默认
值),以更简洁、更愉悦、更惯用的方式使用 Kotlin 进行 Android 开发。Android KTX 不会向现有的 Android
API 添加任何新功能。 - AppCompat: 帮助较低版本的Android系统进行兼容。
- Auto: 有助于开发 Android Auto 应用的组件。是 Google推出的专为汽车所设计之 Android 功能,旨在取代
汽车制造商之原生车载系统来执行 Android应用与服务并访问与存取Android手机内容。 - Benchmark: 从 Android Studio 中快速检测基于 Kotlin 或 Java 的代码。
- Multidex: 为具有多个 DEX 文件的应用提供支持。
- Security: 按照安全最佳做法读写加密文件和共享偏好设置。
- Test: 用于单元和运行时界面测试的 Android 测试框架。
- TV: 有助于开发 Android TV 应用的组件。
- Wear OS by Google: 有助于开发 Wear 应用的组件。
(3)Behavior(行为组件)可帮助您的应用与标准 Android 服务(如通知、权限、分享和 Google 助理)相集成。 - CameraX: 简化相机应用的开发工作。它提供一致且易于使用的 API 界面,适用于大多数 Android 设备,并可向后兼容至 Android 5.0(API 级别 21)。
- DownloadManager: 是一项系统服务,可处理长时间运行的HTTP下载。客户端可以请求将URI下载到特定的
目标文件。下载管理器将在后台进行下载,处理HTTP交互,并在出现故障或在连接更改和系统重新启动后重试下载。 - Media & playback: 用于媒体播放和路由(包括 Google Cast)的向后兼容 API。 4. Notifications: 提供向后兼容的通知 API,支持 Wear 和 Auto。 5. Permissions: 用于检查和请求应用权限的兼容性 API。 6. Preferences: 创建交互式设置屏幕,建议使用 AndroidX Preference Library 将用户可配置设置集成至应用中。
- Sharing: 提供适合应用操作栏的共享操作。
- Slices: 是UI模板,可以通过启用全屏应用程序之外的互动来帮助用户更快地执行任务,即可以创建在应用外
部显示应用数据的灵活界面。
(4)UI(界面组件)可提供微件和辅助程序,让您的应用不仅简单易用,还能带来愉悦体验。了解有助于简化界面开发的
Jetpack Compose。 - Animation & transitions: 使开发者可以轻松地为两个视图层次结构之间的变化设置动画。该框架通过随时间更改其某些属性值在运行时为视图设置动画。该框架包括用于常见效果的内置动画,并允许开发者创建自定义动画和过渡生命周期回调。
- Emoji: 使Android设备保持最新的最新emoji表情,开发者的应用程序用户无需等待Android OS更新即可获取最新的表情符号。
- Fragment: Activity的模块化组成部分。
- Layout: 定义应用中的界面结构。可以在xml中声明界面元素,也可以在运行时实例化布局元素。
- Palette: 是一个支持库,可从图像中提取突出的颜色,以帮助开发者创建视觉上引人入胜的应用程序。开发者可以使用调色板库设计布局主题,并将自定义颜色应用于应用程序中的视觉元素。
二.应用架构
三.LifeCycle:
1.简介
一直以来,解藕都是软件开发永恒的话题。在Android开发中,解藕很大程度上表现为系统组件的生命周期与普通组件之间的解藕,因为普通组件在使用过程中需要依赖系统组件的的生命周期。
举个例子,我们经常需要在页面的onCreate()方法中对组件进行初始化,然后在onStop()中停止组件,或者在onDestory()方法中对进行进行销毁。事实上,这样的工作非常繁琐,会让页面和页面耦合度变高,但又不得不做,因为如果不即时的释放资源,有可能会导致内存泄露。例如,下面是一个在Activity的同生命周期方法中监听调用的例子,代码如下。
public class MyListener {
private static final String TAG = "MyListener";
public void start(){
Log.d(TAG, "start: ");
}
public void stop(){
Log.d(TAG, "stop: ");
}
public void resume(){
Log.d(TAG, "resume: ");
}
}
public class LifecycleActivity extends AppCompatActivity {
private MyListener mMyListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lifecycle);
mMyListener = new MyListener();
mMyListener.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMyListener.stop();
}
}
虽然,代码看起来没什么问题,但在实际开发中可能会有多个组件在Activity的生命周期中进行回调,这样Activity的生命周期的方法中可能就需要编写大量的代码,这就使得它们难以维护。 我们希望在对组件进行管理不依赖页面的生命周期的回调方法,同时当页面生命周期发生改变时,也能够即时的收到通知。这在Android组件化和架构设计的时候表现的尤为明显。
那纠结什么是Lifecycle组件呢?总的来说,Lifecycle 就是具有生命周期感知能力的组件。简单的理解就是,当Activity/Fragment的生命周期产生变化时,Lifecycle组件会感应相应的生命周期变化,当然我们还可以通过使用Lifecycle组件来在自定义的类中管理Activity/fragment的生命周期。
目前,Lifecycle生命周期组件主要由Lifecycle、LifecycleOwner、LifecycleObserver三个对象构成。
(1)Lifecycle:是一个持有组件生命周期状态与事件(如Activity或Fragment)的信息的类。
(2)LifecycleOwner:Lifecycle的提供者,通过实现LifecycleOwner接口来访问Lifecycle生命周期对象。
Fragment和FragmentActivity类实现了LifecycleOwner接口,它具有访问生命周期的getLifecycle方法,使用时
需要在自己的类中实现LifecycleOwner。
(3)LifecycleObserver:Lifecycle观察者,可以使用LifecycleOwner类的addObserver()方法进行注册,被注册后
LifecycleObserver便可以观察到LifecycleOwner的生命周期事件。
2.简单使用
(1)依赖:
def lifecycle_version = "2.5.1"
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
(2)按照Lifecycle的使用流程,需要先定义观察者,并重写对应的生命周期:设计模式:观察者模式
public class MyObserver implements LifecycleObserver {
private static final String TAG = "MyObserver";
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void start(){
Log.d(TAG, "start: ");
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stop(){
Log.d(TAG, "stop: ");
}
}
(3)activity
public class LifecycleActivity2 extends AppCompatActivity {
private MyObserver mMyObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lifecycle2);
mMyObserver = new MyObserver();
getLifecycle().addObserver(mMyObserver);
}
}
3.实战:Dialog内存泄漏
当activity关闭的时候,dialog没有关闭,进而导致内存泄漏,下面使用lifecycle打造一个完美的dialog
(1)自定义dialog并实现LifecycleObserver接口
public class MyDiaLog extends Dialog implements LifecycleObserver {
public MyDiaLog(@NonNull Context context) {
super(context);
}
public MyDiaLog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}
protected MyDiaLog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_dialog);
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void destory(){
dismiss();
}
}
(2)activity
public class DialogActivity extends AppCompatActivity {
private MyDiaLog mMyDiaLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dialog);
mMyDiaLog = new MyDiaLog(this);
getLifecycle().addObserver(mMyDiaLog);
mMyDiaLog.show();
}
}
4.Lifecycle的应用
- 使用Lifecycle解藕activity/fragment与组件
- 使用LifecycleService解藕Service与组件
- 使用ProcessLifecycleOwner监听Application应用程序生命周期
(0)activity/fragment上面案例都是
(1)Service
举例:一般项项目获得用户的位置需要在Service中监听,当Service被创建的时候需要注册监听,当Service销毁的时候需要解除注册,不然很造成内存泄漏现象(常见的内存泄漏现象可以上网多了解下)
a.自定义LifecycleObserver类,service创建的时候注册监听,service销毁的时候解除注册监听
public class MyLocationObserver implements LifecycleObserver {
private Context context;
private LocationManager locationManager;
private MyLocationListener locationListener;
public MyLocationObserver(Context context) {
this.context = context;
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
private void startGetLocation() {
Log.d("ning","startGetLocation");
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
locationListener = new MyLocationListener();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 1, locationListener);
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private void stopGetLocation() {
Log.d("ning","stopGetLocation");
locationManager.removeUpdates(locationListener);
}
static class MyLocationListener implements LocationListener{
@Override
public void onLocationChanged(Location location) {
Log.d("ning","location changed:"+location.toString());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
}
b。Service:继承LifecycleService
public class MyLocationService extends LifecycleService {
public MyLocationService() {
Log.d("ning","MyLocationService");
MyLocationObserver observer = new MyLocationObserver(this);
getLifecycle().addObserver(observer);
}
}
c。activity启动服务和停止服务
public class Step3Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_two);
}
public void startGps(View view) {
startService(new Intent(this,MyLocationService.class));
}
public void stopGps(View view) {
stopService(new Intent(this,MyLocationService.class));
}
}
d。权限以及动态权限,此处省略
(2)Application:ProcessLifecycleOwner
a。自定义LifecycleObserver类
public class ApplicationObserver implements LifecycleObserver {
private String TAG = "ning";
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
Log.d(TAG, "Lifecycle.Event.ON_CREATE");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
Log.d(TAG, "Lifecycle.Event.ON_START");
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
Log.d(TAG, "Lifecycle.Event.ON_RESUME");
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
Log.d(TAG, "Lifecycle.Event.ON_PAUSE");
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
Log.d(TAG, "Lifecycle.Event.ON_STOP");
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
Log.d(TAG, "Lifecycle.Event.ON_DESTROY");
}
}
b。application添加观察者
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle()
.addObserver(new ApplicationObserver());
}
}
6.总结
四.VideModel
0.源码解析:面试问题为什么viewmodel数据不会消失
https://zhuanlan.zhihu.com/p/488904151
1.介绍
ViewModel 同样具有生命周期意识的处理跟UI相关的数据,并且,当设备的一些配置信息改变(例如屏幕旋转)它的数据不会消失。
通常情况下,如果我们不做特殊处理,当屏幕旋转的时候,数据会消失,那 ViewModel 管理的数据为什么不会消失呢,是因为 ViewModel 的生命周期:
注意:不要向ViewModel中传入Context,会导致内存泄漏,如果使用Context请使用AndroidViewModel的Appliction
2.简单使用
案例:当屏幕方向发生改变的时候,数据保持不变
(1)自定义ViewModel
public class NumViewModel extends ViewModel {
public int num;
}
(2)activity实现,并旋转屏幕测试
public class ViewModelActivity extends AppCompatActivity {
private TextView tv;
private NumViewModel numViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
tv = findViewById(R.id.tv);
//获得viewmodel,页面切换后数据不变
numViewModel = new ViewModelProvider(this).get(NumViewModel.class);
tv.setText(numViewModel.num+"");
}
public void plus(View view) {
tv.setText(numViewModel.num+++"");
}
}
3.AndroidViewModel使用
public class MyAndriodViewModel extends AndroidViewModel {
public int num;
public MyAndriodViewModel(@NonNull Application application) {
super(application);
}
}
public class AndrodiViewModelActivity extends AppCompatActivity {
private TextView tv;
private MyAndriodViewModel mAndroidViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_androdi_view_model);
tv = findViewById(R.id.tv);
mAndroidViewModel = new ViewModelProvider(this).get(MyAndriodViewModel.class);
tv.setText(mAndroidViewModel.num);
}
public void plus(View view) {
tv.setText(mAndroidViewModel.num+++"");
}
}
4.使用viewmodel实现fragment直接数据共享
public class FragmentViewModel extends ViewModel {
public int num;
}
public class OneFragment extends Fragment {
private Button bt;
private View view;
private FragmentViewModel fragmentViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_one, container, false);
bt = view.findViewById(R.id.tv);
fragmentViewModel = new ViewModelProvider(getActivity()).get(FragmentViewModel.class);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fragmentViewModel.num++;
bt.setText(fragmentViewModel.num+"");
}
});
return view;
}
}
public class TwoFragment extends Fragment {
private Button bt;
private View view;
private FragmentViewModel fragmentViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_two, container, false);
bt = view.findViewById(R.id.tv);
fragmentViewModel = new ViewModelProvider(getActivity()).get(FragmentViewModel.class);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fragmentViewModel.num++;
bt.setText(fragmentViewModel.num+"");
}
});
return view;
}
}
五.LiveData
在讲 LiveData 之前,我们先看看 LiveData 和 ViewModel 的作用
1.介绍
从官网的介绍可以看到, LiveData 作用跟RxJava类似,是观察数据的类,相比RxJava,它能够在Activity、Fragment和Service之中正确的处理生命周期。那么 LiveData 有什么优点呢?
- 数据变更的时候更新UI
- 没有内存泄漏
- 不会因为停止Activity崩溃
- 无需手动处理生命周期
- 共享资源
乍看之下 LiveData 挺鸡肋的,事实也确实如此,因为 LiveData 能够实现的功能 RxJava 也可以实现,而且与LiveData 相比, RxJava 拥有着更加丰富的生态,当然,谷歌的官方架构仍然值得我们去学习。
2.4个常用方法
getValue:获得数据
setValue:主线程设置数据
postValue:子线程设置数据
observe:观察数据的变化
3.面试重点:LiveData事件总线封装/LiveData粘性事件/LiveData数据倒灌(后面讲解)
https://blog.csdn.net/qq_34178710/article/details/131280832
4.结合viewModel使用
(1)自定义ViewModel并定义MutableLiveData变量
public class NumViewModel2 extends ViewModel {
private MutableLiveData<Integer> num;
public MutableLiveData<Integer> getNum() {
if(num == null){
num = new MutableLiveData<>();
num.setValue(0);//主线程设置默认值
}
return num;
}
}
(2)activity代码:定时器不断修改数据,liveData观察数据变化并更新UI
public class MainActivity2 extends AppCompatActivity {
private TextView mTextView;
private NumViewModel2 mNumViewModel2;
private Timer timer = new Timer();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mTextView = findViewById(R.id.tv);
mNumViewModel2 = new ViewModelProvider(this).get(NumViewModel2.class);
initTimer();
/***************观察LiveData对象变化************/
mNumViewModel2.getNum().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
mTextView.setText(integer+"");
}
});
}
//定时器:更新livedata数据
private void initTimer() {
timer.schedule(new TimerTask() {
@Override
public void run() {
//子线程设置新的值:postValue
int num = mNumViewModel2.getNum().getValue()+1;
mNumViewModel2.getNum().postValue(num);
}
},1000,1000);
}
}
5.加强练习:
2个fragment,第二个fragment进度条发生改变,第一个fragment值实时更新
6.结合room使用:后面会用到
五.DataBinding
1.mvvm介绍
MVVM(全称Model-View-ViewModel)同 MVC 和 MVP 一样,是逻辑分层解偶的模式
2.Data Binding介绍
Data Binding 不算特别新的东西,2015年Google就推出了,但即便是现在,很多人都没有学习过它,我就是这些工程师中的一位,因为我觉得MVP已经足够帮我处理日常的业务, Android Jetpack 的出现,是我研究 Data Binding 的一个契机。
在进行下文之前,我有必要声明一下,MVVM和Data Binding是两个不同的概念,MVVM是一种架构模式,而Data Binding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。
3.简单使用
(1)app gradle配置
android {
//....
dataBinding {
enabled true
}
}
(2)xml布局,在根标签快捷键选择第一个
自动生成 databinding 布局文件
databing标签介绍
(3)User类:
public class User {
private String username;//用户名
private int age;//年龄
private boolean show;//是否显示
private String imageUrl;//图片
public User(String username, int age, String imageUrl) {
this.username = username;
this.age = age;
this.imageUrl = imageUrl;
}
public boolean isShow() {
return show;
}
public void setShow(boolean show) {
this.show = show;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public User(String username, int age, boolean show, String imageUrl) {
this.username = username;
this.age = age;
this.show = show;
this.imageUrl = imageUrl;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
(4)activity布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.bawei.jetpackforjava.databing.User" />
<import type="android.view.View"></import>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".databing.DataBingActivityOne">
<EditText
android:text="@{user.username}"
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"></EditText>
<EditText
android:text='@{user.age+""}'
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"></EditText>
<ImageView
android:background="@color/black"
android:visibility="@{user.show ? View.VISIBLE :View.GONE}"
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="80dp"></ImageView>
<Button
android:id="@+id/bt_login"
android:text="修改"
android:layout_width="match_parent"
android:layout_height="wrap_content"></Button>
</LinearLayout>
</layout>
(5)activity代码:
public class DataBingActivityOne extends AppCompatActivity {
private ActivityDataBingOneBinding mActivityDataBingOneBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_bing_one);
mActivityDataBingOneBinding = DataBindingUtil.setContentView(this,R.layout.activity_data_bing_one);
User user = new User("张三", 3,true,"");
mActivityDataBingOneBinding.setUser(user);
}
}
4.图片处理
思考问题:DataBind中如何显示图片
(1)ImageViewBinderAdapter:图片适配器
public class ImageViewBinderAdapter {
//加载网络图片
@BindingAdapter("image")
public static void setImage(ImageView imageView, String url){
if(!TextUtils.isEmpty(url)){
Glide.with(imageView.getContext())//这里上下文建议使用application,避免内存泄漏
.load(url)
.placeholder(R.drawable.ic_launcher_background)
.into(imageView);
}else{
imageView.setBackgroundColor(Color.GRAY);
}
}
//加载本地图片
@BindingAdapter("image")
public static void setImage(ImageView imageView, int resId){
imageView.setImageResource(resId);
}
//参数可选,网络图片为空时,加载本地图片
@BindingAdapter(value = {"image", "defaultImageResource"}, requireAll = false)
public static void setImage(ImageView imageView, String url, int resId){
if(!TextUtils.isEmpty(url)){
Glide.with(imageView.getContext())这里上下文建议使用application,避免内存泄漏
.load(url)
.placeholder(R.drawable.ic_launcher_background)
.into(imageView);
}else{
imageView.setImageResource(resId);
}
}
}
(2)布局文件中设置image属性引用图片地址
<ImageView
image="@{user.imageUrl}"
android:background="@color/black"
android:visibility="@{user.show ? View.VISIBLE :View.GONE}"
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="80dp"></ImageView>
5.重点:recyclerview结合使用
(1)User实体类:
public class User {
private String username;//用户名
private String caption;//描述
private String imageUrl;//图片
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public User(String username, String caption, String imageUrl) {
this.username = username;
this.caption = caption;
this.imageUrl = imageUrl;
}
}
(2)item布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.bawei.jetpackforjava.databing_recyclerview.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dip">
<ImageView
android:id="@+id/imageView"
android:layout_width="100dip"
android:layout_height="100dip"
image="@{user.imageUrl}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintHorizontal_bias="0.432"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.054"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/textViewChName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.username}"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.138"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.063"
tools:text="斯嘉丽.约翰逊" />
<TextView
android:id="@+id/textViewEnName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text='@{user.caption}'
android:textSize="18sp"
app:layout_constraintStart_toStartOf="@+id/textViewChName"
app:layout_constraintTop_toBottomOf="@+id/textViewChName"
tools:text="Scarlett Johansson" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.4" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(3)ImageViewBinderAdapter
public class ImageViewBinderAdapter {
//加载网络图片
@BindingAdapter("image")
public static void setImage(ImageView imageView, String url){
if(!TextUtils.isEmpty(url)){
Glide.with(imageView.getContext())//这里上下文建议使用application,避免内存泄漏
.load(url)
.placeholder(R.drawable.ic_launcher_background)
.into(imageView);
}else{
imageView.setBackgroundColor(Color.GRAY);
}
}
//加载本地图片
@BindingAdapter("image")
public static void setImage(ImageView imageView, int resId){
imageView.setImageResource(resId);
}
//参数可选,网络图片为空时,加载本地图片
@BindingAdapter(value = {"image", "defaultImageResource"}, requireAll = false)
public static void setImage(ImageView imageView, String url, int resId){
if(!TextUtils.isEmpty(url)){
Glide.with(imageView.getContext())这里上下文建议使用application,避免内存泄漏
.load(url)
.placeholder(R.drawable.ic_launcher_background)
.into(imageView);
}else{
imageView.setImageResource(resId);
}
}
}
(4)适配器
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
private List<User> mUserList;
public RecyclerViewAdapter(List<User> userList) {
mUserList = userList;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),R.layout.item,parent,false);
return new MyViewHolder(itemBinding);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
User user = mUserList.get(position);
holder.mItemBinding.setUser(user);
}
@Override
public int getItemCount() {
return mUserList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder{
ItemBinding mItemBinding;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
}
public MyViewHolder(ItemBinding itemBinding) {
super(itemBinding.getRoot());
this.mItemBinding = itemBinding;
}
}
}
(5)activity
public class RecyclerViewActivity extends AppCompatActivity {
private ActivityRecyclerViewBinding viewDataBinding;
private RecyclerViewAdapter mRecyclerViewAdapter;
private List<User> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view);
initData();
mRecyclerViewAdapter = new RecyclerViewAdapter(list);
viewDataBinding.rv.setAdapter(mRecyclerViewAdapter);
viewDataBinding.rv.setLayoutManager(new LinearLayoutManager(this));
}
private void initData() {
User i1 = new User("斯嘉丽.约翰逊","Scarlett Johansson","https://5b0988e595225.cdn.sohucs.com/images/20190624/d93dbf866aa2405f8b9b1d660c15db9d.jpeg");
list.add(i1);
User i2 = new User("安吉丽娜·朱莉","Angelina Jolie","https://5b0988e595225.cdn.sohucs.com/images/20190624/0657ccc0066b4e1797ead2b3293230b0.jpeg");
list.add(i2);
User i3 = new User("杰西卡·辛普森","Jessica Simpson","https://5b0988e595225.cdn.sohucs.com/images/20190624/49c95e9b542a4854b2232e67579b9215.jpeg");
list.add(i3);
User i4 = new User("萨尔玛·海耶克","Salma Hayek","https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3893590240,2013198505&fm=26&gp=0.jpg");
list.add(i4);
User i5 = new User("卡门·伊莱克特拉","Carmen Electra","https://5b0988e595225.cdn.sohucs.com/images/20190624/1399d0fda46c467dbd988f2996dccaad.jpeg");
list.add(i5);
User i6 = new User("凯瑟琳·海格尔","Katherine Heigl","https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fq_70%2Cc_zoom%2Cw_640%2Fimages%2F20180116%2F74c81a087a28446590734ca257e3eacf.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618118692&t=6c5c1eecb1b0db8664810bfc51f8609b");
list.add(i6);
User i7 = new User("珍妮佛·安妮斯顿","Jennifer Aniston","https://5b0988e595225.cdn.sohucs.com/images/20190624/98e8b18a86004eb79bef58318e93446d.jpeg");
list.add(i7);
User i8 = new User("梅根·福克斯","Megan Fox","https://5b0988e595225.cdn.sohucs.com/images/20190624/37991ee797e6496d99cdee5315082b76.jpeg");
list.add(i8);
User i9 = new User("杰西卡·阿尔芭","Jessica Alba","https://5b0988e595225.cdn.sohucs.com/images/20190624/eb49bf15b9634d579ff89f596c54e0ca.jpeg");
list.add(i9);
User i10 = new User("詹妮弗·洛芙·休伊特","Jennifer Love Hewitt","https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=791729948,2390587761&fm=26&gp=0.jpg");
list.add(i10);
}
}
6.其他
(1)sub二级标签使用
(2)事件监听
(3)双向绑定
六.Room数据库
1.介绍
Room 是一个持久性库,属于 Android Jetpack 的一部分。Room 是 SQLite 数据库之上的一个抽象层。Room 并不直接使用 SQLite,而是负责简化数据库设置和配置以及与数据库交互方面的琐碎工作。此外,Room 还提供 SQLite 语句的编译时检查
**数据实体(Entity)**表示应用的数据库中的表。数据实体用于更新表中的行所存储的数据以及创建新行供插入。
数据访问对象 (DAO) 提供应用在数据库中检索、更新、插入和删除数据所用的方法。
**数据库类(DataBase)***持有数据库,并且是应用数据库底层连接的主要访问点。数据库类为应用提供与该数据库关联的 DAO 的实例。
2.优点
使用编译时注解,能够对 @Query 和 @Entity 里面的SQL语句等进行验证。
与SQL语句的使用更加贴近,能够降低学习成本。
对 RxJava 2 的支持(大部分都Android数据库框架都支持),对 LiveData 的支持。
@Embedded 能够减少表的创建。
3.简单使用CRUD
(1)app的gradle配置
依赖:
def room_version = "2.4.0"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
// To use Kotlin annotation processing tool (kapt)
// To use Kotlin Symbol Processing (KSP)
// ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation("androidx.room:room-rxjava2:$room_version")
// optional - Paging 3 Integration
implementation("androidx.room:room-paging:$room_version")
defaultConfig里面配置:这里注意一定是+=不是= ,后面会介绍
javaCompileOptions {
annotationProcessorOptions {
//room的数据库概要、记录
arguments += ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
(2)Student实体类:@Entity
@Entity(tableName = "student")
public class Student {
@PrimaryKey(autoGenerate = true)//主键自增
@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
private long id;
@ColumnInfo(name = "username", typeAffinity = ColumnInfo.TEXT)
private String username;
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
private int age;
public Student(long id, String username, int age) {
this.id = id;
this.username = username;
this.age = age;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
(3)StudentDao抽象类:@Dao
@Dao
public interface StudentDao {
//添加一条数据
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Student student);
//添加多条数据
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(List<Student> students);
//删除一条数据
@Delete
void delete(Student student);
//删除多条数据
@Delete
void deleteList(List<Student> students);
//删除所有数据
@Query("delete from Student")
void deleteAll();
//修改数据
@Update
void update(Student student);
//查询所有
@Query("select * from student")
List<Student> getAllStudents();
//根据姓名查找
@Query("select * from student where username = :username ")
List<Student> getStudentByName(String username);
}
(4)AppDataBase抽象类:@Database
/**
* entities:数据表实体类
* version:版本号
* exportSchema:是否导出Schema文件
*/
@Database(entities = {Student.class},version = 2,exportSchema = true)
public abstract class AppDataBase extends RoomDatabase {
public static final String DB_NAME = "StudentDatabase.db";//数据库名称
//注意构造不能私有化
private static AppDataBase appDataBase;
public static AppDataBase getInstance(){
if(appDataBase == null){
synchronized (AppDataBase.class){
appDataBase = Room.databaseBuilder(App.instance,AppDataBase.class,DB_NAME)
.allowMainThreadQueries()数据库耗时操作,默认不能在主线程执行
.build();
}
}
return appDataBase;
}
private StudentDao studentDao;
public abstract StudentDao getStudentDao();
}
(5)增删改查操作
public class RoomActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room);
List<Student> list = new ArrayList<>();
Student student1 = new Student(1,"张三1",18);
Student student2 = new Student(2,"张三2",28);
Student student3 = new Student(3,"张三3",38);
Student student4 = new Student(4,"张三4",48);
list.add(student1);
list.add(student2);
list.add(student3);
list.add(student4);
AppDataBase.getInstance().getStudentDao().insert(list);
student2.setUsername("李四");
AppDataBase.getInstance().getStudentDao().update(student2);
AppDataBase.getInstance().getStudentDao().delete(student2);
List<Student> allStudents = AppDataBase.getInstance().getStudentDao().getAllStudents();
List<Student> lisi = AppDataBase.getInstance().getStudentDao().getStudentByName("李四");
}
}
4.数据库升级
1.异常现象
如果直接将数据库版本升级到4,却没有写对应的Migration,则会出现IllegalStateException异常
2.解决办法
第一种,直接添加fallbackToDestructiveMigration方法,该方法在出现异常时,重建数据表同时数据会丢失,不建议
第二种,写出对应的版本升级Migration,Room 会判断当前有没有直接1到4的升级方案,如果有,直接执行1到4的升级方案,如果没有,Room就会按照顺序执行Migration(1,2)、Migration(2,3)、Migration(3,4)已完成升级
3.代码实现
1-2:添加新的字段
2-3:数据销毁以及重建策略
/**
* entities:数据表实体类
* version:版本号
* exportSchema:是否导出Schema文件
*/
@Database(entities = {Student.class},version = 2,exportSchema = true)
public abstract class AppDataBase extends RoomDatabase {
public static final String DB_NAME = "StudentDatabase.db";//数据库名称
//注意构造不能私有化
private static AppDataBase appDataBase;
public static AppDataBase getInstance(){
if(appDataBase == null){
synchronized (AppDataBase.class){
appDataBase = Room.databaseBuilder(App.instance,AppDataBase.class,DB_NAME)
.allowMainThreadQueries()//数据库耗时操作,默认不能在主线程执行
.addMigrations(MIGRATION_1_2)//数据库升级
.addMigrations(MIGRATION_2_3)//数据库升级
// .fallbackToDestructiveMigration()//表重新建立,数据销毁,
.build();
}
}
return appDataBase;
}
private StudentDao studentDao;
public abstract StudentDao getStudentDao();
//1->2:增加sex字段
static final Migration MIGRATION_1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1");
}
};
//2->3:销毁与重建策略
static final Migration MIGRATION_2_3 = new Migration(2,3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE temp_student (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"+
"name TEXT,"+
"age INTEGER NOT NULL,"+
"sex TEXT DEFAULT 'M',"+
"bar_data INTEGER NOT NULL DEFAULT 1)");
database.execSQL("INSERT INTO temp_student (name,age,sex,bar_data)" +
"SELECT name,age,sex,bar_data FROM student");
database.execSQL("DROP TABLE student");
database.execSQL("ALTER TABLE temp_student RENAME TO student");
}
};
}
@Entity(tableName = "student")
public class Student {
@PrimaryKey(autoGenerate = true)//主键自增
@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
private long id;
@ColumnInfo(name = "username", typeAffinity = ColumnInfo.TEXT)
private String username;
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
private int age;
//V2.0
@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)
public int sex;
//V4.0
/*@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.TEXT)
public String sex;*/
public Student(long id, String username, int age) {
this.id = id;
this.username = username;
this.age = age;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
4.Schema文件
5.预填充数据
public static AppDataBase getInstance(){
if(appDataBase == null){
synchronized (AppDataBase.class){
appDataBase = Room.databaseBuilder(App.instance,AppDataBase.class,DB_NAME)
.allowMainThreadQueries()//数据库耗时操作,默认不能在主线程执行
.addMigrations(MIGRATION_1_2)//数据库升级
.addMigrations(MIGRATION_2_3)//数据库升级
// .fallbackToDestructiveMigration()//表重新建立,数据销毁,
.createFromAsset("xxx.db")//从assets目录中预填充数据
.createFromFile(new File("xxx.db"))//从sdcard目录中预填充数据
.build();
}
}
return appDataBase;
}
注意:
1.预填充的数据库db文件中的表名和自己创建的表名名称不能一致
2.查看生成的数据库结构需要打开三个文件:https://blog.csdn.net/u014521739/article/details/90672082
七.请跳转到:MVVM+JetPack综合使用
八.Hilt依赖注入
1.介绍
为什么要依赖注入?记住一句话,不需要自己new对象。
2.依赖注入普通对象
(1)准备
project:build.gradle配置Hilt插件
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'com.google.dagger.hilt.android' version '2.44' apply false//hilt插件
}
app:build.gradle 使用插件
plugins {
id 'com.android.application'
id 'com.google.dagger.hilt.android'//使用插件
}
依赖
//Hilt
implementation("com.google.dagger:hilt-android:2.44")
//Hilt注解
annotationProcessor("com.google.dagger:hilt-android-compiler:2.44")
(2)创建User类:构造函数添加 @Inject注解来告知Hilt应该如何提供该类的实例
public class User {
@Inject
public User() {
}
public void show(){
Log.d("ytx", "执行show方法: ");
}
}
(3)activity:
注意:1.HiltActivity添加@AndroidEntryPoint注解 2.实例变量添加@Inject注解并且不能私有
@AndroidEntryPoint
public class HiltActivity extends AppCompatActivity {
@Inject
User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hit);
user.show();
}
}
运行报错,提示必须自定义Application并添加@HiltAndroidApp注解
(4) application
@HiltAndroidApp
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
}
}
至此,简单的依赖注入完成,试着将之前写的mvvm添加依赖注入
3.使用@Module依赖注入第三方组件
以RetrofitManager为例,由于Retrofit是第三方类库,开发者无法直接在构造方法中添加注解,所以要使用@Module和@Install完成。
@Module
@InstallIn(SingletonComponent.class)
public class RetrofitManager {
@Provides
@Singleton
public Retrofit getRetrofit(){
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
Retrofit mRetrofit = new Retrofit.Builder()
.client(client)
.baseUrl("http://43.143.146.165:8181")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return mRetrofit;
}
}
**@Module:**用于提供依赖注入实例的模块
**@InstallIn:**装载到哪个模块中
@Provides:提供获取实例的方法
**@Singleton:**一般开发者需要将Retorfit定义成单例模式,只需要将@InstallIn属性值设置为SingletonComponent.class,之前api为ApplicaitonComponent.class,并在方法上添加@Singleton注解
3-1 :组件生命周期
Hilt内置的android组件的生命周期如下表格所示,可以看出这些Component组件是随着对应的Android类的生命周期的创建和销毁而变化,所以我们不需要手动去管理这些组件的生命周期。
3-2: 组件的作用域
默认情况下,Hilt中的所有绑定都未限制作用域。这就是说:每当应用请求绑定的时候,都会创建所需要的类型的一个实例。
Hilt允许将绑定的作用域限定为特定组件。Hilt为在同一个作用域限定范围内的组件创建一次实例,这样应用的绑定请求共享同一实例。
下面看看生成组件的作用域:
注意⚠️:绑定的作用域必须与其安装的组件的作用域一致,不然会出现异常,比如
4.使用Qualifier
需求:上述代码中创建retrofit对象的设置log打印拦截器,超时时间为60秒。实际开发业务中可能还会配置其他属性,如添加token拦截器,sign拦截器。
思路:RetrofitManager中添加getTokenRetrofit方法
@Module
@InstallIn(SingletonComponent.class)
public class RetrofitManager {
@Provides
@Singleton
public Retrofit getRetrofit(){
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
Retrofit mRetrofit = new Retrofit.Builder()
.client(client)
.baseUrl("http://43.143.146.165:8181")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return mRetrofit;
}
@Provides
@Singleton
public Retrofit getTokenRetrofit(){
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor(new TokenInterceptor())
.build();
Retrofit mRetrofit = new Retrofit.Builder()
.client(client)
.baseUrl("http://43.143.146.165:8181")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return mRetrofit;
}
}
报错:提示有2个方法中返回值都是Retrofit,在使用的时候Hilt并不知道依赖注入哪个实例
完美解决:
1.定义注解
@Qualifier
@Retention(RetentionPolicy.CLASS)
public @interface RetrofitStandard {
}
@Qualifier
@Retention(RetentionPolicy.CLASS)
public @interface RetrofitToken {
}
2.方法上添加注解
@Module
@InstallIn(SingletonComponent.class)
public class RetrofitManager {
@Provides
@Singleton
@RetrofitStandard
public Retrofit getRetrofit(){
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
Retrofit mRetrofit = new Retrofit.Builder()
.client(client)
.baseUrl("http://43.143.146.165:8181")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return mRetrofit;
}
@Provides
@Singleton
@RetrofitToken
public Retrofit getTokenRetrofit(){
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor(new TokenInterceptor())
.build();
Retrofit mRetrofit = new Retrofit.Builder()
.client(client)
.baseUrl("http://43.143.146.165:8181")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return mRetrofit;
}
}
3.activity
@AndroidEntryPoint
public class HiltActivity extends AppCompatActivity {
@Inject
User user;
@Inject
@RetrofitStandard
Retrofit mRetrofit;
@Inject
@RetrofitToken
Retrofit tokenRetrofit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hit);
user.show();
Log.d("ytx", "onCreate: "+mRetrofit);
Log.d("ytx", "onCreate: "+tokenRetrofit);
}
}
5.依赖注入架构组件:
Hilt目前只支持ViewModel和WorkManager组件,学习kotlin后
九.WorkManager
1.介绍
WorkManager 是google提供的异步执行任务的管理框架,是 Android Jetpack 的一部分,会根据手机的API版本和应用程序的状态来选择适当的方式执行任务。
在后台执行任务的需求是非常常见的,Android也提供了多种解决方案,如JobScheduler、Loader、Service等,如果这些API没有被恰当使用,则可能会消耗大量电量。Android在解决应用程序耗电问题上做了各种尝试,从Doze到App Standby,通过各种方式限制和管理应用程序,以保证应用程序不会在后台消耗过多的设备电量。WorkManager为应用程序中那些不需要及时完成的任务提供了一个统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡
Doze低电耗模式:
Android 6.0 开始引入的省电机制。英文名称 Doze mode(Doze 即打盹),又称打盹模式。
如果用户设备未连接电源、处于静止状态一段时间且屏幕关闭,设备就会进入低电耗模式。在低电耗模式下,系统会尝试通过限制应用对网络的访问以及对CPU的使用来节省电量,还可以限制应用的一些同步处理以及对系统闹钟的使用。
系统会定期退出低电耗模式一会儿,好让应用完成其推迟的活动。
在每个维护时段结束后,系统会再次进入低电耗模式。随着时间的推移,系统安排维护时段的次数越来越少,这有助于降低对电池的消耗。
一旦用户移动设备、打开屏幕或者连接到充电器的时候,系统就会自动退出低电耗模式,所有应用都会恢复正常。
在低电耗模式下,您的应用会受到以下限制:
1、暂停访问网络。
2、系统将忽略 wake locks(唤醒锁)。
3、AlarmManager 闹钟推迟到下一维护时段。
4、系统不执行 Wi-Fi 扫描。
5、系统不允许运行数据同步。
6、系统不允许运行 JobScheduler。
Standby应用待机模式:
也是一种系统机制。当用户有一段时间未主动使用应用时,系统判定该应用处于空闲状态。以下情况除外:
1、用户显式启动应用。
2、应用当前有一个进程位于前台(表现为 Activity 或前台服务形式,或被另一 Activity 或前台服务占用)。
3、用户可在锁屏或通知栏中看到的通知。
4、系统应用。
当用户将设备插入电源时,系统将退出待机状态,从而让它们可以自由访问网络并执行任何待定作业和同步。 如果设备长时间处于空闲状态,系统将按每天大约一次的频率允许空闲应用访问网络。
具有前台服务(Foreground Service,通过startForeground启动的service)的应用,将不会进入待机模式。
可以看出,低电耗模式是系统级别的,应用待机模式是应用级别的。
2.WorkManager特点
(1)针对的是不需要及时完成的任务
例如,发送应用程序日志、同步应用程序数据、备份用户数据等,这些任务一般都不需要立即完成,如果我们自己来管理这些任务,逻辑可能会非常复杂,若API使用不恰当,可能会消耗大量电量。
(2)保证任务一定会执行
WorkManager能保证任务一定会被执行,即使应用程序当前不在运行中,甚至在设备重启过后任务仍然会在适当的时刻被执行。WorkManager有自己的数据库,关于任务的所有信息和数据都保存在该数据库中。因此只要任务交给了WorkManager,哪怕应用程序彻底退出或者设备被重新启动,WorkManager依然能够保证完成任务。
(3)兼容范围广
WorkManager最低能兼容API Level 14,并且不需要设备安装Google Play Services。因此,不用过于担心兼容性问题,因为API Level 14已经能够兼容几乎100%的设备了。
3.WorkManager兼容方案
WorkManager能根据设备的情况,选择不同的执行方案。在API Level 23以上的设备中通过JobScheduler完成任务,在API Level 23以下的设备中,通过AlarmManager和Broadcast Receivers组合来完成任务。但无论采用哪种方案,任务最终都是由Executor来执行的。另外,WorkManager不是一种新的工作线程,它的出现不是为了替代其他类型的工作线程。工作线程通常立即运行,并在任务执行完成后给用户反馈,而WorkManager不是即时的,它不能保证任务能立即得到执行。
4.WorkManager使用方法
(1)导入依赖
dependencies {
def work_version = "2.8.0"
implementation "androidx.work:work-runtime:$work_version"
}
(2)Work任务类
新建一个MyWork类继承Worker类,重写dowork()方法,在其中添加希望由WorkManager 运行的工作任务
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class MyWork extends Worker {
public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
//任务的具体行为
Log.d("tag", "Work doWork");
return Result.success();
}
}
doWork()将返回执行结果Result,会通知WorkManager服务工作是否成功,以及工作失败时是否应重试工作。
Result.success():工作成功完成。
Result.failure():工作失败。
Result.retry():工作失败,应根据其重试策略在其他时间尝试。
(3)简单activity配置任务
//一次性执行的任务
OneTimeWorkRequest workRequest1 = new OneTimeWorkRequest.Builder(MyWork.class)
.build();
//任务提交给WorkManager
WorkManager workManager = WorkManager.getInstance(this);
workManager.enqueue(workRequest1);
其中,WorkRequest对象有两种类型:
OneTimeWorkRequest:只会执行一次的任务请求
PeriodicWorkRequest:将以周期形式反复执行的任务请求
a:设置任务触发条件:
需要实例化一个Constraints对象指定任务运行的约束(触发条件),常用的约束类型如下
以NetworkType为例,当将网络的约束设置为无要求时,任务将立刻执行,但当对网络类型有要求时,即使已经处在符合要求的网络环境下,任务也不是立刻执行,而是由系统选择合适的时间再执行。
public void myAddWork(View view) {
//定义触发条件
Constraints constraints = new Constraints.Builder()
//NetworkType.NOT REQUIRED: 对网络没有要求
//NetworkType.CONNECTED: 网络连接的时候执行
//NetworkType.UNMETERED:环计费的网络比如WIFI下执行
//NetworkType.NOT ROAMING:非漫游网络状态执行
//NetworkType.METERED: 计费网络比如3G,4G下执行
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.build();
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWork.class)
//设置触发条件
.setConstraints(constraints)
.build();
}
b.设置延迟执行任务与任务标签
当队列中的任务满足了全部约束条件后,系统可能会立即运行该任务。可以通过设置延迟时间来使任务经过一段时间后再启动,下面举例说明了如何将任务设置为在加入队列后至少经过5秒后再运行。
在WorkRequest中可以为单个任务设置标签,也可以为一组具有逻辑联系的多个任务设置相同的标签,标签可以在任务状态操作中作为任务的标识,可以通过标签来监听任务状态,也可以取消所有具有相同标签的任务。
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWork.class)
//设置延迟执行
.setInitialDelay(5, TimeUnit.SECONDS)
//设置tag标签
.addTag("workRequest1")
.build();
c.设置重试和退避策略
当任务的返回值为Result.retry()时,系统需要根据一定的策略来决定每次重试的间隔时间,策略中包含退避延迟时间和退避策略。其中,退避延迟时间指定了首次尝试后重试工作前的最短等待时间,其值不得小于10秒;退避策略定义了退避延迟时间随时间以怎样的方式增长,取值分为两种,线性倍数增长:LINEAR 和指数型增长:EXPONENTIAL。
系统默认的策略是EXPONENTIAL,延迟时间为 10 秒,我们也可以在WorkRequest中自定义策略,下面举例设置了一个线性增长的,等待时间初值为12秒的策略。
public class MyWork extends Worker {
public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
//任务的具体行为
Log.d("tag", "Work doWork");
return Result.retry();//重试
}
}
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWork.class)
//设置退避策略
.setBackoffCriteria(BackoffPolicy.LINEAR, Duration.ofSeconds(12))
.build();
(4)观察任务状态与取消任务
在任务运行的时候,我们可以随时通过任务id或任务标签来查询其状态,利用对应的LiveData方法可以注册监听器来观察WorkInfo的变化
//观察任务状态
workManager.getWorkInfoByIdLiveData(workRequest.getId()).observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
Log.d("workInfo", "onChanged: "+workInfo.toString());
}
});
当不再需要运行先前加入队列的任务时,可以根据任务id或任务标签来取消任务,WorkManager 会在后台检查工作的状态。如果工作已经完成,系统不会执行任何操作。否则,工作的状态会更改为CANCELLED,之后就不会运行这个工作。任何依赖于此工作的WorkRequest的状态也将变为CANCELLED。下面举例了在任务运行前将其取消的情况:
workManager.cancelWorkById(workRequest.getId());
(5)参数传递
WorkManager可以与具体任务之间互相传递参数,在定义WorkRequest对象时可以将参数传入任务,所传参数是一个Data类型的实例化对象。
//定义所要传递的数据
Data input = new Data.Builder()
.putString("inputData", "输入信息")
.build();
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWork.class)
//参数传递
.setInputData(input)
.build();
在任务中可以获得传入的数据并使用,相应的,也可以将数据从任务中传递回WorkManager。
public Result doWork() {
Log.d("tag", "Work doWork");
//获取传递至任务中的数据
String input = getInputData().getString("inputData");
Log.d("input", input);
//定义数据并通过返回值传回WorkManager
Data output = new Data.Builder()
.putString("outputData", "执行成功")
.build();
return Result.success(output);
}
在观察任务状态的监听器中增加以下代码,获取任务传递回来的数据并使用。
workManager.getWorkInfoByIdLiveData(workRequest.getId()).observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
Log.d("workInfo", workInfo.toString());
//当任务执行状态为SUCCESSED时,获取回传的数据信息
if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED){
String output = workInfo.getOutputData().getString("outputData");
Log.d("output", output);
}
}
});
(6)周期任务
上述示例均为一次性任务,在实际开发中可能需要定期运行某些任务。例如,定期备份数据、定期下载应用中的新鲜内容或者定期上传日志到服务器,此时则需要用到WorkRequest的另一种类型:PeriodicWorkRequest,创建对象的代码如下:
PeriodicWorkRequest PeriodicRequest =
new PeriodicWorkRequest.Builder(myWorker.class, 2, TimeUnit.HOURS)
// Constraints
.build();
在此示例中,工任务的运行时间间隔定为2小时,需要注意的是,可以定义的间隔时间最短不得少于15分钟
(7)任务链与任务组合
当需要以特定顺序运行多个任务时,可以创建任务链并将其加入队列,任务链用于指定多个依存任务并定义这些任务的运行顺序。
新建两个类AWorker和BWorker继承Worker类,将其按照任务链形式添加进任务列表
OneTimeWorkRequest ARequest = new OneTimeWorkRequest.Builder(AWorker.class)
.build();
OneTimeWorkRequest BRequest = new OneTimeWorkRequest.Builder(BWorker.class)
.build();
//先运行A,再运行B
WorkManager.getInstance(this)
.beginWith(ARequest)
.then(BRequest)
.enqueue();
有时一些任务需要在若干个任务执行完毕后方可执行,此时就需要用到任务组合。定义五个任务来模拟图中的顺序执行情况
//创建五个任务请求
OneTimeWorkRequest ARequest = new OneTimeWorkRequest.Builder(AWorker.class)
.build();
OneTimeWorkRequest BRequest = new OneTimeWorkRequest.Builder(BWorker.class)
.build();
OneTimeWorkRequest CRequest = new OneTimeWorkRequest.Builder(CWorker.class)
.build();
OneTimeWorkRequest DRequest = new OneTimeWorkRequest.Builder(DWorker.class)
.build();
OneTimeWorkRequest ERequest = new OneTimeWorkRequest.Builder(EWorker.class)
.build();
//将同一条任务链上的任务执行顺序定义成一个任务组合
WorkContinuation continuation1 = WorkManager.getInstance(this)
.beginWith(ARequest)
.then(BRequest);
WorkContinuation continuation2 = WorkManager.getInstance(this)
.beginWith(CRequest)
.then(DRequest);
//把两个任务组合放入一个集合
List<WorkContinuation> taskList = new ArrayList<>();
taskList.add(continuation1);
taskList.add(continuation2);
//设置当集合中的所有任务组合都运行完毕后,才运行任务E
WorkContinuation.combine(taskList)
.then(ERequest)
.enqueue();