V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
winlee28
V2EX  ›  Android

Android Jetpack 架构开发组件化应用实战

  •  
  •   winlee28 · 2020-06-04 21:09:25 +08:00 · 10108 次点击
    这是一个创建于 1659 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目介绍

    本项目采用组件化+MVVM 架构进行开发,对功能组件和业务组件进行拆分,通过 ARouter 进行组件之间的通信。本文主要是对整个应用的技术架构作一个简单的介绍。

    项目地址: https://github.com/winlee28/Jetpack-WanAndroid 欢迎 star

    技术要点

    Kotlin+Jetpack+Coroutines+Retrofit+koin

    项目整体架构图:

    https://i.imgur.com/iT1p6Xd.jpg

    宿主 App 没有任何的业务代码,整个业务被拆分为各个 ft_lib 模块。对一些功能组件进行封装抽取为 lib,提供给上层依赖。ft_lib 之间没有任务依赖关系,通过 Arouter 进行通信。

    首页功能

    首页分为 5 个 Tab,主要为首页、项目、导航、体系和我的。

    整个页面框架使用 BottomNavigationView + Navigation 来搭建。通过 menu 来配置底部菜单。 通过 NavHostFragment 来配置各个 fragment 。系统提供的 FragmentNavigator 的 navigate 方法中是通过 replace 来加载 fragment 。 这就导致在切换 Tab 的时候 fargment 会重复的调用 onCreateView 方法。这肯定不是我们需要的。那么我们需要自定义自己的 FragmentNavigator 来替换系统 的,通过 show 和 hide 的方式来加载 fragment 。具体方式会在后续系列文章中进行讲解。

    因为 App 使用了沉浸式状态栏,那么在使用的过程中也会出现只有第一个 fragment 的沉浸式是起作用的,其他的基本不生效。这个问题要从系统源码着手解决。 根本原因是这段代码:

    private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
            if (!insets.isConsumed()) {
                final int count = getChildCount();
                for (int i = 0; i < count; i++) {
                    insets = getChildAt(i).dispatchApplyWindowInsets(insets);
                    if (insets.isConsumed()) {
                        break;
                    }
                }
            }
            return insets;
        }
    

    直接 break 了。所以我们也需要自定义一个 view 来重写 dispatchApplyWindowInsets 方法。具体也会在后续文章中进行讲解。

    下面就简单介绍下各个 Tab 的技术要点。

    首页

    首页主要分为顶部 Banner 和底部的文章列表。列表使用 Paging 加载。 并且封装了 AbsListFragment 和 AbsListViewModel 来快速搭建列表页面。 AbsListFragment 主要是封装了页面布局相关的元素。 AbsListViewModel 主要是封装了 paging 的一些常用配置等信息。 通过上述两个封装我们在开发列表页面的时候只需要关系 DataSource 和 adapter 即可。

    项目

    项目 Tab 主要使用的是 TabLayout 和 ViewPager2 来配合实现联动。同样的 ViewPager2 的页面也是直接继承了 AbsListFragment 来实现列表页面。

    导航

    导航页面主要分为顶部的搜索和底部的分类。点击搜索后打开新的页面。通过 fragment 来承载热门搜索和搜索返回结果。 底部的分类主要是通过 RecyclerView+ViewPager2 来实现的。通过设置 ViewPager2 的 orientation 为 ORIENTATION_VERTICAL 来实现列表滑动。

    体系

    通过自定义 FlowLayout 来实现 Tag 标签管理。并进一步封装 TagFlowLayout 并对外提供 TagAdapter 来加载数据。

    所有的下拉刷新换个下拉加载都是通过 SmartRefreshLayout 来实现的。通过阅读 Paging 源码我们了解到当请求数据返回空数据的时候,那么 Paging 就 不会继续帮我们来做分页加载了,默认会认为数据已经加载结束了。这个逻辑在实际开发中是有些问题的。所以就需要我们来返回空数据的时候手动来加载 分页逻辑。这里是通过自定义 DataSource 来完成。其实 Paging 框架只提供了加载数据的方式,在增加或者删除的逻辑目前都是不不支持的。这些都可以通过自定义 DataSource 来完成。具体也会在后续文章中进行讲解。

    网络请求

    主要还是通过 Retrofit+协程来完成。整个应用的数据流向架构为:

    https://i.imgur.com/NUojKeC.jpg

    目前还未加缓存功能,后续会通过 room 来实现缓存功能。

    组件通信

    组件通信主要是通过 Arouter 来实现的。通过提供的 IProvider 来定义 Service 来完成。Service 的实现定义在各自的 module 中。 并在 base 模块中定义 ServiceImpl 的包装类共调用方调用,这样对方就无需关心业务逻辑,直接使用即可。

    依赖注入

    因为 App 是采用 Kotlin 来开发的,所以 没有选择 dagger2 而是选择了 Koin,适用于 Kotlin 开发人员的实用轻量级依赖注入框架。 用纯 Kotlin 编写,无代理,无代码生成,无反射。

    学习地址 : https://start.insert-koin.io/#/quickstart/kotlin

    以上就是整个 App 功能的简单介绍,后续会单独讲解一些技术要点。

    扫描下方二维码关注公众号,获取更多技术干货。

    https://imgur.com/BfckUpC

    13 条回复    2020-06-12 09:20:51 +08:00
    aabbcc112233
        1
    aabbcc112233  
       2020-06-04 21:35:02 +08:00
    粗略看了几个类,和我看过的某个 wanandroid 的项目代码很相似,并且有些地方封装的不好。希望能继续完善。
    winterbells
        2
    winterbells  
       2020-06-04 22:03:36 +08:00 via Android
    当初看到 koin 就直接放弃了,感觉太花哨

    不过新公司也没有 MVVM 这个东西了,自己研究研究也可以扩展一下
    winlee28
        3
    winlee28  
    OP
       2020-06-04 23:38:27 +08:00
    @winterbells koin 还花哨? 简单 易用
    winlee28
        4
    winlee28  
    OP
       2020-06-04 23:39:36 +08:00
    @aabbcc112233 可以把不好的说不出来 一起交流
    mxalbert1996
        5
    mxalbert1996  
       2020-06-04 23:55:56 +08:00 via Android
    FourAndHalf
        6
    FourAndHalf  
       2020-06-05 06:51:56 +08:00
    写的不错 楼主加油
    winlee28
        7
    winlee28  
    OP
       2020-06-05 08:31:01 +08:00 via iPhone
    @mxalbert1996 嗯 感谢 之前还真不知道有这个类 不过解决方案都是复写 onApplyWindowInsets 方法
    winlee28
        8
    winlee28  
    OP
       2020-06-05 08:31:45 +08:00 via iPhone
    @FourAndHalf 一起学习
    maninfog
        9
    maninfog  
       2020-06-05 09:56:08 +08:00
    所以 Navigation 不销毁 Fragment 的方案是什么呢?我目前是通过在 fragment 保存 View 实例来实现,每次 inflate 的时候,如果 View 不为空,直接返回。不知道这是不是和官方的思路相悖了?毕竟官方的意思是通过保存一致的数据来重新渲染 UI,让用户感觉不到 UI 的刷新,不过这颠覆和前的 show 、hide 开发模式,很难受。
    winlee28
        10
    winlee28  
    OP
       2020-06-05 11:00:09 +08:00
    复写 NavigatorFragment 的 navigate 方法 将 replace 改写为 show 和 hide 方式
    今天晚些的时候 会发篇文章在公众号上
    hantsy
        11
    hantsy  
       2020-06-06 10:26:14 +08:00
    我一直有个很简单的问题,Android 怎么写测试, 没有测试如何实现自动化发布?官方教程我也看了 Kotlin 视频那个,只有简单针对 Database,Repository 的测试(这东西跟 Android 一点关系都没有),没有整个 app 跑起来的 UI 功能测试。
    longyuan5
        12
    longyuan5  
       2020-06-12 09:20:08 +08:00 via Android
    为啥都喜欢用 kotlin,没感觉好用在哪里
    longyuan5
        13
    longyuan5  
       2020-06-12 09:20:51 +08:00 via Android
    还有封装的很一般,这样的项目很难🔥,加油
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5168 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 03:44 · PVG 11:44 · LAX 19:44 · JFK 22:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.