乐趣区

关于后端:藏在微信里的温度无障碍开发框架分享

👉 腾小云导读

现我国现有 4471w 视障 / 听障人士,60 岁及以上人群达 2.6 亿规模。微信作为国民级利用,实现无障碍火烧眉毛。为了帮忙他们更好地应用微信 App,Android 微信实现了适老化及无障碍革新。本文次要介绍 Android 微信开发团队依据适老化及无障碍需要,实现的一个帮助业务侧进行无障碍性能开发的框架。心愿能给宽广开发爱好者带来帮忙和启发!


👉 看目录,点珍藏

1 无障碍需要框架背景

1.1 无障碍需要

1.2 框架简介

2 无障碍开发基础知识

2.1 读屏软件辨认 View 原理

2.2 读屏软件后的事件散发原理

3 框架实现的整体流程和执行原理

3.1 整体流程

3.2 执行原理

4 外围阐明:全局热区补足机制

4.1 背景阐明

4.2 具体实现

4.3 额定阐明

5 走查工具

6 总结

01、无障碍需要框架

目前,业界曾经有共识性的无障碍开发守则。例如 Web Content Accessibility Guidelines (WCAG) 2.0,它是由互联网的次要国际标准组织万维网联盟 (W3C) 的 Web 可拜访性倡导 (WAI) 公布的一系列 Web 可拜访性指南的一部分。

此外,WAI-ARIA(可拜访的富 Internet 应用程序套件)是由万维网联盟(W3C)公布的一项对于 A11 Y 技术利用标准。该标准定义了一种使残障人士更易于拜访 Web 内容和 Web 应用程序的办法,减少 HTML、JavaScript 和相干技术开发的网站动静内容以及用户界面组件的可拜访性。

目前,Android 没有官网对立、不便的框架,官网提供的原生 api 并不是特地好用,所以微信团队对其进行参考,开发了一个无障碍框架,基于原生的 api 进行了再封装,将繁琐的无障碍适配逻辑封装在底层,以申明式接口的模式,让下层业务能以更简便更解耦的代码,实现无障碍的适配。接下来咱们进行分享:

1.1 无障碍需要

本框架次要具备以下个性:

  • 可感知性:包含大字体适配,色彩对比度等。
  • 可操作性:次要是过小热区的放大,进步老年人 / 残疾人的交互体验。
  • 可了解性:微信应提供读屏文案等信息,帮忙盲人在开启 Talkback 等读屏软件的状况下,失常应用微信。

上面给出一些较为典型的需要:

  • 需要 1:过小热区的放大

需要是要求微信内的所有可交互控件,可点击范畴不得低于 44dp * 44dp。

大小不合规的控件,如果一个个进行排查、布局批改。工程量宏大。

  • 需要 2:响应区域会随无障碍开关发生变化

该 Item 由一个 SwitchButton + TextView 组成。

开启 Talkback 时,整个 Item 辨认为一个焦点,选中双击是触发点击 switch 的逻辑。在无障碍模式下,选中双击是间接触发相应控件的 Click 事件。然而在不开 Talkback 的状况下点击 Item 又无需响应,只响应 SwitchButton。也就是点击区域会随 Talkback 开关发生变化。

实现可能是:在 ItemClick 中进行 if 判断。但这样写侵入性高,难保护。

  • 需要 3:读屏文案由其余的控件的值组合

选中头像,读屏文案:腾讯行政的头像,有 2 条未读音讯。须要读出列表中其余关联内容,这种只能把适配代码侵入到 Adapter 中。

1.2 框架简介

框架将不同的无障碍需要的实现进行封装,形象成不同的规定。

业务侧能够将一个页面 / 业务的无障碍需要,在一个配置类里应用规定表达出来,再由框架进行解决。实现相应的成果。

class ChatAccessibility(activity: AppCompatActivity) :  
BaseAccessibilityConfig(activity) {override fun initConfig() {  
        // 设置 contentDesc  
   view(rootId,viewId).desc(R.string.send_smiley)  
        // ...  
  }  
}

框架基类 BaseAccessibilityConfig 提供了一系列用于表白规定的 api,包含但不限于如下性能:

  • 通过配置对立设置 contentDescription
  • 反对把多个 View 组合成一体进行读屏
  • 通过配置禁用某个 View 被 Talkback 聚焦的能力
  • 反对按指定程序进行读屏,反对部分管制 Talkback 聚焦程序
  • 反对设定在 Activity 启动后的第一个读屏控件
  • 反对对某个父 View 的 disableChildren 性能
  • 在某个 View 满足条件时,对其进行读屏,但不聚焦
  • 在某个 View 满足条件时,读出提前设定的 string,但不聚焦
  • 全局热区宽高补齐至 44dp,并提供自定义热区放大 / 禁用热区放大的性能 …

02、无障碍开发基础知识

在深刻理解框架的设计前,先来介绍一些无障碍性能开发的基础知识。

2.1 基础知识 1:读屏软件辨认 View 原理

读屏软件无奈间接辨认到 View,只能辨认到 View 提供的虚构节点「Node」,View 和虚构节点个别是一一对应的。当页面内容发生变化,比方 View 被设值,或者产生滚动等状况,View 会向无障碍零碎发送一个事件,告诉零碎。

而后零碎就回头向 View 索取节点,组成页面更新后新的节点树,而 「节点树 和 ViewTree 是一一对应的」。此时读屏软件拿到的就是新的内容了。

2.2 基础知识 2:读屏软件后的事件散发流程

分为高低两局部:读屏软件拦挡解决行为、读屏软件承受事件。

流程如下:

  • 读屏软件拦挡用户 Touch 事件,依据事件的坐标去定位到指标节点。
  • 将 Touch 事件解释为节点行为,这里以触摸选中为例,那么就是聚焦行为。
  • 读屏软件通过该节点向无障碍零碎发送,无障碍零碎又转发给 View(聚焦产生的绿框就是在 View 的外部解决里去绘制的)。
  • 生成新的虚构节点并提供给读屏软件后,读屏软件组合信息,通过 TTS 语音引擎的 api 读出。

读屏软件展现给用户的所有信息,全副来自虚构节点。能够在节点生成的过程中,批改节点的信息,所以这里是一个绝佳的 「信息自定义」 的中央。

采纳将所有的 View 都「Wrap 一层 AccessibilityDelegate」的形式,「在 onInitializeAccessibilityNodeInfo 办法中批改节点信息」。

03、框架实现整体流程与执行原理

3.1 整体流程

  1. 业务侧实现规定配置类,编写的规定会进入配置池。
  2. 框架在 View 生成节点给零碎的时候进行拦挡 「(onInitializeAccessibilityNodeInfo)」
  3. 在配置池中寻找匹配的规定。
  4. 依据匹配的规定对节点进行批改。
  5. 最初生成的节点就会由零碎交由给读屏软件进行读屏。

3.2 执行原理

外围原理:采纳基于责任链的流水线来解决。整体流程次要分为两局部:

  • View 预处理责任链(图示右边):执行预进去操作,如异步生成缓存、View 标记等;
  • 节点解决责任链(图示左边):节点解决的同时会同步查找规定进行设置。

接下来次要简略介绍下框架的一个外围性能的实现:「全局热区补足机制」(位于框架流程中的预处理责任链中的一环)。

04、外围阐明:全局热区补足机制

4.1 背景阐明

  • 需要阐明

过小热区放大,即微信内的所有可交互控件可点击范畴不得低于 44dp * 44dp,像一些大小不合规的控件,如果一个个进行排查、布局批改,工程量太宏大。还有热区其余一些需要 etc。

  • 问题难点

个别会抉择间接批改 padding,有些甚至须要改变相应布局,但这样的改变工作量太大且容易影响原来视图布局。

  • 解决方案

须要一个全局的热区补足机制,将过小热区补足至标准。

4.2 具体实现

「创立 View 的对立入口」 去设置 TouchDelegate 代理,由父 View 作为 TouchDelegate 的承载 View 去代理 Touch 事件,这里有几个问题须要解决:

  • 如何找到适合的承载 View
  • 热区及时更新
  • 性能优化
  • 读屏模式下的热区扩充

上面咱们别离开展讲。

  • 重点问题 1:如何找到适合的承载 View

从指标 View 向上冒泡,找到一个适合的父 View。那么须要 「冒泡终止条件」。首先条件一必定是 「足够大」。以后 View 够大了就没必要再往上冒了。

然而这样会存在问题:子 View 的 Click 优先级高于父 View 的 TouchDelegate。事件派发机制:

从父 View 往子 View 派发,从子 View 向上解决。View 的事件处理程序是先 OnTouchListener, 而后是 TouchDelegate,再是 Click、LongClick。

所以会导致下图的状况:

目前进行了折中解决,相比上图,显然是下图的放大后的体验更佳:

同时退出了条件二:「该承载 View 是 Clickable、LongClickable」。最终计划流程确定如下:

  • 重点问题 2:热区及时更新

背景: 承载 View 的 TouchDelegate 须要的参数蕴含一个 Rect,也就是对扩充的热区进行响应。

问题: 这个矩阵是提前传入,且和 小 View 没有间接的关系。如果小 View 的布局产生变动,会导致扩充后热区没有及时跟上变动。导致热区错位。

解决方案: 在 小 View 的 onLayoutChange 中从新进行一遍 ·View 扩充计划· 的解决。同时为了避免 onLayoutChange  执行过于频繁,将 onLayoutChange 包装成 View 的一个事件。如果短时间内屡次 onLayoutChange,则只在最初一次 onLayoutChange 的时候进行「View 扩充计划」解决。

  • 重点问题 3:性能优化

背景:最后的 View 扩充计划执行机会是在创立 View 的对立入口,也就是在 LayoutInflate 的 onCreateView 中同步执行,每个 View 都得执行。

问题:因为 View 数量较为宏大,所以存在较大的性能隐患。

解决方案:采纳了异步计划并同时对 View 解决工作进行收拢。将执行机会提前到 LayoutInflate.inflate 并异步解决,在异步工作中去遍历该 inflate 的根 View 的所有子 View。尽量不去阻塞主线程的运行。

  • 重点问题 4:读屏模式下的热区扩充

通过下面的实现,点击热区的确是扩充了。然而在读屏模式下选中的时候,选中的框并没有扩充。那么首先须要晓得,选中时的框是以什么作为 Bound。

绿框的绘制外围逻辑位于 ViewRootImpl 中的一个 drawAccessibilityFocusedDrawableIfNeeded(),该办法的调用机会是用户触摸选中某个 View 后,传递到 ViewRootImpl 时进行调用,也就是读屏选中的绿框是由零碎绘制的,而不是由读屏软件绘制的。从源码中可能得悉的是,绿框的 Bound 依据是否有虚构节点,分为两种状况:

private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {  
    final Rect bounds = mAttachInfo.mTmpInvalRect;  
    if (getAccessibilityFocusedRect(bounds)) {final Drawable drawable = getAccessibilityFocusedDrawable();  
        if (drawable != null) {drawable.setBounds(bounds);  
            drawable.draw(canvas);  
        }  
    } else if (mAttachInfo.mAccessibilityFocusDrawable != null) {mAttachInfo.mAccessibilityFocusDrawable.setBounds(0, 0, 0, 0);  
    }  
}  
  
private boolean getAccessibilityFocusedRect(Rect bounds) {  
    ...  
    final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();  
    if (provider == null) {host.getBoundsOnScreen(bounds, true);  
    } else if (mAccessibilityFocusedVirtualView != null) {mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);  
    } else {return false;}  
  ...  
    return !bounds.isEmpty();}

通过跟踪源码发现,这是因为 「绿框的绘制」 是依据 View.getBoundInScreen 获取的矩阵来做到的。而 TouchDelegate 的设置无奈扭转 View.getBoundInScreen 获取到的矩阵。在应用虚构节点的状况下,才会应用虚构节点的 Bound 进行绘制。

对于这个问题,咱们的解决思路是:

  • 对每个 View 设置自定义的 AccessibilityDelegate, 并实现其中的 getAccessibilityNodeProvider 办法。
  • 如果判断 View 须要扩充,在 getAccessibilityNodeProvider 中返回自定义的 Provider。
  • 在自定义的 Provider 中,计算 View 的扩充后的矩阵在屏幕上的地位。
  • 将矩阵设置给虚构节点,并返回给零碎。

4.3 额定阐明

  • 如何匹配规定与 View?

框架将配置池按 Activity 划分,极大缩小抵触概率,同时缩小配置池大小,放慢查找规定的速度,提供 layoutId + viewId,rootId + viewId 两种模式的 View 定位机制。由两个 Id 确定一个 View,缩小抵触。

  • 查找规定工夫长可能导致的主线程卡顿?

因为查找规定的机会是在生成节点,是由零碎触发且无奈异步。在查找规定的过程中,应用预处理的时候提前生成的缓存进行查找,尽可能减少耗时。

05、走查工具

5.1 背景

当实现无障碍需要的开发后,需进行验证。在验证过程中发现开启验证效率低下,需开启读屏软件后,一一元素验证。

5.1.1 解决方案与原理

基于无障碍服务(AccessibilityService)开发、集成了在不开启 Talkback 的状况下能展示读屏区域一个无障碍性能走查工具,无需开启 Talkback 一一手动触摸,就能高效查看无障碍适配状况。

实现原理如下:

  • 自定义实现一个 AccessibilityService 用于获取到以后沉闷窗口的根节点。
  • 每隔 0.5s 进行一次节点的获取:从以后沉闷窗口的根节点遍历所有的节点,一一进行判断是否会被聚焦。
  • 对通过容许聚焦的节点进行信息收集,在一次遍历实现后告诉到 DrawService。
  • 提前在 window 中增加一个 View 用于绘制信息,由 DrawService 进行绘制。

5.2 具体实现

要害实现:如何判断一个节点是否被聚焦,即需了解 Talkback 是如何聚焦,流程如下:

1、如果是反对 WebView 中 Html 无障碍,非凡判断。

2、如果不可见,则不聚焦。

3、判断是否是画中画,像下图的红框这种就是画中画,如果是画中画,这个就是焦点。

4、该节点是否和 window 边界重合等大。对于这种和 window 等大的节点,Talkback 抉择不做聚焦。

5、查看该节点是否 clickable/longClickable/focusable 或者是列表的“会谈话的”顶层视图(满足 ->6 不满足 ->7)列表(ListView/RecycleView)的顶层视图例子如下:

然而聚焦的前提是“会谈话的”。“会谈话的”包含以下几个条件:

  • HasText:包含 contentDescription、text、hintText(包含 Button 的 Text)。
  • hasStateDescription:包含 CheckBox 的已选未选状态、进度条的进度状态等。
  • hasNonActionableSpeakingChildren:含有无奈聚焦、点击然而 HasText 的子 View(如上图通讯录中的“新的敌人”TextView,就是无奈聚焦、点击然而 HasText 的子 View)。

6、基本上满足了步骤 5 就能够视为可聚焦了,然而有一些 View 仅仅是 Focusable,然而却”什么话都没得说“,对于这种 View 应该是要排除的。故按如下步骤做判断:只有是没有子节点的 focusable/clickable/longclickable 的 View,全副聚焦、“会谈话的”全副聚焦 6.3 剩下的就不聚焦了(“不会谈话”、“有子节点”)。

7、能到这一步,阐明步骤 5 不满足,即该节点是一般的不可聚焦的 View。然而避免错过一些没有点击事件的 TextView 之类的须要聚焦,须要再最初做一步判断(这一步也是啥为了保障所有的信息都能够不脱漏);如果没有可聚焦父节点,但依然 hasText 或 hasStateDescription,汇集该节点。

8、一路闯关到这的 View,就终于逃离 TalkBack 的聚焦了。

06、总结

为了帮忙老年人、视障 / 听障人群等更好地应用微信 App,Android 微信实现了适老化及无障碍革新如上。本文次要介绍 Android 微信开发团队依据适老化及无障碍需要,实现的一个帮助业务侧进行无障碍性能开发的框架。咱们在介绍了无障碍开发所波及的 2 大重点基础知识(读屏辨认 View 原理和读屏软件后的事件散发原理)之后,为各位开展回顾了咱们框架具体细节和办法。

以上是本次分享全部内容,欢送大家在评论区分享交换。如果感觉内容有用,欢送转发~

-End-

原创作者|许怀鑫

技术责编|许怀鑫

现我国现有 4471w 视障 / 听障人士,60 岁及以上人群达到 2.6 亿规模。信息无障碍(Web Accessibility)的概念在近几年受到关注。 信息无障碍是指通过信息化伎俩补救身材机能、所处环境等存在的差别,使任何人(无论是健全人还是残疾人、无论是年轻人还是老年人)都能平等、不便、平安地获取、交互、应用信息。微信、QQ、腾讯新闻和腾讯地图等利用加适老化元素,装备为老人而设的“关心模式”;搜狗输入法推出为视障群体量身打造的“保益盲人输入法”……

当说到无障碍,大家第一反馈是弱势群体。实际上,无障碍是实用于全民的。每个人都可能有 遇障时刻。当你手提重物或受伤时,你可能会抉择乘坐无障碍电梯;当你处在嘈杂的环境下看视频时,你可能须要通过字幕获取信息……每个人都是无障碍环境的受益者,视障、听障人群、含残疾人、老年人是信息无障碍的重点受害群体。

事件分享:你还见到过哪些让你眼前一亮的信息无障碍案例?

脑洞时刻:程序员还能够为信息无障碍做些什么?

欢送在公众号评论区聊一聊你的认识。在 4 月 10 日前将你的评论记录截图,发送给腾讯云开发者公众号后盾,可支付腾讯云「开发者秋季限定红包封面」一个,数量无限先到先得😄。咱们还将选取点赞量最高的 1 位敌人,送出腾讯 QQ 公仔 1 个。4 月 10 日中午 12 点开奖。快邀请你的开发者敌人们一起来参加吧!

回复「微信」,支付更多微信的技术 case 和论文资源

退出移动版