现代化 Android 开发:组件化与模块化的抉择

简介: 本文为现代化 Android 开发系列文章第四篇。

项目初始的时候,一般都是使用一个分层架构,接入各种框架,然后就写业务代码。但如果项目慢慢变大,那就会出现很多项目管理的问题,诸如:

1.代码复用与抽象问题

2.编译速度问题

3.版本迭代速度问题


所以组件化、模块化、动态化、插件化、跨平台等各种技术就会被提及被重视。

组件化


我们已经度过了洪荒时期的 Android 开发,因为官方已经给出了非常优秀的 Jetpack 组件库,所以我们目前更多的是制作业务型的组件与抽象,去解决某一类场景或者大家都可能用到而写起来又繁琐又容易出错的场景。


写组件也不是一件容易的事情。


首先,开发组件得意识到某些场景是可以抽象的,如果抱着代码能跑起来就行了的态度去编程,那基本不可能想到去抽象的,也是最容易陷入代码陷阱无法自拔的。即使是最简单的抽取,很多时候也是宁愿不厌其烦的复制粘贴,也不愿意去抽取一个公共方法。然后等别人开发出了组件后,除了感叹真香,也就没什么其它感悟了。


其次,开发组件得设计出好用而简洁的 API,很多童鞋,除了数据实体类和各种 Manager 类,基本不会再定义新的数据结构了,就更别提设计接口供其它人使用了。


然后,开发组件得想好组件的配置,提升组件的灵活性,产品最喜欢复用以前的逻辑而再加上一点点的不同,如果不能够配置,那组件的局限性就大打折扣了。


最后,开发组件还得做好边缘情况的处理、异常的处理。组件会被各种人以各种姿势调用,所以各种想不到的场景都有可能出现,这也考验开发写代码对细节的把控。


所以我一直重视的是写组件的能力,是需要不断的训练的,也是需要不断的阅读优秀的组件库去掌握的。面试必问 Okhttp 的拦截器,可以你有真正在自己的组件里使用过类似的东西吗?


最举个例子? 假设现在我们要用 AudioRecord 写一个录制功能。最开始那可能直接在 Activity 里开撸了:

private var isRecording = false;
private suspend fun startRecording() {
    audioRecord = AudioRecord(...)
    val data = ByteArray(bufferSize)
    audioRecord.startRecording()
    isRecording = true
    File(applicationContext.filesDir, "recording.pcm").outputStream().use {outputStream ->
        while (isRecording) {
            val bytesRead = audioRecord.read(data, 0, bufferSize)
            if (bytesRead > 0) {
                outputStream.write(data, 0, bytesRead)
            }
        }
    }
    audioRecord.stop()
    audioRecord.release()
}

这样写下来,流程一通,然后就觉得大功告成了。


然后产品逻辑就变更了:要把音频数据实时上传到服务器。


改:把写文件的部分换成网络传输

过一会儿,老板又找来了,在没网络的时候录制就失败了,这不行,要在无网络的时候录制到本地。


改:判断下网络环境,加 ifelse

过一会儿,老板又找来了,我开始有网络,但是进入电梯后录制就失败了,所以一定要保存一份到文件中,如果有网络,那也往网络写一份。


改:从 ifelse 逻辑换成双写逻辑。

...


只针对上面的场景,整体流程是相当稳定的,只是写入的目标地不一样,那为何不抽象一个 AudioSink 的接口来表示写入的存储目标?

interface AudioSink {
    suspend fun write(buf: ByteArray, offset: Int, len: Int)
    fun close()
}

那整个流程就稳定为:

private suspend fun startRecording(sink: AudioSink) {
    audioRecord = AudioRecord(...)
    val data = ByteArray(bufferSize)
    audioRecord.startRecording()
    isRecording = true
    while (isRecording) {
        val bytesRead = audioRecord.read(data, 0, bufferSize)
        if (bytesRead > 0) {
            sink.write(data, 0, bytesRead)
        }
    }
    audioRecord.stop()
    audioRecord.release()
    sink.close()
}

如果要写文件,写个实现:

class NetworkAudioSink(api): AudioSink {
    ...
}

如果要写网络,写个实现:

class NetworkAudioSink(api): AudioSink {
    ...
}

如果要判断本地与网络,就写个代理:

class DelegateAudioSink(
    val origin: AudioSink
): AudioSink {
    ...
}

如果要同时写本地与网络,就是双写或多写:

class MultiAudioSink(
    val list: List<AudioSink>
): AudioSink {
    ...
}

这些类一写,那具体逻辑就是入口传参的变更了。当然实际情况应该考虑各种异常情况。如果你足够有经验,那在一开始就能把这些场景考虑到,并且把其用处和局限性同步给产品,帮助产品一开始做出最优选择。这才是所谓的经验。


如果回头来看上面的例子,发现都是很熟悉的东西,说是设计模式也行。如果将整个抽象做好,如果从音频切换到视频,那是否这一块可以完美复用?那是不是就可以更早的完成需求?——然后就可以更早的被裁掉~


组件化,就是对业务场景、功能场景的抽象。这是最需要大家去投入时间去做、去训练的。

模块化


一些复杂的组件,本身就可以构成一个个的模块,然后单独出来维护。但在谈论模块的时候,更多的是谈论业务模块。就是把不同业务放在不同的模块下。这对于巨型 App, 其意义当然是有的,毕竟可能是一个团队维护某一个模块。而对于小 App, 那意义就不是特别大了。


因为模块化会带来非常棘手的问题:模块之间的通信问题。ArouterTheRouter 都很大程度上是为了解决模块间的通信问题。而简单的模块化一般是考虑基础模块的抽取,但基础模块的定义总是模糊的,不经意间就抽取出一个巨无霸的基础模块。就跟很多喜欢用 Base 类的同学,最后就一个巨大的 Base 类。


所以一般小的 App, 搞模块化是大可不必的。巨型 App 是存在编译速度、多团队开发的问题,所以各大公司都会出品和分享自己的多模块化实践。小公司就容易跟风把工程搞得巨复杂,这就没意思了。


例如大公司拆分多模块,然后用 aar 包来加速构建速度,相对应的有私有 Maven 仓库,自动打包脚本,搞得很是复杂,对他们而言是很有必要。而对于小公司,其投入产出比,还不如花点钱,买台好一点的电脑来得香。

动态化、插件化


对于动态化和插件化,我只能感叹下,写出这些框架的大佬们,技术是真的强,都是真正的黑客。大概也只有国内的巨型 App 和奇怪的市场审核逻辑才能造就出这些需求。


有时间的话,看看这些库的源码也是很不错的,很涨见识。重要的还是看各大框架遇到的问题场景,以及是如何探索解决方案,最终是如何解决的。


不过问题始终是维护问题,随着 Android 版本的迭代,总归是需要有新的改动或者需要寻求新的方案,但国内的框架和库的成功往往和推动这个项目的人有关,没有一个稳定维护的机制,很容易受到人员变动的影响。

最后


再复杂的东西,也是由一个个的零件慢慢组装起来的。我们要有蓝图,然后为实现这个蓝图而拧螺丝。拧得多了,也许就有机会拧更大的螺丝。我在 emo 组件库上拧了 20 个螺丝,拧得多了,也算是一个不小的库了。不过目前我源码已经转 private 了,后面拧的螺丝,只想留给小团体孤芳自赏了。用行动证明一下,国内的开发者的开源库是多么的不稳定。

目录
相关文章
|
1天前
|
JavaScript 前端开发 Java
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
IT寒冬使APP开发门槛提升,安卓程序员需转型。选项包括:深化Android开发,跟进Google新技术如Kotlin、Jetpack、Flutter及Compose;研究Android底层框架,掌握AOSP;转型Java后端开发,学习Spring Boot等框架;拓展大前端技能,掌握JavaScript、Node.js、Vue.js及特定框架如微信小程序、HarmonyOS;或转向C/C++底层开发,通过音视频项目如FFmpeg积累经验。每条路径都有相应的书籍和技术栈推荐,助你顺利过渡。
12 3
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
|
6天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的世界中,选择正确的平台是关键。本文通过比较安卓和iOS开发的核心差异,揭示平台选择如何影响应用的性能、用户体验和市场覆盖。我们将深入探讨各自的开发环境、编程语言、用户界面设计原则以及发布流程,以帮助开发者和企业做出明智的决策。
27 9
|
3天前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:技术选择的影响
【8月更文挑战第17天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文通过比较这两个平台的编程语言、开发工具及市场策略,揭示了技术选择对开发者和产品成功的重要性。我们将从开发者的视角出发,深入探讨不同平台的技术特性及其对项目实施的具体影响,旨在为即将步入移动开发领域的新手提供一个清晰的指南,同时给予资深开发者新的思考角度。
|
6天前
|
Java 开发工具 Android开发
探索安卓与iOS开发的差异:从新手到专家的旅程
在数字时代的浪潮中,移动应用开发成为了连接世界的桥梁。本文将带你走进安卓与iOS这两大移动操作系统的开发世界,通过比较它们的编程语言、开发工具和环境、用户界面设计以及市场分布等方面,揭示各自的独特之处。无论你是初涉编程的新手,还是寻求进阶的开发者,这篇文章都将为你提供宝贵的洞见,助你在移动应用开发的征途上一帆风顺。
20 5
|
4天前
|
vr&ar Android开发 iOS开发
探索安卓和iOS开发的未来趋势
在移动应用开发的广阔天地里,安卓和iOS两大平台如同双子星座般璀璨夺目。随着技术的不断进步,这两个平台的开发趋势也在悄然发生着变化。本文将带你一探究竟,看看未来安卓和iOS开发将会迎来哪些令人激动的新特性和挑战。让我们一起跟随技术的脚步,开启这场探索之旅吧!
|
5天前
|
移动开发 Java Android开发
安卓与iOS开发:异同探析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文旨在深入探讨这两个平台在开发环境、编程语言、用户界面设计、性能优化及市场分布等方面的异同,为开发者提供实用的比较视角和决策参考。通过对比分析,我们不仅能更清晰地认识到各平台的特性,还能洞察未来移动开发的可能趋势。
|
6天前
|
Java 开发工具 Android开发
探索Android和iOS开发的差异与挑战
在移动应用开发的广阔天地中,Android和iOS两大平台如同两座高峰,各自拥有独特的风景。本文将深入探讨这两个平台的开发差异,包括编程语言、开发工具、用户界面设计等方面,并分析开发者面临的挑战。无论你是初涉移动应用开发的新手,还是已经在这条路上走了很远的老手,这篇文章都将为你提供新的视角和思考。让我们一起走进这个充满创新与挑战的世界,发现那些隐藏在代码背后的秘密。
|
3天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:从新手到专家的旅程
本文将带你走进移动应用开发的两大平台——安卓和iOS,揭示它们之间的主要差异。我们将从新手的视角出发,逐步深入到更复杂的技术层面,帮助你理解这两个平台的开发环境、编程语言和用户界面设计等方面的不同。无论你是刚入门的新手,还是有一定经验的开发者,这篇文章都将为你提供有价值的见解和建议。现在,让我们一起开始这段探索之旅吧!
|
3天前
|
搜索推荐 Android开发 iOS开发
探索安卓与iOS开发的差异之美
在数字时代的浪潮中,移动应用开发如同一场精心编排的交响乐,安卓和iOS这两大平台扮演着不同乐器的角色,各自以独特的方式奏响。本文将带领读者走进这场音乐盛宴,感受两大平台在开发过程中所展现的不同韵律,从设计理念到用户体验,从市场占有率到生态系统,我们将一探究竟,欣赏它们如何在竞争激烈的市场中和谐共存,共同推动技术的进步与创新。
12 0
|
4天前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的关键考量
在数字时代,选择正确的开发平台是成功的一半。本文深入探讨了安卓与iOS两大移动操作系统的开发差异,并分析了各自对创新、用户体验和市场需求的响应。通过比较两者的设计哲学、开发工具、市场覆盖和用户参与度,我们揭示了每个平台的独特优势和潜在挑战,旨在为开发者提供决策时的洞见,帮助他们在竞争激烈的应用市场中做出明智的选择。