一、无障碍功能简介
首先我们先来了解下无障碍功能的官方介绍:
无障碍服务仅应用于帮助残障用户使用 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源码,请查看文章开头资源链接。