zoukankan      html  css  js  c++  java
  • Android自动化之AccessibilityService

    简介

    由于许多Android用户由于某些原因(视力,身体,年龄)要求他们以不同的方式与手机设备交互。

    安卓提供了辅助功能特性和服务来帮助这些用户更容易的操作他们的设备,包括文字转语音(原生不支持中文,国内ROM可能会有,我的测试OPPO自带中文哟!),触觉反馈,手势操作,轨迹球和手柄操作。开发者可以利用这些服务使得程序更好用,也可以创建自己的辅助服务。

    随着Android版本的不断升级,Android Accessibility功能也越来越强大,Android 4.0版本以前,系统辅助服务功能比较单一,仅仅能过单向获取窗口元素信息,比如获取输入框用户输入内容。到Android 4.1版本以后(现在主流厂商的版本都在KITKAT4.4),系统辅助服务增加了与窗口元素的双向交互,此时可以通过辅助功能服务操作窗口元素,比如点击按钮等。
    由于系统辅助服务能够实时获取您当前操作应用的窗口元素信息,这有可能给你带来隐私信息的泄露风险,比如获取非密码输入框的输入内容等。同时通过辅助功能也可以模拟用户自动化点击应用内元素,也会带来一定的安全风险。

    已经有其他同学使用AccessibilityService实现了抢红包~以及自动安装等,大家可以自行百度。

    demo示例说明

    接下来我们将通过AccessibilityService实现点击元素之后语言反馈所点击的内容。
    测试辅助抓包应用为【微信】

    下载地址
    链接:http://pan.baidu.com/s/1pJVyo0z 密码:00i6

    Manifest声明

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    3. package="com.leestar.example.accessibilityservice"
    4. android:versionCode="1"
    5. android:versionName="1.0" >
    6. <uses-sdk
    7. android:minSdkVersion="19"
    8. android:targetSdkVersion="21" />
    9. <application
    10. android:allowBackup="true"
    11. android:icon="@drawable/ic_launcher"
    12. android:label="@string/app_name"
    13. android:theme="@style/AppTheme" >
    14. <activity
    15. android:name=".MainActivity"
    16. android:label="@string/app_name" >
    17. <intent-filter>
    18. <action android:name="android.intent.action.MAIN" />
    19. <category android:name="android.intent.category.LAUNCHER" />
    20. </intent-filter>
    21. </activity>
    22. <service
    23. android:name=".AccessibilityServiceExample"
    24. android:label="@string/accessibility_service_label"
    25. android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
    26. <intent-filter>
    27. <action android:name="android.accessibilityservice.AccessibilityService" />
    28. </intent-filter>
    29. <meta-data
    30. android:name="android.accessibilityservice"
    31. android:resource="@xml/accessibility_service_config" />
    32. </service>
    33. </application>
    34. </manifest>

    AccessibilityService的XML配置文件

    保存路径为<project_dir>/res/xml/accessibility_service_config.xml

    1. <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    2. android:description="@string/accessibility_service_description"
    3. android:packageNames="com.tencent.mm"
    4. android:accessibilityEventTypes="typeAllMask"
    5. android:accessibilityFlags="flagDefault"
    6. android:accessibilityFeedbackType="feedbackSpoken"
    7. android:notificationTimeout="100"
    8. android:canRetrieveWindowContent="true"
    9. android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
    10. />

    其中
    android:packageNames为需要捕获的包名
    android:accessibilityEventTypes为需要回调的事件
    android:notificationTimeout为事件回调的延迟事件
    其他具体含义请查询官方文档~

    创建继承自AccessibilityService的服务类

    具体见代码以及注释,里面也实现了语言反馈功能。

    1. package com.leestar.example.accessibilityservice;
    2. import android.accessibilityservice.AccessibilityService;
    3. import android.annotation.TargetApi;
    4. import android.content.Intent;
    5. import android.os.Build;
    6. import android.speech.tts.TextToSpeech;
    7. import android.util.Log;
    8. import android.view.accessibility.AccessibilityEvent;
    9. import android.view.accessibility.AccessibilityNodeInfo;
    10. public class AccessibilityServiceExample extends AccessibilityService {
    11. private AccessibilityNodeInfo rootNodeInfo;
    12. private TextToSpeech tts = null;
    13. private boolean ttsIsInit = false;
    14. /**
    15. * 可选。系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,
    16. * 也可以调用setServiceInfo()进行配置工作。
    17. */
    18. @Override
    19. protected void onServiceConnected() {
    20. // TODO Auto-generated method stub
    21. super.onServiceConnected();
    22. tts = new TextToSpeech(getApplicationContext(), new android.speech.tts.TextToSpeech.OnInitListener() {
    23. @Override
    24. public void onInit(int status) {
    25. if (status == TextToSpeech.SUCCESS) {
    26. ttsIsInit = true;
    27. }
    28. }
    29. });
    30. }
    31. /**
    32. * 可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。
    33. */
    34. @Override
    35. public boolean onUnbind(Intent intent) {
    36. // TODO Auto-generated method stub
    37. return super.onUnbind(intent);
    38. }
    39. /**
    40. * 根据XML的android:accessibilityEventTypes来进行事件回调,所有的业务逻辑都要在回调中完成
    41. */
    42. @TargetApi(Build.VERSION_CODES.KITKAT)
    43. @Override
    44. public void onAccessibilityEvent(AccessibilityEvent event) {
    45. rootNodeInfo = event.getSource();
    46. if (rootNodeInfo == null)
    47. return;
    48. switch (event.getEventType()) {
    49. case AccessibilityEvent.TYPE_VIEW_CLICKED:
    50. // 语音反馈
    51. if (ttsIsInit) {
    52. // event.getContentDescription根据触发事件节点来自动查找子树的ContentDescription
    53. //所以你会发现UIAutomatorViewer上的相关节点信息为空,但是event里赋值了。
    54. // event.getText同上
    55. if (event.getContentDescription() != null) {
    56. tts.speak(event.getContentDescription().toString(), TextToSpeech.QUEUE_ADD, null);
    57. } else if (event.getText() != null) {
    58. tts.speak(event.getText().toString(), TextToSpeech.QUEUE_ADD, null);
    59. }
    60. }
    61. // 获取精确节点信息,可以通过UIAutomatorViewer检索,实现相关模拟操作
    62. //AccessibilityNodeInfo提供了2种自动查找字节点的函数
    63. //rootNodeInfo.findAccessibilityNodeInfosByText
    64. //rootNodeInfo.findAccessibilityNodeInfosByViewId(viewId)
    65. if (rootNodeInfo.getChild(0) != null
    66. && rootNodeInfo.getChild(0).getClassName().equals("android.widget.TextView")
    67. && rootNodeInfo.getChild(0).getText() != null
    68. && rootNodeInfo.getChild(0).getText().toString().equals("我")) {
    69. // 如果发现按的是【我】,模拟全局返回按钮,即退出微信
    70. performGlobalAction(GLOBAL_ACTION_BACK);
    71. // 模拟点击元素
    72. // if (rootNodeInfo.isClickable()) {
    73. // rootNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
    74. // }
    75. }
    76. break;
    77. default:
    78. break;
    79. }
    80. Log.i("leestar", AccessibilityEvent.eventTypeToString(event.getEventType()));
    81. Log.i("leestar", rootNodeInfo.getClassName().toString());
    82. }
    83. /**
    84. * 这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。
    85. */
    86. @Override
    87. public void onInterrupt() {
    88. Log.i("leestar", "onInterrupt");
    89. }
    90. }

    MainActivity检测服务是否开启

    1. package com.leestar.example.accessibilityservice;
    2. import java.util.List;
    3. import android.accessibilityservice.AccessibilityServiceInfo;
    4. import android.app.Activity;
    5. import android.content.Context;
    6. import android.content.Intent;
    7. import android.os.Bundle;
    8. import android.provider.Settings;
    9. import android.view.View;
    10. import android.view.WindowManager;
    11. import android.view.accessibility.AccessibilityManager;
    12. import android.widget.Button;
    13. public class MainActivity extends Activity {
    14. private Button button_switch;
    15. @Override
    16. protected void onCreate(Bundle savedInstanceState) {
    17. // TODO Auto-generated method stub
    18. super.onCreate(savedInstanceState);
    19. setContentView(R.layout.activity_main);
    20. button_switch = (Button) findViewById(R.id.button_switch);
    21. }
    22. @Override
    23. protected void onResume() {
    24. super.onResume();
    25. updateServiceStatus();
    26. }
    27. @Override
    28. protected void onDestroy() {
    29. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    30. super.onDestroy();
    31. }
    32. public void onButtonClick(View view) {
    33. startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
    34. }
    35. private void updateServiceStatus() {
    36. boolean ServiceEnabled = false;
    37. // 循环遍历所有服务,查看是否开启
    38. AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
    39. List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
    40. .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
    41. for (AccessibilityServiceInfo info : accessibilityServices) {
    42. if (info.getId().equals(getPackageName() + "/.AccessibilityServiceExample")) {
    43. ServiceEnabled = true;
    44. break;
    45. }
    46. }
    47. if (ServiceEnabled) {
    48. button_switch.setText("关闭测试服务");
    49. // Prevent screen from dimming
    50. getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    51. } else {
    52. button_switch.setText("开启测试服务");
    53. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    54. }
    55. }
    56. }

    UiAutomatorViewer

    在需要精确元素节点操作检索的时候,就需要用到UiAutomatorViewer工具,一般情况下,Android SDK下会自带这个工具

    找到uiautomatorviewer.bat即可,打开之后,点击看起来是这个样子的

    我想你应该知道怎么做了,树形结构UI,AccessibilityNodeInfo通过getChild、getParent尽情的识别吧~!

    参考

    http://www.android-doc.com/guide/topics/ui/accessibility/services.html

  • 相关阅读:
    ThinkPHP5+jQuery+MySql实现投票功能
    JQ input输入框回车生成标签,可删除,并获取标签的值
    php 使用 CURL 获取数据
    new String创建了几个对象
    java高级开发面试总结
    使用 Sublime Text 将含下划线的字符串批量替换为驼峰命名法格式的字符串
    Synchronized方法锁、对象锁、类锁区别
    利用Redisson实现分布式锁及其底层原理解析
    MySQL索引
    JVM常见面试题
  • 原文地址:https://www.cnblogs.com/leestar54/p/6518875.html
Copyright © 2011-2022 走看看