我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

本篇内容主要讲解“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无障碍监听通知怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

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

android无障碍服务功能怎么实现

Android无障碍服务功能可以通过编写无障碍服务来实现。以下是实现无障碍服务功能的一般步骤:1. 创建一个继承自AccessibilityService的类,例如 MyAccessibilityService。2. 在AndroidMan
2023-10-07

Android开发怎么实现Chip监听及ChipGroup监听

这篇文章主要介绍“Android开发怎么实现Chip监听及ChipGroup监听”,在日常操作中,相信很多人在Android开发怎么实现Chip监听及ChipGroup监听问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对
2023-07-02

怎么在Android中利用Activity实现一个监听器

怎么在Android中利用Activity实现一个监听器?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Activity在Activity中,使用findViewById(int
2023-06-14

Android中的监听触摸事件怎么在Fragment中实现

本篇文章为大家展示了Android中的监听触摸事件怎么在Fragment中实现,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。activity的触摸事件 @Override public boolea
2023-05-31

Vue怎么通过监听滚动事件实现动态锚点

本文小编为大家详细介绍“Vue怎么通过监听滚动事件实现动态锚点”,内容详细,步骤清晰,细节处理妥当,希望这篇“Vue怎么通过监听滚动事件实现动态锚点”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。具体效果如下:一、
2023-07-04

Android开发中怎么样实现屏幕切换监听功能

本篇文章为大家展示了Android开发中怎么样实现屏幕切换监听功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。由于屏幕切换会调用activity的各个生命周期,需要在manifest的activi
2023-05-31

Android监听手机电话状态与发送邮件通知来电号码的方法(基于PhoneStateListene实现)

本文实例讲述了Android监听手机电话状态与发送邮件通知来电号码的方法。分享给大家供大家参考,具体如下: 在android中可以用PhoneStateListener来聆听手机电话状态(比如待机、通话中、响铃等)。本例是通过它来监听手机电
2022-06-06

Android应用中怎么实现通知栏闪动效果

Android应用中怎么实现通知栏闪动效果?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。显示通知代码:private void showNotification(Context
2023-05-31

Android媒体通知栏多系统适配怎么实现

今天小编给大家分享一下Android媒体通知栏多系统适配怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。需要考虑的问题
2023-07-05

怎么在Android应用中利用ListView实现一个监听滑动事件

怎么在Android应用中利用ListView实现一个监听滑动事件?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。ListView的主要有两种滑动事件监听方法,OnTouchLi
2023-05-31

Android开发中怎么实现一个沉浸式通知栏

Android开发中怎么实现一个沉浸式通知栏?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。①DrawerLayout+Toolbar添加依赖库(谷歌提供)compile com
2023-05-31

怎么在Android中利用google实现一个消息通知功能

怎么在Android中利用google实现一个消息通知功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 定义一个派生于WakefulBroadcastReceiver的类
2023-05-31

Android中怎么使用AlarmManager和Notification实现定时通知提醒功能

这期内容当中小编将会给大家带来有关Android中怎么使用AlarmManager和Notification实现定时通知提醒功能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。AlarmManager简介A
2023-05-30

Android切换前后台点击通知进入当前页面怎么实现

本篇内容主要讲解“Android切换前后台点击通知进入当前页面怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android切换前后台点击通知进入当前页面怎么实现”吧!首先,先展示效果,有
2023-07-05

Android无需读写权限通过临时授权读写用户文件怎么实现

这篇文章主要讲解了“Android无需读写权限通过临时授权读写用户文件怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android无需读写权限通过临时授权读写用户文件怎么实现”吧!在
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录