前语:苟有恒,何须三更眠五更起;最无益,莫过一日曝十日寒。
前语
之前一直想写个 WanAndroid 项目来稳固自己对 Kotlin+Jetpack+协程
等常识的学习,可是一直没有时刻。这里重新行动起来,从项目建立到完结前前后后用了两个月时刻,往常时刻比较少,基本上都是只能运用零碎的时刻来写。但不再是想写一个简略的玩安卓项目,我从多个大型项目中学习和吸取经验,从0到1打造一个契合大型项意图架构办法。
这或许是一个缩影,可是麻雀虽小,五脏俱全,这肯定能给咱们带来一些主意和思考。当然这个项意图功用并未悉数完善,由于咱们的意图不是造一个 WanAndroid 客户端,而是学习建立和运用 Kotlin+协程+Flow+Retrofit+Jetpack+MVVM+组件化+模块化+短视频 这一种架构,更好的提高自己。后续我也会不断完善和优化,在保证具有一个正常的 APP 功用之外,继续参加 Compose
,依靠注入Hint
,功用优化
,MVI办法
,支付功用
等的实践。
一、项目简介
- 项目选用 Kotlin 语言编写,结合
Jetpack
相关控件,Navigation
,Lifecyle
,DataBinding
,LiveData
,ViewModel
等建立的 MVVM 架构办法; - 经过组件化,模块化拆分,完结项目更好解耦和复用,ARouter 完结模块间通讯;
- 运用 协程+Flow+Retrofit+OkHttp 高雅地完结网络恳求;
- 经过
mmkv
,Room
数据库等完结对数据缓存的办理; - 运用谷歌
ExoPlayer
完结短视频播映; - 运用
Glide
完结图片加载; - 经过 WanAndroid 供给的 API 完结的一款玩安卓客户端。
项目运用 MVVM架构办法,基本上遵循 Google 引荐的架构,关于 Repository
,Google 认为 ViewModel
仅仅用来做数据的存储,数据加载应该由 Repository
来完结。经过 Room
数据库完结对数据的缓存,在无网络或许弱网的情况下优先展现缓存数据。
项目截图:
项目地址: github.com/suming77/Su…
二、项目概况
2.1 根底架构
(1) BaseActicity
经过单一责任原则,完结职能分级,运用者只需求按需继承即可。
- BaseActivity: 封装了通用的 init 办法,初始化布局,加载弹框等办法,供给了原始的增加布局的办法;
-
BaseDataBindActivity:继承自
BaseActivity
,经过 dataBinding 绑定布局,运用泛型参数反射创立布局文件实例,获取布局 view,不再需求findViewById()
;
val type = javaClass.genericSuperclass
val vbClass: Class<DB> = type!!.saveAs<ParameterizedType>().actualTypeArguments[0].saveAs()
val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
mBinding = method.invoke(this, layoutInflater)!!.saveAsUnChecked()
setContentView(mBinding.root)
-
BaseMvvmActivity: 继承自
BaseDataBindActivity
,经过泛型参数反射主动创立ViewModel
实例,更便利运用ViewModel
完结网络恳求。
val argument = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
mViewModel = ViewModelProvider(this).get(argument[1] as Class<VM>)
(2) BaseFragment
BaseFragment 的封装与上面的 BaseActivity 相似。
(3) BaseRecyclerViewAdapter
-
BaseRecyclerViewAdapter:封装了
RecyclerViewAdapter
基类,完结供给创立ViewHolder
才能,供给增加头尾布局才能,通用的 Item 点击事情,供给 dataBinding 才能,不再需求findViewById()
,供给了多种改写数据的办法,大局改写,部分改写等等。 -
BaseMultiItemAdapter: 供给了完结多种不同布局的 Adapter,依据不同的 ViewType 完结不同的
ViewBinding
,再创立返回不同的ViewHolder
。
(4) Ext拓宽类
项目中供给了很多控件扩展类,能够快速开发,进步功率:
- ResourceExt: 资源文件扩展类;
- TextViewExt: TextView 扩展类;
- SpanExt: Span 拓宽类,完结多种 Span 作用;
- RecyclerViewExt:一行代码快速完结增加笔直分割线,网格分割线;
- ViewExt: View 扩展类,完结点击防抖,增加距离,设置宽度,设置可见性等等;
-
EditTextExt: 经过 Flow 构建输入框文字改变流,
filter{}
完结数据过滤,防止无效恳求,debounce()
完结防抖; - GsonExt: 一行代码快速完结 Bean 和 Json 之间的相互转化。
//将Bean目标转化成json字符串
fun Any.toJson(includeNulls: Boolean = true): String {
return gson(includeNulls).toJson(this)
}
//将json字符串转化成目标Bean目标
inline fun <reified T> String.toBean(includeNulls: Boolean = true): T {
return gson(includeNulls).fromJson(this, object : TypeToken<T>() {}.type)
}
(5) xlog
XLog 是一个高功用文本存储方案,在实在环境中经受了微信数亿级别的考验,具有很好的安稳性。由于其是运用C语言来完结的,故有占用功用、内存小,存储速度快等优点,支撑多线程,乃至多进程的运用,支撑定期删去日志,一同,具有特定算法,进行了文件的紧缩,乃至能够装备文件加密。
运用 Xlog 建设客户端运转时日志体系,长途日志按需回捞,以打点的办法记载关键执行流程。
2.2 Jetpack组件
项目中仅仅运用到上图的一小部分组件。
(1) Navtgation
Navtgation 作为构建运用内界面的结构,重点是让单 Activity 运用成为首选架构(一个运用只需一个 Activity),它的定位是页面路由。
项目中主页分为5个 Tab,首要为主页、分类、体系、我的。运用 BottomNavigationView
+ Navigation
来建立。经过 menu 来装备底部菜单,经过 NavHostFragment
来装备各个 Fragment。一同处理了 Navigation
与 BottomNavigationView
结合运用时,点击 tab,Fragment 每次都会重新创立问题。处理办法是自定义 FragmentNavigator
,将内部 replace()
替换为 show()/hide()
。
(2) ViewBinding&DataBinding
-
ViewBinding
的出现便是不再需求写findViewById()
; -
DataBinding
是一种工具,它处理了 View 和数据之间的双向绑定;削减代码模板,不再需求写findViewById()
;开释 Activity/Fragment,能够在 XML 中完结数据,事情绑定作业,让Activity/Fragment
愈加关心中心事务;数据绑定空安全,在 XML 中绑定数据它是空安全的,由于DataBinding
在数据绑定上会主动装箱和空判别,所以大大削减了 NPE 问题。
(3) ViewModel
ViewModel
具备生命感知才能的数据存储组件。页面装备更改数据不会丢掉,数据共享(单 Activity 多 Fragment 场景下的数据共享),以生命周期的办法办理界面相关的数据,通常和 DataBinding 配合运用,为完结 MVVM 架构供给了强有力的支撑。
(4) LiveData
LiveData
是一个具有生命周期感知才能的数据订阅,分发组件。支撑共享资源(一个数据支撑被多个观察者接收的),支撑粘性事情的分发,不再需求手动处理生命周期(和宿主生命周期主动关联),保证界面契合数据状态。在底层数据库更改时通知 View。
(5) Room
一个轻量级 orm 数据库,本质上是一个 SQLite 抽象层。运用愈加简略(Builder 办法,相似 Retrofit),经过注解的办法完结相关功用,编译时主动生成完结类 IMPL。
这里首要用于主页视频列表缓存数据,与 LiveData
和 Flow 结合处理能够防止不必要的 NPE,能够监听数据库表中的数据的改变,也能够和 RXJava 的 Observer 运用,一旦发生了 insert,update,delete等操作,Room
会主动读取表中最新的数据,发送给 UI 层,改写页面。
- 数据库类:用于保存数据库并作为运用持久性数据底层连接的首要拜访点;
- 数据实体:用于表示运用的数据库中的表;
- 数据拜访目标 (DAO):供给您的运用可用于查询、更新、刺进和删去数据库中的数据的办法。
Dao
@Dao
interface VideoListCacheDao {
//刺进单个数据
@Insert(entity = VideoInfo::class, onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(videoInfo: VideoInfo)
//刺进多个数据
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(videoList: MutableList<VideoInfo>)
//删去指定item 运用主键将传递的实体实例与数据库中的行进行匹配。假如没有具有相同主键的行,则不会进行任何更改
@Delete
fun delete(videoInfo: VideoInfo): Int
//删去表中一切数据
@Query("DELETE FROM $TABLE_VIDEO_LIST")
suspend fun deleteAll()
//更新某个item,不指定的entity也能够,会依据你传入的参数目标来找到你要操作的那张表
@Update
fun update(videoInfo: VideoInfo): Int
//依据id更新数据
@Query("UPDATE $TABLE_VIDEO_LIST SET title=:title WHERE id=:id")
fun updateById(id: Long, title: String)
//查询一切数据
@Query("SELECT * FROM $TABLE_VIDEO_LIST")
fun queryAll(): MutableList<VideoInfo>?
//依据id查询某个数据
@Query("SELECT * FROM $TABLE_VIDEO_LIST WHERE id=:id")
fun query(id: Long): VideoInfo?
//经过LiveData以观察者的办法获取数据库数据,能够防止不必要的NPE
@Query("SELECT * FROM $TABLE_VIDEO_LIST")
fun queryAllLiveData(): LiveData<List<VideoInfo>>
}
Database
@Database(entities = [VideoInfo::class], version = 1, exportSchema = false)
abstract class SumDataBase : RoomDatabase() {
//抽象办法或许抽象类标记
abstract fun videoListDao(): VideoListCacheDao
companion object {
private var dataBase: SumDataBase? = null
//同步锁,或许在多个线程中一同调用
@Synchronized
fun getInstance(): SumDataBase {
return dataBase ?: Room.databaseBuilder(SumAppHelper.getApplication(), SumDataBase::class.java, "SumTea_DB")
//是否答应在主线程查询,默许是false
.allowMainThreadQueries()
.build()
}
}
}
注意:Room 数据库中的 Dao 中定义数据库操作的办法一定要保证用法正确,否则会导致 Room 编译时生成的完结类过错,编译不经过等问题。
2.3 网络恳求库
项意图网络恳求封装供给了两种办法的完结,一种是协程+Retrofit+ViewModel+Repository,像官网那样加一层 Repository
去办理网络恳求调用;另一种办法是经过 Flow 流配合 Retrofit 更高雅完结网络恳求,比照官网的做法愈加简练。
(1) Retrofit+协程+Repository
BaseViewModel
open class BaseViewModel : ViewModel() {
//需求运转在协程作用域中
suspend fun <T> safeApiCall(
errorBlock: suspend (Int?, String?) -> Unit,
responseBlock: suspend () -> T?
): T? {
try {
return responseBlock()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.e(e)
val exception = ExceptionHandler.handleException(e)
errorBlock(exception.errCode, exception.errMsg)
}
return null
}
}
BaseRepository
open class BaseRepository {
//IO中处理恳求
suspend fun <T> requestResponse(requestCall: suspend () -> BaseResponse<T>?): T? {
val response = withContext(Dispatchers.IO) {
withTimeout(10 * 1000) {
requestCall()
}
} ?: return null
if (response.isFailed()) {
throw ApiException(response.errorCode, response.errorMsg)
}
return response.data
}
}
HomeRepository的运用
class HomeRepository : BaseRepository() {
//项目tab
suspend fun getProjectTab(): MutableList<ProjectTabItem>? {
return requestResponse {
ApiManager.api.getProjectTab()
}
}
}
HomeViewModel的运用
class HomeViewModel : BaseViewModel() {
//恳求项目Tab数据
fun getProjectTab(): LiveData<MutableList<ProjectTabItem>?> {
return liveData {
val response = safeApiCall(errorBlock = { code, errorMsg ->
TipsToast.showTips(errorMsg)
}) {
homeRepository.getProjectTab()
}
emit(response)
}
}
}
(2) Flow高雅完结网络恳求
Flow 其实和 RxJava 很像,十分便利,用它来做网络恳求愈加简练。
suspend fun <T> requestFlowResponse(
errorBlock: ((Int?, String?) -> Unit)? = null,
requestCall: suspend () -> BaseResponse<T>?,
showLoading: ((Boolean) -> Unit)? = null
): T? {
var data: T? = null
//1.执行恳求
flow {
//设置超时时刻
val response = requestCall()
if (response?.isFailed() == true) {
errorBlock.invoke(response.errorCode, response.errorMsg)
}
//2.发送网络恳求结果回调
emit(response)
//3.指定运转的线程,flow {}执行的线程
}.flowOn(Dispatchers.IO)
.onStart {
//4.恳求开端,展现加载框
showLoading?.invoke(true)
}
//5.捕获反常
.catch { e ->
e.printStackTrace()
LogUtil.e(e)
val exception = ExceptionHandler.handleException(e)
errorBlock?.invoke(exception.errCode, exception.errMsg)
}
//6.恳求完结,包括成功和失败
.onCompletion {
showLoading?.invoke(false)
//7.调用collect获取emit()回调的结果,便是恳求最终的结果
}.collect {
data = it?.data
}
return data
}
2.4 图片加载库
Glide
图片加载运用 Glide 进行了简略的封装,对 ImageView 做扩展函数处理:
//加载图片,敞开缓存
fun ImageView.setUrl(url: String?) {
if (ActivityManager.isActivityDestroy(context)) {
return
}
Glide.with(context).load(url)
.placeholder(R.mipmap.default_img) // 占位符,反常时显现的图片
.error(R.mipmap.default_img) // 过错时显现的图片
.skipMemoryCache(false) //启用内存缓存
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) //磁盘缓存战略
.into(this)
}
//加载圆形图片
fun ImageView.setUrlCircle(url: String?) {
if (ActivityManager.isActivityDestroy(context)) return
Glide.with(context).load(url)
.placeholder(R.mipmap.default_head)
.error(R.mipmap.default_head)
.skipMemoryCache(false) //启用内存缓存
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.transform(CenterCrop()) // 圆形
.into(this)
}
//加载圆角图片
fun ImageView.setUrlRound(url: String?, radius: Int = 10) {
if (ActivityManager.isActivityDestroy(context)) return
Glide.with(context).load(url)
.placeholder(R.mipmap.default_img)
.error(R.mipmap.default_img)
.skipMemoryCache(false) // 启用内存缓存
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.transform(CenterCrop(), RoundedCorners(radius))
.into(this)
}
//加载Gif图片
fun ImageView.setUrlGif(url: String?) {
if (ActivityManager.isActivityDestroy(context)) return
Glide.with(context).asGif().load(url)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.placeholder(R.mipmap.default_img)
.error(R.mipmap.default_img)
.into(this)
}
/**
* 设置图片高斯模糊
* @param radius 设置模糊度(在0.0到25.0之间),默许25
* @param sampling 图片缩放比例,默许1
*/
fun ImageView.setBlurView(url: String?, radius: Int = 25, sampling: Int = 1) {
if (ActivityManager.isActivityDestroy(context)) return
//恳求装备
val options = RequestOptions.bitmapTransform(BlurTransformation(radius, sampling))
Glide.with(context)
.load(url)
.placeholder(R.mipmap.default_img)
.error(R.mipmap.default_img)
.apply(options)
.into(this)
}
- 修复 Glide 的图片裁剪和 ImageView 的
scaleType
的抵触问题,Bitmap 会先圆角裁剪,再加载到 ImageView 中,假如 Bitmap 图片尺度大于 ImageView 尺度,则会看不到,运用CenterCrop()
重载,会先将 Bitmap 居中裁剪,再进行圆角处理,这样就能看到。 - 供给了 GIF 图加载和图片高斯模糊作用功用。
2.5 WebView
咱们都知道原生的 WebView 存在许多问题,运用腾讯X5内核 WebView 进行封装,兼容性,安稳性,安全性,速度都有很大的提高。
项目中运用 WebView 展现文章概况页。
2.6 MMKV
MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化 / 反序列化运用 protobuf 完结,功用高,安稳性强。运用简略,支撑多进程。
在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在Application
里:
public void onCreate() {
super.onCreate();
String rootDir = MMKV.initialize(this);
LogUtil.e("mmkv root: " + rootDir);
}
MMKV 供给一个大局的实例,能够直接运用:
import com.tencent.mmkv.MMKV;
//……
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");
kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");
kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");
循环写入随机的 int
1k 次,有如下功用比照:
2.7 ExoPlayer视频播映器
ExoPlayer
是 google 推出的开源播映器,首要是集成了 Android 供给的一套解码体系来解析视频和音频,将 MediaCodec
封装地十分完善,形成了一个功用优越,播映安稳性较好的一个开发播映器,支撑更多的视频播映格局(包括 DASH 和 SmoothStreaming
,这2种 MediaPlayer
不支撑),经过组件化自定义播映器,便利扩展定制,持久的高速缓存,另外 ExoPlayer
包巨细轻便,接入简略。
项目中运用 ExoPlayer
完结防抖音短视频播映:
class VideoPlayActivity : BaseDataBindActivity<ActivityVideoPlayBinding>() {
//创立exoplayer播映器实例,视屏画面烘托工厂类,语音选择器,缓存控制器
private fun initPlayerView(): Boolean {
//创立exoplayer播映器实例
mPlayView = initStylePlayView()
// 创立 MediaSource 媒体资源 加载的工厂类
mMediaSource = ProgressiveMediaSource.Factory(buildCacheDataSource())
mExoPlayer = initExoPlayer()
//缓冲完结主动播映
mExoPlayer?.playWhenReady = mStartAutoPlay
//将显现控件绑定ExoPlayer
mPlayView?.player = mExoPlayer
//资源准备,假如设置 setPlayWhenReady(true) 则资源准备好就立马播映。
mExoPlayer?.prepare()
return true
}
//初始化ExoPlayer
private fun initExoPlayer(): ExoPlayer {
val playerBuilder = ExoPlayer.Builder(this).setMediaSourceFactory(mMediaSource)
//视频每一帧的画面怎样烘托,完结默许的完结类
val renderersFactory: RenderersFactory = DefaultRenderersFactory(this)
playerBuilder.setRenderersFactory(renderersFactory)
//视频的音视频轨道怎样加载,运用默许的轨道选择器
playerBuilder.setTrackSelector(DefaultTrackSelector(this))
//视频缓存控制逻辑,运用默许的即可
playerBuilder.setLoadControl(DefaultLoadControl())
return playerBuilder.build()
}
//创立exoplayer播映器实例
private fun initStylePlayView(): StyledPlayerView {
return StyledPlayerView(this).apply {
controllerShowTimeoutMs = 10000
setKeepContentOnPlayerReset(false)
setShowBuffering(SHOW_BUFFERING_NEVER)//不展现缓冲view
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
useController = false //是否运用默许控制器,如需求可参考PlayerControlView
// keepScreenOn = true
}
}
//创立能够 边播映边缓存的 本地资源加载和http网络数据写入的工厂类
private fun buildCacheDataSource(): DataSource.Factory {
//创立http视频资源怎样加载的工厂目标
val upstreamFactory = DefaultHttpDataSource.Factory()
//创立缓存,指定缓存位置,和缓存战略,为最近最少运用原则,最大为200m
mCache = SimpleCache(
application.cacheDir,
LeastRecentlyUsedCacheEvictor(1024 * 1024 * 200),
StandaloneDatabaseProvider(this)
)
//把缓存目标cache和担任缓存数据读取、写入的工厂类CacheDataSinkFactory 相关联
val cacheDataSinkFactory = CacheDataSink.Factory().setCache(mCache).setFragmentSize(Long.MAX_VALUE)
return CacheDataSource.Factory()
.setCache(mCache)
.setUpstreamDataSourceFactory(upstreamFactory)
.setCacheReadDataSourceFactory(FileDataSource.Factory())
.setCacheWriteDataSinkFactory(cacheDataSinkFactory)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
}
}
2.8 组件化&模块化
组件化&模块化有利于事务模块分离,高内聚,低耦合,代码边界清晰。有利于团队合作多线开发,加快编译速度,进步开发功率,办理愈加便利,利于维护和迭代。
(1) 模块化
项目中经过以事务为维度把 App 拆分成主页模块,登录模块,搜索模块,用户模块,视频模块等,相互间不能够拜访不能够作为依靠,与此一同他们共同依靠于根底库,网络恳求库,公共资源库,图片加载库等。假如还需求运用到启动器组件、Banner组件、数据库Room组件等则独自按需增加。
APP 壳工程担任打包环境,签名,混淆规矩,事务模块集成,APP 主题等装备等作业,一般不包括任何事务。
(2) 组件化
模块化和组件化最显着的区别便是模块相对组件来说粒度更大。一个模块中或许包括多个组件。在区别的时候,模块化是事务导向,组件化是功用导向。组件化是建立在模块化思想上的一次演进。
项目中以功用维度拆分了启动器组件、Banner组件、数据库Room组件等组件。模块化&组件化拆分后工程图:
(3) 组件间通讯
组件化之后就无法直接拜访其他模块的类和办法,这是个比较突出的问题,就像本来能够直接运用 LogintManager
来拉起登录,判别是否已登录,可是这个类现已被拆分到了 mod_login 模块下,而事务模块之间是不能相互作为依靠的,所以无法在其他模块直接运用 LogintManager
。
首要凭借阿里的路由结构 ARouter 完结组件间通讯,把对外供给的才能,以接口的办法露出出去。
比方在公共资源库中的 service 包下创立 ILoginService
,供给对外露出登录的才能,在 mod_login 模块中供给 LoginServiceImpl
完结类,任意模块就能够经过 LoginServiceProvider
运用 iLoginService
对外供给露出的才能。
- 公共资源库中创立
ILoginService
,供给对外露出登录的才能。
interface ILoginService : IProvider {
//是否登录
fun isLogin(): Boolean
//跳转登录页
fun login(context: Context)
//登出
fun logout(
context: Context,
lifecycleOwner: LifecycleOwner?,
observer: Observer<Boolean>
)
}
- mod_login 模块中
LoginService
供给ILoginService
的具体完结。
@Route(path = LOGIN_SERVICE_LOGIN)
class LoginService : ILoginService {
//是否登录
override fun isLogin(): Boolean {
return UserServiceProvider.isLogin()
}
//跳转登录页
override fun login(context: Context) {
context.startActivity(Intent(context, LoginActivity::class.java))
}
//登出
override fun logout(
context: Context,
lifecycleOwner: LifecycleOwner?,
observer: Observer<Boolean>
) {
val scope = lifecycleOwner?.lifecycleScope ?: GlobalScope
scope.launch {
val response = ApiManager.api.logout()
if (response?.isFailed() == true) {
TipsToast.showTips(response.errorMsg)
return@launch
}
LogUtil.e("logout${response?.data}", tag = "smy")
observer.onChanged(response?.isFailed() == true)
login(context)
}
}
override fun init(context: Context?) {}
}
- 公共资源库中创立
LoginServiceProvider
,获取LoginService
,供给运用办法。
object LoginServiceProvider {
//获取loginService完结类
val loginService = ARouter.getInstance().build(LOGIN_SERVICE_LOGIN).navigation() as? ILoginService
//是否登录
fun isLogin(): Boolean {
return loginService.isLogin()
}
//跳转登录
fun login(context: Context) {
loginService.login(context)
}
//登出
fun logout(
context: Context,
lifecycleOwner: LifecycleOwner?,
observer: Observer<Boolean>
) {
loginService.logout(context, lifecycleOwner, observer)
}
}
那么其他模块就能够经过 LoginServiceProvider
运用 iLoginService
对外供给露出的才能。虽然看起来这么做会显得更杂乱,单一工程或许愈加合适咱们,每个类都能直接拜访,每个办法都能直接调用,可是咱们不能局限于单人开发的环境,在实际场景上多人协作是常态,模块化开发是干流。
(4) Module独自运转
使得模块能够在集成和独立调试之间切换特性。在打包时是 library,在调试是 application。
- 在
config.gradle
文件中参加isModule
参数:
//是否独自运转某个module
isModule = false
- 在每个
Module
的build.gradle
中参加isModule
的判别,以区别是 application 还是 library:
// 组件办法和根底办法切换
def root = rootProject.ext
if (root.isModule) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
sourceSets {
main {
if (rootProject.ext.isModule) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//library办法下扫除debug文件夹中的一切Java文件
java {
exclude 'debug/**'
}
}
}
}
}
- 将经过修改
SourceSets
中的属性,能够指定需求被编译的源文件,假如是library,则编译 manifest 下AndroidManifest.xml
,反之则直接编译 debug 目录下AndroidManifest.xml
,一同参加Application
和intent-filter
等参数。
存疑一:
至于模块独自编译独自运转,这种是一个伪需求,实际上必定存在多个模块间通讯的场景。否则跨模块的服务提取和获取,初始化使命,模块间的联合测试该怎样处理呢?一个模块运转后需求和其他的模块通讯,比方对外供给服务,获取服务,与之相关联的模块假如没有运转起来的话是无法运用的。
与此一同还需求在 suorceSets 下维护两套 AndoidManifest
以及 Javasource 目录,这个不只麻烦并且每次更改都需求同步一段时刻。所以这种流传的模块化独立编译的办法,是否真的合适就仁者见仁了。
三、写在最终
如需求更具体的代码能够到项目源码中检查,地址在下面给出。由于时刻仓促,项目中有部分功用尚未完善,或许部分完结办法有待优化,也有更多的Jetpack组件尚未在项目中实践,比方 依靠注入Hilt
,相机功用CameraX
,权限处理Permissions
, 分页处理Paging
等等。项意图继续迭代更新依然是一项艰苦持久战。
除去能够学到 Kotlin + MVVM + Android Jetpack + 协程 + Flow + 组件化 + 模块化 + 短视频
的常识,相信你还能够在我的项目中学到:
- 怎样运用 Charles 抓包。
- 供给很多扩展函数,快速开发,进步功率。
-
ChipGroup
和FlexboxLayoutManager
等多种原生办法完结流式布局。 - 契合阿里巴巴 Java 开发规范和阿里巴巴 Android 开发规范,并有良好的注释。
-
CoordinatorLayout
和Toolbar
完结主页栏目吸顶作用和轮播图电影作用。 - 运用
ViewOutlineProvider
给控件增加圆角,大大削减手写 shape 圆角 xml。 -
ConstraintLayout
的运用,几乎每个界面布局都选用的ConstraintLayout
。 - 异步使命启动器,高雅地处理 Application 中同步初始化使命问题,有用削减 APP启动耗时。
- 无论是模块化或许组件化,它们本质思想都是一样的,都是化整为零,化繁为简,两者的意图都是为了重用和解耦,仅仅叫法不一样。
项目地址:ST_Wan_Android
点关注,不走失
好了各位,以上便是这篇文章的悉数内容了,很感谢您阅览这篇文章。我是suming,感谢支撑和认可,您的点赞便是我创造的最大动力。山水有相逢,咱们下篇文章见!
本人水平有限,文章难免会有过错,请批评指正,不胜感激 !
感谢
API: 鸿洋供给的 WanAndroid API
首要运用的开源结构:
- Retrofit
- OkHttp
- Glide
- ARouter
- MMKV
- RxPermission
- SmartRefreshLayout