本篇分析对接收事件的处理
需要思考的问题
- 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
//todo
break;
...
...
}
}
}
}
微信页面停留,不触发新的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<AccessibilityEvent> 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;
}
}
到此,我们前三个问题都分析完了。
下篇分析之后的几个问题。