AndroidアクセスbilityServiceによるアナログクリック機能-onAccessibilityEvent受信イベントの詳細処理(3)

6652 ワード

demoアドレス
本編分析受信イベントの処理
考えるべき問題
  • androidのイベントはpublic void onAccessibilityEvent(AccessibilityEvent event)という方法にコールバックされますが、これはメインスレッドで、直接ここで処理するのは適切ですか?
  • もしあなたのアプリケーションが複数の機能を実現する必要があるならば、どのようにこの方法の中で彼らの実行隔離を保証しますか?
  • 微信が何が原因なのか、あるいはあなたのコードが丈夫でないことによるページが停止し、新しいeventイベントをトリガーしなくなった場合、あなたはどのように処理しますか?
  • どのように1つのリスト(大量に友达を加えて、大量にメッセージを送ります)を循環して各itemを反復して1組の機能を完成しますか?
  • 複数の機能に重複論理がある場合のコード多重化をどのようにしますか?
  • は、機能実行中の一時停止、継続、終了後のフレームインタラクションをどのように設計するか.

  • OK,本編では1,2,3の問題の分析を開始する.
    スレッドの問題
    まず、eventがトリガーされると、このeventのgetEventType()を判断し、ノードのクラス名を判断したり、コントロールのidを探したりする必要があります.通常、void onAccessibilityEvent(AccessibilityEvent)にすべての機能の方法を入れて、その論理コードに適用されることを知らないので、判断して実行する必要があります.
    考えてみてください.もしあなたがすべての友达(判断ラベル、注釈、性別)に文字と画像を送信する機能の論理コードの断片が数十カ所あれば、1つのeventが入ってきて、この数十の短いコードを1つずつ歩く必要があります.そして、現在の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を1つずつ処理して、焦らないようにします.
    複数のタスクの問題
    これはグローバル変数を通じて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を送信する必要がある場所でこれらのサービスに送信します.
    では、AccessibilityEventオブジェクトの作成を手動で反射してサービスに送ってもらえませんか.答えはもちろん肯定的だ.
    私たちは事件を処理するとき、まず事件のタイプが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です.では、その構造を直接反射してこの2つの属性を設定し、IntentServiceに渡して処理すればいいのです.
    ここでは、5 s、10 sなど、この微信ページの応答のないタイムアウト時間を自分で設定する必要があります.
    ここで私は、微信の応答のない状況を守るために、保護サービスを再起動しました.各機能がオンになると、起動します.
    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);
            }
        }
    }
    

    反射イベントオブジェクトの取得:
    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;
        }
    }
    

    ここで、私たちの前の3つの問題はすべて分析しました.
    次の分析の後のいくつかの問題.