Android 从0搭建初始化MVVM项目框架(一):如何搭建属于自己的项目框架?如何创建BaseActivity?如何整合viewbinding?如何整合网络框架?

目录

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()
}
  1. 在onCreate方法中,通过调用抽象方法createVB()来创建View Binding实例,并将其赋值给dataBinding变量。这意味着每个继承BaseActivity的子类都需要实现createVB()方法,以返回对应布局的View Binding实例。
  2. 为什么会有var dataBinding: VB:类型为泛型VB?这个泛型VB是继承BaseActivity的子类需要指定的,因为Base是父类,他必须提供一些通用的东西。
  3. 定义了四个抽象方法: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

网络框架就不用多说了,项目必备。

  1. 添加依赖
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)
    }
}
  1. 创建接口
interface HomeApiService {
    
    
    /**
     * 请求密码
     */
    @GET("/xxxx")
    suspend fun getPwd(@QueryMap params: Map<String, String>): String

}
  1. 在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

猜你喜欢

转载自blog.csdn.net/qq_40853919/article/details/143118035