0%

Android辅助功能开发-一键优化

前言

公司项目需求:为App提供一键优化功能(一键获取相关所有权限),确保App在优化过后能够常驻手机,为用户提供服务。——商户类App,用户非主动关闭时常驻手机合情合理

阅读源码

  1. getWindows()和getRootInActiveWindow()

    List<AccessibilityNodeInfo> getWindows() 即:获取当前页面所有的节点——包括顶部状态栏、底部系统按钮等各自的root节点。

    AccessibilityNodeInfo getRootInActiveWindow()即:获取当前活动页面(Activity)中的root节点。

    getRootInActiveWindow()方法在页面未加载完成时可能为null,而getWindows()一直有值。

  2. AccessibilityNodeInfo

    AccessibllityNodeInfo类是含有链表结构的类(应该是这样称呼吧)。

    譬如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class 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
    18
    class 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!");
    }
    ...
    }
  3. 全局事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public 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进行简单的封装,方便使用。

  1. 获取root节点

    由上可知,在寻找目标的root节点这方面,getRootInActiveWindow()会根据有优势,所以

    1
    2
    3
    4
    5
    6
    /**
    * @return 获取root节点
    */
    private AccessibilityNodeInfo findRoot() {
    return getRootInActiveWindow();
    }
  2. 获取所有当前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;
    }
  3. 查找第一个匹配节点

    马后炮:在Activity中,可能存在ListViewRecyclerView滑动控件使得目标节点出现超出屏幕情况,所以我们应当先在当前可见区域内查找是否有目标节点,如果没有,则向下滚动,再次查找目标节点——直至滑动到屏幕底部。为防止当前界面并非从最顶部开始向下滑动,故应该再向上滑动到顶部一次。

    查找滑动组件代码如下:

    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
    private 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
    11
    class 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
    12
    private 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;
    }
  4. 点击事件

    找到目标节点后,自然是要对目标节点进行操作——这里仅封装点击事件

    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之类的点击事件(如下图)应该怎么处理?
    1.jpg

    首先转化为查询该CheckBox的同级节点,再通过该同级节点反查父节点,之后再查询父节点中的子节点中包含isCheckable的节点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private 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;
    }
  5. 补充

    静态变量、日志与返回

    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
    <?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/accessibility_desc"
    android:notificationTimeout="100"
    android:packageNames="com.yooking.accessibility,com.android.settings,com.android.systemui,com.huawei.systemmanager,com.miui.securitycenter" >

    </accessibility-service>

    以上就是整个工具的封装了

如何使用

  1. 辅助功能触发点的设置

    辅助功能顾名思义,就是辅助用户使用当前App,甚至可以延伸为使用当前手机。比如:

    • 监测App接收到的通知消息,根据消息对手机进行辅助操作
    • 监测文件下载状态,根据下载状态进行自动安装、打开等操作
    • 当用户点击了某个位置时,根据点击的位置做出反馈
    • 为用户自动开启相关权限等

    这时候:监测到App接收通知消息和监测到文件下载状态即为触发点;用户点击事件即为出发点;而开启权限,则可以是在辅助功能权限开启的时候。

    触发点为辅助功能权限开启:辅助功能权限开启的时候会调用初始化Service方法:onServiceConnected(),这里可以作为一个监测点,利用sendBroadcast(Intent intent)服务启动的消息广播出去。而广播接收器就是真实的触发点

    触发点为某种状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if(该状态可回调){
    if(辅助功能权限开启){
    直接执行辅助功能
    }else{
    弹出无障碍设置页面,引导用户开启辅助功能权限-触发点转为辅助功能权限开启时
    }
    }else{
    监测状态改变引发的界面变化
    ...
    }

    触发点为用户点击事件:如果点击的是自己的App,在点击事件触发辅助功能即可。如果点击的不为自己的App,可以在onAccessibilityEvent(AccessibilityEvent event)触发。

  2. 辅助功能执行

    辅助功能是一个富有想象力的功能,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
    28
    public 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
    45
    public 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
    54
    public 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
    105
    public 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
    60
    public 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

------------本文结束感谢您的阅读------------

Thank you for your accept!