目录
Android 从0搭建初始化MVVM项目框架:如何搭建属于自己的项目框架?如何创建BaseActivity?如何整合viewbinding?如何整合网络框架?
前言
我们要做的,和下面这种差不多,提供一个初始化项目,比如:使用MVVM架构进行搭建,使用Kotlin语言开发,整合网络框架OkHttp+Retorfit,依赖注入框架Hilt,数据库Room等等。当我们搭建了一个这样的框架后,以后我们开发项目可以直接拿来就用,省去了很多的开发时间。
为什么明明github有了,我们还需要自己去做一个呢?
一方面是为了学习,了解他的架构设计,如何整合其他框架,整合的过程中会遇到什么问题(我遇到最多的问题就是构建问题)。
一方面是弥补知识的缺漏,有时候,我们以为自己懂了,拿来就可以用了,但只有动手做了,这个知识,才会属于自己。
本篇文章主要是采用MVVM架构搭建项目,会整合一些常用框架:网络框架、组件化、分包分模块、图片框架、bugly…等等框架。
一、BaseActivity、BaseViewModel以及BaseRepository里面应该有什么?
一个项目会有很多Activity或者Fragment,BaseActivity的作用主要是抽取Activity里面的相同内容,比如最有代表的就是viewbinding、一些类注册呀等等。旨在为应用程序中的所有活动(Activities)提供一个基础模板,以减少重复代码并确保一致性。使得开发者可以在子类中专注于实现具体的业务逻辑,而不需要重复编写一些通用的初始化代码。
首先,我们先创建一个Android项目:
1.1 创建BaseActivity:整合viewbinding和Eventbus
一、创建BaseActivity,添加viewbinding
private const val TAG = "BaseActivity"
abstract class BaseActivity<VB:ViewBinding>: AppCompatActivity() {
lateinit var dataBinding : VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = createVB()
setContentView(dataBinding.root)
init()
initObserve()
initRequestData()
}
/**
* 创建 [ViewBinding] 实例
* 布局由各自BaseActivity的实现类来提供
*/
abstract fun createVB(): VB
/**
* 初始化ui
*/
abstract fun init()
/**
* 订阅 [LiveData]
*/
abstract fun initObserve()
/**
* 用于在页面创建时进行请求接口
*/
abstract fun initRequestData()
}
- 在onCreate方法中,通过调用抽象方法createVB()来创建View Binding实例,并将其赋值给dataBinding变量。这意味着每个继承BaseActivity的子类都需要实现createVB()方法,以返回对应布局的View Binding实例。
- 为什么会有var dataBinding: VB:类型为泛型VB?这个泛型VB是继承BaseActivity的子类需要指定的,因为Base是父类,他必须提供一些通用的东西。
- 定义了四个抽象方法:createVB(), init(), initObserve(), 和 initRequestData()。这些方法需要在继承BaseActivity的子类中实现。
init(): 用于初始化UI组件,如设置监听器、初始化视图状态等。
initObserve(): 用于订阅ViewModel中的LiveData或其他可观察数据,以更新UI。
initRequestData(): 用于在页面创建时发起网络请求或其他数据获取操作。
二、接下来,我们创建一个MainActivity来继承BaseActivity
class MainActivity : BaseActivity<ActivityMainBinding>() {
private val TAG = "MainActivity"
@Inject
lateinit var mApi: HomeApiService
override fun createVB(): ActivityMainBinding {
return ActivityMainBinding.inflate(layoutInflater)
}
override fun init() {
dataBinding.tvTitle.setText("hello android init")
dataBinding.tvTitle.setOnClickListener {
ARouter.getInstance().build(RouteUrl.Background.MainActivity2).navigation()
Log.d(TAG, "init: " + mApi)
}
}
override fun initObserve() {
}
override fun initRequestData() {
}
}
是不是便捷了很多,我们只需要提供一个泛型,以及重写createVB方法就可以。
三、我们也可以整合一个Eventbus框架,用于消息订阅和通知,因为Eventbus注册和取消是固定步骤,所以我们写到BaseActivity里面。
private const val TAG = "BaseActivity"
abstract class BaseActivity<VB:ViewBinding>: AppCompatActivity() {
lateinit var dataBinding : VB
/**
* 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotation]
*/
private var mHaveRegisterEventBus = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = createVB()
setContentView(dataBinding.root)
// 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus
if (javaClass.isAnnotationPresent(RegisterEventBus::class.java) && !mHaveRegisterEventBus) {
mHaveRegisterEventBus = true
EventBusUtils.register(this)
Log.d(TAG, "onCreate: ")
}
init()
initObserve()
initRequestData()
}
/**
* 创建 [ViewBinding] 实例
* 布局由各自BaseActivity的实现类来提供
*/
abstract fun createVB(): VB
/**
* 初始化ui
*/
abstract fun init()
override fun onDestroy() {
// 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus
if (mHaveRegisterEventBus) {
EventBusUtils.unRegister(this)
}
super.onDestroy()
}
/**
* 订阅 [LiveData]
*/
abstract fun initObserve()
/**
* 用于在页面创建时进行请求接口
*/
abstract fun initRequestData()
}
/**
* 辅助注册 EventBus 的注解
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class RegisterEventBus
进行使用
@RegisterEventBus
class MainActivity : BaseActivity<ActivityMainBinding>() {
以前,我们使用Eventbus,还需要调用EventBus.getDefault().register(subscriber),页面结束的时候调用EventBus.getDefault().unregister(subscriber),有时候常常忘记关掉。现在写到了BaseActivity里面,一次搞定,以后其他Activity使用,可以不用写注册和销毁,直接使用一个注解来决定是否使用Eventbus。
1.2 创建BaseModel和BaseRepository
ViewModel里面主要是做逻辑处理,有相同的内容可以抽取到BaseViewModel。
abstract class BaseViewModel : ViewModel() {
}
MainViewModel 里面需要拿到Repository,所以我们创建一个BaseRepository,Repository里面主要是做一些数据内容的获取,比如网络框架的、数据库的等等,接着我们创建一个MainRepository继承BaseRepository
class MainViewModel constructor(private val mRepo: MainRepository) : BaseViewModel() {
suspend fun getPwd() {
mRepo.getPwd()
}
}
到这里M、V、VM层都有了。MainActivity中如何创建MainViewModel 实例呢?
class MainActivity : BaseActivity<ActivityMainBinding,MainViewModel>() {
private val TAG = "MainActivity"
val mViewModel by viewModels<MainViewModel>()
....
viewModels 函数会自动创建一个 ViewModelProvider 对象,并使用它来获取 ViewModel 实例。如果 ViewModel 已经存在(例如,在配置更改如屏幕旋转后),则会返回现有的实例,否则会创建一个新的实例。
由于ViewModel每个Activity都会有,所以我们抽取到Base里面,然后重写mViewModel 变量
abstract class BaseActivity<VB:ViewBinding,VM:BaseViewModel>: AppCompatActivity() {
lateinit var dataBinding : VB
protected abstract val mViewModel: VM
....
class MainActivity : BaseActivity<ActivityMainBinding,MainViewModel>() {
private val TAG = "MainActivity"
override val mViewModel by viewModels<MainViewModel>()
....
BaseActivity、BaseViewmodel以及BaseRepository都有了。但我们会发现,如果我们有很多的Repository,每次逗得手动创建,非常麻烦。接下来我们来整合Hilt,依赖注入框架,将创建对象交由Hilt来管理。
二、整合Hilt
Hilt是Google为Android推出的依赖注入库,它减少了手动注入的代码,提高了代码可读性。比如我们在MainModel里面还需要手动创建MainRepository,我们使用Hilt以后,只需要添加一个注解,就可以自动帮助我们创建。
比如:
class MainRepository @Inject constructor(/*application: Application*/) : BaseRepository() {
}
@HiltViewModel
class MainViewModel @Inject constructor(private val mRepo: MainRepository) : BaseViewModel() {
suspend fun getPwd() {
mRepo.getPwd()
}
}
@AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding,MainViewModel>() {
private val TAG = "MainActivity"
@HiltAndroidApp
class AppApplication : Application() {
}
我们只需要使用@Inject注解,Hilt就会自动帮助我们注入。接下来,我们在进行整合网络框架。
三、整合网络框架:Okhttp+Retofit2
网络框架就不用多说了,项目必备。
- 添加依赖
const val OkHttp = "com.squareup.okhttp3:okhttp:${
Version.OkHttp}"
const val OkHttpInterceptorLogging = "com.squareup.okhttp3:logging-interceptor:${
Version.OkHttpInterceptorLogging}"// OkHttp 请求Log拦截器
const val Retrofit = "com.squareup.retrofit2:retrofit:${
Version.Retrofit}"
const val RetrofitConverterGson =
"com.squareup.retrofit2:converter-gson:${
Version.RetrofitConverterGson}"
const val RetrofitConverterScalars =
"com.squareup.retrofit2:converter-scalars:${
Version.RetrofitConverterScalars}"
2.创建对象:交由Hilt来注入
@Module
@InstallIn(SingletonComponent::class)
class DINetworkModule {
/**
* [OkHttpClient]依赖提供方法
*
* @return OkHttpClient
*/
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
// 日志拦截器部分
// val level = if (BuildConfig.VERSION_TYPE != VersionStatus.RELEASE) BODY else NONE
val logInterceptor = HttpLoggingInterceptor().setLevel(BODY)
return OkHttpClient.Builder()// 1.使用内部工厂类 Builder 来设置 OkHttpClient
.connectTimeout(15L * 1000L, TimeUnit.MILLISECONDS) // 2.这行代码设置了连接超时时间,如果15秒内无法建立连接,则会抛出超时异常。
.readTimeout(20L * 1000L, TimeUnit.MILLISECONDS)//3. 这行代码设置了读取超时时间,如果20秒内无法完成数据的读取,则会抛出超时异常。
.addInterceptor(logInterceptor)//4. 拦截器可以在请求发送前后或响应接收前后执行自定义操作,比如打印日志
.retryOnConnectionFailure(true)//5. 意味着如果网络连接失败,OkHttp会尝试自动重试。
.build()
}
/**
* 项目主要服务器地址的[Retrofit]依赖提供方法
*
* @param okHttpClient OkHttpClient OkHttp客户端
* @return Retrofit
*/
@Singleton
@Provides
fun provideMainRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(NetBaseUrlConstant.MAIN_URL)
.addConverterFactory(ScalarsConverterFactory.create())//这行代码添加了一个转换工厂ScalarsConverterFactory,它用于将HTTP响应体转换为标量类型(如String、Integer等)。
.client(okHttpClient)//这行代码设置了用于网络请求的OkHttpClient实例。这个okHttpClient应该是在代码的其他地方创建并配置好的,可能包含了一些自定义的设置,比如超时时间、拦截器等。
.build()
}
}
3.构建实例
@Module //表示这一个用于提供依赖注入实例的模块。该模块定义了如何提供依赖项。在这个模块中,你可以定义方法来创建和提供对象实例。
@InstallIn(SingletonComponent::class)//这个模块中定义的依赖项将被安装在应用程序的全局单例组件中。这意味着这些依赖项将在整个应用程序的生命周期内只被创建一次,并在所有需要它们的地方共享。
class DIHomeNetServiceModule {
/**
* Home模块的[HomeApiService]依赖提供方法
*
* @param retrofit Retrofit
* @return HomeApiService
*/
@Provides//表示provideHomeApiService方法是一个提供依赖项的方法,Dagger Hilt会调用这些方法来获取它们提供的依赖项。也就是@Inject能拿到依赖。
@Singleton//@Singleton 注解也用于 provideDatabase 方法上,以确保返回的 AppDatabase 实例是单例的。
fun provideHomeApiService(retrofit: Retrofit): HomeApiService {
return retrofit.create(HomeApiService::class.java)
}
}
- 创建接口
interface HomeApiService {
/**
* 请求密码
*/
@GET("/xxxx")
suspend fun getPwd(@QueryMap params: Map<String, String>): String
}
- 在repository中使用,因为属于数据获取操作,所以我们写在repository里面。
class MainRepository @Inject constructor(/*application: Application*/) : BaseRepository() {
@Inject
lateinit var mApi: HomeApiService
//请求密码:emit将数据发射到catch或者collect上面
suspend fun getPwd() = request<String> {
var map = HashMap<String, String>()
map.put("xxx", "xxx")
mApi.getPwd(map).run {
it.emit(this)
}
}
}
这里我们使用到了flow,下面我们也整合flow。
四、整合Flow
结合Flow和Retrofit,可以将网络请求抽象为一个数据流。这样我们可以使用Flow的各种操作符来处理网络请求的结果。
/**
* 仓库层 Repository 基类
*
*/
open class BaseRepository {
//我们定义了一个request函数,它接受一个名为requestBlock的lambda表达式作为参数。
//这个lambda表达式接受一个FlowCollector<T>类型的参数,并且没有返回值(Unit)。
//requestBlock内部,你可以调用FlowCollector的emit方法来发射元素。
protected fun <T> request(
requestBlock: suspend (FlowCollector<T>) -> Unit)
: Flow<T> {
return flow(requestBlock).flowOn(Dispatchers.IO)
}
}
//请求密码:emit将数据发射到catch或者collect上面
suspend fun getPwd() = request<String> {
var map = HashMap<String, String>()
map.put("xxx", "xxx")
mApi.getPwd(map).run {
it.emit(this)
}
}
本篇文章就介绍到这里~,下一篇文章:Android 从0搭建初始化MVVM项目框架(二):添加版本依赖管理、分包分模块、组件化Aroute