下集预告:
android 借助AccessibilityService实现模拟点击功能-代码复用架构的注解和细节(六)
本篇介绍辅助功能重复逻辑的代码复用
为什么复用?
假想,不,你可以实际操作一下:在AccessibilityService-onAccessibilityEvent(event)方法里面,不停的在switch(event.getEventType())下case 类型、类名,然后在每一个case下面,又是好多功能id的判断。
通常好多功能的公共部分有很多,比如一个弹框弹出,然后需要点击最右边的menu按钮,再比如滑动通讯录列表去点击每一个好友,返回一个页面。类似的场景有好多,有没有考虑把通用的场景涉及的代码抽取出来然后去复用呢。
of course,你把代码片段alt+command+M来抽取成static方法,在需要调用的地方去调用,当然可以。但是这样写下来,你的代码有没有很乱,或者是很难找某一步的位置。
为了达到既美观,又容易分析的效果,我是这样做的。
模仿retrofit2采用一个动态代理,统一执行代码片段
- 首先创建统一接口
public interface Action {
void run(AccessibilityEvent event);
}
所有的一段代码片段我称作一个Action,如果满足条件(eventType,className等),就去run();
比如点击微信主界面右上角加号的这么一个操作:
@EVENT_TYPE
@EVENT_CLASS(UI_LUANCHER)
public class ActionClickPlusAdd implements Action{
@Override
public void run(AccessibilityEvent event) {
//todo find the node by this view's id ,and click it
}
}
这里的注解是为了简化代码,之后统一介绍。
- 然后一个个的Action写在一个Interface里面(对,就是retrofit的实现方式):
public interface ActionService {
/**
* 点击首页【加号】
*
* @param event
*/
@TASK_IDS({TASK_MASS_SEND_GROUP, TASK_GET_ALL_GROUP_NAMES})
@RUN(ActionClickPlusAdd.class)
void i(AccessibilityEvent event);
}
- 再然后就是重点了,创建代理对象,实现判断逻辑的统一处理:
public class H {
private ActionService service;
private SparseArray<List<Method>> mTaskMethods = new SparseArray<>();
private HashMap<Method, ActionAssertEntity> mActions = new HashMap<>();
private static final H ourInstance = new H();
private long mUpdateTime ;
public static H getInstance() {
return ourInstance;
}
private H() {
}
public ActionService createService() {
if (service == null) {
service = (ActionService) Proxy.newProxyInstance(ActionService.class.getClassLoader(), new Class<?>[]{ActionService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 逻辑省略...
ActionAssertEntity actionAssertEntity = mActions.get(method);
actionAssertEntity.getAction().run(event);
return null;
}
});
}
return service;
}
}
- 然后就是在IntentService中的调用了:
public class HandleAccessibilityEventService extends IntentService {
//重复代码前面贴过 略...
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
AccessibilityEvent event = intent.getParcelableExtra(EXTRA_EVENT);
if (event != null) {
H.getInstance().excuteServiceMethods(TaskId.get().mCurrentId, event);
}
}
}
}
总结下来,写一个功能中的一个操作,比如点一个特定的按钮的操作就变成下面两步了:
- 写一个类实现Action接口,在run(event)里面去实现逻辑,当然了上面需要加上特性eventType和ClassName的注解;
- 把这个类名粘在ActionService里面,相当于注册了。和retrofit类似。
当然了,有人会说,通过反射注解,执行方法的方式耗时耗内存,其实不然,当某一个功能任务的方法第一次需要执行的时候,这里的处理是首先把当前功能任务的id所拥有的全部method全部提取出来存在mTaskMethods里面,这个map是一个任务id和methodList的映射,当需要执行一个event所对应的方法时,直接从mTaskMethods中取出来当前任务的所有method,然后遍历去执行。其次是反射的过程中已经把每个action注解对应的属性存放在ActionAssertEntity中,这样,下次就没必要重复反射获取注解属性了。无非是多用了两个Map而已。
这样看来,是不是很简洁,我们就可以把注意力集中在实现功能的逻辑上了,而不必担心因为代码的杂乱引起的额外的注意力开销。
下篇具体讲解用到的注解,和在代理对象中的解析和判断逻辑。