前言
公司项目需求:为App提供一键优化功能(一键获取相关所有权限),确保App在优化过后能够常驻手机,为用户提供服务。——商户类App,用户非主动关闭时常驻手机合情合理
阅读源码
getWindows()和getRootInActiveWindow()
List<AccessibilityNodeInfo> getWindows()
即:获取当前页面所有的节点——包括顶部状态栏、底部系统按钮等各自的root节点。AccessibilityNodeInfo getRootInActiveWindow()
即:获取当前活动页面(Activity)中的root节点。getRootInActiveWindow()
方法在页面未加载完成时可能为null,而getWindows()
一直有值。AccessibilityNodeInfo
AccessibllityNodeInfo
类是含有链表结构的类(应该是这样称呼吧)。譬如:
1
2
3
4
5
6
7
8
9
10
11class AccessibilityNodeInfo implements Parcelable{
...
public AccessibilityNodeInfo getParent() {
throw new RuntimeException("Stub!");
}
public AccessibilityNodeInfo getChild(int index) {
throw new RuntimeException("Stub!");
}
...
}AccessibllityNodeInfo
页包含了对View的操作(仅包含有用到的部分):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class AccessibilityNodeInfo implements Parcelable{
...
//点击事件
public static final int ACTION_CLICK = 16;
//朝后滚动 performAction返回false即滑动到顶部
public static final int ACTION_SCROLL_BACKWARD = 8192;
//朝前滚动 performAction返回false即滑动到底部
public static final int ACTION_SCROLL_FORWARD = 4096;
...
public boolean performAction(int action) {
throw new RuntimeException("Stub!");
}
public boolean performAction(int action, Bundle arguments) {
throw new RuntimeException("Stub!");
}
...
}全局事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14public abstract class AccessibilityService extends Service {
...
//返回键
public static final int GLOBAL_ACTION_BACK = 1;
//Home键
public static final int GLOBAL_ACTION_HOME = 2;
//菜单键
public static final int GLOBAL_ACTION_RECENTS = 3;
...
public final boolean performGlobalAction(int action) {
throw new RuntimeException("Stub!");
}
...
}
封装工具
了解完AccessibilityService后,可以对Accessibility进行简单的封装,方便使用。
获取root节点
由上可知,在寻找目标的root节点这方面,
getRootInActiveWindow()
会根据有优势,所以1
2
3
4
5
6/**
* @return 获取root节点
*/
private AccessibilityNodeInfo findRoot() {
return getRootInActiveWindow();
}获取所有当前Activity可见节点
遍历多叉树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* 遍历多叉树 全遍历
*/
private List<AccessibilityNodeInfo> iteratorTree(AccessibilityNodeInfo parent) {
List<AccessibilityNodeInfo> childList = new ArrayList<>();
if (parent == null) return childList;
for (int i = 0; i < parent.getChildCount(); i++) {
AccessibilityNodeInfo child = parent.getChild(i);
childList.add(child);
if (child.getChildCount() > 0) {
//利用递归方法
childList.addAll(iteratorTree(child));
}
}
return childList;
}获取全部可见节点
1
2
3
4
5
6
7
8
9
10/**
* 遍历当前页面所有节点
*/
private List<AccessibilityNodeInfo> findAllView() {
AccessibilityNodeInfo parent = findRoot();
List<AccessibilityNodeInfo> list = new ArrayList<>();
list.add(parent);
list.addAll(iteratorTree(parent));
return list;
}查找第一个匹配节点
马后炮:在Activity中,可能存在
ListView
、RecyclerView
等滑动控件使得目标节点出现超出屏幕情况,所以我们应当先在当前可见区域内查找是否有目标节点,如果没有,则向下滚动,再次查找目标节点——直至滑动到屏幕底部。为防止当前界面并非从最顶部开始向下滑动,故应该再向上滑动到顶部一次。查找滑动组件代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29private static final List<String> SCROLL_CLASS_NAME_ARRAY = Arrays.asList(
"android.widget.ListView",
"android.support.v7.widget.RecyclerView",
"androidx.recyclerview.widget.RecyclerView"
);
/**
* 查找可滑动的view
*/
private List<AccessibilityNodeInfo> findCanScrollView() {
List<AccessibilityNodeInfo> viewList = findAllView();
List<AccessibilityNodeInfo> scrollViewList = new ArrayList<>();
for (AccessibilityNodeInfo info : viewList) {
if (info != null)
//info.isScrollable判断是否为可滚动控件
if (info.isScrollable()) {
//其中滚动控件还包含了spinner、viewpage等,而这些控件目前业务中并没有需要(且会影响业务内容),因此做了个限定
for (String scrollClassName : SCROLL_CLASS_NAME_ARRAY) {
if (scrollClassName.equals(info.getClassName().toString())) {
scrollViewList.add(info);
setLog("------------------------------------------------------------");
setLog("查询到可滑动组件" + info.getViewIdResourceName());
setLog("查询到可滑动组件" + info.getClassName());
setLog("------------------------------------------------------------");
}
}
}
}
return scrollViewList;
}遍历多叉树,根据条件查寻当前可见部分的节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42/**
* 遍历多叉树 查询方法
* 找到控件会抛出异常,异常中包含目标数据
*/
private void findIteratorTree(AccessibilityNodeInfo parent, String text) throws StopIteratorException {
if (parent != null)
for (int i = 0; i < parent.getChildCount(); i++) {
AccessibilityNodeInfo child = parent.getChild(i);
if (child != null)
if (multiCriteriaMatching(child, text)) {
throw new StopIteratorException(child);
} else if (child.getChildCount() > 0) {
findIteratorTree(child, text);
}
}
}
/**
* 自定义报错 用于中断递归
*/
private static class StopIteratorException extends RuntimeException {
private AccessibilityNodeInfo info;
private StopIteratorException(AccessibilityNodeInfo info) {
this.info = info;
}
private AccessibilityNodeInfo getInfo() {
return info;
}
}
/**
* 多条件匹配 multiCriteriaMatching
*
* @param info 目前包含getViewIdResourceName、getClassName、getText
* @param text 匹配词
* @return 返回匹配结果
*/
private boolean multiCriteriaMatching(AccessibilityNodeInfo info, String text) {
return text.equals(info.getViewIdResourceName())
|| text.equals(info.getClassName() == null ? null : info.getClassName().toString())
|| text.equals(info.getText() == null ? null : info.getText().toString());
}查找第一个匹配节点代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56/**
* @param text 要查找目标组件的标识 (className/resourceId/text)
* @return 返回查找到的目标组件 null则表示查找失败
*/
private AccessibilityNodeInfo findFirst(String text) {
AccessibilityNodeInfo info = null;
try {
findIteratorTree(findRoot(), text);
int searchNum = 0;
boolean scroll = true, isForward = true;
List<AccessibilityNodeInfo> scrollViewList = new ArrayList<>();
while (scroll) {//利用关键字scroll和Exception跳出循环
//找不到控件,向下滑动继续找
if (scrollViewList.size() == 0) {
if (searchNum < MAX_SEARCH_NUM) {
++searchNum;
scrollViewList.addAll(findCanScrollView());
} else {
//找不到可滑动组件
setLog("找不到可滑动组件,结束循环体,查找次数:" + MAX_SEARCH_NUM);
scroll = false;
}
}
if (scrollViewList.size() != 0) {
//如果查找到滑动组件,则滑动滑动组件
//下、上各滑动一次,确保找到控件
for (AccessibilityNodeInfo scrollViewInfo : scrollViewList) {
if (isForward) {
if (!scrollViewInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) {
isForward = false;//滑动到底部,修改滑动状态,再滑一次
}
} else {
if (!scrollViewInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)) {
scroll = false;
}
}
}
}
//滑动后 睡眠 之后查找组件
SystemClock.sleep(TIME_SLEEP);
findIteratorTree(findRoot(), text);
}
} catch (StopIteratorException e) {
info = e.getInfo();
if (info != null) {
setLog("------------------------------------------------------------");
setLog("查询到目标组件id:" + info.getViewIdResourceName());
setLog("查询到目标组件text:" + info.getText());
setLog("查询到目标组件clsName:" + info.getClassName());
setLog("------------------------------------------------------------");
}
}
return info;
}补充说明:(可不看)在查看AccessibilityNodeInfo类中有如下方法
1
2
3
4
5
6
7
8
9
10
11class AccessibilityNodeInfo implements Parcelable{
...
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
throw new RuntimeException("Stub!");
}
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
throw new RuntimeException("Stub!");
}
...
}由此联想到
findIteratorTree(AccessibilityNodeInfo parent, String text)
或许可以改成:(以下代码未尝试,只是临时想到的改进方案)1
2
3
4
5
6
7
8
9
10
11
12private AccessibilityNodeInfo findIteratorTree(AccessibilityNodeInfo parent, String text){
List<AccessibilityNodeInfo> infoList = new ArrayList<>();
infoList.addAll(parent.findAccessibilityNodeInfosByText(text));
if(info.size() == 0){
infoList.addAll(parent.findAccessibilityNodeInfosByViewId(text));
}
AccessibilityNodeInfo info = null;
if(infoList.size() > 0){
info = infoList.get(0);
}
return info;
}点击事件
找到目标节点后,自然是要对目标节点进行操作——这里仅封装点击事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* 点击事件
*
* @param info 要点击的目标View
* @return 点击结果(成功/失败)
*/
private boolean clickView(AccessibilityNodeInfo info) {
if (info != null)
if (info.isClickable()) {
info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
} else {
AccessibilityNodeInfo parent = info.getParent();
if (parent != null) {
boolean b = clickView(parent);
parent.recycle();
return b;
}
}
return false;
}将查询节点与点击节点组合起来就是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 点击事件(点击本身)
*
* @param text 要点击的目标节点标识
* @return 点击结果(true/false)
*/
public boolean clickFirstView(String text) {
AccessibilityNodeInfo info = findFirst(text);
if (info == null) {
setLog(text + "找不到匹配的控件");
return false;
}
return clickView(info);
}那如果是CheckBox之类的点击事件(如下图)应该怎么处理?
首先转化为查询该CheckBox的同级节点,再通过该同级节点反查父节点,之后再查询父节点中的子节点中包含
isCheckable
的节点:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21private AccessibilityNodeInfo findBrotherCheckBox(AccessibilityNodeInfo child) {
AccessibilityNodeInfo checkBoxInfo = null;
AccessibilityNodeInfo parent = child.getParent();
if (parent != null) {
List<AccessibilityNodeInfo> infoList = iteratorTree(parent);
setLog("------------------------------------------------------------");
for (AccessibilityNodeInfo info : infoList) {
if (info.isCheckable()) {
checkBoxInfo = info;
setLog("------------------------------------------------------------");
setLog("查询到目标组件id:" + info.getViewIdResourceName());
setLog("查询到目标组件text:" + info.getText());
setLog("查询到目标组件clsName:" + info.getClassName());
setLog("------------------------------------------------------------");
break;
}
}
setLog("------------------------------------------------------------");
}
return checkBoxInfo;
}CheckBox节点中有
isChecked
字段用于判断是( true )否( false )选中,因此有:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**
* checkBox的点击事件(带选中状态)
*
* @param brotherText 要点击目标的兄弟节点标识
* @param setChecked 将checkBox的状态修改为true/false
* @return 返回点击事件触发结果(true/false)
*/
public boolean clickCheckBox(String brotherText, boolean setChecked) {
AccessibilityNodeInfo brotherInfo = findFirst(brotherText);
if (brotherInfo == null) {
setLog(brotherText + "找不到匹配的控件");
return false;
}
AccessibilityNodeInfo checkBoxInfo = findBrotherCheckBox(brotherInfo);
if (checkBoxInfo == null) {
setLog(brotherText + "找不到匹配的checkBox控件");
return false;
}
if (checkBoxInfo.isChecked() != setChecked) {
return clickView(checkBoxInfo);
} else {
setLog("checkBox控件为目标状态,不点击");
}
return false;
}补充
静态变量、日志与返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//静态变量
private static final int TIME_SLEEP = 300;//单位毫秒
private static final int MAX_SEARCH_NUM = 20;//查找循环体次数
//日志
protected abstract boolean isDebugger();
private void setLog(String msg) {
if (isDebugger()) {
String tag = "AManagerLog";
Log.i(tag, msg);
}
}
//系统返回键
public void goBack() {
performGlobalAction(GLOBAL_ACTION_BACK);
}AndroidManifest.xml中的配置(AccessibilityService的父类是Service)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<manifest>
<application>
<service
android:name=".MyAccessibilityService"
android:enabled="true"
android:exported="true"
android:label="@string/accessibility_label"
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/accessibility_config" />
</service>
</application>
</manifest>accessibility_config.xml中的配置
1
2
3
4
5
6
7
8
9
10
11
12
<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/accessibility_desc"
android:notificationTimeout="100"
android:packageNames="com.yooking.accessibility,com.android.settings,com.android.systemui,com.huawei.systemmanager,com.miui.securitycenter" >
</accessibility-service>以上就是整个工具的封装了
如何使用
辅助功能触发点的设置
辅助功能顾名思义,就是辅助用户使用当前App,甚至可以延伸为使用当前手机。比如:
- 监测App接收到的通知消息,根据消息对手机进行辅助操作
- 监测文件下载状态,根据下载状态进行自动安装、打开等操作
- 当用户点击了某个位置时,根据点击的位置做出反馈
- 为用户自动开启相关权限等
- …
这时候:监测到App接收通知消息和监测到文件下载状态即为触发点;用户点击事件即为出发点;而开启权限,则可以是在辅助功能权限开启的时候。
触发点为辅助功能权限开启:辅助功能权限开启的时候会调用初始化Service方法:
onServiceConnected()
,这里可以作为一个监测点,利用sendBroadcast(Intent intent)
将服务启动的消息广播出去。而广播接收器就是真实的触发点。触发点为某种状态:
1
2
3
4
5
6
7
8
9
10if(该状态可回调){
if(辅助功能权限开启){
直接执行辅助功能
}else{
弹出无障碍设置页面,引导用户开启辅助功能权限-触发点转为辅助功能权限开启时
}
}else{
监测状态改变引发的界面变化
...
}触发点为用户点击事件:如果点击的是自己的App,在点击事件触发辅助功能即可。如果点击的不为自己的App,可以在
onAccessibilityEvent(AccessibilityEvent event)
触发。辅助功能执行
辅助功能是一个富有想象力的功能,TA的执行能力取决于脑洞。比如模拟用户点击,让用户的操作更快捷(比如自动抢红包、一键连招等)或者取代繁琐操作(自动下载并安装等)甚至也可以是智能分词翻译(锤子BigBang)等等…
这里以“取代繁琐操作”为例——征得用户同意后,替用户勾选一些无法直接申请的权限:
任务:
AssignmentEntity
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class AssignmentEntity {
private Queue<StepEntity> queue = new LinkedList(); //步骤队列
private String name; //任务名称
public AssignmentEntity() {
}
public String getName() {
return this.name;
}
public AssignmentEntity setName(String name) {
this.name = name;
return this;
}
public Queue<StepEntity> getQueue() {
return this.queue;
}
/**
* 添加步骤
**/
public AssignmentEntity addStep(StepEntity entity) {
this.queue.add(entity);
return this;
}
}具体步骤:
StepEntity
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45public class StepEntity {
public static final int TYPE_INTENT = 0; //页面跳转
public static final int TYPE_CLICK = 1; //点击事件
public static final int TYPE_CHECKED = 2; //选中事件
public static final int TYPE_BACK = 3; //返回事件
private String name; //目标控件名称
private int type; //事件类型
private boolean checked; //选中事件-true:选中-false:反选
private Intent intent; //跳转事件-Intent
public StepEntity() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return this.type;
}
public void setType(int type) {
this.type = type;
}
public Intent getIntent() {
return this.intent;
}
public void setIntent(Intent intent) {
this.intent = intent;
}
public boolean isChecked() {
return this.checked;
}
public void setChecked(boolean checked) {
this.checked = checked;
}
}步骤辅助类
StepHelper
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54public class StepHelper {
public StepHelper() {
}
/**
* 跳转事件
**/
public static StepEntity intentStep(Intent intent) {
return getStep("页面跳转", 0, intent, (Boolean)null);
}
/**
* 点击事件
**/
public static StepEntity clickStep(String text) {
return getStep(text, 1, (Intent)null, (Boolean)null);
}
/**
* 选中事件
**/
public static StepEntity checkStep(String text, boolean setChecked) {
return getStep(text, 2, (Intent)null, setChecked);
}
/**
* 返回事件
**/
public static StepEntity backStep() {
return getStep("返回上一页", 3, (Intent)null, (Boolean)null);
}
private static StepEntity getStep(String name, int type, @Nullable Intent intent, @Nullable Boolean setChecked) {
StepEntity step = new StepEntity();
step.setName(name);
step.setType(type);
if (0 == type) {
if (intent == null) {
throw new IllegalArgumentException("代码错误,Type为TYPE_INTENT时Intent参数不能为空");
}
step.setIntent(intent);
} else if (2 == type) {
if (setChecked == null) {
throw new IllegalArgumentException("代码错误,Type为TYPE_CHECKED时setChecked参数不能为空");
}
boolean b = setChecked;
step.setChecked(b);
}
return step;
}
}添加任务示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105public class AssignmentFactory {
...
public static Queue<AssignmentEntity> create() {
Queue<AssignmentEntity> queue = new LinkedList<>();
if (RomUtils.isHuawei()) {
queue.addAll(HuaweiFactory.create());
} else if (RomUtils.isMiui()) {
queue.addAll(XiaomiFactory.create());
} else if (RomUtils.isOppo()) {
} else if (RomUtils.isMeizu()) {
queue.addAll(MeizuFactory.create());
}
return queue;
}
...
static class HuaweiFactory {
private static int getVersion() {
int version = -1;
try {
String systemProperty = RomUtils.getSystemProperty("ro.build.version.emui");
if (systemProperty != null) {
String trim = systemProperty.replace("EmotionUI", "").replace("_", "").trim();
if (trim.contains(".")) {
trim = trim.substring(0, trim.indexOf("."));
}
version = Integer.valueOf(trim);
}
} catch (Exception ignored) {
}
return version;
}
static Queue<AssignmentEntity> create() {
L.i("手机版本号:" + getVersion()); //目前适配10
Queue<AssignmentEntity> queue = new LinkedList<>();
queue.add(ignoreBatteryOptimization());
queue.addAll(newMessageNotification());
queue.add(selfStarting());
return queue;
}
//忽略电池优化
private static AssignmentEntity ignoreBatteryOptimization() {
AssignmentEntity assignment = new AssignmentEntity();
assignment.setName("忽略电池优化");
assignment.addStep(StepHelper.intentStep(IntentUtils.hightPowerManger()))
.addStep(StepHelper.clickStep("不允许"))
.addStep(StepHelper.clickStep("所有应用"))
.addStep(StepHelper.clickStep(BaseApplication.getInstance().getAppName()))
.addStep(StepHelper.clickStep("不允许"))
.addStep(StepHelper.clickStep("确定"))
.addStep(StepHelper.backStep());
return assignment;
}
//新消息通知
private static Queue<AssignmentEntity> newMessageNotification() {
AssignmentEntity ae1 = new AssignmentEntity();
ae1.setName("新消息通知");
AssignmentEntity ae2 = new AssignmentEntity();
ae2.setName("通知锁屏显示");
ae1.addStep(StepHelper.intentStep(IntentUtils.huaweiNotification()))
.addStep(StepHelper.clickStep(BaseApplication.getInstance().getAppName()))
.addStep(StepHelper.checkStep("允许通知", true))
.addStep(StepHelper.backStep());
ae2.addStep(StepHelper.clickStep("锁屏通知"))
.addStep(StepHelper.clickStep("显示所有通知"))
.addStep(StepHelper.clickStep("更多通知设置"))
.addStep(StepHelper.checkStep("通知亮屏提示", true))
.addStep(StepHelper.backStep())
.addStep(StepHelper.backStep());
Queue<AssignmentEntity> queue = new LinkedList<>();
queue.add(ae1);
queue.add(ae2);
return queue;
}
//自启动
private static AssignmentEntity selfStarting() {
AssignmentEntity assignment = new AssignmentEntity();
assignment.setName("自启动");
assignment.addStep(StepHelper.intentStep(IntentUtils.huaweiStartupNormalApp()))
.addStep(StepHelper.checkStep(BaseApplication.getInstance().getAppName(), true))
.addStep(StepHelper.checkStep(BaseApplication.getInstance().getAppName(), false))
.addStep(StepHelper.checkStep("允许自启动", true))
.addStep(StepHelper.checkStep("允许关联启动", true))
.addStep(StepHelper.checkStep("允许后台活动", true))
.addStep(StepHelper.clickStep("确定"))
.addStep(StepHelper.backStep());
return assignment;
}
}
...
}执行任务示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60public class AssignmentFactory {
...
public static void run(Activity activity, Queue<AssignmentEntity> queue) {
L.i("队列开始执行");
int assignmentSize = queue.size();
CompositeDisposable co = new CompositeDisposable();
Disposable subscribe = Observable.create(
(ObservableOnSubscribe<AssignmentEntity>) emitter -> {
int progressSize = 0;
for (int i = 0; i < assignmentSize; i++) {
AssignmentEntity entity = queue.poll();
if (entity != null) {
Queue<StepEntity> stepQueue = entity.getQueue();
if (stepQueue != null) {
int stepSize = stepQueue.size();
for (int j = 0; j < stepSize; j++) {
progressSize++;
emitter.onNext(entity);
}
}
}
}
//FloatWindowView.getInstance().progressBar.setMax(progressSize);
emitter.onComplete();
})
.subscribeOn(Schedulers.io())//执行在io线程
.observeOn(Schedulers.io())//回调在io线程 //主线程阻塞将无法更新ui AndroidSchedulers.mainThread()
.subscribe(
assignment -> {
//FloatWindowView.getInstance().progressAdd();
//L.i("当前进度:" + assignment.getName() + "===" + FloatWindowView.getInstance().getProgress());
poll(activity, assignment.getQueue().poll());
},
throwable -> L.e("", throwable),
() -> //FloatWindowView.getInstance().stopFloatWindow()
);
co.add(subscribe);
}
private static void poll(Activity activity, StepEntity poll) {
if (poll == null) return;
switch (poll.getType()) {
case StepEntity.TYPE_INTENT://跳转事件
activity.startActivity(poll.getIntent());
break;
case StepEntity.TYPE_CLICK://点击事件
MyAccessibilityService.getInstance().clickFirstView(poll.getName());
break;
case StepEntity.TYPE_CHECKED://选中事件
MyAccessibilityService.getInstance().clickCheckBox(poll.getName(), poll.isChecked());
break;
case StepEntity.TYPE_BACK://返回事件
MyAccessibilityService.getInstance().goBack();
break;
}
SystemClock.sleep(POST_DELAY_MILLIS);
}
...
}链接
完整demo:Github