- 1、新建一个Service 集成AccessibilityService
- 1.1、DingService.java
- 1.2、控件定制化操作
- 1.3、通过文字点击
- 1.4、通过文字给EditText填充数据
- 1.5、打印界面节点
- 2、配置
- 3、在MainActivity中启动
- AccessibilityNodeInfo支持的操作
- 借鉴资料
1、新建一个Service 集成AccessibilityService
1.1、DingService.java
public class DingService extends AccessibilityService {
//初始化工作
public void onServiceConnected() {
}
//获得控件节点
public void onAccessibilityEvent(AccessibilityEvent event) {
//获得根节点
AccessibilityNodeInfo rowNode = getRootInActiveWindow();
if (rowNode == null) {
return;
} else {
//在这里对根节点遍历等操作,寻找需要的控件,进行操作
recycle(rowNode);
}
}
/**
* 中断AccessibilityService的反馈时调用
*/
@Override
public void onInterrupt() {
}
}
1.2、控件定制化操作
@SuppressLint("NewApi")
public void recycle(AccessibilityNodeInfo info) {
if (info.getChildCount() == 0) {
click("注册/登录");//遍历到名为“注册/登录”的控件信息,并点击他
if (info.getClassName().equals("android.widget.EditText"))
{
setText(info, "请输入密码","123456");//给内容为“请输入密码”的EditText填充123456数据
}
}
else {//继续遍历
for (int i = 0; i < info.getChildCount(); i++) {
if (info.getChild(i) != null) {
recycle(info.getChild(i));
}
}
}
}
1.3、通过文字点击
//通过文字点击
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private boolean click(String viewText) {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo == null) {
//Log.w(TAG, "点击失败,rootWindow为空");
return false;
}
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(viewText);
if (list.isEmpty()) {
//没有该文字的控件
//Log.w(TAG, "点击失败,"+viewText+"控件列表为空");
return false;
} else {
//有该控件
//找到可点击的父控件
AccessibilityNodeInfo view = list.get(0);
//Log.i(TAG, "点击" + viewText);
Boolean fal = onclick(view); //遍历点击
return fal;
}
}
private boolean onclick(AccessibilityNodeInfo view) {
if (view.isClickable()) {
view.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//Log.i(TAG, "成功");
return true;
} else {
AccessibilityNodeInfo parent = view.getParent();
if (parent == null) {
return false;
}
onclick(parent);
}
return false;
}
1.4、通过文字给EditText填充数据
void setText(AccessibilityNodeInfo info, String title, String vaule) {
if (info == null) {
return;
}
String str = info.getText().toString();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.i(TAG, "HintText" + info.getHintText() + "");
}
Log.i(TAG, "Text" + info.getText() + "");
if (str.equals(title)) {
putClipboard1(info, vaule);
}
}
//自动为edittext粘贴上文字内容
public Boolean putClipboard1(AccessibilityNodeInfo edittext, String text) {
if (edittext != null) {
ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text", text);
clipboard.setPrimaryClip(clip);
//焦点(n是AccessibilityNodeInfo对象)
edittext.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
粘贴进入内容
return edittext.performAction(AccessibilityNodeInfo.ACTION_PASTE);
//发送
//...
}
return false;
}
1.5、打印界面节点
private static int tabcount = -1;
private static StringBuilder sb;
//打印此时的界面状况,便于分析
private static void analysisPacketInfo(AccessibilityNodeInfo info, int... ints) {
if (info == null) {
return;
}
if (tabcount > 0) {
for (int i = 0; i < tabcount; i++) {
sb.append("\t\t");
}
}
if (ints != null && ints.length > 0) {
StringBuilder s = new StringBuilder();
for (int j = 0; j < ints.length; j++) {
s.append(ints[j]).append(".");
}
sb.append(s).append(" ");
}
String name = info.getClassName().toString();
String[] split = name.split("\\.");
name = split[split.length - 1];
if ("TextView".equals(name)) {
CharSequence text = info.getText();
sb.append("text:").append(text);
} else if ("Button".equals(name)) {
CharSequence text = info.getText();
sb.append("Button:").append(text);
} else {
sb.append(name);
}
sb.append("\n");
int count = info.getChildCount();
if (count > 0) {
tabcount++;
int len = ints.length + 1;
int[] newInts = Arrays.copyOf(ints, len);
for (int i = 0; i < count; i++) {
newInts[len - 1] = i;
analysisPacketInfo(info.getChild(i), newInts);
}
tabcount--;
}
}
public static void printPacketInfo(AccessibilityNodeInfo root) {
sb = new StringBuilder();
tabcount = 0;
int[] is = {};
analysisPacketInfo(root, is);
Log.d("节点", sb.toString());
}
//查找节点
public static AccessibilityNodeInfo findNodeByViewName(AccessibilityNodeInfo info, String viewName) {
String name = info.getClassName().toString();
String[] split = name.split("\\.");
name = split[split.length - 1];
if (name.equals(viewName)) {
return info;
} else {
int count = info.getChildCount();
if (count > 0) {
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo inf = findNodeByViewName(info.getChild(i), viewName);
if (inf != null) {
return inf;
}
}
} else {
return null;
}
}
return null;
}
- 这样我们可以通过前面的0.0.0.1.1直接定位到View
AccessibilityNodeInfo info = root;
int[] path = {0, 0, 0, 1, 1};
for (int i = 0; i < path.length; i++) {
info = info.getChild(path[i]);
if (info == null || info.getChildCount() <= 0) {
return null;
}
}
return info;
- 当然你有可能不知道0.0.0.1.1对应哪一个视图,可以
Rect rect = new Rect();
info.getBoundsInScreen(rect);
//状态栏的高度
int h = GUtil.getStatusBarHeight(context.getApplicationContext());
rect.top -= h;
rect.bottom -= h;
打印rect,或者直接在全局窗口创建window,显示rect为有色区域.
2、配置
- manifest
<!-- AccessibilityService服务实现权限 -->
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"
tools:ignore="ProtectedPermissions" />
<!-- 注册监听手机状态Servicer -->
<service
android:name=".DingService"
android:enabled="true"
android:exported="true"
android:label="@string/app_name"
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/accessibilityservice" />
</service>
- accessibilityservice.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_description"
android:packageNames="com.hbc.hbc"//com.hbc.hbc是你要监听的应用包名,不写则监听所有包
android:notificationTimeout="200" />
3、在MainActivity中启动
//在onCreate中调用startAccessibilityService就可以开启服务
/**
* 前往设置界面开启服务
*/
private void startAccessibilityService() {
new AlertDialog.Builder(this)
.setTitle("开启辅助功能")
.setIcon(R.mipmap.ic_launcher)
.setMessage("使用此项功能需要您开启辅助功能")
.setPositiveButton("立即开启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 隐式调用系统设置界面
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
}).create().show();
}
/**
* 判断自己的应用的AccessibilityService是否在运行
*
* @return
*/
private boolean serviceIsRunning() {
ActivityManager am = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Short.MAX_VALUE);
for (ActivityManager.RunningServiceInfo info : services) {
if (info.service.getClassName().equals(getPackageName() + ".DingService")) {//DingService是你的包名
return true;
}
}
return false;
}
AccessibilityNodeInfo支持的操作
- 监听包过滤,一般在onServiceConnected中进行初始化
“`
String[] PACKAGE_NAMES = {“com.hbc.hbc”};//要监听的包名
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
info.packageNames = PACKAGE_NAMES;
// …配置
setServiceInfo(info);
- AccessibilityService本身有方法,模拟返回键,home键等
performGlobalAction(GLOBAL_ACTION_BACK)
- AccessibilityNodeInfo还可以直接模拟点击,长按等事件。
info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
**但是,performAction有时候根本没用!!!**
因为现在很多应用都是混合应用,内容页可能是Html5写的,看起来是按钮,其实就是普通View..他的点击事件不是通过OnClick产生,而是直接判断TouchEvent。AccessibilityNodeInfo没有提供发送down,move,up事件的api。我不能通过这系列模拟所有操作了,替代方案使用root 后的手机,向系统发送全局点击命令。
/*点击某个视图/
public static void perforGlobalClick(AccessibilityNodeInfo info) {
Rect rect = new Rect();
info.getBoundsInScreen(rect);
perforGlobalClick(rect.centerX(), rect.centerY());
}
//点击屏幕指定点
public static void perforGlobalClick(int x, int y) {
execShellCmd(“input tap ” + x + ” ” + y);
}
/**
* 执行shell命令
*
* @param cmd
*/
public static void execShellCmd(String cmd) {
try {
// 申请获取root权限,这一步很重要,不然会没有作用
Process process = Runtime.getRuntime().exec("su");
// 获取输出流
OutputStream outputStream = process.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeBytes(cmd);
dataOutputStream.flush();
dataOutputStream.close();
outputStream.close();
// process.waitFor();
} catch (Throwable t) {
t.printStackTrace();
}
}
“`
借鉴资料