失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 如何使用 AccessibilityService 实现蚂蚁森林自动收取能量 无需Root 无需连接电脑

如何使用 AccessibilityService 实现蚂蚁森林自动收取能量 无需Root 无需连接电脑

时间:2019-01-31 13:49:30

相关推荐

如何使用 AccessibilityService 实现蚂蚁森林自动收取能量 无需Root 无需连接电脑

如何使用 AccessibilityService 实现蚂蚁森林自动收取能量,无需Root,无需连接电脑

AccessibilityService 设计初衷在于帮助残障用户使用android设备和应用,在后台运行,可以监听用户界面的一些状态转换,例如页面切换、焦点改变、通知、Toast等,并在触发AccessibilityEvents时由系统接收回调。后来被开发者另辟蹊径,用于一些插件开发,比如微信红包助手,还有一些需要监听第三方应用的插件。

AccessibilityService官网

前提准备

1. 继承 AccessbilityService

继承AccessbilityService,在 onServiceConnected 的时候做一些初始化,在 onAccessibilityEvent 里面监听页面事件,实现具体的辅助功能

public class AccessibilityServiceDemo extends AccessibilityService {//初始化@Overrideprotected void onServiceConnected() {super.onServiceConnected();}//实现辅助功能@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {}@Overridepublic void onInterrupt() {}@Overridepublic void onDestroy() {super.onDestroy();}}

2. 在AndroidManifest中注册该服务

因为 AccessbilityService 本质上还是一个 Service ,所以必须要在 AndroidManifest.xml 中注册该服务

<serviceandroid:name=".auto.AccessibilityServiceDemo"android:enabled="true"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/accessibitydemo" /></service>

android:permission=“android.permission.BIND_ACCESSIBILITY_SERVICE” 是为了确保只有系统可以绑定该服务。

3. 在 meta-data 中配置监听属性

<accessibility-service xmlns:android="/apk/res/android"android:accessibilityEventTypes="typeAllMask"android:accessibilityFeedbackType="feedbackAllMask"// 必须加上 flagRequestEnhancedWebAccessibility ,不然无法检查到 WebView 里面的元素,支付宝大部分页面都是 WebViewandroid:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"android:canPerformGestures="true"android:canRequestEnhancedWebAccessibility="true"android:canRequestFilterKeyEvents="true"android:canRetrieveWindowContent="true"// 此处必须用@string方式,不能直接写,会报错android:description="@string/accessibility_des"android:notificationTimeout="100"// 监听的包名如果多个可以用 "," 隔开,例如:"com.eg.android.AlipayGphone,com.eg.android.AlipayGphone"android:packageNames="com.eg.android.AlipayGphone" />

4. 指引用户去手动打开该服务

因为无障碍服务无法自动开启,只能引导用户手动开启

public static boolean isAccessibilitySettingsOn(Context mContext, Class<? extends AccessibilityService> clazz) {int accessibilityEnabled = 0;final String service = mContext.getPackageName() + "/" + clazz.getCanonicalName();try {accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),Settings.Secure.ACCESSIBILITY_ENABLED);} catch (Settings.SettingNotFoundException e) {e.printStackTrace();}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;}

没有开启的话,就跳到服务的开启页面,让用户手动开启

startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));

5. 封装元素查找的方法

下面举个例子,想看完整版的,自己看demo吧

public AccessibilityNodeInfo findFirst(@NonNull AbstractTF... tfs) {if (tfs.length == 0) throw new InvalidParameterException("AbstractTF不允许传空");AccessibilityNodeInfo rootInfo = getRootInActiveWindow();if (rootInfo == null) return null;int idTextTFCount = 0, idTextIndex = 0;for (int i = 0; i < tfs.length; i++) {if (tfs[i] instanceof AbstractTF.IdTextTF) {idTextTFCount++;idTextIndex = i;}}switch (idTextTFCount) {case 0://id或text数量为0,直接循环查找AccessibilityNodeInfo returnInfo = findFirstRecursive(rootInfo, tfs);rootInfo.recycle();return returnInfo;case 1://id或text数量为1,先查出对应的id或text,然后再查其他条件if (tfs.length == 1) {AccessibilityNodeInfo returnInfo2 = ((AbstractTF.IdTextTF) tfs[idTextIndex]).findFirst(rootInfo);rootInfo.recycle();return returnInfo2;} else {...}default:throw new RuntimeException("由于时间有限,并且多了也没什么用,所以IdTF和TextTF只能有一个");}rootInfo.recycle();return null;}

开始自动领取能量

自己的 AccessbilityService 无障碍服务已经创建了,接下来就要做自动领取能量流程了

1. 打开支付宝

Intent intent = activity.getPackageManager().getLaunchIntentForPackage("com.eg.android.AlipayGphone");if (intent != null) {intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);activity.startActivity(intent);}

直接打开支付宝的 Launch 页面

2. 创建子线程执行任务

protected ExecutorService mExecutor;public void onAccessibilityEvent(AutoAccessibilityService autoAccessibilityService, AccessibilityEvent event) {this.autoAccessibilityService = autoAccessibilityService;if (needStop) {LogUtils.d(TAG, "needStop = true, stop");return;}if (!needStart) return;if (mExecutor != null && !mExecutor.isShutdown()) {mExecutor.shutdownNow();}mExecutor = Executors.newSingleThreadExecutor();mExecutor.execute(new Runnable() {@Overridepublic void run() {doTask();}});needStart = false;}

创建一个线程来执行任务,虽然在 Service 中可以做的操作是在后台的,但是在主线程执行的操作过长还是会卡主线程,造成anr的

3. 查找“蚂蚁森林”入口

不知道查找元素的,先看下这篇:如何使用 AccessibilityService 查找元素

找到“蚂蚁森林”这个元素,然后点击进入

AccessibilityNodeInfo homeAntTreesNode = loopFindFirst(AbstractTF.newText("蚂蚁森林", true));if (homeAntTreesNode == null) {LogUtils.d(TAG, "collectEnergy, homeAntTreesNode == null");needStart = true;return;}// 进入蚂蚁森林页面autoAccessibilityService.clickView(homeAntTreesNode);

这里我用的是一个叫 Auto.js 的工具,自己也一直想实现一个,实在懒,还没动工

Auto.js git地址

伸手党这里直接打包了一个debug包

首先进行布局范围分析

然后点击蚂蚁森林,可以看到 txt 是“蚂蚁森林”,所以这个可以用 newText 来查找

4.领取自己的能量

首先判断是否到了领取能量页面,这里用了“种树”元素,找到说明到了,没找到说明还没到,就怕网不好,所以循环等待

AccessibilityNodeInfo plantTreeNode = loopFindFirst(10, AbstractTF.newWebText("种树", true));if (plantTreeNode == null) {LogUtils.d(TAG, "collectEnergy, plantTreeNode == null");needStart = true;return;} else {LogUtils.d(TAG, "collectEnergy, plantTreeNode != null");}

到了自己页面之后领取能量,首先“收集能量”的元素,也是同样通过 Auto.js 来获取的,可以点击能量球,看到"收集能量4克",所以就查找搜索“收集能量”四个字了,而且看了下是WebView上的,所以用 newWebText 来找

要是有好友的昵称这么奇葩,带了“收集能量”,那就自认倒霉吧

找到可以收取的能量后疯狂点击(好歹隔了0.5s,防止手机被你点坏了),由于这些元素都是没有点击事件的,所以只能点击这个元素的中心点了(必须API 24 以上才有用)

List<AccessibilityNodeInfo> energyNode = autoAccessibilityService.findAll(AbstractTF.newWebText("收集能量", false));for (int i = 0; i < energyNode.size(); i++) {autoAccessibilityService.dispatchGestureClick(energyNode.get(i));SystemClock.sleep(500);}

5.收取好友能量

由于支付宝小手手是一张图片,而且那个位置固定有这个元素(估计是看多了太多自动收能量脚本,迫不得已让你找不到),没办法看谁有谁没有能量,只能暴力所有人点一遍了

点击“查看更多好友”按钮

搜索带有"g"的元素,第一个肯定是自己舍去,看第二个元素,然后通过层级关系,找到 ListView 相应的层级

找到当前昵称所在的元素(记录下来,避免重复点击)

找到“邀请”所在的元素(需要邀请的人不需要点,反正没能量,也不能点击)

点击 ListView 里面的一个个Item,开始收好友能量吧

一页点完,滚动下,继续循环找

通过找元素“没有更多了”在屏幕内,就说明点完了,收工

// 点击查看更多好友AccessibilityNodeInfo checkAllFriendNode = loopFindFirst(10, AbstractTF.newWebText("查看更多好友", true));autoAccessibilityService.clickView(checkAllFriendNode);SystemClock.sleep(2000);collectedNames.clear();AccessibilityNodeInfo noMoreNode = null;Rect noMoreNodeOutBounds = new Rect();while (noMoreNode == null || (noMoreNodeOutBounds.bottom - noMoreNodeOutBounds.top) < Utils.dp2px(AutoApplication.context, 10)) {if (!isInPackage()) {LogUtils.d(TAG, "collectEnergy, not in package return");return;}List<AccessibilityNodeInfo> specialNodes = loopFindAll(AbstractTF.newWebText("g", false));AccessibilityNodeInfo listItems = null;try {listItems = specialNodes.get(1).getParent().getParent().getParent();} catch (Exception e) {LogUtils.d(TAG, "collectEnergy, can not find lists, can not collect friend energy");}LogUtils.d(TAG, "collectEnergy, collect friend energy: " + (listItems == null ? 0 : listItems.getChildCount()));for (int i = 0; listItems != null && i < listItems.getChildCount(); i++) {if (!isInPackage()) {LogUtils.d(TAG, "collectEnergy, not in package return");return;}AccessibilityNodeInfo item = listItems.getChild(i);Rect outBounds = new Rect();item.getBoundsInScreen(outBounds);String friendName = "";try {friendName = item.getChild(2).getChild(0).getChild(0).getText().toString();String friendNameContent = item.getChild(2).getChild(0).getChild(0).getContentDescription().toString();LogUtils.d(TAG, "collectEnergy, get friend name text: " + friendName + ", ContentDescription: " + friendNameContent);} catch (Exception e) {LogUtils.d(TAG, "collectEnergy, get friend name error");}String hasNoEnergy = "";try {hasNoEnergy = item.getChild(3).getChild(0).getChild(0).getText().toString();} catch (Exception e) {LogUtils.d(TAG, "collectEnergy, get has no energy error");}if (!TextUtils.isEmpty(friendName) && !"邀请".equals(hasNoEnergy) && !collectedNames.contains(friendName)&& outBounds.top > 0 && outBounds.bottom < AutoApplication.context.getResources().getDisplayMetrics().heightPixels && outBounds.top < outBounds.bottom) {LogUtils.d(TAG, "collectEnergy, try collect friend energy name: " + friendName);collectFriendEnergy(item);collectedNames.add(friendName);SystemClock.sleep(1000);} else {LogUtils.d(TAG, "collectEnergy, can not collect friend energy name: " + friendName);}}noMoreNode = autoAccessibilityService.findFirst(AbstractTF.newWebText("没有更多了", false));if (noMoreNode != null) {noMoreNode.getBoundsInScreen(noMoreNodeOutBounds);LogUtils.d(TAG, "collectEnergy, has no more node left: " + noMoreNodeOutBounds.left + ", right: " + noMoreNodeOutBounds.right+ ", top: " + noMoreNodeOutBounds.top + ", bottom: " + noMoreNodeOutBounds.bottom);}LogUtils.d(TAG, "collectEnergy, do scroll to collect other friends");doLargeScroll(true);}

总结

这里用的还是比较暴力的方法,直接每一个好友都点一遍,实在是太耗时了。(主要是支付宝太鸡贼了,估计这种脚本太多,蚂蚁森林过段时间就更新,改结构,让你脚本失效)

后面考虑优化下,采用截图的方式,判断收取能量小手手存不存在,再点进去收取能量,期待下次的分析吧。。。。。。

最后附上完整demo的地址:蚂蚁森林自动领取能量

如果觉得《如何使用 AccessibilityService 实现蚂蚁森林自动收取能量 无需Root 无需连接电脑》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。