现代化 Android 开发:基础架构

news2024/11/27 5:34:20

作者:古哥E下

Android 开发经过 10 多年的发展,技术在不断更迭,软件复杂度也在不断提升。到目前为止,虽然核心需求越来越少,但是对开发速度的要求越来越高。高可用、流畅的 UI、完善的监控体系等都是现在的必备要求了。国内卷的方向又还包括了跨平台、动态化、模块化。

目前的整体感觉就是,移动开发基本是奄奄一息了。不过也不用过于悲观:一是依旧有很多存量的 App 堪称屎山,是需要有维护人员的,就跟现在很多人去卷 framework 层一样,千万行代码中找 bug。 二是 AI 日益成熟,那么应用层的创新也会出现,在没有更简洁的设备出现前,手机还是主要载体,总归是需要移动开发去接入的,如果硬件层越来越好,模型直接跑在手机上也不是不可能,所以对跨平台技术也会是新一层的考验,有可能直接去跨平台化了。毕竟去中台化也成了历史的选择。

因而,在这个存量市场,虽然竞争压力很大,但是如果技术过硬,还是能寻求一席之地的。因而我决定用几篇文章来介绍下,当前我认为的现代化 Android 开发是怎样的。其目录为:

  • 现代化 Android 开发:基础架构(本文)
  • 现代化 Android 开发:数据类
  • 现代化 Android 开发:逻辑层
  • 现代化 Android 开发:组件化与模块化的抉择
  • 现代化 Android 开发:多 Activity 多 Page 的 UI 架构
  • 现代化 Android 开发:Jetpack Compose 最佳实践
  • 现代化 Android 开发:性能监控

Scope

提到 Android 基础架构,大家可能首先想到的是 MVCMVPMVVMMVI 等分层架构。但针对现代化的 Android 开发,我们首要有的是 scope 的概念。其可以分两个方面:

  • 结构化并发之 CoroutineScope:目前协程基本已经是最推荐的并发工具了,CoroutineScope 的就是对并发任务的管理,例如 viewModelScope 启动的任务的生命周期就小于 viewModel 的存活周期。
  • 依赖注入之 KoinScope:虽然官方推荐的是 hilt,但其实它并没有 koin 好用与简洁,所以我还是推荐 koinKoinScope 是对实例对象的管理,如果 scope 结束, 那么 scope 管理的所有实例都被销毁。

一般应用总会有登录,所以大体的 scope 管理流程图是这样的:

  • 我们启动 app, 创建 AppScope,对于 koin 而言就是用于存放单例,对于协程来说就是全局任务
  • 当我们登录后,创建 AuthSessionScope, 对于 koin 而言,就是存放用户相关的单例,对于协程而言就是用户执行相关的任务。当退出登录时,销毁当前的 AuthSessionScope,那么其对应的对象实例、任务全部都会被销毁。用户再次登录,就再次重新创建 AuthSessionScope。目前很多 App 对于用户域内的实例,基本上还是用单例来实现,退出登录时,没得办法,就只能杀死整个进程再重启, 所以会有黑屏现象,实现不算优雅。而用 scope 管理后,就是一件很自然而实现的事情了。所以尽量用依赖注入,而不要用单例模式
  • 当我们进入界面后,一般都是从逻辑层获取数据进行渲染,所以依赖注入没多大用了。而协程的 lifecycleScopeviewModelScope 就比较有用,管理界面相关的异步任务。

所以我们在做架构、做某些业务时,首要考虑 scope 的问题。我们可以把 CoroutineScope 也作为实例存放到 KoinScope 里,也可以把 KoinScope 作为 Context 存放到 CorutineScope 里。

岐黄小筑是将 CoroutineScope 放到 koin 里去以便依赖查找

val sessionCoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob() + coroutineLogExceptionHandler(TAG))
val sessionKoinScope = GlobalContext.get().createScope(...)
sessionKoinScope.declare(sessionCoroutineScope)

其实我们也完全可以用 CoroutineScopeContext 来做实例管理,而移除 koin 的使用。但是 Context 的使用并没有那么便捷,或许以后它可以进化为完全取代 koin

架构分层

随着软件复杂度的提升,MVCMVPMVVMMVI 等先后被提出,但我觉得目前所有的开发,都大体遵循某一模式而又不完全遵循,很容易因为业务的节奏,很容易打破,变成怎么方便怎么来。所以使用简单的分层 + 足够优秀的组件化,才是保证开发模式不被打破的最佳实践。下图是岐黄小筑的整体架构图:

整体架构不算复杂,其实重点是在于组件库,emo 已经有 20 个子库了,然后岐黄小筑有一些对于通用逻辑的抽象与封装,使得逻辑层虽然都集中在 logic 层,但整体都是写模板式的代码,可以面向 copy-paste 编程。

BookLogic 为例:


// 通过依赖注入传参, 拿到 db 层、网络层、以及用户态信息的应用
class BookLogic(
    val authSession: AuthSession,
    val kv: EmoKV,
    val db: AccountDataBase,
    private val bookApi: BookApi
) {
    // 并发请求复用管理
    private val concurrencyShare = ConcurrencyShare(successResultKeepTime = 10 * 1000L)

    // 加载书籍信息,使用封装好的通用请求组件
    fun logicBookInfo(bookId: Int, mode: Int = 0) = logic(
        scope = authSession.coroutineScope, // 使用用户 session 协程 scope,因为有请求复用,所以退出界面,再进入,会复用之前的网络请求
        mode = mode,
        dbAction = { // 从 db 读取本地数据
            db.bookDao().bookInfo(bookId)
        },
        syncAction = { // 从网络同步数据
            concurrencyShare.joinPreviousOrRun("syncBookInfo-$bookId") {
                bookApi.bookInfo(bookId).syncThen { _, data ->
                    db.runInTransaction {
                        db.userDao().insert(data.author)
                        db.bookDao().insert(data.info)
                    }
                    SyncRet.Full
                }
            }
        }
    )
    // 类似的模板代码
    suspend fun logicBookClassicContent(bookId: Int, mode: Int = 0) = logic(...)
    suspend fun logicBookExpoundContent(bookId: Int, mode: Int = 0) = logic(...)
    ...
}

//将其注册到 `module` 中去,目前好像也可以通过注解的方式来做,不过我还没采用那种方式:
scopedOf(::BookLogic)

ViewModel 层浮层从 Logic 层读取数据,并可以进行特殊化处理:

class BookInfoViewModel(navBackStackEntry: NavBackStackEntry) : ViewModel() {
    val bookId = navBackStackEntry.arguments?.getInt(SchemeConst.ARG_BOOK_ID) ?: throw RuntimeException("book_id is required!.")

    val bookInfoFlow = MutableStateFlow(logicResultLoading<BookInfoPojo>())

    init {
        viewModelScope.launch {
            runInBookLogic {
                logicBookInfo(bookId, mode).collectLatest {
                    bookInfoFlow.emit(it)
                }
            }
        }
    }
}

Compose 界面再使用 ViewModel

@ComposeScheme(
    action = SchemeConst.ACTION_BOOK_INFO,
    alternativeHosts = [BookActivity::class]
)
@SchemeIntArg(name = SchemeConst.ARG_BOOK_ID)
@Composable
fun BookInfoPage(navBackStackEntry: NavBackStackEntry) {
    LogicPage(navBackStackEntry = navBackStackEntry) {
        val infoVm = schemeActivityViewModel<BookInfoViewModel>(navBackStackEntry)
        val detailVm = schemeViewModel<BookDetailViewModel>(navBackStackEntry)
        val bookInfo by infoVm.bookInfoFlow.collectAsStateWithLifecycle()
        //...
    }
}

这样整个数据流从网络加载、到存储到数据库、到传递给 UI 进行渲染的整个流程就结束了。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/627185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始 Spring Boot 36:注入集合

从零开始 Spring Boot 36&#xff1a;注入集合 图源&#xff1a;简书 (jianshu.com) 在前面一篇文章从零开始 Spring Boot 27&#xff1a;IoC中&#xff0c;讨论过依赖注入集合&#xff08;Java 容器&#xff09;的内容&#xff0c;这里更深入地讨论注入集合的相关内容。 我们…

ThinkPHP5学生学术管理系统

有需要请私信或看评论链接哦 可远程调试 ThinkPHP5学生学术管理系统 一 介绍 此学生学术管理系统基于ThinkPHP5框架开发&#xff0c;数据库mysql&#xff0c;前端Amazeui。系统角色分为学生用户和管理员。学生可以对个人信息&#xff0c;发表论文&#xff0c;专利授权&#x…

chatgpt赋能python:Python快速建站的SEO(搜索引擎优化)指南

Python快速建站的SEO&#xff08;搜索引擎优化&#xff09;指南 在当今数字时代&#xff0c;任何企业都需要一个强大和有效的网站。随着多个开源和商业网站平台的出现&#xff0c;建立一个网站变得更加容易。其中一个让人充满激情的开源工具是Python&#xff0c;它是一种流行的…

06_ MySQL优化实战

1. 计算并指定索引长度 阿里开发手册&#xff1a; 强制】在 varchar 字段上建立索引时&#xff0c;必须指定索引长度&#xff0c;没必要对全字段建立索引&#xff0c;根据实际文本区分度决定索引长度。 说明&#xff1a;索引的长度与区分度是一对矛盾体&#xff0c;一般对字符…

2.4 网络设计与redis、memcached、nginx组件

目录 一、网络模块需要处理哪些事情二、reactor网络设计模型三、网络模块与业务的关系四、redis、memcached、nginx1、redis2、memcached3、ngnix4、总结 一、网络模块需要处理哪些事情 网络编程主要关注客户端与服务端交互的四个问题&#xff1a; 1、连接建立 2、消息到达 3、…

学历不代表能力,但学历不够就意味着没资格!

今年的高考报名人数再创历史新高。 据悉&#xff0c;2023年全国高考报名人数1291万人&#xff0c;比去年增加98万人。 那么&#xff0c;今年的高校毕业生人数呢&#xff1f; 据人社部统计,今年我国高校毕业生人数达到1158万&#xff0c;继2022年破千万后再创历史新高。 大家…

Vue路由到新的页面,页面的名称需要改变

如下图&#xff1a;在页面中点击“属性列表”和“参数列表”的时候&#xff0c;要路由到新的页面&#xff0c;之后页面的title不用路由中的名称&#xff0c;而是用新的名称。也就是要显示对应的按钮名称&#xff0c;这个路由地址的名称是动态的。 在旧的页面上加上&#xff1a;…

汇报演示领导都说好,只因用了Smartbi幻灯片这个功能

在日常工作中&#xff0c;定期以PPT的方式汇报工作是非常常见的需求。假设你是一位销售经理&#xff0c;每个月都要参加公司的销售会议。在会议上&#xff0c;你需要向团队和高层展示销售数据、市场趋势和业绩报告等信息。过去&#xff0c;你通常是PPT来制作演示文稿&#xff0…

链表及相关面试题

链表 单链表 特点&#xff1a; 逻辑上顺序存储&#xff0c;物理上无序存储头指针根据情况而定&#xff0c;不保存数据&#xff0c;很多操作需要头指针&#xff0c;比如原地反转链表。每个节点包含 data, Node next保存下个Node public class LinkList {public Node headern…

系统初始化加载动画逻辑以及隐藏

需求&#xff1a;进入系统默认有如下的加载界面&#xff0c;但是由于网页内嵌到了其他网页中&#xff0c;这种环境下进入时再加载就不合适&#xff0c;需要隐藏掉。 因此本文的内容逻辑为 文章目录 研究加载逻辑解决需求&#xff1a;在被内嵌时隐藏掉loading 研究加载逻辑 1.…

【SpinalHDL快速入门】3、Scala 快速入门

SpinalHDL本质上来讲是Scala语言的一个库&#xff0c;所以需要先学习Scala&#xff0c;才能在此基础上学习SpinalHDL。 文章目录 Scala 基础Scala 数据类型&#xff08;5种&#xff1a;Boolean、Int、Float、Double、String&#xff09;Scala VariablesScala FunctionsReturnRe…

Python自动化测试框架:unittest介绍

Unittest是Python中最常用的测试框架之一&#xff0c;它提供了丰富和强大的测试工具和方法&#xff0c;可以帮助开发者更好地保证代码质量和稳定性&#xff0c;本文就来介绍下Unittest单元测试框架。 1. 介绍 unittest是Python的单元测试框架&#xff0c;它提供了一套丰富的测…

2023软件测试卷出天际!!!性能测试为啥一枝独秀?

近十年是中国互联网发展最快的10年&#xff0c;互联网用户从4亿增长至10亿。面对用户量的暴增&#xff0c;用户体验就成为互联网产品最大的考验。而 影响用户体验的最重要因素就是性能。 流量为王的时代&#xff0c;性能测试是所有产品上线前必须通过的重要环节。 企业招聘性…

12米与30米TanDEM-X数字高程模型DEM数据的下载申请方法

本文介绍全球12米与30米高空间分辨率的数字高程模型&#xff08;DEM&#xff09;数据——TanDEM-X数据的下载申请方法。 Tandem-X卫星项目于2010年6月启动&#xff0c;并于2010年6月21日和2010年12月21日分别发射两颗卫星&#xff0c;即TerraSAR-X和TanDEM-X。Tandem-X卫星之间…

裸辞3个月,面试了25家公司,这难度真不一般····

上半年裁员&#xff0c;下半年裸辞&#xff0c;有不少人高呼裸辞后躺平真的好快乐&#xff01;但也有很多人&#xff0c;裸辞后的生活五味杂陈。 面试25次终于找到心仪工作 因为工作压力大、领导PUA等各种原因&#xff0c;今年2月下旬我从一家互联网小厂裸辞&#xff0c;没想到…

【Android】WMS(五)输入事件原理

输入事件原理 安卓输入事件整体流程 Android 系统是由事件驱动的&#xff0c;而 input 是最常见的事件之一&#xff0c;用户的点击、滑动、长按等操作&#xff0c;都属于 input 事件驱动&#xff0c;其中的核心就是 InputReader 和 InputDispatcher。 InputReader 和 InputD…

申请国家标准项目管理专业人员能力评级(CSPM)报名条件有哪些?

2021年10月&#xff0c;中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系&#xff0c;开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会&#xff08;CAS&#xff09;组织开展的项…

3.JavaScript常用对象数组对象

3.1、数组对象 3.1.1、概述 目录 3.1、数组对象 3.1.1、概述 3.1.2、创建数组 3.1.2.1、使用对象创建 3.1.2.2、使用字面量创建 3.1.3、遍历数组 3.1.4、数组属性 3.1.5、数组方法 3.2、函数对象 3.2.1、call()和apply() 3.2.2、this指向 3.2.3、arguments参数 3…

JavaSE-06 [面向对象+封装]

JavaSE-06 [面向对象封装] 第一章 面向对象思想 1.1 面向过程和面向对象 面向过程&#xff1a; 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候一个一个依次调用就可以了面向对象&#xff1a; 面向对象是把构成…

PYTHON元素定位方式总结

一&#xff0c;常用的8种定位方式 id定位 driver.find_element_by_id("id 值")   driver.find_element(by "id", value "ID值" ) name定位 单个元素&#xff1a;     driver.find_element_by_name("name值")     drive…