😀
记录关于Note接入侧边栏小工具相关事情,包括需求、分析、设计、实现等

🤑 背景

  • 在海外教育的整机上,Note是作为书写应用,是终端用户场景的高频使用应用之一
  • 为了用户交互需要,整机在所有的页面启动之后,默认都会有侧边栏UI,用于方便用户做应用切换等操作
  • 在侧边栏中,有各种应用的入口,同时,还集成了各种常用的小工具入口,如计时器、录屏工具、云课堂、聚光灯等

🥵 需求

  • 客户在使用过程中,发现Note是主要使用应用,而侧边栏的部分小工具,经常和Note在教学场景会结合起来使用,具有较高的绑定关系
  • 为了方便用户使用唤起,客户反馈,希望能将这部分高频使用的小工具入口,添加到Note工具栏中

🥸 前期考虑

由于小工具入口,原先是在侧边栏中做,且不同客户,对于侧边栏,会有不同的定制,note如果直接接入,可能会在后期面临不同客户的侧边栏定制需求时,Note没有及时同步这些定制,从而给客户带来问题,如果Note每次都去同步这些需求,无异于将侧边栏的部分工作,重复到Note中,后续还会带来无穷无尽的开发投入成本。
并且由于Note是独立于侧边栏的,在多语言场景下,还要做翻译适配。
综上,Note不应该单独接入小工具,最好是复用侧边栏的能力,Note只作为入口,和侧边栏进行交互,而将与小工具的交互,委托给侧边栏。

😘 侧边栏接口

在进行了上述的分析之后,与相关同事进行沟通,发现侧边栏确实提供了这样的能力(庆幸),并且定义暴露了完善的接口和数据结构。因此,Note可以直接接入侧边栏的接口,并复用侧边栏的该能力。

接口定义

侧边栏对外暴露的接口是以广播的形式进行的,调用如下:

数据结构定义

侧边栏会把当前系统支持的小工具信息进行json结构化,并放到 all_tools_app_config.json 文件中,其数据结构定义如下:
根据上面的json格式,定义对应的kotlin数据类:

接口数据读取

对于该json文件,其完整的读取方法如下:

🙈 接入状态定义

由于小工具的信息来源与Note中其他工具的来源不一样(前者是通过读取解析外部json文件,后者是Note内部定义的数据信息),且工具栏已经有了一定的封装行为,无法在原先的基础上,直接添加json数据来源的格式,所以为了尽量减少工具栏业务层逻辑的修改,这里需要对小工具操作做一些封装和状态定义,以供工具栏调用

事件、状态定义

为了减少工具栏的调用难度和复杂度,小工具操作只对外暴露一个方法,并通过传入不同意图参数,触发内部执行不同逻辑,具体的事件定义如下
同时,上面的事件主要用于定义工具栏需要向小工具操作类传递的意图,是从上到下的;而对于小工具向上层的状态传递,则需要定义状态,具体的状态定义如下:
定义了状态和事件,怎么将其结合起来,如下:

🤪 Note接入工具栏

Note接入的时候,前期按照普通工具的接入方式,添加入口即可,这里只记录接入小工具的差异点

同步侧边栏命名

由于是接入侧边栏的小工具,所以,为了保持一致性,Note里小工具的命名,也要做相应的同步,具体的做法是在Note启动后,对工具栏做UI更新,并读取最新的json文件,更新到工具栏中

可见性过滤

小工具在Note中是否可用,主要取决于三个因素:
  • Note配置是否支持
  • 侧边栏是否含有该小工具的定义
  • 系统是否预装了该小工具
具体判断小工具是否在Note中可用的逻辑如下:

😵‍💫 Bug记录

系统切换语言-多语言适配(内存泄露)

由于工具栏中小工具的命名,已经适配成从侧边栏的json文件解析,所以,在系统语言切换的时候,需要再次去解析最新的json,并更新到工具栏中显示。
根据代码逻辑,理论上,在系统切换语言时,该对象会被销毁,并重新读取json信息
  • 现象一:但是实际上,并没有生效,通过log日志打印,发现哪怕是重新应用,也没有走重新解析json文件的逻辑,但是重启却会重新读取。且通过hash打印,发现重启后,对象还是同一个。
    • 猜测:内存泄露,因为只有内存泄露,才会导致对象的生命周期超过应用本身的生命周期
    • 发现问题:
      • 从内存泄露的角度出发,依次排查内部是否持有了长生命周期的对象
      • 最后发现,内部持有的对象基本都是内部对象,只有一个异常 packageManager
    • 结论:
      • PackageManager对象的生命周期,比应用还长,应用中,不要直接持有其引用
  • 现象二:解决了上述内存泄露后,发现应用重启后,确实会重新读取json文件了,但是系统切换语言时,还是没有及时重新读取json信息
    • 猜测:对象生命周期不对,因为按照逻辑,这个小工具读取的对象生命周期应该是跟随主线程的,而不是跟随应用进程的的(系统切换语言时,线程重建,进程不重建)
    • 发现问题:
      • 对于json信息的对象,是一个静态单例,而单例的生命周期,是随应用进程的。
    • 解决:

    启动耗时

    • 问题:由于需要去读取解析小工具的整个json文件,实测在不同平台上,会带来90~150ms的启动延迟,且对于没有引入该需求的渠道,也会引入这个启动延迟
    • 预期:只在有该需求的客户渠道,读取解析json文件,避免引入全平台的延迟
    • 分析:
      • 从解析json的代码处,添加日志打印出其调用栈
      • 根据上面的调用栈,往上追终,最终发现是在工具栏处,有读取json文件,进行多语言适配的逻辑,从而引入了耗时
    • 解决:
      • 根据是否开启了小工具的配置,决定是否进行读取json信息
      • 又因为在工具栏中,会先根据小工具的配置,先对按钮做可见性变动
      • 所以,只需要在读取json之前,先判断对应的小工具入口按钮的可见性,即可作为判断的条件

    🥺 UML描述

    PlantUML语法:

    🥳 成长

    • packageManager生命周期非常长,由系统控制,应用不应该持有其引用,避免内存泄露
    • 静态单例对象的生命周期,是对标应用进程的;而系统语言切换等引发config change的情况,是不会导致应用进程重建的。
     
    Note功能-工具栏左右交换功能Android-BuildVariants记录
    Loading...