Kotlin

08 项目架构-MVVM-1

2021-11-12  本文已影响0人  凤邪摩羯

componentization-mvvm-kotlin 是一个Android工程框架,所使用技术栈为:组件化、Kotlin、MVVM、Jetpack、Repository、Kotlin-Coroutine-Flow,本框架既是一个可以开箱即用的工程框架基础层,也是一个很好的学习资源,文档下面会对框架中所使用的一些核心技术进行阐述。该框架作为个人技术积累的产物。

框架图示

谷歌 Android 团队 Jetpack 视图模型:

img2.png

模块

组件化相关

组件初始化

  为了更好的代码隔离与解耦,在特定组件内使用的SDK及三方库,应该只在该组件内依赖,不应该让该组件的特定SDK及三方库的API暴露给其他不需要用的组件。有一个问题就出现了,SDK及三方库常常需要手动去初始化,而且一般都需要在项目一启动(即 Application 中)初始化,但是一个项目肯定只能有一个自定义的 Application,该项目中的自定义 Applicationlib_base 模块中,并且也是在 lib_base 模块中的清单文件中声明的,那其他组件该如何初始化呢?带着这个问题我们一起来深入研究下。

常见的组件初始化解决方案:

在我的了解范围内,目前有两种最为常见的解决方案:

资源命名冲突

  在组件化方案中,资源命名冲突是一个比较严重的问题,由于在打包时会进行资源的合并,如果两个模块中有两个相同名字的文件,那么最后只会保留一份,如果不知道这个问题的小伙伴,在遇到这个问题时肯定是一脸懵逼的状态。问题既然已经出现,那我们就要去解决,解决办法就是每个组件都用固定的命名前缀,这样就不会出现两个相同的文件的现象了,我们可以在 build.gradle 配置文件中去配置前缀限定,如果不按该前缀进行命名,AS 就会进行警告提示,配置如下:

android {
    resourcePrefix "前缀_"
}

组件划分

  其实组件的划分一直是一个比较难的部分,这里其实也给不到一些非常适合的建议,看是看具体项目而定。

  关于基础组件通常要以独立可直接复用的角度出现,比如网络模块、二维码识别模块等。

  关于业务组件,业务组件一般可以进行单独调试,也就是可以作为 app 运行,这样才能发挥组件化的一大用处,当项目越来越大,业务组件越来越多时,编译耗时将会是一个非常棘手的问题,但是如果每个业务模块都可以进行的单独调试,那就大大减少了编译时间,同时,开发人员也不需要关注其他组件。

  关于公共模块,lib_base 放一些基础性代码,属于框架基础层,不应该和项目业务有牵扯,而和项目业务相关的公共部分则应该放在 lib_common 中,不要污染 lib_base

依赖版本控制

  组件化常见的一个问题就是依赖版本,每个组件都有可能自己的依赖库,那我们应该统一管理各种依赖库及其版本,使项目所有使用的依赖都是同一个版本,而不是不同版本。本项目中使用 buildSrc 中的几个kt文件进行依赖版本统一性的管理,及其项目的一些配置。

MVVM相关

项目使用的三方库及其简单示例和资料

Kotlin协程

关于 Kotlin 协程,是真的香,具体教程可以看我的一篇文章:

Flow 类似于 RxJava,它也有一系列的操作符,资料:

PermissionX

PermissionX 是郭霖的一个权限申请框架
使用方式:

PermissionX.init(this)
     .permissions("需要申请的权限")
     .request { allGranted, grantedList, deniedList -> }

资料:

GitHub: https://github.com/guolindev/PermissionX

EventBus APT

事件总线这里选择的还是 EventBus,也有很多比较新的事件总线框架,还是选择了这个直接上手的
在框架内我对 EventBus 进行了基类封装,自动注册和解除注册,在需要注册的类上添加 @EventBusRegister 注解即可,无需关心内存泄漏及没及时解除注册的情况,基类里已经做了处理

@EventBusRegister
class MainActivity : AppCompatActivity() {}

很多使用 EventBus 的开发者其实都没有发现 APT 的功能,这是 EventBus3.0 的重大更新,使用 EventBus APT 可以在编译期生成订阅类,这样就可以避免使用低效率的反射,很多人不知道这个更新,用着3.0的版本,实际上却是2.0的效率。
项目中已经在各模块中开启了 EventBus APTEventBus 会在编译器对各模块生成订阅类,需要我们手动编写代码去注册这些订阅类:

// 在APP壳的AppApplication类中
EventBus
     .builder()
     .addIndex("各模块生成的订阅类的实例 类名在base_module.gradle脚本中进行了设置 比如 module_home 生成的订阅类就是 module_homeIndex")
     .installDefaultEventBus()

屏幕适配 AndroidAutoSize

屏幕适配使用的是 JessYan 大佬的 今日头条屏幕适配方案终极版

GitHub: https://github.com/JessYanCoding/AndroidAutoSize

使用方式:

// 在清单文件中声明
<manifest>
    <application> 
    // 主单位使用dp 没设置副单位
        <meta-data
            android:name="design_width_in_dp"
            android:value="360"/>
        <meta-data
            android:name="design_height_in_dp"
            android:value="640"/>           
     </application>           
</manifest>

// 默认是以竖屏的宽度为基准进行适配
// 如果是横屏项目要适配Pad(Pad适配尽量使用两套布局 因为手机和Pad屏幕宽比差距很大 无法完美适配)
<manifest>
    <application>            
    // 以高度为基准进行适配 (还需要手动代码设置以高度为基准进行适配) 目前以高度适配比宽度为基准适配 效果要好
        <meta-data
            android:name="design_height_in_dp"
            android:value="400"/>           
     </application>           
</manifest>

// 在Application 中设置
// 屏幕适配 AndroidAutoSize 以横屏高度为基准进行适配
AutoSizeConfig.getInstance().isBaseOnWidth = false

ARoute

ARoute 是阿里巴巴的一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

使用方式:

// 1.在需要进行路由跳转的Activity或Fragment上添加 @Route 注解
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

// 2.发起路由跳转
ARouter.getInstance()
    .build("目标路由地址")
    .navigation()
    
// 3.携带参数跳转
ARouter.getInstance()
    .build("目标路由地址")
    .withLong("key1", 666L)
    .withString("key3", "888")
    .withObject("key4", new Test("Jack", "Rose"))
    .navigation()

// 4.接收参数
@Route(path = RouteUrl.MainActivity2)
class MainActivity : AppCompatActivity() {

    // 通过name来映射URL中的不同参数
    @Autowired(name = "key")
    lateinit var name: String
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        // ARouter 依赖注入 ARouter会自动对字段进行赋值,无需主动获取
        ARouter.getInstance().inject(this)
    }
}

// 5.获取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();

资料:

官方文档:https://github.com/alibaba/ARouter

ViewBinding

通过视图绑定功能,可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
在大多数情况下,视图绑定会替代 findViewById

使用方式:

按模块启用ViewBinding

// 模块下的build.gradle文件
android {
    // 开启ViewBinding
    // 高版本AS
    buildFeatures {
        viewBinding = true
    }
    // 低版本AS 最低3.6
    viewBinding {
        enabled = true
    }
}

ActivityViewBinding 的使用

// 之前设置视图的方法
setContentView(R.layout.activity_main)

// 使用ViewBinding后的方法
val mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)

// ActivityMainBinding类是根据布局自动生成的 如果没有请先build一下项目
// ViewBinding会将控件id转换为小驼峰命名法,所以为了保持一致规范,在xml里声明id时也请使用小驼峰命名法
// 比如你有一个id为mText的控件,可以这样使用
mBinding.mText.text = "ViewBinding"

FragmentViewBinding 的使用

// 原来的写法
return inflater.inflate(R.layout.fragment_blank, container, false)

// 使用ViewBinding的写法
mBinding = FragmentBlankBinding.inflate(inflater)
return mBinding.root

资料:

官方文档: https://developer.android.com/topic/libraries/view-binding

CSDN: https://blog.csdn.net/u010976213/article/details/104501830?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5

ViewModel

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

使用方式:

class MainViewModel : ViewModel(){}

class MainActivity : AppCompatActivity() {
        // 获取无参构造的ViewModel实例
    val mViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}

资料:

官方文档: https://developer.android.com/topic/libraries/architecture/viewmodel

Android ViewModel,再学不会你砍我: https://juejin.im/post/6844903919064186888

LiveData

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 ActivityFragmentService)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者

LiveData 分为可变值的 MutableLiveData 和不可变值的 LiveData

常用方法:

fun test() {
        val liveData = MutableLiveData<String>()
        // 设置更新数据源
        liveData.value = "LiveData"
        // 将任务发布到主线程以设置给定值
        liveData.postValue("LiveData")
        // 获取值
        val value = liveData.value
        // 观察数据源更改(第一个参数应是owner:LifecycleOwner 比如实现了LifecycleOwner接口的Activity)
        liveData.observe(this, {
            // 数据源更改后触发的逻辑
        })
    }

资料:

官方文档: https://developer.android.com/topic/libraries/architecture/livedata

Lifecycle

Lifecycle 是一个类,用于存储有关组件(如 ActivityFragment)的生命周期状态的信息,并允许其他对象观察此状态。LifecycleOwner 是单一方法接口,表示类具有 Lifecycle。它具有一种方法(即 getLifecycle()),该方法必须由类实现。实现 LifecycleObserver 的组件可与实现 LifecycleOwner 的组件无缝协同工作,因为所有者可以提供生命周期,而观察者可以注册以观察生命周期。

资料:

官方文档: https://developer.android.com/topic/libraries/architecture/lifecycle

Hilt

HiltAndroid 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。执行手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。

Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法。Hilt 在热门 DIDagger 的基础上构建而成,因而能够受益于 Dagger 的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。

资料:

目前官方文档还没有更新正式版的,还是 alpha 版本的文档:使用 Hilt 实现依赖项注入

DaggerHilt 文档目前是最新的:Dagger-Hilt

上一篇下一篇

猜你喜欢

热点阅读