createWindowContext创建失败:未使用上下文过多的解决方案咨询
createWindowContext failed! Too many unused window contexts崩溃问题 问题背景
收到如下崩溃报错:
java.lang.UnsupportedOperationException: createWindowContext failed! Too many unused window contexts. Please see Context createWindowContext documentation for detail.
查阅Context#createWindowContext文档说明:
创建窗口上下文是一项开销较大的操作。误用该API可能导致性能大幅下降。最佳实践是尽可能复用同一窗口上下文,即针对特定窗口类型和显示器创建一个窗口上下文,并在所有需要的地方复用。
业务场景是:应用退至后台时启动服务,服务通过createWindowContext获取上下文显示悬浮窗;短时间内多次启停应用时,每次启动服务都调用createWindowContext,最终触发该崩溃。
目前尝试过将创建好的windowContext存储在服务的companion object中,测试可行但担心存在隐患,也考虑过用WeakReference在companion object中存储windowContext。
现有方案的隐患
- 直接存在companion object(静态单例):如果服务被销毁但静态引用未释放,会导致Context内存泄漏,长期占用系统资源,甚至引发其他内存相关问题。
- WeakReference方案:虽能避免内存泄漏,但如果GC回收了WeakReference中的Context,下次使用时需要重新创建,仍可能遇到短时间多次创建的问题,还需额外处理空值逻辑。
更优解决方案
1. 服务内复用+主动释放
窗口上下文是和服务绑定用于显示悬浮窗的,最佳方式是在服务内部维护实例,服务生命周期内复用,销毁时主动释放资源:
- 在服务类中用成员变量存储windowContext,而非静态companion object
- 仅在
onCreate中创建一次,后续直接复用 onDestroy中移除悬浮窗后,将windowContext置为null,允许GC回收
优化后的示例代码:
class FloatingWindowService : Service() { private var windowContext: Context? = null private var floatingView: View? = null override fun onCreate() { super.onCreate() // 仅首次创建服务时初始化windowContext windowContext = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { displayContext().createWindowContext( WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 替换为你的WindowType null ) } else { this } // 初始化悬浮窗ComposeView floatingView = ComposeView(windowContext!!).apply { // 填充你的Compose内容 } // 添加到WindowManager windowManager.addView(floatingView, layoutParams) } override fun onDestroy() { super.onDestroy() // 移除悬浮窗 floatingView?.let { windowManager.removeView(it) } // 释放上下文及View引用 windowContext = null floatingView = null } private fun Context.displayContext(): Context { val manager = getSystemService(DISPLAY_SERVICE) as DisplayManager val display = manager.getDisplay(Display.DEFAULT_DISPLAY) return createDisplayContext(display) } // ... 其他服务逻辑 }
2. 全局单例管理(带生命周期感知)
如果多个组件需要复用同一窗口上下文,可以创建全局单例管理器,配合Application生命周期管理Context的创建与释放:
object WindowContextManager { private var windowContext: Context? = null fun getWindowContext(baseContext: Context): Context { // 检查上下文是否有效,无效则重新创建 if (windowContext == null || windowContext!!.applicationContext !== baseContext.applicationContext) { windowContext = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { baseContext.displayContext().createWindowContext( WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null ) } else { baseContext } } return windowContext!! } // 全局释放上下文 fun release() { windowContext = null } private fun Context.displayContext(): Context { val manager = getSystemService(DISPLAY_SERVICE) as DisplayManager val display = manager.getDisplay(Display.DEFAULT_DISPLAY) return createDisplayContext(display) } }
在Application的onTerminate方法中调用WindowContextManager.release(),或者在所有使用该上下文的组件销毁时统一执行释放操作。
3. WeakReference折中方案
如果担心全局单例内存泄漏,又想复用上下文,可以用WeakReference存储,获取时检查有效性,无效则重新创建:
object WindowContextManager { private var windowContextRef: WeakReference<Context>? = null fun getWindowContext(baseContext: Context): Context { val existingContext = windowContextRef?.get() return if (existingContext != null && existingContext.applicationContext === baseContext.applicationContext) { existingContext } else { val newContext = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { baseContext.displayContext().createWindowContext( WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null ) } else { baseContext } windowContextRef = WeakReference(newContext) newContext } } }
这种方式既能复用上下文,又能在无引用时被GC回收,只要应用进程未被杀死,WeakReference中的Context大概率不会被回收,能有效避免短时间多次创建的问题。
关键注意事项
- 必须确保窗口上下文和悬浮窗View一起释放,避免内存泄漏
- 窗口上下文与特定Display、WindowType绑定,若业务支持多显示器或不同窗口类型,需按类型缓存实例,而非单一实例
- 仅Android R及以上版本需要使用
createWindowContext,低版本直接使用服务自身上下文即可
内容的提问来源于stack exchange,提问作者zoltish

