目录
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 统一应用签名
1. 📂 简介
1.1 背景
为减少应用开发重复造轮子,于是希望有一套统一的应用框架,可以让应用开发者快速上手开发需求,而无需过多关注应用框架相关内容。
而且,基于同一套应用框架,有利于同事互相之间的代码Review与维护,也方便后期项目交接。不仅减少开发人员繁复的工作,而且提升整个团队的产研效率。
1.2 专业术语
术语名称 | 描述 |
AndroidStudio | 简称AS,是Android开发者用来开发应用的一个工具 |
Retrofit/okhttp3 | 知名的网络请求框架 |
Glide | 知名的图片加载框架 |
LiveEventBus | 基于LiveData的应用开发事件总线,方便业务间消息通信,有取代EventBus的趋势 |
MVVM | 基于MVC、MVP的一套代码开发框架 |
Kotlin | Android开发官方推荐语言 |
2. 🔱 总体设计思想
2.1 分层:组件化设计框架
我们知道常见的应用开发框架主要有:模块化、组件化、插件化,那么随着各个应用不断的迭代升级,应用的开发框架也从最开始的单App模块到多模块化,再到组件化与插件化。那么对于一般的应用开发框架,应该遵循什么样的设计原则呢?
首先,我想到的是不能过度设计,一口不能吃一个大胖子,一来就嚷着要做插件化是不太现实的,框架应该跟随应用需求一步步迭代或重构。其次,直接使用AS创建一个新项目,会缺少应用迭代升级的一些常见元素(如常见的Retrofit、Glide、LiveEventBus等三方开源SDK和一些工具类),在后期慢慢引入时很可能会没有必要的重复踩前人走过的坑。于是,依赖于组件化设计思想——无惧应用后期扩展迭代,再结合应用基本所需元素——搭一个轮子减少重复劳动,就设计出本文将要阐述的应用开发框架,具体思想请继续往下看。
2.2 分类:应用开发架构图
总体方向是:首先建立一个组件化框架的外形架构,然后实现业务模块化,再搭建一套MVVM+Kotlin代码编程框架。
3. ⚛️ 框架详细设计
3.1 组件化框架外形
以app模块为总入口,作为组件化中提到的壳工程。app模块不应包含相关业务代码,它的主要工作应该是进行Application初始化、依赖各个模块,和声明各模块manifest相关配置。
3.2 业务模块化
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
3.3 代码编程框架
各业务模块采用MVVM框架,包结构为:data-model-view-viewmodel
使用Kotlin语言,依赖于下沉到middleware模块的BaseActivity、BaseFragment,以及ViewBinding,建立起一套如下的代码编程框架:
4. 💠 框架其他设计
4.1 版本统一控制
根目录新建version.gradle文件,将各类版本号统一放置此处,在其他gradlle脚本通过 apply from: "${rootDir}/version.gradle" 导入使用。
4.2 引入部分第三方框架
在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"
4.3 封装网络请求框架
基于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<String, String>): List<CatBean>
// suspend fun getCat(@QueryMap hashMap: HashMap<String, String>): BaseResponse<CatBean>}class MainModel : BaseModel<MainApi>() {override fun createApi(): Class<MainApi> = MainApi::class.java// suspend fun getCat(hashMap: HashMap<String, String>): BaseResponse<CatBean> =suspend fun getCat(hashMap: HashMap<String, String>): List<CatBean> =withContext(Dispatchers.IO) {try {apiStores.getCat(hashMap)} catch (e: Exception) {mutableListOf()}}
}abstract class BaseModel<T> {protected var apiStores: Tinit {apiStores = ApiFactory.remoteService(createApi())}protected abstract fun createApi(): Class<T>}
网络请求工厂:
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()@JvmStaticfun <T> remoteService(apiService: Class<T>): 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<Interceptor> = mutableListOf()enum class HeaderNames(val headerName: String) {TOKEN("Token"), PLATFORM("Platform"), UID("UID"), VERSION("Version"),}}
4.4 统一应用签名
在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 truev2SigningEnabled true}}buildTypes {release {minifyEnabled falsesigningConfig signingConfigs.KeyStoreproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}debug {minifyEnabled falsesigningConfig signingConfigs.KeyStoreproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}