如何在悬浮窗中使用 Compose?

如何在悬浮窗中使用 Compose?

显示悬浮窗

声明悬浮窗权限。

XML 复制代码
<manifest>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
</manifest>

判断是否拥有显示权限。

Kotlin 复制代码
fun hasOverlayPermission(): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Settings.canDrawOverlays(weakReference.get())
    } else {
        // Android 23 以下,应用默认是有权限的。
        true
    }
}

打开悬浮窗显示权限设置页

Kotlin 复制代码
fun enterSettings(activity: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        activity.startActivity(
            Intent(
                Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                "package:${activity.packageName}".toUri()
            )
        )
    } else {
        // Android 23 以下,应用默认是有权限的,无需申请,也没有设置界面。
    }
}

获取 WindowManager

Kotlin 复制代码
val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

创建 WindowManager.LayoutParams

Kotlin 复制代码
val layoutParams = WindowManager.LayoutParams().apply {
    format = PixelFormat.RGBA_8888
    flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    gravity = Gravity.START or Gravity.TOP
    width = WindowManager.LayoutParams.MATCH_PARENT
    height = WindowManager.LayoutParams.MATCH_PARENT
    x = 0
    y = 0
    windowAnimations = android.R.style.Animation_Dialog
    // 根据API版本设置窗口类型,优先使用TYPE_APPLICATION_OVERLAY(API 26+),低版本使用TYPE_SYSTEM_ALERT
    type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    } else {
        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
    }
}

显示悬浮窗视图

Kotlin 复制代码
fun show() {
    if (isShowing) return
    if (!hasOverlayPermission()) {
        return
    }
    try {
        weakReference.get()?.let { context ->
            windowManager.addView(TextView(context).apply {
                text = "悬浮窗已经打开"
            }, layoutParams)
            isShowing = true
        }
    } catch (e: Exception) {
        isShowing = false
        e.printStackTrace()
    }
}

使用 Compose 显示有什么问题?

直接换用 Compose 显示会报错。

Kotlin 复制代码
windowManager.addView(ComposeView(context).apply {
    setContent {
        Text("悬浮窗已经打开")
    }
}, layoutParams)

会抛出 ViewTreeLifecycleOwner not found from androidx.compose.ui.platform.ComposeView 异常。

自定义 LifecycleOwner 来解决本问题。

MyLifecycleOwner

Kotlin 复制代码
inner class MyLifecycleOwner : SavedStateRegistryOwner, ViewModelStoreOwner {
    private val lifecycleRegistry = LifecycleRegistry(this)
    private val savedStateRegistryController = SavedStateRegistryController.create(this)
    private var view: View? = null

    @Volatile
    private var viewModelStoreInstance: ViewModelStore? = null

    override val lifecycle: Lifecycle
        get() = lifecycleRegistry

    override val savedStateRegistry: SavedStateRegistry
        get() = savedStateRegistryController.savedStateRegistry

    override val viewModelStore: ViewModelStore
        get() = viewModelStoreInstance ?: synchronized(this) {
            viewModelStoreInstance ?: ViewModelStore().also {
                viewModelStoreInstance = it
            }
        }

    fun performRestore(savedState: Bundle?) {
        savedStateRegistryController.performRestore(savedState)
    }

    fun onCreate() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
    }

    fun onStart() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
    }

    fun onResume() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
    }

    fun onPause() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    }

    fun onStop() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
    }

    fun onDestroy() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        // 手动清理 ViewModelStore
        viewModelStore.clear()
        view?.apply {
            setViewTreeLifecycleOwner(null)
            setViewTreeViewModelStoreOwner(null)
            setViewTreeSavedStateRegistryOwner(null)
        }
        view = null
    }

    fun attachToView(view: View) {
        // 需要为 Compose 设置 ViewTreeLifecycleOwner
        view.setViewTreeLifecycleOwner(this)
        view.setViewTreeViewModelStoreOwner(this)
        view.setViewTreeSavedStateRegistryOwner(this)
        this.view = view
    }
}

在悬浮窗中使用 Compose

Kotlin 复制代码
var lifecycleOwner = MyLifecycleOwner()

fun show() {
    if (isShowing) return
    if (!hasOverlayPermission()) {
        return
    }
    lifecycleOwner = MyLifecycleOwner()
    try {
        weakReference.get()?.let { context ->
            windowManager.addView(ComposeView(context).apply {
                // 绑定生命周期到 ComposeView
                lifecycleOwner.attachToView(this)
                setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
                lifecycleOwner.performRestore(null)
                lifecycleOwner.onCreate()
                lifecycleOwner.onStart()
                lifecycleOwner.onResume()
                setContent {
                    Text("悬浮窗已经打开")
                }
            }, layoutParams)
            isShowing = true
        }
    } catch (e: Exception) {
        lifecycleOwner.onPause()
        lifecycleOwner.onStop()
        lifecycleOwner.onDestroy()
        isShowing = false
        e.printStackTrace()
    }
}

关闭悬浮窗

Kotlin 复制代码
fun hide() {
    if (!isShowing) return
    try {
        lifecycleOwner.onPause()
        lifecycleOwner.onStop()
        windowManager.removeView(composeView)
        lifecycleOwner.onDestroy()
        isShowing = false
    } catch (e: Exception) {
        Log.e(TAG, "Error removing view: ${e.message}")
    }
}

总结

Compose 需要感知 ViewTreeLifecycleOwner。

在 androidx.activity:activity 依赖库中,androidx.activity.ComponentActivity 中同样为根 View 设置了 ViewTreeLifecycleOwner

androidx.activity.ComponentActivity#initializeViewTreeOwners

Kotlin 复制代码
open fun initializeViewTreeOwners() {
    window.decorView.setViewTreeLifecycleOwner(this)
    window.decorView.setViewTreeViewModelStoreOwner(this)
    window.decorView.setViewTreeSavedStateRegistryOwner(this)
    window.decorView.setViewTreeOnBackPressedDispatcherOwner(this)
    window.decorView.setViewTreeFullyDrawnReporterOwner(this)
}
Kotlin 复制代码
open class ComponentActivity() : androidx.core.app.ComponentActivity(),
    LifecycleOwner,
    ViewModelStoreOwner,
    SavedStateRegistryOwner {
}
相关推荐
XDMrWu5 小时前
Compose 智能重组:编译器视角下的黑科技
android·kotlin
vivo高启强5 小时前
R8 如何优化我们的代码(1) -- 减少类的加载
android·android studio
诺诺Okami6 小时前
Android Framework-WMS-从setContentView开始
android
前行的小黑炭8 小时前
Android :Compose如何监听生命周期?NavHostController和我们传统的Activity的任务栈有什么不同?
android·kotlin·app
Lei活在当下16 小时前
【业务场景架构实战】5. 使用 Flow 模式传递状态过程中的思考点
android·架构·android jetpack
前行的小黑炭18 小时前
Android 关于状态栏的内容:开启沉浸式页面内容被状态栏遮盖;状态栏暗亮色设置;
android·kotlin·app
用户091 天前
Flutter构建速度深度优化指南
android·flutter·ios
PenguinLetsGo1 天前
关于「幽灵调用」一事第三弹:完结?
android
雨白1 天前
Android 多线程:理解 Handler 与 Looper 机制
android