android 借助AccessibilityService实现模拟点击功能-onAccessibilityEvent接收事件的详细处理(三)

demo地址

本篇分析对接收事件的处理

需要思考的问题

  1. android的事件会回调在public void onAccessibilityEvent(AccessibilityEvent event)这个方法里,这是在主线程,直接在这里处理合适吗?
  2. 如果你的应用需要实现多个功能,怎么在这个方法里面保证他们的执行隔离呢?
  3. 当微信不知道什么原因,或者是你代码不健壮导致的页面停住,不再触发新的event事件,你怎么处理?
  4. 怎么循环一个列表(批量加好友,批量发消息)去迭代每个item去完成一组功能?
  5. 怎么做到多个功能有重复逻辑的时候的代码复用
  6. 怎么设计一个功能执行的过程中的暂停、继续,结束之后的弹框交互

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;
    }
}

到此,我们前三个问题都分析完了。

下篇分析之后的几个问题。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值