HOME. ANGDO ❤ VENKY.

0%

Kotlin协程改造Activity生命周期

从开始Android起一直有个想法,如果把生命周期回调中执行的代码全部放到第二线程中执行多好。刚好Kotlin 1.3已经发布了有一阵了,协程也正式转正,于是便打算用协程实现这个想法。

创建协程Context

首先我们创建一个协程Context,让代码在一个单一的线程中执行。所以我们开始面向Google编程发现了一个newSingleThreadContext方法,因为要全局唯一所以我们创建下面这样一个object类来存放Context。

等等,这里为什么会有一个Warn,作为一个优秀的码农,怎么可以容忍Warn的产生呢。让我们来瞧瞧。This declaration is experimental???说好的协程转正呢?让我们点进去看下到底怎么了。

于是我们来到了这个Issue,我们只是通过协程将UI线程代码在新的执行线程执行,所以我们可以按照提到的内容用Executors.newSingleThreadExecutor().toCoroutineDispatcher()来替换。所以我们的代码是这样的。

1
2
3
internal object LifeDispatcher {
val MAIN = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
}

修改Activity生命周期回调

接下来给Activity的onCreate()中添加onInitRoot()用来设置给Activity设置布局内容,onStart()中添加onInitView()进行各种子View的设置。

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

final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch(LifeDispatcher.MAIN) {
onInitRoot()
}
}

final override fun onStart() {
super.onStart()
GlobalScope.launch(LifeDispatcher.MAIN) {
onInitView()
}
}

abstract suspend fun onInitRoot()
abstract suspend fun onInitView()
}

加上println打印方法的执行顺序,用delay()onInitRoot()、``onInitView()模拟负载。但实际上delay()`只能挂起协程不会阻塞线程。

1
2
3
4
5
6
7
8
I/System.out: onCreate start main
I/System.out: onCreate return
I/System.out: onInitRoot start pool-1-thread-1
I/System.out: onStart start main
I/System.out: onStart return
I/System.out: onInitView start pool-1-thread-1
I/System.out: onInitView return
I/System.out: onInitRoot return

可以看到onCreateonStart没有阻塞就执行完了,但是onInitViewonInitRoot执行时就开始执行了,如果onInitView中写一些对子View的操作明显会得到一个NullPointerException

保证自定义周期顺序执行

让我们浏览Kotlin协程文档,于是发现了一个叫做runBlocking()的函数可以阻塞所在的线程。所以我们加上runBlocking()试试。

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

final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch(LifeDispatcher.MAIN) {
runBlocking {
onInitRoot()
}
}
}

final override fun onStart() {
super.onStart()
GlobalScope.launch(LifeDispatcher.MAIN) {
runBlocking {
onInitView()
}
}
}
......
}

加上println打印方法的执行顺序,用delay()模拟负载。

1
2
3
4
5
6
7
8
I/System.out: onCreate start main
I/System.out: onCreate return
I/System.out: onInitRoot start pool-1-thread-1
I/System.out: onStart start main
I/System.out: onStart return
I/System.out: onInitRoot return
I/System.out: onInitView start pool-1-thread-1
I/System.out: onInitView return

可以看到onInitRoot()onInitView()按照先后顺序执行了。毕竟只给了一个线程用来执行协程,onInitRoot()的执行block了线程,当然得按先后顺序来了。

测试

我们来创建一个MainActivity给他它设置一个带按钮的布局,点击之后再打开一个MainActivity

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

override suspend fun onInitRoot() {
delay(100)
setContentView(R.layout.activity_main)
}

override suspend fun onInitView() {
btn_next.setOnClickListener {
btn_next.text = "Clicked!"
startActivity(Intent(context(), MainActivity::class.java))
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/btn_next"
android:text="NEXT"
android:textSize="72sp"
android:gravity="center"
android:layout_gravity="center"
android:layout_width="200dp"
android:layout_height="120dp"
android:textColor="#FFFFFF"
android:background="#786598"/>
</FrameLayout>

来看下运行结果。

点击之后会改变按钮文字并打开一个新页面。
嗯,坑我就挖下了。