目录
1. 简介
1.1 背景
1.2 专业术语
2. 总体设计思想
2.1 分层:组件化设计框架
2.2 分类:应用开发架构图
3. ⚛️ 框架详细设计
3.1 组件化框架外形
3.2 业务模块化
3.3 代码编程框架
4. 框架其他设计
4.1 版本统一控制
4.2 引入部分第三方框架
4.3 封装网络请求框架
4.4 统一应用签名
为减少应用开发重复造轮子,于是希望有一套统一的应用框架,可以让应用开发者快速上手开发需求,而无需过多关注应用框架相关内容。
而且,基于同一套应用框架,有利于同事互相之间的代码Review与维护,也方便后期项目交接。不仅减少开发人员繁复的工作,而且提升整个团队的产研效率。
术语名称 |
描述 |
AndroidStudio |
简称AS,是Android开发者用来开发应用的一个工具 |
Retrofit/okhttp3 |
知名的网络请求框架 |
Glide |
知名的图片加载框架 |
LiveEventBus |
基于LiveData的应用开发事件总线,方便业务间消息通信,有取代EventBus的趋势 |
MVVM |
基于MVC、MVP的一套代码开发框架 |
Kotlin |
Android开发官方推荐语言 |
我们知道常见的应用开发框架主要有:模块化、组件化、插件化,那么随着各个应用不断的迭代升级,应用的开发框架也从最开始的单App模块到多模块化,再到组件化与插件化。那么对于一般的应用开发框架,应该遵循什么样的设计原则呢?
首先,我想到的是不能过度设计,一口不能吃一个大胖子,一来就嚷着要做插件化是不太现实的,框架应该跟随应用需求一步步迭代或重构。其次,直接使用AS创建一个新项目,会缺少应用迭代升级的一些常见元素(如常见的Retrofit、Glide、LiveEventBus等三方开源SDK和一些工具类),在后期慢慢引入时很可能会没有必要的重复踩前人走过的坑。于是,依赖于组件化设计思想——无惧应用后期扩展迭代,再结合应用基本所需元素——搭一个轮子减少重复劳动,就设计出本文将要阐述的应用开发框架,具体思想请继续往下看。
总体方向是:首先建立一个组件化框架的外形架构,然后实现业务模块化,再搭建一套MVVM+Kotlin代码编程框架。
以app模块为总入口,作为组件化中提到的壳工程。app模块不应包含相关业务代码,它的主要工作应该是进行Application初始化、依赖各个模块,和声明各模块manifest相关配置。
app模块依赖portal业务模块,暂且我们可将所有业务写在portal模块,后期业务壮大后可朝组件化方向再拆分业务模块。
middleware模块作为中间件层,放置一些模块间共用的元素,那么项目中除middleware模块之外的其他所有模块,如:app模块和portal模块,都应依赖middleware模块。但需注意,随着业务的增长middleware模块可预料的会不断膨胀,所以写业务时需注意解耦,切勿将非模块间公用的元素放进middleware模块。
portal模块包括主要的业务代码,采用组件化分包方式搭建各业务模块,比如下面这样的包名结构:
com.xxx.xxx.portal.feature.home
com.xxx.xxx.portal.feature.bt
com.xxx.xxx.portal.feature.wifi
各业务模块采用MVVM框架,包结构为:data-model-view-viewmodel
使用Kotlin语言,依赖于下沉到middleware模块的BaseActivity、BaseFragment,以及ViewBinding,建立起一套如下的代码编程框架:
根目录新建version.gradle文件,将各类版本号统一放置此处,在其他gradlle脚本通过 apply from: "${rootDir}/version.gradle" 导入使用。
在middleware模块中引入一些应用开发必要的第三方库,主要包括blankj工具包合集、图片加载框架Glide、网络请求框架和相关配置Retrofit/okhttp3、动效加载工具lottie、以及应用开发事件总线LiveEventBus等。
api "androidx.constraintlayout:constraintlayout:$CONSTRAINTLAYOUT"
api "androidx.lifecycle:lifecycle-livedata-ktx:$LIVEDATA"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$VIEWMODEL"
api "androidx.viewpager2:viewpager2:$VIEWPAGER2"
api "com.blankj:utilcodex:$COM_BLANKJ_UTILCODEX"
api "com.github.bumptech.glide:glide:$GLIDE"
api "com.squareup.retrofit2:retrofit:$RETROFIT"
api "com.squareup.retrofit2:converter-gson:$RETROFIT_CONVERTER_GSON"
api "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_ADAPTER_RXJAVA"
api "com.squareup.okhttp3:logging-interceptor:$OKHTTP_LOGGING_INTERCEPTOR"
api "com.airbnb.android:lottie:$LOTTIE"
api "io.github.jeremyliao:live-event-bus-x:$LIVE_EVENT_BUS_X"
基于Retrofit/okhttp3,封装出相应的ApiFactory以及BaseModel、BaseViewModel,方便基于MVVM项目访问网络,本应用开发框架中已包含整套网络请请求和处理的编程范式,具体可参考如下代码:
Model层:
interface MainApi {
// 随机获取1张猫图(GET):https://api.thecatapi.com/v1/images/search?limit=1
@GET("v1/images/search")
suspend fun getCat(@QueryMap hashMap: HashMap): List
// suspend fun getCat(@QueryMap hashMap: HashMap): BaseResponse
}
class MainModel : BaseModel() {
override fun createApi(): Class = MainApi::class.java
// suspend fun getCat(hashMap: HashMap): BaseResponse =
suspend fun getCat(hashMap: HashMap): List =
withContext(Dispatchers.IO) {
try {
apiStores.getCat(hashMap)
} catch (e: Exception) {
mutableListOf()
}
}
}
abstract class BaseModel {
protected var apiStores: T
init {
apiStores = ApiFactory.remoteService(createApi())
}
protected abstract fun createApi(): Class
}
网络请求工厂:
object ApiFactory {
private val okHttpClient: OkHttpClient by lazy { setHttpClient() }
private fun retrofit(): Retrofit {
return Retrofit.Builder().baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()).client(okHttpClient).build()
}
private fun setHttpClient(): OkHttpClient = OkHttpClient.Builder().apply {
addInterceptor(InterceptorManager.headerInterceptor()).addInterceptor(InterceptorManager.httpLoggingInterceptor())
InterceptorManager.otherInterceptors.forEach { addInterceptor(it) }
}.connectTimeout(NET_TIMEOUT, TimeUnit.SECONDS).readTimeout(NET_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(NET_TIMEOUT, TimeUnit.SECONDS).build()
@JvmStatic
fun remoteService(apiService: Class): T {
return retrofit().create(apiService)
}
}
网络请求拦截器:
object InterceptorManager {
/**
* 返回头拦截器
* @return
*/
fun headerInterceptor(): Interceptor = Interceptor { chain: Interceptor.Chain ->
chain.proceed(
chain.request().newBuilder().addHeader(HeaderNames.TOKEN.headerName, "")
.addHeader(HeaderNames.PLATFORM.headerName, "android")
.addHeader(HeaderNames.UID.headerName, "")
.addHeader(HeaderNames.VERSION.headerName, AppUtils.getAppVersionName()).build()
)
}
/**
* 返回http拦截器
*/
fun httpLoggingInterceptor(): Interceptor = HttpLoggingInterceptor().apply {
this.level =
if (Constants.RELEASE) HttpLoggingInterceptor.Level.NONE else HttpLoggingInterceptor.Level.BODY
}
/**
* 自定义http拦截器
*/
var otherInterceptors: MutableList = mutableListOf()
enum class HeaderNames(val headerName: String) {
TOKEN("Token"), PLATFORM("Platform"), UID("UID"), VERSION("Version"),
}
}
在app模块中新增signing.properties文件存储签名信息,xxx.jks签名文件放在同级目录,然后在app模块build.dradle文件中增加如下签名校验信息:
Properties signingProps = new Properties()
signingProps.load(new FileInputStream(file("signing.properties")))
android {
signingConfigs {
KeyStore {
keyAlias signingProps['KEY_STORE_ALIAS']
keyPassword signingProps['KEY_STORE_KEY_PASSWD']
storeFile file(signingProps['KEY_STORE_FILE'])
storePassword signingProps['KEY_STORE_PASSWD']
v1SigningEnabled true
v2SigningEnabled true
}
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.KeyStore
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
signingConfig signingConfigs.KeyStore
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}