多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Android悬浮窗权限在RK平台的适配与优化

Android悬浮窗权限在RK平台的适配与优化 1. 项目背景与需求解析在RK平台基于Android 15系统进行定制开发时我们经常需要处理各种特殊权限的适配问题。其中Allow display over other apps在其他应用上层显示权限是许多悬浮窗类应用必须获取的关键权限。这个权限在Android系统中属于危险权限Dangerous Permission范畴需要开发者显式声明并在运行时动态申请。1.1 权限的技术本质android.permission.SYSTEM_ALERT_WINDOW对应用户可见的Allow display over other apps允许应用创建系统级窗口这类窗口会显示在其他所有常规应用和系统界面之上。典型的应用场景包括全局悬浮球工具如辅助功能按钮屏幕录制应用的录制提示框即时通讯应用的聊天头Chat Heads系统性能监控悬浮窗在标准Android实现中从Android 6.0API 23开始这个权限就被移入了危险权限列表应用必须在AndroidManifest.xml中声明在运行时向用户请求授权处理用户拒绝授权的情况1.2 RK平台的特性挑战RKRockchip平台在Android系统实现上有一些特殊的处理权限白名单机制部分RK定制系统会对系统级权限做额外限制窗口管理器修改RK的SurfaceFlinger可能有定制逻辑默认权限策略相比AOSP可能有更严格的默认拒绝策略我们在RK3588开发板上实测发现即使正确实现了标准Android的权限请求流程部分应用仍然无法正常获取SYSTEM_ALERT_WINDOW权限。这通常表现为权限请求对话框不弹出用户授权后仍然无法显示悬浮窗特定分辨率下窗口显示异常2. 完整实现方案2.1 基础权限声明首先确保AndroidManifest.xml中包含正确的权限声明uses-permission android:nameandroid.permission.SYSTEM_ALERT_WINDOW/ uses-permission android:nameandroid.permission.FOREGROUND_SERVICE/ !-- 针对Android 10需要额外声明 -- uses-permission android:nameandroid.permission.USE_FULL_SCREEN_INTENT/注意从Android 10开始如果应用需要在前台服务中显示悬浮窗必须同时申请FOREGROUND_SERVICE权限。2.2 运行时权限请求标准的权限请求代码如下fun requestOverlayPermission(activity: Activity) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(activity)) { val intent Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(package:${activity.packageName}) ) activity.startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE) } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode OVERLAY_PERMISSION_REQ_CODE) { if (Settings.canDrawOverlays(this)) { // 权限已授予 showFloatingWindow() } else { // 处理拒绝情况 showPermissionDeniedDialog() } } }2.3 RK平台特殊适配针对RK平台的特殊情况我们需要额外处理检查RK系统设置白名单fun isInRKPkgWhiteList(context: Context): Boolean { return try { val contentResolver context.contentResolver Settings.Secure.getInt(contentResolver, rk_overlay_whitelist_${context.packageName}) 1 } catch (e: Exception) { false } }添加RK专用请求路径fun requestRKOverlayPermission(activity: Activity) { if (isInRKPkgWhiteList(activity)) return try { val intent Intent().apply { setClassName( com.android.settings, com.android.settings.rk.RKOverlayPermissionActivity ) putExtra(package, activity.packageName) } activity.startActivityForResult(intent, RK_OVERLAY_PERMISSION_REQ_CODE) } catch (e: Exception) { // 回退到标准请求流程 requestOverlayPermission(activity) } }窗口类型适配WindowManager.LayoutParams params new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); // RK平台特殊参数 if (isRKPlatform()) { params.privateFlags | 0x00004000; // RK自定义标志位 }3. 常见问题与解决方案3.1 权限请求不弹出现象调用Settings.ACTION_MANAGE_OVERLAY_PERMISSION后无反应排查步骤检查是否已在Manifest声明权限验证Intent是否被拦截intent.resolveActivity(packageManager)?.let { // Intent可正常处理 } ?: run { // 无应用能处理此Intent }检查RK系统设置中的特殊应用权限是否被禁用解决方案添加备用请求路径val intent if (isRKPlatform()) { Intent(com.rk.settings.ACTION_OVERLAY_PERMISSION).apply { putExtra(package, packageName) } } else { // 标准Intent }3.2 授权后仍无法显示可能原因窗口类型不匹配RK系统有额外的限制策略应用处于后台时被限制验证方法adb shell dumpsys window windows | grep -E Window|mCurrentFocus解决方案添加前台服务通知val service Notification.Builder(this, CHANNEL_ID) .setContentTitle(悬浮窗运行中) .setSmallIcon(R.drawable.ic_notification) .build() startForeground(1, service)检查RK电源管理白名单fun addToRKPwrWhiteList() { if (isRKPlatform()) { val intent Intent(com.rk.powerwhitelist.ADD) intent.putExtra(package, packageName) sendBroadcast(intent) } }3.3 窗口显示异常典型表现悬浮窗位置偏移触摸事件不响应窗口尺寸异常调试方法检查布局参数params.gravity Gravity.START | Gravity.TOP; params.x 100; // 明确指定坐标 params.y 200;验证DisplayMetricsval metrics Resources.getSystem().displayMetrics Log.d(Overlay, Density: ${metrics.density}, Width: ${metrics.widthPixels})RK平台特殊处理if (isRKPlatform()) { params.flags | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; }4. 高级技巧与优化4.1 权限状态监听常规Android系统不提供权限变化的广播但可以通过以下方式监听private val overlayObserver ContentObserver(Handler(Looper.getMainLooper())) { checkOverlayPermission() } fun registerPermissionObserver() { contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.OVERLAY_DISPLAY_APPROVED), false, overlayObserver ) } private fun checkOverlayPermission() { val hasPermission Settings.canDrawOverlays(this) // 更新UI状态 }4.2 多窗口类型适配针对不同场景使用合适的窗口类型窗口类型适用场景所需权限TYPE_APPLICATION_OVERLAY常规悬浮窗SYSTEM_ALERT_WINDOWTYPE_ACCESSIBILITY_OVERLAY辅助功能应用ACCESSIBILITY_SERVICETYPE_TOAST简单提示无但有严格限制TYPE_PHONE来电显示SYSTEM_ALERT_WINDOW4.3 RK平台性能优化SurfaceFlinger调优// 在WindowManager.LayoutParams中设置 params.privateFlags | 0x00000040; // RK硬件合成标志内存管理fun optimizeRKMemory() { if (isRKPlatform()) { SystemProperties.set(sys.rk.window.optimize, true) } }GPU加速!-- 在AndroidManifest中添加 -- meta-data android:nameandroid.hardware.graphics.composer android:valuerk3568 /5. 测试验证方案5.1 自动化测试脚本import uiautomator2 as u2 def test_overlay_permission(): d u2.connect() d.app_start(com.example.myapp) # 触发权限请求 d(resourceIdcom.example.myapp:id/btn_request).click() # 验证权限对话框 if d(text允许显示在其他应用的上层).exists: d(text允许).click() assert d(text悬浮窗已显示).exists else: print(权限请求未弹出) check_rk_special_settings()5.2 ADB调试命令# 检查当前授予的悬浮窗权限 adb shell appops get package_name SYSTEM_ALERT_WINDOW # 模拟授权仅调试版本可用 adb shell appops set package_name SYSTEM_ALERT_WINDOW allow # RK专用调试命令 adb shell getprop | grep rk.window5.3 兼容性测试矩阵需要覆盖的测试场景测试项标准AndroidRK Android11RK Android15首次请求流程✅✅需特殊处理✅需特殊处理权限拒绝后恢复✅❌需额外代码❌需额外代码重启后权限保持✅✅✅多窗口交互✅❌部分异常✅横竖屏切换✅✅✅低内存场景✅❌可能崩溃✅6. 安全与隐私考量实现悬浮窗功能时需特别注意敏感信息保护避免在悬浮窗中显示密码等敏感信息对截屏功能做特殊处理getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);输入安全params.flags WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;RK平台特有安全策略fun checkRKSecurityPolicy() { try { val clazz Class.forName(com.rk.security.RKPermissionManager) val method clazz.getMethod(checkOverlaySecurity, String::class.java) val result method.invoke(null, packageName) as Boolean if (!result) { showSecurityWarning() } } catch (e: Exception) { // 忽略非RK平台 } }在实际项目中我们发现RK平台对TYPE_SYSTEM_ALERT窗口的内存管理更为激进建议在onTrimMemory()中做好资源释放override fun onTrimMemory(level: Int) { if (level ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // 释放非必要资源但保持窗口 optimizeWindowResources() } } private fun optimizeWindowResources() { // RK平台建议保留至少128KB的图形内存 if (isRKPlatform()) { textureView?.setBufferSize(128 * 1024) } }
返回列表