现代化 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 了,后面拧的螺丝,只想留给小团体孤芳自赏了。用行动证明一下,国内的开发者的开源库是多么的不稳定。

目录
相关文章
|
4天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
6天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
8天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
6天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
7天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
19 2
|
8天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
14天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
13天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
14天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
33 3
|
11天前
|
移动开发 Java Android开发
探索Android与iOS开发的差异性与互联性
【10月更文挑战第32天】在移动开发的大潮中,Android和iOS两大平台各领风骚。本文将深入浅出地探讨这两个平台的开发差异,并通过实际代码示例,展示如何在各自平台上实现相似的功能。我们将从开发环境、编程语言、用户界面设计、性能优化等多个角度进行对比分析,旨在为开发者提供跨平台开发的实用指南。
33 0