Android 异步UI之二
2021-10-19 本文已影响0人
折剑游侠
前面写了下Android异步UI,简单来说就是在子线程中通过WindowManager.addView()
的方式创建UI,那么UI的绘制流程就执行在这个子线程中。又众所周知ViewRootImpl.checkThread()
检查更新UI的线程是否是创建UI的线程,那么在创建UI的这个子线程中我们可以任意操作UI。
要维护一个子线程,当然是选择HandlerThread
啊,但是像前面一样写样板代码也太难受了。封装一下这套逻辑,调用时最好能一行代码搞定,下面开撸。
AsyncHandlerThread
abstract class AsyncHandlerThread @JvmOverloads constructor(
private val asyncActivity: WeakReference<FragmentActivity>,
name: String = "AsyncHandlerThread"
) :
HandlerThread(name), LifecycleEventObserver {
private var handler: H? = null
fun inflate() {
start()
handler = H(looper, asyncActivity)
handler?.sendEmptyMessage(INFLATE_UI)
asyncActivity.get()?.lifecycle?.addObserver(this)
}
open fun getLayoutId() = R.layout.common_view_empty
abstract fun onInflated(root: View?)
abstract fun onFinish()
private inner class H(
looper: Looper,
private val asyncActivity: WeakReference<FragmentActivity>
) :
Handler(looper) {
var root: View? = null
var wm: WindowManager? = null
override fun handleMessage(msg: Message) {
when (msg.what) {
INFLATE_UI -> {
root = LayoutInflater.from(asyncActivity.get()).inflate(getLayoutId(), null)
wm = asyncActivity.get()
?.getSystemService(Context.WINDOW_SERVICE) as WindowManager?
val param = WindowManager.LayoutParams()
param.width = WindowManager.LayoutParams.MATCH_PARENT
param.height = WindowManager.LayoutParams.MATCH_PARENT
wm?.addView(root, param)
//处理返回键
root?.run {
isFocusable = true
isFocusableInTouchMode = true
requestFocus()
setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
onStop()
true
} else {
false
}
}
}
onInflated(root)
}
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
quitSafely()
onFinish()
}
}
open fun onStop() {
handler?.run {
removeCallbacksAndMessages(null)
wm?.removeViewImmediate(root)
root = null
wm = null
}
handler = null
asyncActivity.get()?.finish()
}
companion object {
private const val INFLATE_UI = 10001
}
}
对外提供inflate()方法,启动HandlerThread并创建UI。实现LifecycleEventObserver接口,在onDestroy()时释放资源。
更新UI需要在这个子线程中进行,封装下线程切换方法。
fun post(block: () -> Unit) {
handler?.post {
block()
}
}
fun postDelay(mills: Long, block: () -> Unit) {
handler?.postDelayed({
block()
}, mills)
}
基类写好了,但是每次都去写实现类也很麻烦啊,没关系,kotlin invoke约定。
AsyncView
object AsyncView {
operator fun invoke(
activity: FragmentActivity,
layout: Int? = null,
finish: (() -> Unit)? = null,
block: AsyncHandlerThread.() -> Unit
) = object : AsyncHandlerThread(WeakReference(activity)) {
override fun getLayoutId(): Int {
return layout ?: super.getLayoutId()
}
override fun onInflated(root: View?) {
block()
}
override fun onFinish() {
finish?.invoke()
}
}.apply { inflate() }
}
如此一来调用处就很简洁了
class AsyncActivity : BaseActivity() {
private var async: AsyncHandlerThread? = null
var tv: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
async = AsyncView(this@AsyncActivity, R.layout.business_activity_async) {
}
}
}
但是这个lambda内部this指向AsyncHandlerThread,通过这个AsyncHandlerThread点handler点root去findViewById也太麻烦了,给AsyncHandlerThread加个bindView()方法:
fun <T : View> bindView(id: Int): T? {
return handler?.root?.findViewById(id) as T?
}
那么完整版的AsyncHandlerThread
abstract class AsyncHandlerThread @JvmOverloads constructor(
private val asyncActivity: WeakReference<FragmentActivity>,
name: String = "AsyncHandlerThread"
) :
HandlerThread(name), LifecycleEventObserver {
private var handler: H? = null
fun inflate() {
start()
handler = H(looper, asyncActivity)
handler?.sendEmptyMessage(INFLATE_UI)
asyncActivity.get()?.lifecycle?.addObserver(this)
}
fun <T : View> bindView(id: Int): T? {
return handler?.root?.findViewById(id) as T?
}
fun post(block: () -> Unit) {
handler?.post {
block()
}
}
fun postDelay(mills: Long, block: () -> Unit) {
handler?.postDelayed({
block()
}, mills)
}
open fun getLayoutId() = R.layout.common_view_empty
abstract fun onInflated(root: View?)
abstract fun onFinish()
private inner class H(
looper: Looper,
private val asyncActivity: WeakReference<FragmentActivity>
) :
Handler(looper) {
var root: View? = null
var wm: WindowManager? = null
override fun handleMessage(msg: Message) {
when (msg.what) {
INFLATE_UI -> {
root = LayoutInflater.from(asyncActivity.get()).inflate(getLayoutId(), null)
wm = asyncActivity.get()
?.getSystemService(Context.WINDOW_SERVICE) as WindowManager?
val param = WindowManager.LayoutParams()
param.width = WindowManager.LayoutParams.MATCH_PARENT
param.height = WindowManager.LayoutParams.MATCH_PARENT
wm?.addView(root, param)
root?.run {
isFocusable = true
isFocusableInTouchMode = true
requestFocus()
setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
onStop()
true
} else {
false
}
}
}
onInflated(root)
}
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
quitSafely()
onFinish()
}
}
open fun onStop() {
handler?.run {
removeCallbacksAndMessages(null)
wm?.removeViewImmediate(root)
root = null
wm = null
}
handler = null
asyncActivity.get()?.finish()
}
companion object {
private const val INFLATE_UI = 10001
}
}
再糖一下,去掉this,给FragmentActivity加个扩展函数
fun FragmentActivity.async(
layout: Int? = null,
finish: (() -> Unit)? = null,
block: AsyncHandlerThread.() -> Unit
) = AsyncView(this, layout, finish, block)
调用处
class AsyncActivity : BaseActivity() {
private var async: AsyncHandlerThread? = null
var tv: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
async = async(R.layout.business_activity_async) {
tv = bindView(R.id.tvAsync)
tv?.text = "chenxuan"
tv?.setSingleClick {
tv?.text = "chenxuan"
}
}
async?.postDelay(3000) {
tv?.text = "async"
}
}
}
最终做到了一行代码调用AsyncView(){}
。但是在主线程中操作UI还是得通过AsyncHandlerThread的handler.post()切换到子线程中执行,理由嘛就等同于在子线程中操作UI需要post()到主线程中,只不过异步UI中主线程和子线程反过来了。