当前位置: 首页 > article >正文

通过无障碍服务(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),该服务将用于显示全局的透明水印文字层。

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 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 中声明无障碍服务,并配置相关的无障碍权限。

        <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 文件,去配置无障碍服务的相关属性。

<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、启用无障碍服务

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

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源码,请查看文章开头资源链接。


http://www.kler.cn/a/461376.html

相关文章:

  • Docker 搭建 Gogs
  • SpringBoot 实现登录功能
  • 书生·浦语大模型全链路开源体系-第9关 LMDeploy 量化部署进阶实践
  • TB1801D 线性驱动 LED 恒流芯片
  • 苹果系统MacOS下采用ObjectC访问opencv加载图片的一个简单实例
  • Flink的多流转换(分流-侧输出流、合流-union、connect、join)
  • 中华人民共和国网络安全法
  • BOE(京东方)“向新2025”年终媒体智享会落地深圳
  • 关于MCU复位电路的分析与设计
  • Java重要面试名词整理(十二):Netty
  • C++ Brain Teasers: 未指定和实现定义的行为-函数参数的求值顺序
  • 网络安全靶场合集:知识点与功能解析
  • 数据可视化-1:使用Matplotlib绘制多种图表
  • 手机租赁平台开发助力智能设备租赁新模式
  • 机器学习、深度学习、神经网络之间的关系
  • (推荐)【通用业务分发架构】1.业务分发 2.rpc调用 3.Event事件系统
  • win32汇编环境下,窗口程序中生成listview列表控件及显示
  • 深度学习day6|用pytorch实现VGG-16模型人脸识别
  • 2025跨年倒计时
  • qt5.15.2+visual studio2022 免安装版环境配置