├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── multidexKeep.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── quyunshuo │ │ └── androidbaseframemvvm │ │ └── app │ │ └── AppApplication.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values │ └── strings.xml │ └── xml │ └── network_security_config.xml ├── base_lib.gradle ├── base_module.gradle ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── com │ └── quyunshuo │ └── androidbaseframemvvm │ └── buildsrc │ ├── DependencyConfig.kt │ ├── ProjectBuildConfig.kt │ └── SDKKeyConfig.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img └── img2.png ├── lib_base ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── quyunshuo │ │ └── androidbaseframemvvm │ │ └── base │ │ ├── BaseApplication.kt │ │ ├── app │ │ ├── ActivityLifecycleCallbacksImpl.kt │ │ ├── ApplicationLifecycle.kt │ │ └── LoadModuleProxy.kt │ │ ├── constant │ │ └── VersionStatus.kt │ │ ├── ktx │ │ ├── ActivityKtx.kt │ │ ├── EditTextKtx.kt │ │ ├── LifecycleOwnerKtx.kt │ │ ├── NetUtils.kt │ │ ├── PopupWindowKtx.kt │ │ ├── SizeUnitKtx.kt │ │ ├── VideoViewKtx.kt │ │ ├── ViewKtx.kt │ │ ├── ViewModelKtx.kt │ │ └── ViewPager2Ktx.kt │ │ ├── mvvm │ │ ├── m │ │ │ └── BaseRepository.kt │ │ ├── v │ │ │ ├── BaseFrameActivity.kt │ │ │ ├── BaseFrameFragment.kt │ │ │ └── FrameView.kt │ │ └── vm │ │ │ ├── BaseViewModel.kt │ │ │ └── EmptyViewModel.kt │ │ ├── utils │ │ ├── ActivityStackManager.kt │ │ ├── AndroidBugFixUtils.kt │ │ ├── AppUtils.kt │ │ ├── BarUtils.java │ │ ├── ClipboardUtils.kt │ │ ├── CoilGIFImageLoader.kt │ │ ├── DateUtils.kt │ │ ├── EventBusUtils.kt │ │ ├── ForegroundBackgroundHelper.kt │ │ ├── ProcessUtils.kt │ │ ├── RegisterEventBus.kt │ │ ├── ResourcesFun.kt │ │ ├── SpUtils.kt │ │ ├── SpannableStringUtils.java │ │ ├── StateLayoutEnum.kt │ │ ├── ThreadUtils.kt │ │ ├── ToastUtils.kt │ │ ├── Utils.kt │ │ ├── network │ │ │ ├── AutoRegisterNetListener.kt │ │ │ ├── NetworkCallbackImpl.kt │ │ │ ├── NetworkStateChangeListener.kt │ │ │ ├── NetworkStateClient.kt │ │ │ └── NetworkTypeEnum.kt │ │ └── status │ │ │ ├── ViewStatusHelper.kt │ │ │ └── imp │ │ │ └── BaseFrameViewStatusHelperImp.kt │ │ └── view │ │ └── OnSingleClickListener.kt │ └── res │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── lib_common ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── quyunshuo │ │ └── androidbaseframemvvm │ │ └── common │ │ ├── CommonApplication.kt │ │ ├── constant │ │ ├── NetBaseUrlConstant.kt │ │ ├── RouteKey.kt │ │ ├── RouteUrl.kt │ │ └── SpKey.kt │ │ ├── di │ │ └── DINetworkModule.kt │ │ ├── helper │ │ ├── ExceptionHandler.kt │ │ ├── ResponseCodeEnum.kt │ │ └── ResponseException.kt │ │ └── ui │ │ ├── BaseActivity.kt │ │ ├── BaseFragment.kt │ │ └── BaseFragmentStateAdapter.kt │ └── res │ └── values │ └── colors.xml ├── module_home ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── quyunshuo │ │ └── module │ │ └── home │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── quyunshuo │ │ │ └── module │ │ │ └── home │ │ │ ├── activity │ │ │ ├── HomeRepository.kt │ │ │ ├── HomeViewModel.kt │ │ │ ├── InternalPagerActivity.kt │ │ │ └── MainActivity.kt │ │ │ ├── di │ │ │ └── DIHomeNetServiceModule.kt │ │ │ ├── fragment │ │ │ ├── InternalFragment.kt │ │ │ ├── InternalRepository.kt │ │ │ └── InternalViewModel.kt │ │ │ └── net │ │ │ └── HomeApiService.kt │ └── res │ │ ├── layout │ │ ├── home_activity_internal_layout.xml │ │ ├── home_activity_main.xml │ │ └── home_fragment_internal_layout.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── quyunshuo │ └── module │ └── home │ └── ExampleUnitTest.kt └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | .idea/* 45 | 46 | # Keystore files 47 | # Uncomment the following line if you do not want to check your keystore files in. 48 | #*.jks 49 | 50 | # External native build folder generated in Android Studio 2.2 and later 51 | .externalNativeBuild 52 | 53 | # Google Services (e.g. APIs or Firebase) 54 | google-services.json 55 | 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

AndroidBaseFrameMVVM 🐽

2 | 3 | >   **AndroidBaseFrameMVVM** 是一个Android工程框架,所使用技术栈为:**组件化、Kotlin、MVVM、Jetpack、Repository、Kotlin-Coroutine-Flow**,本框架既是一个可以开箱即用的工程框架基础层,也是一个很好的学习资源,文档下面会对框架中所使用的一些核心技术进行阐述。该框架作为个人技术积累的产物,会一直更新维护,如果有技术方面的谈论或者框架中的错误点,可以在 **GitHub** 上提 **Issues**,我会及时进行回应。希望这个框架项目能给大家带来帮助,喜欢可以Start🌟。 4 | > 5 | >   项目地址:[AndroidBaseFrameMVVM](https://github.com/Quyunshuo/AndroidBaseFrameMVVM) 6 | 7 | ## Demo 8 | 9 | ​ 以鸿洋大神的玩安卓开放Api做了简单的页面示例,仓库地址:[WanAndroidMVVM](https://github.com/Quyunshuo/WanAndroidMVVM) 10 | 11 | ## 框架图示 12 | 13 | **谷歌 Android 团队 Jetpack 视图模型:** 14 | 15 |

16 | 17 | ## 模块 18 | 19 | - **app:** 20 | 21 | **app壳** 工程,是依赖所有组件的壳,该模块不应该包含任何代码,它只作为一个空壳存在,由于项目中使用了EventBusAPT技术,需要索引到各业务组件的对应的APT生成类,所以在 **app壳** 内有这一部分的代码。 22 | 23 | 24 | - **buildSrc:** 25 | 26 | 这是一个特殊的文件夹,负责项目的构建,里面存放着一些项目构建时用到的东西,比如项目配置,依赖。这里面还是存放 **Gradle** 插件的地方,一些自定义的 **Gradle** 的插件都需要放在此处。 27 | 28 | - **lib_base:** 29 | 30 | 项目的基础公共模块,存放着各种基类封装、对远程库的依赖、以及工具类、三方库封装,该组件是和项目业务无关的,和项目业务相关的公共部分需要放在 **lib_common** 中。 31 | 32 | - **lib_common:** 33 | 34 | 项目的业务公共模块,这里面存放着项目里各个业务组件的公共部分,还有一些项目特定需要的一些文件等,该组件是和项目业务有关系的。 35 | 36 | ## 组件化相关 37 | 38 | ### 组件初始化 39 | 40 | >   为了更好的代码隔离与解耦,在特定组件内使用的SDK及三方库,应该只在该组件内依赖,不应该让该组件的特定SDK及三方库的API暴露给其他不需要用的组件。有一个问题就出现了,SDK及三方库常常需要手动去初始化,而且一般都需要在项目一启动(即 **Application** 中)初始化,但是一个项目肯定只能有一个自定义的 **Application**,该项目中的自定义 **Application** 在 **lib_base** 模块中,并且也是在 **lib_base** 模块中的清单文件中声明的,那其他组件该如何初始化呢?带着这个问题我们一起来深入研究下。 41 | 42 | **常见的组件初始化解决方案:** 43 | 44 | 在我的了解范围内,目前有两种最为常见的解决方案: 45 | 46 | - **面向接口编程 + 反射扫描实现类:** 47 | 48 |   该方案是基于接口编程,自定义 **Application** 去实现一个自定义的接口(**interface**),这个接口中定一些和 **Application** 生命周期相对应的抽象方法及其他自定义的抽象方法,每个组件去编写一个实现类,该实现类就类似于一个假的自定义 **Application**,然后在真正的自定义 **Application** 中去通过反射去动态查找当前运行时环境中所有该接口的实现类,并且去进行实例化,然后将这些实现类收集到一个集合中,在 **Application** 的对应声明周期方法中去逐一调用对应方法,以实现各实现类能够和 **Application** 生命周期相同步,并且持有 **Application** 的引用及 **context** 上下文对象,这样我们就可以在组件内模拟 **Application** 的生命周期并初始化SDK和三方库。使用反射还需要做一些异常的处理。该方案是我见过的最常见的方案,在一些商业项目中也见到过。 49 | 50 | - **面向接口编程 + meta-data + 反射:** 51 | 52 |   该方案的后半部分也是和第一种方法一样,通过接口编程实现 **Application** 的生命周期同步,其实这一步是避免不了的,在我的方案中,后半部分也是这样实现的。不同的是前半部分,也就是如何找到接口的实现类,该方案使用的是 **AndroidManifest** 的 **meta-data** 标签,通过每个组件内的 **AndroidManifest** 内去声明一个 **meta-data** 标签,包含该组件实现类的信息,然后在 **Application** 中去找到这些配置信息,然后通过反射去创建这些实现类的实例,再将它们收集到一个集合中,剩下的操作基本相同了。该方案和第一种方案一样都需要处理很多的异常。这种方案我在一些开源项目中见到过,个人认为过于繁琐,还要处理很多的异常。 53 | 54 | **本项目中所使用的方案:** 55 | 56 | - **面向接口编程 + Java的SPI机制(ServiceLoader)+AutoService:** 57 | 58 |   先来认识下 **Java** 的 **SPI** 机制:面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。**JavaSPI** 就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似 **IOC** 的思想,将装配的控制权移到了程序之外。这段话也是我复制的别人的,听起来很懵逼,大致意思就是我们可以通过 **SPI** 机制将实现类暴露出去。关于如何使用 **SPI**,这里不在陈述,总之是我们在各组件内通过 **SPI** 去将实现类暴露出去,在 **Application** 中我们通过 **Java** 提供的 **SPI** **API** 去获取这些暴露的服务,这样我们就拿到了这些类的实例,剩下的步骤就和上面的方案一样了,通过一个集合遍历实现类调用其相应的方法完成初始化的工作。由于使用 **SPI** 需要在每个模块创建对应的文件配置,这比较麻烦,所以我们使用 **Google** 的 **AutoService** 库来帮助我们自动创建这些配置文件,使用方式也非常的简单,就是在实现类添加一个 **AutoService** 注解。本框架中的核心类是这几个:**lib_base-LoadModuleProxy**、**lib_base-ApplicationLifecycle**。这种方案是我请教的一个米哈游的大佬,这位大佬告诉我在组件化中组件的初始化可以使用 **ServiceLoader** 来做,于是我就去研究了下,最后发现这种方案还不错,比前面提到的两种方案都要简单、安全。 59 | 60 | ### 资源命名冲突 61 | 62 |   在组件化方案中,资源命名冲突是一个比较严重的问题,由于在打包时会进行资源的合并,如果两个模块中有两个相同名字的文件,那么最后只会保留一份,如果不知道这个问题的小伙伴,在遇到这个问题时肯定是一脸懵逼的状态。问题既然已经出现,那我们就要去解决,解决办法就是每个组件都用固定的命名前缀,这样就不会出现两个相同的文件的现象了,我们可以在 **build.gradle** 配置文件中去配置前缀限定,如果不按该前缀进行命名,**AS** 就会进行警告提示,配置如下: 63 | 64 | ```Groovy 65 | android { 66 | resourcePrefix "前缀_" 67 | } 68 | ``` 69 | 70 | ### 组件划分 71 | 72 |   其实组件的划分一直是一个比较难的部分,这里其实也给不到一些非常适合的建议,看是看具体项目而定。 73 | 74 |   关于基础组件通常要以独立可直接复用的角度出现,比如网络模块、二维码识别模块等。 75 | 76 |   关于业务组件,业务组件一般可以进行单独调试,也就是可以作为 **app** 运行,这样才能发挥组件化的一大用处,当项目越来越大,业务组件越来越多时,编译耗时将会是一个非常棘手的问题,但是如果每个业务模块都可以进行的单独调试,那就大大减少了编译时间,同时,开发人员也不需要关注其他组件。 77 | 78 |   关于公共模块,**lib_base** 放一些基础性代码,属于框架基础层,不应该和项目业务有牵扯,而和项目业务相关的公共部分则应该放在 **lib_common** 中,不要污染 **lib_base**。 79 | 80 | ### 依赖版本控制 81 | 82 |   组件化常见的一个问题就是依赖版本,每个组件都有可能自己的依赖库,那我们应该统一管理各种依赖库及其版本,使项目所有使用的依赖都是同一个版本,而不是不同版本。本项目中使用 **buildSrc** 中的几个kt文件进行依赖版本统一性的管理,及其项目的一些配置。 83 | 84 | ## **MVVM相关** 85 | 86 | * **MVVM** 采用 **Jetpack** 组件 + **Repository** 设计模式 实现,所使用的 **Jetpack** 并不是很多,像 **DataBinding**、**Paging 3**、**Room** 等并没有使用,如果需要可以添加。采用架构模式目的就是为了解偶代码,对代码进行分层,各模块各司其职,所以既然使用了架构模式那就要遵守好规范。 87 | * **Repository** 仓库层负责数据的提供,**ViewModel** 无需关心数据的来源,**Repository** 内避免使用 **LiveData**,框架里使用了 **Kotlin** 协程的 **Flow** 进行处理请求或访问数据库,**Repository** 的函数会返回一个 **Flow** 给 **ViewModel** 的调用函数,**Flow** 上游负责提供数据,下游也就是 **ViewModel** 获取到数据使用 **LiveData** 进行存储,**View** 层订阅 **LiveData**,实现数据驱动视图 88 | * 三者的依赖都是单向依赖,**View** -> **ViewModel** -> **Repository** 89 | 90 | ## 项目使用的三方库及其简单示例和资料 91 | 92 | * [Kotlin](https://github.com/JetBrains/kotlin) 93 | * [Kotlin-Coroutines-Flow](https://github.com/JetBrains/kotlin) 94 | * [Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) 95 | * [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) 96 | * [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) 97 | * [ViewBinding](https://developer.android.com/topic/libraries/view-binding) 98 | * [Hilt](https://developer.android.com/jetpack/androidx/releases/hilt) 99 | * [OkHttp](https://github.com/square/okhttp):网络请求 100 | * [Retrofit](https://github.com/square/retrofit):网络请求 101 | * [MMKV](https://github.com/Tencent/MMKV):腾讯基于 **mmap** 内存映射的 **key-value** 本地存储组件 102 | * [Coil](https://github.com/coil-kt/coil):一个 Android 图片加载库,通过 Kotlin 协程的方式加载图片 103 | * [ARoute](https://github.com/alibaba/ARouter):阿里用于帮助 **Android App** 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦 104 | * [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper):一个强大并且灵活的 **RecyclerViewAdapter** 105 | * [EventBus](https://github.com/greenrobot/EventBus):适用于 **Android** 和 **Java** 的发布/订阅事件总线 106 | * [Bugly](https://bugly.qq.com/v2/index):腾讯异常上报及热更新(只集成了异常上报) 107 | * [PermissionX](https://github.com/guolindev/PermissionX):郭霖权限请求框架 108 | * [LeakCanary](https://github.com/square/leakcanary):**Android** 的内存泄漏检测库 109 | * [AndroidAutoSize](https://github.com/JessYanCoding/AndroidAutoSize):**JessYan** 大佬的 今日头条屏幕适配方案终极版 110 | 111 | ### **Kotlin协程** 112 | 113 | 关于 **Kotlin 协程**,是真的香,具体教程可以看我的一篇文章: 114 | 115 | - [万字长文 - Kotlin 协程进阶](https://juejin.cn/post/6950616789390721037) 116 | 117 | **Flow** 类似于 **RxJava**,它也有一系列的操作符,资料: 118 | 119 | - [Google 推荐在 MVVM 架构中使用 Kotlin Flow: ](https://juejin.im/post/6854573211930066951) 120 | - [即学即用Kotlin - 协程:](https://juejin.im/post/6854573211418361864) 121 | - [Kotlin Coroutines Flow 系列(1-5):](https://juejin.im/post/6844904057530908679) 122 | 123 | ### **PermissionX** 124 | 125 | **PermissionX** 是郭霖的一个权限申请框架 126 | **使用方式:** 127 | 128 | ``` 129 | PermissionX.init(this) 130 | .permissions("需要申请的权限") 131 | .request { allGranted, grantedList, deniedList -> } 132 | ``` 133 | 134 | **资料:** 135 | 136 | GitHub: [https://github.com/guolindev/PermissionX](https://github.com/guolindev/PermissionX) 137 | 138 | ### EventBus APT 139 | 140 | 事件总线这里选择的还是 **EventBus**,也有很多比较新的事件总线框架,还是选择了这个直接上手的 141 | 在框架内我对 **EventBus** 进行了基类封装,自动注册和解除注册,在需要注册的类上添加 **@EventBusRegister** 注解即可,无需关心内存泄漏及没及时解除注册的情况,基类里已经做了处理 142 | 143 | ```kotlin 144 | @EventBusRegister 145 | class MainActivity : AppCompatActivity() {} 146 | ``` 147 | 148 | 很多使用 **EventBus** 的开发者其实都没有发现 **APT** 的功能,这是 **EventBus3.0** 的重大更新,使用 **EventBus APT** 可以在编译期生成订阅类,这样就可以避免使用低效率的反射,很多人不知道这个更新,用着**3.0**的版本,实际上却是**2.0**的效率。 149 | 项目中已经在各模块中开启了 **EventBus APT**,**EventBus** 会在编译器对各模块生成订阅类,需要我们手动编写代码去注册这些订阅类: 150 | 151 | ```kotlin 152 | // 在APP壳的AppApplication类中 153 | EventBus 154 | .builder() 155 | .addIndex("各模块生成的订阅类的实例 类名在base_module.gradle脚本中进行了设置 比如 module_home 生成的订阅类就是 module_homeIndex") 156 | .installDefaultEventBus() 157 | ``` 158 | 159 | ### 屏幕适配 AndroidAutoSize 160 | 161 | 屏幕适配使用的是 **JessYan** 大佬的 今日头条屏幕适配方案终极版 162 | 163 | GitHub: [https://github.com/JessYanCoding/AndroidAutoSize](https://github.com/JessYanCoding/AndroidAutoSize) 164 | 165 | **使用方式:** 166 | 167 | ``` 168 | // 在清单文件中声明 169 | 170 | 171 | // 主单位使用dp 没设置副单位 172 | 175 | 178 | 179 | 180 | 181 | // 默认是以竖屏的宽度为基准进行适配 182 | // 如果是横屏项目要适配Pad(Pad适配尽量使用两套布局 因为手机和Pad屏幕宽比差距很大 无法完美适配) 183 | 184 | 185 | // 以高度为基准进行适配 (还需要手动代码设置以高度为基准进行适配) 目前以高度适配比宽度为基准适配 效果要好 186 | 189 | 190 | 191 | 192 | // 在Application 中设置 193 | // 屏幕适配 AndroidAutoSize 以横屏高度为基准进行适配 194 | AutoSizeConfig.getInstance().isBaseOnWidth = false 195 | ``` 196 | 197 | ### ARoute 198 | 199 | **ARoute** 是阿里巴巴的一个用于帮助 **Android App** 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦 200 | 201 | **使用方式:** 202 | 203 | ``` 204 | // 1.在需要进行路由跳转的Activity或Fragment上添加 @Route 注解 205 | @Route(path = "/test/activity") 206 | public class YourActivity extend Activity { 207 | ... 208 | } 209 | 210 | // 2.发起路由跳转 211 | ARouter.getInstance() 212 | .build("目标路由地址") 213 | .navigation() 214 | 215 | // 3.携带参数跳转 216 | ARouter.getInstance() 217 | .build("目标路由地址") 218 | .withLong("key1", 666L) 219 | .withString("key3", "888") 220 | .withObject("key4", new Test("Jack", "Rose")) 221 | .navigation() 222 | 223 | // 4.接收参数 224 | @Route(path = RouteUrl.MainActivity2) 225 | class MainActivity : AppCompatActivity() { 226 | 227 | // 通过name来映射URL中的不同参数 228 | @Autowired(name = "key") 229 | lateinit var name: String 230 | 231 | override fun onCreate(savedInstanceState: Bundle?) { 232 | super.onCreate(savedInstanceState) 233 | setContentView(mBinding.root) 234 | // ARouter 依赖注入 ARouter会自动对字段进行赋值,无需主动获取 235 | ARouter.getInstance().inject(this) 236 | } 237 | } 238 | 239 | // 5.获取Fragment 240 | Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation(); 241 | ``` 242 | 243 | **资料:** 244 | 245 | 官方文档:[https://github.com/alibaba/ARouter](https://github.com/alibaba/ARouter) 246 | 247 | ### ViewBinding 248 | 249 | 通过视图绑定功能,可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 **XML** 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 **ID** 的所有视图的直接引用。 250 | 在大多数情况下,视图绑定会替代 **findViewById** 251 | 252 | **使用方式:** 253 | 254 | 按模块启用**ViewBinding** 255 | 256 | ```groovy 257 | // 模块下的build.gradle文件 258 | android { 259 | // 开启ViewBinding 260 | // 高版本AS 261 | buildFeatures { 262 | viewBinding = true 263 | } 264 | // 低版本AS 最低3.6 265 | viewBinding { 266 | enabled = true 267 | } 268 | } 269 | ``` 270 | 271 | **Activity** 中 **ViewBinding** 的使用 272 | 273 | ```kotlin 274 | // 之前设置视图的方法 275 | setContentView(R.layout.activity_main) 276 | 277 | // 使用ViewBinding后的方法 278 | val mBinding = ActivityMainBinding.inflate(layoutInflater) 279 | setContentView(mBinding.root) 280 | 281 | // ActivityMainBinding类是根据布局自动生成的 如果没有请先build一下项目 282 | // ViewBinding会将控件id转换为小驼峰命名法,所以为了保持一致规范,在xml里声明id时也请使用小驼峰命名法 283 | // 比如你有一个id为mText的控件,可以这样使用 284 | mBinding.mText.text = "ViewBinding" 285 | ``` 286 | 287 | **Fragment** 中 **ViewBinding** 的使用 288 | 289 | ```kotlin 290 | // 原来的写法 291 | return inflater.inflate(R.layout.fragment_blank, container, false) 292 | 293 | // 使用ViewBinding的写法 294 | mBinding = FragmentBlankBinding.inflate(inflater) 295 | return mBinding.root 296 | ``` 297 | 298 | **资料:** 299 | 300 | 官方文档: [https://developer.android.com/topic/libraries/view-binding](https://developer.android.com/topic/libraries/view-binding) 301 | 302 | 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](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) 303 | 304 | ### ViewModel 305 | 306 | **ViewModel** 类旨在以注重生命周期的方式存储和管理界面相关的数据。**ViewModel** 类让数据可在发生屏幕旋转等配置更改后继续留存。 307 | 308 | **使用方式:** 309 | 310 | ```kotlin 311 | class MainViewModel : ViewModel(){} 312 | 313 | class MainActivity : AppCompatActivity() { 314 | // 获取无参构造的ViewModel实例 315 | val mViewModel = ViewModelProvider(this).get(MainViewModel::class.java) 316 | } 317 | ``` 318 | 319 | **资料:** 320 | 321 | 官方文档: [https://developer.android.com/topic/libraries/architecture/viewmodel](https://developer.android.com/topic/libraries/architecture/viewmodel) 322 | 323 | Android ViewModel,再学不会你砍我: [https://juejin.im/post/6844903919064186888](https://juejin.im/post/6844903919064186888) 324 | 325 | ### LiveData 326 | 327 | **LiveData** 是一种可观察的数据存储器类。与常规的可观察类不同,**LiveData** 具有生命周期感知能力,意指它遵循其他应用组件(如 **Activity**、**Fragment** 或 **Service**)的生命周期。这种感知能力可确保 **LiveData** 仅更新处于活跃生命周期状态的应用组件观察者 328 | 329 | **LiveData** 分为可变值的 **MutableLiveData** 和不可变值的 **LiveData** 330 | 331 | **常用方法:** 332 | 333 | ```kotlin 334 | fun test() { 335 | val liveData = MutableLiveData() 336 | // 设置更新数据源 337 | liveData.value = "LiveData" 338 | // 将任务发布到主线程以设置给定值 339 | liveData.postValue("LiveData") 340 | // 获取值 341 | val value = liveData.value 342 | // 观察数据源更改(第一个参数应是owner:LifecycleOwner 比如实现了LifecycleOwner接口的Activity) 343 | liveData.observe(this, { 344 | // 数据源更改后触发的逻辑 345 | }) 346 | } 347 | ``` 348 | 349 | **资料:** 350 | 351 | 官方文档: [https://developer.android.com/topic/libraries/architecture/livedata](https://developer.android.com/topic/libraries/architecture/livedata) 352 | 353 | ### Lifecycle 354 | 355 | **Lifecycle** 是一个类,用于存储有关组件(如 **Activity** 或 **Fragment**)的生命周期状态的信息,并允许其他对象观察此状态。**LifecycleOwner** 是单一方法接口,表示类具有 **Lifecycle**。它具有一种方法(即 **getLifecycle()**),该方法必须由类实现。实现 **LifecycleObserver** 的组件可与实现 **LifecycleOwner** 的组件无缝协同工作,因为所有者可以提供生命周期,而观察者可以注册以观察生命周期。 356 | 357 | **资料:** 358 | 359 | 官方文档: [https://developer.android.com/topic/libraries/architecture/lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle) 360 | 361 | ### Hilt 362 | 363 | **Hilt** 是 **Android** 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。执行手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。 364 | 365 | **Hilt** 通过为项目中的每个 **Android** 类提供容器并自动管理其生命周期,提供了一种在应用中使用 **DI(依赖项注入)**的标准方法。**Hilt** 在热门 **DI** 库 **Dagger** 的基础上构建而成,因而能够受益于 **Dagger** 的编译时正确性、运行时性能、可伸缩性和 **Android Studio** 支持。 366 | 367 | **资料:** 368 | 369 | 目前官方文档还没有更新正式版的,还是 **alpha** 版本的文档:[使用 Hilt 实现依赖项注入](https://developer.android.com/training/dependency-injection/hilt-android) 370 | 371 | **Dagger** 的 **Hilt** 文档目前是最新的:[Dagger-Hilt](https://dagger.dev/hilt/) 372 | 373 | ### Coil 374 | 375 | **Coil** 是一个 Android 图片加载库,通过 Kotlin 协程的方式加载图片。特点如下: 376 | 377 | - **更快**: Coil 在性能上有很多优化,包括内存缓存和磁盘缓存,把缩略图存保存在内存中,循环利用 bitmap,自动暂停和取消图片网络请求等。 378 | - **更轻量级**: Coil 只有2000个方法(前提是你的 APP 里面集成了 OkHttp 和 Coroutines),Coil 和 Picasso 的方法数差不多,相比 Glide 和 Fresco 要轻量很多。 379 | - **更容易使用**: Coil 的 API 充分利用了 Kotlin 语言的新特性,简化和减少了很多样板代码。 380 | - **更流行**: Coil 首选 Kotlin 语言开发并且使用包含 Coroutines, OkHttp, Okio 和 AndroidX Lifecycles 在内最流行的开源库。 381 | 382 | **Coil** 名字的由来:取 **Co**routine **I**mage **L**oader 首字母得来。 383 | 384 | **资料:** 385 | 386 | 官方文档: [https://coil-kt.github.io/coil/](https://coil-kt.github.io/coil/) 387 | 388 | 三方库源码笔记(13)-可能是全网第一篇 Coil 的源码分析文章:[https://juejin.cn/post/6897872882051842061](https://juejin.cn/post/6897872882051842061) 389 | 390 | 【奇技淫巧】新的图片加载库?基于Kotlin协程的图片加载库——Coil:[https://juejin.cn/post/6844904159527829518](https://juejin.cn/post/6844904159527829518) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | //**************************************** 2 | //************ app 壳的配置文件 ************ 3 | //**************************************** 4 | 5 | import com.quyunshuo.androidbaseframemvvm.buildsrc.* 6 | 7 | plugins { 8 | alias(libs.plugins.application) 9 | alias(libs.plugins.kotlin) 10 | alias(libs.plugins.hilt) 11 | id "kotlin-kapt" 12 | } 13 | 14 | android { 15 | namespace 'com.quyunshuo.androidbaseframemvvm' 16 | compileSdk ProjectBuildConfig.compileSdkVersion 17 | 18 | defaultConfig { 19 | applicationId ProjectBuildConfig.applicationId 20 | minSdk ProjectBuildConfig.minSdkVersion 21 | targetSdk ProjectBuildConfig.targetSdkVersion 22 | versionCode ProjectBuildConfig.versionCode 23 | versionName ProjectBuildConfig.versionName 24 | 25 | testInstrumentationRunner DependencyConfig.AndroidX.AndroidJUnitRunner 26 | multiDexKeepProguard file("multidexKeep.pro") 27 | 28 | ndk { 29 | // 设置支持的SO库架构 30 | //abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' 31 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' 32 | } 33 | } 34 | 35 | // signingConfigs { 36 | // releaseConfig { 37 | // storeFile file('') 38 | // storePassword "" 39 | // keyAlias "" 40 | // keyPassword "" 41 | // } 42 | // } 43 | 44 | buildTypes { 45 | // 对应 ALPHA 版本 46 | debug { 47 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.ALPHA}\"" 48 | // signingConfig signingConfigs.releaseConfig 49 | minifyEnabled false 50 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 51 | } 52 | beta { 53 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.BETA}\"" 54 | // signingConfig signingConfigs.releaseConfig 55 | minifyEnabled false 56 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 57 | } 58 | release { 59 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.RELEASE}\"" 60 | // signingConfig signingConfigs.releaseConfig 61 | minifyEnabled false 62 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 63 | } 64 | } 65 | 66 | // 自定义打包apk的文件名 67 | android.applicationVariants.all { variant -> 68 | variant.outputs.all { output -> 69 | if (outputFileName != null && outputFileName.endsWith('.apk')) { 70 | outputFileName = "${ProjectBuildConfig.applicationId}" + 71 | "_${ProjectBuildConfig.versionCode}" + 72 | "(${ProjectBuildConfig.versionName})" + 73 | "_${variant.buildType.name}" + 74 | ".apk" 75 | } 76 | } 77 | } 78 | 79 | compileOptions { 80 | sourceCompatibility JavaVersion.VERSION_17 81 | targetCompatibility JavaVersion.VERSION_17 82 | } 83 | 84 | kotlinOptions { 85 | jvmTarget = '17' 86 | } 87 | } 88 | 89 | dependencies { 90 | implementation fileTree(dir: "libs", include: ["*.jar"]) 91 | 92 | if (!ProjectBuildConfig.isAppMode) { 93 | // 有业务组件时 把这个去掉 这里只是为了使用base里的依赖库 94 | implementation project(path: ':module_home') 95 | } else { 96 | implementation project(path: ':lib_common') 97 | } 98 | implementation DependencyConfig.JetPack.HiltCore 99 | 100 | kapt DependencyConfig.GitHub.AutoServiceAnnotations 101 | kapt DependencyConfig.JetPack.HiltApt 102 | } -------------------------------------------------------------------------------- /app/multidexKeep.pro: -------------------------------------------------------------------------------- 1 | -keep public class com.tencent.bugly.**{*;} -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # AGP 8.x 警告生成 2 | # Please add these rules to your existing keep rules in order to suppress warnings. 3 | # This is generated automatically by the Android Gradle plugin. 4 | -dontwarn dalvik.system.VMStack 5 | -dontwarn javax.lang.model.element.Element 6 | -dontwarn org.bouncycastle.jsse.BCSSLParameters 7 | -dontwarn org.bouncycastle.jsse.BCSSLSocket 8 | -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider 9 | -dontwarn org.conscrypt.Conscrypt$Version 10 | -dontwarn org.conscrypt.Conscrypt 11 | -dontwarn org.conscrypt.ConscryptHostnameVerifier 12 | -dontwarn org.openjsse.javax.net.ssl.SSLParameters 13 | -dontwarn org.openjsse.javax.net.ssl.SSLSocket 14 | -dontwarn org.openjsse.net.ssl.OpenJSSE -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 23 | 24 | 25 | 28 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/quyunshuo/androidbaseframemvvm/app/AppApplication.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.app 2 | 3 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 4 | import dagger.hilt.android.HiltAndroidApp 5 | import org.greenrobot.eventbus.EventBus 6 | 7 | /** 8 | * App壳 9 | * 10 | * @author Qu Yunshuo 11 | * @since 4/23/21 6:08 PM 12 | */ 13 | @HiltAndroidApp 14 | class AppApplication : BaseApplication() { 15 | 16 | override fun onCreate() { 17 | // 开启EventBusAPT,优化反射效率 当组件作为App运行时需要将添加的Index注释掉 因为找不到对应的类了 18 | EventBus 19 | .builder() 20 | // .addIndex(MainEventIndex()) 21 | .installDefaultEventBus() 22 | super.onCreate() 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidBaseFrameMVVM 3 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | android.bugly.qq.com 5 | 6 | -------------------------------------------------------------------------------- /base_lib.gradle: -------------------------------------------------------------------------------- 1 | //**************************************** 2 | //********* lib 模块的公共脚本配置 ********** 3 | //**************************************** 4 | 5 | import com.quyunshuo.androidbaseframemvvm.buildsrc.* 6 | 7 | android { 8 | compileSdkVersion ProjectBuildConfig.compileSdkVersion 9 | 10 | defaultConfig { 11 | minSdkVersion ProjectBuildConfig.minSdkVersion 12 | targetSdkVersion ProjectBuildConfig.targetSdkVersion 13 | versionCode ProjectBuildConfig.versionCode 14 | versionName ProjectBuildConfig.versionName 15 | 16 | consumerProguardFiles "consumer-rules.pro" 17 | 18 | ndk { 19 | // 设置支持的SO库架构 20 | //abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' 21 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' 22 | } 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_17 27 | targetCompatibility JavaVersion.VERSION_17 28 | } 29 | 30 | kotlinOptions { 31 | jvmTarget = "17" 32 | } 33 | 34 | buildTypes { 35 | // 对应 ALPHA 版本 36 | debug { 37 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.ALPHA}\"" 38 | minifyEnabled false 39 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 40 | } 41 | beta { 42 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.BETA}\"" 43 | minifyEnabled false 44 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 45 | } 46 | release { 47 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.RELEASE}\"" 48 | minifyEnabled false 49 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 50 | } 51 | } 52 | } 53 | 54 | kapt { 55 | arguments { 56 | arg("AROUTER_MODULE_NAME", project.getName()) 57 | } 58 | } -------------------------------------------------------------------------------- /base_module.gradle: -------------------------------------------------------------------------------- 1 | //**************************************** 2 | //******** module模块的公共脚本配置 ********* 3 | //**************************************** 4 | 5 | import com.quyunshuo.androidbaseframemvvm.buildsrc.* 6 | 7 | android { 8 | compileSdk ProjectBuildConfig.compileSdkVersion 9 | 10 | defaultConfig { 11 | minSdk ProjectBuildConfig.minSdkVersion 12 | targetSdk ProjectBuildConfig.targetSdkVersion 13 | versionCode ProjectBuildConfig.versionCode 14 | versionName ProjectBuildConfig.versionName 15 | testInstrumentationRunner DependencyConfig.AndroidX.AndroidJUnitRunner 16 | 17 | ndk { 18 | // 设置支持的SO库架构 19 | //abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' 20 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' 21 | } 22 | } 23 | 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_17 26 | targetCompatibility JavaVersion.VERSION_17 27 | } 28 | 29 | kotlinOptions { 30 | jvmTarget = "17" 31 | } 32 | 33 | buildFeatures { 34 | viewBinding = true 35 | } 36 | 37 | sourceSets { 38 | main { 39 | manifest.srcFile 'src/main/AndroidManifest.xml' 40 | java { 41 | //排除debug文件夹下的所有文件 42 | exclude 'debug/**' 43 | } 44 | } 45 | } 46 | 47 | buildTypes { 48 | // 对应 ALPHA 版本 49 | debug { 50 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.ALPHA}\"" 51 | minifyEnabled false 52 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 53 | } 54 | beta { 55 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.BETA}\"" 56 | minifyEnabled false 57 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 58 | } 59 | release { 60 | buildConfigField "String", "VERSION_TYPE", "\"${ProjectBuildConfig.Version.RELEASE}\"" 61 | minifyEnabled false 62 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 63 | } 64 | } 65 | } 66 | 67 | kapt { 68 | arguments { 69 | arg("AROUTER_MODULE_NAME", project.name) 70 | arg("eventBusIndex", "${ProjectBuildConfig.applicationId}.eventbus.index.${project.name}EventIndex") 71 | } 72 | } 73 | 74 | dependencies { 75 | implementation fileTree(dir: 'libs', include: ['*.jar']) 76 | 77 | api project(path: ':lib_common') 78 | 79 | testImplementation DependencyConfig.Android.Junit 80 | androidTestImplementation DependencyConfig.AndroidX.TestExtJunit 81 | androidTestImplementation DependencyConfig.AndroidX.TestEspresso 82 | implementation DependencyConfig.JetPack.HiltCore 83 | 84 | kapt DependencyConfig.GitHub.ARouteCompiler 85 | kapt DependencyConfig.GitHub.EventBusAPT 86 | kapt DependencyConfig.GitHub.AutoServiceAnnotations 87 | kapt DependencyConfig.JetPack.HiltApt 88 | kapt DependencyConfig.JetPack.LifecycleCompilerAPT 89 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.application) apply false 4 | alias(libs.plugins.library) apply false 5 | alias(libs.plugins.kotlin) apply false 6 | alias(libs.plugins.hilt) apply false 7 | } 8 | true // Needed to make the Suppress annotation work for the plugins block -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { `kotlin-dsl` } 2 | repositories { mavenCentral() } -------------------------------------------------------------------------------- /buildSrc/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /buildSrc/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.buildsrc 2 | 3 | /** 4 | * 项目依赖版本统一管理 5 | * 6 | * @author Qu Yunshuo 7 | * @since 4/24/21 4:00 PM 8 | */ 9 | object DependencyConfig { 10 | 11 | /** 12 | * 依赖版本号 13 | * 14 | * @author Qu Yunshuo 15 | * @since 4/24/21 4:01 PM 16 | */ 17 | object Version { 18 | 19 | // AndroidX-------------------------------------------------------------- 20 | const val AppCompat = "1.3.1" 21 | const val CoreKtx = "1.7.0" 22 | const val ConstraintLayout = "2.1.3" // 约束布局 23 | const val TestExtJunit = "1.1.2" 24 | const val TestEspresso = "3.3.0" 25 | const val ActivityKtx = "1.5.1" 26 | const val FragmentKtx = "1.5.2" 27 | const val MultiDex = "2.0.1" 28 | 29 | // Android--------------------------------------------------------------- 30 | const val Junit = "4.13" 31 | const val Material = "1.2.0" // 材料设计UI套件 32 | 33 | // Kotlin---------------------------------------------------------------- 34 | const val Kotlin = "1.6.21" 35 | const val Coroutines = "1.6.1" // 协程 36 | 37 | // JetPack--------------------------------------------------------------- 38 | const val Lifecycle = "2.4.1" // Lifecycle 39 | const val Hilt = "2.44" // DI框架-Hilt 40 | 41 | // GitHub---------------------------------------------------------------- 42 | const val OkHttp = "4.9.0" // OkHttp 43 | const val OkHttpInterceptorLogging = "4.9.1" // OkHttp 请求Log拦截器 44 | const val Retrofit = "2.9.0" // Retrofit 45 | const val RetrofitConverterGson = "2.9.0" // Retrofit Gson 转换器 46 | const val Gson = "2.8.7" // Gson 47 | const val MMKV = "1.2.9" // 腾讯 MMKV 替代SP 48 | const val AutoSize = "1.2.1" // 屏幕适配 49 | const val ARoute = "1.5.1" // 阿里路由 50 | const val ARouteCompiler = "1.5.1" // 阿里路由 APT 51 | const val RecyclerViewAdapter = "3.0.4" // RecyclerViewAdapter 52 | const val EventBus = "3.2.0" // 事件总线 53 | const val PermissionX = "1.4.0" // 权限申请 54 | const val LeakCanary = "2.7" // 检测内存泄漏 55 | const val AutoService = "1.0" // 自动生成SPI暴露服务文件 56 | const val Coil = "1.3.0" // Kotlin图片加载框架 57 | 58 | // 第三方SDK-------------------------------------------------------------- 59 | const val TencentBugly = "3.3.9" // 腾讯Bugly 异常上报 60 | const val TencentBuglyNative = "3.8.0" // Bugly native异常上报 61 | const val TencentTBSX5 = "43939" // 腾讯X5WebView 62 | } 63 | 64 | /** 65 | * AndroidX相关依赖 66 | * 67 | * @author Qu Yunshuo 68 | * @since 4/24/21 4:01 PM 69 | */ 70 | object AndroidX { 71 | const val AndroidJUnitRunner = "androidx.test.runner.AndroidJUnitRunner" 72 | const val AppCompat = "androidx.appcompat:appcompat:${Version.AppCompat}" 73 | const val CoreKtx = "androidx.core:core-ktx:${Version.CoreKtx}" 74 | const val ConstraintLayout = 75 | "androidx.constraintlayout:constraintlayout:${Version.ConstraintLayout}" 76 | const val TestExtJunit = "androidx.test.ext:junit:${Version.TestExtJunit}" 77 | const val TestEspresso = "androidx.test.espresso:espresso-core:${Version.TestEspresso}" 78 | const val ActivityKtx = "androidx.activity:activity-ktx:${Version.ActivityKtx}" 79 | const val FragmentKtx = "androidx.fragment:fragment-ktx:${Version.FragmentKtx}" 80 | const val MultiDex = "androidx.multidex:multidex:${Version.MultiDex}" 81 | } 82 | 83 | /** 84 | * Android相关依赖 85 | * 86 | * @author Qu Yunshuo 87 | * @since 4/24/21 4:02 PM 88 | */ 89 | object Android { 90 | const val Junit = "junit:junit:${Version.Junit}" 91 | const val Material = "com.google.android.material:material:${Version.Material}" 92 | } 93 | 94 | /** 95 | * JetPack相关依赖 96 | * 97 | * @author Qu Yunshuo 98 | * @since 4/24/21 4:02 PM 99 | */ 100 | object JetPack { 101 | const val ViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.Lifecycle}" 102 | const val ViewModelSavedState = 103 | "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Version.Lifecycle}" 104 | const val LiveData = "androidx.lifecycle:lifecycle-livedata-ktx:${Version.Lifecycle}" 105 | const val Lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Version.Lifecycle}" 106 | const val LifecycleCompilerAPT = 107 | "androidx.lifecycle:lifecycle-compiler:${Version.Lifecycle}" 108 | const val HiltCore = "com.google.dagger:hilt-android:${Version.Hilt}" 109 | const val HiltApt = "com.google.dagger:hilt-compiler:${Version.Hilt}" 110 | } 111 | 112 | /** 113 | * Kotlin相关依赖 114 | * 115 | * @author Qu Yunshuo 116 | * @since 4/24/21 4:02 PM 117 | */ 118 | object Kotlin { 119 | const val Kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Version.Kotlin}" 120 | const val CoroutinesCore = 121 | "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.Coroutines}" 122 | const val CoroutinesAndroid = 123 | "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.Coroutines}" 124 | } 125 | 126 | /** 127 | * GitHub及其他相关依赖 128 | * 129 | * @author Qu Yunshuo 130 | * @since 4/24/21 4:02 PM 131 | */ 132 | object GitHub { 133 | const val OkHttp = "com.squareup.okhttp3:okhttp:${Version.OkHttp}" 134 | const val OkHttpInterceptorLogging = 135 | "com.squareup.okhttp3:logging-interceptor:${Version.OkHttpInterceptorLogging}" 136 | const val Retrofit = "com.squareup.retrofit2:retrofit:${Version.Retrofit}" 137 | const val RetrofitConverterGson = 138 | "com.squareup.retrofit2:converter-gson:${Version.RetrofitConverterGson}" 139 | const val Gson = "com.google.code.gson:gson:${Version.Gson}" 140 | const val MMKV = "com.tencent:mmkv-static:${Version.MMKV}" 141 | const val AutoSize = "me.jessyan:autosize:${Version.AutoSize}" 142 | const val ARoute = "com.alibaba:arouter-api:${Version.ARoute}" 143 | const val ARouteCompiler = "com.alibaba:arouter-compiler:${Version.ARouteCompiler}" 144 | const val RecyclerViewAdapter = 145 | "com.github.CymChad:BaseRecyclerViewAdapterHelper:${Version.RecyclerViewAdapter}" 146 | const val EventBus = "org.greenrobot:eventbus:${Version.EventBus}" 147 | const val EventBusAPT = "org.greenrobot:eventbus-annotation-processor:${Version.EventBus}" 148 | const val PermissionX = "com.permissionx.guolindev:permissionx:${Version.PermissionX}" 149 | const val LeakCanary = "com.squareup.leakcanary:leakcanary-android:${Version.LeakCanary}" 150 | const val AutoService = "com.google.auto.service:auto-service:${Version.AutoService}" 151 | const val AutoServiceAnnotations = 152 | "com.google.auto.service:auto-service-annotations:${Version.AutoService}" 153 | const val Coil = "io.coil-kt:coil:${Version.Coil}" 154 | const val CoilGIF = "io.coil-kt:coil-gif:${Version.Coil}" 155 | const val CoilSVG = "io.coil-kt:coil-svg:${Version.Coil}" 156 | const val CoilVideo = "io.coil-kt:coil-video:${Version.Coil}" 157 | } 158 | 159 | /** 160 | * SDK相关依赖 161 | * 162 | * @author Qu Yunshuo 163 | * @since 4/24/21 4:02 PM 164 | */ 165 | object SDK { 166 | const val TencentBugly = "com.tencent.bugly:crashreport:${Version.TencentBugly}" 167 | const val TencentBuglyNative = 168 | "com.tencent.bugly:nativecrashreport:${Version.TencentBuglyNative}" 169 | const val TencentTBSX5 = "com.tencent.tbs.tbssdk:sdk:${Version.TencentTBSX5}" 170 | } 171 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/ProjectBuildConfig.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.buildsrc 2 | 3 | /** 4 | * 项目相关参数配置 5 | * 6 | * @author Qu Yunshuo 7 | * @since 4/24/21 5:56 PM 8 | */ 9 | object ProjectBuildConfig { 10 | const val compileSdkVersion = 33 11 | const val applicationId = "com.quyunshuo.androidbaseframemvvm" 12 | const val minSdkVersion = 21 13 | const val targetSdkVersion = 33 14 | const val versionCode = 1 15 | const val versionName = "1.0" 16 | const val isAppMode = false 17 | 18 | /** 19 | * 项目当前的版本状态 20 | * 该状态直接反映当前App是测试版 还是正式版 或者预览版 21 | * 正式版:RELEASE、预览版(α)-内部测试版:ALPHA、测试版(β)-公开测试版:BETA 22 | */ 23 | object Version { 24 | 25 | const val RELEASE = "VERSION_STATUS_RELEASE" 26 | 27 | const val ALPHA = "VERSION_STATUS_ALPHA" 28 | 29 | const val BETA = "VERSION_STATUS_BETA" 30 | } 31 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/SDKKeyConfig.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.buildsrc 2 | 3 | /** 4 | * 存放需要存在本地的SDK的密钥 5 | * 这种方式并不安全 6 | */ 7 | object SDKKeyConfig { 8 | 9 | /** 10 | * Bugly APP_ID 11 | * 正式环境需要与测试环境分开 正式ID:""、测试ID:"" 12 | */ 13 | const val BUGLY_APP_ID = "" 14 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | android.defaults.buildfeatures.buildconfig=true 23 | android.nonTransitiveRClass=false 24 | android.nonFinalResIds=false 25 | android.enableR8.fullMode=false -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # plugin 3 | agp = "8.1.0" 4 | kotlin-android = "1.8.0" 5 | hilt = "2.44" 6 | 7 | #lib 8 | 9 | 10 | [libraries] 11 | 12 | 13 | [plugins] 14 | application = { id = "com.android.application", version.ref = "agp" } 15 | library = { id = "com.android.library", version.ref = "agp" } 16 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-android" } 17 | hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } 18 | 19 | 20 | [bundles] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 22 08:57:40 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /img/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/img/img2.png -------------------------------------------------------------------------------- /lib_base/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /lib_base/build.gradle: -------------------------------------------------------------------------------- 1 | //**************************************** 2 | //********** lib_base 的配置文件 *********** 3 | //**************************************** 4 | 5 | plugins { 6 | alias(libs.plugins.library) 7 | alias(libs.plugins.kotlin) 8 | alias(libs.plugins.hilt) 9 | id "kotlin-kapt" 10 | } 11 | 12 | apply from: '../base_lib.gradle' 13 | 14 | import com.quyunshuo.androidbaseframemvvm.buildsrc.* 15 | 16 | android { 17 | 18 | buildFeatures { 19 | viewBinding = true 20 | } 21 | 22 | resourcePrefix "base_" 23 | namespace 'com.quyunshuo.androidbaseframemvvm.base' 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: "libs", include: ["*.jar"]) 28 | 29 | api DependencyConfig.AndroidX.CoreKtx 30 | api DependencyConfig.AndroidX.AppCompat 31 | api DependencyConfig.AndroidX.ConstraintLayout 32 | api DependencyConfig.AndroidX.ActivityKtx 33 | api DependencyConfig.AndroidX.FragmentKtx 34 | api DependencyConfig.AndroidX.MultiDex 35 | 36 | api DependencyConfig.Android.Material 37 | 38 | api DependencyConfig.Kotlin.Kotlin 39 | api DependencyConfig.Kotlin.CoroutinesCore 40 | api DependencyConfig.Kotlin.CoroutinesAndroid 41 | 42 | api DependencyConfig.JetPack.ViewModel 43 | api DependencyConfig.JetPack.ViewModelSavedState 44 | api DependencyConfig.JetPack.LiveData 45 | api DependencyConfig.JetPack.Lifecycle 46 | api DependencyConfig.JetPack.HiltCore 47 | 48 | api DependencyConfig.GitHub.Gson 49 | api DependencyConfig.GitHub.MMKV 50 | api DependencyConfig.GitHub.AutoSize 51 | api DependencyConfig.GitHub.ARoute 52 | api DependencyConfig.GitHub.RecyclerViewAdapter 53 | api DependencyConfig.GitHub.EventBus 54 | api DependencyConfig.GitHub.PermissionX 55 | api DependencyConfig.GitHub.AutoService 56 | api DependencyConfig.GitHub.OkHttp 57 | api DependencyConfig.GitHub.OkHttpInterceptorLogging 58 | api DependencyConfig.GitHub.Retrofit 59 | api DependencyConfig.GitHub.RetrofitConverterGson 60 | api DependencyConfig.GitHub.Coil 61 | api DependencyConfig.GitHub.CoilGIF 62 | api DependencyConfig.GitHub.CoilSVG 63 | api DependencyConfig.GitHub.CoilVideo 64 | 65 | api DependencyConfig.SDK.TencentBugly 66 | api DependencyConfig.SDK.TencentBuglyNative 67 | api DependencyConfig.SDK.TencentTBSX5 68 | 69 | kapt DependencyConfig.GitHub.ARouteCompiler 70 | kapt DependencyConfig.GitHub.EventBusAPT 71 | kapt DependencyConfig.GitHub.AutoServiceAnnotations 72 | kapt DependencyConfig.JetPack.HiltApt 73 | kapt DependencyConfig.JetPack.LifecycleCompilerAPT 74 | 75 | debugApi DependencyConfig.GitHub.LeakCanary 76 | } -------------------------------------------------------------------------------- /lib_base/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/lib_base/consumer-rules.pro -------------------------------------------------------------------------------- /lib_base/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /lib_base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/BaseApplication.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.util.Log 6 | import androidx.multidex.MultiDexApplication 7 | import com.quyunshuo.androidbaseframemvvm.base.app.ActivityLifecycleCallbacksImpl 8 | import com.quyunshuo.androidbaseframemvvm.base.app.LoadModuleProxy 9 | import kotlinx.coroutines.* 10 | import kotlin.system.measureTimeMillis 11 | 12 | /** 13 | * Application 基类 14 | * 15 | * @author Qu Yunshuo 16 | * @since 4/24/21 5:30 PM 17 | */ 18 | open class BaseApplication : MultiDexApplication() { 19 | 20 | private val mCoroutineScope by lazy(mode = LazyThreadSafetyMode.NONE) { MainScope() } 21 | 22 | private val mLoadModuleProxy by lazy(mode = LazyThreadSafetyMode.NONE) { LoadModuleProxy() } 23 | 24 | companion object { 25 | // 全局Context 26 | @SuppressLint("StaticFieldLeak") 27 | lateinit var context: Context 28 | 29 | @SuppressLint("StaticFieldLeak") 30 | lateinit var application: BaseApplication 31 | } 32 | 33 | override fun attachBaseContext(base: Context) { 34 | super.attachBaseContext(base) 35 | context = base 36 | application = this 37 | mLoadModuleProxy.onAttachBaseContext(base) 38 | } 39 | 40 | override fun onCreate() { 41 | super.onCreate() 42 | 43 | // 全局监听 Activity 生命周期 44 | registerActivityLifecycleCallbacks(ActivityLifecycleCallbacksImpl()) 45 | 46 | mLoadModuleProxy.onCreate(this) 47 | 48 | // 策略初始化第三方依赖 49 | initDepends() 50 | } 51 | 52 | /** 53 | * 初始化第三方依赖 54 | */ 55 | private fun initDepends() { 56 | // 开启一个 Default Coroutine 进行初始化不会立即使用的第三方 57 | mCoroutineScope.launch(Dispatchers.Default) { 58 | mLoadModuleProxy.initByBackstage() 59 | } 60 | 61 | // 前台初始化 62 | val allTimeMillis = measureTimeMillis { 63 | val depends = mLoadModuleProxy.initByFrontDesk() 64 | var dependInfo: String 65 | depends.forEach { 66 | val dependTimeMillis = measureTimeMillis { dependInfo = it() } 67 | Log.d("BaseApplication", "initDepends: $dependInfo : $dependTimeMillis ms") 68 | } 69 | } 70 | Log.d("BaseApplication", "初始化完成 $allTimeMillis ms") 71 | } 72 | 73 | override fun onTerminate() { 74 | super.onTerminate() 75 | mLoadModuleProxy.onTerminate(this) 76 | mCoroutineScope.cancel() 77 | } 78 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/ActivityLifecycleCallbacksImpl.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.app 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.os.Bundle 6 | import android.util.Log 7 | import com.quyunshuo.androidbaseframemvvm.base.utils.ActivityStackManager 8 | import com.quyunshuo.androidbaseframemvvm.base.utils.ForegroundBackgroundHelper 9 | 10 | /** 11 | * Activity生命周期监听 12 | * 13 | * @author Qu Yunshuo 14 | * @since 4/20/21 9:10 AM 15 | */ 16 | class ActivityLifecycleCallbacksImpl : Application.ActivityLifecycleCallbacks { 17 | 18 | private val TAG = "ActivityLifecycle" 19 | 20 | override fun onActivityCreated(activity: Activity, bundle: Bundle?) { 21 | ActivityStackManager.addActivityToStack(activity) 22 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityCreated") 23 | } 24 | 25 | override fun onActivityStarted(activity: Activity) { 26 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityStarted") 27 | ForegroundBackgroundHelper.onActivityStarted() 28 | } 29 | 30 | override fun onActivityResumed(activity: Activity) { 31 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityResumed") 32 | } 33 | 34 | override fun onActivityPaused(activity: Activity) { 35 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityPaused") 36 | } 37 | 38 | override fun onActivityStopped(activity: Activity) { 39 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityStopped") 40 | ForegroundBackgroundHelper.onActivityStopped() 41 | } 42 | 43 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { 44 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivitySaveInstanceState") 45 | } 46 | 47 | override fun onActivityDestroyed(activity: Activity) { 48 | ActivityStackManager.popActivityToStack(activity) 49 | Log.e(TAG, "${activity.javaClass.simpleName} --> onActivityDestroyed") 50 | } 51 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/ApplicationLifecycle.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.app 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | 6 | /** 7 | * Application 生命周期 用于初始化各个组件 8 | * 9 | * @author Qu Yunshuo 10 | * @since 4/23/21 5:22 PM 11 | */ 12 | interface ApplicationLifecycle { 13 | 14 | /** 15 | * 同[Application.attachBaseContext] 16 | * @param context Context 17 | */ 18 | fun onAttachBaseContext(context: Context) 19 | 20 | /** 21 | * 同[Application.onCreate] 22 | * @param application Application 23 | */ 24 | fun onCreate(application: Application) 25 | 26 | /** 27 | * 同[Application.onTerminate] 28 | * @param application Application 29 | */ 30 | fun onTerminate(application: Application) 31 | 32 | /** 33 | * 主线程前台初始化 34 | * @return MutableList<() -> String> 初始化方法集合 35 | */ 36 | fun initByFrontDesk(): MutableList<() -> String> 37 | 38 | /** 39 | * 不需要立即初始化的放在这里进行后台初始化 40 | */ 41 | fun initByBackstage() 42 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/app/LoadModuleProxy.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.app 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.util.Log 6 | import java.util.* 7 | 8 | /** 9 | * 加载组件代理类 10 | * 组件初始化的工作将由该代理类代理实现 11 | * 12 | * @author Qu Yunshuo 13 | * @since 4/23/21 5:37 PM 14 | */ 15 | class LoadModuleProxy : ApplicationLifecycle { 16 | 17 | private var mLoader: ServiceLoader = 18 | ServiceLoader.load(ApplicationLifecycle::class.java) 19 | 20 | /** 21 | * 同[Application.attachBaseContext] 22 | * @param context Context 23 | */ 24 | override fun onAttachBaseContext(context: Context) { 25 | mLoader.forEach { 26 | Log.d("ApplicationInit", it.toString()) 27 | it.onAttachBaseContext(context) 28 | } 29 | } 30 | 31 | /** 32 | * 同[Application.onCreate] 33 | * @param application Application 34 | */ 35 | override fun onCreate(application: Application) { 36 | mLoader.forEach { it.onCreate(application) } 37 | } 38 | 39 | /** 40 | * 同[Application.onTerminate] 41 | * @param application Application 42 | */ 43 | override fun onTerminate(application: Application) { 44 | mLoader.forEach { it.onTerminate(application) } 45 | } 46 | 47 | /** 48 | * 主线程前台初始化 49 | * @return MutableList<() -> String> 初始化方法集合 50 | */ 51 | override fun initByFrontDesk(): MutableList<() -> String> { 52 | val list: MutableList<() -> String> = mutableListOf() 53 | mLoader.forEach { list.addAll(it.initByFrontDesk()) } 54 | return list 55 | } 56 | 57 | /** 58 | * 不需要立即初始化的放在这里进行后台初始化 59 | */ 60 | override fun initByBackstage() { 61 | mLoader.forEach { it.initByBackstage() } 62 | } 63 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/constant/VersionStatus.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.constant 2 | 3 | /** 4 | * 版本状态 5 | * 6 | * @author Qu Yunshuo 7 | * @since 4/20/21 9:05 AM 8 | */ 9 | object VersionStatus { 10 | 11 | const val RELEASE = "VERSION_STATUS_RELEASE" 12 | 13 | const val ALPHA = "VERSION_STATUS_ALPHA" 14 | 15 | const val BETA = "VERSION_STATUS_BETA" 16 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ActivityKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.app.Activity 4 | import android.view.WindowManager 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.lifecycle.Lifecycle 7 | 8 | /** 9 | * 设置当前 [Activity] 是否允许截屏操作 10 | * 11 | * @receiver [Activity] 12 | * @param isAllow Boolean 是否允许截屏 13 | */ 14 | fun Activity.isAllowScreenCapture(isAllow: Boolean) { 15 | if (isAllow) { 16 | window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE) 17 | } else { 18 | window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) 19 | } 20 | } 21 | 22 | /** 23 | * 判断当前 Activity 是否是 [Lifecycle.State.RESUMED] 24 | */ 25 | fun AppCompatActivity.isResumed(): Boolean = lifecycle.currentState == Lifecycle.State.RESUMED 26 | -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/EditTextKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.text.InputFilter 4 | import android.widget.EditText 5 | 6 | /** 7 | * EditText相关扩展方法 8 | * 9 | * @author Qu Yunshuo 10 | * @since 2020/9/17 11 | */ 12 | 13 | /** 14 | * 过滤掉空格和回车 15 | */ 16 | fun EditText.filterBlankAndCarriageReturn() { 17 | val filterList = mutableListOf() 18 | filterList.addAll(filters) 19 | filterList.add(InputFilter { source, _, _, _, _, _ -> if (source == " " || source == "\n") "" else null }) 20 | filters = filterList.toTypedArray() 21 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/LifecycleOwnerKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.LiveData 5 | 6 | /** 7 | * 对LiveData订阅的简化封装 8 | * 9 | * 使用示例 10 | * ``` 11 | * override fun initObserve() { 12 | * observeLiveData(mViewModel.stateViewLD, ::processStateViewLivaData) 13 | * } 14 | * 15 | * private fun processStateViewLivaData(data: StateLayoutEnum) { 16 | * ... 17 | * } 18 | * ``` 19 | * 20 | * @receiver LifecycleOwner 21 | * @param liveData LiveData 需要进行订阅的LiveData 22 | * @param action action: (t: T) -> Unit 处理订阅内容的方法 23 | * @return Unit 24 | */ 25 | inline fun LifecycleOwner.observeLiveData( 26 | liveData: LiveData, 27 | crossinline action: (t: T) -> Unit 28 | ) { 29 | liveData.observe(this) { it?.let { t -> action(t) } } 30 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/NetUtils.kt: -------------------------------------------------------------------------------- 1 | package com.ad.newad 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | 7 | /** 8 | * 网络相关工具类 9 | */ 10 | object NetUtils { 11 | 12 | /** 13 | * 当前网络是否是 Wi-Fi 14 | */ 15 | fun currentNetIsWiFi(context: Context): Boolean { 16 | val connectivityManager = 17 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 18 | val networkCapabilities = 19 | connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) 20 | return networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ?: false 21 | } 22 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/PopupWindowKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.PopupWindow 6 | 7 | /** 8 | * PopupWindow相关扩展 9 | * 10 | * @author Qu Yunshuo 11 | * @since 1/4/21 10:48 AM 12 | */ 13 | 14 | /** 15 | * 测量view宽高 16 | */ 17 | fun PopupWindow.makeDropDownMeasureSpec(measureSpec: Int): Int { 18 | val mode = 19 | if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) View.MeasureSpec.UNSPECIFIED else View.MeasureSpec.EXACTLY 20 | return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode) 21 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/SizeUnitKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.content.Context 4 | import androidx.fragment.app.Fragment 5 | 6 | /** 7 | * @Author: QuYunShuo 8 | * @Time: 2020/9/17 9 | * @Class: SizeUnitKtx 10 | * @Remark: 尺寸单位换算相关扩展属性 11 | */ 12 | 13 | /** 14 | * dp 转 px 15 | */ 16 | fun Context.dp2px(dpValue: Float): Int { 17 | val scale = resources.displayMetrics.density 18 | return (dpValue * scale + 0.5f).toInt() 19 | } 20 | 21 | /** 22 | * px 转 dp 23 | */ 24 | fun Context.px2dp(pxValue: Float): Int { 25 | val scale = resources.displayMetrics.density 26 | return (pxValue / scale + 0.5f).toInt() 27 | } 28 | 29 | /** 30 | * sp 转 px 31 | */ 32 | fun Context.sp2px(spValue: Float): Int { 33 | val scale = resources.displayMetrics.scaledDensity 34 | return (spValue * scale + 0.5f).toInt() 35 | } 36 | 37 | /** 38 | * px 转 sp 39 | */ 40 | fun Context.px2sp(pxValue: Float): Int { 41 | val scale = resources.displayMetrics.scaledDensity 42 | return (pxValue / scale + 0.5f).toInt() 43 | } 44 | 45 | /** 46 | * dp 转 px 47 | */ 48 | fun Fragment.dp2px(dpValue: Float): Int { 49 | val scale = resources.displayMetrics.density 50 | return (dpValue * scale + 0.5f).toInt() 51 | } 52 | 53 | /** 54 | * px 转 dp 55 | */ 56 | fun Fragment.px2dp(pxValue: Float): Int { 57 | val scale = resources.displayMetrics.density 58 | return (pxValue / scale + 0.5f).toInt() 59 | } 60 | 61 | /** 62 | * sp 转 px 63 | */ 64 | fun Fragment.sp2px(spValue: Float): Int { 65 | val scale = resources.displayMetrics.scaledDensity 66 | return (spValue * scale + 0.5f).toInt() 67 | } 68 | 69 | /** 70 | * px 转 sp 71 | */ 72 | fun Fragment.px2sp(pxValue: Float): Int { 73 | val scale = resources.displayMetrics.scaledDensity 74 | return (pxValue / scale + 0.5f).toInt() 75 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/VideoViewKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.media.MediaPlayer 4 | import android.view.ViewGroup 5 | import android.widget.VideoView 6 | 7 | /** 8 | * 根据视频的尺寸与容器尺寸比例,动态调整 [VideoView] 的尺寸以适应视频的尺寸 9 | * 解决 [VideoView] 尺寸比例与视频尺寸比例不一致导致视频拉伸的问题 10 | * 11 | * 容器可以是屏幕或者 [VideoView] 12 | * 13 | * 使用方式: 14 | * 1. 通过 [VideoView.setOnPreparedListener] 向 [VideoView] 设置 [MediaPlayer.OnPreparedListener] 监听 15 | * 2. 通过 [MediaPlayer.OnPreparedListener] 回调获取到视频的真实宽高,调用该方法传入参数进行适配 16 | * 3. 如果需要考虑横竖屏切换,请在横竖屏改变监听回调中再次调用该方法进行适配 17 | * 18 | * @receiver [VideoView] 19 | * @param containerW Float 容器的真实宽 20 | * @param containerH Float 容器的真实高 21 | * @param videoW Float 视频的真实宽 22 | * @param videoH Float 视频的真实高 23 | */ 24 | fun VideoView.resetVideoViewDimensions( 25 | containerW: Float, 26 | containerH: Float, 27 | videoW: Float, 28 | videoH: Float, 29 | ) { 30 | // 计算宽高比进行调整宽高 31 | this.layoutParams = if (videoW / containerW < videoH / containerH) { 32 | this.layoutParams.apply { 33 | width = ViewGroup.LayoutParams.WRAP_CONTENT 34 | height = ViewGroup.LayoutParams.MATCH_PARENT 35 | } 36 | } else { 37 | this.layoutParams.apply { 38 | width = ViewGroup.LayoutParams.MATCH_PARENT 39 | height = ViewGroup.LayoutParams.WRAP_CONTENT 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.animation.Animator 4 | import android.animation.IntEvaluator 5 | import android.animation.ValueAnimator 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.quyunshuo.androidbaseframemvvm.base.view.OnSingleClickListener 9 | 10 | /** 11 | * @Author: QuYunShuo 12 | * @Time: 2020/9/1 13 | * @Class: ViewKtx 14 | * @Remark: View相关的扩展方法 15 | */ 16 | 17 | /*************************************** View可见性相关 ********************************************/ 18 | /** 19 | * 隐藏View 20 | * @receiver View 21 | */ 22 | fun View.gone() { 23 | visibility = View.GONE 24 | } 25 | 26 | /** 27 | * 显示View 28 | * @receiver View 29 | */ 30 | fun View.visible() { 31 | visibility = View.VISIBLE 32 | } 33 | 34 | /** 35 | * View不可见但存在原位置 36 | * @receiver View 37 | */ 38 | fun View.invisible() { 39 | visibility = View.INVISIBLE 40 | } 41 | 42 | /** 43 | * 设置 View 为 [View.VISIBLE] 44 | * 如果 [isVisible] 值为true,将 [View.setVisibility] 设置为 [View.VISIBLE],反之为 [View.GONE] 45 | * 46 | * @receiver View 47 | * @param isVisible Boolean 是否显示 48 | */ 49 | fun View.setVisible(isVisible: Boolean) { 50 | if (isVisible) visible() else gone() 51 | } 52 | 53 | /** 54 | * 设置 View 为 [View.GONE] 55 | * 如果 [isGone] 值为true,将 [View.setVisibility] 设置为 [View.GONE],反之为 [View.VISIBLE] 56 | * 57 | * @receiver View 58 | * @param isGone Boolean 是否隐藏 59 | */ 60 | fun View.setGone(isGone: Boolean) { 61 | if (isGone) visible() else gone() 62 | } 63 | 64 | /*************************************** View宽高相关 ********************************************/ 65 | /** 66 | * 设置 View 的高度 67 | * @receiver View 68 | * @param height Int 目标高度 69 | * @return View 70 | */ 71 | fun View.height(height: Int): View { 72 | val params = layoutParams ?: ViewGroup.LayoutParams( 73 | ViewGroup.LayoutParams.MATCH_PARENT, 74 | ViewGroup.LayoutParams.WRAP_CONTENT 75 | ) 76 | params.height = height 77 | layoutParams = params 78 | return this 79 | } 80 | 81 | /** 82 | * 设置View的宽度 83 | * @receiver View 84 | * @param width Int 目标宽度 85 | * @return View 86 | */ 87 | fun View.width(width: Int): View { 88 | val params = layoutParams ?: ViewGroup.LayoutParams( 89 | ViewGroup.LayoutParams.MATCH_PARENT, 90 | ViewGroup.LayoutParams.WRAP_CONTENT 91 | ) 92 | params.width = width 93 | layoutParams = params 94 | return this 95 | } 96 | 97 | /** 98 | * 设置View的宽度和高度 99 | * @receiver View 100 | * @param width Int 要设置的宽度 101 | * @param height Int 要设置的高度 102 | * @return View 103 | */ 104 | fun View.widthAndHeight(width: Int, height: Int): View { 105 | val params = layoutParams ?: ViewGroup.LayoutParams( 106 | ViewGroup.LayoutParams.MATCH_PARENT, 107 | ViewGroup.LayoutParams.WRAP_CONTENT 108 | ) 109 | params.width = width 110 | params.height = height 111 | layoutParams = params 112 | return this 113 | } 114 | 115 | /** 116 | * 设置宽度,带有过渡动画 117 | * @param targetValue 目标宽度 118 | * @param duration 时长 119 | * @param action 可选行为 120 | * @return 动画 121 | */ 122 | fun View.animateWidth( 123 | targetValue: Int, duration: Long = 400, listener: Animator.AnimatorListener? = null, 124 | action: ((Float) -> Unit)? = null 125 | ): ValueAnimator? { 126 | var animator: ValueAnimator? = null 127 | post { 128 | animator = ValueAnimator.ofInt(width, targetValue).apply { 129 | addUpdateListener { 130 | width(it.animatedValue as Int) 131 | action?.invoke((it.animatedFraction)) 132 | } 133 | if (listener != null) addListener(listener) 134 | setDuration(duration) 135 | start() 136 | } 137 | } 138 | return animator 139 | } 140 | 141 | /** 142 | * 设置高度,带有过渡动画 143 | * @param targetValue 目标高度 144 | * @param duration 时长 145 | * @param action 可选行为 146 | * @return 动画 147 | */ 148 | fun View.animateHeight( 149 | targetValue: Int, 150 | duration: Long = 400, 151 | listener: Animator.AnimatorListener? = null, 152 | action: ((Float) -> Unit)? = null 153 | ): ValueAnimator? { 154 | var animator: ValueAnimator? = null 155 | post { 156 | animator = ValueAnimator.ofInt(height, targetValue).apply { 157 | addUpdateListener { 158 | height(it.animatedValue as Int) 159 | action?.invoke((it.animatedFraction)) 160 | } 161 | if (listener != null) addListener(listener) 162 | setDuration(duration) 163 | start() 164 | } 165 | } 166 | return animator 167 | } 168 | 169 | /** 170 | * 设置宽度和高度,带有过渡动画 171 | * @param targetWidth 目标宽度 172 | * @param targetHeight 目标高度 173 | * @param duration 时长 174 | * @param action 可选行为 175 | * @return 动画 176 | */ 177 | fun View.animateWidthAndHeight( 178 | targetWidth: Int, 179 | targetHeight: Int, 180 | duration: Long = 400, 181 | listener: Animator.AnimatorListener? = null, 182 | action: ((Float) -> Unit)? = null 183 | ): ValueAnimator? { 184 | var animator: ValueAnimator? = null 185 | post { 186 | val startHeight = height 187 | val evaluator = IntEvaluator() 188 | animator = ValueAnimator.ofInt(width, targetWidth).apply { 189 | addUpdateListener { 190 | widthAndHeight( 191 | it.animatedValue as Int, 192 | evaluator.evaluate(it.animatedFraction, startHeight, targetHeight) 193 | ) 194 | action?.invoke((it.animatedFraction)) 195 | } 196 | if (listener != null) addListener(listener) 197 | setDuration(duration) 198 | start() 199 | } 200 | } 201 | return animator 202 | } 203 | 204 | /*************************************** View其他 ********************************************/ 205 | /** 206 | * 获取View id 207 | */ 208 | fun View.getViewId(): Int { 209 | var id = id 210 | if (id == View.NO_ID) { 211 | id = View.generateViewId() 212 | } 213 | return id 214 | } 215 | 216 | /** 217 | * 给 [View] 设置带有防抖效果的点击事件 218 | * 219 | * @receiver [View] 220 | * @param delayTime Int 防抖间隔时间,单位是毫秒,默认值 500ms 221 | * @param listener (v: View) -> Unit 具体的点击事件 222 | * @see OnSingleClickListener 223 | */ 224 | fun View.setOnSingleClickListener(delayTime: Int = 500, listener: (v: View) -> Unit) { 225 | setOnClickListener(OnSingleClickListener(delayTime, listener)) 226 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewModelKtx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import kotlinx.coroutines.* 6 | 7 | /** 8 | * 开启一个线程调度模式为[Dispatchers.IO]的协程 有默认的异常处理器 9 | * 10 | * **sample:** 11 | * ``` 12 | * class SampleViewModel : ViewModel() { 13 | * 14 | * fun sample() { 15 | * launchIO { 16 | * // 协程体 17 | * } 18 | * launchIO(exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> 19 | * // exception handling 20 | * }) { 21 | * // 协程体 22 | * } 23 | * } 24 | * } 25 | * ``` 26 | * 27 | * @receiver ViewModel 28 | * 29 | * @param exceptionHandler CoroutineExceptionHandler 异常处理器 30 | * @param block suspend CoroutineScope.() -> Unit 协程体 31 | * @return Job 32 | */ 33 | fun ViewModel.launchIO( 34 | exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> 35 | throwable.printStackTrace() 36 | }, 37 | block: suspend CoroutineScope.() -> Unit 38 | ): Job = viewModelScope.launch(Dispatchers.IO + exceptionHandler, block = block) 39 | 40 | /** 41 | * 开启一个线程调度模式为[Dispatchers.Default]的协程 有默认的异常处理器 42 | * 43 | * **sample:** 44 | * ``` 45 | * class SampleViewModel : ViewModel() { 46 | * 47 | * fun sample() { 48 | * launchDefault { 49 | * // 协程体 50 | * } 51 | * launchDefault(exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> 52 | * // exception handling 53 | * }) { 54 | * // 协程体 55 | * } 56 | * } 57 | * } 58 | * ``` 59 | * 60 | * @receiver ViewModel 61 | * 62 | * @param exceptionHandler CoroutineExceptionHandler 异常处理器 63 | * @param block suspend CoroutineScope.() -> Unit 协程体 64 | * @return Job 65 | */ 66 | fun ViewModel.launchDefault( 67 | exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> 68 | throwable.printStackTrace() 69 | }, 70 | block: suspend CoroutineScope.() -> Unit 71 | ): Job = viewModelScope.launch(Dispatchers.Default + exceptionHandler, block = block) 72 | 73 | /** 74 | * 开启一个线程调度模式为[Dispatchers.Main]的协程 有默认的异常处理器 75 | * 76 | * **sample:** 77 | * ``` 78 | * class SampleViewModel : ViewModel() { 79 | * 80 | * fun sample() { 81 | * launchMain { 82 | * // 协程体 83 | * } 84 | * launchMain(exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> 85 | * // exception handling 86 | * }) { 87 | * // 协程体 88 | * } 89 | * } 90 | * } 91 | * ``` 92 | * 93 | * @receiver ViewModel 94 | * 95 | * @param exceptionHandler CoroutineExceptionHandler 异常处理器 96 | * @param block suspend CoroutineScope.() -> Unit 协程体 97 | * @return Job 98 | */ 99 | fun ViewModel.launchMain( 100 | exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> 101 | throwable.printStackTrace() 102 | }, 103 | block: suspend CoroutineScope.() -> Unit 104 | ): Job = viewModelScope.launch(Dispatchers.Main + exceptionHandler, block = block) -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/ktx/ViewPager2Ktx.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.ktx 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import androidx.viewpager2.widget.ViewPager2 6 | 7 | /** 8 | * 设置ViewPager2的过度滚动模式为绝不允许用户过度滚动此视图 9 | * @receiver ViewPager2 10 | */ 11 | fun ViewPager2.setOverScrollModeToNever() { 12 | val childView: View = this.getChildAt(0) 13 | if (childView is RecyclerView) { 14 | childView.overScrollMode = RecyclerView.OVER_SCROLL_NEVER 15 | } 16 | } 17 | 18 | /** 19 | * 设置ViewPager2的过度滚动模式为始终允许用户过度滚动此视图,前提是它是可以滚动的视图 20 | * @receiver ViewPager2 21 | */ 22 | fun ViewPager2.setOverScrollModeToAlways() { 23 | val childView: View = this.getChildAt(0) 24 | if (childView is RecyclerView) { 25 | childView.overScrollMode = RecyclerView.OVER_SCROLL_ALWAYS 26 | } 27 | } 28 | 29 | /** 30 | * 设置ViewPager2的过度滚动模式为仅当内容大到足以有意义地滚动时,才允许用户过度滚动此视图,前提是它是可以滚动的视图。 31 | * @receiver ViewPager2 32 | */ 33 | fun ViewPager2.setOverScrollModeToIfContentScrolls() { 34 | val childView: View = this.getChildAt(0) 35 | if (childView is RecyclerView) { 36 | childView.overScrollMode = RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS 37 | } 38 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/m/BaseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.mvvm.m 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.FlowCollector 6 | import kotlinx.coroutines.flow.flow 7 | import kotlinx.coroutines.flow.flowOn 8 | 9 | /** 10 | * 仓库层 Repository 基类 11 | * 12 | * @author Qu Yunshuo 13 | * @since 8/27/20 14 | */ 15 | open class BaseRepository { 16 | 17 | /** 18 | * 发起请求封装 19 | * 该方法将flow的执行切换至IO线程 20 | * 21 | * @param requestBlock 请求的整体逻辑 22 | * @return Flow @BuilderInference block: suspend FlowCollector.() -> Unit 23 | */ 24 | protected fun request(requestBlock: suspend FlowCollector.() -> Unit): Flow { 25 | return flow(block = requestBlock).flowOn(Dispatchers.IO) 26 | } 27 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameActivity.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.mvvm.v 2 | 3 | import android.content.res.Resources 4 | import android.os.Bundle 5 | import android.os.Looper 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.viewbinding.ViewBinding 8 | import com.alibaba.android.arouter.launcher.ARouter 9 | import com.quyunshuo.androidbaseframemvvm.base.R 10 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel 11 | import com.quyunshuo.androidbaseframemvvm.base.utils.* 12 | import com.quyunshuo.androidbaseframemvvm.base.utils.network.AutoRegisterNetListener 13 | import com.quyunshuo.androidbaseframemvvm.base.utils.network.NetworkStateChangeListener 14 | import com.quyunshuo.androidbaseframemvvm.base.utils.network.NetworkTypeEnum 15 | import me.jessyan.autosize.AutoSizeCompat 16 | 17 | /** 18 | * Activity基类 19 | * 20 | * @author Qu Yunshuo 21 | * @since 8/27/20 22 | */ 23 | abstract class BaseFrameActivity : AppCompatActivity(), 24 | FrameView, NetworkStateChangeListener { 25 | 26 | protected abstract val mViewModel: VM 27 | 28 | protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { createVB() } 29 | 30 | /** 31 | * 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotation] 32 | */ 33 | private var mHaveRegisterEventBus = false 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | setContentView(mBinding.root) 38 | // ARouter 依赖注入 39 | ARouter.getInstance().inject(this) 40 | 41 | // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus 42 | if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) { 43 | mHaveRegisterEventBus = true 44 | EventBusUtils.register(this) 45 | } 46 | 47 | setStatusBar() 48 | mBinding.initView() 49 | initNetworkListener() 50 | initObserve() 51 | initRequestData() 52 | } 53 | 54 | /** 55 | * 初始化网络状态监听 56 | * @return Unit 57 | */ 58 | private fun initNetworkListener() { 59 | lifecycle.addObserver(AutoRegisterNetListener(this)) 60 | } 61 | 62 | /** 63 | * 设置状态栏 64 | * 子类需要自定义时重写该方法即可 65 | * @return Unit 66 | */ 67 | open fun setStatusBar() {} 68 | 69 | /** 70 | * 网络类型更改回调 71 | * @param type Int 网络类型 72 | * @return Unit 73 | */ 74 | override fun networkTypeChange(type: NetworkTypeEnum) {} 75 | 76 | /** 77 | * 网络连接状态更改回调 78 | * @param isConnected Boolean 是否已连接 79 | * @return Unit 80 | */ 81 | override fun networkConnectChange(isConnected: Boolean) { 82 | toast(if (isConnected) getString(R.string.base_network_connected) else getString(R.string.base_network_disconnected)) 83 | } 84 | 85 | override fun onDestroy() { 86 | // 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus 87 | if (mHaveRegisterEventBus) { 88 | EventBusUtils.unRegister(this) 89 | } 90 | super.onDestroy() 91 | } 92 | 93 | override fun getResources(): Resources { 94 | // 主要是为了解决 AndroidAutoSize 在横屏切换时导致适配失效的问题 95 | // 但是 AutoSizeCompat.autoConvertDensity() 对线程做了判断 导致Coil等图片加载框架在子线程访问的时候会异常 96 | // 所以在这里加了线程的判断 如果是非主线程 就取消单独的适配 97 | if (Looper.myLooper() == Looper.getMainLooper()) { 98 | AutoSizeCompat.autoConvertDensityOfGlobal((super.getResources())) 99 | } 100 | return super.getResources() 101 | } 102 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/BaseFrameFragment.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.mvvm.v 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewbinding.ViewBinding 9 | import com.alibaba.android.arouter.launcher.ARouter 10 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel 11 | import com.quyunshuo.androidbaseframemvvm.base.utils.RegisterEventBus 12 | import com.quyunshuo.androidbaseframemvvm.base.utils.EventBusUtils 13 | 14 | /** 15 | * Fragment基类 16 | * 17 | * @author Qu Yunshuo 18 | * @since 8/27/20 19 | */ 20 | abstract class BaseFrameFragment : Fragment(), 21 | FrameView { 22 | 23 | /** 24 | * 私有的 ViewBinding 此写法来自 Google Android 官方 25 | */ 26 | private var _binding: VB? = null 27 | 28 | protected val mBinding get() = _binding!! 29 | 30 | protected abstract val mViewModel: VM 31 | 32 | /** 33 | * 是否有 [RegisterEventBus] 注解,避免重复调用 [Class.isAnnotation] 34 | */ 35 | private var mHaveRegisterEventBus = false 36 | 37 | override fun onCreateView( 38 | inflater: LayoutInflater, 39 | container: ViewGroup?, 40 | savedInstanceState: Bundle? 41 | ): View? { 42 | _binding = createVB() 43 | return _binding?.root 44 | } 45 | 46 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 47 | super.onViewCreated(view, savedInstanceState) 48 | // ARouter 依赖注入 49 | ARouter.getInstance().inject(this) 50 | 51 | // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus 52 | if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) { 53 | mHaveRegisterEventBus = true 54 | EventBusUtils.register(this) 55 | } 56 | _binding?.initView() 57 | initObserve() 58 | initRequestData() 59 | } 60 | 61 | override fun onDestroyView() { 62 | super.onDestroyView() 63 | _binding = null 64 | } 65 | 66 | override fun onDestroy() { 67 | // 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus 68 | if (mHaveRegisterEventBus) { 69 | EventBusUtils.unRegister(this) 70 | } 71 | super.onDestroy() 72 | } 73 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/v/FrameView.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.mvvm.v 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.viewbinding.ViewBinding 5 | 6 | /** 7 | * View层基类抽象 8 | * 9 | * @author Qu Yunshuo 10 | * @since 10/13/20 11 | */ 12 | interface FrameView { 13 | 14 | /** 15 | * 创建 [ViewBinding] 实例 16 | */ 17 | fun createVB(): VB 18 | 19 | /** 20 | * 初始化 View 21 | */ 22 | fun VB.initView() 23 | 24 | /** 25 | * 订阅 [LiveData] 26 | */ 27 | fun initObserve() 28 | 29 | /** 30 | * 用于在页面创建时进行请求接口 31 | */ 32 | fun initRequestData() 33 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/vm/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.mvvm.vm 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.quyunshuo.androidbaseframemvvm.base.utils.StateLayoutEnum 6 | import kotlin.jvm.Throws 7 | 8 | /** 9 | * ViewModel 基类 10 | * 11 | * @author Qu Yunshuo 12 | * @since 8/27/20 13 | */ 14 | abstract class BaseViewModel : ViewModel() { 15 | 16 | /** 17 | * 控制状态视图的LiveData 18 | */ 19 | val stateViewLD = MutableLiveData() 20 | 21 | /** 22 | * 更改状态视图的状态 23 | * 24 | * @param hide Boolean 是否进行隐藏状态视图 25 | * @param loading Boolean 是否显示加载中视图 26 | * @param error Boolean 是否显示错误视图 27 | * @param noData Boolean 是否显示没有数据视图 28 | * @return Unit 29 | * @throws IllegalArgumentException 如果入参没有传入任何参数或者为true的参数 >1 时,会抛出[IllegalArgumentException] 30 | */ 31 | @Throws(IllegalArgumentException::class) 32 | protected fun changeStateView( 33 | hide: Boolean = false, 34 | loading: Boolean = false, 35 | error: Boolean = false, 36 | noData: Boolean = false 37 | ) { 38 | // 对参数进行校验 39 | var count = 0 40 | if (hide) count++ 41 | if (loading) count++ 42 | if (error) count++ 43 | if (noData) count++ 44 | when { 45 | count == 0 -> throw IllegalArgumentException("必须设置一个参数为true") 46 | count > 1 -> throw IllegalArgumentException("只能有一个参数为true") 47 | } 48 | 49 | // 修改状态 50 | when { 51 | hide -> stateViewLD.postValue(StateLayoutEnum.HIDE) 52 | loading -> stateViewLD.postValue(StateLayoutEnum.LOADING) 53 | error -> stateViewLD.postValue(StateLayoutEnum.ERROR) 54 | noData -> stateViewLD.postValue(StateLayoutEnum.NO_DATA) 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/mvvm/vm/EmptyViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.mvvm.vm 2 | 3 | import dagger.hilt.android.lifecycle.HiltViewModel 4 | import javax.inject.Inject 5 | 6 | /** 7 | * 空的ViewModel 主要给现阶段不需要ViewModel的界面使用 8 | * 9 | * @author Qu Yunshuo 10 | * @since 2021/7/10 11:04 上午 11 | */ 12 | @HiltViewModel 13 | class EmptyViewModel @Inject constructor() : BaseViewModel() -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ActivityStackManager.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.app.Activity 4 | import java.util.* 5 | 6 | /** 7 | * @Author: QuYunShuo 8 | * @Time: 2020/9/11 9 | * @Class: ActivityStackManager 10 | * @Remark: Activity 栈管理类 11 | */ 12 | object ActivityStackManager { 13 | 14 | // 管理栈 15 | val activityStack by lazy { Stack() } 16 | 17 | /** 18 | * 添加 Activity 到管理栈 19 | * @param activity Activity 20 | */ 21 | fun addActivityToStack(activity: Activity) { 22 | activityStack.push(activity) 23 | } 24 | 25 | /** 26 | * 弹出栈内指定Activity 不finish 27 | * @param activity Activity 28 | */ 29 | fun popActivityToStack(activity: Activity) { 30 | if (!activityStack.empty()) { 31 | activityStack.forEach { 32 | if (it == activity) { 33 | activityStack.remove(activity) 34 | return 35 | } 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * 返回到上一个 Activity 并结束当前 Activity 42 | */ 43 | fun backToPreviousActivity() { 44 | if (!activityStack.empty()) { 45 | val activity = activityStack.pop() 46 | if (!activity.isFinishing) activity.finish() 47 | } 48 | } 49 | 50 | /** 51 | * 根据类名 判断是否是当前的 Activity 52 | * @param cls Class<*> 类名 53 | * @return Boolean 54 | */ 55 | fun isCurrentActivity(cls: Class<*>): Boolean { 56 | val currentActivity = getCurrentActivity() 57 | return if (currentActivity != null) currentActivity.javaClass == cls else false 58 | } 59 | 60 | /** 61 | * 获取当前的 Activity 62 | */ 63 | fun getCurrentActivity(): Activity? = 64 | if (!activityStack.empty()) activityStack.lastElement() else null 65 | 66 | /** 67 | * 结束一个栈内指定类名的 Activity 68 | * @param cls Class<*> 69 | */ 70 | fun finishActivity(cls: Class<*>) { 71 | activityStack.forEach { 72 | if (it.javaClass == cls) { 73 | if (!it.isFinishing) it.finish() 74 | return 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * 弹出其他 Activity 81 | */ 82 | fun popOtherActivity() { 83 | val activityList = activityStack.toList() 84 | getCurrentActivity()?.run { 85 | activityList.forEach { activity -> 86 | if (this != activity) { 87 | activityStack.remove(activity) 88 | activity.finish() 89 | } 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * 返回到指定 Activity 96 | */ 97 | fun backToSpecifyActivity(activityClass: Class<*>) { 98 | val activityList = activityStack.toList().reversed() 99 | activityList.forEach { 100 | if (it.javaClass == activityClass) { 101 | return 102 | } else { 103 | activityStack.pop() 104 | it.finish() 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/AndroidBugFixUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.view.View 6 | import android.view.inputmethod.InputMethodManager 7 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 8 | import java.lang.reflect.Field 9 | 10 | /** 11 | * 解决 Android 自身的 Bug 12 | * 13 | * @author Qu Yunshuo 14 | * @since 2020/10/22 15 | */ 16 | class AndroidBugFixUtils { 17 | 18 | /** 19 | * 解决 InputMethodManager 造成的内存泄露 20 | * 21 | * 使用方式: 22 | * ``` 23 | * override fun onDestroy() { 24 | * AndroidBugFixUtils().fixSoftInputLeaks(this) 25 | * super.onDestroy() 26 | * } 27 | * ``` 28 | */ 29 | fun fixSoftInputLeaks(activity: Activity) { 30 | val imm = 31 | BaseApplication.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 32 | val leakViews = arrayOf("mLastSrvView", "mCurRootView", "mServedView", "mNextServedView") 33 | for (leakView in leakViews) { 34 | try { 35 | val leakViewField: Field = 36 | InputMethodManager::class.java.getDeclaredField(leakView) ?: continue 37 | if (!leakViewField.isAccessible) leakViewField.isAccessible = true 38 | val view: Any? = leakViewField.get(imm) 39 | if (view !is View) continue 40 | if (view.rootView == activity.window.decorView.rootView) { 41 | leakViewField.set(imm, null) 42 | } 43 | } catch (t: Throwable) { 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/AppUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.content.pm.PackageInfo 4 | import android.os.Build 5 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 6 | 7 | /** 8 | * App 相关工具类 9 | * 10 | * @author Qu Yunshuo 11 | * @sine 2023/2/13 23:15 12 | */ 13 | class AppUtils { 14 | 15 | /** 16 | * 获取当前 App 版本号 17 | * 18 | * @return Long 19 | */ 20 | fun getAppVersionCode(): Long { 21 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 22 | getAppPackageInfo().longVersionCode 23 | } else { 24 | getAppPackageInfo().versionCode.toLong() 25 | } 26 | } 27 | 28 | /** 29 | * 获取当前 App 版本名 30 | * 31 | * @return String 32 | */ 33 | fun getAppVersionName(): String = getAppPackageInfo().versionName 34 | 35 | /** 36 | * 获取当前 App 的 [PackageInfo] 37 | * 38 | * @return PackageInfo 39 | */ 40 | fun getAppPackageInfo(): PackageInfo { 41 | return BaseApplication.context 42 | .packageManager 43 | .getPackageInfo(BaseApplication.context.packageName, 0) 44 | } 45 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ClipboardUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 7 | 8 | /** 9 | * 剪切板工具类 10 | * 11 | * @author Qu Yunshuo 12 | * @since 2023/5/31 10:27 13 | */ 14 | object ClipboardUtils { 15 | 16 | /** 17 | * 复制内容到剪切板 18 | * 19 | * @param text String 内容 20 | * @param label String 标签,用于区分内容 21 | */ 22 | fun copyToClipboard(text: String, label: String = "") { 23 | val clipboard = 24 | BaseApplication.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 25 | val clip = ClipData.newPlainText(label, text) 26 | clipboard.setPrimaryClip(clip) 27 | } 28 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/CoilGIFImageLoader.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.os.Build.VERSION.SDK_INT 4 | import coil.ImageLoader 5 | import coil.decode.GifDecoder 6 | import coil.decode.ImageDecoderDecoder 7 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 8 | 9 | /** 10 | * 用于加载 Gif 的 Coil ImageLoader 11 | * 12 | * @author Qu Yunshuo 13 | * @since 2021/9/6 4:26 下午 14 | */ 15 | object CoilGIFImageLoader { 16 | 17 | val imageLoader = ImageLoader.Builder(BaseApplication.context) 18 | .componentRegistry { 19 | if (SDK_INT >= 28) { 20 | add(ImageDecoderDecoder(BaseApplication.context)) 21 | } else { 22 | add(GifDecoder()) 23 | } 24 | } 25 | .build() 26 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import java.text.ParseException 4 | import java.text.SimpleDateFormat 5 | import java.util.* 6 | 7 | /** 8 | * 时间工具类 9 | * ________________________________________________________________________________________ 10 | * |字母 |日期或时间元素 | 表示 | 示例 | 11 | * |:--:|:--------------------:|:-----------------:|:------------------------------------:| 12 | * |G |Era 标志符 | Text | AD | 13 | * |y |年 | Year | 1996; 96 | 14 | * |M |年中的月份 | Month | July; Jul; 07 | 15 | * |w |年中的周数 | Number | 27 | 16 | * |W |月份中的周数 | Number | 2 | 17 | * |D |年中的天数 | Number | 189 | 18 | * |d |月份中的天数 | Number | 10 | 19 | * |F |月份中的星期 | Number | 2 | 20 | * |E |星期中的天数 | Text | Tuesday; Tue | 21 | * |a |Am/pm 标记 | Text | PM | 22 | * |H |一天中的小时数(0-23) | Number | 0 | 23 | * |k |一天中的小时数(1-24) | Number | 24 | 24 | * |K |am/pm 中的小时数(0-11) | Number | 0 | 25 | * |h |am/pm 中的小时数(1-12) | Number | 12 | 26 | * |m |小时中的分钟数 | Number | 30 | 27 | * |s |分钟中的秒数 | Number | 55 | 28 | * |S |毫秒数 | Number | 978 | 29 | * |z |时区 | General time zone | Pacific Standard Time; PST; GMT-08:00| 30 | * |Z |时区 | RFC 822 time zone | -0800 | 31 | *  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 32 | * @author Qu Yunshuo 33 | * @since 2020/9/8 34 | */ 35 | object DateUtils { 36 | 37 | /** 38 | * 获取时间格式化String 39 | * @param timestamp 时间戳 40 | * @param dateFormat 日期格式 41 | * @return 格式化后的字符串 42 | */ 43 | fun getDateFormatString(timestamp: Long, dateFormat: String): String = 44 | SimpleDateFormat(dateFormat, Locale.CHINESE).format(Date(timestamp)) 45 | 46 | /** 47 | * 将固定格式[dateFormat]的时间字符串[dateString]转换为时间值 48 | */ 49 | fun getDateStringToDate(dateString: String, dateFormat: String): Long? { 50 | val simpleDateFormat = SimpleDateFormat(dateFormat, Locale.CHINESE) 51 | var date: Date? = null 52 | try { 53 | date = simpleDateFormat.parse(dateString) 54 | } catch (e: ParseException) { 55 | e.printStackTrace() 56 | } 57 | return date?.time 58 | } 59 | 60 | /** 61 | * 将计时毫秒值[millisecond]转换为时分秒 62 | */ 63 | fun getGapTime(millisecond: Long): String { 64 | val hours = millisecond / (1000 * 60 * 60) 65 | val minutes = (millisecond - hours * (1000 * 60 * 60)) / (1000 * 60) 66 | val second = (millisecond - hours * (1000 * 60 * 60) - minutes * (1000 * 60)) / 1000 67 | var diffTime: String 68 | diffTime = if (minutes < 10) { 69 | "$hours:0$minutes" 70 | } else { 71 | "$hours:$minutes" 72 | } 73 | diffTime = if (second < 10) { 74 | "$diffTime:0$second" 75 | } else { 76 | "$diffTime:$second" 77 | } 78 | return diffTime 79 | } 80 | 81 | /** 82 | * 获取以当前日期为基准的某一时间段的日期 83 | * @param isFuture Boolean 真为未来时间 假为以前的时间 84 | * @param interval Int 间隔时间 以当前时间为基准 距今天前n天或后n天开始 就是n 0是当前日期 85 | * @param size String 时间区间长度 比如获取五天的时间 就是5 当前日期也算一天 86 | * @return List 日期集合 顺序为日期的新旧程度 87 | * @throws RuntimeException 如果[interval]小于0或者[size]小于1会抛出[RuntimeException] 88 | * 89 | * 示例:获取后天开始 为期七天的时间就是 getExcerptDate(true, 2, 7) 90 | * 获取昨天开始再往前7天的时间 getExcerptDate(false, 1, 7) 91 | */ 92 | fun getExcerptDate( 93 | isFuture: Boolean, 94 | interval: Int, 95 | size: Int, 96 | dateFormat: String 97 | ): List { 98 | if (interval < 0) throw RuntimeException("\"interval\" it can't be less than 0") 99 | if (size < 1) throw RuntimeException("\"size\" it can't be less than 1") 100 | val simpleDateFormat = SimpleDateFormat(dateFormat, Locale.CHINESE) 101 | val calendar = Calendar.getInstance() 102 | val currentDayOfYear = calendar.get(Calendar.DAY_OF_YEAR) 103 | val currentYear = calendar.get(Calendar.YEAR) 104 | val dateList = mutableListOf() 105 | if (isFuture) { 106 | (interval until interval + size).forEach { 107 | val timestamp = getSomedayDate(it, calendar, currentDayOfYear, currentYear) 108 | dateList.add(simpleDateFormat.format(timestamp)) 109 | } 110 | } else { 111 | (-interval downTo -interval - size + 1).forEach { 112 | val timestamp = getSomedayDate(it, calendar, currentDayOfYear, currentYear) 113 | dateList.add(simpleDateFormat.format(timestamp)) 114 | } 115 | } 116 | return dateList 117 | } 118 | 119 | /** 120 | * 获取距离今天的某一天的时间戳 121 | * @param numberOfDaysBetween Int 间隔今天的天数 正数为未来时间 负数为以前的时间 122 | * @param calendar Calendar Calendar对象 使用依赖注入方式 提高对象的复用性 123 | * @param currentDayOfYear Int 当前时间在当年的天 使用Calendar获取 124 | * @param currentYear Int 当前年 使用Calendar获取 125 | * @return Long 时间戳 126 | */ 127 | fun getSomedayDate( 128 | numberOfDaysBetween: Int, 129 | calendar: Calendar, 130 | currentDayOfYear: Int, 131 | currentYear: Int 132 | ): Long { 133 | calendar.set(Calendar.DAY_OF_YEAR, currentDayOfYear) 134 | calendar.set(Calendar.YEAR, currentYear) 135 | calendar.set( 136 | Calendar.DAY_OF_YEAR, 137 | calendar.get(Calendar.DAY_OF_YEAR) + numberOfDaysBetween 138 | ) 139 | return calendar.time.time 140 | } 141 | 142 | /** 143 | * String 转化 Calendar 144 | * @param string String 145 | * @param format String 146 | */ 147 | fun stringToCalendar(string: String, format: String): Calendar? { 148 | val sdf = SimpleDateFormat(format, Locale.CHINESE) 149 | var calendar: Calendar 150 | try { 151 | val date: Date = sdf.parse(string) ?: return null 152 | calendar = Calendar.getInstance() 153 | calendar.time = date 154 | } catch (e: ParseException) { 155 | e.printStackTrace() 156 | calendar = Calendar.getInstance() 157 | } 158 | return calendar 159 | } 160 | 161 | /** 162 | * String 转化Date 163 | * @param str String 164 | * @param format String 165 | * @return Date 166 | */ 167 | fun strToDate(str: String, format: String): Date? { 168 | val sdf = SimpleDateFormat(format, Locale.CHINESE) 169 | return try { 170 | sdf.parse(str) 171 | } catch (e: ParseException) { 172 | e.printStackTrace() 173 | null 174 | } 175 | } 176 | 177 | /** 178 | * 判断两个时间是否是同一天 179 | * @param cal1 Calendar 180 | * @param cal2 Calendar 181 | * @return Boolean 182 | */ 183 | fun isSameDay(cal1: Calendar, cal2: Calendar): Boolean { 184 | return cal1[0] == cal2[0] && cal1[1] == cal2[1] && cal1[6] == cal2[6] 185 | } 186 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/EventBusUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import org.greenrobot.eventbus.EventBus 4 | 5 | /** 6 | * @Author: QuYunShuo 7 | * @Time: 2020/8/29 8 | * @Class: EventBusUtil 9 | * @Remark: EventBus工具类 10 | */ 11 | object EventBusUtils { 12 | 13 | /** 14 | * 订阅 15 | * @param subscriber 订阅者 16 | */ 17 | fun register(subscriber: Any) = EventBus.getDefault().register(subscriber) 18 | 19 | /** 20 | * 解除注册 21 | * @param subscriber 订阅者 22 | */ 23 | fun unRegister(subscriber: Any) = EventBus.getDefault().unregister(subscriber) 24 | 25 | /** 26 | * 发送普通事件 27 | * @param event 事件 28 | */ 29 | fun postEvent(event: Any) = EventBus.getDefault().post(event) 30 | 31 | /** 32 | * 发送粘性事件 33 | * @param stickyEvent 粘性事件 34 | */ 35 | fun postStickyEvent(stickyEvent: Any) = EventBus.getDefault().postSticky(stickyEvent) 36 | 37 | /** 38 | * 手动获取粘性事件 39 | * @param stickyEventType 粘性事件 40 | * @param 事件泛型 41 | * @return 返回给定事件类型的最近粘性事件 42 | */ 43 | fun getStickyEvent(stickyEventType: Class): T = 44 | EventBus.getDefault().getStickyEvent(stickyEventType) 45 | 46 | /** 47 | * 手动删除粘性事件 48 | * @param stickyEventType 粘性事件 49 | * @param 事件泛型 50 | * @return 返回给定事件类型的最近粘性事件 51 | */ 52 | fun removeStickyEvent(stickyEventType: Class): T = 53 | EventBus.getDefault().removeStickyEvent(stickyEventType) 54 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ForegroundBackgroundHelper.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | /** 4 | * 前后台切换帮助类,该类实现了前后台监听以及支持注册变化响应监听 5 | * 6 | * @see ForegroundBackgroundObserver 7 | * @see ForegroundBackgroundSubject 8 | * 9 | * @author Qu Yunshuo 10 | * @since 2023/5/31 14:22 11 | */ 12 | object ForegroundBackgroundHelper : ForegroundBackgroundSubject { 13 | 14 | private var mActivityStartCount = 0 15 | 16 | private var mIsForeground = false 17 | 18 | private val mObservers = mutableListOf() 19 | 20 | fun onActivityStarted() { 21 | mActivityStartCount++ 22 | if (mActivityStartCount == 1) { 23 | mIsForeground = true 24 | notifyObservers() 25 | } 26 | } 27 | 28 | fun onActivityStopped() { 29 | mActivityStartCount-- 30 | if (mActivityStartCount == 0) { 31 | mIsForeground = false 32 | notifyObservers() 33 | } 34 | } 35 | 36 | /** 37 | * 通知所有订阅者状态变化 38 | */ 39 | override fun notifyObservers() { 40 | mObservers.forEach { 41 | it.foregroundBackgroundNotify(mIsForeground) 42 | } 43 | } 44 | 45 | /** 46 | * 添加订阅者 47 | * 48 | * @param observer ForegroundBackgroundObserver 49 | */ 50 | override fun addObserve(observer: ForegroundBackgroundObserver) { 51 | mObservers.add(observer) 52 | } 53 | 54 | /** 55 | * 移除订阅者 56 | * 57 | * @param observer ForegroundBackgroundObserver 58 | */ 59 | override fun removeObserver(observer: ForegroundBackgroundObserver) { 60 | mObservers.remove(observer) 61 | } 62 | } 63 | 64 | /** 65 | * 订阅者需要实现的接口 66 | * 67 | * @author Qu Yunshuo 68 | * @since 2023/5/31 14:23 69 | */ 70 | interface ForegroundBackgroundObserver { 71 | fun foregroundBackgroundNotify(isForeground: Boolean) 72 | } 73 | 74 | /** 75 | * 被观察者抽象主题 76 | * 77 | * @author Qu Yunshuo 78 | * @since 2023/5/31 14:24 79 | */ 80 | interface ForegroundBackgroundSubject { 81 | fun notifyObservers() 82 | fun addObserve(observer: ForegroundBackgroundObserver) 83 | fun removeObserver(observer: ForegroundBackgroundObserver) 84 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ProcessUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.os.Process 7 | import kotlin.jvm.Throws 8 | 9 | /** 10 | * 进程工具类 11 | * 12 | * @author Qu Yunshuo 13 | * @since 3/16/21 9:06 AM 14 | */ 15 | object ProcessUtils { 16 | 17 | /** 18 | * 获取当前所有进程 19 | * 20 | * @param context Context 上下文 21 | * @return List 当前所有进程 22 | */ 23 | fun getRunningAppProcessList(context: Context): List { 24 | val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 25 | return activityManager.runningAppProcesses 26 | } 27 | 28 | /** 29 | * 判断该进程id是否属于该进程名的进程 30 | * 31 | * @param context Context 上下文 32 | * @param processId Int 进程Id 33 | * @param processName String 进程名 34 | * @return Boolean 35 | */ 36 | fun isPidOfProcessName(context: Context, processId: Int, processName: String): Boolean { 37 | // 遍历所有进程找到该进程id对应的进程 38 | for (process in getRunningAppProcessList(context)) { 39 | if (process.pid == processId) { 40 | // 判断该进程id是否和进程名一致 41 | return (process.processName == processName) 42 | } 43 | } 44 | return false 45 | } 46 | 47 | /** 48 | * 获取主进程名 49 | * 50 | * @param context Context 上下文 51 | * @return String 主进程名 52 | * @throws PackageManager.NameNotFoundException if a package with the given name cannot be found on the system. 53 | */ 54 | @Throws(PackageManager.NameNotFoundException::class) 55 | fun getMainProcessName(context: Context): String { 56 | val applicationInfo = context.packageManager.getApplicationInfo(context.packageName, 0) 57 | return applicationInfo.processName 58 | } 59 | 60 | /** 61 | * 判断当前进程是否是主进程 62 | * 63 | * @param context Context 上下文 64 | * @return Boolean 65 | * @throws PackageManager.NameNotFoundException if a package with the given name cannot be found on the system. 66 | */ 67 | @Throws(PackageManager.NameNotFoundException::class) 68 | fun isMainProcess(context: Context): Boolean { 69 | val processId = Process.myPid() 70 | val mainProcessName = getMainProcessName(context) 71 | return isPidOfProcessName(context, processId, mainProcessName) 72 | } 73 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/RegisterEventBus.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | /** 4 | * 辅助注册 EventBus 的注解 5 | * 6 | * - **使用方式:** 7 | * 在基类中的 `onCreate()`、`onDestroy()` 生命周期回调中去判断当前 Class 对象是否使用了该注解, 8 | * 然后根据结果去注册或反注册 9 | * 10 | * - **为什么不统一注册:** 11 | * 统一注册会在 EventBus 内部集合中留存,每次发送事件时,会遍历集合,过多无用的注册会导致速度变慢, 12 | * 所以最好的方式就是根据需要进行注册,避免无意义的全部注册 13 | * 14 | * - **sample:** 15 | * ``` 16 | * abstract class BaseActivity : AppCompatActivity() { 17 | * 18 | * // 是否有 [RegisterEventBus] 注解 , 避免重复调用 [Class.isAnnotation] 19 | * private var mHaveRegisterEventBus = false 20 | * override fun onCreate(savedInstanceState: Bundle?) { 21 | * super.onCreate(savedInstanceState) 22 | * // 根据子类是否有 RegisterEventBus 注解決定是否进行注册 EventBus 23 | * if (javaClass.isAnnotationPresent(RegisterEventBus::class.java)) { 24 | * mHaveRegisterEventBus = true 25 | * EventBusUtils.register(this) 26 | * } 27 | * } 28 | * 29 | * override fun onDestroy() { 30 | * // 根据子类是否有 RegisterEventBus 注解决定是否进行注册 EventBus 31 | * if (mHaveRegisterEventBus) { 32 | * EventBusUtils.unRegister(this) 33 | * } 34 | * super.onDestroy() 35 | * } 36 | * } 37 | * 38 | * // 子类: 39 | * @RegisterEventBus 40 | * class SampleActivity : BaseActivity() 41 | * ``` 42 | * 43 | * @author Qu Yunshuo 44 | * @since 2020/8/29 45 | */ 46 | @Target(AnnotationTarget.CLASS) 47 | @Retention(AnnotationRetention.RUNTIME) 48 | annotation class RegisterEventBus -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ResourcesFun.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import androidx.annotation.StringRes 4 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication.Companion.application as app 5 | 6 | fun getString(@StringRes stringRes: Int): String { 7 | return app.getString(stringRes) 8 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.content.Context 4 | import com.tencent.mmkv.MMKV 5 | 6 | /** 7 | * MMKV使用封装 8 | * 9 | * @author Qu Yunshuo 10 | * @since 8/28/20 11 | */ 12 | object SpUtils { 13 | 14 | /** 15 | * 初始化 16 | */ 17 | fun initMMKV(context: Context): String? = MMKV.initialize(context) 18 | 19 | /** 20 | * 保存数据(简化) 21 | * 根据value类型自动匹配需要执行的方法 22 | */ 23 | fun put(key: String, value: Any) = 24 | when (value) { 25 | is Int -> putInt(key, value) 26 | is Long -> putLong(key, value) 27 | is Float -> putFloat(key, value) 28 | is Double -> putDouble(key, value) 29 | is String -> putString(key, value) 30 | is Boolean -> putBoolean(key, value) 31 | else -> false 32 | } 33 | 34 | fun putString(key: String, value: String): Boolean? = MMKV.defaultMMKV()?.encode(key, value) 35 | 36 | fun getString(key: String, defValue: String): String? = 37 | MMKV.defaultMMKV()?.decodeString(key, defValue) 38 | 39 | fun putInt(key: String, value: Int): Boolean? = MMKV.defaultMMKV()?.encode(key, value) 40 | 41 | fun getInt(key: String, defValue: Int): Int? = MMKV.defaultMMKV()?.decodeInt(key, defValue) 42 | 43 | fun putLong(key: String, value: Long): Boolean? = MMKV.defaultMMKV()?.encode(key, value) 44 | 45 | fun getLong(key: String, defValue: Long): Long? = MMKV.defaultMMKV()?.decodeLong(key, defValue) 46 | 47 | fun putDouble(key: String, value: Double): Boolean? = MMKV.defaultMMKV()?.encode(key, value) 48 | 49 | fun getDouble(key: String, defValue: Double): Double? = 50 | MMKV.defaultMMKV()?.decodeDouble(key, defValue) 51 | 52 | fun putFloat(key: String, value: Float): Boolean? = MMKV.defaultMMKV()?.encode(key, value) 53 | 54 | fun getFloat(key: String, defValue: Float): Float? = 55 | MMKV.defaultMMKV()?.decodeFloat(key, defValue) 56 | 57 | fun putBoolean(key: String, value: Boolean): Boolean? = MMKV.defaultMMKV()?.encode(key, value) 58 | 59 | fun getBoolean(key: String, defValue: Boolean): Boolean? = 60 | MMKV.defaultMMKV()?.decodeBool(key, defValue) 61 | 62 | fun contains(key: String): Boolean? = MMKV.defaultMMKV()?.contains(key) 63 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/StateLayoutEnum.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | /** 4 | * 状态视图的状态枚举 5 | * 6 | * @author Qu Yunshuo 7 | * @since 2021/7/10 9:16 上午 8 | */ 9 | enum class StateLayoutEnum { 10 | HIDE, // 隐藏 11 | LOADING, // 加载中 12 | ERROR, // 错误 13 | NO_DATA // 没有数据 14 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ThreadUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.os.Build 4 | import android.os.Looper 5 | 6 | /** 7 | * 线程相关工具类 8 | * 9 | * @author Qu Yunshuo 10 | * @since 2023/3/12 19:29 11 | */ 12 | object ThreadUtils { 13 | 14 | /** 15 | * 判断当前是否是主线程 16 | * 在 [Build.VERSION.SDK_INT] >= [Build.VERSION_CODES.M] 有一个简化方法来判断当前线程是否是主线程 17 | * ``` 18 | * Looper.getMainLooper().isCurrentThread() 19 | * ``` 20 | * 21 | * @return Boolean 22 | */ 23 | fun isMainThread(): Boolean = Looper.getMainLooper().thread == Thread.currentThread() 24 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/ToastUtils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.os.Build 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.widget.Toast 7 | import androidx.annotation.StringRes 8 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 9 | 10 | private val mToastHandler by lazy { Handler(Looper.getMainLooper()) } 11 | 12 | private var mToast: Toast? = null 13 | 14 | @JvmOverloads 15 | fun toast(text: String, duration: Int = Toast.LENGTH_SHORT) { 16 | postToast(text, duration) 17 | } 18 | 19 | @JvmOverloads 20 | fun toast(@StringRes id: Int, duration: Int = Toast.LENGTH_SHORT) { 21 | postToast(getString(id), duration) 22 | } 23 | 24 | private fun postToast(text: String, duration: Int) { 25 | mToastHandler.post { 26 | setToast(text, duration) 27 | mToast?.show() 28 | } 29 | } 30 | 31 | private fun setToast(text: String, duration: Int) { 32 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { 33 | if (mToast == null) { 34 | mToast = Toast.makeText(BaseApplication.context, text, duration) 35 | } else { 36 | mToast?.duration = duration 37 | mToast?.setText(text) 38 | } 39 | } else { 40 | if (mToast != null) { 41 | mToast?.cancel() 42 | mToast = null 43 | } 44 | mToast = Toast.makeText(BaseApplication.context, text, duration) 45 | } 46 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils 2 | 3 | import android.util.Log 4 | import com.alibaba.android.arouter.launcher.ARouter 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.flow.catch 8 | import kotlinx.coroutines.flow.flow 9 | import kotlinx.coroutines.flow.flowOn 10 | 11 | /** 12 | * 使用 Flow 做的简单的轮询 13 | * 请使用单独的协程来进行管理该 Flow 14 | * Flow 仍有一些操作符是实验性的 使用时需添加 @InternalCoroutinesApi 注解 15 | * @param intervals 轮询间隔时间/毫秒 16 | * @param block 需要执行的代码块 17 | */ 18 | suspend fun startPolling(intervals: Long, block: () -> Unit) { 19 | flow { 20 | while (true) { 21 | delay(intervals) 22 | emit(0) 23 | } 24 | } 25 | .catch { Log.e("flow", "startPolling: $it") } 26 | .flowOn(Dispatchers.Main) 27 | .collect { block.invoke() } 28 | } 29 | /**************************************************************************************************/ 30 | 31 | /** 32 | * 发送普通EventBus事件 33 | */ 34 | fun sendEvent(event: Any) = EventBusUtils.postEvent(event) 35 | 36 | /**************************************************************************************************/ 37 | /** 38 | * 阿里路由不带参数跳转 39 | * @param routerUrl String 路由地址 40 | */ 41 | fun aRouterJump(routerUrl: String) { 42 | ARouter.getInstance().build(routerUrl).navigation() 43 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/AutoRegisterNetListener.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.network 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleObserver 5 | import androidx.lifecycle.OnLifecycleEvent 6 | 7 | /** 8 | * 自动注册网络状态监听 9 | * 使用的是[androidx.lifecycle.LifecycleObserver]来同步生命周期 10 | * 11 | * @author Qu Yunshuo 12 | * @since 2021/7/11 4:56 下午 13 | */ 14 | class AutoRegisterNetListener constructor(listener: NetworkStateChangeListener) : 15 | LifecycleObserver { 16 | 17 | /** 18 | * 当前需要自动注册的监听器 19 | */ 20 | private var mListener: NetworkStateChangeListener? = null 21 | 22 | init { 23 | mListener = listener 24 | } 25 | 26 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 27 | fun register() { 28 | mListener?.run { NetworkStateClient.setListener(this) } 29 | } 30 | 31 | @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 32 | fun unregister() { 33 | NetworkStateClient.removeListener() 34 | } 35 | 36 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 37 | fun clean() { 38 | NetworkStateClient.removeListener() 39 | mListener = null 40 | } 41 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/NetworkCallbackImpl.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.network 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.Network 5 | import android.net.NetworkCapabilities 6 | 7 | 8 | /** 9 | * 实时监听网络状态变化的[ConnectivityManager.NetworkCallback]实现类 10 | * 11 | * @author Qu Yunshuo 12 | * @since 2021/7/10 5:38 下午 13 | */ 14 | class NetworkCallbackImpl : ConnectivityManager.NetworkCallback() { 15 | 16 | /** 17 | * 当前网络类型 18 | */ 19 | var currentNetworkType: NetworkTypeEnum = NetworkTypeEnum.OTHER 20 | 21 | /** 22 | * 当前网络是否已连接 23 | */ 24 | var isConnected = false 25 | 26 | /** 27 | * 注册的监听 28 | */ 29 | var changeCall: NetworkStateChangeListener? = null 30 | 31 | override fun onAvailable(network: Network) { 32 | super.onAvailable(network) 33 | isConnected = true 34 | changeCall?.networkConnectChange(isConnected) 35 | } 36 | 37 | override fun onLost(network: Network) { 38 | super.onLost(network) 39 | isConnected = false 40 | changeCall?.networkConnectChange(isConnected) 41 | } 42 | 43 | override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { 44 | super.onCapabilitiesChanged(network, networkCapabilities) 45 | if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { 46 | currentNetworkType = networkTypeConvert(networkCapabilities) 47 | changeCall?.networkTypeChange(currentNetworkType) 48 | } 49 | } 50 | 51 | /** 52 | * 网络类型转换 53 | */ 54 | private fun networkTypeConvert(networkCapabilities: NetworkCapabilities): NetworkTypeEnum { 55 | return when { 56 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> { 57 | NetworkTypeEnum.TRANSPORT_CELLULAR 58 | } 59 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> { 60 | NetworkTypeEnum.TRANSPORT_WIFI 61 | } 62 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> { 63 | NetworkTypeEnum.TRANSPORT_BLUETOOTH 64 | } 65 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> { 66 | NetworkTypeEnum.TRANSPORT_ETHERNET 67 | } 68 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> { 69 | NetworkTypeEnum.TRANSPORT_VPN 70 | } 71 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE) -> { 72 | NetworkTypeEnum.TRANSPORT_WIFI_AWARE 73 | } 74 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN) -> { 75 | NetworkTypeEnum.TRANSPORT_LOWPAN 76 | } 77 | else -> NetworkTypeEnum.OTHER 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/NetworkStateChangeListener.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.network 2 | 3 | /** 4 | * 网络状态改变监听起 5 | * 6 | * @author Qu Yunshuo 7 | * @since 2021/7/11 4:56 下午 8 | */ 9 | interface NetworkStateChangeListener { 10 | 11 | /** 12 | * 网络类型更改回调 13 | * @param type NetworkTypeEnum 网络类型 14 | * @return Unit 15 | */ 16 | fun networkTypeChange(type: NetworkTypeEnum) 17 | 18 | /** 19 | * 网络连接状态更改回调 20 | * @param isConnected Boolean 是否已连接 21 | * @return Unit 22 | */ 23 | fun networkConnectChange(isConnected: Boolean) 24 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/NetworkStateClient.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.network 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | import android.net.NetworkRequest 7 | import androidx.annotation.RequiresPermission 8 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 9 | 10 | /** 11 | * 网络状态监听 12 | * 13 | * @author Qu Yunshuo 14 | * @since 2021/7/11 3:58 下午 15 | */ 16 | object NetworkStateClient { 17 | 18 | private val mNetworkCallback = NetworkCallbackImpl() 19 | 20 | /** 21 | * 注册网络监听客户端 22 | * @return Unit 23 | */ 24 | @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) 25 | fun register() { 26 | val build = NetworkRequest.Builder().build() 27 | val cm = 28 | BaseApplication.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 29 | cm.registerNetworkCallback(build, mNetworkCallback) 30 | } 31 | 32 | /** 33 | * 设置监听器 34 | * @param listener NetworkStateChangeListener 监听器 35 | * @return Unit 36 | */ 37 | fun setListener(listener: NetworkStateChangeListener) { 38 | mNetworkCallback.changeCall = listener 39 | } 40 | 41 | /** 42 | * 移除监听器 43 | * @return Unit 44 | */ 45 | fun removeListener() { 46 | mNetworkCallback.changeCall = null 47 | } 48 | 49 | /** 50 | * 获取网络类型 51 | * 当前网络类型是缓存的最近一次连接的网络类型,当无网络连接时其实拿到的是上一次的 52 | * 所以网络是否连接应该作为第一判断,确定网络是连接状态时再获取当前的网络类型,因为网络类型中没有设定无网 53 | * @return NetworkTypeEnum 参照[NetworkTypeEnum] 54 | */ 55 | fun getNetworkType(): NetworkTypeEnum = mNetworkCallback.currentNetworkType 56 | 57 | /** 58 | * 网络是否连接 59 | * @return Boolean 60 | */ 61 | fun isConnected(): Boolean = mNetworkCallback.isConnected 62 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/network/NetworkTypeEnum.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.network 2 | 3 | /** 4 | * 网络类型的枚举 5 | * 6 | * @author Qu Yunshuo 7 | * @since 2021/8/22 10:35 下午 8 | */ 9 | enum class NetworkTypeEnum { 10 | 11 | /** 12 | * 使用蜂窝移动网络传输 13 | */ 14 | TRANSPORT_CELLULAR, 15 | 16 | /** 17 | * 使用Wi-Fi传输 18 | */ 19 | TRANSPORT_WIFI, 20 | 21 | /** 22 | * 使用蓝牙传输 23 | */ 24 | TRANSPORT_BLUETOOTH, 25 | 26 | /** 27 | * 使用以太网传输 28 | */ 29 | TRANSPORT_ETHERNET, 30 | 31 | /** 32 | * 使用 VPN 传输 33 | */ 34 | TRANSPORT_VPN, 35 | 36 | /** 37 | * 使用 Wi-Fi Aware 传输 38 | */ 39 | TRANSPORT_WIFI_AWARE, 40 | 41 | /** 42 | * 使用 LoWPAN 传输 43 | */ 44 | TRANSPORT_LOWPAN, 45 | 46 | /** 47 | * 其他 48 | */ 49 | OTHER 50 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/status/ViewStatusHelper.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.status 2 | 3 | import android.os.Bundle 4 | 5 | /** 6 | * @author DBoy 2021/8/5

7 | * - 文件描述 : 采用了一种链式调用,所有对象持有自己父级帮助类,进行场景回复时先恢复链头的数据 8 | */ 9 | abstract class ViewStatusHelper(val parentViewStatusHelper: ViewStatusHelper?) { 10 | 11 | open fun onRestoreInstanceStatus(savedInstanceState: Bundle?) { 12 | parentViewStatusHelper?.onRestoreInstanceStatus(savedInstanceState) 13 | } 14 | 15 | open fun onSaveInstanceState(bundle: Bundle) { 16 | parentViewStatusHelper?.onSaveInstanceState(bundle) 17 | } 18 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/status/imp/BaseFrameViewStatusHelperImp.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.utils.status.imp 2 | 3 | import android.os.Bundle 4 | import com.quyunshuo.androidbaseframemvvm.base.utils.status.ViewStatusHelper 5 | 6 | /** 7 | * @author DBoy 2021/7/8 8 | * 9 | * - 文件描述 : 视图,activity,fragment重建帮助类 10 | */ 11 | open class BaseFrameViewStatusHelperImp(parentViewStatusHelper: ViewStatusHelper? = null) : ViewStatusHelper(parentViewStatusHelper) { 12 | /** 13 | * 重建标记key 以包名保存数据可以防止嵌套层级出现重复Key 14 | */ 15 | private val KEY_RECREATE = "com.quyunshuo.androidbaseframemvvm.base.utils.status.BaseFrameViewStatusHelperImp.Recreate" 16 | 17 | /** 18 | * 是否重建 19 | */ 20 | var isRecreate = false 21 | private set 22 | 23 | 24 | /** 25 | * 恢复状态 26 | */ 27 | override fun onRestoreInstanceStatus(savedInstanceState: Bundle?) { 28 | super.onRestoreInstanceStatus(savedInstanceState) 29 | isRecreate = savedInstanceState?.getBoolean(KEY_RECREATE) ?: false 30 | } 31 | 32 | /** 33 | * 保存状态 34 | */ 35 | override fun onSaveInstanceState(bundle: Bundle) { 36 | super.onSaveInstanceState(bundle) 37 | bundle.putBoolean(KEY_RECREATE, true) 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/view/OnSingleClickListener.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.base.view 2 | 3 | import android.view.View 4 | 5 | /** 6 | * 带有防抖效果的单击监听 7 | * 8 | * @param mDelayTime Int 防抖间隔时间,单位是毫秒,默认值 500ms 9 | * @param mListener (v: View) -> Unit 具体的点击事件 10 | * 11 | * @author Qu Yunshuo 12 | * @since 2023/3/15 23:39 13 | */ 14 | class OnSingleClickListener( 15 | private val mDelayTime: Int = 500, 16 | private val mListener: (v: View) -> Unit 17 | ) : View.OnClickListener { 18 | 19 | /** 20 | * 上次有效点击的时间 21 | */ 22 | private var mLastClickTime = 0L 23 | override fun onClick(v: View) { 24 | val currentTimeMillis = System.currentTimeMillis() 25 | if (currentTimeMillis - mLastClickTime >= mDelayTime) { 26 | mLastClickTime = currentTimeMillis 27 | mListener.invoke(v) 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /lib_base/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /lib_base/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 网路已断开 3 | 网络已连接 4 | -------------------------------------------------------------------------------- /lib_base/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib_common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /lib_common/build.gradle: -------------------------------------------------------------------------------- 1 | //**************************************** 2 | //********* lib_common 的配置文件 ********** 3 | //**************************************** 4 | 5 | plugins { 6 | alias(libs.plugins.library) 7 | alias(libs.plugins.kotlin) 8 | alias(libs.plugins.hilt) 9 | id "kotlin-kapt" 10 | } 11 | 12 | apply from: '../base_lib.gradle' 13 | 14 | import com.quyunshuo.androidbaseframemvvm.buildsrc.* 15 | 16 | android { 17 | 18 | defaultConfig { 19 | resValue "string", "BUGLY_APP_ID", SDKKeyConfig.BUGLY_APP_ID 20 | } 21 | 22 | buildFeatures { 23 | viewBinding = true 24 | } 25 | 26 | resourcePrefix "common_" 27 | namespace 'com.quyunshuo.androidbaseframemvvm.common' 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: "libs", include: ["*.jar"]) 32 | 33 | api project(path: ':lib_base') 34 | 35 | api DependencyConfig.JetPack.HiltCore 36 | 37 | kapt DependencyConfig.GitHub.ARouteCompiler 38 | kapt DependencyConfig.GitHub.EventBusAPT 39 | kapt DependencyConfig.GitHub.AutoServiceAnnotations 40 | kapt DependencyConfig.JetPack.HiltApt 41 | kapt DependencyConfig.JetPack.LifecycleCompilerAPT 42 | } -------------------------------------------------------------------------------- /lib_common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/lib_common/consumer-rules.pro -------------------------------------------------------------------------------- /lib_common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /lib_common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 19 | 20 | 21 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/CommonApplication.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.content.Context 6 | import android.util.Log 7 | import com.alibaba.android.arouter.launcher.ARouter 8 | import com.google.auto.service.AutoService 9 | import com.quyunshuo.androidbaseframemvvm.base.app.ApplicationLifecycle 10 | import com.quyunshuo.androidbaseframemvvm.base.BaseApplication 11 | import com.quyunshuo.androidbaseframemvvm.base.constant.VersionStatus 12 | import com.quyunshuo.androidbaseframemvvm.base.utils.ForegroundBackgroundObserver 13 | import com.quyunshuo.androidbaseframemvvm.base.utils.ProcessUtils 14 | import com.quyunshuo.androidbaseframemvvm.base.utils.SpUtils 15 | import com.quyunshuo.androidbaseframemvvm.base.utils.network.NetworkStateClient 16 | import com.tencent.bugly.crashreport.CrashReport 17 | import com.tencent.smtt.export.external.TbsCoreSettings 18 | import com.tencent.smtt.sdk.QbSdk 19 | import com.tencent.smtt.sdk.QbSdk.PreInitCallback 20 | 21 | /** 22 | * 项目相关的Application 23 | * 24 | * @author Qu Yunshuo 25 | * @since 4/16/21 3:37 PM 26 | */ 27 | @AutoService(ApplicationLifecycle::class) 28 | class CommonApplication : ApplicationLifecycle, ForegroundBackgroundObserver { 29 | 30 | companion object { 31 | // 全局CommonApplication 32 | @SuppressLint("StaticFieldLeak") 33 | lateinit var mCommonApplication: CommonApplication 34 | } 35 | 36 | /** 37 | * 同[Application.attachBaseContext] 38 | * @param context Context 39 | */ 40 | override fun onAttachBaseContext(context: Context) { 41 | mCommonApplication = this 42 | } 43 | 44 | /** 45 | * 同[Application.onCreate] 46 | * @param application Application 47 | */ 48 | override fun onCreate(application: Application) {} 49 | 50 | /** 51 | * 同[Application.onTerminate] 52 | * @param application Application 53 | */ 54 | override fun onTerminate(application: Application) {} 55 | 56 | /** 57 | * 主线程前台初始化 58 | * @return MutableList<() -> String> 初始化方法集合 59 | */ 60 | override fun initByFrontDesk(): MutableList<() -> String> { 61 | val list = mutableListOf<() -> String>() 62 | // 以下只需要在主进程当中初始化 按需要调整 63 | if (ProcessUtils.isMainProcess(BaseApplication.context)) { 64 | list.add { initMMKV() } 65 | list.add { initARouter() } 66 | list.add { initNetworkStateClient() } 67 | } 68 | list.add { initTencentBugly() } 69 | return list 70 | } 71 | 72 | /** 73 | * 不需要立即初始化的放在这里进行后台初始化 74 | */ 75 | override fun initByBackstage() { 76 | initX5WebViewCore() 77 | } 78 | 79 | /** 80 | * 初始化网络状态监听客户端 81 | * @return Unit 82 | */ 83 | private fun initNetworkStateClient(): String { 84 | NetworkStateClient.register() 85 | return "NetworkStateClient -->> init complete" 86 | } 87 | 88 | /** 89 | * 腾讯TBS WebView X5 内核初始化 90 | */ 91 | private fun initX5WebViewCore() { 92 | // dex2oat优化方案 93 | val map = HashMap() 94 | map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true 95 | map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true 96 | QbSdk.initTbsSettings(map) 97 | 98 | // 允许使用非wifi网络进行下载 99 | QbSdk.setDownloadWithoutWifi(true) 100 | 101 | // 初始化 102 | QbSdk.initX5Environment(BaseApplication.context, object : PreInitCallback { 103 | 104 | override fun onCoreInitFinished() { 105 | Log.d("ApplicationInit", " TBS X5 init finished") 106 | } 107 | 108 | override fun onViewInitFinished(p0: Boolean) { 109 | // 初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核 110 | Log.d("ApplicationInit", " TBS X5 init is $p0") 111 | } 112 | }) 113 | } 114 | 115 | /** 116 | * 腾讯 MMKV 初始化 117 | */ 118 | private fun initMMKV(): String { 119 | val result = SpUtils.initMMKV(BaseApplication.context) 120 | return "MMKV -->> $result" 121 | } 122 | 123 | /** 124 | * 阿里路由 ARouter 初始化 125 | */ 126 | private fun initARouter(): String { 127 | // 测试环境下打开ARouter的日志和调试模式 正式环境需要关闭 128 | if (BuildConfig.VERSION_TYPE != VersionStatus.RELEASE) { 129 | ARouter.openLog() // 打印日志 130 | ARouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) 131 | } 132 | ARouter.init(BaseApplication.application) 133 | return "ARouter -->> init complete" 134 | } 135 | 136 | /** 137 | * 初始化 腾讯Bugly 138 | * 测试环境应该与正式环境的日志收集渠道分隔开 139 | * 目前有两个渠道 测试版本/正式版本 140 | */ 141 | private fun initTencentBugly(): String { 142 | // 第三个参数为SDK调试模式开关 143 | CrashReport.initCrashReport( 144 | BaseApplication.context, 145 | BaseApplication.context.getString(R.string.BUGLY_APP_ID), 146 | BuildConfig.VERSION_TYPE != VersionStatus.RELEASE 147 | ) 148 | return "Bugly -->> init complete" 149 | } 150 | 151 | override fun foregroundBackgroundNotify(isForeground: Boolean) { 152 | Log.d("ForegroundBackground", "isForeground: $isForeground") 153 | } 154 | } -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/constant/NetBaseUrlConstant.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.constant 2 | 3 | /** 4 | * 接口公共地址 5 | * 6 | * @author Qu Yunshuo 7 | * @since 4/17/21 3:27 PM 8 | */ 9 | internal object NetBaseUrlConstant { 10 | 11 | val MAIN_URL = "http://www.baidu.com" 12 | get() { 13 | if (field.isEmpty()){ 14 | throw NotImplementedError("请求改你的 MAIN_URL 的值为自己的请求地址") 15 | } 16 | return field 17 | } 18 | } -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/constant/RouteKey.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.constant 2 | 3 | /** 4 | * @Author: QuYunShuo 5 | * @Time: 2020/8/28 6 | * @Class: RouteKey 7 | * @Remark: 路由使用中 用到的Key 统一写在此类中 8 | */ 9 | object RouteKey -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/constant/RouteUrl.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.constant 2 | 3 | /** 4 | * @Author: QuYunShuo 5 | * @Time: 2020/8/28 6 | * @Class: RoutePath 7 | * @Remark: 路由地址 8 | */ 9 | object RouteUrl -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/constant/SpKey.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.constant 2 | 3 | /** 4 | * @Author: QuYunShuo 5 | * @Time: 2020/8/29 6 | * @Class: SpKey 7 | * @Remark: 本地存储的键 放在此类中 8 | */ 9 | object SpKey -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/di/DINetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.di 2 | 3 | import com.quyunshuo.androidbaseframemvvm.base.BuildConfig 4 | import com.quyunshuo.androidbaseframemvvm.base.constant.VersionStatus 5 | import com.quyunshuo.androidbaseframemvvm.common.constant.NetBaseUrlConstant 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import okhttp3.OkHttpClient 11 | import okhttp3.logging.HttpLoggingInterceptor 12 | import retrofit2.Retrofit 13 | import java.util.concurrent.TimeUnit 14 | import okhttp3.logging.HttpLoggingInterceptor.Level.NONE 15 | import okhttp3.logging.HttpLoggingInterceptor.Level.BODY 16 | import retrofit2.converter.gson.GsonConverterFactory 17 | import javax.inject.Singleton 18 | 19 | /** 20 | * 全局作用域的网络层的依赖注入模块 21 | * 22 | * @author Qu Yunshuo 23 | * @since 6/4/21 8:58 AM 24 | */ 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | class DINetworkModule { 28 | 29 | /** 30 | * [OkHttpClient]依赖提供方法 31 | * 32 | * @return OkHttpClient 33 | */ 34 | @Singleton 35 | @Provides 36 | fun provideOkHttpClient(): OkHttpClient { 37 | // 日志拦截器部分 38 | val level = if (BuildConfig.VERSION_TYPE != VersionStatus.RELEASE) BODY else NONE 39 | val logInterceptor = HttpLoggingInterceptor().setLevel(level) 40 | 41 | return OkHttpClient.Builder() 42 | .connectTimeout(15L * 1000L, TimeUnit.MILLISECONDS) 43 | .readTimeout(20L * 1000L, TimeUnit.MILLISECONDS) 44 | .addInterceptor(logInterceptor) 45 | .retryOnConnectionFailure(true) 46 | .build() 47 | } 48 | 49 | /** 50 | * 项目主要服务器地址的[Retrofit]依赖提供方法 51 | * 52 | * @param okHttpClient OkHttpClient OkHttp客户端 53 | * @return Retrofit 54 | */ 55 | @Singleton 56 | @Provides 57 | fun provideMainRetrofit(okHttpClient: OkHttpClient): Retrofit { 58 | return Retrofit.Builder() 59 | .baseUrl(NetBaseUrlConstant.MAIN_URL) 60 | .addConverterFactory(GsonConverterFactory.create()) 61 | .client(okHttpClient) 62 | .build() 63 | } 64 | } -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/helper/ExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.helper 2 | 3 | import com.quyunshuo.androidbaseframemvvm.base.utils.toast 4 | 5 | /** 6 | * 响应code异常统一处理 7 | * 8 | * 该方法主要做两件事: 9 | * 10 | * - 1.做统一的code码处理 11 | * - 2.未进行统一处理的code码会被转换为自定义异常[ResponseException]抛出 12 | * 13 | * 使用方式为:进行统一处理的异常进行抛出[ResponseEmptyException],未进行处理的code抛出[ResponseException],成功状态下执行[successBlock] 14 | * 15 | * @param code Int code码 16 | * @param msg String? 错误信息 17 | * @param successBlock suspend () -> Unit 没有异常的情况下执行的方法体 可以在此处进行数据的发射 18 | * @throws ResponseException 未进行处理的异常会进行抛出,让ViewModel去做进一步处理 19 | */ 20 | @Throws(ResponseException::class) 21 | suspend fun responseCodeExceptionHandler( 22 | code: Int, 23 | msg: String?, 24 | successBlock: suspend () -> Unit 25 | ) { 26 | // 进行异常的处理 27 | when (code) { 28 | ResponseCodeEnum.ERROR.getCode() -> { 29 | toast(ResponseCodeEnum.ERROR.getMessage()) 30 | throw ResponseEmptyException() 31 | } 32 | ResponseCodeEnum.SUCCESS.getCode() -> successBlock.invoke() 33 | } 34 | } -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/helper/ResponseCodeEnum.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.helper 2 | 3 | /** 4 | * 请求响应code枚举抽象 5 | * 6 | * @author Qu Yunshuo 7 | * @since 2021/7/9 2:56 下午 8 | */ 9 | interface IResponseCode { 10 | 11 | /** 12 | * 获取该枚举的code码 13 | * @return Int 14 | */ 15 | fun getCode(): Int 16 | 17 | /** 18 | * 获取该枚举的描述 19 | * @return String 20 | */ 21 | fun getMessage(): String 22 | } 23 | 24 | /** 25 | * 请求响应code的枚举 26 | * 27 | * @author Qu Yunshuo 28 | * @since 2021/7/9 2:55 下午 29 | */ 30 | enum class ResponseCodeEnum : IResponseCode { 31 | 32 | // 通用异常 33 | ERROR { 34 | override fun getCode() = 100 35 | override fun getMessage() = "处理失败" 36 | }, 37 | 38 | // 成功 39 | SUCCESS { 40 | override fun getCode() = 200 41 | override fun getMessage() = "成功" 42 | } 43 | } -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/helper/ResponseException.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.helper 2 | 3 | /** 4 | * 自定义响应异常的抽象类型 5 | * 6 | * @author Qu Yunshuo 7 | * @since 2021/8/27 9:50 上午 8 | */ 9 | interface IResponseException 10 | 11 | /** 12 | * 请求响应异常,主要为各种code码专门定义的异常 13 | * 14 | * @param type IResponseCode 异常类型枚举,用于标记该异常的类型 15 | * @param msg String 异常信息 16 | * 17 | * @author Qu Yunshuo 18 | * @since 2021/7/9 2:57 下午 19 | */ 20 | class ResponseException(val type: IResponseCode, val msg: String) : Exception(), IResponseException 21 | 22 | /** 23 | * 空异常,表示该异常已经被处理过了,不需要再做额外处理了 24 | * 25 | * @author Qu Yunshuo 26 | * @since 2021/7/9 3:11 下午 27 | */ 28 | class ResponseEmptyException : Exception(), IResponseException -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/ui/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.ui 2 | 3 | import android.util.Log 4 | import androidx.viewbinding.ViewBinding 5 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.v.BaseFrameActivity 6 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel 7 | import com.quyunshuo.androidbaseframemvvm.base.utils.ActivityStackManager 8 | import com.quyunshuo.androidbaseframemvvm.base.utils.AndroidBugFixUtils 9 | import com.quyunshuo.androidbaseframemvvm.base.utils.BarUtils 10 | 11 | /** 12 | * Activity基类 13 | * 14 | * @author Qu Yunshuo 15 | * @since 8/27/20 16 | */ 17 | abstract class BaseActivity : BaseFrameActivity() { 18 | 19 | /** 20 | * 设置状态栏 21 | * 子类需要自定义时重写该方法即可 22 | * @return Unit 23 | */ 24 | override fun setStatusBar() { 25 | BarUtils.transparentStatusBar(this) 26 | BarUtils.setStatusBarLightMode(this, true) 27 | } 28 | 29 | override fun onResume() { 30 | super.onResume() 31 | Log.d("ActivityLifecycle", "ActivityStack: ${ActivityStackManager.activityStack}") 32 | } 33 | 34 | override fun onDestroy() { 35 | super.onDestroy() 36 | // 解决某些特定机型会触发的Android本身的Bug 37 | AndroidBugFixUtils().fixSoftInputLeaks(this) 38 | } 39 | } -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/ui/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.ui 2 | 3 | import androidx.viewbinding.ViewBinding 4 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.v.BaseFrameFragment 5 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel 6 | 7 | /** 8 | * Fragment基类 9 | * 10 | * @author Qu Yunshuo 11 | * @since 8/27/20 12 | */ 13 | abstract class BaseFragment : BaseFrameFragment() -------------------------------------------------------------------------------- /lib_common/src/main/java/com/quyunshuo/androidbaseframemvvm/common/ui/BaseFragmentStateAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.androidbaseframemvvm.common.ui 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentActivity 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import androidx.viewpager2.adapter.FragmentStateAdapter 8 | import kotlin.random.Random 9 | 10 | /** 11 | * @author DBoy 2021/8/5

12 | * - 文件描述 : ViewPager2 FragmentAdapter封装 13 | * - 对于元数据[mData]的增加删除操作只能通过内部提供的4种方式进行操作: 14 | * - [setNewData] 15 | * - [addNewData] 16 | * - [addData] 17 | * - [removeData] 18 | * - 内部使用[DiffUtil]工具实现更新UI,不需调用[notifyDataSetChanged]等一系列方法。 19 | */ 20 | abstract class BaseFragmentStateAdapter : FragmentStateAdapter { 21 | 22 | private val TAG = "FragmentAdapter" 23 | 24 | /** 25 | * 记录生成的Fragment id列表 26 | */ 27 | private var mFragmentIdMap = mutableMapOf() 28 | 29 | /** 30 | * 需要生成页面的数据 31 | */ 32 | var mData: MutableList 33 | set(value) { 34 | field = value 35 | mFragmentIdMap.clear() 36 | createFragmentsIds(field) 37 | } 38 | 39 | constructor(fragment: Fragment, data: MutableList = mutableListOf()) : super(fragment) { 40 | mData = data 41 | } 42 | 43 | constructor(activity: FragmentActivity, data: MutableList = mutableListOf()) : super( 44 | activity 45 | ) { 46 | mData = data 47 | } 48 | 49 | /** 50 | * 获取需要创建几个Fragment 51 | */ 52 | override fun getItemCount(): Int = mData.size 53 | 54 | /** 55 | * 创建Fragment 56 | */ 57 | final override fun createFragment(position: Int): Fragment { 58 | return createFragment(mData[position], position) 59 | } 60 | 61 | /** 62 | * 创建fragment 传递数据 63 | */ 64 | abstract fun createFragment(item: T, position: Int): Fragment 65 | 66 | /** 67 | * 获取fragment 对应 id 68 | */ 69 | override fun getItemId(position: Int): Long { 70 | if (position >= mData.size) return RecyclerView.NO_ID 71 | return mFragmentIdMap[mData[position]] ?: return RecyclerView.NO_ID 72 | } 73 | 74 | /** 75 | * 判断是否包含这个id的数 76 | */ 77 | override fun containsItem(itemId: Long): Boolean { 78 | return mFragmentIdMap.values.contains(itemId) 79 | } 80 | 81 | /** 82 | * 设置新数据 83 | */ 84 | fun setNewData(data: MutableList = mutableListOf()) { 85 | val oldData = copyData() 86 | mData = data 87 | diffNotifyDataSetChanged(oldData, mData) 88 | } 89 | 90 | /** 91 | * 累加新数据 92 | */ 93 | fun addNewData(data: MutableList = mutableListOf()) { 94 | val oldData = copyData() 95 | mData.addAll(data) 96 | //创建新的对应位置的id 97 | createFragmentsIds(data) 98 | diffNotifyDataSetChanged(oldData, mData) 99 | 100 | } 101 | 102 | /** 103 | * 添加数据 104 | */ 105 | fun addData(data: T) { 106 | val oldData = copyData() 107 | mData.add(data) 108 | //随机一个id对应当前位置Fragment,两次随机确保同id率为最低概率 109 | mFragmentIdMap[data] = Random.nextLong() - Random.nextInt() 110 | diffNotifyDataSetChanged(oldData, mData) 111 | } 112 | 113 | /** 114 | * 移除某个数据 115 | */ 116 | fun removeData(data: T): Boolean { 117 | val oldData = copyData() 118 | if (mData.remove(data)) { 119 | mFragmentIdMap.remove(data) 120 | diffNotifyDataSetChanged(oldData, mData) 121 | return true 122 | } 123 | return false 124 | } 125 | 126 | /** 127 | * 移除某个位置的数据 128 | */ 129 | fun removeData(position: Int): Boolean { 130 | if (position < mData.size && position < mFragmentIdMap.size) { 131 | val oldData = copyData() 132 | val removeItem = mData.removeAt(position) 133 | mFragmentIdMap.remove(removeItem) 134 | diffNotifyDataSetChanged(oldData, mData) 135 | return true 136 | } 137 | return false 138 | } 139 | 140 | /** 141 | * 拷贝原数据 142 | */ 143 | private fun copyData(): MutableList { 144 | val oldData = mutableListOf() 145 | oldData.addAll(mData) 146 | return oldData 147 | } 148 | 149 | /** 150 | * 使用diff工具更新UI,当前diff工具对比使用的方式是 [==] 所以如果需要精确对比不同item数据,可以重写[T]的[equals]方法. 151 | */ 152 | private fun diffNotifyDataSetChanged(oldData: MutableList, newData: MutableList) { 153 | DiffUtil.calculateDiff(object : DiffUtil.Callback() { 154 | 155 | override fun getOldListSize(): Int = oldData.size 156 | 157 | override fun getNewListSize(): Int = newData.size 158 | 159 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldData[oldItemPosition] == newData[newItemPosition] 160 | 161 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldData[oldItemPosition] == newData[newItemPosition] 162 | 163 | }, true).dispatchUpdatesTo(this) 164 | } 165 | 166 | 167 | /** 168 | * 创建[mData]对应Fragment的id 169 | */ 170 | private fun createFragmentsIds(data: MutableList) { 171 | for (item in data) { 172 | mFragmentIdMap[item] = Random.nextLong() - Random.nextInt() 173 | } 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /lib_common/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #00BCD4 5 | -------------------------------------------------------------------------------- /module_home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /module_home/build.gradle: -------------------------------------------------------------------------------- 1 | import com.quyunshuo.androidbaseframemvvm.buildsrc.ProjectBuildConfig 2 | 3 | //**************************************** 4 | //******** module_home 的配置文件 ********* 5 | //**************************************** 6 | 7 | plugins { 8 | alias(libs.plugins.kotlin) 9 | alias(libs.plugins.hilt) 10 | id "kotlin-kapt" 11 | } 12 | if (ProjectBuildConfig.isAppMode) { 13 | apply plugin: 'com.android.application' 14 | } else { 15 | apply plugin: 'com.android.library' 16 | } 17 | 18 | apply from: '../base_module.gradle' 19 | 20 | android { 21 | resourcePrefix "home_" 22 | namespace 'com.quyunshuo.module.home' 23 | } -------------------------------------------------------------------------------- /module_home/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quyunshuo/AndroidBaseFrameMVVM/df93eeec3f80363a1fdfbad1fede151fa0aeb792/module_home/consumer-rules.pro -------------------------------------------------------------------------------- /module_home/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /module_home/src/androidTest/java/com/quyunshuo/module/home/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.module.home 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.quyunshuo.module.home.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /module_home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/activity/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.module.home.activity 2 | 3 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.m.BaseRepository 4 | import com.quyunshuo.module.home.net.HomeApiService 5 | import kotlinx.coroutines.delay 6 | import javax.inject.Inject 7 | 8 | /** 9 | * 首页M层 10 | * 11 | * @author Qu Yunshuo 12 | * @since 5/25/21 5:42 PM 13 | */ 14 | class HomeRepository @Inject constructor() : BaseRepository() { 15 | 16 | @Inject 17 | lateinit var mApi: HomeApiService 18 | 19 | /** 20 | * 模拟获取数据 21 | */ 22 | suspend fun getData() = request { 23 | delay(1000L) 24 | emit("Hello Hilt") 25 | } 26 | } -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/activity/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.module.home.activity 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.MutableLiveData 5 | import com.quyunshuo.androidbaseframemvvm.base.ktx.launchIO 6 | import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.flow.catch 9 | import kotlinx.coroutines.flow.collect 10 | import javax.inject.Inject 11 | 12 | /** 13 | * 首页的VM层 14 | * 15 | * @property mRepository HomeRepository 仓库层 通过Hilt注入 16 | * 17 | * @author Qu Yunshuo 18 | * @since 5/25/21 5:41 PM 19 | */ 20 | @HiltViewModel 21 | class HomeViewModel @Inject constructor(private val mRepository: HomeRepository) : BaseViewModel() { 22 | 23 | val data = MutableLiveData() 24 | 25 | /** 26 | * 模拟获取数据 27 | */ 28 | fun getData() { 29 | launchIO { 30 | mRepository.getData() 31 | .catch { Log.d("qqq", "getData: $it") } 32 | .collect { data.postValue(it) } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/activity/InternalPagerActivity.kt: -------------------------------------------------------------------------------- 1 | //package com.quyunshuo.module.home.activity 2 | // 3 | //import android.os.Bundle 4 | //import androidx.activity.viewModels 5 | //import androidx.fragment.app.Fragment 6 | //import androidx.fragment.app.FragmentActivity 7 | //import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.EmptyViewModel 8 | //import com.quyunshuo.androidbaseframemvvm.common.ui.BaseActivity 9 | //import com.quyunshuo.androidbaseframemvvm.common.ui.BaseFragmentStateAdapter 10 | //import com.quyunshuo.module.home.databinding.HomeActivityInternalLayoutBinding 11 | //import com.quyunshuo.module.home.fragment.InternalFragment 12 | //import dagger.hilt.android.AndroidEntryPoint 13 | //import kotlin.random.Random 14 | // 15 | ///** 16 | // * @author DBoy 2021/7/6

17 | // * - 文件描述 : ViewPager2+fragment 模拟Fragment页面重建。 18 | // */ 19 | //@AndroidEntryPoint 20 | //class InternalPagerActivity : BaseActivity() { 21 | // 22 | // override val mViewModel: EmptyViewModel by viewModels() 23 | // 24 | // private val mCreateFragmentData = mutableListOf() 25 | // 26 | // private var mAdapter: InternalPagerFragmentAdapter? = null 27 | // 28 | // 29 | // override fun HomeActivityInternalLayoutBinding.initView() { 30 | // addFragment.setOnClickListener { 31 | // //添加一个随机页面 32 | // mAdapter?.addData("Pager ID:${Random.nextInt()}") 33 | // } 34 | // removeFragment.setOnClickListener { 35 | // //移除当前展示页面 36 | //// mAdapter?.removeData("更多") 37 | // mAdapter?.removeData(viewPager.currentItem) 38 | // } 39 | // initPager() 40 | // } 41 | // 42 | // private fun initPager() { 43 | // mCreateFragmentData.add("首页") 44 | // mCreateFragmentData.add("我的") 45 | // mCreateFragmentData.add("设置") 46 | // mCreateFragmentData.add("更多") 47 | // mCreateFragmentData.add("动态") 48 | // mAdapter = InternalPagerFragmentAdapter(this, mCreateFragmentData) 49 | // mBinding.viewPager.adapter = mAdapter 50 | // 51 | // } 52 | // 53 | // override fun initObserve() {} 54 | // 55 | // override fun initRequestData() {} 56 | // 57 | // class InternalPagerFragmentAdapter(activity: FragmentActivity, data: MutableList = mutableListOf()) : 58 | // BaseFragmentStateAdapter(activity, data) { 59 | // override fun createFragment(item: String, position: Int): Fragment { 60 | // val bundle = Bundle().apply { 61 | // putString("What", item) 62 | // } 63 | // return when (item) { 64 | // "首页" -> { 65 | // //假装首页 66 | // InternalFragment() 67 | // } 68 | // "我的" -> { 69 | // //假装我的 70 | // InternalFragment() 71 | // } 72 | // "设置" -> { 73 | // //假装设置 74 | // InternalFragment() 75 | // } 76 | // "更多" -> { 77 | // //假装更多 78 | // InternalFragment() 79 | // } 80 | // else -> { 81 | // //另外动态item创建类型 82 | // InternalFragment() 83 | // } 84 | // }.apply { 85 | // //设置传递参数bundle 86 | // arguments = bundle 87 | // } 88 | // } 89 | // 90 | // 91 | // } 92 | // 93 | //} -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.module.home.activity 2 | 3 | import android.graphics.Color 4 | import androidx.activity.viewModels 5 | import com.quyunshuo.androidbaseframemvvm.base.ktx.observeLiveData 6 | import com.quyunshuo.androidbaseframemvvm.common.ui.BaseActivity 7 | import com.quyunshuo.module.home.databinding.HomeActivityMainBinding 8 | import dagger.hilt.android.AndroidEntryPoint 9 | 10 | /** 11 | * 首页 12 | * 13 | * @author Qu Yunshuo 14 | * @since 5/22/21 2:26 PM 15 | */ 16 | @AndroidEntryPoint 17 | class MainActivity : BaseActivity() { 18 | 19 | /** 20 | * 通过 viewModels() + Hilt 获取 ViewModel 实例 21 | */ 22 | override val mViewModel by viewModels() 23 | 24 | override fun createVB() = HomeActivityMainBinding.inflate(layoutInflater) 25 | 26 | override fun HomeActivityMainBinding.initView() {} 27 | 28 | override fun initObserve() { 29 | observeLiveData(mViewModel.data, ::processData) 30 | } 31 | 32 | private fun processData(data: String) { 33 | mBinding.vTvHello.text = data 34 | mBinding.vTvHello.setTextColor(Color.BLUE) 35 | } 36 | 37 | override fun initRequestData() { 38 | // 模拟获取数据 39 | mViewModel.getData() 40 | } 41 | } -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/di/DIHomeNetServiceModule.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.module.home.di 2 | 3 | import com.quyunshuo.module.home.net.HomeApiService 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import retrofit2.Retrofit 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * 全局作用域的Home组件网络接口代理依赖注入模块 13 | * 14 | * @author Qu Yunshuo 15 | * @since 6/4/21 5:51 PM 16 | */ 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | class DIHomeNetServiceModule { 20 | 21 | /** 22 | * Home模块的[HomeApiService]依赖提供方法 23 | * 24 | * @param retrofit Retrofit 25 | * @return HomeApiService 26 | */ 27 | @Singleton 28 | @Provides 29 | fun provideHomeApiService(retrofit: Retrofit): HomeApiService { 30 | return retrofit.create(HomeApiService::class.java) 31 | } 32 | } -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/fragment/InternalFragment.kt: -------------------------------------------------------------------------------- 1 | //package com.quyunshuo.module.home.fragment 2 | // 3 | //import android.os.Bundle 4 | //import android.util.Log 5 | //import androidx.fragment.app.viewModels 6 | //import com.quyunshuo.androidbaseframemvvm.base.utils.status.ViewStatusHelper 7 | //import com.quyunshuo.androidbaseframemvvm.common.ui.BaseFragment 8 | //import com.quyunshuo.module.home.databinding.HomeFragmentInternalLayoutBinding 9 | //import dagger.hilt.android.AndroidEntryPoint 10 | // 11 | ///** 12 | // * @author DBoy 2021/7/6

13 | // * - 文件描述 : 测试fragment 14 | // */ 15 | //@AndroidEntryPoint 16 | //class InternalFragment : BaseFragment() { 17 | // 18 | // override val mViewModel by viewModels() 19 | // 20 | // /** 21 | // * 页面状态数据管理帮助类 22 | // */ 23 | // private lateinit var mInternalFragmentStatusHelper: InternalFragmentStatusHelper 24 | // 25 | // /*** 26 | // * 注册帮助类 27 | // */ 28 | // override fun onRegisterStatusHelper(): ViewStatusHelper? { 29 | // mInternalFragmentStatusHelper = InternalFragmentStatusHelper(super.onRegisterStatusHelper()) 30 | // return mInternalFragmentStatusHelper 31 | // } 32 | // 33 | // override fun HomeFragmentInternalLayoutBinding.initView() {} 34 | // 35 | // 36 | // override fun initObserve() { 37 | // mViewModel.increase(mInternalFragmentStatusHelper.rebuildSize) 38 | // val s = arguments?.getString("What") ?: "" 39 | // mBinding.toolBarTitle.text = s 40 | // mViewModel.recreatedCont.observe(viewLifecycleOwner) { 41 | // mBinding.recreateContTv.text = "重建次数 $it" 42 | // } 43 | // mViewModel.firstData.observe(viewLifecycleOwner) { 44 | // mBinding.loadDataTv.text = it 45 | // } 46 | //// mViewModel.isLoading.observe(viewLifecycleOwner) { 47 | //// mBinding.loadingStatusTv.text = if (it) { 48 | //// "正在加载..." 49 | //// } else { 50 | //// "加载完成!" 51 | //// } 52 | //// } 53 | // } 54 | // 55 | // override fun initRequestData() { 56 | // //当页面重建的时候不再重新请求数据,且当前页面数据数据有且没有刷新逻辑的情况下不再请求数据。 57 | // if (isRecreate() && mViewModel.firstData.value != null) { 58 | // return 59 | // } 60 | // mViewModel.getData() 61 | // } 62 | // 63 | // /** 64 | // * 当前Fragment重建帮助类 65 | // */ 66 | // internal class InternalFragmentStatusHelper(parentViewStatusHelper: ViewStatusHelper?) : ViewStatusHelper(parentViewStatusHelper) { 67 | // /** 68 | // * 重建次数 69 | // */ 70 | // var rebuildSize = 0 71 | // 72 | // private val KEY_REBUILD = "com.quyunshuo.module.home.fragment.InternalFragment.InternalFragmentStatusHelper.rebuild" 73 | // 74 | // override fun onRestoreInstanceStatus(savedInstanceState: Bundle?) { 75 | // super.onRestoreInstanceStatus(savedInstanceState) 76 | // rebuildSize = (savedInstanceState?.getInt(KEY_REBUILD) ?: 0) + 1 77 | // } 78 | // 79 | // override fun onSaveInstanceState(bundle: Bundle) { 80 | // super.onSaveInstanceState(bundle) 81 | // bundle.putInt(KEY_REBUILD, rebuildSize) 82 | // } 83 | // } 84 | // 85 | //} -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/fragment/InternalRepository.kt: -------------------------------------------------------------------------------- 1 | //package com.quyunshuo.module.home.fragment 2 | // 3 | //import com.quyunshuo.androidbaseframemvvm.base.mvvm.m.BaseRepository 4 | //import kotlinx.coroutines.delay 5 | //import javax.inject.Inject 6 | // 7 | ///** 8 | // * @author DBoy 2021/7/6

9 | // * - 文件描述 : 10 | // */ 11 | //class InternalRepository @Inject constructor() : BaseRepository() { 12 | // 13 | // suspend fun getData() = request { 14 | // delay(1000) 15 | // emit("数据加载成功") 16 | // } 17 | //} -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/fragment/InternalViewModel.kt: -------------------------------------------------------------------------------- 1 | //package com.quyunshuo.module.home.fragment 2 | // 3 | //import android.util.Log 4 | //import androidx.lifecycle.MutableLiveData 5 | //import androidx.lifecycle.viewModelScope 6 | //import com.quyunshuo.androidbaseframemvvm.base.mvvm.vm.BaseViewModel 7 | //import com.quyunshuo.androidbaseframemvvm.base.utils.status.ViewStatusHelper 8 | //import dagger.hilt.android.lifecycle.HiltViewModel 9 | //import kotlinx.coroutines.Dispatchers 10 | //import kotlinx.coroutines.delay 11 | //import kotlinx.coroutines.flow.catch 12 | //import kotlinx.coroutines.flow.collect 13 | //import kotlinx.coroutines.flow.onStart 14 | //import kotlinx.coroutines.launch 15 | //import javax.inject.Inject 16 | // 17 | ///** 18 | // * @author DBoy 2021/7/6

19 | // * - 文件描述 : ViewModel再ViewPager2的Fragment中会随着Fragment执行[Fragment.onDestory]一同销毁。 20 | // * 所以一些需要长期保存的变量数据,不适合保存再ViewModel,考虑使用[ViewStatusHelper]保存页面上部分数据, 21 | // * 页面恢复的时候再交给ViewModel处理,例如[recreatedCont] 22 | // */ 23 | //@HiltViewModel 24 | //class InternalViewModel @Inject constructor() : 25 | // BaseViewModel() { 26 | // 27 | // @Inject 28 | // lateinit var repository: InternalRepository 29 | // 30 | // /** 31 | // * 重建计数 32 | // */ 33 | // val recreatedCont = MutableLiveData() 34 | // 35 | // /** 36 | // * 首个数据 37 | // */ 38 | // val firstData = MutableLiveData() 39 | // 40 | // /** 41 | // * 累加重建次数 42 | // */ 43 | // fun increase(size: Int) { 44 | // recreatedCont.value = size 45 | // } 46 | // 47 | // /** 48 | // * 获取数据 49 | // */ 50 | // fun getData() { 51 | // viewModelScope.launch(Dispatchers.IO) { 52 | // repository.getData() 53 | // .catch { 54 | // Log.d("DJC", "getData: ") 55 | // } 56 | // .onStart { changeStateView(loading = true) } 57 | // .collect { 58 | // changeStateView(hide = true) 59 | // delay(200) 60 | // firstData.postValue(it) 61 | // } 62 | // } 63 | // } 64 | // 65 | //} 66 | -------------------------------------------------------------------------------- /module_home/src/main/java/com/quyunshuo/module/home/net/HomeApiService.kt: -------------------------------------------------------------------------------- 1 | package com.quyunshuo.module.home.net 2 | 3 | /** 4 | * Home模块的接口 5 | * 6 | * @author Qu Yunshuo 7 | * @since 6/4/21 5:51 PM 8 | */ 9 | interface HomeApiService -------------------------------------------------------------------------------- /module_home/src/main/res/layout/home_activity_internal_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 14 | 15 | 16 | 22 | 23 |