无悬浮窗权限实现全局Dialog

有些场景下需要显示一些提示弹窗,但把握不好弹出时机容易先弹出弹窗然后界面马上被杀掉进而看不到提示内容,例如强制下线:客户端退回登录界面并弹出提示弹窗。

如果是直接拿的栈顶activity去弹出,没有将弹窗逻辑写到具体activity中,或不好确定activty的变化就容易出现这种现象。

由于applicationContext没有AppWindowToken,所以dialog无法使用applicationContext创建,要么就使用windowManager配合WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY使用创建全局悬浮窗。但是这种做法需要申请权限。那么,在没有悬浮权限情况下如何做到让dialog不受栈顶activity变化的影响?

我的想法是通过application.registerActivityLifecycleCallbacks在activity变化时,关闭原来的弹窗,并重新创建一个一样的dialog并显示。

效果演示:

1. 栈顶界面被杀

2. 有新界面弹出

以下是代码实现:

kotlin 复制代码
/**
 * @Description 无需悬浮权限的全局弹窗,栈顶activity变化后通过反射重建,所以子类构造方法需无参
 */
open class BaseAppDialog<T : ViewModel>() : Dialog(topActivity!!.get()!!), ViewModelStoreOwner {

    companion object {
        private val TAG = BaseAppDialog::class.java.simpleName
        private var topActivity: WeakReference<Activity>? = null
        private val staticRestoreList = linkedMapOf<Class<*>, String>()
        private val staticViewModelStore: ViewModelStore = ViewModelStore()

        @JvmStatic
        fun init(application: Application) {
            application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
                override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                    topActivity = WeakReference(activity)
                }

                override fun onActivityStarted(activity: Activity) {

                }

                override fun onActivityResumed(activity: Activity) {
                    topActivity = WeakReference(activity)
                    val tempList = arrayListOf<BaseAppDialog<*>>()
                    val iterator = staticRestoreList.iterator()
                    while (iterator.hasNext()) {
                        val next = iterator.next()
                        val topName = (topActivity?.get() ?: "")::class.java.name
                        if (next.value != topName) {  //避免onCreate创建的弹窗重复弹出
                            //重新创建dialog
                            val newInstance = Class.forName(next.key.name).getConstructor().newInstance() as BaseAppDialog<*>
                            tempList.add(newInstance)
                            Log.e(TAG, "重新创建${next.key.name},于$topName")
                            iterator.remove()
                        }

                    }

                    tempList.forEach {
                        it.show()
                    }

                    if (staticRestoreList.size == 0) {
                        staticViewModelStore.clear()
                    }
                }

                override fun onActivityPaused(activity: Activity) {
                }

                override fun onActivityStopped(activity: Activity) {

                }

                override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
                }

                override fun onActivityDestroyed(activity: Activity) {
                }
            })
        }
    }


    var vm: T? = null

    init {
        val genericClass = getGenericClass()
        if (vm == null) {
            (genericClass as? Class<T>)?.let {
                vm = ViewModelProvider(this)[it]
            }
        }

        //activity暂停时关闭
        topActivity?.get()?.let {
            (it as LifecycleOwner).lifecycle.addObserver(object : DefaultLifecycleObserver {
                override fun onPause(owner: LifecycleOwner) {
                    super.onPause(owner)
                    dismissSilent()
                }
            })
        }
    }

    fun setSilentVm(viewModel: ViewModel?) {
        vm = viewModel as? T
    }


    //用于栈顶变化时的关闭
    private fun dismissSilent() {
        super.dismiss()
    }

    override fun show() {
        super.show()
        staticRestoreList.put(this::class.java, (topActivity?.get() ?: "")::class.java.name)
    }

    override fun dismiss() {
        super.dismiss()
        staticRestoreList.remove(this::class.java)
    }


    //获取泛型实际类型
    private fun getGenericClass(): Class<*>? {
        val superclass = javaClass.genericSuperclass
        if (superclass is ParameterizedType) {
            val actualTypeArguments: Array<Type>? = superclass.actualTypeArguments
            if (!actualTypeArguments.isNullOrEmpty()) {
                val type: Type = actualTypeArguments[0]
                if (type is Class<*>) {
                    return type
                }
            }
        }
        return ViewModel::class.java
    }


    //自己管理viewModel以便恢复数据
    override fun getViewModelStore(): ViewModelStore {
        return staticViewModelStore
    }
}

参数传递的话,直接通过修改dialog的viewmodel变量或调用其方法来实现。

kotlin 复制代码
class TipDialogVm : ViewModel() {
    val content = MutableLiveData<String>("")
}


class TipDialog2 : BaseAppDialog<TipDialogVm>() {

    var binding : DialogTip2Binding? = null

    init {
        binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.dialog_tip2, null, false)
        binding?.lifecycleOwner = context as? LifecycleOwner
        binding?.vm = vm
        setContentView(binding!!.root)

    }
}

弹出弹窗

kotlin 复制代码
TipDialog2().apply {
    vm?.content?.value = "嗨嗨嗨"
}.show()
相关推荐
Kapaseker1 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴2 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android