前言赘述:
本博客只对使用Android AccessibilityService实现自动抢单测试程序Demo的api使用,不对AccessibilityService的类结构及相关配置信息做详细说明,如需了解,请查阅谷歌安卓AccessibilityService的说明文档。
google Android官网AccessibilityService的文档说明:AccessibilityService | Android Developers
我最早是想在哈罗出行上做个脚本来抢单/找单,思考着要如何来实现这样一个功能。用lua脚本?用Android Monkey?很明显,这些都有局限性,实现不了。有一天,我想到某个电视台中的情景:某个电视台中,说的是小米集团(小米手机),在一个工作房间中的机架上整整齐齐的摆放着有上千乃至上万台的手机,每个手机的屏幕都在疯狂的闪烁着。后来才知道原来那是在做自动化交互测试。我在想:我的这个需求功能,是不是也是自动化交互测试方面的技术点?百度了一下,并由此接触到了Android AccessibilityService。
public abstract class AccessibilityService
extends Service
java.lang.Object | ||||
↳ | android.content.Context | |||
↳ | android.content.ContextWrapper | |||
↳ | android.app.Service | |||
↳ | android.accessibilityservice.AccessibilityService |
继承于android四大组件之Service,其使用方式与service基本相同。但它是一个系统级别的“监听器”服务,可监听手机上所有应用所产生的小变化、大变化(比如某个被监听应用的发生的点击事件、控件文本内容变化、消息通知等)。另它有自己独立的生命周期(思路有点类似于微信公众号开发中消息与事件的处理),如下:
onCreate()
onServiceConnected()
onAccessibilityEvent(AccessibilityEvent event)//线程阻塞式
onInterrupt()
下面进入主题:
第一步.注册服务
1.1在清单文件中注册服务
<service android:name=".service.HaloAccessibilityServiceV5" android:enabled="true" android:exported="true" 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_setting" /> </service>
1.2meta-data引入配置
<meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice_setting" />
1.3accessibilityservice.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="flagReportViewIds|flagRetrieveInteractiveWindows" android:canPerformGestures="true" android:canRetrieveWindowContent="true" android:description="@string/accessibilityservice_setting" android:notificationTimeout="100" android:packageNames="填写你要监听的应用完整包名以逗号隔开可填写多个"></accessibility-service>
1.4须手动为apk应用程序打开无障碍权限开关。
位置在设置->快捷与辅助->无障碍,找到自己的apk辅助程序应用,手动将开关打开。
第二步.实现辅助功能
思路:实现辅助功能的关键是接受到事件,并使用AccessibilityService相关api来获取应用控件信息,以及回应适当的事件,来完成"辅助"功能。
1.1下面先来看一下AccessibilityService做为"系统级"服务可以监听到指定应用的哪些事件,或者说可以监听到应用的哪些变化。
Event types支持的事件类型(全面)
AccessibilityEvent#TYPE_VIEW_CLICKED//View控件点击事件
AccessibilityEvent#TYPE_VIEW_LONG_CLICKED//长按事件
AccessibilityEvent#TYPE_VIEW_FOCUSED//焦点事件
AccessibilityEvent#TYPE_VIEW_SELECTED//选择事件
AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED//文本变化
AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED//窗口状态变化
AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED//消息通知状态变化
AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START//手势START
AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END//手势END
AccessibilityEvent#TYPE_VIEW_HOVER_ENTER//悬停
AccessibilityEvent#TYPE_VIEW_HOVER_EXIT//悬停
AccessibilityEvent#TYPE_VIEW_SCROLLED//滚动
AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED//文本选择变化
AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED//窗口连接变化
AccessibilityEvent#TYPE_ANNOUNCEMENT//公告
AccessibilityEvent#TYPE_GESTURE_DETECTION_START//手指检测START
AccessibilityEvent#TYPE_GESTURE_DETECTION_END//手指检测END
AccessibilityEvent#TYPE_TOUCH_INTERACTION_START//触摸互动START
AccessibilityEvent#TYPE_TOUCH_INTERACTION_END//触摸互动END
AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED//可访问性
AccessibilityEvent#TYPE_WINDOWS_CHANGED//窗口变化
AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED//可访问性清除
1.2获取应用控件信息的方式有两种
1).通过id寻找控件,id格式为:(包名:id/+制定控件的id),对第三方应用无效。
List<AccessibilityNodeInfo> list = rowNode.findAccessibilityNodeInfosByViewId("com.jingyao.easybike:id/tvAmount");
2).通过控件的text寻找控件
List<AccessibilityNodeInfo> list = rowNode.findAccessibilityNodeInfosByText("不拼车");
1.3.可回应的事件
1)节点可回应执行的操作
android.view.accessibility.AccessibilityNodeInfo#performAction(int action)
有
android.view.accessibility.AccessibilityNodeInfo#ACTION_CLICK//节点点击
android.view.accessibility.AccessibilityNodeInfo#ACTION_FOCUS
android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
android.view.accessibility.AccessibilityNodeInfo#ACTION_SELECT
android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
android.view.accessibility.AccessibilityNodeInfo#ACTION_CLICK
android.view.accessibility.AccessibilityNodeInfo#ACTION_LONG_CLICK
android.view.accessibility.AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
android.view.accessibility.AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
android.view.accessibility.AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
android.view.accessibility.AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
android.view.accessibility.AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
android.view.accessibility.AccessibilityNodeInfo#ACTION_COPY//复制
android.view.accessibility.AccessibilityNodeInfo#ACTION_PASTE//粘贴
android.view.accessibility.AccessibilityNodeInfo#ACTION_CUT//剪切
android.view.accessibility.AccessibilityNodeInfo#ACTION_SET_SELECTION
android.view.accessibility.AccessibilityNodeInfo#ACTION_EXPAND
android.view.accessibility.AccessibilityNodeInfo#ACTION_COLLAPSE
android.view.accessibility.AccessibilityNodeInfo#ACTION_DISMISS
android.view.accessibility.AccessibilityNodeInfo#ACTION_SET_TEXT
android.view.accessibility.AccessibilityNodeInfo#LAST_LEGACY_STANDARD_ACTION
android.view.accessibility.AccessibilityNodeInfo#ACTION_TYPE_MASK
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_PROGRESS_VALUE
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_X
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_Y
android.view.accessibility.AccessibilityNodeInfo#ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN
2)全局回应可执行的操作
android.accessibilityservice.AccessibilityService#performGlobalAction(int action)
有
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK//后退,相当于手机后退按键
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME//回到手机HOME屏幕
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_QUICK_SETTINGS
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_POWER_DIALOG
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_LOCK_SCREEN
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TAKE_SCREENSHOT
第三步.写个自动化测试DEMO应用。
下面贴部分对哈罗出行自动化测试抢单/找单的主要逻辑代码,HaloAccessibilityServiceV5.java
/** * Copyright (C), 2000-2021, XXX有限公司 * FileName: HaloAccessibilityServiceV5 * Author: wangyetao * Date: 21-10-6 00:41:24 * Description:哈罗自动找单,无障碍服务编程V5 * History: * <author>wangyetao * <time>2021年 10月 04日 星期一 13:07:43 CST * <version>V4 * <desc>1.添加语音提示功能 2.开关按钮与回应事件频率微调 */ package com.example.driverslanguage.service; import android.accessibilityservice.AccessibilityService; import android.annotation.SuppressLint; import android.graphics.PixelFormat; import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.RequiresApi; import com.example.driverslanguage.MainActivity; import com.example.driverslanguage.R; import com.example.driverslanguage.TimerManager; import com.example.driverslanguage.entity.FreerideOrder; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @ClassName: HaloAccessibilityServiceV5 * @Description: 哈罗出行自动找单, 无障碍服务编程V5 * @Author: wangyetao * @Date: 21-10-6 00:41:24 */ public class HaloAccessibilityServiceV5 extends AccessibilityService { private static String TAG = "HaloAccessibilityServiceV5"; private static int GETORDER_STATE = 0;//0默认值 1抢单结束 2抢单成功 public static AccessibilityService instance; private boolean isStart = false;//抢单任务开启标识 protected static TimerManager timerManager; //定义浮动窗口布局 private LinearLayout mFloatLayout; private TextView tv_targetsection; private WindowManager.LayoutParams wmParams; //创建浮动窗口设置布局参数的对象 private WindowManager mWindowManager; private Button mFloatView; private float DownX, DownY = 0; private float moveX, moveY = 0; private long currentMS, moveTime = 0; String regex_double = "\\d+(?:\\.\\d+)?"; String regex_int = "\\d+(?:\\d+)?"; //android输出语音提示、异步播放语音 private HaloTextToSpeech tts; private String ttsText = ""; @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @Override public void onAccessibilityEvent(AccessibilityEvent event) { // TODO Auto-generated method stub timerManager = TimerManager.getInstance(); if (isStart == true) { int eventType = event.getEventType(); String packageName = event.getPackageName() + ""; String page_eventText = "[" + packageName + "]"; switch (eventType) { case AccessibilityEvent.TYPE_VIEW_CLICKED: page_eventText += "TYPE_VIEW_CLICKED"; //入口1 allAutoOption(page_eventText); break; case AccessibilityEvent.TYPE_VIEW_FOCUSED: page_eventText += "TYPE_VIEW_FOCUSED"; break; case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED: page_eventText += "TYPE_VIEW_LONG_CLICKED"; break; case AccessibilityEvent.TYPE_VIEW_SELECTED: page_eventText += "TYPE_VIEW_SELECTED"; break; case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: page_eventText += "TYPE_VIEW_TEXT_CHANGED"; break; case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: page_eventText += "TYPE_WINDOW_STATE_CHANGED"; //入口2 //allAutoOption(page_eventText); break; case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: page_eventText += "TYPE_NOTIFICATION_STATE_CHANGED"; break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: page_eventText += "TYPE_TOUCH_EXPLORATION_GESTURE_END"; break; case AccessibilityEvent.TYPE_ANNOUNCEMENT: page_eventText += "TYPE_ANNOUNCEMENT"; break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: page_eventText += "TYPE_TOUCH_EXPLORATION_GESTURE_START"; break; case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: page_eventText += "TYPE_VIEW_HOVER_ENTER"; break; case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: page_eventText += "TYPE_VIEW_HOVER_EXIT"; break; case AccessibilityEvent.TYPE_VIEW_SCROLLED: page_eventText += "TYPE_VIEW_SCROLLED"; break; case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: page_eventText += "TYPE_VIEW_TEXT_SELECTION_CHANGED"; break; case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: page_eventText += "TYPE_WINDOW_CONTENT_CHANGED"; break; default: throw new IllegalStateException("Unexpected value: " + eventType); } } } private void allAutoOption(String page_eventText) { try { try { //微调450ms效果较佳,2021年 10月 03日 星期日 20:54:25 CST Thread.sleep(450);//300ms,350ms,400ms,450ms,500ms } catch (InterruptedException e) { e.printStackTrace(); } //获得所有窗体控件 AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow(); //0.确定是哈罗出行应用 if (rootInActiveWindow != null && rootInActiveWindow.getPackageName().equals("com.jingyao.easybike")) { //1.确定是市内路线/城际路线页面 List<AccessibilityNodeInfo> routePage = rootInActiveWindow.findAccessibilityNodeInfosByText("城际路线"); if (routePage != null && routePage.size() > 0) { //通过屏幕显示的内容,比如:下一步、完成等字眼,获取包含该文本的所有节点 //仅适用于城际路线不拼车自动抢单 String text_tip1 = "不拼车"; List<AccessibilityNodeInfo> nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByText(text_tip1); if (nodeInfoList != null && nodeInfoList.size() > 0) { for (int i = 0; i < nodeInfoList.size(); i++) { if (nodeInfoList.get(i).getClassName().equals("android.widget.TextView")) { AccessibilityNodeInfo item_parent = nodeInfoList.get(i).getParent(); FreerideOrder freerideOrder = new FreerideOrder(); int counts = item_parent.getChildCount(); if (counts > 0) { /*for (int j = 0; j < counts; j++) Log.i(TAG, "[" + j + "]" + String.valueOf(item_parent.getChild(j).getText()));*/ freerideOrder.setInsertTime(String.valueOf(System.currentTimeMillis()));//1633338788891 freerideOrder.setPointoftime(String.valueOf(item_parent.getChild(0).getText()));//10月6日 15:00-15:15 freerideOrder.setBegin(String.valueOf(item_parent.getChild(1).getText()));//东莞市·赤马路/赤岗西坊路路口 String tmpStrFromkm = String.valueOf(item_parent.getChild(2).getText());//41.7km,提取出double距离数值 List<Double> result_doubles = new ArrayList<Double>(); Matcher m = Pattern.compile(regex_double, Pattern.MULTILINE).matcher(tmpStrFromkm); while (m.find()) { result_doubles.add(Double.valueOf(m.group())); } freerideOrder.setFromkm(Double.valueOf(result_doubles.get(0)));//41.7 freerideOrder.setEnd(String.valueOf(item_parent.getChild(3).getText()));//广州市·建兴烟酒 freerideOrder.setPrice(Double.valueOf(String.valueOf(item_parent.getChild(4).getText())));//117 String tmpStrPNum = String.valueOf(item_parent.getChild(6).getText()); List<Integer> result_ints = new ArrayList<Integer>(); m = Pattern.compile(regex_int, Pattern.MULTILINE).matcher(tmpStrPNum);//4人,提取出int人数值 while (m.find()) { result_ints.add(Integer.valueOf(m.group())); } freerideOrder.setPeopleNum(Integer.valueOf(result_ints.get(0)));//4 if (counts == 9) { freerideOrder.setType(String.valueOf(item_parent.getChild(8).getText()));//不拼车 } else { freerideOrder.setType(String.valueOf(item_parent.getChild(7).getText()));//不拼车 } /*Log.i(TAG, freerideOrder.toString());*/ //检测订单是否符合指定目标 if (freerideOrder.getPrice() >= MainActivity.targetPriceMin && freerideOrder.getPrice() <= MainActivity.targetPriceMax && freerideOrder.getPrice() / 10 > freerideOrder.getFromkm()) { item_parent.performAction(AccessibilityNodeInfo.ACTION_CLICK); break; } } } } } } //2.确定是订单详情页面-确认同行 List<AccessibilityNodeInfo> detailPage = rootInActiveWindow.findAccessibilityNodeInfosByText("尾号"); if (detailPage != null && detailPage.size() > 0) { //2.2确定是订单详情页面-确认同行 String text_tip2 = "确认同行"; List<AccessibilityNodeInfo> tip2nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByText(text_tip2); if (tip2nodeInfoList != null && tip2nodeInfoList.size() > 0) { //微调点击3次效果较佳,2021年 10月 04日 星期一 12:54:03 CST tip2nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); tip2nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); tip2nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); } //2.4确定是订单详情页面-抢单成功 String text_tip4 = "出发并通知乘客"; List<AccessibilityNodeInfo> tip4nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByText(text_tip4); if (tip4nodeInfoList != null && tip4nodeInfoList.size() > 0) { GETORDER_STATE = 2;//抢单成功标识 stopTask(mFloatView, timerManager); } } //2.3确定是订单详情页面-确定 List<AccessibilityNodeInfo> popCheckTimePage = rootInActiveWindow.findAccessibilityNodeInfosByText("请选择到达乘客起点的时间"); if (popCheckTimePage != null && popCheckTimePage.size() > 0) { String text_tip3 = "确定"; List<AccessibilityNodeInfo> tip3nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByText(text_tip3); if (tip3nodeInfoList != null && tip3nodeInfoList.size() > 0) { //微调点击3次效果较佳,2021年 10月 04日 星期一 12:55:03 CST tip3nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); tip3nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); tip3nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); //2.3.2确定是订单详情页面-抢单结束 GETORDER_STATE = 1;//抢单结束 stopTask(mFloatView, timerManager); } } } } catch (Exception e) { } } @Override public void onInterrupt() { } @Override protected void onServiceConnected() { super.onServiceConnected(); instance = this; } @Override public void onCreate() { super.onCreate(); createFloatView(); tts = new HaloTextToSpeech(getApplicationContext()); } @SuppressLint("ClickableViewAccessibility") private void createFloatView() { wmParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_TOAST, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); //通过getApplication获取的是WindowManagerImpl.CompatModeWrapper mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE); //设置window type //wmParams.type = WindowManager.LayoutParams.TYPE_PHONE; wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; //设置图片格式,效果为背景透明 wmParams.format = PixelFormat.RGBA_8888; //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; //调整悬浮窗显示的停靠位置为左侧置顶 wmParams.gravity = Gravity.LEFT | Gravity.TOP; // 以屏幕左上角为原点,设置x、y初始值,相对于gravity wmParams.x = 0; wmParams.y = 152; //设置悬浮窗口长宽数据 wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; LayoutInflater inflater = LayoutInflater.from(getApplication()); //获取浮动窗口视图所在布局 mFloatLayout = (LinearLayout) inflater.inflate(R.layout.alert_window_menu, null); //添加mFloatLayout mWindowManager.addView(mFloatLayout, wmParams); //浮动窗口按钮 mFloatView = (Button) mFloatLayout.findViewById(R.id.alert_window_imagebtn); tv_targetsection = mFloatLayout.findViewById(R.id.tv_targetsection); mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); //初始化设置监听价格区间 tv_targetsection.setText(MainActivity.targetPriceMin + "-" + MainActivity.targetPriceMax); //设置监听浮动窗口的触摸移动 mFloatView.setOnTouchListener(new View.OnTouchListener() { @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: DownX = event.getX(); DownY = event.getY(); currentMS = System.currentTimeMillis(); break; case MotionEvent.ACTION_MOVE: DownX = event.getX(); DownY = event.getY(); moveX += Math.abs(event.getRawX() - DownX);//x轴移动距离 moveY += Math.abs(event.getRawY() - DownY);//y轴移动距离 if ((DownX - event.getRawX()) > 20 || (event.getRawX() - DownX) > 20 || (DownY - event.getRawY()) > 20 || (event.getRawY() - DownY) > 20 ) { // getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标 wmParams.x = (int) event.getRawX() - mFloatView.getMeasuredWidth() / 2; // 减75为状态栏的高度 wmParams.y = (int) event.getRawY() - mFloatView.getMeasuredHeight() / 2 - 75; // 刷新 mWindowManager.updateViewLayout(mFloatLayout, wmParams); } break; case MotionEvent.ACTION_UP: moveTime = System.currentTimeMillis() - currentMS; //判断是滑动还是点击操作、判断是否继续传递信号 if (moveTime < 300 && moveX < 20 && moveY < 20) {//点击事件 return false; } else {//滑动事件 wmParams.x = (int) event.getRawX(); wmParams.y = (int) event.getRawY() - mFloatView.getMeasuredHeight() / 2 - 75; // 通过Resources获取屏幕宽高及尺寸 DisplayMetrics dm2 = getResources().getDisplayMetrics(); int widthPixels = dm2.widthPixels; // 重置wmParams参数后刷新view,实现浮动按钮横向贴边效果 if (wmParams.x > widthPixels / 2) { wmParams.x = widthPixels; } else { wmParams.x = 0; } // 刷新 mWindowManager.updateViewLayout(mFloatLayout, wmParams); moveX = moveY = 0;//归零 return true;//返回true,表示不再执行后面的事件 } default: break; } return false; } }); mFloatView.setOnClickListener(new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onClick(View v) { //TODO home //performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); isStart = !isStart;//取反 if (isStart == true) { startTask(mFloatView, timerManager); } else { stopTask(mFloatView, timerManager); } } }); mFloatView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { //TODO home return true; } }); } private void stopTask(Button mFloatView, TimerManager timerManager) { //开启HaloAccessibilityServiceV2服务,仅修改按钮颜色 if (GETORDER_STATE == 2) { tts.notifyNewMessage("哈罗顺风车抢到订单啦,赶快去查看一下吧"); } else if (GETORDER_STATE == 1) { tts.notifyNewMessage("哈罗顺风车抢单结束啦,赶快去查看一下结果吧"); tts.notifyNewMessage("哈罗顺风车抢单结束啦,赶快去查看一下结果吧"); tts.notifyNewMessage("哈罗顺风车抢单结束啦,赶快去查看一下结果吧"); } else { tts.notifyNewMessage("停止找单任务"); } isStart = false; GETORDER_STATE = 0;//重置 mFloatView.setText("开启"); tv_targetsection.setText(MainActivity.targetPriceMin + "-" + MainActivity.targetPriceMax); mFloatView.setBackgroundResource(R.drawable.dw_teal_200); //timerManager.stopTimerTask(); } private void startTask(Button mFloatView, TimerManager timerManager) { //停止HaloAccessibilityServiceV2服务,仅修改按钮颜色 tts.notifyNewMessage("开启找单任务" + MainActivity.targetPriceMin + "-" + MainActivity.targetPriceMax); isStart = true; GETORDER_STATE = 0;//重置 mFloatView.setText("停止"); tv_targetsection.setText(MainActivity.targetPriceMin + "-" + MainActivity.targetPriceMax); mFloatView.setBackgroundResource(R.drawable.dw_red); //timerManager.startTimerTask(); } }
部分Log日志与运行截图(如需完整源码请邮件wangyetao_binary@126.com联系博主):
城际路线 2021-10-06 02:51:04.218 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [0]10月12日 10:00-10:15 2021-10-06 02:51:04.224 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [1]东莞市·华为(凤岗店) 2021-10-06 02:51:04.234 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [2]61.4km 2021-10-06 02:51:04.238 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [3]深圳市·深圳南北小筑客栈 2021-10-06 02:51:04.243 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [4]93.1 2021-10-06 02:51:04.252 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [5]元 2021-10-06 02:51:04.266 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [6]3人 2021-10-06 02:51:04.266 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [7]不拼车 2021-10-06 02:51:04.269 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: FreerideOrder{insertTime='1633459864266', pointoftime='10月12日 10:00-10:15', begin='东莞市·华为(凤岗店)', end='深圳市·深圳南北小筑客栈', fromkm=61.4, price=93.1, peopleNum=3, type='不拼车'} 市内路线 2021-10-06 02:52:32.808 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [0]明天 15:30-15:40 2021-10-06 02:52:32.818 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [1]距你12.6km 2021-10-06 02:52:32.822 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [2]石龙广场 2021-10-06 02:52:32.827 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [3]东莞市·南岗 2021-10-06 02:52:32.833 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [4]广州新华学院东莞校区(西门) 2021-10-06 02:52:32.837 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [5]62.7 2021-10-06 02:52:32.841 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [6]元 2021-10-06 02:52:32.849 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [7]已预付 2021-10-06 02:52:32.852 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [8]2人 2021-10-06 02:52:32.852 22973-22973/com.example.driverslanguage I/HaloAccessibilityServiceV5: [9]不拼车
在此记录与总结,2021年 10月 06日 星期三 02:29:23 CST。
免责声明:本程序仅为辅助点击工具与IT开发、测试人员之间交流学习使用,无任何黑科技功能,禁止用于任何违法违规行为,否则后果自负,请遵纪守法。