AccessibilityService 的 onInterrupt 方法是怎么用的?

onInterrupt 是实现自定义无障碍服务必须实现的方法,但官方文档和注释中对此方法的描述十分模糊,从字面上可以理解这是无障碍服务中断的回调,但具体是什么样的场景并没有一个明确的示例。

onInterrupt 在官方文档中查到的相关描述是:

onInterrupt() - (required) This method is called when the system wants to interrupt the feedback your service is providing, usually in response to a user action such as moving focus to a different control. This method may be called many times over the lifecycle of your service.

(必需)当系统要中断服务正在提供的反馈(通常是为了响应将焦点移到其他控件等用户操作)时,会调用此方法。此方法可能会在服务的整个生命周期内被调用多次。

onInterrupt 在 AccessibilityService 中定义,是一个抽象方法:

public abstract class AccessibilityService extends Service {
		// ...
		public abstract void onInterrupt();
		// ...
}

AccessibilityService#onInterrupt() 的调用在 AccessibilityService 的 onBind 方法中:

    @Override
    public final IBinder onBind(Intent intent) {
        return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
        		// ...
            @Override
            public void onInterrupt() {
                AccessibilityService.this.onInterrupt();
            }    
				}
		}

这里返回了一个 IAccessibilityServiceClientWrapper 对象作为 IBinder,IAccessibilityServiceClientWrapper 构造参数有三个:

        public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                Callbacks callback) {
            mCallback = callback;
            mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);
        }

在构造方法中,通过 Context。和 Looper 构造了一个 HandlerCaller 对象。
而最后的 Callbacks 参数是 AccessibilityService 的内部接口。将其保存到了 mCallback 。
AccessibilityService 中定义 Callbacks 接口:

    public interface Callbacks {
        void onAccessibilityEvent(AccessibilityEvent event);
        void onInterrupt();
        void onServiceConnected();
        void init(int connectionId, IBinder windowToken);
        /** The detected gesture information for different displays */
        boolean onGesture(AccessibilityGestureEvent gestureInfo);
        boolean onKeyEvent(KeyEvent event);
        /** Magnification changed callbacks for different displays */
        void onMagnificationChanged(int displayId, @NonNull Region region,
                float scale, float centerX, float centerY);
        void onSoftKeyboardShowModeChanged(int showMode);
        void onPerformGestureResult(int sequence, boolean completedSuccessfully);
        void onFingerprintCapturingGesturesChanged(boolean active);
        void onFingerprintGesture(int gesture);
        /** Accessbility button clicked callbacks for different displays */
        void onAccessibilityButtonClicked(int displayId);
        void onAccessibilityButtonAvailabilityChanged(boolean available);
        /** This is called when the system action list is changed. */
        void onSystemActionsChanged();
    }

AccessibilityService.Callback 接口是 IAccessibilityServiceClientWrapper 用于从其主线程调用 Service 的接口。这里是通过 handler 机制来进行的。

IAccessibilityServiceClientWrapper 在构造时,会构造一个 HandlerCaller :

mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);

HandlerCaller 是一个 Handler 的包装器:

    public HandlerCaller(Context context, Looper looper, Callback callback,
            boolean asyncHandler) {
        mMainLooper = looper != null ? looper : context.getMainLooper();
        mH = new MyHandler(mMainLooper, asyncHandler);
        mCallback = callback;
    }

HandlerCaller 内部定义了 Callback 接口:

    public interface Callback {
        public void executeMessage(Message msg);
    }

这个 Callback 由 IAccessibilityServiceClientWrapper 实现,并处理了中断相关类型的消息:

        @Override
        public void executeMessage(Message message) {
            switch (message.what) {
            		// ...
                case DO_ON_INTERRUPT: {
                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                        mCallback.onInterrupt();
                    }
                    return;
                }
				}

还记得这里 mCallback 吗,就是 IAccessibilityServiceClientWrapper 构造方法中传入的 AccessibilityService 中的 onBind 中构造的匿名对象,从而调用到 AccessibilityService 的 onInterrupt:

    @Override
    public final IBinder onBind(Intent intent) {
        return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
        		// ...
            @Override
            public void onInterrupt() {
                AccessibilityService.this.onInterrupt();
            }    
				}
		}

IAccessibilityServiceClientWrapper 可以作为 IBinder 对象,是因为实现了 IAccessibilityServiceClient.Stub ,IAccessibilityServiceClient 是 AIDL 接口,内部定义了 onInterrupt() 方法,在这里的实现是通过 Handler 发送了 DO_ON_INTERRUPT 类型的消息。

public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
        implements HandlerCaller.Callback {
        public void onInterrupt() {
            Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
            mCaller.sendMessage(message);
        }
}

发送DO_ON_INTERRUPT类型的消息,正好回调到了 mCallback.onInterrupt 。而
IAccessibilityServiceClientWrapper 的 onInterrupt 是在哪里调用的呢?

IAccessibilityServiceClientWrapper 的 onInterrupt 方法是 AIDL 实现,说明来自于 Service 调用。查看这个方法的调用,发现来自于 AccessibilityManagerService 。

AccessibilityManagerService 实现了 IAccessibilityManager.Stub:

public class AccessibilityManagerService extends IAccessibilityManager.Stub
        implements AbstractAccessibilityServiceConnection.SystemSupport,
        AccessibilityUserState.ServiceInfoChangeListener,
        AccessibilityWindowManager.AccessibilityEventSender,
        AccessibilitySecurityPolicy.AccessibilityUserManager,
        SystemActionPerformer.SystemActionsChangedListener

IAccessibilityManager.Stub 中定义了 interrupt 方法,AccessibilityManagerService 实现了这个方法,并在这个方法中调用了 IAccessibilityServiceClientWrapper#onInterrupt():

// in IAccessibilityManager
				private static class Proxy implements IAccessibilityManager {
            // ... 

            public void interrupt(int userId) throws RemoteException {
            }
				}
// in AccessibilityManagerService
		@Override
    public void interrupt(int userId) {
        List<IAccessibilityServiceClient> interfacesToInterrupt;
        synchronized (mLock) {
            // We treat calls from a profile as if made by its parent as profiles
            // share the accessibility state of the parent. The call below
            // performs the current profile parent resolution.
            final int resolvedUserId = mSecurityPolicy
                    .resolveCallingUserIdEnforcingPermissionsLocked(userId);
            // This method does nothing for a background user.
            if (resolvedUserId != mCurrentUserId) {
                return;
            }
            List<AccessibilityServiceConnection> services =
                    getUserStateLocked(resolvedUserId).mBoundServices;
            int numServices = services.size();
            interfacesToInterrupt = new ArrayList<>(numServices);
            for (int i = 0; i < numServices; i++) {
                AccessibilityServiceConnection service = services.get(i);
                IBinder a11yServiceBinder = service.mService;
                IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
                if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
                    interfacesToInterrupt.add(a11yServiceInterface);
                }
            }
        }
        for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
            try {
                interfacesToInterrupt.get(i).onInterrupt();
            } catch (RemoteException re) {
                Slog.e(LOG_TAG, "Error sending interrupt request to "
                        + interfacesToInterrupt.get(i), re);
            }
        }
    }

AccessibilityManagerService 的 interrupt 方法的注释,仍然让我无法理解:

请求所有无障碍服务的反馈中断。
Requests feedback interruption from all accessibility services.

那么只能继续跟进哪里调用了 AccessibilityManagerService 的 interrupt 方法。
最后在 frameworks/base/core/java/android/view/accessibility/AccessibilityManager.java # interrupt() 方法发现了调用。

AccessibilityManager#interrupt() 备注是:

Requests feedback interruption from all accessibility services.
请求所有无障碍服务的反馈中断。

到了这里彻底断了调用链(没有任何位置调用该方法)。

但是! 很明显 AccessibilityManager 这个东西是 Framework 层供应用层调用的。就是 Context.getSystemService 可以访问到的 Manager 。

查看 AccessibilityManager 的备注:

AccessibilityManager 是 系统级服务,用作 AccessibilityEvent 的事件调度,并提供用于查询系统无障碍状态的工具方法。

也就是说 ·AccessibilityManager#interrupt()· 可以由开发中手动调用。

手写一个测试代码看看,将下面的代码放在一个点击事件中:

val am = getSystemService(ACCESSIBILITY_SERVICE) as? AccessibilityManager
am?.interrupt()

在无障碍服务的实现类中的 onInterrupt 方法中打印日志:

    override fun onInterrupt() {
        Log.i("BaseA11yService", "onInterrupt")
    }

运行代码,发现的确打印出了日志:

2022-09-26 15:17:34.375 I/BaseA11yService: onUnbind
2022-09-26 15:17:44.094 I/BaseA11yService: onInterrupt

顺便提一下,在设置中关闭无障碍服务,会回调 AccessibilityService 的 onUnBind 。

总结

所以 onInterrupt 这个抽象方法回调,是应用层可以自行实现打点位置的。
整体的调用逻辑是:

// 从上到下为回调顺序
AccessibilityManager.interrupt()
|
AccessibilityManagerService.interrupt() // 本质是 IAccessibilityManager.Stub.interrupt()
|
IAccessibilityServiceClientWrapper.onInterrupt() // 本质是 IAccessibilityServiceClient.Stub.onInterrupt()
|
HandlerCaller.sendMessage(message) // DO_ON_INTERRUPT 类型的 message
|
IAccessibilityServiceClientWrapper.executeMessage(message) // 本质是 HandlerCaller.Callback.executeMessage(message)
|
AccessibilityService.Callbacks.onInterrupt() // AccessibilityService.onBind 中的匿名对象
|
AccessibilityService.onInterrupt() // 无障碍服务自己的抽象方法 onInterrupt

作者:自动化BUG制造器
链接:https://juejin.cn/post/7147602016737427492
更多Android学习资料可点击下方卡片~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
当使用UiAutomator框架调用Accessibility Service时,可以使用以下代码示例: ```java import android.accessibilityservice.AccessibilityService; import android.content.Intent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { // 在这里处理来自Accessibility Service的事件 // 例如,获取当前活动窗口的根节点 AccessibilityNodeInfo rootNode = getRootInActiveWindow(); if (rootNode != null) { // 进行节点遍历,查找特定的控件 AccessibilityNodeInfo targetNode = findNodeById(rootNode, "com.example.app:id/myButton"); if (targetNode != null) { // 在这里执行你的操作,比如点击按钮 targetNode.performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } @Override public void onInterrupt() { // 在这里处理服务中断的情况 } @Override protected void onServiceConnected() { super.onServiceConnected(); // 在这里执行初始化操作,例如注册监听器 } @Override public boolean onUnbind(Intent intent) { // 在这里执行清理操作,例如注销监听器 return super.onUnbind(intent); } // 辅助方法:通过控件ID查找节点 private AccessibilityNodeInfo findNodeById(AccessibilityNodeInfo rootNode, String id) { if (rootNode == null) { return null; } if (rootNode.getViewIdResourceName() != null && rootNode.getViewIdResourceName().equals(id)) { return rootNode; } for (int i = 0; i < rootNode.getChildCount(); i++) { AccessibilityNodeInfo childNode = rootNode.getChild(i); AccessibilityNodeInfo targetNode = findNodeById(childNode, id); if (targetNode != null) { return targetNode; } } return null; } } ``` 在此示例中,我们创建了一个名为`MyAccessibilityService`的类,它扩展了`AccessibilityService`。在`onAccessibilityEvent`方法中,我们可以处理来自Accessibility Service的事件。我们可以使用`getRootInActiveWindow`方法获取当前活动窗口的根节点,并使用`findNodeById`辅助方法查找特定的控件节点。如果找到了目标节点,我们可以执行相应的操作,比如点击按钮。 请注意,为了让应用程序使用我们的Accessibility Service,还需要在AndroidManifest.xml文件中声明和注册该服务: ```xml <service android:name=".MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> ``` 上述代码示例仅用于演示如何使用UiAutomator调用Accessibility Service。实际使用中,您可能需要根据自己的应用程序和需求进行适当的修改和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值