android 借助AccessibilityService实现模拟点击功能-onAccessibilityEvent接收事件的详细处理(三)
demo地址
本篇分析对接收事件的处理
需要思考的问题
- android的事件会回调在
public void onAccessibilityEvent(AccessibilityEvent event)
这个方法里,这是在主线程,直接在这里处理合适吗? - 如果你的应用需要实现多个功能,怎么在这个方法里面保证他们的执行隔离呢?
- 当微信不知道什么原因,或者是你代码不健壮导致的页面停住,不再触发新的event事件,你怎么处理?
- 怎么循环一个列表(批量加好友,批量发消息)去迭代每个item去完成一组功能?
- 怎么做到多个功能有重复逻辑的时候的代码复用?
- 怎么设计一个功能执行的过程中的暂停、继续,结束之后的弹框交互。
ok,本篇开始分析1、2、3的问题。
线程问题
首先当一个event触发的时候,你需要去判断这个event的getEventType()
,然后判断节点的类名,或者是寻找某一个控件的id的节点。那么通常,你需要将你所有的功能的方法都放到void onAccessibilityEvent(AccessibilityEvent event)里面,去挨个判断执行,因为当然你不知道这个event适用于那段逻辑代码。
试想,如果你的给所有好友(判断标签、备注、性别)发送文字和图片的一个功能的逻辑代码片段有几十处,一个event进来需要挨个去走这几十短代码,并且,可能当前的event都不是这几十短代码判断之后符合的(因为有各种的click,contentChange,scroll事件)。如此看来,再加上更多的功能片段,可想而知,会耗时阻塞主线程,极有可能出现ANR异常。
这么说来当然有必要将这些event事件发送到一个子线程去处理啦。
在这里,我采用IntentService,将Service里面触发的event都发送到IntentService里面去处理.
public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable{}
当然了,AccessibilityEvent是Parcelble的,可以通过Bundle传送。
public class MainService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { HandleAccessibilityEventService.startWithEvent(getApplicationContext(), event); }}
public class HandleAccessibilityEventService extends IntentService { private static final String EXTRA_EVENT = "extra_event"; public HandleAccessibilityEventService() { super("HandleAccessibilityEventService"); } public static void startWithEvent(Context context, AccessibilityEvent event) { if (TYPE_NOTIFICATION_STATE_CHANGED == event.getEventType()) { H.getInstance().excuteServiceMethods(TaskId.get().mCurrentId, event); return; } Intent intent = new Intent(context, HandleAccessibilityEventService.class); intent.putExtra(EXTRA_EVENT, event); context.startService(intent); } @Override protected void onHandleIntent(Intent intent) { if (intent != null) { AccessibilityEvent event = intent.getParcelableExtra(EXTRA_EVENT); if (event != null) { // TODO: 2019/3/18 处理event } } }}
这样,即便是event事件同一时刻会触发很多,没关系,让IntentService一个一个处理,不着急。
多个任务的问题
这个可以通过一个全局变量,设置一个id,相同功能的代码包含在一个id里面。
那么在判断event的时候,先判断当前的功能id,如果是的话再去执行这一片代码。
@Override protected void onHandleIntent(Intent intent) { if (intent != null) { AccessibilityEvent event = intent.getParcelableExtra(EXTRA_EVENT); if (event != null) {switch(mCurrentId){case Id.Add_FIRENDS://todo//todobreak;...... } } } }
微信页面停留,不触发新的event,导致功能不能继续下去。
当你的应用中注册了AccessibilityService时,系统会在需要发送AccessibilityEvent的地方去遍历发送到这些service里面.
那么我们可不可以手动反射出创建AccessibilityEvent对象,来发送到我们的service呢。答案当然是肯定的。
我们可以在处理事件的时候,首先去判断事件类型是不是TYPE_WINDOW_STATE_CHANGED,因为所有的activity切换都会触发这个事件,但是Dialog的alert也会发出,为了区分,只能通过类名来判断。恰巧的是,微信大多activity的类名都是以UI结尾的,那么,我斗胆用这种方式来记录下当前微信停留的页面的类名。
public class MainService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { String s = event.getClassName().toString(); if (s.endsWith("UI")) { WechatCurrentActivity.getInstance().updateCurrent(s); } } HandleAccessibilityEventService.startWithEvent(getApplicationContext(), event); }}
这样以来就好办啦,AccessibilityEvent这个类最核心的属性就是eventType和ClassName,那么我们直接反射它的构造去设置这两个属性,然后传递到我们的IntentService里面去处理就好啦。
这里我们需要自己设定这个微信页面无响应的超时时间啦,比如5s,10s。
在这里我重新启动一个守护服务,去守护微信无响应的情况。当每一个功能开启时,它也就启动。
public class DeamonService extends IntentService { private static final String EXTRA_TASK_ID = "extra_task_id"; private AccessibilityEvent event; public DeamonService() { super("DeamonService"); } public static void startWithTaskId(Context context, int taskId) { Intent intent = new Intent(context, DeamonService.class); intent.putExtra(EXTRA_TASK_ID, taskId); context.startService(intent); } @Override protected void onHandleIntent(Intent intent) { //更新当前操作时间戳 H.getInstance().updateOpreateTime(); if (intent != null) { int taskId = intent.getIntExtra(EXTRA_TASK_ID ,-2); while (TaskId.assertTaskId(taskId)) { if (Delay.isResponseTimeOut()) { //超时 //手动触发动作 todo(taskId); } UiKit.sleep(5000); } } } private void todo(int taskId) { if (event != null) { event.recycle(); } event = ObtainWindowStateChangeEvent.obtainEvent(WechatCurrentActivity.getInstance().getCurrentActivity()); if (event != null) { L.e("手动触发Event :" + event); H.getInstance().excuteServiceMethods(taskId,event); } }}
反射获取Event对象:
public class ObtainWindowStateChangeEvent { public static AccessibilityEvent obtainLuancherEvent() { return obtainEvent(WechatUI.UI_LUANCHER); } public static AccessibilityEvent obtainEvent(String className) { try { Constructor constructor = AccessibilityEvent.class.getDeclaredConstructor(); constructor.setAccessible(true); AccessibilityEvent accessibilityEvent = constructor.newInstance(); accessibilityEvent.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); accessibilityEvent.setClassName(className); return accessibilityEvent; } catch (Exception e) { e.printStackTrace(); } return null; }}
到此,我们前三个问题都分析完了。
下篇分析之后的几个问题。
更多相关文章
- 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
- Android开发学习笔记之 Service 的使用
- Android(安卓)实现对话框圆角功能
- Android(安卓)应用桌面角标显示各厂商规则说明
- 下载并编译Chrome浏览器For Android
- Android资源文件在配置文件中的使用
- Android代码优化
- [百度空间] [原]跨平台编程注意事项(三): window 到 android 的
- 利用Eclipse和NDK建立一个简单service