In some scenarios, it is necessary to display some prompt pop-up windows, but if you do not grasp the pop-up timing well, the pop-up window will pop up first and then the interface will be killed immediately and the prompt content will not be visible. For example, forced offline: the client returns to the login interface and a prompt pop-up window will pop up.
Author: Abin Link: https://juejin.cn/post/7295576843653087266
This phenomenon is likely to occur if the activity on the top of the stack is directly used to pop up, without writing the pop-up logic into the specific activity, or if it is difficult to determine the change in activity.
Since applicationContext does not have AppWindowToken
, the dialog cannot be created using applicationContext, or windowManager must be used with WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
to create a global floating window. But this approach requires applying for permission. So, how to prevent the dialog from being affected by changes in the activity on the top of the stack without suspending permission?
My idea is to use application.registerActivityLifecycleCallbacks
to close the original pop-up window when the activity changes, and recreate the same dialog and display it.
Effect demonstration:
1. The top interface of the stack is killed
2. A new interface pops up
The following is the code implementation:
/** * @Description A global pop-up window that does not require floating permissions. After the activity on the top of the stack changes, it is rebuilt through reflection, so the subclass construction method requires no parameters. */ 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<*>, Boolean>() //Second parameter: whether to temporarily close 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 == true) { //Avoid pop-up windows created by onCreate from popping up repeatedly val newInstance = Class.forName(next.key.name).getConstructor().newInstance() as BaseAppDialog<*> tempList.add(newInstance) Log.e(TAG, "Recreate ${next.key.name} at $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] } } topActivity?.get()?.let { (it as LifecycleOwner).lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onPause(owner: LifecycleOwner) { super.onPause(owner) dismissSilent() } }) } } // Used to close when the top of the stack changes private fun dismissSilent() { super.dismiss() staticRestoreList.replace(this::class.java, true) } override fun show() { super.show() staticRestoreList.put(this::class.java, false) } override fun dismiss() { super.dismiss() staticRestoreList.remove(this::class.java) } //Get the actual type of the generic 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 } //Manage the viewModel yourself to restore data override fun getViewModelStore(): ViewModelStore { return staticViewModelStore } }
If parameters are passed, it is implemented directly by modifying the viewmodel variable of the dialog or calling its method.
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) } }
Pop-up window
TipDialog2().apply { vm?.content?.value = "Hi hi hi hi" }.show()
Follow me to get more knowledge or contribute articles