一、無障礙功能簡介
首先我們先來了解下無障礙功能的官方介紹:
? ? ? ? ?無障礙服務僅應用于幫助殘障用戶使用 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: WindowManagerprivate var overlayView: View? = nulloverride fun onServiceConnected() {super.onServiceConnected()// 初始化WindowManageroverlayWindowManager = 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// 添加到WindowManageroverlayWindowManager.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"><LinearLayoutandroid:id="@+id/layout1"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_marginTop="20dp"><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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><LinearLayoutandroid:id="@+id/layout2"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_marginTop="50dp"><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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><LinearLayoutandroid:id="@+id/layout3"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_marginTop="50dp"><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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><LinearLayoutandroid:id="@+id/layout4"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_marginTop="50dp"><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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
中聲明無障礙服務,并配置相關的無障礙權限。
<serviceandroid:name=".CustomOverlayService"android:exported="true"android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"><intent-filter><action android:name="android.accessibilityservice.AccessibilityService" /></intent-filter><meta-dataandroid:name="android.accessibilityservice"android:resource="@xml/accessibility_service_config" /></service>
另外,上面的android:resource引用的無障礙屬性配置文件,需要在 res/xml/
文件夾中創建 accessibility_service_config.xml
文件,去配置無障礙服務的相關屬性。
<accessibility-servicexmlns: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.currentButton(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 = 0val service: String = context.packageName.toString() + "/" + accessibilityServiceClass.canonicalNametry {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源碼,請查看文章開頭資源鏈接。