Repository: iceCola7/AndroidModuleSamples Branch: master Commit: 1c3abb817160 Files: 139 Total size: 181.2 KB Directory structure: gitextract_u32wwojs/ ├── .gitignore ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── cxz/ │ │ └── module/ │ │ └── samples/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── cxz/ │ │ │ └── module/ │ │ │ └── samples/ │ │ │ ├── MainActivity.kt │ │ │ ├── OtherActivity.kt │ │ │ └── app/ │ │ │ └── App.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── app_activity_main.xml │ │ │ └── app_activity_other.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── cxz/ │ └── module/ │ └── samples/ │ └── ExampleUnitTest.kt ├── baselibs/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── cxz/ │ │ └── kotlin/ │ │ └── baselibs/ │ │ ├── base/ │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseMvpActivity.kt │ │ │ ├── BaseMvpFragment.kt │ │ │ └── BaseMvpTitleActivity.kt │ │ ├── bean/ │ │ │ └── BaseBean.kt │ │ ├── config/ │ │ │ └── AppConfig.kt │ │ ├── ext/ │ │ │ ├── CommonExt.kt │ │ │ └── RxExt.kt │ │ ├── http/ │ │ │ ├── HttpStatus.kt │ │ │ ├── RetrofitFactory.kt │ │ │ ├── constant/ │ │ │ │ └── HttpConstant.kt │ │ │ ├── cookies/ │ │ │ │ ├── CookieManager.kt │ │ │ │ ├── OkHttpCookies.kt │ │ │ │ └── PersistentCookieStore.kt │ │ │ ├── exception/ │ │ │ │ ├── ApiException.kt │ │ │ │ └── ExceptionHandle.kt │ │ │ ├── function/ │ │ │ │ └── RetryWithDelay.kt │ │ │ └── interceptor/ │ │ │ ├── CacheInterceptor.kt │ │ │ ├── CookieInterceptor.kt │ │ │ ├── HeaderInterceptor.kt │ │ │ └── QueryParameterInterceptor.kt │ │ ├── mvp/ │ │ │ ├── BaseModel.kt │ │ │ ├── BasePresenter.kt │ │ │ ├── IModel.kt │ │ │ ├── IPresenter.kt │ │ │ └── IView.kt │ │ ├── provider/ │ │ │ └── NewsService.kt │ │ ├── rx/ │ │ │ ├── BaseObserver.kt │ │ │ ├── BaseSubscriber.kt │ │ │ ├── SchedulerUtils.kt │ │ │ └── scheduler/ │ │ │ ├── BaseScheduler.kt │ │ │ ├── ComputationMainScheduler.kt │ │ │ ├── IoMainScheduler.kt │ │ │ ├── NewThreadMainScheduler.kt │ │ │ ├── SingleMainScheduler.kt │ │ │ └── TrampolineMainScheduler.kt │ │ ├── utils/ │ │ │ ├── AnimatorUtil.kt │ │ │ ├── AppUtils.kt │ │ │ ├── CommonUtil.kt │ │ │ ├── FileProvider7.kt │ │ │ ├── KeyBoardUtil.kt │ │ │ ├── NLog.kt │ │ │ ├── NetWorkUtil.kt │ │ │ ├── Preference.kt │ │ │ ├── RomUtil.kt │ │ │ ├── RxTimerUtil.kt │ │ │ └── StatusBarUtil.kt │ │ └── widget/ │ │ ├── CustomToast.kt │ │ ├── LoadingDialog.kt │ │ └── OnNoDoubleClickListener.kt │ └── res/ │ ├── drawable/ │ │ ├── bg_loading_dialog.xml │ │ └── bg_toast_custom.xml │ ├── layout/ │ │ ├── activity_base_title.xml │ │ ├── base_toolbar.xml │ │ ├── layout_loading_dialog.xml │ │ └── toast_custom.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimen.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── xml/ │ ├── file_paths.xml │ └── network_security_config.xml ├── build.gradle ├── config.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── module_me/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ ├── com/ │ │ │ └── cxz/ │ │ │ └── module/ │ │ │ └── me/ │ │ │ ├── MeMainActivity.kt │ │ │ └── mvp/ │ │ │ ├── contract/ │ │ │ │ └── MeMainContract.kt │ │ │ ├── model/ │ │ │ │ └── MeMainModel.kt │ │ │ └── persenter/ │ │ │ └── MeMainPresenter.kt │ │ └── debug/ │ │ └── MeApplication.kt │ ├── module/ │ │ └── AndroidManifest.xml │ └── res/ │ ├── layout/ │ │ └── me_activity_me_main.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── module_news/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ ├── com/ │ │ │ └── cxz/ │ │ │ └── module/ │ │ │ └── news/ │ │ │ ├── NewsMainActivity.kt │ │ │ ├── NewsServiceImpl.kt │ │ │ └── mvp/ │ │ │ ├── contract/ │ │ │ │ └── NewsMainContract.kt │ │ │ ├── model/ │ │ │ │ └── NewsMainModel.kt │ │ │ └── persenter/ │ │ │ └── NewsMainPresenter.kt │ │ └── debug/ │ │ └── NewsApplication.kt │ ├── module/ │ │ └── AndroidManifest.xml │ └── res/ │ ├── layout/ │ │ └── news_activity_news_main.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── module_video/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ ├── com/ │ │ │ └── cxz/ │ │ │ └── module/ │ │ │ └── video/ │ │ │ ├── VideoMainActivity.kt │ │ │ └── mvp/ │ │ │ ├── contract/ │ │ │ │ └── VideoMainContract.kt │ │ │ ├── model/ │ │ │ │ └── VideoMainModel.kt │ │ │ └── persenter/ │ │ │ └── VideoMainPresenter.kt │ │ └── debug/ │ │ └── VideoApplication.kt │ ├── module/ │ │ └── AndroidManifest.xml │ └── res/ │ ├── layout/ │ │ └── video_activity_video_main.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea .DS_Store /build /captures .externalNativeBuild ================================================ FILE: README.md ================================================ # 基于 MVP 的 Android 组件化开发框架实践 ## 一、背景 当我们的项目变得越来越大,代码变得越来越臃肿,耦合会越来越多,编译速度越来越慢,开发效率也会变得越来越低,怎么办?这个时候我们就需要对旧项目进行重构,即是模块的拆分,官方的说法就是组件化。 ## 二、简介 那什么是组件化呢?其基本理念是:把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,我们称之为基础组件(`baselibs`);把业务分成 N 个模块进行独立的管理,每一个模块我们称之为业务组件;而所有的业务组件都需要依赖于封装的基础组件,业务组件之间不做依赖,这样的目的是为了让每一个业务模块都能单独运行。而在 APP 层对整个项目的模块进行封装。 > 业务模块之间的跳转可以通过路由(`Arouter`)实现;业务模块之间的通信可以通过消息(`EventBus`)来实现。 ## 三、基础搭建 #### 1、组件框架图 ![](/art/00.png) #### 2、根据组件框架图搭建的项目结构图 ![项目结构图](/art/01.png) #### 3、接下来介绍每个模块 项目中总共有五个 `module` ,包括 3 个业务模块、一个基础模块和一个 `APP` 壳模块。 在建好项目之后我们需要给 3 个 `module` 配置 “集成开发模式” 和 “组件开发模式” 的切换开关,可以在 `gradle.properties` 文件中定义变量 `isModel` ,`isModel=false` 代表是 “集成开发模式” , `isModel=true` 代表是 “组件开发模式” (**注:每次修改isModel的值后一定要Sysn才会生效**)。 ![](/art/02.png) **1)APP 壳模块** 主要就是集成每一个模块,最终打包成一个完整的 `apk` ,其中 `gradle` 做了如下配置,根据配置文件中的 `isModel` 字段来依赖不同的业务组件; ![](/art/03.png) **2)baselibs 模块** 主要负责封装公共部分,如 `MVP` 架构、 `BaseView` 的封装、网络请求库、图片加载库、工具类以及自定义控件等; > 为了防止重复依赖,所有的第三方库都放在这个模块,业务模块不做任何第三方依赖,只依赖于 `baselibs` 模块。 `baselibs` 模块的结构如下: ![](/art/04.png) 在 `baselibs` 模块的 `gradle` 中引入的库 ``` dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') configurations { all*.exclude group: 'com.android.support', module: 'support-v13' } testImplementation rootProject.ext.testDeps["junit"] androidTestImplementation rootProject.ext.testDeps["runner"] androidTestImplementation rootProject.ext.testDeps["espresso-core"] //leakCanary debugApi rootProject.ext.testDeps["leakcanary-debug"] releaseApi rootProject.ext.testDeps["leakcanary-release"] // Support库 api rootProject.ext.supportLibs // 网络请求库 api rootProject.ext.networkLibs // RxJava2 api rootProject.ext.rxJavaLibs // commonLibs api rootProject.ext.commonLibs kapt rootProject.ext.otherDeps["arouter-compiler"] } ``` **3)业务模块(module_news、module_video、module_me)** 每一个业务模块在 “集成开发模式” 下以 `library` 的形式存在;在 “组件开发模式” 下以 `application` 的形式存在,可以单独运行。 由于每个业务模块的配置文件都差不多,下面就以 `module_news` 模块为例; 以下是 `module_news` 模块的 `gradle` 配置文件: ``` if (isModule.toBoolean()) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' } android { if (isModule.toBoolean()) { applicationId "com.cxz.module.me" } compileSdkVersion rootProject.ext.android.compileSdkVersion defaultConfig { minSdkVersion rootProject.ext.android.minSdkVersion targetSdkVersion rootProject.ext.android.targetSdkVersion versionCode 1 versionName "1.0" } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation rootProject.ext.testDeps["junit"] androidTestImplementation rootProject.ext.testDeps["runner"] androidTestImplementation rootProject.ext.testDeps["espresso-core"] implementation project(':baselibs') kapt rootProject.ext.otherDeps["arouter-compiler"] } ``` **4)配置文件 `config.gradle` ,对项目中的第三库、 `app` 的版本等配置** ``` ext { android = [ compileSdkVersion: 28, buildToolsVersion: "28.0.3", minSdkVersion : 16, targetSdkVersion : 27, versionCode : 1, versionName : "1.0.0" ] dependVersion = [ androidSupportSdkVersion: "28.0.0", espressoSdkVersion : "3.0.2", retrofitSdkVersion : "2.4.0", glideSdkVersion : "4.8.0", rxJava : "2.2.2", rxAndroid : "2.1.0", rxKotlin : "2.3.0", anko : "0.10.7" ] supportDeps = [ "supportv4" : "com.android.support:support-v4:${dependVersion.androidSupportSdkVersion}", "appcompatv7" : "com.android.support:appcompat-v7:${dependVersion.androidSupportSdkVersion}", "cardview" : "com.android.support:cardview-v7:${dependVersion.androidSupportSdkVersion}", "design" : "com.android.support:design:${dependVersion.androidSupportSdkVersion}", "constraint-layout": "com.android.support.constraint:constraint-layout:1.1.3", "annotations" : "com.android.support:support-annotations:${dependVersion.androidSupportSdkVersion}" ] retrofit = [ "retrofit" : "com.squareup.retrofit2:retrofit:${dependVersion.retrofitSdkVersion}", "retrofitConverterGson" : "com.squareup.retrofit2:converter-gson:${dependVersion.retrofitSdkVersion}", "retrofitAdapterRxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${dependVersion.retrofitSdkVersion}", "okhttp3LoggerInterceptor": 'com.squareup.okhttp3:logging-interceptor:3.11.0', "retrofitConverterMoshi" : 'com.squareup.retrofit2:converter-moshi:2.4.0', "retrofitKotlinMoshi" : "com.squareup.moshi:moshi-kotlin:1.7.0" ] rxJava = [ "rxJava" : "io.reactivex.rxjava2:rxjava:${dependVersion.rxJava}", "rxAndroid": "io.reactivex.rxjava2:rxandroid:${dependVersion.rxAndroid}", "rxKotlin" : "io.reactivex.rxjava2:rxkotlin:${dependVersion.rxKotlin}", "anko" : "org.jetbrains.anko:anko:${dependVersion.anko}" ] testDeps = [ "junit" : 'junit:junit:4.12', "runner" : 'com.android.support.test:runner:1.0.2', "espresso-core" : "com.android.support.test.espresso:espresso-core:${dependVersion.espressoSdkVersion}", "espresso-contrib" : "com.android.support.test.espresso:espresso-contrib:${dependVersion.espressoSdkVersion}", "espresso-intents" : "com.android.support.test.espresso:espresso-intents:${dependVersion.espressoSdkVersion}", "leakcanary-debug" : 'com.squareup.leakcanary:leakcanary-android:1.6.1', "leakcanary-release" : 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1', "leakcanary-debug-fragment": 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1', "debug-db" : 'com.amitshekhar.android:debug-db:1.0.4' ] commonDeps = [ "multidex": 'com.android.support:multidex:1.0.3', "logger" : 'com.orhanobut:logger:2.2.0', "glide" : 'com.github.bumptech.glide:glide:4.8.0', "eventbus": 'org.greenrobot:eventbus:3.1.1', "spinkit" : 'com.github.ybq:Android-SpinKit:1.2.0', "arouter" : 'com.alibaba:arouter-api:1.4.0' ] otherDeps = [ "arouter-compiler": 'com.alibaba:arouter-compiler:1.2.1' ] supportLibs = supportDeps.values() networkLibs = retrofit.values() rxJavaLibs = rxJava.values() commonLibs = commonDeps.values() } ``` **最后别忘记在工程的中 `build.gradle` 引入该配置文件** ``` apply from: "config.gradle" ``` ## 四、业务模块之间交互 > 业务模块之间的跳转可以通过路由(`Arouter`)实现;业务模块之间的通信可以通过消息(`EventBus`)来实现。 #### 1、Arouter 实现业务模块之间的跳转 我们在之前已经依赖了 `Arouter` (详细用法参照:[https://github.com/alibaba/ARouter](https://github.com/alibaba/ARouter)),用它来实现跳转只需要以下两步: **第一步** - `gradle` 配置 ``` kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } generateStubs = true } dependencies { ... kapt rootProject.ext.otherDeps["arouter-compiler"] } ``` **第二步** - 需要指明目标页面以及要带的参数,然后在调用 `navigation()` 方法; ![](/art/05.png) **第三步** - 首先在 `onCreate` 方法调用 `ARouter.getInstance().inject(this)` 注入; - 然后要用 `@Route` 注解标注页面,并在 `path` 变量中给页面定义一个路径; - 最后对于传送过来的变量我们直接定义一个同名的字段用 `@Autowired` 变量标注,`Arouter` 会对该字段自动赋值 ![](/art/06.png) #### 2、EventBus 实现业务模块之间的通讯 利用第三方如 `EventBus` 对消息进行管理。在 `baselibs` 组件中的 `BaseActivity` 、 `BaseFragment` 类做了对消息的简单封装,子类只需要重写 `useEventBus()` 返回 `true` 即可对事件的注册。 ## 五、搭建过程中遇到的问题 #### 1、AndroidManifest 我们知道 `APP` 在打包的时候最后会把所有的 `AndroidManifest` 进行合并,所以每个业务组件的 `Activity` 只需要在各自的模块中注册即可。 如果业务组件要单独运行,则需要单独的一个 `AndroidManifest` ,在 `gradle` 的 `sourceSets` 加载不同的 `AndroidManifest` 即可。 ![](/art/07.png) **gradle 配置** ``` android { ... sourceSets { main { if (isModule.toBoolean()) { manifest.srcFile 'src/main/module/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' //集成开发模式下排除debug文件夹中的所有Java文件 java { exclude 'debug/**' } kotlin { exclude 'debug/**' } } } } ... } ``` **注意:集成模式下的 AndroidManifest 不需要配置 Application ,组件模式下的 AndroidManifest 需要单独配置 Application 。** #### 2、资源文件冲突的问题 不同业务组件里的资源文件的名称可能相同,所以就可能出现资源文件冲突的问题,我们可以通过设置资源的前缀来防止资源文件的冲突。 ![](/art/08.png) **gradle 配置,以 module_news 模块为例** ``` android { ... resourcePrefix "news_" ... } ``` 这样配置以后,如果我们在命名资源文件没有加前缀的时候,编译器就会提示我们没加前缀。 **至此, `Android` 基本组件化框架已经搭建完成,如有错误之处还请指正。** ## 五、最后 **完整的项目地址:[https://github.com/iceCola7/AndroidModuleSamples](https://github.com/iceCola7/AndroidModuleSamples)** ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } generateStubs = true } android { compileSdkVersion rootProject.ext.android.compileSdkVersion defaultConfig { applicationId "com.cxz.module.samples" minSdkVersion rootProject.ext.android.minSdkVersion targetSdkVersion rootProject.ext.android.targetSdkVersion versionCode 1 versionName "1.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' multiDexEnabled true } resourcePrefix "app_" buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true shrinkResources true zipAlignEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation rootProject.ext.testDeps["junit"] androidTestImplementation rootProject.ext.testDeps["runner"] androidTestImplementation rootProject.ext.testDeps["espresso-core"] // leakCanary debugImplementation rootProject.ext.testDeps["leakcanary-debug"] releaseImplementation rootProject.ext.testDeps["leakcanary-release"] kapt rootProject.ext.annotationProcessorDeps["arouter-compiler"] // implementation project(':baselibs') // if (!isModule.toBoolean()) { // implementation project(':module_news') // implementation project(':module_video') // implementation project(':module_me') // } if (!isMeModule.toBoolean()){ implementation project(':module_me') } if (!isNewsModule.toBoolean()){ implementation project(':module_news') } if (!isVideoModule.toBoolean()){ implementation project(':module_video') } } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/cxz/module/samples/ExampleInstrumentedTest.kt ================================================ package com.cxz.module.samples import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getTargetContext() assertEquals("com.cxz.module.samples", appContext.packageName) } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/cxz/module/samples/MainActivity.kt ================================================ package com.cxz.module.samples import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.alibaba.android.arouter.facade.annotation.Autowired import com.alibaba.android.arouter.facade.annotation.Route import com.alibaba.android.arouter.launcher.ARouter import com.cxz.kotlin.baselibs.ext.showToast import com.cxz.kotlin.baselibs.provider.NewsService import kotlinx.android.synthetic.main.app_activity_main.* @Route(path = "/app/main") class MainActivity : AppCompatActivity() { @Autowired(name = "/news/service") @JvmField var newsService: NewsService? = null override fun onCreate(savedInstanceState: Bundle?) { ARouter.getInstance().inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.app_activity_main) btn_news.setOnClickListener { ARouter.getInstance().build("/news/main") // 目标页面 .withString("key1", "test_key1") // 参数 .withString("key2", "test_key2") // 参数 .navigation() } btn_video.setOnClickListener { val bundle = Bundle() bundle.putString("key1", "test_key1") bundle.putString("key2", "test_key2") ARouter.getInstance().build("/video/main") .with(bundle) .navigation() } btn_me.setOnClickListener { ARouter.getInstance().build("/me/main") .navigation() } btn_news_service.setOnClickListener { showToast(newsService?.getNewsName().toString()) } } } ================================================ FILE: app/src/main/java/com/cxz/module/samples/OtherActivity.kt ================================================ package com.cxz.module.samples import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.alibaba.android.arouter.facade.annotation.Route @Route(path = "/app/other") class OtherActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.app_activity_other) } } ================================================ FILE: app/src/main/java/com/cxz/module/samples/app/App.kt ================================================ package com.cxz.module.samples.app import android.app.Activity import android.app.Application import android.content.Context import android.os.Bundle import androidx.multidex.MultiDex import com.alibaba.android.arouter.launcher.ARouter import com.cxz.kotlin.baselibs.BuildConfig import com.cxz.kotlin.baselibs.config.AppConfig import com.cxz.kotlin.baselibs.utils.NLog import com.squareup.leakcanary.LeakCanary import com.squareup.leakcanary.RefWatcher /** * @author chenxz * @date 2018/12/22 * @desc */ class App : Application() { private val TAG = "App" private var refWatcher: RefWatcher? = null companion object { fun getRefWatcher(context: Context): RefWatcher? { val app = context.applicationContext as App return app.refWatcher } } override fun onCreate() { super.onCreate() AppConfig.init(this) initLeakCanary() initRouter() } private fun initLeakCanary() { refWatcher = if (LeakCanary.isInAnalyzerProcess(this)) RefWatcher.DISABLED else LeakCanary.install(this) registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks) } private fun initRouter() { if (BuildConfig.DEBUG) { ARouter.openLog() ARouter.openDebug() } ARouter.init(this) } override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) MultiDex.install(this) } private val mActivityLifecycleCallbacks = object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { NLog.d(TAG, "onCreated: " + activity.componentName.className) } override fun onActivityStarted(activity: Activity) { NLog.d(TAG, "onStart: " + activity.componentName.className) } override fun onActivityResumed(activity: Activity) { NLog.d(TAG, "onResume: " + activity.componentName.className) } override fun onActivityPaused(activity: Activity) { NLog.d(TAG, "onPause: " + activity.componentName.className) } override fun onActivityStopped(activity: Activity) { NLog.d(TAG, "onStop: " + activity.componentName.className) } override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { NLog.d(TAG, "onSaveInstanceState: " + activity.componentName.className) } override fun onActivityDestroyed(activity: Activity) { NLog.d(TAG, "onDestroy: " + activity.componentName.className) } } } ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/layout/app_activity_main.xml ================================================