You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 21:00:03