第一章 Jetpack基础

本章节所有代码地址:https://gitee.com/evancola/jetpack-study.git

1.1 Lifecycle

LifeCycle 是一个可以感知宿主生命周期变化的组件。常见的宿主包括 Activity/Fragment、Service 和 Application。LifeCycle 会持有宿主的生命周期状态的信息,当宿主生命周期发生变化时,会通知监听宿主的观察者。

Jetpack提供了两个接口:

  • LifecycleOwner 被观察者
  • LifecycleObserver 观察者

被监听的系统组件需要去实现 LifecycleOwner 接口,观察者需要实现 LifecycleObserver 接口

使用场景

  • 监听activity生命周期
  • 监听service生命周期
  • 使用ProcessLifecycle监听应用生命周期

监听activity生命周期

声明一个类实现LifecycleObserver,并重写相关生命周期方法,方法名可以随便定义,通过注解来绑定生命周期,这样这个方法就相当于生命周期的回调方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MyObserver:LifecycleObserver {

companion object{
const val TAG = "MyListener"
}

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate(){
Log.d(TAG, "onCreate: ")
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart(){
Log.d(TAG, "onStart: ")
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume(){
Log.d(TAG, "onResume: ")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause(){
Log.d(TAG, "onPause: ")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop(){
Log.d(TAG, "onStop: ")
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(){
Log.d(TAG, "onDestroy: ")
}
}

添加观察者

1
2
3
4
5
6
7
8
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//添加观察者
lifecycle.addObserver(MyObserver())
}
}

注意:默认情况下,如果是使用的androidX的activity,已经实现了LifecycleOwner 接口,如果你的项目的activity还是使用的support库,最新版的SDK,也通过supportActivity实现了LifecycleOwner接口

监听service生命周期

添加依赖:

1
implementation "androidx.lifecycle:lifecycle-service:2.2.0"

第一步:创建观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyServiceObserver:LifecycleObserver {

companion object{
const val TAG = "MyServiceObserver"
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate(){
Log.d(TAG, "onCreate: ")
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart(){
Log.d(TAG, "onStart: ")
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(){
Log.d(TAG, "onDestroy: ")
}
}

第二步:创建service

1
2
3
4
5
6
class MyService:LifecycleService() {
private val observer = MyServiceObserver()
init {
lifecycle.addObserver(observer) //添加观察者
}
}

第三步:在activity中创建service,启动service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LifeCycle2: AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lifecycle2)
}

fun stop(view: View) {
stopService(Intent(this,MyService::class.java))
}
fun start(view: View) {
startService(Intent(this,MyService::class.java))
}
}

监听应用生命周期

以前我们都是通过自己维护activity栈来监听应用生命周期,很麻烦,很low,现在使用Jetpack中的ProcessLifecycleOwner监听则非常方便

第一步:添加依赖

1
2
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"

第二步:编写观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class AppLifecycleObserver:LifecycleObserver {

companion object{
const val TAG = "AppLifecycleObserver"
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForeground(){
Log.d(TAG, "onAppForeground: APP进入前台啦")
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackground(){
Log.d(TAG, "onAppBackground: APP进入后台啦")
}
}

第三步:在application中添加观察者

1
2
3
4
5
6
7
class App : Application() {

override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
}
}

1.2 ViewModel

ViewModel可以解耦model和view,用于管理和存储数据,不会受到屏幕旋转而丢失瞬时数据

引入依赖:

1
2
3
4
5
6
7
8
9
10
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

//如要要使用kotlin属性委托方式生成viewModel
def activity_version = "1.2.2"
implementation "androidx.activity:activity-ktx:$activity_version"

//fragment 如要要使用kotlin属性委托方式生成viewModel
def fragment_version = "1.3.3"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

创建ViewModel

1
2
3
class MyViewModel:ViewModel() {
var num:Int = 0
}

activity中获取ViewModel

有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ViewModelActivity : AppCompatActivity() {
private lateinit var binding: ActivityViewModelBinding
//1. 第一种方式创建viewModel,需要使用activity-ktx库提供的属性委托
private val mViewModel:MyViewModel by viewModels()
//2. 第二种方式创建viewModel
private lateinit var mViewModel: MyViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityViewModelBinding.inflate(layoutInflater)
setContentView(binding.root)

///获取ViewModel
mViewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this.application)
).get(MyViewModel::class.java)


binding.mTvText.text = mViewModel.num.toString()

}
//点击做加法
fun plus(view: View) {
binding.mTvText.text = (++mViewModel.num).toString()
}
}

这里我们使用了ViewBinding,参考下一节

fragment中共享数据

需求:我们在activity的布局中,直接放入两个fragment,现在需要让彼此通信,共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class OneFragment : Fragment() {

//使用kotlin属性委托
private val viewModel: SharedViewModel by activityViewModels()
//使用ViewBinding
private lateinit var binding: VmFragmentBinding

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//ViewBinding
binding = VmFragmentBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//观察数据变化
viewModel.num.observe(viewLifecycleOwner, Observer {
binding.mTvContent.text = it.toString()
})
//点击事件
binding.mBtnPlus.setOnClickListener {
viewModel.add()
}
}
}

注意:两个fragment获取的是同一个ViewModel,所以它们之间能共享数据,

第二个fragment代码类似,这里就不列出来了,下面来看viewModel

1
2
3
4
5
6
7
8
class SharedViewModel : ViewModel() {

var num = MutableLiveData(0)

fun add(){
num.value = num.value?.plus(1)
}
}

1.3 ViewBinding

在新版的Android studio中废弃了 Kotlin Extensions,也就是直接用布局里面控件id拿到对象

启用ViewBinding

1
2
3
viewBinding {
enabled = true
}

获取binding

针对每个布局都会生成对应binding类

1
2
3
4
5
6
7
8
9
10
11
12
///1. 定义变量
private lateinit var binding: ActivityViewModelBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//2. 通过如下两步绑定布局
binding = ActivityViewModelBinding.inflate(layoutInflater)
setContentView(binding.root)

//3.通过binding获取布局对象
binding.mTvText.text = mViewModel.num.toString()
}

1.4 LiveData

LiveData是一种具有生命周期感知能力的可观察的数据存储器类,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期,这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

1.LiveData基础

创建LiveData

1
2
3
4
5
6
class NameViewModel:ViewModel() {
//创建LiveData对象
val mCurrentName:MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
}

观察和修改LiveData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class LiveDataActivity : AppCompatActivity() {

private val nameViewModel: NameViewModel by viewModels()
private lateinit var binding: ActivityLiveDataBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLiveDataBinding.inflate(layoutInflater)
setContentView(binding.root)
//观察LiveData对象
nameViewModel.mCurrentName.observe(this, Observer {
binding.mTvTitle.text = it
})
//修改LiveData
binding.mBtnChange.setOnClickListener {
nameViewModel.mCurrentName.value = "三上亚悠"
}
}
}

2.转换LiveData之map和switchMap

什么时候需要转换?当你将livedata的数据分发给观察者之前,想对数据做一些改变,或者根据一个livedata生成另一个livedata

  • map作用:将一个函数作用于livedata中的数据,传播到下游(观察者)
  • switchmap:和map类似,也是将一个函数作用于livedata中的数据,但是函数必须返回livedata

map举例:比如有一个user类,有用户名和年龄等字段,我们在viewModel中声明LiveData<User>,但是界面中只需要用户姓名,所以可以这样做

1
2
3
4
5
6
7
8
9
data class User(var firstName: String, var lastName: String, var age: Int)

class MainViewModel(countReserved: Int) : ViewModel() {
private val userLiveData = MutableLiveData<User>()
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
...
}

使用map方法,将LiveData<User> 转为 LiveData<String> 进行观察

switchMap:查看如下代码,不建议这样写,每次调用getPostalCode方法,返回的新的livedata,而界面中观察的老的livedata对象

1
2
3
4
5
6
7
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

private fun getPostalCode(address: String): LiveData<String> {
// DON'T DO THIS
return repository.getPostCode(address)
}
}

更建议的写法(switchMap)

1
2
3
4
5
6
7
8
9
10
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private val addressInput = MutableLiveData<String>()
val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
address -> repository.getPostCode(address) }


private fun setInput(address: String) {
addressInput.value = address
}
}

执行流程:当页面调用setInput方法时,不会发起请求或者函数调用,只会改变addressInput这个livedata的值,livedata发生变化的时候,switch会感知变化,会调用getPostCode,并将结果转为postalCode,界面中只需要去观察postalCode这个liveData

注意:如果是没有参数,则只需要重新赋值即可,也会感知变化(其实并没变化),如下写法:

当界面中调用fetchAll方法,livedata的value重新赋值了,也会触发switchMap执行

1
2
3
4
5
6
7
8
 val fetch:MutableLiveData<Any?> = MutableLiveData()
val users:LiveData<List<User>> = Transformations.switchMap(fetch){
it-> repository.getAll()
}

fun fetchAll(){
fetch.value = fetch.value
}

LiveData结合Room

待更新

LiveData结合协程

待更新

1.5 DataBinding

1. 启用dataBinding

1
2
3
dataBinding{
enabled = true
}

2.布局和绑定表达式

注意:在xml布局中的根布局需改为layout

声明对象

1
2
3
4
5
<data>
<variable
name="user"
type="com.ivan.lifecycle.demo7_databinding.User" />
</data>

绑定表达式

1
2
3
4
5
<TextView
android:id="@+id/mTvFirstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />

数据对象

1
2
3
4
5
6
7
8
9
10
11
12
13
class UserActivity : AppCompatActivity() {

private lateinit var binding: ActivityUserBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityUserBinding.inflate(layoutInflater)
setContentView(binding.root)
//这里给对象进行初始化
binding.user = User("三上", "悠亚")
}
}

常用的表达式

1
2
3
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

??表达式:如果左边为null,则使用右边的值

1
android:text="@{user.displayName ?? user.lastName}"

集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>

android:text="@{list[index]}"

android:text="@{sparse[index]}"

android:text="@{map[key]}"

**注意**:要使 XML 不含语法错误,您必须转义 <字符。例如:不要写成List< String > 形式,而是必须写成List&lt;String>

双引号单引号:

1
android:text='@{map["firstName"]}' 等价于 android:text="@{map[`firstName`]}"

引入资源:

1
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

事件处理:有两种方式

  • 方法引用
  • 监听器绑定

方法引用:

如下有一个类MyHandlers,提供一个方法showToast

1
2
3
4
5
6
class MyHandlers {

fun showToast(view:View){
Toast.makeText(view.context, "点击了", Toast.LENGTH_SHORT).show()
}
}

xml中调用showToast方法

1
2
3
4
5
6
7
<variable
name="handler"
type="com.ivan.lifecycle.demo7_databinding.MyHandlers" />

<TextView
android:onClick="@{handler::showToast}"
>

activity中进行赋值

1
binding.handler = MyHandlers()

监听器绑定

1
2
3
fun onClick2(name:String){
println("点击2$name")
}
1
2
<TextView
android:onClick="@{()-> handler.onClick2(user.secondName)}">

3.import导入

4.bindingAdapter

参考官方文档:https://developer.android.google.cn/topic/libraries/data-binding/binding-adapters

1.6 Room

Room参考文档:https://developer.android.google.cn/training/data-storage/room?hl=zh_cn

Room数据库主要包含三个部分:

  • entity:实体类
  • dao:操作数据库的方法
  • database:数据库对象,抽象类

添加依赖:

1
2
3
4
5
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"

创建实体类:

1
2
3
4
5
6
7
8
@Entity
data class User(
@ColumnInfo(name = "name") val name: String? = null,
@ColumnInfo(name = "country") val country: String? = null
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

注意:主键不要放在data class 的主构造方法中,插入数据的时候,不需要传id,由数据库自增

创建dao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Dao
interface UserDao {

@Query("select * from user")
fun getAll():LiveData<List<User>>


@Insert
fun insert(user: User)

@Update()
fun update(user:User)

@Delete
fun delete(user: User)

@Query("delete from user")
fun deleteAll()
}

创建数据库对象(单例模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 数据库对象,单例模式(实例化的成本很高)
*/
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {

abstract fun userDao(): UserDao

companion object {
private var instance: AppDatabase? = null
//单例模式
@Synchronized
fun getInstance(context: Context): AppDatabase {
instance?.let {
return it
}

return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"user.db"
).build().apply { instance = this }
}
}
}

创建repository

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class UserRepository(context: Context) {

private var userDao:UserDao = AppDatabase.getInstance(context).userDao()


fun insertUser(user: User){
thread {
userDao.insert(user)
}

}

fun update(user: User){
thread {
userDao.update(user)
}
}

fun deleteUser(user: User){
thread {
userDao.delete(user)
}
}

fun deleteAll(){
thread {
userDao.deleteAll()
}
}

fun getAll():LiveData<List<User>>{
return userDao.getAll()
}

}

创建ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class UserViewModel(app: Application) : AndroidViewModel(app) {

private var repository = UserRepository(app)


fun insert(user: User){
repository.insertUser(user)
}

fun update(user: User){
repository.update(user)
}

fun deleteUser(user: User){
repository.deleteUser(user)
}

fun deleteAll(){
repository.deleteAll()
}

fun getAll():LiveData<List<User>>{
return repository.getAll()
}
}

在activity中操作数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class RoomMainActivity : AppCompatActivity() {

lateinit var binding:ActivityRoomMainBinding
private val mViewModel:UserViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRoomMainBinding.inflate(layoutInflater)
setContentView(binding.root)


binding.mRvList.adapter = UserListAdapter(mutableListOf())

mViewModel.getAll().observe(this, Observer {
println("数据改变了:${it.size}")
(binding.mRvList.adapter as UserListAdapter).setData(it)
})
}

fun insert(view: View) {
val user = User(name = "赵丽颖",country = "中国")
mViewModel.insert(user)
}
fun update(view: View) {
val user = User(name = "科比",country = "美国").apply { id = 4 }
mViewModel.update(user)
}
fun delete(view: View) {
val user = User().apply { id = 4 }
mViewModel.deleteUser(user)
}
fun clear(view: View) {
mViewModel.deleteAll()
}
}

说明当新增,修改,删除了表中的数据,LiveData都能观察到数据的变化 说明,最新代码已改,主要参考livedata中的switchMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private val refreshLiveData:MutableLiveData<Any?> = MutableLiveData()
val users:LiveData<List<User>> = Transformations.switchMap(refreshLiveData){
it-> repository.getAll()
}

fun fetchAll(){
refreshLiveData.value = refreshLiveData.value
}

mViewModel.users.observe(this, Observer {
println("users数据改变了:${it.size}")
(binding.mRvList.adapter as UserListAdapter).setData(it)
})
//一进入界面主动获取数据,点击的时候也调用这个方法
mViewModel.fetchAll()

1.7WorkManager

1.添加依赖

1
2
3
4
5
6
def work_version = "2.5.0"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"

// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"

2.基本用法

  • 定义一个后台任务,并实现具体的任务逻辑
  • 配置该后台任务的运行条件和约束信息,并构建后台任务请求
  • 将该后台任务请求传入WorkManager的enqueue()方法中,系统会在合适的时间运行

3.实际案例

第一步:创建后台任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 作者:Administrator on 2021/5/11 15:47
* 邮箱:loabc24@163.com
* description:定义一个后台任务
* - 每一个后台任务都必须继承自Worker类
* - 重写父类中的doWork(),编写具体的后台任务逻辑,doWork()方法运行在异步线程
* - doWork()方法要求返回一个Result对象,用于表示任务的运行结果,成功就返回Result.success(),失败就返回Result.failure()。
*/
class SimpleWorker(context:Context,params:WorkerParameters):Worker(context,params) {
override fun doWork(): Result {
Log.d("SimpleWorker", "do work in SimpleWorker")
return Result.success()
}
}

提示:doWork()方法要求返回一个Result对象,用于表示任务的运行结果,成功就返回Result.success(),失败就返回Result.failure(),还有一个Result.retry()方法,它其实也代表着失败,只是可以结合WorkRequest.Builder的setBackoffCriteria()方法来重新执行任务

第二三步合并如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class WorkManagerTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_work_manager_test)
}
//点击事件
fun executeTask(view: View) {
//2. 配置该后台任务的运行条件和约束信息。 OneTimeWorkRequestBuilder用于构建单次运行的后台任务请求
val request = OneTimeWorkRequestBuilder<SimpleWorker>().build()

//3. 执行任务
WorkManager.getInstance(this).enqueue(request)
}
}

WorkRequest.Builder的子类:

  • OneTimeWorkRequest.Builder:用于构建单次运行的后台任务请求
  • PeriodicWorkRequest.Builder:用于构建周期性运行的后台任务请求,但是为了降低设备性能消耗,PeriodicWorkRequest.Builder构造函数中传入的运行周期间隔不能短于15分钟

代码如下:

1
val request = PeriodicWorkRequest.Builder(SimpleWorker::class.java, 15,TimeUnit.MINUTES).build()

4.复杂应用

1.延迟执行

setInitialDelay

1
2
3
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5, TimeUnit.MINUTES)
.build()
2.添加标签,取消后台任务
1
2
3
4
5
6
7
8
9
10
11
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
...
.addTag("simple")
.build()

//根据tag取消任务
WorkManager.getInstance(this).cancelAllWorkByTag("simple")
//根据id取消任务
WorkManager.getInstance(this).cancelWorkById(request.id)
//取消所有任务
WorkManager.getInstance(this).cancelAllWork()
3.重新执行任务

setBackoffCriteria

1
2
3
4
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
...
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
.build()

参数含义:第二个和第三个参数用于指定在多久之后重新执行任务,时间最短不能少于10秒钟;第一个参数表示如果任务再次失败,下次如何执行

4.链式任务

依次执行任务,前一个任务执行成功,才会继续执行下一个任务

1
2
3
4
5
6
7
8
val sync = ...
val compress = ...
val upload = ...
WorkManager.getInstance(this)
.beginWith(sync)
.then(compress)
.then(upload)
.enqueue()

1.8 Startup

Startup这个组件,对于我们来说,没啥用处,Startup出现的原由,现在越来越多的三方库的初始化,都简化了,不需要我们手动调用初始化,库作者将初始化放在了ContentProvider中初始化,这是为什么呢,因为ContentProvider的onCreate执行时机是在Application的attatchBaseContext()和onCreate()之间,但是ContentProvider作为四大组件之一,是很重量级的,如果这样的三方库过多,APP启动时间会很长,Startup组件的诞生就是来解决这个问题

1.9 Hilt

依赖注入

dagger1:Square公司发布的,基于Java反射

dagger2: Google fork了dagger1,基于Java注解修改后,发布了dagger2

1.10 paging

第二章 kotlin协程

Google kotlin协程文档:https://developer.android.google.cn/kotlin/coroutines

协程的特点:

  • 轻量,单个线程上可以运行多个协程,协程支持挂起,而挂起不会阻塞正在运行协程的线程

2.1协程基础

1.添加依赖

1
2
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'//kotlin协程核心库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'//Android上的协程扩展

2.创建协程

创建协程有多种方式

  • GlobalScope.launch 非阻塞式,顶级协程
  • runBlocking 阻塞式(协程内的代码执行完之前,会阻塞当前线程),一般只在测试环境下使用
  • launch:非阻塞式,这个函数必须在协程作用域中调用,会在当前协程内创建子协程,子协程特点:外层协程执行完,子协程也会结束,创建多个launch协程,是并发执行的
  • coroutineScope:阻塞式,挂起函数,会获得外部协程作用域,同时会阻塞外部协程
  • async:只能在协程作用域中调用,创建子协程,返回一个Deferred对象,如果需要获取结果,调用await方法
  • withContext:简化的async,可以指定线程(强制,必须指定),线程参数有三个值
    • Dispatchers.Default 适用于计算密集型任务
    • Dispatchers.IO:并发线程策略,如异步网络请求,会开启线程
    • Dispatchers.Main:主线程,只能用于Android

GlobalScope

1
2
3
4
5
6
7
fun main() {
GlobalScope.launch {
println("codes run in coroutine scope")
}
//需要延迟一下,否则应用程序结束,GlobalScope协程来不及执行
Thread.sleep(2000)
}

runBlocking

1
2
3
4
5
6
7
8
9
fun main() {
println("before runBlocking")
runBlocking {
println("协程代码执行啦")
delay(2000)
println("协程代码执行结束啦")
}
println("after runBlocking")
}

执行结果:

before runBlocking
协程代码执行啦
协程代码执行结束啦
after runBlocking

可以看出,runBlocking会阻塞当前线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
// testGlobalScope()
// testRunBlocking()
runBlocking {
println("外协程执行开始")
launch {
println("launch1")
delay(1000)
println("launch1 finished")
}
launch {
println("launch2")
delay(1000)
println("launch2 finished")
}
println("外协程执行完毕")
}
}

两个launch子协程是交替并发执行的

并发创建10万个协程

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val start = System.currentTimeMillis()
runBlocking {
repeat(100000) {
launch {
println(".")
}
}
}
val end = System.currentTimeMillis()
println("${end - start}")
}

执行结果只耗时几百毫秒,可以看出,这10万个协程并发的执行

coroutineScope

1
2
3
4
5
6
7
8
9
10
11
fun main() {
runBlocking {
println("runBlocking start")
coroutineScope {
println("...start")
delay(3000)
println("...end")
}
println("runBlocking end")
}
}

执行结果:
runBlocking start
…start
…end
runBlocking end

3.挂起函数

前面我们看到的delay是一个挂起函数,如果我们需要自定义一个挂起函数(抽离代码),那么我们需要使用suspend,但是suspend只是标记了它是一个挂起函数,但是仅仅如此,在这个函数内,并无法获得协程作用域,比如就无法使用launch,

那么我们可以使用coroutineScope,coroutineScope也是一个挂起函数,并且它会继承外部协程作用域

1
2
3
4
5
private suspend fun printDot() = coroutineScope {
println("suspend...start")
delay(3000)
println("suspend...end")
}

调用挂起函数,printDot只能在协程中被调用,并且会阻塞外部协程

1
2
3
4
5
6
7
fun main() {
runBlocking {
println("runBlocking start")
printDot()
println("runBlocking end")
}
}

总结runBlocking和coroutineScope的区别?

借用郭霖的总结:coroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此是不

会造成任何性能上的问题的。而runBlocking函数由于会挂起外部线程,如果你恰好又在主线

程中当中调用它的话,那么就有可能会导致界面卡死的情况,所以不太推荐在实际项目中使

用。

4.取消协程

1
2
3
4
5
6
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
// 处理具体的逻辑
}
job.cancel()

当调用cancel后,会取消该协程作用域中的所有子协程

5.async await

使用async的await方法,可以获取协程执行的结果,当调用await方法时,如果async子协程内部的代码没执行完,会阻塞外部协程,本代码中,当async1调用await获取结果时,内部代码未执行完,所以async2的代码会被阻塞,所以async1和async2,它俩是一个串行执行,都执行完后,执行最后的打印语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
runBlocking {
val start = System.currentTimeMillis()
val async1 = async {
delay(1000)
5 + 5
}.await()
val async2 = async {
delay(1000)
5 + 5
}.await()
println("$async1 , $async2")
val end = System.currentTimeMillis()
println("time:${end - start}")
}

优化代码:串行改为并行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
runBlocking {
val start = System.currentTimeMillis()
val async1 = async {
delay(1000)
5 + 5
}.await()
val async2 = async {
delay(1000)
5 + 5
}.await()
println("$async1 , $async2")
val end = System.currentTimeMillis()
println("time:${end - start}")
}

6.协程简化回调

使用suspendCoroutine来简化回调,suspendCoroutine函数必须在协程作用域或挂起函数中才能调用,它会将当前协程立即挂起,去执行个Lambda的代码,会传入个Continuation参数,调用它的resume()方法或resumeWithException()可以让协程恢复执行。

1
2
3
4
5
6
7
8
9
10
11
12
suspend fun request(address: String): String {
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
continuation.resume(response)
}
override fun onError(e: Exception) {
continuation.resumeWithException(e)
}
})
}
}

调用request方法:

1
2
3
4
5
6
7
8
suspend fun getBaiduResponse() {
try {
val response = request("https://www.baidu.com/")
// 对服务器响应的数据进行处理
} catch (e: Exception) {
// 对异常情况进行处理
}
}

协程优化okhttp网络请求回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
suspend fun <T> Call<T>.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body != null) continuation.resume(body)
else continuation.resumeWithException(
RuntimeException("response body is null"))
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}

这里的await是对Call的一个扩展方法,调用时就简单多了

1
2
3
4
5
6
7
8
suspend fun getAppData() {
try {
val appList = ServiceCreator.create<AppService>().getAppData().await()
// 对服务器响应的数据进行处理
} catch (e: Exception) {
// 对异常情况进行处理
}
}

2.2实战案例

1.倒计时进入首页

1
2
3
4
5
6
7
8
9
10
//定义job管理协程
private val job by lazy { Job() }
private val splashDuration = 3 * 1000L //倒计时3秒

///创建协程,3秒后进入首页
CoroutineScope(job).launch {
delay(splashDuration)
MainActivity.start(this@SplashActivity)
finish()
}

别忘记在页面销毁时,取消job

1
2
3
4
override fun onDestroy() {
job.cancel()
super.onDestroy()
}

2. 每隔一秒显示当前时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private val job: Job by lazy { Job() }
private val sdf by lazy { SimpleDateFormat("HH:mm:ss") }
//创建协程
CoroutineScope(job).launch {
while (true) {
val time = sdf.format(Date())
withContext(Dispatchers.Main) {
mTvContent.text = "$time"
}
delay(1000)
}
}

//取消协程
override fun onDestroy() {
job.cancel()
super.onDestroy()
}

2.3综合案例:网络请求

本小节,我们将使用ViewModel,LiveData,kotlin协程,Retrofit演示一个网络请求

第一步:Retrofit单例模式封装

1
2
3
4
5
6
7
8
9
10
11
12
object ServiceCreator {
private const val BASE_URL = "https://api.caiyunapp.com/"

private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()

fun <T> create(clazz: Class<T>): T = retrofit.create(clazz)

inline fun <reified T> create(): T = create(T::class.java)
}

第二步:创建网络API接口

1
2
3
4
5
6
7
interface PlaceService {
/**
* 根据搜索关键词查询城市信息
*/
@GET("v2/place?token=${SunnyWeatherApplication.TOKEN}&lang=zh_CN")
suspend fun searchPlaces2(@Query("query") query:String):PlaceResponse
}

注意:这里使用suspend标识其是一个挂起函数,返回值是实体类

第三步:网络层,生成动态代理对象,执行网络请求,获取网络请求结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
object SunnyWeatherNetwork {

private val placeService = ServiceCreator.create<PlaceService>()
private val weatherService = ServiceCreator.create<WeatherService>()

//搜索地区
suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()

//搜索地区
suspend fun searchPlaces2(query: String) = placeService.searchPlaces2(query)

//获取每日天气
suspend fun getDailyWeather(lng: String, lat: String) = weatherService.getDailyWeather(lng, lat).await()

//获取未来几天天气
suspend fun getRealtimeWeather(lng: String, lat: String) = weatherService.getRealtimeWeather(lng, lat).await()

//从response中解析出body
private suspend fun <T> Call<T>.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body != null) {
continuation.resume(body)
} else {
continuation.resumeWithException(RuntimeException("response body is null"))
}
}

override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
}

第四步:仓库层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
object AppRepository {
/**
* 因为在android中,不允许在主线程发起网络调用,所以这里指定为 Dispatchers.IO
*/

fun searchPlaces2(query: String) = fire(Dispatchers.IO) {
Log.d("qwIvan", "searchPlaces2: $query")
//调用suspend方法searchPlaces
val placeResponse = SunnyWeatherNetwork.searchPlaces2(query)
if (placeResponse.status == "ok") {
val places = placeResponse.places
Result.success(places)
} else {
Result.failure(RuntimeException("response status is ${placeResponse.status}"))
}
}

//演示发起两个并行请求,最后获取串行结果
fun refreshWeather(lng:String,lat:String) = fire(Dispatchers.IO){
coroutineScope {
//发起并行的两个请求
val deferredRealtime = async {
SunnyWeatherNetwork.getRealtimeWeather(lng,lat)
}
val deferredDaily = async {
SunnyWeatherNetwork.getDailyWeather(lng,lat)
}
//获取串行结果
val realtimeResponse = deferredRealtime.await()
val dailyResponse = deferredDaily.await()
if (realtimeResponse.status == "ok" && dailyResponse.status == "ok"){
val weather = Weather(realtimeResponse.result.realtime,dailyResponse.result.daily)
Result.success(weather)
}else{
Result.failure( RuntimeException(
"realtime response status is ${realtimeResponse.status}" +
"daily response status is ${dailyResponse.status}"
))
}
}
}


/**
* 协程与 LiveData 一起使用:
* https://developer.android.google.cn/topic/libraries/architecture/coroutines#livedata
* 如果要异步使用livedata,那么可以使用liveData 构建器方法,在构建器方法中,去调用suspend方法,
* suspend方法返回livedata对象
*
*/
private fun <T> fire(context: CoroutineContext, block: suspend () -> Result<T>) =
liveData(context) {
var result = try {
block()
} catch (e: Exception) {
Result.failure<T>(e)
}
//emit相当于setValue()
emit(result)
}
}

第五步:ViewModel层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PlaceViewModel:ViewModel() {
private val searchLiveData = MutableLiveData<String>()
val placeLiveData = Transformations.switchMap(searchLiveData){
it -> AppRepository.searchPlaces2(it)//网络请求
}
val placeList = arrayListOf<Place>()

fun searchPlace(query:String){
searchLiveData.value = query
}

fun savePlace(place: Place) {
AppRepository.savePlace(place)
}

fun getSavedPlace():Place = AppRepository.getSavedPlace()

fun isPlaceSaved() = AppRepository.isPlaceSaved()
}

第六步:展示界面UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
viewModel.placeLiveData.observe(viewLifecycleOwner, Observer {
val places = it.getOrNull()
println("查询结束:" + (places?.size ?: 0))
if (places != null) {
recyclerView.isVisible = true
bgImageView.isVisible = false
viewModel.placeList.apply {
clear()
addAll(places)
}
adapter.notifyDataSetChanged()
} else {
Toast.makeText(activity, "未能查询到任何地点", Toast.LENGTH_SHORT).show()
it.exceptionOrNull()?.printStackTrace()
}
})

第三章 其他热门库

3.1 coil

kotlin协程图片加载库,

github地址:https://github.com/*coil*-kt/*coil*/