揭秘时间
在过去的十天里,我都做了些什么?说好的要准备秋招,咋又不见了?实际上在过去的十天里,我也在为秋招努力(完善自己准备的小项目),很高兴今天将整个APP的初稿完成了,《卡片学习》是一款Android系统的工具APP,由于审核材料问题,目前并未上架,只是在蒲公英上传了内测版本(这里就不贴链接了)。源码开源在GitHub - CardStudy。如果有同学想要练手项目可以去拿,不过别忘了star一下 嘻嘻。拿之前看一下下面这些参数噢
项目结构以及涉及到的知识点解析
工具 平台: AndroidStudio、Windows10
语言: Java
架构设计模式 : MVVM
应用搭建 : 采用 单Activity + 多Fragment 的模式、这里的Fragment的管理是使用的官方推荐的Navigation方案。
将上面的参数列出来其实也是为了想要拿这个项目去练手的同学能够至少有以上知识储备。在这个前提下,才去拿项目源码看。
然后我再介绍一下,项目中有关模块的实现细节:
视图绑定、数据缓存及绑定
视图的绑定,笔者是使用的DataBinding、数据缓存及绑定采用的是Livedata(Kotlin推荐Flow)。但是笔者并没有在布局文件中进行绑定,而是在Java代码中通过数据驱动来更新的UI,笔者觉得这个看个人习惯。并且控制数据流为单一流向,这也是官方推荐的。
数据持久化
这里对于APP产生的数据都是在本地进行的存储并持久化,按照数据的分类(笔者为分为两类)将数据分别以数据库和文件的形式存储在了本地,那么这里用到的数据库是基于Sqlite进行封装的ROOM框架,文件则是使用的SharedPrefrences(xml文件,也算是文件存储吧 哈哈~)
异步
Java中异步使用的多线程,本项目中笔者是采用了AlibabaJava开发者手册中推荐的,不使用现成提供的线程池实现类,而是提供构造方法进行创建。(这里有很些坑,和数据库一起使用时出现的,之前使用Kotlin协程配合ROOM进行开发时,没有出现过,感叹Kotlin还是比较爽的)。避坑指南
体积优化
当然这里也是为了减少包体积,我们尽可能的对代码进行复用(理解为小小的优化),对于项目中的资源,可以采用动态下发的形式,(比如图片资源,可以放到图库,然后在用户使用时从远端下拉,但是这里笔者由于囊中羞涩哈忽略),所以笔者只能从代码量来尽可能优化了,兜底组件以及可能在多个地方出现的弹窗组件我们抽离出来。其中弹窗使用了建造者模式 哈哈 ~ 这个必须提出来。
基类
因为是单Activity + 多Fragment 模式,那么基类也是只对Fragment进行了封装,主要是对ViewModel的反射获取,公用的方法以及基础环境进行了配置。其实利用好基类,在后续的开发过程中可以避免出现冗余的代码以及不必要的错误。
开发过程中遇到的问题
滑动冲突问题
这个问题是经常出现的,所以我们在设计开发过程中应该尽量避免出现同向滑动的场景,当然如果是一定要这样做,我们也是可以解决的,但是你永远也想不到,一个坑的背后有多少大坑等着你,所以尽量不要冒这个险。现在我们聊聊笔者在项目中遇到的滑动冲突问题,笔者为了在同级的三个主Fragment上实现滑动切页,以及为其提供一个容器的需求,使用了ViewPager,但是笔者又在第一个Fragment上使用了一个DrawerLayout(抽屉),由于都是左右滑动的,那么出现了滑动冲突,其实这个笔者在写之前就想到了,当时觉得一个小小的滑动冲突,我反手事件分发机制分分钟给你解决。是的,冲突是没有了,在我触摸到drawer时,事件交给了drawer处理,当我觉得“一切正常!”了,但是这时候出现了一个大问题,当翻页后再回来通过逻辑调用open()打不开drawer了,并且不会回调任何方法,但是用手去抽是可以抽出来的,然后笔者进入了调试模式~一顿排查操作下来发现所有的参数都是正常的,但是就是不绘制drawer。通过在交流群以及网上查阅资料得出的结论是ViewPager本身存在的一个坑,并且在我更换方案后确实这个bug消失了。
RecyclerView 可能意想不到的坑
在使用Recyclerview作为列表展示数据是比较舒服的,但是在Fragment中也存在着一些意想不到的坑。我们通常在为adapter设置或者更新数据源后是调用notifyDatasetChanged()方法通知并刷新view。这里我们知道在adapter中,我们必须要帮证被观察对象List的不变性,才能使这个方法生效。但是我们可能大多数新手都不会知道的,在Fragment这里不仅需要保证List不变,还要保证视图对象不变。这里可能有些同学会觉得视图咋会改变,其实了解了Fragment的生命周期,以及什么情况下会调用后,我们就会明白,onCreateView方法实际在每次切换后都会重新调用。所以视图也就会重新创建。由于我们在onCreateView方法中绑定的视图也需要不变。那么我们可以采用一些方法使得其只创建一次,比如设置为全局变量,每次创建前判断是否为null,不为null则不创建或者在onActivityCreated方法中重复set和bind。显然使用方法一会更好一点。
最后建议不要为了少写几行代码随意使用 “ ?:” ,老老实实将所有条件补全。
为什么这样说呢? 这是因为笔者在实现APP中一个算法时,使用了该三目运算符,前期测试,结果是ok的,但是当我将数据打通,使用真实环境进行测试时就发生了非常奇怪的bug。而且很难复现(这种情况打点debug异常困难),最后发现是因为index越界,其实如果在保证index不出问题的情况下,使用该三目运算符也是没问题的,但是你无法保证一点问题不出,这时候多加一层保险,真的可以避免出现问题的带来的灾难。
ArrayList<**> arr //该参数是由方法传入,且一定不为null
int arrSize = arr.size();
if(arrSize == 0) return null;
... // 一些列操作之后,我会得到一个大于等于0 小于 arrSize()的数
int index //上面得到的那个数
index > 0 ? arr.get(index) : arr.get(0); // 奇怪的bug出现了 index :1 size :0 ;
//注意在上面我已经判断了如果size = 0 则提前退出。但是有可能是因为index越界了,但是按照程序逻辑不存在越界问题。于是我将该三目运算符改成了条件的判断。bug消失了!没错真是index越界了。