個(gè)人博客地址:https://blog.yookingh.cn
該文章地址:https://blog.yookingh.cn/dev/200424-accessibilityService.html
前言
公司項(xiàng)目需求:為App提供一鍵優(yōu)化功能(一鍵獲取相關(guān)所有權(quán)限)喊暖,確保App在優(yōu)化過后能夠常駐手機(jī)伟桅,為用戶提供服務(wù)渐裸÷央梗——商戶類App忘蟹,用戶非主動(dòng)關(guān)閉時(shí)常駐手機(jī)合情合理
閱讀源碼
-
getWindows()和getRootInActiveWindow()
List<AccessibilityNodeInfo> getWindows()
即:獲取當(dāng)前頁面所有的節(jié)點(diǎn)——包括頂部狀態(tài)欄铣揉、底部系統(tǒng)按鈕等各自的root節(jié)點(diǎn)窜骄。AccessibilityNodeInfo getRootInActiveWindow()
即:獲取當(dāng)前活動(dòng)頁面(Activity)中的root節(jié)點(diǎn)娶牌。getRootInActiveWindow()
方法在頁面未加載完成時(shí)可能為null奔浅,而getWindows()
一直有值。 -
AccessibilityNodeInfo
AccessibllityNodeInfo
類是含有鏈表結(jié)構(gòu)的類(應(yīng)該是這樣稱呼吧)诗良。譬如:
class AccessibilityNodeInfo implements Parcelable{ ... public AccessibilityNodeInfo getParent() { throw new RuntimeException("Stub!"); } public AccessibilityNodeInfo getChild(int index) { throw new RuntimeException("Stub!"); } ... }
AccessibllityNodeInfo
頁包含了對View的操作(僅包含有用到的部分):class AccessibilityNodeInfo implements Parcelable{ ... //點(diǎn)擊事件 public static final int ACTION_CLICK = 16; //朝后滾動(dòng) performAction返回false即滑動(dòng)到頂部 public static final int ACTION_SCROLL_BACKWARD = 8192; //朝前滾動(dòng) performAction返回false即滑動(dòng)到底部 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!"); } ... }
-
全局事件
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進(jìn)行簡單的封裝,方便使用鉴裹。
-
獲取root節(jié)點(diǎn)
由上可知舞骆,在尋找目標(biāo)的root節(jié)點(diǎn)這方面钥弯,
getRootInActiveWindow()
會(huì)根據(jù)有優(yōu)勢,所以/** * @return 獲取root節(jié)點(diǎn) */ private AccessibilityNodeInfo findRoot() { return getRootInActiveWindow(); }
-
獲取所有當(dāng)前Activity可見節(jié)點(diǎn)
遍歷多叉樹
/** * 遍歷多叉樹 全遍歷 */ 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; }
獲取全部可見節(jié)點(diǎn)
/** * 遍歷當(dāng)前頁面所有節(jié)點(diǎn) */ private List<AccessibilityNodeInfo> findAllView() { AccessibilityNodeInfo parent = findRoot(); List<AccessibilityNodeInfo> list = new ArrayList<>(); list.add(parent); list.addAll(iteratorTree(parent)); return list; }
-
查找第一個(gè)匹配節(jié)點(diǎn)
馬后炮:在Activity中督禽,可能存在
ListView
脆霎、RecyclerView
等滑動(dòng)控件使得目標(biāo)節(jié)點(diǎn)出現(xiàn)超出屏幕情況,所以我們應(yīng)當(dāng)先在當(dāng)前可見區(qū)域內(nèi)查找是否有目標(biāo)節(jié)點(diǎn)狈惫,如果沒有睛蛛,則向下滾動(dòng),再次查找目標(biāo)節(jié)點(diǎn)——直至滑動(dòng)到屏幕底部胧谈。為防止當(dāng)前界面并非從最頂部開始向下滑動(dòng)忆肾,故應(yīng)該再向上滑動(dòng)到頂部一次。查找滑動(dòng)組件代碼如下:
private static final List<String> SCROLL_CLASS_NAME_ARRAY = Arrays.asList( "android.widget.ListView", "android.support.v7.widget.RecyclerView", "androidx.recyclerview.widget.RecyclerView" ); /** * 查找可滑動(dòng)的view */ private List<AccessibilityNodeInfo> findCanScrollView() { List<AccessibilityNodeInfo> viewList = findAllView(); List<AccessibilityNodeInfo> scrollViewList = new ArrayList<>(); for (AccessibilityNodeInfo info : viewList) { if (info != null) //info.isScrollable判斷是否為可滾動(dòng)控件 if (info.isScrollable()) { //其中滾動(dòng)控件還包含了spinner菱肖、viewpage等,而這些控件目前業(yè)務(wù)中并沒有需要(且會(huì)影響業(yè)務(wù)內(nèi)容)客冈,因此做了個(gè)限定 for (String scrollClassName : SCROLL_CLASS_NAME_ARRAY) { if (scrollClassName.equals(info.getClassName().toString())) { scrollViewList.add(info); setLog("------------------------------------------------------------"); setLog("查詢到可滑動(dòng)組件" + info.getViewIdResourceName()); setLog("查詢到可滑動(dòng)組件" + info.getClassName()); setLog("------------------------------------------------------------"); } } } } return scrollViewList; }
遍歷多叉樹,根據(jù)條件查尋當(dāng)前可見部分的節(jié)點(diǎn)
/** * 遍歷多叉樹 查詢方法 * 找到控件會(huì)拋出異常稳强,異常中包含目標(biāo)數(shù)據(jù) */ 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); } } } /** * 自定義報(bào)錯(cuò) 用于中斷遞歸 */ 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 返回匹配結(jié)果 */ 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()); }
查找第一個(gè)匹配節(jié)點(diǎn)代碼如下:
/** * @param text 要查找目標(biāo)組件的標(biāo)識(shí) (className/resourceId/text) * @return 返回查找到的目標(biāo)組件 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) {//利用關(guān)鍵字scroll和Exception跳出循環(huán) //找不到控件退疫,向下滑動(dòng)繼續(xù)找 if (scrollViewList.size() == 0) { if (searchNum < MAX_SEARCH_NUM) { ++searchNum; scrollViewList.addAll(findCanScrollView()); } else { //找不到可滑動(dòng)組件 setLog("找不到可滑動(dòng)組件渠缕,結(jié)束循環(huán)體,查找次數(shù):" + MAX_SEARCH_NUM); scroll = false; } } if (scrollViewList.size() != 0) { //如果查找到滑動(dòng)組件蹄咖,則滑動(dòng)滑動(dòng)組件 //下褐健、上各滑動(dòng)一次,確保找到控件 for (AccessibilityNodeInfo scrollViewInfo : scrollViewList) { if (isForward) { if (!scrollViewInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) { isForward = false;//滑動(dòng)到底部澜汤,修改滑動(dòng)狀態(tài)蚜迅,再滑一次 } } else { if (!scrollViewInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)) { scroll = false; } } } } //滑動(dòng)后 睡眠 之后查找組件 SystemClock.sleep(TIME_SLEEP); findIteratorTree(findRoot(), text); } } catch (StopIteratorException e) { info = e.getInfo(); if (info != null) { setLog("------------------------------------------------------------"); setLog("查詢到目標(biāo)組件id:" + info.getViewIdResourceName()); setLog("查詢到目標(biāo)組件text:" + info.getText()); setLog("查詢到目標(biāo)組件clsName:" + info.getClassName()); setLog("------------------------------------------------------------"); } } return info; }
補(bǔ)充說明:(可不看)在查看AccessibilityNodeInfo類中有如下方法
class AccessibilityNodeInfo implements Parcelable{ ... public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { throw new RuntimeException("Stub!"); } public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) { throw new RuntimeException("Stub!"); } ... }
由此聯(lián)想到
findIteratorTree(AccessibilityNodeInfo parent, String text)
或許可以改成:(以下代碼未嘗試,只是臨時(shí)想到的改進(jìn)方案)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; }
-
點(diǎn)擊事件
找到目標(biāo)節(jié)點(diǎn)后俊抵,自然是要對目標(biāo)節(jié)點(diǎn)進(jìn)行操作——這里僅封裝點(diǎn)擊事件
/** * 點(diǎn)擊事件 * * @param info 要點(diǎn)擊的目標(biāo)View * @return 點(diǎn)擊結(jié)果(成功/失斔弧) */ 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; }
將查詢節(jié)點(diǎn)與點(diǎn)擊節(jié)點(diǎn)組合起來就是:
/** * 點(diǎn)擊事件(點(diǎn)擊本身) * * @param text 要點(diǎn)擊的目標(biāo)節(jié)點(diǎn)標(biāo)識(shí) * @return 點(diǎn)擊結(jié)果(true/false) */ public boolean clickFirstView(String text) { AccessibilityNodeInfo info = findFirst(text); if (info == null) { setLog(text + "找不到匹配的控件"); return false; } return clickView(info); }
那如果是CheckBox之類的點(diǎn)擊事件(如下圖)應(yīng)該怎么處理?
1.jpg首先轉(zhuǎn)化為查詢該CheckBox的同級節(jié)點(diǎn)徽诲,再通過該同級節(jié)點(diǎn)反查父節(jié)點(diǎn)刹帕,之后再查詢父節(jié)點(diǎn)中的子節(jié)點(diǎn)中包含
isCheckable
的節(jié)點(diǎn):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("查詢到目標(biāo)組件id:" + info.getViewIdResourceName()); setLog("查詢到目標(biāo)組件text:" + info.getText()); setLog("查詢到目標(biāo)組件clsName:" + info.getClassName()); setLog("------------------------------------------------------------"); break; } } setLog("------------------------------------------------------------"); } return checkBoxInfo; }
CheckBox節(jié)點(diǎn)中有
isChecked
字段用于判斷是( true )否( false )選中,因此有:/** * checkBox的點(diǎn)擊事件(帶選中狀態(tài)) * * @param brotherText 要點(diǎn)擊目標(biāo)的兄弟節(jié)點(diǎn)標(biāo)識(shí) * @param setChecked 將checkBox的狀態(tài)修改為true/false * @return 返回點(diǎn)擊事件觸發(fā)結(jié)果(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控件為目標(biāo)狀態(tài)谎替,不點(diǎn)擊"); } return false; }
-
補(bǔ)充
靜態(tài)變量偷溺、日志與返回
//靜態(tài)變量 private static final int TIME_SLEEP = 300;//單位毫秒 private static final int MAX_SEARCH_NUM = 20;//查找循環(huán)體次數(shù) //日志 protected abstract boolean isDebugger(); private void setLog(String msg) { if (isDebugger()) { String tag = "AManagerLog"; Log.i(tag, msg); } } //系統(tǒng)返回鍵 public void goBack() { performGlobalAction(GLOBAL_ACTION_BACK); }
以上就是整個(gè)工具的封裝了
如何使用
-
輔助功能觸發(fā)點(diǎn)的設(shè)置
輔助功能顧名思義,就是輔助用戶使用當(dāng)前App钱贯,甚至可以延伸為使用當(dāng)前手機(jī)挫掏。比如:
- 監(jiān)測App接收到的通知消息,根據(jù)消息對手機(jī)進(jìn)行輔助操作
- 監(jiān)測文件下載狀態(tài)秩命,根據(jù)下載狀態(tài)進(jìn)行自動(dòng)安裝尉共、打開等操作
- 當(dāng)用戶點(diǎn)擊了某個(gè)位置時(shí)褒傅,根據(jù)點(diǎn)擊的位置做出反饋
- 為用戶自動(dòng)開啟相關(guān)權(quán)限等
- ...
這時(shí)候:監(jiān)測到App接收通知消息和監(jiān)測到文件下載狀態(tài)即為觸發(fā)點(diǎn);用戶點(diǎn)擊事件即為出發(fā)點(diǎn)袄友;而開啟權(quán)限殿托,則可以是在輔助功能權(quán)限開啟的時(shí)候。
觸發(fā)點(diǎn)為輔助功能權(quán)限開啟:輔助功能權(quán)限開啟的時(shí)候會(huì)調(diào)用初始化Service方法:
onServiceConnected()
剧蚣,這里可以作為一個(gè)監(jiān)測點(diǎn)支竹,利用sendBroadcast(Intent intent)
將服務(wù)啟動(dòng)的消息廣播出去。而廣播接收器就是真實(shí)的觸發(fā)點(diǎn)券敌。觸發(fā)點(diǎn)為某種狀態(tài):
if(該狀態(tài)可回調(diào)){ if(輔助功能權(quán)限開啟){ 直接執(zhí)行輔助功能 }else{ 彈出無障礙設(shè)置頁面唾戚,引導(dǎo)用戶開啟輔助功能權(quán)限-觸發(fā)點(diǎn)轉(zhuǎn)為輔助功能權(quán)限開啟時(shí) } }else{ 監(jiān)測狀態(tài)改變引發(fā)的界面變化 ... }
觸發(fā)點(diǎn)為用戶點(diǎn)擊事件:如果點(diǎn)擊的是自己的App,在點(diǎn)擊事件觸發(fā)輔助功能即可待诅。如果點(diǎn)擊的不為自己的App,可以在
onAccessibilityEvent(AccessibilityEvent event)
觸發(fā)熊镣。 -
輔助功能執(zhí)行
輔助功能是一個(gè)富有想象力的功能卑雁,TA的執(zhí)行能力取決于腦洞。比如模擬用戶點(diǎn)擊绪囱,讓用戶的操作更快捷(比如自動(dòng)搶紅包测蹲、一鍵連招等)或者取代繁瑣操作(自動(dòng)下載并安裝等)甚至也可以是智能分詞翻譯(錘子BigBang)等等...
這里以“取代繁瑣操作”為例——征得用戶同意后,替用戶勾選一些無法直接申請的權(quán)限:
任務(wù):
AssignmentEntity
:public class AssignmentEntity { private Queue<StepEntity> queue = new LinkedList(); //步驟隊(duì)列 private String name; //任務(wù)名稱 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
:public class StepEntity { public static final int TYPE_INTENT = 0; //頁面跳轉(zhuǎn) public static final int TYPE_CLICK = 1; //點(diǎn)擊事件 public static final int TYPE_CHECKED = 2; //選中事件 public static final int TYPE_BACK = 3; //返回事件 private String name; //目標(biāo)控件名稱 private int type; //事件類型 private boolean checked; //選中事件-true:選中-false:反選 private Intent intent; //跳轉(zhuǎn)事件-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
:public class StepHelper { public StepHelper() { } /** * 跳轉(zhuǎn)事件 **/ public static StepEntity intentStep(Intent intent) { return getStep("頁面跳轉(zhuǎn)", 0, intent, (Boolean)null); } /** * 點(diǎn)擊事件 **/ 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("代碼錯(cuò)誤鬼吵,Type為TYPE_INTENT時(shí)Intent參數(shù)不能為空"); } step.setIntent(intent); } else if (2 == type) { if (setChecked == null) { throw new IllegalArgumentException("代碼錯(cuò)誤扣甲,Type為TYPE_CHECKED時(shí)setChecked參數(shù)不能為空"); } boolean b = setChecked; step.setChecked(b); } return step; } }
添加任務(wù)示例:
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("手機(jī)版本號(hào):" + getVersion()); //目前適配10 Queue<AssignmentEntity> queue = new LinkedList<>(); queue.add(ignoreBatteryOptimization()); queue.addAll(newMessageNotification()); queue.add(selfStarting()); return queue; } //忽略電池優(yōu)化 private static AssignmentEntity ignoreBatteryOptimization() { AssignmentEntity assignment = new AssignmentEntity(); assignment.setName("忽略電池優(yōu)化"); assignment.addStep(StepHelper.intentStep(IntentUtils.hightPowerManger())) .addStep(StepHelper.clickStep("不允許")) .addStep(StepHelper.clickStep("所有應(yīng)用")) .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("更多通知設(shè)置")) .addStep(StepHelper.checkStep("通知亮屏提示", true)) .addStep(StepHelper.backStep()) .addStep(StepHelper.backStep()); Queue<AssignmentEntity> queue = new LinkedList<>(); queue.add(ae1); queue.add(ae2); return queue; } //自啟動(dòng) private static AssignmentEntity selfStarting() { AssignmentEntity assignment = new AssignmentEntity(); assignment.setName("自啟動(dòng)"); assignment.addStep(StepHelper.intentStep(IntentUtils.huaweiStartupNormalApp())) .addStep(StepHelper.checkStep(BaseApplication.getInstance().getAppName(), true)) .addStep(StepHelper.checkStep(BaseApplication.getInstance().getAppName(), false)) .addStep(StepHelper.checkStep("允許自啟動(dòng)", true)) .addStep(StepHelper.checkStep("允許關(guān)聯(lián)啟動(dòng)", true)) .addStep(StepHelper.checkStep("允許后臺(tái)活動(dòng)", true)) .addStep(StepHelper.clickStep("確定")) .addStep(StepHelper.backStep()); return assignment; } } ... }
執(zhí)行任務(wù)示例:
public class AssignmentFactory { ... public static void run(Activity activity, Queue<AssignmentEntity> queue) { L.i("隊(duì)列開始執(zhí)行"); 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())//執(zhí)行在io線程 .observeOn(Schedulers.io())//回調(diào)在io線程 //主線程阻塞將無法更新ui AndroidSchedulers.mainThread() .subscribe( assignment -> { //FloatWindowView.getInstance().progressAdd(); //L.i("當(dāng)前進(jìn)度:" + 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://跳轉(zhuǎn)事件 activity.startActivity(poll.getIntent()); break; case StepEntity.TYPE_CLICK://點(diǎn)擊事件 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