您的位置:首页 > 移动开发 > 微信开发

利用AccessibilityServicer查找节点实现模拟点击,直接打开微信朋友圈

2019-04-23 18:10 357 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_40112993/article/details/89476833

继上一篇 [https://blog.csdn.net/weixin_40112993/article/details/89246258] 了解到AccessibilityService基本用法之后,我们知道在使用时需要 自定义一个类 MyAccessibilityService 继承 AccessibilityService ,然后复写其中的两个方法,onAccessibilityEvent() and onInterrupt(),在 onAccessibilityEvent 中我们可以监听界面的变化,继而查找节点,做相应事件。

直接上代码:

MainActivity.java 中写进入微信App的的流程:

package com.huawei.myaccessibilityservice;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
public static String packageName = "com.tencent.mm";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.btn_open_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});

findViewById(R.id.btn_open_friend_circle).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startOpenFriendCircle();
}
});
}

/**
* 開始正常流程
*/
private void startOpenFriendCircle() {
if (!Utils.isAppAvilible(MainActivity.this, packageName)) { //没有安装微信
Toast.makeText(this, "未安装微信", Toast.LENGTH_LONG).show();
} else {
//检测用户是否开启辅助功能权限,如果沒有則進入設置界面開啟輔助
if (Utils.isNeededPermissionsGranted(MainActivity.this)) {
//開始正常流程。。。
startApp(packageName);
}
}
}

/**
* 進入APP
* @param packageName 包名
*/
private void startApp(String packageName) {
PackageManager packageManager = getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(packageName);
if (intent == null) {
return;
}
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}

从代码中可以看到在进入微信APP之前我们先判断是否安装微信,如果安装再判断无障碍服务是否打开,如果打开直接进入微信,如果没有则先进入无障碍服务页面打开服务。

其中Utils工具类为:

package com.huawei.myaccessibilityservice;

import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.util.List;

/**
* 工具类
*/
public class Utils {
/**
* Check whether an application is installed
*
* @param context     context
* @param packageName the name of package
*/
public static boolean isAppAvilible(Context context, String packageName) {
final PackageManager packageManager = context.getPackageManager();// 獲取packagemanager
List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);//獲取所有已安裝程序的包信息
if (pinfo != null) {
int size = pinfo.size();
for (int i = 0; i < size; i++) {
String str = pinfo.get(i).packageName;
if (str.equals(packageName)) {
return true;
}
}
}
return false;
}

/**
* Return whether service is running.
*
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isNeededPermissionsGranted(Context context) {
boolean accessibilityGranted = isAccessibilitySettingsOn(context);

if (!accessibilityGranted) { //判斷輔助功能是否打開  如果沒有打開 則跳轉至輔助功能界面
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
context.startActivity(intent);
return false;

}
return true;
}

private static boolean isAccessibilitySettingsOn(Context mContext) {
int accessibilityEnabled = 0;
final String service = Utils.getApp().getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
Log.e("e", "Error finding setting, default accessibility to not found: " + e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

if (accessibilityEnabled == 1) {
String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();

if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
}

/**
* Return the context of Application object.
*
* @return the context of Application object
*/
public static Application getApp() {
Application app = getApplicationByReflect();
return app;
}

private static Application getApplicationByReflect() {
try {
@SuppressLint("PrivateApi")
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object thread = activityThread.getMethod
4000
("currentActivityThread").invoke(null);
Object app = activityThread.getMethod("getApplication").invoke(thread);
if (app == null) {
throw new NullPointerException("u should init first");
}
return (Application) app;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) {
Log.e("e", e.toString());
}
throw new NullPointerException("u should init first");
}
}

打开了无障碍服务 后,我们自定义的 MyAccessibilityService 就开始工作了,然后进入微信APP 获取节点并进行模拟点击,打开朋友圈。

MyAccessibilityService.java代码为:

package com.huawei.myaccessibilityservice;

import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

/**
* custom service
*/
public class MyAccessibilityService extends AccessibilityService {
private static MyAccessibilityService instance;

private static void setInstance(MyAccessibilityService service) {
instance = service;
}

public static synchronized MyAccessibilityService getInstance() {
return instance;
}

@Override
public void onCreate() {
super.onCreate();
setInstance(this);
}

@Override
public void onDestroy() {
super.onDestroy();
setInstance(null);
}

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event == null) {
return;
}
String pkgName = event.getPackageName() != null ? event.getPackageName().toString() : "";
String clsName = event.getClassName() != null ? event.getClassName().toString() : "";
String desc = event.getContentDescription() != null ? event.getContentDescription().toString() : "";
String kText = event.getText() != null ? event.getText().toString() : "";

Log.e("---tag--", "type=" + event.getEventType() + ",---clsName--" + clsName + ",---desc--" + desc + ",---kText--" + kText);
if (!pkgName.equals(MainActivity.packageName)) {
return;
}
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
return;
}

if (pkgName.equals(MainActivity.packageName)) {
if (clsName.equals(BaseContact.clsName_Main) && event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
setNodeClick(nodeInfo, BaseContact.text_find);

} else if (clsName.equals(BaseContact.clsName_listView) && event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
setNodeClick(nodeInfo, BaseContact.text_friend_circle);
}
}
}

@Override
public void onInterrupt() {
}

/**
* Auto-click after finding the node
*
* @param nodeInfo AccessibilityNodeInfo
* @param text     the message of node
*/
private void setNodeClick(AccessibilityNodeInfo nodeInfo, String text) {
AccessibilityNodeInfo info = NodeUtils.findNodeByParentClick(nodeInfo, text);
if (info != null) {
info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}

在代码中看到有个NodeUtils类,这个类就是用来寻找节点的。
NodeUtils.java 代码为:

package com.huawei.myaccessibilityservice;

import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;

import java.util.List;

/**
* find node tools
*/
public class NodeUtils {

/**
* find node by classname
*
* @param nodeInfo  AccessibilityNodeInfo
* @param className classname
* @return node
*/
public static AccessibilityNodeInfo findNodeInfosByClassName(AccessibilityNodeInfo nodeInfo, String className) {
if (nodeInfo == null) {
nodeInfo = MyAccessibilityService.getInstance().getRootInActiveWindow();
if (nodeInfo == null) {
return null;
}
}
int size = nodeInfo.getChildCount();
for (int i = 0; i < size; i++) {
AccessibilityNodeInfo childNode = nodeInfo.getChild(i);
if (childNode != null) {
String childClassName = (childNode.getClassName() != null) ? childNode.getClassName().toString() : "";
if (childClassName.equals(className)) {
return childNode;
}
AccessibilityNodeInfo grandNode = findNodeInfosByClassName(childNode, className);
if (grandNode != null) {
return grandNode;
}
recycel(childNode);
}
}
return null;
}

/**
* 查找到含应用字段的Node列表后,根据相应字段作排除,只适应节点字段相等的匹配情况。
*/
public static AccessibilityNodeInfo findNodeInfosByText(AccessibilityNodeInfo root, String text) {
if (root == null) {
root = MyAccessibilityService.getInstance().getRootInActiveWindow();
if (root == null) {
return null;
}
}
List<AccessibilityNodeInfo> list = root.findAccessibilityNodeInfosByText(text);
if (list == null || list.isEmpty()) {
return null;
}
AccessibilityNodeInfo resultNode = null;
String nodeText;
String nodeDesc;
for (AccessibilityNodeInfo node : list) {
nodeText = ObjectUtils.objStr(node.getText());
nodeDesc = ObjectUtils.objStr(node.getContentDescription());
if (nodeText == null && nodeDesc == null) {
continue;
}
if (nodeText != null && (nodeText.equals("[" + text + "]") || nodeText.equals(text))) {
resultNode = node;
break;
}
if (nodeDesc != null && nodeDesc.equals(text)) {
resultNode = node;
break;
}
}
for (AccessibilityNodeInfo node : list) {
if (!node.equals(resultNode)) {
recycel(node);
}
}
return resultNode;
}

/**
* find the parent node that can be clicked
*/
public static AccessibilityNodeInfo findNodeByParentClick(AccessibilityNodeInfo root, String text) {
AccessibilityNodeInfo node = findNodeInfosByText(root, text);
if (node == null) {
return null;
}
AccessibilityNodeInfo parent = node;
while (parent != null && !parent.isClickable()) {
parent = parent.getParent();
}

if (parent != null && parent.isClickable()) {
return parent;
}
return null;
}

private static void recycel(AccessibilityNodeInfo node) {
try {
if (node != null) {
node.recycle();
}
} catch (IllegalStateException e) {
Log.e("e", e.getMessage());
}
}
}

其中有个方法为 findNodeByParentClick() 这个方法目的在于找到能够点击的父节点。
在我们查找节点的时候并不是每个节点都是能够点击的,因为它的 clickable 状态为 false,通过 while循环 一直查找到 clickable 状态为 true 的父节点,然后设置点击事件。

如下图,我们需要点击的 发现 按钮,但是这个节点的 clickable 状态为 false ,这就说明它是不能够点击的,调用 info.performAction(AccessibilityNodeInfo.ACTION_CLICK); 也不会起作用,这就需要我们查找到它的能够点击的父节点。
向上一直查找节点的父节点我们就可以看到 RelativeLayout 是可以点击的,就用这个能够点击的 父节点设置 info.performAction(AccessibilityNodeInfo.ACTION_CLICK)

其中 BaseContact.java 主要用来放节点相关的东西:

/**
* the message of node
*/
public class BaseContact {

public static String clsName_Main = "com.tencent.mm.ui.LauncherUI";
public static String clsName_listView = "android.widget.ListView";

public static final String text_find = "发现";
public static final String text_friend_circle = "朋友圈";
}

通过以上步骤就可以简单实现一步打开微信朋友圈功能。

过程中我们需要注意的是,我们需要的节点并不是都可以点击的,另外就是 event.getEventType() 的事件不同,在页面转换后,event.getEventType()可能会有多个值,可以选取唯一一个不同其他的值。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐