前言
想当年刚开始做Android开发的时候,体验了一番无障碍服务实现的功能,发现它动不动就得重新手动开启,当时就觉得这功能毫无用处。事实又证明,我还是太年轻了。最近在工作中又接触到无障碍服务,就突然又觉得这种东西在某些时候还是有点用的,甚至有人用它来做成自动化功能卖钱。不过在利用无障碍服务来做一些产品的时候并没有想象中的那么顺利,除了上面提到的可能要频繁开启它还有许多地方需要注意。本文就不扯关于无障碍的一些基本配置了,如果还没用过,建议先去了解再来看本文。
问题1:无障碍服务意外死亡
在大多数情况下,如果AccessibilityService类中出现异常,那么无障碍服务就会死亡,设置中的无障碍服务开关会置回关闭状态。或者你首次运行App,无障碍服务也没有开启。这些情况可以用代码直接进行判断,然后提示用户转到设置界面开启
/**
* 检查无障碍服务是否启用
*
* @param serviceName serviceName 服务名
* @return 是否启用
*/
public boolean checkAccessibilityEnabled(Context context,String serviceName) {
AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().equals(serviceName)) {
return true;
}
}
return false;
}
/**
* 前往无障碍服务设置界面
*/
public void goSettingUi() {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
在少数情况下,无障碍设置中开启了服务,但是服务并没有在运行。对于这个问题,也没有很好的解决办法。虽然这类问题出现的概率较少,但是为了用户体验,可以提醒用户去重新开启服务,使界面交互更加友好。那么如何判断服务有没有真正在运行呢?
有个取巧的方法是在AccessibilityService的子类onAccessibilityEvent方法里记录时间,该方法在不过滤包名的情况下,它会被比较频繁的调用,那么可以在其他地方读取这个时间,如果当前时间和onAccessibilityEvent里记录的时间相差超过30秒,这个辅助功能服务多半是死了,就可以提醒用户去手动重启服务了。
问题2:控件节点查找
AccessibilityNodeInfo类实例在无障碍服务中代表一个控件节点,它保存了控件的一些信息,同时它也提供了findAccessibilityNodeInfosByViewId和findAccessibilityNodeInfosByText方法来供开发者查找控件节点,findAccessibilityNodeInfosByViewId方法是通过Id来查找控件节点,findAccessibilityNodeInfosByText是通过控件的文本来查找控件节点。但是现在许多大型的App,控件Id几乎都是被混淆过的,而且更加棘手的是,控件Id是随着软件版本的变化而改变的,这样维护起来简直是灾难。想要通过文本来查找节点?但是很多控件都没有文本,也就没法用findAccessibilityNodeInfosByText方法来查找。那么如何实现在目标App更新版本后,查找控件仍能准确?
我们把整个页面布局看成一棵数据结构中的树,那么大多数控件在树上面的位置是不一样的,即它们可能在树中也有不同的深度,即使深度一样,它们也可能处于不同的分支,那么我们就可以利用它们深度的不同以及父节点的不同来得到它们。
/**
* 根据多个父节点来查找控件
*
* @param nodeParams
* @return List
*/
public List<AccessibilityNodeInfo> findViewListByParents(List<NodeParam> nodeParams) {
AccessibilityNodeInfo rootNodeInfo = mAccessibilityService.getRootInActiveWindow();
if(rootNodeInfo != null) {
rootNodeInfo.refresh();
}
Queue<AccessibilityNodeInfo> queue = new LinkedList<>();
List<AccessibilityNodeInfo> results = new ArrayList<>();
queue.offer(rootNodeInfo);
while (!queue.isEmpty()) {
AccessibilityNodeInfo node = queue.poll();
if(node == null)
continue;
int childCount = node.getChildCount();
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if(child != null) {
child.refresh();
}
int conformNode = 0;
for (NodeParam nodeParam : nodeParams) {
AccessibilityNodeInfo parentNode = child;
for (int j = 0; j < nodeParam.getLevel(); j++) {
if (parentNode != null) {
parentNode = parentNode.getParent();
}
}
if (parentNode != null && parentNode.getClassName().equals(nodeParam.getClassName())) {
conformNode++;
}
}
if (conformNode == nodeParams.size()) {
results.add(child);
}
if(child != null && child.getChildCount() > 0){
queue.offer(child);
}
}//for childCount
}//while
return results;
}
NodeParam类
public class NodeParam {
public NodeParam(String className, int level) {
this.className = className;
this.level = level;
}
public String getClassName() {
return className;
}
public int getLevel() {
return level;
}
//节点类名
private String className;
//节点级数。本身,level = 0;父亲,level = 1;爷爷,level = 2;以此类推
private int level;
}
比如,要查找一个ImageView,它的父节点,爷爷节点,曾祖父节点都是LinearLayout,那么可以这么写:
List<NodeParam> imageViewNodeParams = new ArrayList<>();
imageViewNodeParams.add(new NodeParam("android.widget.ImageView",0));
imageViewNodeParams.add(new NodeParam("android.widget.LinearLayout",1));
imageViewNodeParams.add(new NodeParam("android.widget.LinearLayout",2));
imageViewNodeParams.add(new NodeParam("android.widget.LinearLayout",3));
List<AccessibilityNodeInfo> foundNodeInfo = findViewListByParents(imageViewNodeParams);
聪明的你可能又要提出疑问了,如果多个控件是兄弟,它们的类型也一样,那如何准确定位到我们想要定位到的控件?通过常识可以知道,互为兄弟的控件位置不可能一样(控件叠在一起也没有意义),那我们可以通过比较它们的位置来进行得到目标控件节点,刚好Android在AccessibilityNodeInfo类中提供了getBoundsInScreen方法,帮助我们更准确的定位控件节点。
问题3. 模拟操作过程中被干扰
想要利用无障碍服务做成稳定的自动任务,就要考虑在执行模拟操作的时候被外界干扰的或者突然找不到元素情况。对于这些突发情况,目前没有想到非常好的解决办法,但是办法还是有的,就是当元素找不到的时候,不断的模拟按下返回键,直至返回到桌面(Launch),接下来又把目标App的Activity启动,将任务从头开始。
总结
无障碍服务使用起来看似简单,但是想做到稳定,还是需要投入许多心思的。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...