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、组件框架图

#### 2、根据组件框架图搭建的项目结构图

#### 3、接下来介绍每个模块
项目中总共有五个 `module` ,包括 3 个业务模块、一个基础模块和一个 `APP` 壳模块。
在建好项目之后我们需要给 3 个 `module` 配置 “集成开发模式” 和 “组件开发模式” 的切换开关,可以在 `gradle.properties` 文件中定义变量 `isModel` ,`isModel=false` 代表是 “集成开发模式” , `isModel=true` 代表是 “组件开发模式” (**注:每次修改isModel的值后一定要Sysn才会生效**)。

**1)APP 壳模块**
主要就是集成每一个模块,最终打包成一个完整的 `apk` ,其中 `gradle` 做了如下配置,根据配置文件中的 `isModel` 字段来依赖不同的业务组件;

**2)baselibs 模块**
主要负责封装公共部分,如 `MVP` 架构、 `BaseView` 的封装、网络请求库、图片加载库、工具类以及自定义控件等;
> 为了防止重复依赖,所有的第三方库都放在这个模块,业务模块不做任何第三方依赖,只依赖于 `baselibs` 模块。
`baselibs` 模块的结构如下:

在 `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()` 方法;

**第三步**
- 首先在 `onCreate` 方法调用 `ARouter.getInstance().inject(this)` 注入;
- 然后要用 `@Route` 注解标注页面,并在 `path` 变量中给页面定义一个路径;
- 最后对于传送过来的变量我们直接定义一个同名的字段用 `@Autowired` 变量标注,`Arouter` 会对该字段自动赋值

#### 2、EventBus 实现业务模块之间的通讯
利用第三方如 `EventBus` 对消息进行管理。在 `baselibs` 组件中的 `BaseActivity` 、 `BaseFragment` 类做了对消息的简单封装,子类只需要重写 `useEventBus()` 返回 `true` 即可对事件的注册。
## 五、搭建过程中遇到的问题
#### 1、AndroidManifest
我们知道 `APP` 在打包的时候最后会把所有的 `AndroidManifest` 进行合并,所以每个业务组件的 `Activity` 只需要在各自的模块中注册即可。
如果业务组件要单独运行,则需要单独的一个 `AndroidManifest` ,在 `gradle` 的 `sourceSets` 加载不同的 `AndroidManifest` 即可。

**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、资源文件冲突的问题
不同业务组件里的资源文件的名称可能相同,所以就可能出现资源文件冲突的问题,我们可以通过设置资源的前缀来防止资源文件的冲突。

**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
================================================
================================================
FILE: app/src/main/res/layout/app_activity_other.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#008577
#00574B
#D81B60
================================================
FILE: app/src/main/res/values/strings.xml
================================================
AndroidModuleSamples
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/test/java/com/cxz/module/samples/ExampleUnitTest.kt
================================================
package com.cxz.module.samples
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
================================================
FILE: baselibs/.gitignore
================================================
/build
================================================
FILE: baselibs/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "1.0.0"
multiDexEnabled true
}
// resourcePrefix "base_"
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation rootProject.ext.androisxLibs
// Support库
// api rootProject.ext.supportLibs
// 网络请求库
api rootProject.ext.networkLibs
// RxJava2
api rootProject.ext.rxJavaLibs
// commonLibs
api rootProject.ext.commonLibs
kapt rootProject.ext.annotationProcessorLibs
}
================================================
FILE: baselibs/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: baselibs/src/main/AndroidManifest.xml
================================================
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/base/BaseActivity.kt
================================================
package com.cxz.kotlin.baselibs.base
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.MenuItem
import android.view.MotionEvent
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import com.cxz.kotlin.baselibs.utils.CommonUtil
import com.cxz.kotlin.baselibs.utils.KeyBoardUtil
import com.cxz.kotlin.baselibs.utils.StatusBarUtil
import com.tbruyelle.rxpermissions2.RxPermissions
import org.greenrobot.eventbus.EventBus
/**
* @author chenxz
* @date 2018/11/19
* @desc BaseActivity
*/
abstract class BaseActivity : AppCompatActivity() {
/**
* 布局文件id
*/
@LayoutRes
protected abstract fun attachLayoutRes(): Int
/**
* 初始化数据
*/
open fun initData() {}
/**
* 初始化 View
*/
abstract fun initView()
/**
* 开始请求
*/
abstract fun start()
/**
* 是否使用 EventBus
*/
open fun useEventBus(): Boolean = false
/**
* 获取权限处理类
*/
protected val rxPermissions: RxPermissions by lazy {
RxPermissions(this)
}
/**
* 设置状态栏的背景颜色
*/
fun setStatusBarColor(@ColorInt color: Int) {
StatusBarUtil.setColor(this, color, 0)
}
/**
* 设置状态栏图标的颜色
*
* @param dark true: 黑色 false: 白色
*/
fun setStatusBarIcon(dark: Boolean) {
if (dark) {
StatusBarUtil.setLightMode(this)
} else {
StatusBarUtil.setDarkMode(this)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
super.onCreate(savedInstanceState)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT // 强制竖屏
setContentView(attachLayoutRes())
if (useEventBus()) {
EventBus.getDefault().register(this)
}
initView()
initData()
start()
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_UP) {
val v = currentFocus
// 如果不是落在EditText区域,则需要关闭输入法
if (KeyBoardUtil.isHideKeyboard(v, ev)) {
KeyBoardUtil.hideKeyBoard(this, v)
}
}
return super.dispatchTouchEvent(ev)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
}
}
return super.onOptionsItemSelected(item)
}
override fun onDestroy() {
super.onDestroy()
if (useEventBus()) {
EventBus.getDefault().unregister(this)
}
CommonUtil.fixInputMethodManagerLeak(this)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/base/BaseFragment.kt
================================================
package com.cxz.kotlin.baselibs.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import com.tbruyelle.rxpermissions2.RxPermissions
import org.greenrobot.eventbus.EventBus
/**
* @author chenxz
* @date 2018/11/19
* @desc BaseFragment
*/
abstract class BaseFragment : androidx.fragment.app.Fragment() {
/**
* 视图是否加载完毕
*/
private var isViewPrepare = false
/**
* 数据是否加载过了
*/
private var hasLoadData = false
/**
* 加载布局
*/
@LayoutRes
abstract fun attachLayoutRes(): Int
/**
* 初始化数据
*/
open fun initData() {}
/**
* 初始化 View
*/
abstract fun initView(view: View)
/**
* 懒加载
*/
abstract fun lazyLoad()
/**
* 是否使用 EventBus
*/
open fun useEventBus(): Boolean = false
/**
* 获取权限处理类
*/
protected val rxPermissions: RxPermissions by lazy {
RxPermissions(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(attachLayoutRes(), null)
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
lazyLoadDataIfPrepared()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (useEventBus()) EventBus.getDefault().register(this)
isViewPrepare = true
initView(view)
initData()
lazyLoadDataIfPrepared()
}
private fun lazyLoadDataIfPrepared() {
if (userVisibleHint && isViewPrepare && !hasLoadData) {
lazyLoad()
hasLoadData = true
}
}
override fun onDestroy() {
super.onDestroy()
if (useEventBus()) EventBus.getDefault().unregister(this)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/base/BaseMvpActivity.kt
================================================
package com.cxz.kotlin.baselibs.base
import com.cxz.kotlin.baselibs.ext.showToast
import com.cxz.kotlin.baselibs.mvp.IPresenter
import com.cxz.kotlin.baselibs.mvp.IView
/**
* @author chenxz
* @date 2018/11/19
* @desc BaseMvpActivity
*/
abstract class BaseMvpActivity> : BaseActivity(), IView {
/**
* Presenter
*/
protected var mPresenter: P? = null
protected abstract fun createPresenter(): P
override fun initView() {
mPresenter = createPresenter()
mPresenter?.attachView(this as V)
}
override fun onDestroy() {
super.onDestroy()
mPresenter?.detachView()
this.mPresenter = null
}
override fun showLoading() {
}
override fun hideLoading() {
}
override fun showError(errorMsg: String) {
showToast(errorMsg)
}
override fun showDefaultMsg(msg: String) {
showToast(msg)
}
override fun showMsg(msg: String) {
showToast(msg)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/base/BaseMvpFragment.kt
================================================
package com.cxz.kotlin.baselibs.base
import android.view.View
import com.cxz.kotlin.baselibs.ext.showToast
import com.cxz.kotlin.baselibs.mvp.IPresenter
import com.cxz.kotlin.baselibs.mvp.IView
/**
* @author chenxz
* @date 2018/11/19
* @desc BaseMvpFragment
*/
abstract class BaseMvpFragment> : BaseFragment(), IView {
/**
* Presenter
*/
protected var mPresenter: P? = null
protected abstract fun createPresenter(): P
override fun initView(view: View) {
mPresenter = createPresenter()
mPresenter?.attachView(this as V)
}
override fun onDestroyView() {
super.onDestroyView()
mPresenter?.detachView()
this.mPresenter = null
}
override fun showLoading() {
}
override fun hideLoading() {
}
override fun showError(errorMsg: String) {
showToast(errorMsg)
}
override fun showDefaultMsg(msg: String) {
showToast(msg)
}
override fun showMsg(msg: String) {
showToast(msg)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/base/BaseMvpTitleActivity.kt
================================================
package com.cxz.kotlin.baselibs.base
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import com.cxz.kotlin.baselibs.R
import com.cxz.kotlin.baselibs.mvp.IPresenter
import com.cxz.kotlin.baselibs.mvp.IView
import kotlinx.android.synthetic.main.activity_base_title.*
import kotlinx.android.synthetic.main.base_toolbar.*
/**
* @author chenxz
* @date 2018/11/20
* @desc BaseMvpTitleActivity
*/
abstract class BaseMvpTitleActivity> : BaseMvpActivity() {
/**
* 子布局文件id
*/
protected abstract fun attachChildLayoutRes(): Int
/**
* 是否启用返回键
*/
open fun hasBackIcon(): Boolean = true
override fun attachLayoutRes(): Int = R.layout.activity_base_title
override fun initView() {
super.initView()
base_container.addView(layoutInflater.inflate(attachChildLayoutRes(), null))
base_toolbar.title = ""
setSupportActionBar(base_toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(hasBackIcon())
}
/**
* 设置 Title
*/
fun setBaseTitle(title: String) {
base_title_tv.text = title
}
/**
* 设置 Title
*/
fun setBaseTitleText(@StringRes resId: Int) {
base_title_tv.setText(resId)
}
/**
* 设置 Title 颜色
*/
fun setBaseTitleColor(@ColorRes color: Int) {
base_title_tv.setTextColor(resources.getColor(color))
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/bean/BaseBean.kt
================================================
package com.cxz.kotlin.baselibs.bean
/**
* @author admin
* @date 2018/11/21
* @desc
*/
open class BaseBean {
var errorCode: Int = 0
var errorMsg: String = ""
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/config/AppConfig.kt
================================================
package com.cxz.kotlin.baselibs.config
import android.app.Application
/**
* @author chenxz
* @date 2019/11/1
* @desc 用来初始化项目所需要的配置
*/
object AppConfig {
const val TAG = "KotlinMVP"
var debug = false
private var application: Application? = null
/**
* Init, it must be call before used .
*/
fun init(application: Application) {
this.application = application
}
fun getApplication(): Application {
if (application == null) {
throw RuntimeException("Please init in Application#onCreate first.")
}
return application!!
}
fun openDebug() {
debug = true
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/ext/CommonExt.kt
================================================
package com.cxz.kotlin.baselibs.ext
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.view.View
import android.widget.Checkable
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.cxz.kotlin.baselibs.R
import com.cxz.kotlin.baselibs.config.AppConfig
import com.cxz.kotlin.baselibs.utils.NLog
import com.cxz.kotlin.baselibs.widget.CustomToast
import com.google.android.material.snackbar.Snackbar
/**
* @author chenxz
* @date 2018/11/20
* @desc
*/
fun Any.loge(content: String?) {
val tag = this.javaClass.simpleName ?: AppConfig.TAG
NLog.e(tag, content ?: "")
}
fun dp2px(dpValue: Float): Int {
return (0.5f + dpValue * Resources.getSystem().displayMetrics.density).toInt()
}
fun Fragment.showToast(content: String) {
CustomToast(this.activity?.applicationContext, content).show()
}
fun Context.showToast(content: String) {
CustomToast(this, content).show()
}
fun Activity.showSnackMsg(msg: String) {
val snackbar = Snackbar.make(this.window.decorView, msg, Snackbar.LENGTH_SHORT)
val view = snackbar.view
view.findViewById(R.id.snackbar_text)
.setTextColor(ContextCompat.getColor(this, R.color.white))
snackbar.show()
}
fun Fragment.showSnackMsg(msg: String) {
this.activity ?: return
val snackbar =
Snackbar.make(this.activity!!.window.decorView, msg, com.google.android.material.snackbar.Snackbar.LENGTH_SHORT)
val view = snackbar.view
view.findViewById(R.id.snackbar_text)
.setTextColor(ContextCompat.getColor(this.activity!!, R.color.white))
snackbar.show()
}
fun Context.openBrowser(url: String) {
Intent(Intent.ACTION_VIEW, Uri.parse(url)).run { startActivity(this) }
}
// 扩展点击事件属性(重复点击时长)
var T.lastClickTime: Long
set(value) = setTag(1766613352, value)
get() = getTag(1766613352) as? Long ?: 0
// 重复点击事件绑定
inline fun T.setSingleClickListener(time: Long = 1000, crossinline block: (T) -> Unit) {
setOnClickListener {
val currentTimeMillis = System.currentTimeMillis()
if (currentTimeMillis - lastClickTime > time || this is Checkable) {
lastClickTime = currentTimeMillis
block(this)
}
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/ext/RxExt.kt
================================================
package com.cxz.kotlin.baselibs.ext
import com.cxz.kotlin.baselibs.bean.BaseBean
import com.cxz.kotlin.baselibs.http.HttpStatus
import com.cxz.kotlin.baselibs.http.exception.ExceptionHandle
import com.cxz.kotlin.baselibs.http.function.RetryWithDelay
import com.cxz.kotlin.baselibs.mvp.IModel
import com.cxz.kotlin.baselibs.mvp.IView
import com.cxz.kotlin.baselibs.rx.SchedulerUtils
import com.cxz.kotlin.baselibs.utils.NetWorkUtil
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
/**
* @author chenxz
* @date 2018/11/20
* @desc
*/
fun Observable.ss(
model: IModel?,
view: IView?,
isShowLoading: Boolean = true,
onSuccess: (T) -> Unit,
onError: ((T) -> Unit)? = null
) {
this.compose(SchedulerUtils.ioToMain())
.retryWhen(RetryWithDelay())
.subscribe(object : Observer {
override fun onComplete() {
view?.hideLoading()
}
override fun onSubscribe(d: Disposable) {
if (isShowLoading) view?.showLoading()
model?.addDisposable(d)
if (!NetWorkUtil.isConnected()) {
view?.showDefaultMsg("当前网络不可用,请检查网络设置")
d.dispose()
onComplete()
}
}
override fun onNext(t: T) {
view?.hideLoading()
when {
t.errorCode == HttpStatus.SUCCESS -> onSuccess.invoke(t)
t.errorCode == HttpStatus.TOKEN_INVALID -> {
// Token 过期,重新登录
}
else -> {
if (onError != null) {
onError.invoke(t)
} else {
if (t.errorMsg.isNotEmpty())
view?.showDefaultMsg(t.errorMsg)
}
}
}
}
override fun onError(t: Throwable) {
view?.hideLoading()
view?.showError(ExceptionHandle.handleException(t))
}
})
}
fun Observable.sss(
view: IView?,
isShowLoading: Boolean = true,
onSuccess: (T) -> Unit,
onError: ((T) -> Unit)? = null
): Disposable {
if (isShowLoading) view?.showLoading()
return this.compose(SchedulerUtils.ioToMain())
.retryWhen(RetryWithDelay())
.subscribe({
if (isShowLoading) view?.showLoading()
when {
it.errorCode == HttpStatus.SUCCESS -> onSuccess.invoke(it)
it.errorCode == HttpStatus.TOKEN_INVALID -> {
// Token 过期,重新登录
}
else -> {
if (onError != null) {
onError.invoke(it)
} else {
if (it.errorMsg.isNotEmpty())
view?.showDefaultMsg(it.errorMsg)
}
}
}
}, {
view?.hideLoading()
view?.showError(ExceptionHandle.handleException(it))
})
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/HttpStatus.kt
================================================
package com.cxz.kotlin.baselibs.http
/**
* Created by chenxz on 2018/4/21.
*/
object HttpStatus {
/**
* 响应成功
*/
const val SUCCESS = 0
/**
* Token 过期
*/
const val TOKEN_INVALID = 401
/**
* 未知错误
*/
const val UNKNOWN_ERROR = 1002
/**
* 服务器内部错误
*/
const val SERVER_ERROR = 1003
/**
* 网络连接超时
*/
const val NETWORK_ERROR = 1004
/**
* API解析异常(或者第三方数据结构更改)等其他异常
*/
const val API_ERROR = 1005
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/RetrofitFactory.kt
================================================
package com.cxz.kotlin.baselibs.http
import com.cxz.kotlin.baselibs.config.AppConfig
import com.cxz.kotlin.baselibs.http.constant.HttpConstant
import com.cxz.kotlin.baselibs.http.interceptor.CacheInterceptor
import com.cxz.kotlin.baselibs.http.interceptor.CookieInterceptor
import com.cxz.kotlin.baselibs.http.interceptor.HeaderInterceptor
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.moshi.MoshiConverterFactory
import java.io.File
import java.util.concurrent.TimeUnit
/**
* @author admin
* @date 2018/11/21
* @desc RetrofitFactory
*/
abstract class RetrofitFactory {
var service: T
private var mBaseUrl = ""
private var retrofit: Retrofit? = null
abstract fun baseUrl(): String
abstract fun getService(): Class
init {
mBaseUrl = this.baseUrl()
if (mBaseUrl.isEmpty()) {
throw RuntimeException("base url can not be empty!")
}
service = getRetrofit()!!.create(this.getService())
}
/**
* 获取 Retrofit 实例对象
*/
private fun getRetrofit(): Retrofit? {
if (retrofit == null) {
synchronized(RetrofitFactory::class.java) {
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(mBaseUrl) // baseUrl
.client(attachOkHttpClient())
//.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
}
}
return retrofit
}
/**
* 获取 OkHttpClient 实例对象
* 子类可重写,自定义 OkHttpClient
*/
open fun attachOkHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
val httpLoggingInterceptor = HttpLoggingInterceptor()
if (AppConfig.debug) {
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
}
//设置 请求的缓存的大小跟位置
val cacheFile = File(AppConfig.getApplication().cacheDir, "cache")
val cache = Cache(cacheFile, HttpConstant.MAX_CACHE_SIZE)
builder.run {
addInterceptor(httpLoggingInterceptor)
addInterceptor(HeaderInterceptor())
addInterceptor(CookieInterceptor())
addInterceptor(CacheInterceptor())
cache(cache) //添加缓存
connectTimeout(HttpConstant.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
readTimeout(HttpConstant.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
writeTimeout(HttpConstant.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
retryOnConnectionFailure(true) // 错误重连
// cookieJar(CookieManager())
}
return builder.build()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/constant/HttpConstant.kt
================================================
package com.cxz.kotlin.baselibs.http.constant
/**
* @author chenxz
* @date 2018/11/21
* @desc HttpConstant
*/
object HttpConstant {
const val DEFAULT_TIMEOUT: Long = 15
const val MAX_CACHE_SIZE: Long = 1024 * 1024 * 50 // 50M 的缓存大小
const val TOKEN_KEY = "token"
const val SET_COOKIE_KEY = "set-cookie"
const val COOKIE_NAME = "Cookie"
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/cookies/CookieManager.kt
================================================
package com.cxz.kotlin.baselibs.http.cookies
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
/**
* Created by chenxz on 2018/6/6.
*/
class CookieManager : CookieJar {
private val COOKIE_STORE = PersistentCookieStore()
override fun saveFromResponse(url: HttpUrl?, cookies: MutableList?) {
cookies ?: return
url ?: return
if (cookies.size > 0) {
for (cookie in cookies) {
COOKIE_STORE.add(url, cookie)
}
}
}
override fun loadForRequest(url: HttpUrl): List = COOKIE_STORE.get(url)
/**
* 清除所有cookie
*/
fun clearAllCookies() {
COOKIE_STORE.removeAll()
}
/**
* 清除指定cookie
*
* @param url HttpUrl
* @param cookie Cookie
* @return if clear cookies
*/
fun clearCookies(url: HttpUrl, cookie: Cookie): Boolean {
return COOKIE_STORE.remove(url, cookie)
}
/**
* 获取cookies
*
* @return List
*/
fun getCookies(): List {
return COOKIE_STORE.getCookies()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/cookies/OkHttpCookies.kt
================================================
package com.cxz.kotlin.baselibs.http.cookies
import okhttp3.Cookie
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
/**
* Created by chenxz on 2018/6/6.
*/
class OkHttpCookies : Serializable {
private lateinit var cookies: Cookie
private var clientCookies: Cookie? = null
private constructor()
constructor(cookies: Cookie?) {
this.cookies = cookies!!
}
fun getCookies(): Cookie? {
var bestCookies: Cookie = cookies
if (clientCookies != null) {
bestCookies = clientCookies as Cookie
}
return bestCookies
}
@Throws(IOException::class)
private fun writeObject(out: ObjectOutputStream) {
out.writeObject(cookies.name())
out.writeObject(cookies.value())
out.writeLong(cookies.expiresAt())
out.writeObject(cookies.domain())
out.writeObject(cookies.path())
out.writeBoolean(cookies.secure())
out.writeBoolean(cookies.httpOnly())
out.writeBoolean(cookies.hostOnly())
out.writeBoolean(cookies.persistent())
}
@Throws(IOException::class, ClassNotFoundException::class)
private fun readObject(`in`: ObjectInputStream) {
val name = `in`.readObject() as String
val value = `in`.readObject() as String
val expiresAt = `in`.readLong()
val domain = `in`.readObject() as String
val path = `in`.readObject() as String
val secure = `in`.readBoolean()
val httpOnly = `in`.readBoolean()
val hostOnly = `in`.readBoolean()
var builder = Cookie.Builder()
builder = builder.name(name)
builder = builder.value(value)
builder = builder.expiresAt(expiresAt)
builder = if (hostOnly) builder.hostOnlyDomain(domain) else builder.domain(domain)
builder = builder.path(path)
builder = if (secure) builder.secure() else builder
builder = if (httpOnly) builder.httpOnly() else builder
clientCookies = builder.build()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/cookies/PersistentCookieStore.kt
================================================
package com.cxz.kotlin.baselibs.http.cookies
import android.content.Context
import android.content.SharedPreferences
import android.text.TextUtils
import com.cxz.kotlin.baselibs.config.AppConfig
import com.cxz.kotlin.baselibs.utils.NLog
import okhttp3.Cookie
import okhttp3.HttpUrl
import java.io.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* Created by chenxz on 2018/6/6.
*/
class PersistentCookieStore {
private val LOG_TAG: String = "PersistentCookieStore"
private val COOKIE_PREFS: String = "Cookies_Prefs"
private val cookies: HashMap> = HashMap()
private val cookiePrefs: SharedPreferences
init {
cookiePrefs = AppConfig.getApplication().getSharedPreferences(COOKIE_PREFS, Context.MODE_PRIVATE)
val prefsMap = cookiePrefs.all
for (entry in prefsMap) {
val cookieNames = TextUtils.split(entry.value as String, ",")
for (name in cookieNames) {
val encodedCookie = cookiePrefs.getString(name, null)
if (encodedCookie != null) {
val decodedCookie = decodeCookie(encodedCookie)
if (decodedCookie != null) {
if (!cookies.containsKey(entry.key)) {
cookies.put(entry.key, ConcurrentHashMap())
}
cookies[entry.key]?.put(name, decodedCookie)
}
}
}
}
}
private fun getCookieToken(cookie: Cookie): String = cookie.name() + "@" + cookie.domain()
fun add(url: HttpUrl, cookie: Cookie) {
val name = getCookieToken(cookie)
//将cookies缓存到内存中 如果缓存过期 就重置此cookie
if (!cookie.persistent()) {
if (!cookies.containsKey(url.host())) {
cookies.put(url.host(), ConcurrentHashMap(10))
}
cookies[url.host()]?.put(name, cookie)
} else {
if (cookies.containsKey(url.host())) {
cookies[url.host()]?.remove(name)
}
}
// 将Cookie持久化到本地
val prefsWriter: SharedPreferences.Editor = cookiePrefs.edit()
cookies[url.host()]?.entries?.let {
prefsWriter.putString(url.host(), TextUtils.join(",", it))
}
prefsWriter.putString(name, encodeCookie(OkHttpCookies(cookie)))
prefsWriter.apply()
}
fun get(url: HttpUrl): List {
val list: ArrayList = ArrayList()
if (cookies.containsKey(url.host())) {
list.addAll(cookies[url.host()]?.values!!)
}
return list
}
fun removeAll() {
val prefsWriter = cookiePrefs.edit()
prefsWriter.clear()
prefsWriter.apply()
for (key in cookies.keys) {
cookies.remove(key)
}
}
fun remove(url: HttpUrl, cookie: Cookie): Boolean {
val name = getCookieToken(cookie)
return if (cookies.containsKey(url.host()) && cookies[url.host()]?.containsKey(name)!!) {
cookies[url.host()]?.remove(name)
val prefsWriter = cookiePrefs.edit()
if (cookiePrefs.contains(name)) {
prefsWriter.remove(name)
}
cookies[url.host()]?.keys?.let {
prefsWriter.putString(url.host(), TextUtils.join(",", it))
}
prefsWriter.apply()
true
} else {
false
}
}
fun getCookies(): List {
val ret = ArrayList()
for (key in cookies.keys) {
ret.addAll(cookies[key]?.values!!)
}
return ret
}
/**
* cookies 序列化成 string
*
* @param cookie 要序列化的cookie
* @return 序列化之后的string
*/
private fun encodeCookie(cookie: OkHttpCookies?): String? {
if (cookie == null) {
return null
}
val os = ByteArrayOutputStream()
try {
val outputStream = ObjectOutputStream(os)
outputStream.writeObject(cookie)
} catch (e: IOException) {
NLog.d(LOG_TAG, "IOException in encodeCookie $e")
return null
}
return byteArrayToHexString(os.toByteArray())
}
/**
* 将字符串反序列化成cookies
*
* @param cookieString cookies string
* @return cookie object
*/
private fun decodeCookie(cookieString: String): Cookie? {
val bytes = hexStringToByteArray(cookieString)
val byteArrayInputStream = ByteArrayInputStream(bytes)
var cookie: Cookie? = null
try {
val objectInputStream = ObjectInputStream(byteArrayInputStream)
cookie = (objectInputStream.readObject() as OkHttpCookies).getCookies()
} catch (e: IOException) {
NLog.d(LOG_TAG, "IOException in decodeCookie $e")
} catch (e: ClassNotFoundException) {
NLog.d(LOG_TAG, "ClassNotFoundException in decodeCookie $e")
}
return cookie
}
/**
* 二进制数组转十六进制字符串
*
* @param bytes byte array to be converted
* @return string containing hex values
*/
private fun byteArrayToHexString(bytes: ByteArray): String {
val sb = StringBuilder(bytes.size * 2)
for (element in bytes) {
val v = element.toInt() and 0xff
if (v < 16) {
sb.append('0')
}
sb.append(Integer.toHexString(v))
}
return sb.toString().toUpperCase(Locale.US)
}
/**
* 十六进制字符串转二进制数组
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/
private fun hexStringToByteArray(hexString: String): ByteArray {
val len = hexString.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] = ((Character.digit(hexString[i], 16) shl 4) + Character.digit(hexString[i + 1], 16)).toByte()
i += 2
}
return data
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/exception/ApiException.kt
================================================
package com.cxz.kotlin.baselibs.http.exception
/**
* Created by chenxz on 2018/4/21.
*/
class ApiException : RuntimeException {
private var code: Int? = null
constructor(throwable: Throwable, code: Int) : super(throwable) {
this.code = code
}
constructor(message: String) : super(Throwable(message))
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/exception/ExceptionHandle.kt
================================================
package com.cxz.kotlin.baselibs.http.exception
import com.cxz.kotlin.baselibs.http.HttpStatus
import com.google.gson.JsonParseException
import com.orhanobut.logger.Logger
import org.json.JSONException
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.text.ParseException
/**
* Created by chenxz on 2018/4/21.
*/
class ExceptionHandle {
companion object {
private const val TAG = "ExceptionHandle"
var errorCode = HttpStatus.UNKNOWN_ERROR
var errorMsg = "请求失败,请稍后重试"
fun handleException(e: Throwable): String {
e.printStackTrace()
if (e is SocketTimeoutException
|| e is ConnectException
|| e is HttpException
) { //均视为网络错误
Logger.e(TAG, "网络连接异常: " + e.message)
errorMsg = "网络连接异常"
errorCode = HttpStatus.NETWORK_ERROR
} else if (e is JsonParseException
|| e is JSONException
|| e is ParseException
) { //均视为解析错误
Logger.e(TAG, "数据解析异常: " + e.message)
errorMsg = "数据解析异常"
errorCode = HttpStatus.SERVER_ERROR
} else if (e is ApiException) {//服务器返回的错误信息
errorMsg = e.message.toString()
errorCode = HttpStatus.SERVER_ERROR
} else if (e is UnknownHostException) {
Logger.e(TAG, "网络连接异常: " + e.message)
errorMsg = "网络连接异常"
errorCode = HttpStatus.NETWORK_ERROR
} else if (e is IllegalArgumentException) {
errorMsg = "参数错误"
errorCode = HttpStatus.SERVER_ERROR
} else {//未知错误
try {
Logger.e(TAG, "错误: " + e.message)
} catch (e1: Exception) {
Logger.e(TAG, "未知错误Debug调试 ")
}
errorMsg = "未知错误,可能抛锚了吧~"
errorCode = HttpStatus.UNKNOWN_ERROR
}
return errorMsg
}
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/function/RetryWithDelay.kt
================================================
package com.cxz.kotlin.baselibs.http.function
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* @author chenxz
* @date 2018/8/21
* @desc 请求重连
*/
class RetryWithDelay : Function, Observable<*>> {
private var maxRetryCount = 3 // 可重试次数
private var retryDelayMillis: Long = 3000 // 重试等待时间
constructor() {}
constructor(retryDelayMillis: Long) {
this.retryDelayMillis = retryDelayMillis
}
constructor(maxRetryCount: Int, retryDelayMillis: Long) {
this.maxRetryCount = maxRetryCount
this.retryDelayMillis = retryDelayMillis
}
@Throws(Exception::class)
override fun apply(observable: Observable): Observable<*> {
return observable
.zipWith(Observable.range(1, maxRetryCount + 1),
BiFunction { t1, t2 -> Wrapper(t2, t1) })
.flatMap { wrapper ->
val t = wrapper.throwable
if ((t is ConnectException
|| t is SocketTimeoutException
|| t is TimeoutException
|| t is HttpException)
&& wrapper.index < maxRetryCount + 1
) {
Observable.timer(retryDelayMillis * wrapper.index, TimeUnit.MILLISECONDS)
} else Observable.error(wrapper.throwable)
}
}
private inner class Wrapper(val index: Int, val throwable: Throwable)
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/interceptor/CacheInterceptor.kt
================================================
package com.cxz.kotlin.baselibs.http.interceptor
import com.cxz.kotlin.baselibs.config.AppConfig
import com.cxz.kotlin.baselibs.utils.NetWorkUtil
import okhttp3.CacheControl
import okhttp3.Interceptor
import okhttp3.Response
/**
* @author chenxz
* @date 2018/9/26
* @desc CacheInterceptor: 设置缓存
*/
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (!NetWorkUtil.isConnected(AppConfig.getApplication())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
}
val response = chain.proceed(request)
if (NetWorkUtil.isConnected(AppConfig.getApplication())) {
val maxAge = 0
// 有网络时 设置缓存超时时间0个小时 ,意思就是不读取缓存数据,只对get有用,post没有缓冲
response.newBuilder()
.header("Cache-Control", "public, max-age=$maxAge")
.removeHeader("Retrofit")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.build()
} else {
// 无网络时,设置超时为4周 只对get有用,post没有缓冲
val maxStale = 60 * 60 * 24 * 28
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
.removeHeader("nyn")
.build()
}
return response
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/interceptor/CookieInterceptor.kt
================================================
package com.cxz.kotlin.baselibs.http.interceptor
import com.cxz.kotlin.baselibs.http.constant.HttpConstant
import com.cxz.kotlin.baselibs.utils.Preference
import okhttp3.Interceptor
import okhttp3.Response
/**
* @author chenxz
* @date 2018/9/26
* @desc CookieInterceptor: 保存 Cookie
*/
class CookieInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val requestUrl = request.url().toString()
val domain = request.url().host()
// set-cookie maybe has multi, login to save cookie
if (
//(requestUrl.contains(HttpConstant.SAVE_USER_LOGIN_KEY) || requestUrl.contains(HttpConstant.SAVE_USER_REGISTER_KEY)) &&
response.headers(HttpConstant.SET_COOKIE_KEY).isNotEmpty()
) {
val cookies = response.headers(HttpConstant.SET_COOKIE_KEY)
val cookie = encodeCookie(cookies)
saveCookie(requestUrl, domain, cookie)
}
// else if (requestUrl.contains(HttpConstant.REMOVE_USER_LOGOUT_KEY) && domain.isNotEmpty()) {
// remove cookie
// Preference.clearPreference(domain)
// }
return response
}
private fun encodeCookie(cookies: List): String {
val sb = StringBuilder()
val set = HashSet()
cookies.map { cookie ->
cookie.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
}.forEach {
it.filterNot { set.contains(it) }.forEach { set.add(it) }
}
val ite = set.iterator()
while (ite.hasNext()) {
val cookie = ite.next()
sb.append(cookie).append(";")
}
val last = sb.lastIndexOf(";")
if (sb.length - 1 == last) {
sb.deleteCharAt(last)
}
return sb.toString()
}
private fun saveCookie(url: String?, domain: String?, cookies: String) {
url ?: return
var spUrl: String by Preference(url, cookies)
@Suppress("UNUSED_VALUE")
spUrl = cookies
domain ?: return
var spDomain: String by Preference(domain, cookies)
@Suppress("UNUSED_VALUE")
spDomain = cookies
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/interceptor/HeaderInterceptor.kt
================================================
package com.cxz.kotlin.baselibs.http.interceptor
import com.cxz.kotlin.baselibs.http.constant.HttpConstant
import com.cxz.kotlin.baselibs.utils.Preference
import okhttp3.Interceptor
import okhttp3.Response
/**
* @author chenxz
* @date 2018/9/26
* @desc HeaderInterceptor: 设置请求头
*/
class HeaderInterceptor : Interceptor {
/**
* token
*/
private var token: String by Preference(HttpConstant.TOKEN_KEY, "")
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.newBuilder()
builder.addHeader("Content-type", "application/json; charset=utf-8")
// .header("token", token)
// .method(request.method(), request.body())
val domain = request.url().host()
val url = request.url().toString()
if (domain.isNotEmpty()) {
val spDomain: String by Preference(domain, "")
val cookie: String = if (spDomain.isNotEmpty()) spDomain else ""
if (cookie.isNotEmpty()) {
// 将 Cookie 添加到请求头
builder.addHeader(HttpConstant.COOKIE_NAME, cookie)
}
}
return chain.proceed(builder.build())
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/http/interceptor/QueryParameterInterceptor.kt
================================================
package com.cxz.kotlin.baselibs.http.interceptor
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
/**
* @author chenxz
* @date 2018/9/26
* @desc QueryParameterInterceptor 设置公共参数
*/
class QueryParameterInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val request: Request
val modifiedUrl = originalRequest.url().newBuilder()
// Provide your custom parameter here
.addQueryParameter("phoneSystem", "")
.addQueryParameter("phoneModel", "")
.build()
request = originalRequest.newBuilder().url(modifiedUrl).build()
return chain.proceed(request)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/mvp/BaseModel.kt
================================================
package com.cxz.kotlin.baselibs.mvp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
/**
* @author chenxz
* @date 2018/11/18
* @desc BaseModel
*/
abstract class BaseModel : IModel, LifecycleObserver {
private var mCompositeDisposable: CompositeDisposable? = null
override fun addDisposable(disposable: Disposable?) {
if (mCompositeDisposable == null) {
mCompositeDisposable = CompositeDisposable()
}
disposable?.let { mCompositeDisposable?.add(it) }
}
override fun onDetach() {
unDispose()
}
private fun unDispose() {
mCompositeDisposable?.clear() // 保证Activity结束时取消
mCompositeDisposable = null
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/mvp/BasePresenter.kt
================================================
package com.cxz.kotlin.baselibs.mvp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.EventBus
/**
* @author chenxz
* @date 2018/11/18
* @desc BasePresenter
*/
abstract class BasePresenter : IPresenter, LifecycleObserver {
protected var mModel: M? = null
protected var mView: V? = null
private val isViewAttached: Boolean
get() = mView != null
private var mCompositeDisposable: CompositeDisposable? = null
/**
* 创建 Model
*/
open fun createModel(): M? = null
/**
* 是否使用 EventBus
*/
open fun useEventBus(): Boolean = false
override fun attachView(mView: V) {
this.mView = mView
mModel = createModel()
if (mView is LifecycleOwner) {
(mView as LifecycleOwner).lifecycle.addObserver(this)
if (mModel != null && mModel is LifecycleObserver) {
(mView as LifecycleOwner).lifecycle.addObserver(mModel as LifecycleObserver)
}
}
if (useEventBus()) {
EventBus.getDefault().register(this)
}
}
override fun detachView() {
if (useEventBus()) {
EventBus.getDefault().unregister(this)
}
// 保证activity结束时取消所有正在执行的订阅
unDispose()
mModel?.onDetach()
this.mModel = null
this.mView = null
this.mCompositeDisposable = null
}
open fun addDisposable(disposable: Disposable?) {
if (mCompositeDisposable == null) {
mCompositeDisposable = CompositeDisposable()
}
disposable?.let { mCompositeDisposable?.add(it) }
}
private fun unDispose() {
mCompositeDisposable?.clear() // 保证Activity结束时取消
mCompositeDisposable = null
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
// detachView()
owner.lifecycle.removeObserver(this)
}
open fun checkViewAttached() {
if (!isViewAttached) throw MvpViewNotAttachedException()
}
private class MvpViewNotAttachedException internal constructor() :
RuntimeException("Please call IPresenter.attachView(IBaseView) before" + " requesting data to the IPresenter")
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/mvp/IModel.kt
================================================
package com.cxz.kotlin.baselibs.mvp
import io.reactivex.disposables.Disposable
/**
* @author chenxz
* @date 2018/11/18
* @desc IModel
*/
interface IModel {
fun addDisposable(disposable: Disposable?)
fun onDetach()
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/mvp/IPresenter.kt
================================================
package com.cxz.kotlin.baselibs.mvp
/**
* @author chenxz
* @date 2018/11/18
* @desc IPresenter
*/
interface IPresenter {
/**
* 绑定 View
*/
fun attachView(mView: V)
/**
* 解绑 View
*/
fun detachView()
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/mvp/IView.kt
================================================
package com.cxz.kotlin.baselibs.mvp
/**
* @author chenxz
* @date 2018/11/18
* @desc IView
*/
interface IView {
/**
* 显示加载
*/
fun showLoading()
/**
* 隐藏加载
*/
fun hideLoading()
/**
* 使用默认的样式显示信息: CustomToast
*/
fun showDefaultMsg(msg: String)
/**
* 显示信息
*/
fun showMsg(msg: String)
/**
* 显示错误信息
*/
fun showError(errorMsg: String)
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/provider/NewsService.kt
================================================
package com.cxz.kotlin.baselibs.provider
import com.alibaba.android.arouter.facade.template.IProvider
/**
* @author chenxz
* @date 2018/12/22
* @desc
*/
interface NewsService : IProvider {
fun getNewsName(): String
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/BaseObserver.kt
================================================
package com.cxz.kotlin.baselibs.rx
import com.cxz.kotlin.baselibs.bean.BaseBean
import com.cxz.kotlin.baselibs.http.HttpStatus
import com.cxz.kotlin.baselibs.http.exception.ExceptionHandle
import com.cxz.kotlin.baselibs.mvp.IView
import com.cxz.kotlin.baselibs.utils.NetWorkUtil
import io.reactivex.observers.ResourceObserver
/**
* Created by chenxz on 2018/6/11.
*/
abstract class BaseObserver : ResourceObserver {
private var mView: IView? = null
private var mErrorMsg = ""
private var bShowLoading = true
constructor(view: IView) {
this.mView = view
}
constructor(view: IView, bShowLoading: Boolean) {
this.mView = view
this.bShowLoading = bShowLoading
}
/**
* 成功的回调
*/
protected abstract fun onSuccess(t: T)
/**
* 错误的回调
*/
protected fun onError(t: T) {}
override fun onStart() {
super.onStart()
if (bShowLoading) mView?.showLoading()
if (!NetWorkUtil.isConnected()) {
mView?.showDefaultMsg("当前网络不可用,请检查网络设置")
onComplete()
}
}
override fun onNext(t: T) {
mView?.hideLoading()
when {
t.errorCode == HttpStatus.SUCCESS -> onSuccess(t)
t.errorCode == HttpStatus.TOKEN_INVALID -> {
// TODO Token 过期,重新登录
}
else -> {
onError(t)
if (t.errorMsg.isNotEmpty())
mView?.showDefaultMsg(t.errorMsg)
}
}
}
override fun onError(e: Throwable) {
mView?.hideLoading()
if (mView == null) {
throw RuntimeException("mView can not be null")
}
if (mErrorMsg.isEmpty()) {
mErrorMsg = ExceptionHandle.handleException(e)
}
mView?.showDefaultMsg(mErrorMsg)
}
override fun onComplete() {
mView?.hideLoading()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/BaseSubscriber.kt
================================================
package com.cxz.kotlin.baselibs.rx
import com.cxz.kotlin.baselibs.bean.BaseBean
import com.cxz.kotlin.baselibs.http.HttpStatus
import com.cxz.kotlin.baselibs.http.exception.ExceptionHandle
import com.cxz.kotlin.baselibs.mvp.IView
import com.cxz.kotlin.baselibs.utils.NetWorkUtil
import io.reactivex.subscribers.ResourceSubscriber
/**
* Created by chenxz on 2018/6/11.
*/
abstract class BaseSubscriber : ResourceSubscriber {
private var mView: IView? = null
private var mErrorMsg = ""
private var bShowLoading = true
constructor(view: IView) {
this.mView = view
}
constructor(view: IView, bShowLoading: Boolean) {
this.mView = view
this.bShowLoading = bShowLoading
}
/**
* 成功的回调
*/
protected abstract fun onSuccess(t: T)
/**
* 错误的回调
*/
protected fun onError(t: T) {}
override fun onStart() {
super.onStart()
if (bShowLoading) mView?.showLoading()
if (!NetWorkUtil.isConnected()) {
mView?.showDefaultMsg("当前网络不可用,请检查网络设置")
onComplete()
}
}
override fun onNext(t: T) {
mView?.hideLoading()
when {
t.errorCode == HttpStatus.SUCCESS -> onSuccess(t)
t.errorCode == HttpStatus.TOKEN_INVALID -> {
// TODO Token 过期,重新登录
}
else -> {
onError(t)
if (t.errorMsg.isNotEmpty())
mView?.showDefaultMsg(t.errorMsg)
}
}
}
override fun onError(e: Throwable) {
mView?.hideLoading()
if (mView == null) {
throw RuntimeException("mView can not be null")
}
if (mErrorMsg.isEmpty()) {
mErrorMsg = ExceptionHandle.handleException(e)
}
mView?.showDefaultMsg(mErrorMsg)
}
override fun onComplete() {
mView?.hideLoading()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/SchedulerUtils.kt
================================================
package com.cxz.kotlin.baselibs.rx
import com.cxz.kotlin.baselibs.rx.scheduler.IoMainScheduler
/**
* Created by chenxz on 2018/4/21.
*/
object SchedulerUtils {
fun ioToMain(): IoMainScheduler = IoMainScheduler()
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/scheduler/BaseScheduler.kt
================================================
package com.cxz.kotlin.baselibs.rx.scheduler
import io.reactivex.*
import org.reactivestreams.Publisher
/**
* Created by chenxz on 2018/4/21.
*/
abstract class BaseScheduler protected constructor(
private val subscribeOnScheduler: Scheduler,
private val observeOnScheduler: Scheduler
) : ObservableTransformer,
SingleTransformer,
MaybeTransformer,
CompletableTransformer,
FlowableTransformer {
override fun apply(upstream: Completable): CompletableSource {
return upstream.subscribeOn(subscribeOnScheduler)
.observeOn(observeOnScheduler)
}
override fun apply(upstream: Flowable): Publisher {
return upstream.subscribeOn(subscribeOnScheduler)
.observeOn(observeOnScheduler)
}
override fun apply(upstream: Maybe): MaybeSource {
return upstream.subscribeOn(subscribeOnScheduler)
.observeOn(observeOnScheduler)
}
override fun apply(upstream: Observable): ObservableSource {
return upstream.subscribeOn(subscribeOnScheduler)
.observeOn(observeOnScheduler)
}
override fun apply(upstream: Single): SingleSource {
return upstream.subscribeOn(subscribeOnScheduler)
.observeOn(observeOnScheduler)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/scheduler/ComputationMainScheduler.kt
================================================
package com.cxz.kotlin.baselibs.rx.scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* Created by chenxz on 2018/4/21.
*/
class ComputationMainScheduler private constructor() :
BaseScheduler(Schedulers.computation(), AndroidSchedulers.mainThread())
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/scheduler/IoMainScheduler.kt
================================================
package com.cxz.kotlin.baselibs.rx.scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* Created by chenxz on 2018/4/21.
*/
class IoMainScheduler : BaseScheduler(Schedulers.io(), AndroidSchedulers.mainThread())
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/scheduler/NewThreadMainScheduler.kt
================================================
package com.cxz.kotlin.baselibs.rx.scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* Created by chenxz on 2018/4/21.
*/
class NewThreadMainScheduler private constructor() :
BaseScheduler(Schedulers.newThread(), AndroidSchedulers.mainThread())
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/scheduler/SingleMainScheduler.kt
================================================
package com.cxz.kotlin.baselibs.rx.scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* Created by chenxz on 2018/4/21.
*/
class SingleMainScheduler private constructor() :
BaseScheduler(Schedulers.single(), AndroidSchedulers.mainThread())
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/rx/scheduler/TrampolineMainScheduler.kt
================================================
package com.cxz.kotlin.baselibs.rx.scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* Created by chenxz on 2018/4/21.
*/
class TrampolineMainScheduler private constructor() :
BaseScheduler(Schedulers.trampoline(), AndroidSchedulers.mainThread())
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/AnimatorUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.view.View
import android.view.animation.AccelerateInterpolator
import androidx.core.view.ViewCompat
import androidx.core.view.ViewPropertyAnimatorListener
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
/**
* Created by chenxz on 2018/5/13.
*/
object AnimatorUtil {
private val FAST_OUT_SLOW_IN_INTERPOLATOR: LinearOutSlowInInterpolator by lazy {
LinearOutSlowInInterpolator()
}
private val LINER_INTERPOLATOR: AccelerateInterpolator by lazy {
AccelerateInterpolator()
}
/**
* 显示View
* @param view View
* @param listener ViewPropertyAnimatorListener
*/
fun scaleShow(view: View, listener: ViewPropertyAnimatorListener) {
view.visibility = View.VISIBLE
ViewCompat.animate(view)
.scaleX(1.0f)
.scaleY(1.0f)
.alpha(1.0f)
.setDuration(800)
.setListener(listener)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.start()
}
/**
* 隐藏View
* @param view View
* @param listener ViewPropertyAnimatorListener
*/
fun scaleHide(view: View, listener: ViewPropertyAnimatorListener) {
ViewCompat.animate(view)
.scaleX(0.0f)
.scaleY(0.0f)
.alpha(0.0f)
.setDuration(800)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.setListener(listener)
.start()
}
/**
* 显示view
*
* @param view View
* @param listener ViewPropertyAnimatorListener
*/
fun translateShow(view: View, listener: ViewPropertyAnimatorListener) {
view.visibility = View.VISIBLE
ViewCompat.animate(view)
.translationY(0f)
.setDuration(400)
.setListener(listener)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.start()
}
/**
* 隐藏view
*
* @param view View
* @param listener ViewPropertyAnimatorListener
*/
fun translateHide(view: View, listener: ViewPropertyAnimatorListener) {
view.visibility = View.VISIBLE
ViewCompat.animate(view)
.translationY(350f)
.setDuration(400)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.setListener(listener)
.start()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/AppUtils.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.content.Context
import android.content.pm.PackageManager
import java.security.MessageDigest
/**
* Created by chenxz on 2018/4/21.
*/
class AppUtils private constructor() {
init {
throw Error("Do not need instantiate!")
}
companion object {
private val DEBUG = true
private val TAG = "AppUtils"
/**
* 得到软件版本号
*
* @param context 上下文
* @return 当前版本Code
*/
fun getVerCode(context: Context): Int {
var verCode = -1
try {
val packageName = context.packageName
verCode = context.packageManager
.getPackageInfo(packageName, 0).versionCode
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
return verCode
}
/**
* 获取应用运行的最大内存
*
* @return 最大内存
*/
val maxMemory: Long
get() = Runtime.getRuntime().maxMemory() / 1024
/**
* 得到软件显示版本信息
*
* @param context 上下文
* @return 当前版本信息
*/
fun getVerName(context: Context): String {
var verName = ""
try {
val packageName = context.packageName
verName = context.packageManager
.getPackageInfo(packageName, 0).versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
return verName
}
/**
* 获取应用签名
*
* @param context 上下文
* @param pkgName 包名
* @return 返回应用的签名
*/
fun getSign(context: Context, pkgName: String): String? {
return try {
@SuppressLint("PackageManagerGetSignatures") val pis = context.packageManager
.getPackageInfo(
pkgName,
PackageManager.GET_SIGNATURES
)
hexDigest(pis.signatures[0].toByteArray())
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
null
}
}
/**
* 将签名字符串转换成需要的32位签名
*
* @param paramArrayOfByte 签名byte数组
* @return 32位签名字符串
*/
private fun hexDigest(paramArrayOfByte: ByteArray): String {
val hexDigits = charArrayOf(
48.toChar(),
49.toChar(),
50.toChar(),
51.toChar(),
52.toChar(),
53.toChar(),
54.toChar(),
55.toChar(),
56.toChar(),
57.toChar(),
97.toChar(),
98.toChar(),
99.toChar(),
100.toChar(),
101.toChar(),
102.toChar()
)
try {
val localMessageDigest = MessageDigest.getInstance("MD5")
localMessageDigest.update(paramArrayOfByte)
val arrayOfByte = localMessageDigest.digest()
val arrayOfChar = CharArray(32)
var i = 0
var j = 0
while (true) {
if (i >= 16) {
return String(arrayOfChar)
}
val k = arrayOfByte[i].toInt()
arrayOfChar[j] = hexDigits[0xF and k.ushr(4)]
arrayOfChar[++j] = hexDigits[k and 0xF]
i++
j++
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
/**
* 获取设备的可用内存大小
*
* @param context 应用上下文对象context
* @return 当前内存大小
*/
fun getDeviceUsableMemory(context: Context): Int {
val am = context.getSystemService(
Context.ACTIVITY_SERVICE
) as ActivityManager
val mi = ActivityManager.MemoryInfo()
am.getMemoryInfo(mi)
// 返回当前系统的可用内存
return (mi.availMem / (1024 * 1024)).toInt()
}
/**
* 获取手机系统SDK版本
*
* @return 如API 17 则返回 17
*/
val sdkVersion: Int
get() = android.os.Build.VERSION.SDK_INT
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/CommonUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.content.Context
import android.graphics.Color
import android.text.TextUtils
import android.util.TypedValue
import android.view.View
import android.view.inputmethod.InputMethodManager
import java.io.BufferedReader
import java.io.FileReader
import java.io.IOException
import java.lang.reflect.Field
import java.util.*
/**
* Created by chenxz on 2018/5/14.
*/
object CommonUtil {
init {
}
fun dp2px(context: Context, dpValue: Float): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.resources.displayMetrics).toInt()
}
fun sp2px(context: Context, spValue: Float): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.resources.displayMetrics).toInt()
}
/**
* 获取状态栏高度
*
* @param context context
* @return 状态栏高度
*/
fun getStatusBarHeight(context: Context): Int {
// 获得状态栏高度
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
return context.resources.getDimensionPixelSize(resourceId)
}
/**
* 获取随机rgb颜色值
*/
fun randomColor(): Int {
val random = Random()
//0-190, 如果颜色值过大,就越接近白色,就看不清了,所以需要限定范围
var red = random.nextInt(190)
var green = random.nextInt(190)
var blue = random.nextInt(190)
// if (SettingUtil.getIsNightMode()) {
// //150-255
// red = random.nextInt(105) + 150
// green = random.nextInt(105) + 150
// blue = random.nextInt(105) + 150
// }
//使用rgb混合生成一种新的颜色,Color.rgb生成的是一个int数
return Color.rgb(red, green, blue)
}
/**
* 解决InputMethodManager引起的内存泄漏
* 在Activity的onDestroy方法里调用
*/
fun fixInputMethodManagerLeak(context: Context) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val arr = arrayOf("mCurRootView", "mServedView", "mNextServedView")
var field: Field? = null
var objGet: Any? = null
for (i in arr.indices) {
val param = arr[i]
try {
field = imm.javaClass.getDeclaredField(param)
if (field.isAccessible === false) {
field.isAccessible = true
}
objGet = field.get(imm)
if (objGet != null && objGet is View) {
val view = objGet
if (view.context === context) {
// 被InputMethodManager持有引用的context是想要目标销毁的
field.set(imm, null) // 置空,破坏掉path to gc节点
} else {
// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了
break
}
}
} catch (t: Throwable) {
t.printStackTrace()
}
}
}
/**
* 获取当前进程名
*/
fun getProcessName(pid: Int): String {
var reader: BufferedReader? = null
try {
reader = BufferedReader(FileReader("/proc/$pid/cmdline"))
var processName = reader!!.readLine()
if (!TextUtils.isEmpty(processName)) {
processName = processName.trim({ it <= ' ' })
}
return processName
} catch (throwable: Throwable) {
throwable.printStackTrace()
} finally {
try {
if (reader != null) {
reader!!.close()
}
} catch (exception: IOException) {
exception.printStackTrace()
}
}
return ""
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/FileProvider7.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.core.content.FileProvider
import java.io.File
/**
* Created by chenxz on 2018/7/16.
*/
object FileProvider7 {
fun getUriForFile(context: Context, file: File): Uri? {
var fileUri: Uri? = null
if (Build.VERSION.SDK_INT >= 24) {
fileUri = getUriForFile24(context, file)
} else {
fileUri = Uri.fromFile(file)
}
return fileUri
}
fun getUriForFile24(context: Context, file: File): Uri {
val fileUri = FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
return fileUri
}
fun setIntentDataAndType(
context: Context,
intent: Intent,
type: String,
file: File,
writeAble: Boolean
) {
if (Build.VERSION.SDK_INT >= 24) {
intent.setDataAndType(getUriForFile(context, file), type)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
} else {
intent.setDataAndType(Uri.fromFile(file), type)
}
}
fun setIntentData(
context: Context,
intent: Intent,
file: File,
writeAble: Boolean
) {
if (Build.VERSION.SDK_INT >= 24) {
intent.data = getUriForFile(context, file)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
} else {
intent.data = Uri.fromFile(file)
}
}
fun grantPermissions(context: Context, intent: Intent, uri: Uri, writeAble: Boolean) {
var flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
if (writeAble) {
flag = flag or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
intent.addFlags(flag)
val resInfoList = context.packageManager
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
context.grantUriPermission(packageName, uri, flag)
}
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/KeyBoardUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.content.Context
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
/**
* @author chenxz
* @date 2018/11/19
* @desc KeyBoardUtil
*/
object KeyBoardUtil {
/**
* 是否落在 EditText 区域
*/
fun isHideKeyboard(view: View?, event: MotionEvent): Boolean {
if (view != null && view is EditText) {
val location = intArrayOf(0, 0)
view.getLocationInWindow(location)
//获取现在拥有焦点的控件view的位置,即EditText
val left = location[0]
val top = location[1]
val bottom = top + view.height
val right = left + view.width
//判断我们手指点击的区域是否落在EditText上面,如果不是,则返回true,否则返回false
val isInEt = (event.x > left && event.x < right && event.y > top && event.y < bottom)
return !isInEt
}
return false
}
/**
* 关闭软键盘
*/
fun hideKeyBoard(context: Context, view: View?) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view!!.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
/**
* 打卡软键盘
*/
fun openKeyBord(mEditText: EditText, mContext: Context) {
val imm = mContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(mEditText, InputMethodManager.RESULT_SHOWN)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
}
/**
* 关闭软键盘
*/
fun closeKeyBord(mEditText: EditText, mContext: Context) {
val imm = mContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(mEditText.windowToken, 0)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/NLog.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.util.Log
/**
* @author chenxz
* @date 2019/11/9
* @desc 日志打印类
*/
object NLog {
private const val TAG = "KotlinMVP"
private var debug: Boolean = false
fun openLog() {
debug = true
}
fun i(tag: String, content: String) {
if (debug) {
Log.i(tag, content)
}
}
fun i(content: String) {
i(TAG, content)
}
fun v(tag: String, content: String) {
if (debug) {
Log.v(tag, content)
}
}
fun v(content: String) {
v(TAG, content)
}
fun d(tag: String, content: String) {
if (debug) {
Log.d(tag, content)
}
}
fun d(content: String) {
d(TAG, content)
}
fun w(tag: String, content: String) {
if (debug) {
Log.w(tag, content)
}
}
fun w(content: String) {
w(TAG, content)
}
fun e(tag: String, content: String) {
if (debug) {
Log.e(tag, content)
}
}
fun e(content: String) {
e(TAG, content)
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/NetWorkUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.net.wifi.WifiManager
import android.provider.Settings
import android.telephony.TelephonyManager
import com.cxz.kotlin.baselibs.config.AppConfig
import java.net.NetworkInterface
import java.net.SocketException
/**
* @author admin
* @date 2018/11/21
* @desc
*/
object NetWorkUtil {
val NETWORK_WIFI = 1 // wifi network
val NETWORK_4G = 4 // "4G" networks
val NETWORK_3G = 3 // "3G" networks
val NETWORK_2G = 2 // "2G" networks
val NETWORK_UNKNOWN = 5 // unknown network
val NETWORK_NO = -1 // no network
private val NETWORK_TYPE_GSM = 16
private val NETWORK_TYPE_TD_SCDMA = 17
private val NETWORK_TYPE_IWLAN = 18
/**
* 打开网络设置界面
*
* 3.0以下打开设置界面
*
* @param context 上下文
*/
fun openWirelessSettings(context: Context) {
if (android.os.Build.VERSION.SDK_INT > 10) {
context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
} else {
context.startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
/**
* 获取活动网络信息
*
* 需添加权限
* ``
*
* @param context 上下文
*
* @return NetworkInfo
*/
@SuppressLint("MissingPermission")
private fun getActiveNetworkInfo(context: Context): NetworkInfo? {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return cm.activeNetworkInfo
}
/**
* 判断网络是否连接
*
* 需添加权限
* ``
*
* @return `true`: 是 `false`: 否
*/
fun isConnected(): Boolean {
return isConnected(AppConfig.getApplication())
}
/**
* 判断网络是否连接
*
* 需添加权限
* ``
*
* @return `true`: 是 `false`: 否
*/
fun isConnected(context: Context): Boolean {
val info = getActiveNetworkInfo(context)
return info != null && info.isConnected
}
/**
* 打开或关闭移动数据
*
* 需系统应用 需添加权限
* ``
*
* @param context 上下文
* @param enabled `true`: 打开 `false`: 关闭
*/
fun setDataEnabled(context: Context, enabled: Boolean) {
try {
val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val setMobileDataEnabledMethod =
tm.javaClass.getDeclaredMethod("setDataEnabled", Boolean::class.javaPrimitiveType)
setMobileDataEnabledMethod?.invoke(tm, enabled)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 判断网络是否是4G
*
* 需添加权限
* ``
*
* @param context 上下文
*
* @return `true`: 是 `false`: 否
*/
fun is4G(context: Context): Boolean {
val info = getActiveNetworkInfo(context)
return info != null && info.isAvailable && info.subtype == TelephonyManager.NETWORK_TYPE_LTE
}
/**
* 判断wifi是否打开
*
* 需添加权限
* ``
*
* @param context 上下文
*
* @return `true`: 是 `false`: 否
*/
fun getWifiEnabled(context: Context): Boolean {
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
return wifiManager.isWifiEnabled
}
/**
* 打开或关闭wifi
*
* 需添加权限
* ``
*
* @param context 上下文
*
* @param enabled true`: 打开 `false`: 关闭
*/
fun setWifiEnabled(context: Context, enabled: Boolean) {
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
if (enabled) {
if (!wifiManager.isWifiEnabled) {
// wifiManager.setWifiEnabled(true);
}
} else {
if (wifiManager.isWifiEnabled) {
// wifiManager.setWifiEnabled(false);
}
}
}
/**
* 判断wifi是否连接状态
*
* 需添加权限
* ``
*
* @param context 上下文
*
* @return `true`: 连接 `false`: 未连接
*/
fun isWifiConnected(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return cm.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI
}
/**
* 获取网络运营商名称
*
* 中国移动、如中国联通、中国电信
*
* @param context 上下文
*
* @return 运营商名称
*/
fun getNetworkOperatorName(context: Context): String? {
val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return tm?.networkOperatorName
}
/**
* 获取当前的网络类型(WIFI,2G,3G,4G)
*
* 需添加权限
* ``
*
* @param context 上下文
*
* @return 网络类型
*
* * [.NETWORK_WIFI] = 1;
* * [.NETWORK_4G] = 4;
* * [.NETWORK_3G] = 3;
* * [.NETWORK_2G] = 2;
* * [.NETWORK_UNKNOWN] = 5;
* * [.NETWORK_NO] = -1;
*
*/
fun getNetworkType(context: Context): Int {
var netType = NETWORK_NO
val info = getActiveNetworkInfo(context)
if (info != null && info.isAvailable) {
if (info.type == ConnectivityManager.TYPE_WIFI) {
netType = NETWORK_WIFI
} else if (info.type == ConnectivityManager.TYPE_MOBILE) {
when (info.subtype) {
NETWORK_TYPE_GSM, TelephonyManager.NETWORK_TYPE_GPRS, TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_EDGE, TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyManager.NETWORK_TYPE_IDEN -> netType =
NETWORK_2G
NETWORK_TYPE_TD_SCDMA, TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_HSDPA, TelephonyManager.NETWORK_TYPE_HSUPA, TelephonyManager.NETWORK_TYPE_HSPA, TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyManager.NETWORK_TYPE_HSPAP -> netType =
NETWORK_3G
NETWORK_TYPE_IWLAN, TelephonyManager.NETWORK_TYPE_LTE -> netType = NETWORK_4G
else -> {
val subtypeName = info.subtypeName
if ("TD-SCDMA".equals(subtypeName, ignoreCase = true) || "WCDMA".equals(
subtypeName,
ignoreCase = true
)
|| "CDMA2000".equals(subtypeName, ignoreCase = true)
) {
netType = NETWORK_3G
} else {
netType = NETWORK_UNKNOWN
}
}
}
} else {
netType = NETWORK_UNKNOWN
}
}
return netType
}
/**
* 获取当前的网络类型(WIFI,2G,3G,4G)
*
* 依赖上面的方法
*
* @param context 上下文
*
* @return 网络类型名称
*
* * NETWORK_WIFI
* * NETWORK_4G
* * NETWORK_3G
* * NETWORK_2G
* * NETWORK_UNKNOWN
* * NETWORK_NO
*
*/
fun getNetworkTypeName(context: Context): String {
return when (getNetworkType(context)) {
NETWORK_WIFI -> "NETWORK_WIFI"
NETWORK_4G -> "NETWORK_4G"
NETWORK_3G -> "NETWORK_3G"
NETWORK_2G -> "NETWORK_2G"
NETWORK_NO -> "NETWORK_NO"
else -> "NETWORK_UNKNOWN"
}
}
/**
* 获取IP地址
*
* 需添加权限
* ``
*
* @param useIPv4 是否用IPv4
*
* @return IP地址
*/
fun getIPAddress(useIPv4: Boolean): String? {
try {
val nis = NetworkInterface.getNetworkInterfaces()
while (nis.hasMoreElements()) {
val ni = nis.nextElement()
// 防止小米手机返回10.0.2.15
if (!ni.isUp) {
continue
}
val addresses = ni.inetAddresses
while (addresses.hasMoreElements()) {
val inetAddress = addresses.nextElement()
if (!inetAddress.isLoopbackAddress) {
val hostAddress = inetAddress.hostAddress
val isIPv4 = hostAddress.indexOf(':') < 0
if (useIPv4) {
if (isIPv4) {
return hostAddress
}
} else {
if (!isIPv4) {
val index = hostAddress.indexOf('%')
return if (index < 0)
hostAddress.toUpperCase()
else
hostAddress.substring(0, index).toUpperCase()
}
}
}
}
}
} catch (e: SocketException) {
e.printStackTrace()
}
return null
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/Preference.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import com.cxz.kotlin.baselibs.config.AppConfig
import java.io.*
import kotlin.reflect.KProperty
/**
* Created by chenxz on 2018/4/21.
* kotlin委托属性+SharedPreference实例
*/
class Preference(val name: String, private val default: T) {
companion object {
private val file_name = "wan_android_file"
private val prefs: SharedPreferences by lazy {
AppConfig.getApplication().getSharedPreferences(file_name, Context.MODE_PRIVATE)
}
/**
* 删除全部数据
*/
fun clearPreference() {
prefs.edit().clear().apply()
}
/**
* 根据key删除存储数据
*/
fun clearPreference(key: String) {
prefs.edit().remove(key).apply()
}
/**
* 查询某个key是否已经存在
*
* @param key
* @return
*/
fun contains(key: String): Boolean {
return prefs.contains(key)
}
/**
* 返回所有的键值对
*
* @param context
* @return
*/
fun getAll(): Map {
return prefs.all
}
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return getSharedPreferences(name, default)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putSharedPreferences(name, value)
}
@SuppressLint("CommitPrefEdits")
private fun putSharedPreferences(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> putString(name, serialize(value))
}.apply()
}
@Suppress("UNCHECKED_CAST")
private fun getSharedPreferences(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default) ?: ""
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> deSerialization(getString(name, serialize(default)) ?: "")
}
return res as T
}
/**
* 序列化对象
* @param person
* *
* @return
* *
* @throws IOException
*/
@Throws(IOException::class)
private fun serialize(obj: A): String {
val byteArrayOutputStream = ByteArrayOutputStream()
val objectOutputStream = ObjectOutputStream(
byteArrayOutputStream
)
objectOutputStream.writeObject(obj)
var serStr = byteArrayOutputStream.toString("ISO-8859-1")
serStr = java.net.URLEncoder.encode(serStr, "UTF-8")
objectOutputStream.close()
byteArrayOutputStream.close()
return serStr
}
/**
* 反序列化对象
* @param str
* *
* @return
* *
* @throws IOException
* *
* @throws ClassNotFoundException
*/
@Suppress("UNCHECKED_CAST")
@Throws(IOException::class, ClassNotFoundException::class)
private fun deSerialization(str: String): A {
val redStr = java.net.URLDecoder.decode(str, "UTF-8")
val byteArrayInputStream = ByteArrayInputStream(
redStr.toByteArray(charset("ISO-8859-1"))
)
val objectInputStream = ObjectInputStream(
byteArrayInputStream
)
val obj = objectInputStream.readObject() as A
objectInputStream.close()
byteArrayInputStream.close()
return obj
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/RomUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.os.Build
import android.text.TextUtils
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
/**
* Created by chenxz on 2018/4/21.
*/
object RomUtil {
internal object AvailableRomType {
val MIUI = 1
val FLYME = 2
val ANDROID_NATIVE = 3
val NA = 4
}
fun isLightStatusBarAvailable(): Boolean {
return isMIUIV6OrAbove() || isFlymeV4OrAbove() || isAndroidMOrAbove()
}
fun getLightStatausBarAvailableRomType(): Int {
if (isMIUIV6OrAbove()) {
return AvailableRomType.MIUI
}
if (isFlymeV4OrAbove()) {
return AvailableRomType.FLYME
}
return if (isAndroidMOrAbove()) {
AvailableRomType.ANDROID_NATIVE
} else AvailableRomType.NA
}
//Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA]
//Flyme V5的displayId格式为 [Flyme 5.x.x.x beta]
private fun isFlymeV4OrAbove(): Boolean {
val displayId = Build.DISPLAY
if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
val displayIdArray = displayId.split(" ".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
for (temp in displayIdArray) {
//版本号4以上,形如4.x.
if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*".toRegex())) {
return true
}
}
}
return false
}
//MIUI V6对应的versionCode是4
//MIUI V7对应的versionCode是5
private fun isMIUIV6OrAbove(): Boolean {
val miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code")
if (!TextUtils.isEmpty(miuiVersionCodeStr)) {
try {
val miuiVersionCode = Integer.parseInt(miuiVersionCodeStr)
if (miuiVersionCode >= 4) {
return true
}
} catch (e: Exception) {
}
}
return false
}
//Android Api 23以上
private fun isAndroidMOrAbove(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
}
private fun getSystemProperty(propName: String): String? {
val line: String
var input: BufferedReader? = null
try {
val p = Runtime.getRuntime().exec("getprop " + propName)
input = BufferedReader(InputStreamReader(p.inputStream), 1024)
line = input!!.readLine()
input!!.close()
} catch (ex: IOException) {
return null
} finally {
if (input != null) {
try {
input!!.close()
} catch (e: IOException) {
}
}
}
return line
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/RxTimerUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import java.util.concurrent.TimeUnit
/**
* @author chenxz
* @date 2019/1/10
* @desc
*/
object RxTimerUtil {
private var mDisposable: Disposable? = null
/**
* 延迟 milliseconds 毫秒后执行 task
*/
fun postDelay(milliseconds: Long, task: ((Long) -> Unit)? = null) {
mDisposable = Observable.timer(milliseconds, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { number ->
task?.invoke(number)
cancel()
}
}
/**
* 每隔 milliseconds 毫秒循环执行 task
*/
fun interval(milliseconds: Long, task: ((Long) -> Unit)? = null) {
mDisposable = Observable.interval(milliseconds, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { number ->
task?.invoke(number)
}
}
/**
* 取消任务
*/
fun cancel() {
mDisposable?.let {
if (!it.isDisposed) {
it.dispose()
}
}
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/utils/StatusBarUtil.kt
================================================
package com.cxz.kotlin.baselibs.utils
import android.annotation.TargetApi
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.os.Build
import androidx.annotation.ColorInt
import androidx.annotation.IntRange
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.drawerlayout.widget.DrawerLayout
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.LinearLayout
import com.cxz.kotlin.baselibs.R
/**
* Created by chenxz on 2018/4/21.
* 沉浸式状态栏工具类
*/
object StatusBarUtil {
val DEFAULT_STATUS_BAR_ALPHA = 112
private val FAKE_STATUS_BAR_VIEW_ID = R.id.statusbarutil_fake_status_bar_view
private val FAKE_TRANSLUCENT_VIEW_ID = R.id.statusbarutil_translucent_view
private val TAG_KEY_HAVE_SET_OFFSET = -123
/**
* 设置状态栏颜色
*
* @param activity 需要设置的 activity
* @param color 状态栏颜色值
*/
fun setColor(activity: Activity, @ColorInt color: Int) {
setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA)
}
/**
* 设置状态栏颜色
*
* @param activity 需要设置的activity
* @param color 状态栏颜色值
* @param statusBarAlpha 状态栏透明度
*/
fun setColor(activity: Activity, @ColorInt color: Int, @IntRange(from = 0, to = 255) statusBarAlpha: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
activity.window.statusBarColor = calculateStatusColor(color, statusBarAlpha)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
val decorView = activity.window.decorView as ViewGroup
val fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID)
if (fakeStatusBarView != null) {
if (fakeStatusBarView!!.visibility === View.GONE) {
fakeStatusBarView!!.visibility = View.VISIBLE
}
fakeStatusBarView!!.setBackgroundColor(calculateStatusColor(color, statusBarAlpha))
} else {
decorView.addView(createStatusBarView(activity, color, statusBarAlpha))
}
setRootView(activity)
}
}
/**
* 为滑动返回界面设置状态栏颜色
*
* @param activity 需要设置的activity
* @param color 状态栏颜色值
*/
fun setColorForSwipeBack(activity: Activity, color: Int) {
setColorForSwipeBack(activity, color, DEFAULT_STATUS_BAR_ALPHA)
}
/**
* 为滑动返回界面设置状态栏颜色
*
* @param activity 需要设置的activity
* @param color 状态栏颜色值
* @param statusBarAlpha 状态栏透明度 @IntRange(from = 0, to = 255)
*/
fun setColorForSwipeBack(activity: Activity, @ColorInt color: Int, statusBarAlpha: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val contentView = activity.findViewById(android.R.id.content) as ViewGroup
val rootView = contentView.getChildAt(0)
val statusBarHeight = getStatusBarHeight(activity)
if (rootView != null && rootView is androidx.coordinatorlayout.widget.CoordinatorLayout) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
rootView.fitsSystemWindows = false
contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha))
val isNeedRequestLayout = contentView.paddingTop < statusBarHeight
if (isNeedRequestLayout) {
contentView.setPadding(0, statusBarHeight, 0, 0)
rootView.post { rootView.requestLayout() }
}
} else {
rootView.setStatusBarBackgroundColor(calculateStatusColor(color, statusBarAlpha))
}
} else {
contentView.setPadding(0, statusBarHeight, 0, 0)
contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha))
}
setTransparentForWindow(activity)
}
}
/**
* 设置状态栏纯色 不加半透明效果
*
* @param activity 需要设置的 activity
* @param color 状态栏颜色值
*/
fun setColorNoTranslucent(activity: Activity, @ColorInt color: Int) {
setColor(activity, color, 0)
}
/**
* 设置状态栏颜色(5.0以下无半透明效果,不建议使用)
*
* @param activity 需要设置的 activity
* @param color 状态栏颜色值
*/
@Deprecated("")
fun setColorDiff(activity: Activity, @ColorInt color: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
transparentStatusBar(activity)
val contentView = activity.findViewById(android.R.id.content) as ViewGroup
// 移除半透明矩形,以免叠加
val fakeStatusBarView = contentView.findViewById(FAKE_STATUS_BAR_VIEW_ID)
if (fakeStatusBarView != null) {
if (fakeStatusBarView!!.visibility === View.GONE) {
fakeStatusBarView!!.visibility = View.VISIBLE
}
fakeStatusBarView!!.setBackgroundColor(color)
} else {
contentView.addView(createStatusBarView(activity, color))
}
setRootView(activity)
}
/**
* 使状态栏半透明
*
*
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
*
* @param activity 需要设置的activity
*/
fun setTranslucent(activity: Activity) {
setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA)
}
/**
* 使状态栏半透明
*
*
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
*
* @param activity 需要设置的activity
* @param statusBarAlpha 状态栏透明度 @IntRange(from = 0, to = 255)
*/
fun setTranslucent(activity: Activity, statusBarAlpha: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
setTransparent(activity)
addTranslucentView(activity, statusBarAlpha)
}
/**
* 针对根布局是 CoordinatorLayout, 使状态栏半透明
*
*
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
*
* @param activity 需要设置的activity
* @param statusBarAlpha 状态栏透明度 @IntRange(from = 0, to = 255)
*/
fun setTranslucentForCoordinatorLayout(activity: Activity, statusBarAlpha: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
transparentStatusBar(activity)
addTranslucentView(activity, statusBarAlpha)
}
/**
* 设置状态栏全透明
*
* @param activity 需要设置的activity
*/
fun setTransparent(activity: Activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
transparentStatusBar(activity)
setRootView(activity)
}
/**
* 使状态栏透明(5.0以上半透明效果,不建议使用)
*
*
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
*
* @param activity 需要设置的activity
*/
@Deprecated("")
fun setTranslucentDiff(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置状态栏透明
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
setRootView(activity)
}
}
/**
* 为DrawerLayout 布局设置状态栏变色
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
* @param color 状态栏颜色值
*/
fun setColorForDrawerLayout(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout, @ColorInt color: Int) {
setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA)
}
/**
* 为DrawerLayout 布局设置状态栏颜色,纯色
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
* @param color 状态栏颜色值
*/
fun setColorNoTranslucentForDrawerLayout(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout, @ColorInt color: Int) {
setColorForDrawerLayout(activity, drawerLayout, color, 0)
}
/**
* 为DrawerLayout 布局设置状态栏变色
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
* @param color 状态栏颜色值
* @param statusBarAlpha 状态栏透明度 @IntRange(from = 0, to = 255)
*/
fun setColorForDrawerLayout(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout, @ColorInt color: Int, statusBarAlpha: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
activity.window.statusBarColor = Color.TRANSPARENT
} else {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
// 生成一个状态栏大小的矩形
// 添加 statusBarView 到布局中
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
val fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID)
if (fakeStatusBarView != null) {
if (fakeStatusBarView!!.visibility === View.GONE) {
fakeStatusBarView!!.visibility = View.VISIBLE
}
fakeStatusBarView!!.setBackgroundColor(color)
} else {
contentLayout.addView(createStatusBarView(activity, color), 0)
}
// 内容布局不是 LinearLayout 时,设置padding top
if (contentLayout !is LinearLayout && contentLayout.getChildAt(1) != null) {
contentLayout.getChildAt(1)
.setPadding(contentLayout.paddingLeft, getStatusBarHeight(activity) + contentLayout.paddingTop,
contentLayout.paddingRight, contentLayout.paddingBottom)
}
// 设置属性
setDrawerLayoutProperty(drawerLayout, contentLayout)
addTranslucentView(activity, statusBarAlpha)
}
/**
* 设置 DrawerLayout 属性
*
* @param drawerLayout DrawerLayout
* @param drawerLayoutContentLayout DrawerLayout 的内容布局
*/
private fun setDrawerLayoutProperty(drawerLayout: androidx.drawerlayout.widget.DrawerLayout, drawerLayoutContentLayout: ViewGroup) {
val drawer = drawerLayout.getChildAt(1) as ViewGroup
drawerLayout.fitsSystemWindows = false
drawerLayoutContentLayout.fitsSystemWindows = false
drawerLayoutContentLayout.clipToPadding = true
drawer.fitsSystemWindows = false
}
/**
* 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用)
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
* @param color 状态栏颜色值
*/
@Deprecated("")
fun setColorForDrawerLayoutDiff(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout, @ColorInt color: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
// 生成一个状态栏大小的矩形
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
val fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID)
if (fakeStatusBarView != null) {
if (fakeStatusBarView.visibility === View.GONE) {
fakeStatusBarView.visibility = View.VISIBLE
}
fakeStatusBarView!!.setBackgroundColor(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA))
} else {
// 添加 statusBarView 到布局中
contentLayout.addView(createStatusBarView(activity, color), 0)
}
// 内容布局不是 LinearLayout 时,设置padding top
if (contentLayout !is LinearLayout && contentLayout.getChildAt(1) != null) {
contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0)
}
// 设置属性
setDrawerLayoutProperty(drawerLayout, contentLayout)
}
}
/**
* 为 DrawerLayout 布局设置状态栏透明
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
*/
fun setTranslucentForDrawerLayout(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout) {
setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA)
}
/**
* 为 DrawerLayout 布局设置状态栏透明
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
*/
fun setTranslucentForDrawerLayout(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout, statusBarAlpha: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
setTransparentForDrawerLayout(activity, drawerLayout)
addTranslucentView(activity, statusBarAlpha)
}
/**
* 为 DrawerLayout 布局设置状态栏透明
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
*/
fun setTransparentForDrawerLayout(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
activity.window.statusBarColor = Color.TRANSPARENT
} else {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
// 内容布局不是 LinearLayout 时,设置padding top
if (contentLayout !is LinearLayout && contentLayout.getChildAt(1) != null) {
contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0)
}
// 设置属性
setDrawerLayoutProperty(drawerLayout, contentLayout)
}
/**
* 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用)
*
* @param activity 需要设置的activity
* @param drawerLayout DrawerLayout
*/
@Deprecated("")
fun setTranslucentForDrawerLayoutDiff(activity: Activity, drawerLayout: androidx.drawerlayout.widget.DrawerLayout) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置状态栏透明
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
// 设置内容布局属性
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
contentLayout.fitsSystemWindows = true
contentLayout.clipToPadding = true
// 设置抽屉布局属性
val vg = drawerLayout.getChildAt(1) as ViewGroup
vg.fitsSystemWindows = false
// 设置 DrawerLayout 属性
drawerLayout.fitsSystemWindows = false
}
}
/**
* 为头部是 ImageView 的界面设置状态栏全透明
*
* @param activity 需要设置的activity
* @param needOffsetView 需要向下偏移的 View
*/
fun setTransparentForImageView(activity: Activity, needOffsetView: View) {
setTranslucentForImageView(activity, 0, needOffsetView)
}
/**
* 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度)
*
* @param activity 需要设置的activity
* @param needOffsetView 需要向下偏移的 View
*/
fun setTranslucentForImageView(activity: Activity, needOffsetView: View) {
setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView)
}
/**
* 为头部是 ImageView 的界面设置状态栏透明
*
* @param activity 需要设置的activity
* @param statusBarAlpha 状态栏透明度
* @param needOffsetView 需要向下偏移的 View
*/
fun setTranslucentForImageView(
activity: Activity, statusBarAlpha: Int,
needOffsetView: View?
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
setTransparentForWindow(activity)
addTranslucentView(activity, statusBarAlpha)
if (needOffsetView != null) {
val haveSetOffset = needOffsetView!!.getTag(TAG_KEY_HAVE_SET_OFFSET)
if (haveSetOffset != null && haveSetOffset as Boolean) {
return
}
val layoutParams = needOffsetView!!.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.setMargins(
layoutParams.leftMargin, layoutParams.topMargin + getStatusBarHeight(activity),
layoutParams.rightMargin, layoutParams.bottomMargin
)
needOffsetView!!.setTag(TAG_KEY_HAVE_SET_OFFSET, true)
}
}
/**
* 为 fragment 头部是 ImageView 的设置状态栏透明
*
* @param activity fragment 对应的 activity
* @param needOffsetView 需要向下偏移的 View
*/
fun setTranslucentForImageViewInFragment(activity: Activity, needOffsetView: View) {
setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView)
}
/**
* 为 fragment 头部是 ImageView 的设置状态栏透明
*
* @param activity fragment 对应的 activity
* @param needOffsetView 需要向下偏移的 View
*/
fun setTransparentForImageViewInFragment(activity: Activity, needOffsetView: View) {
setTranslucentForImageViewInFragment(activity, 0, needOffsetView)
}
/**
* 为 fragment 头部是 ImageView 的设置状态栏透明
*
* @param activity fragment 对应的 activity
* @param statusBarAlpha 状态栏透明度
* @param needOffsetView 需要向下偏移的 View
*/
fun setTranslucentForImageViewInFragment(
activity: Activity, statusBarAlpha: Int,
needOffsetView: View
) {
setTranslucentForImageView(activity, statusBarAlpha, needOffsetView)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
clearPreviousSetting(activity)
}
}
/**
* 隐藏伪状态栏 View
*
* @param activity 调用的 Activity
*/
fun hideFakeStatusBarView(activity: Activity) {
val decorView = activity.window.decorView as ViewGroup
val fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID)
if (fakeStatusBarView != null) {
fakeStatusBarView!!.visibility = View.GONE
}
val fakeTranslucentView = decorView.findViewById(FAKE_TRANSLUCENT_VIEW_ID)
if (fakeTranslucentView != null) {
fakeTranslucentView!!.visibility = View.GONE
}
}
@TargetApi(Build.VERSION_CODES.M)
fun setLightMode(activity: Activity) {
setMIUIStatusBarDarkIcon(activity, true)
setMeizuStatusBarDarkIcon(activity, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR //or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
@TargetApi(Build.VERSION_CODES.M)
fun setDarkMode(activity: Activity) {
setMIUIStatusBarDarkIcon(activity, false)
setMeizuStatusBarDarkIcon(activity, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//activity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
/**
* 修改 MIUI V6 以上状态栏颜色
*/
private fun setMIUIStatusBarDarkIcon(activity: Activity, darkIcon: Boolean) {
val clazz = activity.window.javaClass
try {
val layoutParams = Class.forName("android.view.MiuiWindowManager\$LayoutParams")
val field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE")
val darkModeFlag = field.getInt(layoutParams)
val extraFlagField =
clazz.getMethod("setExtraFlags", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
extraFlagField.invoke(activity.window, if (darkIcon) darkModeFlag else 0, darkModeFlag)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 修改魅族状态栏字体颜色 Flyme 4.0
*/
private fun setMeizuStatusBarDarkIcon(activity: Activity, darkIcon: Boolean) {
try {
val lp = activity.window.attributes
val darkFlag = WindowManager.LayoutParams::class.java.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON")
val meizuFlags = WindowManager.LayoutParams::class.java.getDeclaredField("meizuFlags")
darkFlag.isAccessible = true
meizuFlags.isAccessible = true
val bit = darkFlag.getInt(null)
var value = meizuFlags.getInt(lp)
if (darkIcon) {
value = value or bit
} else {
value = value and bit.inv()
}
meizuFlags.setInt(lp, value)
activity.window.attributes = lp
} catch (e: Exception) {
//e.printStackTrace();
}
}
///////////////////////////////////////////////////////////////////////////////////
@TargetApi(Build.VERSION_CODES.KITKAT)
private fun clearPreviousSetting(activity: Activity) {
val decorView = activity.window.decorView as ViewGroup
val fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID)
if (fakeStatusBarView != null) {
decorView.removeView(fakeStatusBarView)
val rootView = (activity.findViewById(android.R.id.content) as ViewGroup).getChildAt(0) as ViewGroup
rootView.setPadding(0, 0, 0, 0)
}
}
/**
* 添加半透明矩形条
*
* @param activity 需要设置的 activity
* @param statusBarAlpha 透明值
*/
private fun addTranslucentView(activity: Activity, statusBarAlpha: Int) {
val contentView = activity.findViewById(android.R.id.content) as ViewGroup
val fakeTranslucentView = contentView.findViewById(FAKE_TRANSLUCENT_VIEW_ID)
if (fakeTranslucentView != null) {
if (fakeTranslucentView!!.visibility === View.GONE) {
fakeTranslucentView!!.visibility = View.VISIBLE
}
fakeTranslucentView!!.setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0))
} else {
contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha))
}
}
/**
* 生成一个和状态栏大小相同的彩色矩形条
*
* @param activity 需要设置的 activity
* @param color 状态栏颜色值
* @return 状态栏矩形条
*/
private fun createStatusBarView(activity: Activity, @ColorInt color: Int): View {
return createStatusBarView(activity, color, 0)
}
/**
* 生成一个和状态栏大小相同的半透明矩形条
*
* @param activity 需要设置的activity
* @param color 状态栏颜色值
* @param alpha 透明值
* @return 状态栏矩形条
*/
private fun createStatusBarView(activity: Activity, @ColorInt color: Int, @IntRange(from = 0, to = 255) alpha: Int): View {
// 绘制一个和状态栏一样高的矩形
val statusBarView = View(activity)
val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity))
statusBarView.layoutParams = params
statusBarView.setBackgroundColor(calculateStatusColor(color, alpha))
statusBarView.id = FAKE_STATUS_BAR_VIEW_ID
return statusBarView
}
/**
* 设置根布局参数
*/
private fun setRootView(activity: Activity) {
val parent = activity.findViewById(android.R.id.content) as ViewGroup
var i = 0
val count = parent.childCount
while (i < count) {
val childView = parent.getChildAt(i)
if (childView is ViewGroup) {
childView.setFitsSystemWindows(true)
childView.clipToPadding = true
}
i++
}
}
/**
* 设置透明
*/
private fun setTransparentForWindow(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.statusBarColor = Color.TRANSPARENT
activity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity.window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
/**
* 使状态栏透明
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private fun transparentStatusBar(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
activity.window.statusBarColor = Color.TRANSPARENT
} else {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
/**
* 创建半透明矩形 View
*
* @param alpha 透明值
* @return 半透明 View
*/
private fun createTranslucentStatusBarView(activity: Activity, @IntRange(from = 0, to = 255) alpha: Int): View {
// 绘制一个和状态栏一样高的矩形
val statusBarView = View(activity)
val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity))
statusBarView.layoutParams = params
statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
statusBarView.id = FAKE_TRANSLUCENT_VIEW_ID
return statusBarView
}
/**
* 获取状态栏高度
*
* @param context context
* @return 状态栏高度
*/
private fun getStatusBarHeight(context: Context): Int {
// 获得状态栏高度
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
return context.resources.getDimensionPixelSize(resourceId)
}
/**
* 计算状态栏颜色
*
* @param color color值
* @param alpha alpha值
* @return 最终的状态栏颜色
*/
private fun calculateStatusColor(@ColorInt color: Int, @IntRange(from = 0, to = 255) alpha: Int): Int {
if (alpha == 0) {
return color
}
val a = 1 - alpha / 255f
var red = color shr 16 and 0xff
var green = color shr 8 and 0xff
var blue = color and 0xff
red = (red * a + 0.5).toInt()
green = (green * a + 0.5).toInt()
blue = (blue * a + 0.5).toInt()
return 0xff shl 24 or (red shl 16) or (green shl 8) or blue
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/widget/CustomToast.kt
================================================
package com.cxz.kotlin.baselibs.widget
import android.content.Context
import android.view.Gravity
import android.view.View
import android.widget.TextView
import android.widget.Toast
import com.cxz.kotlin.baselibs.R
/**
* Created by chenxz on 2018/6/9.
* 自定义Toast
*/
class CustomToast {
private var toast: Toast
private var textView: TextView
constructor(context: Context?, message: String) : this(context, message, Toast.LENGTH_SHORT)
constructor(context: Context?, message: String, duration: Int) {
toast = Toast(context)
toast.duration = duration
val view = View.inflate(context, R.layout.toast_custom, null)
textView = view.findViewById(R.id.tv_prompt)
textView.text = message
toast.view = view
toast.setGravity(Gravity.CENTER, 0, 0)
}
fun show() {
toast.show()
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/widget/LoadingDialog.kt
================================================
package com.cxz.kotlin.baselibs.widget
import android.app.Dialog
import android.content.Context
import android.view.Gravity
import android.view.View
import com.cxz.kotlin.baselibs.R
import com.github.ybq.android.spinkit.style.Wave
import kotlinx.android.synthetic.main.layout_loading_dialog.*
/**
* @author chenxz
* @date 2018/11/27
* @desc
*/
class LoadingDialog : Dialog {
constructor(context: Context) : super(context)
constructor(context: Context, theme: Int) : super(context, theme)
override fun onWindowFocusChanged(hasFocus: Boolean) {
loadingBar.setIndeterminateDrawable(Wave())
super.onWindowFocusChanged(hasFocus)
}
companion object {
fun showDialog(context: Context, cancelable: Boolean): LoadingDialog? {
return showDialog(context, null, cancelable)
}
fun showDialog(context: Context, message: CharSequence?, cancelable: Boolean): LoadingDialog? {
val dialog = LoadingDialog(context, R.style.LoadingDialog)
dialog.setContentView(R.layout.layout_loading_dialog)
if (message.isNullOrBlank()) {
dialog.tv_load_msg?.visibility = View.GONE
} else {
dialog.tv_load_msg?.visibility = View.VISIBLE
dialog.tv_load_msg?.text = message
}
dialog.setCanceledOnTouchOutside(false)
dialog.setCancelable(cancelable)
dialog.window?.attributes?.gravity = Gravity.CENTER
val lp = dialog.window?.attributes
lp?.dimAmount = 0.2f
dialog.window?.attributes = lp
dialog.show()
return dialog
}
}
}
================================================
FILE: baselibs/src/main/java/com/cxz/kotlin/baselibs/widget/OnNoDoubleClickListener.kt
================================================
package com.cxz.kotlin.baselibs.widget
import android.view.View
/**
* @author admin
* @date 2018/11/23
* @desc 防止连续点击
*/
abstract class OnNoDoubleClickListener : View.OnClickListener {
private var mThrottleFirstTime: Long = 1000
private var mLastClickTime: Long = 0
constructor()
constructor(throttleFirstTime: Long) {
mThrottleFirstTime = throttleFirstTime
}
override fun onClick(v: View?) {
val currentTime = System.currentTimeMillis()
if (currentTime - mLastClickTime > mThrottleFirstTime) {
mLastClickTime = currentTime
onNoDoubleClick(v)
}
}
abstract fun onNoDoubleClick(v: View?)
}
================================================
FILE: baselibs/src/main/res/drawable/bg_loading_dialog.xml
================================================
================================================
FILE: baselibs/src/main/res/drawable/bg_toast_custom.xml
================================================
================================================
FILE: baselibs/src/main/res/layout/activity_base_title.xml
================================================
================================================
FILE: baselibs/src/main/res/layout/base_toolbar.xml
================================================
================================================
FILE: baselibs/src/main/res/layout/layout_loading_dialog.xml
================================================
================================================
FILE: baselibs/src/main/res/layout/toast_custom.xml
================================================
================================================
FILE: baselibs/src/main/res/values/colors.xml
================================================
#008577
#00574B
#D81B60
#ffffff
#ba000000
================================================
FILE: baselibs/src/main/res/values/dimen.xml
================================================
20dp
24dp
14sp
================================================
FILE: baselibs/src/main/res/values/ids.xml
================================================
================================================
FILE: baselibs/src/main/res/values/strings.xml
================================================
baselibs
================================================
FILE: baselibs/src/main/res/values/styles.xml
================================================
================================================
FILE: baselibs/src/main/res/xml/file_paths.xml
================================================
================================================
FILE: baselibs/src/main/res/xml/network_security_config.xml
================================================
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from :"config.gradle"
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: config.gradle
================================================
ext {
android = [
compileSdkVersion: 30,
buildToolsVersion: "30.0.3",
minSdkVersion : 16,
targetSdkVersion : 30,
versionCode : 1,
versionName : "1.0.0"
]
dependVersion = [
supportSdkVersion: "28.0.0",
retrofit : "2.4.0",
glide : "4.12.0",
arouter : "1.5.1",
rxJava : "2.2.2",
rxAndroid : "2.1.0",
rxKotlin : "2.3.0",
anko : "0.10.7"
]
androidxDeps = [
"appcompat" : 'androidx.appcompat:appcompat:1.2.0',
"material" : 'com.google.android.material:material:1.2.1',
"constraintlayout": 'androidx.constraintlayout:constraintlayout:2.0.4',
"androidx-core" : 'androidx.core:core-ktx:1.3.2',
"recyclerview" : "androidx.recyclerview:recyclerview:1.2.0",
]
// supportDeps = [
// "supportv4" : "com.android.support:support-v4:${dependVersion.supportSdkVersion}",
// "appcompatv7" : "com.android.support:appcompat-v7:${dependVersion.supportSdkVersion}",
// "cardview" : "com.android.support:cardview-v7:${dependVersion.supportSdkVersion}",
// "design" : "com.android.support:design:${dependVersion.supportSdkVersion}",
// "constraint-layout": "com.android.support.constraint:constraint-layout:1.1.3",
// "annotations" : "com.android.support:support-annotations:${dependVersion.supportSdkVersion}"
// ]
retrofit = [
"retrofit" : "com.squareup.retrofit2:retrofit:${dependVersion.retrofit}",
"retrofitConverterGson" : "com.squareup.retrofit2:converter-gson:${dependVersion.retrofit}",
"retrofitAdapterRxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${dependVersion.retrofit}",
"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" : 'androidx.test.ext:junit:1.1.1',
"espresso-core" : 'androidx.test.espresso:espresso-core:3.3.0',
"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:2.0.1',
"logger" : 'com.orhanobut:logger:2.2.0',
"glide" : "com.github.bumptech.glide:glide:${dependVersion.glide}",
"eventbus" : 'org.greenrobot:eventbus:3.1.1',
"spinkit" : 'com.github.ybq:Android-SpinKit:1.2.0',
"arouter" : "com.alibaba:arouter-api:${dependVersion.arouter}",
"rxpermissions": 'com.github.tbruyelle:rxpermissions:0.10.2'
]
annotationProcessorDeps = [
"arouter-compiler": "com.alibaba:arouter-compiler:${dependVersion.arouter}",
"glide-compiler" : "com.github.bumptech.glide:compiler:${dependVersion.glide}"
]
// supportLibs = supportDeps.values()
androisxLibs = androidxDeps.values()
networkLibs = retrofit.values()
rxJavaLibs = rxJava.values()
commonLibs = commonDeps.values()
annotationProcessorLibs = annotationProcessorDeps.values()
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sun Oct 27 19:16:55 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Disables R8 for Android Library modules only.
android.enableR8.libraries = false
# Disables R8 for all modules.
android.enableR8 = false
# trueǡɿģʽ falseģʽл
isModule=false
isMeModule=false
isNewsModule=false
isVideoModule=false
android.useAndroidX=true
android.enableJetifier=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: module_me/.gitignore
================================================
/build
================================================
FILE: module_me/build.gradle
================================================
if (isMeModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
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 {
if (isMeModule.toBoolean()) {
applicationId "com.cxz.module.me"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
resourcePrefix "me_"
sourceSets {
main {
if (isMeModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
kotlin {
exclude 'debug/**'
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation rootProject.ext.testDeps["junit"]
androidTestImplementation rootProject.ext.testDeps["runner"]
androidTestImplementation rootProject.ext.testDeps["espresso-core"]
api project(':baselibs')
kapt rootProject.ext.annotationProcessorDeps["arouter-compiler"]
}
================================================
FILE: module_me/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: module_me/src/main/AndroidManifest.xml
================================================
================================================
FILE: module_me/src/main/java/com/cxz/module/me/MeMainActivity.kt
================================================
package com.cxz.module.me
import com.alibaba.android.arouter.facade.annotation.Route
import com.cxz.kotlin.baselibs.base.BaseMvpActivity
import com.cxz.module.me.mvp.contract.MeMainContract
import com.cxz.module.news.mvp.persenter.MeMainPresenter
@Route(path = "/me/main")
class MeMainActivity : BaseMvpActivity(), MeMainContract.View {
override fun initView() {
super.initView()
}
override fun start() {
}
override fun createPresenter(): MeMainContract.Presenter = MeMainPresenter()
override fun attachLayoutRes(): Int = R.layout.me_activity_me_main
}
================================================
FILE: module_me/src/main/java/com/cxz/module/me/mvp/contract/MeMainContract.kt
================================================
package com.cxz.module.me.mvp.contract
import com.cxz.kotlin.baselibs.mvp.IModel
import com.cxz.kotlin.baselibs.mvp.IPresenter
import com.cxz.kotlin.baselibs.mvp.IView
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
interface MeMainContract {
interface View : IView {
}
interface Presenter : IPresenter {
}
interface Model : IModel {
}
}
================================================
FILE: module_me/src/main/java/com/cxz/module/me/mvp/model/MeMainModel.kt
================================================
package com.cxz.module.me.mvp.model
import com.cxz.kotlin.baselibs.mvp.BaseModel
import com.cxz.module.me.mvp.contract.MeMainContract
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
class MeMainModel : BaseModel(), MeMainContract.Model {
}
================================================
FILE: module_me/src/main/java/com/cxz/module/me/mvp/persenter/MeMainPresenter.kt
================================================
package com.cxz.module.news.mvp.persenter
import com.cxz.kotlin.baselibs.mvp.BasePresenter
import com.cxz.module.me.mvp.contract.MeMainContract
import com.cxz.module.me.mvp.model.MeMainModel
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
class MeMainPresenter : BasePresenter(), MeMainContract.Presenter {
override fun createModel(): MeMainContract.Model? = MeMainModel()
}
================================================
FILE: module_me/src/main/java/debug/MeApplication.kt
================================================
package debug
import android.app.Application
import com.cxz.kotlin.baselibs.config.AppConfig
/**
* @author chenxz
* @date 2018/12/22
* @desc
*/
class MeApplication : Application() {
override fun onCreate() {
super.onCreate()
AppConfig.init(this)
}
}
================================================
FILE: module_me/src/main/module/AndroidManifest.xml
================================================
================================================
FILE: module_me/src/main/res/layout/me_activity_me_main.xml
================================================
================================================
FILE: module_me/src/main/res/values/colors.xml
================================================
================================================
FILE: module_me/src/main/res/values/strings.xml
================================================
module_me
================================================
FILE: module_me/src/main/res/values/styles.xml
================================================
================================================
FILE: module_news/.gitignore
================================================
/build
================================================
FILE: module_news/build.gradle
================================================
if (isNewsModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
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 {
if (isNewsModule.toBoolean()) {
applicationId "com.cxz.module.news"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
resourcePrefix "news_"
sourceSets {
main {
if (isNewsModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
kotlin {
exclude 'debug/**'
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation rootProject.ext.testDeps["junit"]
androidTestImplementation rootProject.ext.testDeps["runner"]
androidTestImplementation rootProject.ext.testDeps["espresso-core"]
api project(':baselibs')
kapt rootProject.ext.annotationProcessorDeps["arouter-compiler"]
}
================================================
FILE: module_news/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: module_news/src/main/AndroidManifest.xml
================================================
================================================
FILE: module_news/src/main/java/com/cxz/module/news/NewsMainActivity.kt
================================================
package com.cxz.module.news
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.base.BaseMvpActivity
import com.cxz.kotlin.baselibs.ext.showToast
import com.cxz.module.news.mvp.contract.NewsMainContract
import com.cxz.module.news.mvp.persenter.NewsMainPresenter
@Route(path = "/news/main")
class NewsMainActivity : BaseMvpActivity(), NewsMainContract.View {
@Autowired
@JvmField
var key1: String? = null
@Autowired
@JvmField
var key2: String? = null
override fun initView() {
ARouter.getInstance().inject(this)
super.initView()
showToast("key1: $key1, key2: $key2")
}
override fun start() {
}
override fun createPresenter(): NewsMainContract.Presenter = NewsMainPresenter()
override fun attachLayoutRes(): Int = R.layout.news_activity_news_main
}
================================================
FILE: module_news/src/main/java/com/cxz/module/news/NewsServiceImpl.kt
================================================
package com.cxz.module.news
import android.content.Context
import com.alibaba.android.arouter.facade.annotation.Route
import com.cxz.kotlin.baselibs.provider.NewsService
/**
* @author chenxz
* @date 2018/12/22
* @desc
*/
@Route(path = "/news/service", name = "News")
class NewsServiceImpl : NewsService {
override fun getNewsName(): String = "这是一条新闻信息"
override fun init(context: Context?) {
}
}
================================================
FILE: module_news/src/main/java/com/cxz/module/news/mvp/contract/NewsMainContract.kt
================================================
package com.cxz.module.news.mvp.contract
import com.cxz.kotlin.baselibs.mvp.IModel
import com.cxz.kotlin.baselibs.mvp.IPresenter
import com.cxz.kotlin.baselibs.mvp.IView
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
interface NewsMainContract {
interface View : IView {
}
interface Presenter : IPresenter {
}
interface Model : IModel {
}
}
================================================
FILE: module_news/src/main/java/com/cxz/module/news/mvp/model/NewsMainModel.kt
================================================
package com.cxz.module.news.mvp.model
import com.cxz.kotlin.baselibs.mvp.BaseModel
import com.cxz.module.news.mvp.contract.NewsMainContract
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
class NewsMainModel : BaseModel(), NewsMainContract.Model {
}
================================================
FILE: module_news/src/main/java/com/cxz/module/news/mvp/persenter/NewsMainPresenter.kt
================================================
package com.cxz.module.news.mvp.persenter
import com.cxz.kotlin.baselibs.mvp.BasePresenter
import com.cxz.module.news.mvp.contract.NewsMainContract
import com.cxz.module.news.mvp.model.NewsMainModel
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
class NewsMainPresenter : BasePresenter(), NewsMainContract.Presenter {
override fun createModel(): NewsMainContract.Model? = NewsMainModel()
}
================================================
FILE: module_news/src/main/java/debug/NewsApplication.kt
================================================
package debug
import android.app.Application
import com.cxz.kotlin.baselibs.config.AppConfig
/**
* @author chenxz
* @date 2018/12/22
* @desc
*/
class NewsApplication : Application() {
override fun onCreate() {
super.onCreate()
AppConfig.init(this)
}
}
================================================
FILE: module_news/src/main/module/AndroidManifest.xml
================================================
================================================
FILE: module_news/src/main/res/layout/news_activity_news_main.xml
================================================
================================================
FILE: module_news/src/main/res/values/colors.xml
================================================
================================================
FILE: module_news/src/main/res/values/strings.xml
================================================
module_news
================================================
FILE: module_news/src/main/res/values/styles.xml
================================================
================================================
FILE: module_video/.gitignore
================================================
/build
================================================
FILE: module_video/build.gradle
================================================
if (isVideoModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
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 {
if (isVideoModule.toBoolean()) {
applicationId "com.cxz.module.video"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
resourcePrefix "video_"
sourceSets {
main {
if (isVideoModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
kotlin {
exclude 'debug/**'
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation rootProject.ext.testDeps["junit"]
androidTestImplementation rootProject.ext.testDeps["runner"]
androidTestImplementation rootProject.ext.testDeps["espresso-core"]
api project(':baselibs')
kapt rootProject.ext.annotationProcessorDeps["arouter-compiler"]
}
================================================
FILE: module_video/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: module_video/src/main/AndroidManifest.xml
================================================
================================================
FILE: module_video/src/main/java/com/cxz/module/video/VideoMainActivity.kt
================================================
package com.cxz.module.video
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.base.BaseMvpActivity
import com.cxz.kotlin.baselibs.ext.showToast
import com.cxz.module.news.mvp.contract.VideoMainContract
import com.cxz.module.news.mvp.persenter.VideoMainPresenter
@Route(path = "/video/main")
class VideoMainActivity : BaseMvpActivity(), VideoMainContract.View {
@Autowired
@JvmField
var key1: String? = null
@Autowired
@JvmField
var key2: String? = null
override fun initView() {
ARouter.getInstance().inject(this)
super.initView()
showToast("key1: $key1, key2: $key2")
}
override fun start() {
}
override fun createPresenter(): VideoMainContract.Presenter = VideoMainPresenter()
override fun attachLayoutRes(): Int = R.layout.video_activity_video_main
}
================================================
FILE: module_video/src/main/java/com/cxz/module/video/mvp/contract/VideoMainContract.kt
================================================
package com.cxz.module.news.mvp.contract
import com.cxz.kotlin.baselibs.mvp.IModel
import com.cxz.kotlin.baselibs.mvp.IPresenter
import com.cxz.kotlin.baselibs.mvp.IView
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
interface VideoMainContract {
interface View : IView {
}
interface Presenter : IPresenter {
}
interface Model : IModel {
}
}
================================================
FILE: module_video/src/main/java/com/cxz/module/video/mvp/model/VideoMainModel.kt
================================================
package com.cxz.module.news.mvp.model
import com.cxz.kotlin.baselibs.mvp.BaseModel
import com.cxz.module.news.mvp.contract.VideoMainContract
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
class VideoMainModel : BaseModel(), VideoMainContract.Model {
}
================================================
FILE: module_video/src/main/java/com/cxz/module/video/mvp/persenter/VideoMainPresenter.kt
================================================
package com.cxz.module.news.mvp.persenter
import com.cxz.kotlin.baselibs.mvp.BasePresenter
import com.cxz.module.news.mvp.contract.VideoMainContract
import com.cxz.module.news.mvp.model.VideoMainModel
/**
* @author chenxz
* @date 2018/12/25
* @desc
*/
class VideoMainPresenter : BasePresenter(), VideoMainContract.Presenter {
override fun createModel(): VideoMainContract.Model? = VideoMainModel()
}
================================================
FILE: module_video/src/main/java/debug/VideoApplication.kt
================================================
package debug
import android.app.Application
import com.cxz.kotlin.baselibs.config.AppConfig
/**
* @author chenxz
* @date 2018/12/22
* @desc
*/
class VideoApplication : Application() {
override fun onCreate() {
super.onCreate()
AppConfig.init(this)
}
}
================================================
FILE: module_video/src/main/module/AndroidManifest.xml
================================================
================================================
FILE: module_video/src/main/res/layout/video_activity_video_main.xml
================================================
================================================
FILE: module_video/src/main/res/values/colors.xml
================================================
================================================
FILE: module_video/src/main/res/values/strings.xml
================================================
module_video
================================================
FILE: module_video/src/main/res/values/styles.xml
================================================
================================================
FILE: settings.gradle
================================================
include ':app', ':baselibs', ':module_news', ':module_me', ':module_video'