无悬浮窗权限实现全局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()
相关推荐
后端码匠6 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
梓仁沐白8 小时前
Android清单文件
android
董可伦10 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空10 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭11 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
flying robot12 小时前
小结:Android系统架构
android·系统架构
xiaogai_gai12 小时前
有效的聚水潭数据集成到MySQL案例
android·数据库·mysql
鹅鹅鹅呢13 小时前
mysql 登录报错:ERROR 1045(28000):Access denied for user ‘root‘@‘localhost‘ (using password Yes)
android·数据库·mysql
在人间负债^13 小时前
假装自己是个小白 ---- 重新认识MySQL
android·数据库·mysql
Unity官方开发者社区13 小时前
Android App View——团结引擎车机版实现安卓应用原生嵌入 3D 开发场景
android·3d·团结引擎1.5·团结引擎车机版