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中主线程和子线程反过来了。

上一篇下一篇

猜你喜欢

热点阅读