通过无障碍服务(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 Studio:键值对存储sharedPreferences
android·ide·android studio·键值对存储
雾里看山2 小时前
【MySQL】 表的约束(上)
android·mysql·adb
q567315234 小时前
无法在Django 1.6中导入自定义应用
android·开发语言·数据库·django·sqlite
a3158238064 小时前
Android设置个性化按钮按键的快捷启动应用
android·开发语言·framework·源码·android13
Henry_He4 小时前
SystemUI通知在阿拉伯语下布局方向RTL下appName显示异常
android
XJSFDX_Ali5 小时前
安卓开发,底部导航栏
android·java·开发语言
云罗张晓_za8986687 小时前
抖音“碰一碰”发视频:短视频社交的新玩法
android·c语言·网络·线性代数·矩阵·php
货拉拉技术10 小时前
记一次无障碍测试引发app崩溃问题的排查与解决
android·前端·程序员
GrimRaider10 小时前
【逆向工程】破解unity的安卓apk包
android·unity·游戏引擎·软件逆向
yzpyzp10 小时前
Jetpack之ViewBinding和DataBinding的区别
android