Android如何在WhatsApp启动时通过无障碍服务显示透明摄像头悬浮层
可行实现方案
现有代码问题排查
- 布局参数类型错误:
TYPE_SYSTEM_OVERLAY仅支持系统应用使用,第三方应用在Android 8.0及以上版本需使用TYPE_APPLICATION_OVERLAY作为悬浮窗类型 - 重复添加View风险:每次无障碍事件触发都会调用
addView,重复添加会直接抛出异常导致崩溃 - 摄像头预览未配置透明属性:默认摄像头预览组件无alpha通道支持,无法实现透明效果
- 缺少摄像头权限校验:运行时未校验CAMERA权限,会导致预览初始化失败
具体实现步骤
1. 权限补充
首先在AndroidManifest.xml中添加必要权限:
<!-- 摄像头权限 --> <uses-permission android:name="android.permission.CAMERA" /> <!-- 悬浮窗权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 无障碍服务权限 --> <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
使用前需提前动态申请CAMERA权限,确保摄像头可以正常初始化。
2. 修正透明悬浮窗配置
修改WindowManager布局参数,适配高版本系统,同时避免拦截下层应用触摸操作:
val params = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams( // 调整悬浮窗大小,可按需求设置为全屏或指定尺寸 WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 不获取焦点、不响应触摸、布局覆盖全屏 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ) } else { // 低版本兼容 WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ) }
3. 实现透明摄像头预览
使用CameraX+TextureView实现透明预览,TextureView本身支持alpha调整,可直接设置透明度实现效果:
首先创建透明预览布局layout_camera_overlay.xml:
<?xml version="1.0" encoding="utf-8"?> <TextureView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/previewTexture" android:layout_width="match_parent" android:layout_height="match_parent" android:alpha="0.3" /> <!-- 调整透明度实现你需要的透明效果 -->
4. 完整修正后的AccessibilityService代码
class CameraAccessibilityService : AccessibilityService() { val tinyDB: TinyDB by inject() private var overlayView: View? = null private var wm: WindowManager? = null private var cameraProvider: ProcessCameraProvider? = null override fun onInterrupt() {} override fun onAccessibilityEvent(event: AccessibilityEvent?) { event ?: return val eventType = event.eventType if (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { return } val pkgName = event.packageName?.toString() ?: return val isWhatsApp = pkgName == Constants.WHATSAPP_PKG_NAME || pkgName == Constants.WHATSAPP_BUSINESS_PKG_NAME val isOverlayEnabled = tinyDB.getBoolean(Constants.isCameraOverLayEnabled) if (isWhatsApp && isOverlayEnabled) { // 避免重复添加悬浮窗 if (overlayView != null && overlayView?.isAttachedToWindow == true) { return } // 校验摄像头权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "请先授予摄像头权限", Toast.LENGTH_SHORT).show() return } initOverlay() startCameraPreview() } else { // 离开WhatsApp时移除悬浮窗释放摄像头 releaseOverlay() } } private fun initOverlay() { wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager overlayView = LayoutInflater.from(this).inflate(R.layout.layout_camera_overlay, null) val params = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ) } else { WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ) } wm?.addView(overlayView, params) } private fun startCameraPreview() { val textureView = overlayView?.findViewById<TextureView>(R.id.previewTexture) ?: return val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ cameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build() val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA // 按需切换前后置 preview.setSurfaceProvider(textureView.surfaceProvider) try { cameraProvider?.unbindAll() cameraProvider?.bindToLifecycle(this, cameraSelector, preview) } catch (e: Exception) { e.printStackTrace() } }, ContextCompat.getMainExecutor(this)) } private fun releaseOverlay() { cameraProvider?.unbindAll() cameraProvider = null overlayView?.let { if (it.isAttachedToWindow) { wm?.removeView(it) } } overlayView = null wm = null } override fun onServiceConnected() { this.serviceInfo.apply { eventTypes = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED or AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED packageNames = arrayOf(Constants.WHATSAPP_PKG_NAME, Constants.WHATSAPP_BUSINESS_PKG_NAME) feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK notificationTimeout = 100 } } override fun onDestroy() { super.onDestroy() releaseOverlay() Log.d(TAG, "Save Status CameraAccessibilityService Destroyed") } }
效果调整说明
- 调整
TextureView的alpha属性值,范围0~1,数值越小越透明,可按需调整到你需要的效果 - 如果需要调整悬浮窗大小,修改LayoutParams的宽高参数即可
- 可按需切换前置/后置摄像头,修改
CameraSelector即可
内容的提问来源于stack exchange,提问作者User
相关产品推荐
相关产品推荐

