返回顶部
首页 > 资讯 > 精选 >Android无障碍监听通知怎么实现
  • 103
分享到

Android无障碍监听通知怎么实现

2023-07-02 16:07:05 103人浏览 八月长安
摘要

本篇内容主要讲解“Android无障碍监听通知怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android无障碍监听通知怎么实现”吧!监听通知Android 中的 Accessibili

本篇内容主要讲解“Android无障碍监听通知怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android无障碍监听通知怎么实现”吧!

监听通知

Android 中的 AccessibilityService 可以监听通知信息的变化,首先需要创建一个无障碍服务,这个教程可以自行百度。在无障碍服务的配置文件中,需要以下配置:

<accessibility-service...android:accessibilityEventTypes="其他内容|typeNotificationStateChanged"android:canRetrieveWindowContent="true" />

然后在 AccessibilityService 的 onAccessibilityEvent 方法中监听消息:

override fun onAccessibilityEvent(event: AccessibilityEvent?) {    when (event.eventType) {        AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> {            Log.d(Tag, "Notification: $event")        }    }}

当有新的通知或 Toast 出现时,在这个方法中就会收到 AccessibilityEvent 。

另一种方案是通过 NotificationListenerService 进行监听,这里不做详细介绍了。两种方案的应用场景不同,推荐使用 NotificationListenerService 而不是无障碍服务。stackoverflow 上一个比较好的回答:

It depends on WHY you want to read it. The general answer would be Notification Listener. Accessibility Services are for unique accessibility services. A user has to enable an accessibility service from within the Accessibility Service menu (where TalkBack and Switch Access are). Their ability to read notifications is a secondary ability, to help them achieve the Goal of creating assistive technologies (alternative ways for people to interact with mobile devices).

Whereas, Notification Listeners, this is their primary goal. They exist as part of the context of an app and as such don't need to be specifically turned on from the accessibility menu.

Basically, unless you are in fact building an accessibility service, you should not use this approach, and go with the generic Notification Listener.

无障碍服务监听通知逻辑

从用法中可以看出一个关键信息 -- TYPE_NOTIFICATION_STATE_CHANGED ,通过这个事件类型入手,发现它用于两个类中:

  • ToastPresenter:用于在应用程序进程中展示系统 UI 样式的 Toast 。

  • NotificationManagerService:通知管理服务。

ToastPresenter

ToastPresenter 的 trySendAccessibilityEvent 方法中,构建了一个 TYPE_NOTIFICATION_STATE_CHANGED 类型的消息:

public void trySendAccessibilityEvent(View view, String packageName) {    if (!MaccessibilityManager.isEnabled()) {        return;    }    AccessibilityEvent event = AccessibilityEvent.obtain(            AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);    event.setClassName(Toast.class.getName());    event.setPackageName(packageName);    view.dispatchPopulateAccessibilityEvent(event);    mAccessibilityManager.sendAccessibilityEvent(event);}

这个方法的调用在 ToastPresenter 中的 show 方法中:

public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,        int xOffset, int yOffset, float horizontalMargin, float verticalMargin,        @Nullable ITransientNotificationCallback callback) {    // ...     trySendAccessibilityEvent(mView, mPackageName);     // ...}

而这个方法的调用就是在 Toast 中的 TN 类中的 handleShow 方法。

Toast.makeText(this, "", Toast.LENGTH_SHORT).show()

在 Toast 的 show 方法中,获取了一个 INotificationManager ,这个是 NotificationManagerService 在客户端暴露的 Binder 对象,通过这个 Binder 对象的方法可以调用 NMS 中的逻辑。

也就是说,Toast 的 show 方法调用了 NMS :

public void show() {    // ...    INotificationManager service = getService();    String pkg = mContext.getOpPackageName();    TN tn = mTN;    tn.mNextView = mNextView;    final int displayId = mContext.getDisplayId();    try {        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {            if (mNextView != null) {                // It's a custom toast                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);            } else {                // It's a text toast                ITransientNotificationCallback callback = new CallbackBinder(mCallbacks, mHandler);                service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);            }        } else {            service.enqueueToast(pkg, mToken, tn, mDuration, displayId);        }    } catch (RemoteException e) {        // Empty    }}

这里是 enqueueToast 方法中,最后调用:

private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,@Nullable ITransientNotification callback, int duration, int displayId,@Nullable ITransientNotificationCallback textCallback) {  // ...record = getToastRecord(callingUid, callingPid, pkg, token, text, callback, duration, windowToken, displayId, textCallback);  // ...}

getToastRecord 中根据 callback 是否为空产生了不同的 Toast :

private ToastRecord getToastRecord(int uid, int pid, String packageName, IBinder token,        @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration,        Binder windowToken, int displayId,        @Nullable ITransientNotificationCallback textCallback) {    if (callback == null) {        return new TextToastRecord(this, mStatusBar, uid, pid, packageName, token, text,duration, windowToken, displayId, textCallback);    } else {        return new CustomToastRecord(this, uid, pid, packageName, token, callback, duration, windowToken, displayId);    }}

两者的区别是展示对象的不同:

  • TextToastRecord 因为 ITransientNotification 为空,所以它是通过 mStatusBar 进行展示的:

        @Override    public boolean show() {        if (DBG) {            Slog.d(TAG, "Show pkg=" + pkg + " text=" + text);        }        if (mStatusBar == null) {            Slog.w(TAG, "StatusBar not available to show text toast for package " + pkg);            return false;        }        mStatusBar.showToast(uid, pkg, token, text, windowToken, getDuration(), mCallback);        return true;    }
  • CustomToastRecord 调用 ITransientNotification 的 show 方法:

        @Override    public boolean show() {        if (DBG) {            Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback);        }        try {            callback.show(windowToken);            return true;        } catch (RemoteException e) {            Slog.w(TAG, "Object died trying to show custom toast " + token + " in package "                    + pkg);            mNotificationManager.keepProcessAliveForToastIfNeeded(pid);            return false;        }    }

    这个 callback 最在 Toast.show() 时传进去的 TN :

    TN tn = mTN;service.enqueueToast(pkg, mToken, tn, mDuration, displayId);

    也就是调用到了 TN 的 show 方法:

            @Override        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)        public void show(IBinder windowToken) {            if (localLOGV) Log.v(TAG, "SHOW: " + this);            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();        }

TN 的 show 方法中通过 mHandler 来传递了一个类型是 SHOW 的消息:

            mHandler = new Handler(looper, null) {                @Override                public void handleMessage(Message msg) {                    switch (msg.what) {                        case SHOW: {                            IBinder token = (IBinder) msg.obj;                            handleShow(token);                            break;                        }                        case HIDE: {                            handleHide();                            // Don't do this in handleHide() because it is also invoked by                            // handleShow()                            mNextView = null;                            break;                        }                        case CANCEL: {                            handleHide();                            // Don't do this in handleHide() because it is also invoked by                            // handleShow()                            mNextView = null;                            try {                                getService().cancelToast(mPackageName, mToken);                            } catch (RemoteException e) {                            }                            break;                        }                    }                }            };

而这个 Handler 在处理 SHOW 时,会调用 handleShow(token) 这个方法里面也就是会触发 ToastPresenter 的 show 方法的地方:

public void handleShow(IBinder windowToken) {    // If a cancel/hide is pending - no need to show - at this point    // the window token is already invalid and no need to do any work.    if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {        return;    }    if (mView != mNextView) {        // remove the old view if necessary        handleHide();        mView = mNextView;      // 【here】        mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, mHorizontalMargin, mVerticalMargin, new CallbackBinder(getCallbacks(), mHandler));    }}

本章节最开始介绍到了 ToastPresenter 的 show 方法中会调用 trySendAccessibilityEvent 方法,也就是从这个方法发送类型是 TYPE_NOTIFICATION_STATE_CHANGED 的无障碍消息给无障碍服务的。

NotificationManagerService

在通知流程中,是通过 NMS 中的 sendAccessibilityEvent 方法来向无障碍发送消息的:

void sendAccessibilityEvent(Notification notification, CharSequence packageName) {    if (!mAccessibilityManager.isEnabled()) {        return;    }    AccessibilityEvent event =        AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);    event.setPackageName(packageName);    event.setClassName(Notification.class.getName());    event.setParcelableData(notification);    CharSequence tickerText = notification.tickerText;    if (!TextUtils.isEmpty(tickerText)) {        event.getText().add(tickerText);    }    mAccessibilityManager.sendAccessibilityEvent(event);}

这个方法的调用有两处,均在 NMS 的 buzzBeepBlinkLocked 方法中,buzzBeepBlinkLocked 方法是用来处理通知是否应该发出铃声、震动或闪烁 LED 的。省略无关逻辑:

int buzzBeepBlinkLocked(NotificationRecord record) {    // ...    if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN && !suppressedByDnd) {        sendAccessibilityEvent(notification, record.getSbn().getPackageName());        sentAccessibilityEvent = true;    }    if (aboveThreshold && isNotificationForCurrentUser(record)) {        if (mSystemReady && mAudioManager != null) {            // ...            if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {                if (!sentAccessibilityEvent) {                    sendAccessibilityEvent(notification, record.getSbn().getPackageName());                    sentAccessibilityEvent = true;                }                // ...            } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {                hasValidSound = false;            }        }    }    // ...}

buzzBeepBlinkLocked 的调用路径有两个:

  • handleRankingReconsideration 方法中 RankingHandlerWorker (这是一个 Handler)调用 handleMessage 处理 MESSAGE_RECONSIDER_RANKING 类型的消息:

    @Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MESSAGE_RECONSIDER_RANKING:handleRankingReconsideration(msg);break;case MESSAGE_RANKING_SORT:handleRankingSort();break;}}

    handleRankingReconsideration 方法中调用了 buzzBeepBlinkLocked :

    private void handleRankingReconsideration(Message message) {    // ...    synchronized (mNotificationLock) {        // ...        if (interceptBefore && !record.isIntercepted()                && record.isNewEnoughForAlerting(System.currentTimeMillis())) {            buzzBeepBlinkLocked(record);        }    }    if (changed) {        mHandler.scheduleSendRankingUpdate();    }}
  • PostNotificationRunnable 的 run 方法。

PostNotificationRunnable

这个东西是用来发送通知并进行处理的,例如提示和重排序等。

PostNotificationRunnable 的构建和 post 在 EnqueueNotificationRunnable 中。在 EnqueueNotificationRunnable 的 run 最后,进行了 post:

public void run() {// ...    // tell the assistant service about the notification    if (mAssistants.isEnabled()) {        mAssistants.onNotificationEnqueuedLocked(r);        mHandler.postDelayed(new PostNotificationRunnable(r.geTKEy()), DELAY_FOR_ASSISTANT_TIME);    } else {        mHandler.post(new PostNotificationRunnable(r.getKey()));    }}

EnqueueNotificationRunnable 在 enqueueNotificationInternal 方法中使用,enqueueNotificationInternal 方法是 INotificationManager 接口中定义的方法,它的实现在 NotificationManager 中:

    public void notifyAsPackage(@NonNull String targetPackage, @Nullable String tag, int id,            @NonNull Notification notification) {        INotificationManager service = getService();        String sender = mContext.getPackageName();        try {            if (localLOGV) Log.v(TAG, sender + ": notify(" + id + ", " + notification + ")");            service.enqueueNotificationWithTag(targetPackage, sender, tag, id,                    fixNotification(notification), mContext.getUser().getIdentifier());        } catch (RemoteException e) {            throw e.rethrowFromSystemServer();        }    }    @UnsupportedAppUsage    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)    {        INotificationManager service = getService();        String pkg = mContext.getPackageName();        try {            if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,                    fixNotification(notification), user.getIdentifier());        } catch (RemoteException e) {            throw e.rethrowFromSystemServer();        }    }

一般发送一个通知都是通过 NotificationManager 或 NotificationManagerCompat 来发送的,例如:

NotificationManagerCompat.from(this).notify(1, builder.build());

NotificationManagerCompat 中的 notify 方法本质上调用的是 NotificationManager:

// NotificationManagerCompatpublic void notify(int id, @NonNull Notification notification) {    notify(null, id, notification);}public void notify(@Nullable String tag, int id, @NonNull Notification notification) {    if (useSideChannelForNotification(notification)) {        pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification));        // Cancel this notification in notification manager if it just transitioned to being side channelled.        mNotificationManager.cancel(tag, id);    } else {        mNotificationManager.notify(tag, id, notification);    }}

mNotificationManager.notify(tag, id, notification) 中的实现:

public void notify(String tag, int id, Notification notification) {    notifyAsUser(tag, id, notification, mContext.getUser());}public void cancel(@Nullable String tag, int id) {    cancelAsUser(tag, id, mContext.getUser());}

串起来了,最终就是通过 NotificationManager 的 notify 相关方法发送通知,然后触发了通知是否要触发铃声/震动/LED 闪烁的逻辑,并且在这个逻辑中,发送出了无障碍消息。

到此,相信大家对“Android无障碍监听通知怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: Android无障碍监听通知怎么实现

本文链接: https://lsjlt.com/news/342912.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Android无障碍监听通知怎么实现
    本篇内容主要讲解“Android无障碍监听通知怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android无障碍监听通知怎么实现”吧!监听通知Android 中的 Accessibili...
    99+
    2023-07-02
  • Android无障碍监听通知的实战过程
    目录监听通知无障碍服务监听通知逻辑ToastPresenterNotificationManagerServicePostNotificationRunnable总结监听通知 And...
    99+
    2024-04-02
  • android无障碍服务功能怎么实现
    Android无障碍服务功能可以通过编写无障碍服务来实现。以下是实现无障碍服务功能的一般步骤:1. 创建一个继承自Accessibi...
    99+
    2023-10-07
    android
  • Android开发怎么实现Chip监听及ChipGroup监听
    这篇文章主要介绍“Android开发怎么实现Chip监听及ChipGroup监听”,在日常操作中,相信很多人在Android开发怎么实现Chip监听及ChipGroup监听问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对...
    99+
    2023-07-02
  • Android无障碍服务performAction怎么调用
    这篇文章主要介绍“Android无障碍服务performAction怎么调用”,在日常操作中,相信很多人在Android无障碍服务performAction怎么调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
    99+
    2023-07-02
  • Android无障碍全局悬浮窗实现示例
    目录无障碍添加 UI配置分析TypeFlagLayoutInDisplayCutoutModeAndroid 无障碍的全局悬浮窗可以在屏幕上添加 UI 供用户进行快捷操作,可以展示在...
    99+
    2024-04-02
  • watch监听怎么实现
    本篇内容主要讲解“watch监听怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“watch监听怎么实现”吧! 父传子;父组件通过:purchaserId...
    99+
    2024-04-02
  • android AccessibilityService无障碍功能开发,实现自动化测试
    android AccessibilityService无障碍功能开发,实现自动化测试,这里使用抖音为例子,仅供技术研究学习使用。 使用方法 安装好APP后,需要打开无障碍功能,打开后,在次打开抖音APP,随便找一个直播间,上下滑动切换直接...
    99+
    2023-09-13
    android android studio java
  • spring整合redis消息监听通知使用的实现示例
    目录问题引入1.1 过期问题描述1.2 常用解决方案分析1.3.整合SpringData Redis开发spring整合redis监听消息1. 配置监听redis消息2 测试消息结合...
    99+
    2024-04-02
  • Android实现自动点击无障碍服务功能的实例代码
    ps: 不想看代码的滑到最下面有apk包百度网盘下载地址 1. 先看效果图 不然都是耍流氓 2.项目目录 3.一些配置 build.gradle plugins { ...
    99+
    2024-04-02
  • python怎么实现监听键盘
    小编给大家分享一下python怎么实现监听键盘,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Python的优点有哪些1、简单易用,与C/C++、Java、C# 等...
    99+
    2023-06-14
  • 怎么在Android中利用Activity实现一个监听器
    怎么在Android中利用Activity实现一个监听器?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Activity在Activity中,使用findViewById(int...
    99+
    2023-06-14
  • C#中怎么实现监听串口
    C#中怎么实现监听串口,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。C#串口监听的实现在 Visual Stdio 2005中,对于串口操作Framework提...
    99+
    2023-06-17
  • 怎么实现一个Java监听器
    这期内容当中小编将会给大家带来有关怎么实现一个Java监听器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一、前言监听器就是监听事件源是否发生了某一个事件,当发生指定监听的事件时事件源会向已注册的监听器发...
    99+
    2023-06-15
  • SpringBoot监听器模式怎么实现
    本篇内容介绍了“SpringBoot监听器模式怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!我们来以应用启动事件:Applicati...
    99+
    2023-07-02
  • Android中的监听触摸事件怎么在Fragment中实现
    本篇文章为大家展示了Android中的监听触摸事件怎么在Fragment中实现,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。activity的触摸事件 @Override public boolea...
    99+
    2023-05-31
    android fragment roi
  • Vue怎么通过监听滚动事件实现动态锚点
    本文小编为大家详细介绍“Vue怎么通过监听滚动事件实现动态锚点”,内容详细,步骤清晰,细节处理妥当,希望这篇“Vue怎么通过监听滚动事件实现动态锚点”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。具体效果如下:一、...
    99+
    2023-07-04
  • 使用JavaScript怎么实现数据监听
    使用JavaScript怎么实现数据监听?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。javascript是一种什么语言javascript是一种动态类型、弱类...
    99+
    2023-06-14
  • JavaWeb中怎么实现一个监听器
    JavaWeb中怎么实现一个监听器,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 监听器实现一个监听器的接口;(有n种监听器)1.1 编写一个监听器(实现监听器接口)Onl...
    99+
    2023-06-20
  • 怎么使用Vue代码实现监听
    这篇“怎么使用Vue代码实现监听”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用Vue代码实现监听”文章吧。demo:...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作