从开始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
|
可以看到onCreate
和onStart
没有阻塞就执行完了,但是onInitView
在onInitRoot
执行时就开始执行了,如果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>
|
来看下运行结果。
点击之后会改变按钮文字并打开一个新页面。
嗯,坑我就挖下了。