基于Kotlin 的记事本App 的开发与实现
2021-07-16李彦龙
李彦龙
(美的智慧生活(上海)科技有限公司,上海 201702)
智能手机已经成为人们工作生活的一个不可缺少的辅助设备,记录备忘功能的应用也成为了用户日常使用率很高的一款应用程序,观察国内手机厂商的系统,都会预装记录备忘类应用,比如华为手机“备忘录”应用,联想手机的“联想记事本”,小米手机的“便签”应用等。另外开放市场上的NearyMe 云笔记,有道云笔记,大姨妈App,宝宝树孕育APP 等记事本备忘类型的应用都很受用户的欢迎[1]。智能手机上的记事本相对于传统的记录方式可以做到记录形式的多样,更加的方便快捷。比如用户可以通过录音、视频、图片、文字等一种或者多种形式记录。除此之外记录的内容还可以通过智能识别的方式,把包含时间点信息的记录在日历应用中自动生成提醒,更进一步提高便捷性。本文介绍基于谷歌在2018 年Goole I/O 大会上发布的JetPack组件库设计开发联想日历的过程,为后续其它应用开发提供一套快捷高效的方案和思路。
1 系统开发相关技术
记事本App 基于安卓平台的应用,使用Kotlin 这一Google官方推荐的语言开发语言,并使用谷歌新推出的Jetpack 组件,实现高效快捷的开发性能稳定的应用。下面介绍使用到的相关技术。
1.1 Kotlin
Kotlin 是一种新型的静态类型编程语言,它有助于提高工作效率、开发者满意度和代码安全性,截止2021 年2 月11 日谷歌商店排名前1000 的应用中已经有60%的采用了Kotlin 语言开发[2]。2019 年Google I/O 更是把Kotlin 定位推荐语言,而且后续的支持组件包都是以Kotlin 为开发语言。可见Google 对Kotlin 语言的重视。Kotlin 语言有以下特点[2]:
兼容java,而且Android Studio 开发工具有一键转换功能,可以将java 语言转换成Kotlin 语言。
空指针安全。
支持Lamada。
支持扩展。
所以使用Kotlin 语言开发Android 应用可以做到:用更少的代码更快速的开发出更少空指针异常的应用。
1.2 JetPack
Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码[3]。它是Google 公司在2018 年Google I/O 大会上推出的一套官方认证的开发系统,包含架构、UI、基础库、行为四个方面到目前一共有大约131 个库[4],使用这些库可以快速的搭建开发一个稳定的应用,比如接下来介绍本文用到的几个库文件。
1.2.1 ViewModel
ViewModel 是JetPack 架构组件中的一个类,它注重生命周期的方式存储和管理界面相关的数据。相对于Activity 的7 个生命周期来说ViewModel 只有两个生命周期,它把自己的生命周期和初始化时候传入的context 绑定,只有在绑定的context生命周期结束时才会销毁。所以它只有两个生命周期的回调函数:创建和初始化传入context 生命周期结束时候的onCleared回调方法。
图1 Activity 旋转生命回调函数和绑定的ViewModel 生命周期回调函数的对比[5]
基于上述ViewModel 的特点可见,使用ViewModel 来作为容器管理UI 层依赖Model 层的数据是个便捷的做法,只需要初始化和使用,而不用管理界面的旋转息屏等特殊场景数据的保存与恢复逻辑了,这样可以把时间投入到业务逻辑的开发中。
1.2.2 LiveData
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如Activity、Fragment 或Service)的生命周期。这种感知能力可确保LiveData 仅更新处于活跃生命周期状态的应用组件观察者[6]。使用时注册观察者Observer,如果LiveData 的实例被通过setValue(T),postValue(T)接口赋值更新数据时候,会回调Observer 的onChanged()接口,一次触发更新UI。
1.2.3 Room
Room 在SQLite 上提供了一个抽象层,以便在充分利用SQLite 的强大功能的同时,能够流畅地访问数据库[7]。Room 是在SQLite 数据库的基础上又做了一层封装,开发者可以使用注解的方式方便的创建数据库和定义访问数据库的接口,且支持多线程。
1.3 MVVM 架构
上文中我们提到了ViewModel,MVVM (Model-View-ViewModel)的架构就是在之前MVP(Model-View-Presenter)基础之上把Presenter 替换成ViewModel 形成的新的代码架构。ViewModel 作为显示View 和数据存储的Model 之间的桥梁,一方面可以作为数据的容器,另一方面可以作为业务逻辑的容器,从数据源获取到的数据在显示之前做一些处理。
图2 MVVM 架构图
2 系统总体功能框架
记事本应用的主要功能有:
2.1 主界面按照添加时间顺序由上而下以列表形式显示,每一个添加项都已卡片的形式展示。每一项有创建时间、标题、文本内容、图片和语音图标。其中语音图标标注时长,并有点击播放按钮。
2.2 主界面下部偏右加号按钮点击进入添加页面、添加界面有创建时间、标题输入框、文本输入框,下部的按钮栏中有语音按钮、图片按钮、拍照按钮。
2.3 主界面列表的每个卡片代表一个记事项,点击进入编辑界面,此时编辑界面同添加界面,只是多了删除和分享功能。
2.4 在主界面上长按一个记事项触发进入ActionMode 也就是多选编辑界面,多以点击其它卡片选中,再次点击取消,ActionBar 也就是顶部栏提示当前已选择项目的数目和删除按钮,这个功能便于用户批量删除。
由于记事本的功能决定的,它把用户创建界面生成记事项存入本地数据库,然后在主界面再进行读取展示,点击编辑再次对数据库中保存的数据进行更新或者删除。而主界面的多选模式则进行对数据库中的记录进行单个或者批量删除。由此我们可以想到利用MVVM 架构模式,如果把主界面展示的记事项作为一个数据item,那么我们可以把item 的列表作为一个LiveData 放入ViewModel 中,主界面的Activity 只需要观察item的列表是否更新,触发更新展示即可。而ViewModel 中可以在对记事项增删改查操作后,触发更新ViewModel 中item 列表项即可。至于LiveData 和Model 之间可以使用前面介绍的Room 组件替换SQLite 组件,实现数据库的创建和接口Dao 的创建。方便业务层ViewModel 对Model 层增删改查的工作。下图是经典的基于Jetpack 架构组件的架构图,本文设计开发的记事本应用由于功能决定的可以参考此经典架构。
图3 使用JetPack 架构组件的典型架构图[8]
3 系统数据库设计与构建
客户端本地数据库的构建也比较简单,一个table 名字为note_table。其中id 为自动生成累加的索引,并设置为primary key。type 取值为枚举类型的索引,取值范围0-1,0 代表普通记事项,只有文字内容;1 代表有语音,图片等内容的记事项;textx用于保存文字内容的字段。image1-image4 存储用户选择的图片路径或者拍照后生成的图片文件的路径。date 字段用于保存日期对应毫秒。Voicepath 是保存语音文件路径的字段。Ismedia,用于表示此项记录是否有多媒体内容,其实不用此字段通过判断多个图片字段和voicepath 字段也是可以判别的,用ismedia 字段只是多存储一个便捷识别多媒体记录项的标志位。
图4 数据库表结构
4 系统主要功能开发与实现
4.1 记事本App 客户端的开发与实现
记事本App 的应用的实现主要有三个部分,UI 部分、viewmodel 和model 部分,下面分别阐述这三部分的实现以及所用到的技术。
4.1.1 UI/UI controller
UI/UI controller 部分指显示界面部分的代码模块。采用单Activity 多Fragment 的架构设计,这样设计的好处是便于各个fragment 之间数据的传递与共享。MainActivity 作为其它两个Framgnet 的容器,EditNoteFragment 显示新建和编辑记事项的界面,NotListFragment 显示所有记事项的列表。
图5 UI 部分代码文件结构图
4.1.2 viewmodel
对 应 UI 部 分 两 个 fragment,viewmodel 部 分 有NoteItemViewModel 和NoteListViewModel 两个文件,其中NoteItemViewModel 中包含有LiveDate<NoteItem>的实例;同时两个ViewModel 实例也持有Model 层数据库的实例。
图6 ViewModel 部分代码文件结构图
4.1.3 model
model 部分是指本地存储数据库,这部分包含数据库的创建,管理以及Dao 接口封装对数据库增删改查的实现。Dao 接口根据业务需要封装了四个功能:(1)查询所有数据库中记事项的数据。(2)查询单个记事项的数据,查询参数是id,这也是数据表中作为primary key 存储的字段,保证唯一性。(3)更新接口,参数是NoteItem 实例。(4)删除单个NoteItem 的接口,参数是id。
这部分的实现由于使用了JetPack 中的Room 组件,所以我们只需要按照Room 组件的样式创建数据库存储数据对应的Bean 文件并使用固定的注解标签例如:@Entity (tableName =“note_table”)就可以创建对应的数据表。同样的对Dao 文件也可以通过在Interface 接口定义的文件中使用@Dao 注解和@Insert 和@Query @Update 等标签就可以实现。举例:查询所有数据库中保存的记事项,可以这样定义@Query (“SELECT *FROM note_table ORDER BY date DESC”);其中只需要注意note_table 为要查询的表格的名字即可。
4.2 智能生成提醒功能
这里主要是通过用的正则表达式来匹配用户创建记事项中的日期内容,如果匹配成功则代表此记事项可以生成对应的提醒,并提示用户是否插入到日历的提醒数据库中,达到定时提醒的功能。比如:8 月15 日体检这样的内容。则会提示用户是否生成一个8 月15 日的提醒,提醒的内容是体检。如果用户选择同意则进入日历创建提醒的界面让用户继续操作。当然这里所说的智能也只是穷举日常中用户输入的日期格式,并不能做到全覆盖,这里的正则表达式可以作为一个服务器接口后台持续维护,动态分发给用户使用。这样可以做到用户反馈匹配错误或者不能智能识别日期的问题可以快速的修正和发布,而用户端则不需要更新应用即可以看到修复后的效果,提升用户体验。
4.3 分享功能
用户有从记事本分享到朋友圈或者微信好友的需求,所以我们接入了微信的sdk 并结合系统的分享功能,通过在分享前重新排版组合生成图片然后再调用分享接口的方式实现了一键分享的功能。
5 结论
记事本App 本身是一个功能相对简单的应用,它的业务功能决定了界面修改内容存储到数据库,显示时候又从数据库中读取这样的特性,特别适合用JetPack 中的ViewModel 结合LiveDate 的架构组件。所以本文借助记事本App 的设计实现介绍MVVM 的架构和ViewModel,LiveDate 以及Room 架构组件的使用。此经验可用于开发设计其它App,提升开发效率和架构的合理性,结合Kotlin 语言的使用也能进一步提升开发App 的稳定性。