通过无障碍服务(AccessibilityService)实现Android设备全局水印显示

一、无障碍功能简介

首先我们先来了解下无障碍功能的官方介绍:

无障碍服务仅应用于帮助残障用户使用 Android 设备和应用。它们在后台运行,并在触发 AccessibilityEvents 时接收系统的回调。此类事件表示用户界面中的某些状态转换,例如焦点已更改、按钮已被点击等。此类服务可以选择性地请求查询活动窗口内容的功能。开发无障碍服务需要扩展此类并实现其抽象方法。

从以上介绍我们得知可以通过这个系统服务,添加全局的活动窗口,并对窗口状态做监听。那么我们今天就借助系统的无障碍服务来实现的设备全局水印效果,具体的需求内容是添加一个全局的文字透明层,同时确保不抢占页面焦点,并允许用户在其他应用上继续操作界面。说白了,我们要实现的全局水印效果就是实现一个顶层的透明层VIEW,只显示出来并不影响应用用户设备操作,同时给设备加水印,防止设备具体内容被剽窃,以保证达到设备安全的目的。

二、设备水印功能实现

在内容实现之前,我们先来了解一下几个Window窗口相关的属性。

窗口类型:WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY ;这种类型的窗口专门为无障碍服务设计,能覆盖在所有应用之上而不会影响用户操作。

窗口标志:WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;让窗口永远不会获得按键输入焦点,因此用户无法向其发送按键或其他按钮事件。

窗口标志:WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;让窗口永远不能接收触摸事件。此标志的目的是让位于此窗口下方的某个窗口(按 Z 顺序)来处理触摸。

窗口标志:WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;该标志可以将窗口放置在整个屏幕内,忽略来自父窗口的任何限制。

1、 创建无障碍服务

首先创建一个无障碍服务(Accessibility Service),该服务将用于显示全局的透明水印文字层。

Kotlin 复制代码
class CustomOverlayService: AccessibilityService() {

    private lateinit var overlayWindowManager: WindowManager

    private var overlayView: View? = null

    override fun onServiceConnected() {
        super.onServiceConnected()

        // 初始化WindowManager
        overlayWindowManager = getSystemService(WINDOW_SERVICE) as WindowManager
        // 加载布局
        overlayView = LayoutInflater.from(this).inflate(R.layout.layout_watermark, null)
        // 设置布局参数
        val params: WindowManager.LayoutParams = WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,  // 无障碍服务专用类型
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or  // 不抢占焦点
                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or  // 不可触摸
                        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,  // 显示在屏幕上
                PixelFormat.TRANSLUCENT)
        // 设置位置,例如屏幕中央
        params.gravity = Gravity.CENTER
        // 添加到WindowManager
        overlayWindowManager.addView(overlayView, params)
    }

    override fun onDestroy() {
        super.onDestroy()
        if (overlayView != null) {
            overlayWindowManager.removeView(overlayView)
        }
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        // 无障碍事件处理,不做任何操作
    }

    override fun onInterrupt() {
        // 中断时的处理
    }
}

2、定义布局文件

创建显示在Window顶层的透明水印显示布局。

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/layout1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="20dp">

        <TextView
            android:layout_width="0dp"
            android:layout_height="100dp"
            android:layout_weight="1"
            android:gravity="center"
            android:rotation="50.0"
            android:text="Hello 2025"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="50dp">

        <TextView
            android:layout_width="0dp"
            android:layout_height="119dp"
            android:layout_weight="1"
            android:gravity="center"
            android:rotation="50.0"
            android:text="Hello 2025"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout3"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="50dp">

        <TextView
            android:layout_width="0dp"
            android:layout_height="119dp"
            android:layout_weight="1"
            android:gravity="center"
            android:rotation="50.0"
            android:text="Hello 2025"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout4"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="50dp">

        <TextView
            android:layout_width="0dp"
            android:layout_height="119dp"
            android:layout_weight="1"
            android:gravity="center"
            android:rotation="50.0"
            android:text="Hello 2025"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello 2025"
            android:rotation="50.0"
            android:textColor="@color/light_gray"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

3、在AndroidManifest.xml 中声明无障碍服务

在应用清单文件AndroidManifest.xml 中声明无障碍服务,并配置相关的无障碍权限。

XML 复制代码
        <service
            android:name=".CustomOverlayService"
            android:exported="true"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config" />
        </service>

另外,上面的android:resource引用的无障碍属性配置文件,需要在 res/xml/ 文件夹中创建 accessibility_service_config.xml 文件,去配置无障碍服务的相关属性。

XML 复制代码
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_descriptions"
    android:accessibilityEventTypes="typeAllMask"
    android:canRetrieveWindowContent="true"
    android:packageNames=""
    android:notificationTimeout="100"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:settingsActivity=""/>

4、启用无障碍服务

写一个页面,添加一个开启无障碍服务的按钮,点击按钮跳转到系统设置中的辅助功能,可以去开启对应应用的无障碍功能开关。具体操作路径为:设置 -> 辅助功能 -> 已安装的服务 中找到你的服务并开启它。

Kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Greeting()
        }
    }
}

@Composable
fun Greeting() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        val context: Context = LocalContext.current
        Button(onClick = {
            if (!isAccessibilityServiceEnabled(context, CustomOverlayService::class.java)) {
                Log.i("AccessibilityService", "AccessibilityService disabled .")
                context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
            } else {
                Log.i("AccessibilityService", "AccessibilityService enabled .")
                context.startService(Intent(context,CustomOverlayService::class.java))
            }
        }, shape = ButtonDefaults.textShape) {
            Text(
                text = "启动无障碍功能"
            )
        }
    }
}

/**
 * 判断无障碍服务是否开启
 */
private fun isAccessibilityServiceEnabled(context: Context, accessibilityServiceClass: Class<*>): Boolean {
    var accessibilityEnabled = 0
    val service: String = context.packageName.toString() + "/" + accessibilityServiceClass.canonicalName
    try {
        accessibilityEnabled = Settings.Secure.getInt(context.contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED)
    } catch (e: Settings.SettingNotFoundException) {
        e.printStackTrace()
    }
    val colonSplitter = TextUtils.SimpleStringSplitter(':')
    if (accessibilityEnabled == 1) {
        val settingValue = Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
        if (settingValue != null) {
            colonSplitter.setString(settingValue)
            while (colonSplitter.hasNext()) {
                val componentName = colonSplitter.next()
                if (componentName.equals(service, ignoreCase = true)) {
                    return true
                }
            }
        }
    }
    return false
}

5、设备全局水印效果展示

系统设置页面水印

设备锁屏页面水印

三、总结

无障碍服务权限是一个非常强大的工具,开启后可以实现很多你意想不到的效果或者功能。比如,还可以通过无障碍服务获取设备上运行的最上层应用的包名以及VIEW。后面有空的话,我也会深挖更多的无障碍服务相关的功能来展示给大家,敬请期待!

demo源码,请查看文章开头资源链接。

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui