通过无障碍服务(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源码,请查看文章开头资源链接。

相关推荐
用户2018792831672 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子2 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82272 小时前
安卓接入Max广告源
android
齊家治國平天下2 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO2 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel2 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢2 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖2 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉4 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql
风起云涌~4 小时前
【Android】浅谈androidx.startup.InitializationProvider
android