├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── key.jks
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── therouter
│ │ └── routeMap.json
│ ├── java
│ └── pan
│ │ └── lib
│ │ └── baseandroidframework
│ │ ├── CustomApplication.kt
│ │ └── ui
│ │ ├── SplashActivity.kt
│ │ └── main
│ │ └── MainActivity.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_main.xml
│ └── activity_splash.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── architecture.png
├── build.gradle.kts
├── common_lib
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── pan
│ │ └── lib
│ │ └── common_lib
│ │ ├── base
│ │ ├── BaseActivity.kt
│ │ ├── BaseApplication.kt
│ │ ├── BaseDialogFragment.kt
│ │ └── BaseRepository.kt
│ │ ├── data
│ │ └── User.kt
│ │ ├── retrofit
│ │ ├── ExceptionHandle.kt
│ │ ├── NetResult.kt
│ │ ├── Response.kt
│ │ ├── ResultException.kt
│ │ └── RetrofitManager.kt
│ │ ├── utils
│ │ ├── BitmapUtil.kt
│ │ ├── DESUtil.java
│ │ ├── LogUtil.kt
│ │ ├── PreferencesUtil.kt
│ │ ├── UserHelper.kt
│ │ ├── WindowUtil.java
│ │ └── ext
│ │ │ ├── DimensionExt.kt
│ │ │ ├── EditTextExt.kt
│ │ │ ├── JsonExt.kt
│ │ │ └── ViewModelExt.kt
│ │ ├── views
│ │ └── BottomDecoration.kt
│ │ └── websocket
│ │ ├── BaseLifecycle.kt
│ │ └── WSManager.kt
│ └── res
│ ├── anim
│ ├── popup_in.xml
│ └── popup_out.xml
│ ├── layout
│ └── activity_base.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── demo
├── .gitignore
├── README.md
├── build.gradle.kts
├── consumer-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── example
│ │ └── demo
│ │ ├── di
│ │ └── WanNetModule.kt
│ │ ├── mockwebserver
│ │ └── MockWebSocket.kt
│ │ ├── pojo
│ │ ├── Article.kt
│ │ ├── ArticlesData.kt
│ │ ├── Data.kt
│ │ ├── Protocol.kt
│ │ ├── Result.java
│ │ └── WanResponse.kt
│ │ ├── repository
│ │ └── ArticleRepository.kt
│ │ ├── service
│ │ ├── DemoWebsocketService.kt
│ │ └── WanApiService.kt
│ │ └── ui
│ │ ├── top
│ │ ├── TopArticleActivity.kt
│ │ ├── TopArticleAdapter.kt
│ │ └── TopArticleViewModel.kt
│ │ └── websocket_demo
│ │ ├── WebsocketDemoActivity.kt
│ │ └── WebsocketDemoViewModel.kt
│ └── res
│ ├── layout
│ ├── activity_top_article.xml
│ ├── activity_websocket_demo.xml
│ └── item_top_article.xml
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 | /.idea/
87 | /app/release/
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
BaseAndroidFramework
2 |
3 | 一款基于google官方推荐架构体系封装和Jetpack的MVVM框架。致力于写出最精简,易读性又高的代码,又保证项目的健壮性。
4 | 项目正在搭建中,如果喜欢的话不妨点个star~
5 |
6 |
7 | ## 主要技术栈
8 | - Kotlin+ Coroutines+KTX
9 | - JetPack
10 | - [Dagger-Hilt](https://developer.android.com/training/dependency-injection/hilt-android)-用于依赖注入(ViewModel,Repository,ApiService)
11 | - LiveData
12 | - Lifecycle
13 | - ViewModel
14 |
15 | - [TheRouter](https://github.com/HuolalaTech/hll-wp-therouter-android) -跨模块通信框架
16 | - [Retrofit2 & OkHttp4](https://github.com/square/retrofit) -Retrofit2.11.0搭配协程
17 | - [Scarlet & Rxjava](https://github.com/Tinder/Scarlet) -Retrofit风格的WebSocket client
18 | - [Logger](https://github.com/orhanobut/logger) - Simple, pretty and powerful logger for android
19 |
20 | - [Kotson](https://github.com/SalomonBrys/Kotson)- 更简易的Gson使用 val userInfo: User="user json".toObject()
21 |
22 |
23 |
24 |
25 | ## HTTP请求Example
26 |
27 | ```kotlin
28 | @AndroidEntryPoint //注解的入口
29 | class TopArticleActivity : BaseActivity() {
30 | private val topArticleViewModel: TopArticleViewModel by viewModels()
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | setContentView(R.layout.activity_top_article)
34 |
35 | initView()
36 |
37 | topArticleViewModel.fetchTopArticle()
38 |
39 |
40 | }
41 |
42 | }
43 | ```
44 |
45 | #### @ViewModelInject依赖注入声明,会自动生成TopArticleViewModel_AssistedFactory,注入ArticleRepository
46 | ```kotlin
47 | class TopArticleViewModel @ViewModelInject constructor(private val articleRepository: ArticleRepository) :
48 | ViewModel() {
49 |
50 |
51 | val articleList = MutableLiveData>()
52 |
53 | fun fetchTopArticle() {
54 | launchOnUI {
55 | val response = articleRepository.fetchTopArticle()
56 |
57 | response.whenSuccess {
58 | articleList.value = it.toMutableList()
59 | }
60 |
61 | }
62 | }
63 |
64 | }
65 | ```
66 |
67 | ```kotlin
68 | @Singleton
69 | class ArticleRepository @Inject constructor(private val wanApiService: WanApiService) :
70 | BaseRepository() {
71 | suspend fun fetchTopArticle(): NetResult> {
72 | return fetchApi { wanApiService.topArticle() }
73 | }
74 |
75 | }
76 | ```
77 |
78 | ```kotlin
79 | @InstallIn(SingletonComponent::class)
80 | @Module
81 | object WanNetModule {
82 | @Provides
83 | @Singleton
84 | fun provideService(): WanApiService = RetrofitManager.getApiService(
85 | WanApiService::class.java) //提供WanApiService单例,只需要写一次
86 |
87 | }
88 | ```
89 |
90 |
91 |
92 | ## WebSocket请求Example
93 |
94 | #### 声明接口
95 | ```kotlin
96 | interface DemoWebsocketService {
97 | @Send
98 | fun send(protocol: Protocol)
99 |
100 | @Receive
101 | fun observeCustomInfo(): Flowable
102 |
103 | @Receive
104 | fun observeWebSocketEvent(): Flowable
105 |
106 | @Send
107 | fun send(msg: String)
108 | }
109 | ```
110 |
111 | #### 初始化WSManager
112 | ```kotlin
113 | val wsManager: WSManager = WSManager()
114 | ```
115 |
116 | #### 发送消息
117 | ```kotlin
118 | wsManager.service.send(message)
119 | ```
120 |
121 | #### 接收消息
122 | ```kotlin
123 | wsManager.service.observeCustomInfo().subscribe {
124 | receivedMessages.postValue(it.message)
125 | }
126 | ```
127 | ## 架构图
128 | 
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidApplication)
3 | alias(libs.plugins.kotlinAndroid)
4 | alias(libs.plugins.kotlinKsp)
5 | alias(libs.plugins.daggerHiltAndroidPlugin)
6 | id ("therouter")
7 | }
8 | android {
9 | namespace = "pan.lib.baseandroidframework"
10 |
11 | compileSdk = libs.versions.compileSdkVersion.get().toInt()
12 |
13 | defaultConfig {
14 | applicationId = "pan.lib.baseandroidframework"
15 | minSdk = libs.versions.minSdk.get().toInt()
16 | targetSdk = libs.versions.targetSdk.get().toInt()
17 | versionCode = 1
18 | versionName = "1.0"
19 | ndk {
20 | abiFilters.add("arm64-v8a")
21 | }
22 |
23 | }
24 |
25 | signingConfigs {
26 | create("jks") {
27 | storeFile = file("key.jks")
28 | storePassword = "yesterday you said tomorrow"
29 | keyAlias = "key"
30 | keyPassword = "yesterday you said tomorrow"
31 | }
32 | }
33 | buildFeatures {
34 | viewBinding = true
35 | }
36 | buildTypes {
37 | release {
38 | signingConfig = signingConfigs.getByName("jks")
39 | isMinifyEnabled = true
40 | isShrinkResources = true
41 | proguardFiles(
42 | getDefaultProguardFile("proguard-android-optimize.txt"),
43 | "proguard-rules.pro"
44 | )
45 | }
46 | debug {
47 | signingConfig = signingConfigs.getByName("jks")
48 | isMinifyEnabled = false
49 | }
50 | }
51 | compileOptions {
52 | sourceCompatibility = JavaVersion.VERSION_17
53 | targetCompatibility = JavaVersion.VERSION_17
54 | }
55 |
56 | kotlinOptions {
57 | jvmTarget = "17"
58 | }
59 |
60 | hilt {
61 | enableAggregatingTask = false
62 | }
63 | }
64 |
65 | dependencies {
66 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
67 | implementation(project(":demo"))
68 |
69 | implementation(project(":common_lib"))
70 | // implementation(libs.baseAndroidFramework)
71 | implementation(libs.hilt.android)
72 | ksp(libs.hilt.compiler)
73 | ksp (libs.therouter.apt)
74 | }
75 |
--------------------------------------------------------------------------------
/app/key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/key.jks
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep public class com.alibaba.android.arouter.routes.**{*;}
23 | -keep public class com.alibaba.android.arouter.facade.**{*;}
24 | -keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
25 |
26 | # If you use the byType method to obtain Service, add the following rules to protect the interface:
27 | -keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
28 |
29 | # If single-type injection is used, that is, no interface is defined to implement IProvider, the following rules need to be added to protect the implementation
30 | -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
31 | -dontwarn javax.lang.model.element.**
32 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
9 |
10 |
11 |
21 |
22 |
23 |
26 |
27 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/assets/therouter/routeMap.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "path": "/demo/WebsocketDemoActivity",
4 | "className": "com.example.demo.ui.websocket_demo.WebsocketDemoActivity",
5 | "action": "",
6 | "description": "",
7 | "params": {}
8 | },
9 | {
10 | "path": "/demo/TopArticleActivity",
11 | "className": "com.example.demo.ui.top.TopArticleActivity",
12 | "action": "",
13 | "description": "",
14 | "params": {}
15 | }
16 | ]
--------------------------------------------------------------------------------
/app/src/main/java/pan/lib/baseandroidframework/CustomApplication.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.baseandroidframework
2 |
3 | import dagger.hilt.android.HiltAndroidApp
4 | import pan.lib.common_lib.base.BaseApplication
5 |
6 | /**
7 | *
8 | * Author: pan qi
9 | * CreateDate: 2020/6/3 15:44
10 | */
11 | @HiltAndroidApp
12 | class CustomApplication : BaseApplication() {
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/pan/lib/baseandroidframework/ui/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.baseandroidframework.ui
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.os.Handler
7 | import android.os.Looper
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import pan.lib.baseandroidframework.databinding.ActivitySplashBinding
11 | import pan.lib.baseandroidframework.ui.main.MainActivity
12 | import pan.lib.common_lib.base.BaseActivity
13 | import pan.lib.common_lib.utils.UserHelper
14 |
15 | @SuppressLint("CustomSplashScreen")
16 | class SplashActivity : BaseActivity() {
17 | private lateinit var binding: ActivitySplashBinding
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | showToolbar(false)
21 | if (UserHelper.userInfo != null) {
22 | Handler(Looper.getMainLooper()).postDelayed({
23 | startActivity(Intent(this, MainActivity::class.java))
24 | finish()
25 | }, 2000)
26 | } else {
27 | startActivity(Intent(this, MainActivity::class.java))
28 | finish()
29 | }
30 |
31 |
32 | }
33 |
34 | override fun getLayout(layoutInflater: LayoutInflater): View {
35 | binding = ActivitySplashBinding.inflate(layoutInflater)
36 | return binding.root
37 | }
38 |
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/pan/lib/baseandroidframework/ui/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.baseandroidframework.ui.main
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import com.therouter.TheRouter
7 | import pan.lib.baseandroidframework.databinding.ActivityMainBinding
8 | import pan.lib.common_lib.base.BaseActivity
9 |
10 | class MainActivity : BaseActivity() {
11 |
12 | private lateinit var binding: ActivityMainBinding
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 |
16 | setTitle("首页")
17 | binding.btRouter.setOnClickListener {
18 | TheRouter.build("/demo/TopArticleActivity")
19 | .navigation(this)
20 | }
21 |
22 | binding.btWebsocket.setOnClickListener {
23 | TheRouter.build("/demo/WebsocketDemoActivity")
24 | .navigation(this)
25 | }
26 | }
27 |
28 |
29 | override fun getLayout(layoutInflater: LayoutInflater): View {
30 | binding = ActivityMainBinding.inflate(layoutInflater)
31 | return binding.root
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BaseAndroidFramework
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/architecture.png
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | plugins {
4 | alias(libs.plugins.androidApplication) apply false
5 | alias(libs.plugins.kotlinAndroid) apply false
6 | alias(libs.plugins.androidLibrary) apply false
7 | alias(libs.plugins.kotlinKsp) apply false
8 | alias(libs.plugins.daggerHiltAndroidPlugin) apply false
9 | alias(libs.plugins.theRouter) apply false
10 | // id("cn.therouter.agp8") version "1.2.1" apply false
11 |
12 | }
13 | true // Needed to make the Suppress annotation work for the plugins block
14 |
15 |
--------------------------------------------------------------------------------
/common_lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/common_lib/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlinAndroid)
4 | alias(libs.plugins.kotlinKsp)
5 | alias(libs.plugins.daggerHiltAndroidPlugin)
6 | id("maven-publish")
7 | }
8 |
9 |
10 | android {
11 | namespace = "pan.lib.common_lib"
12 | compileSdk = libs.versions.compileSdkVersion.get().toInt()
13 |
14 |
15 | defaultConfig {
16 | minSdk = libs.versions.minSdk.get().toInt()
17 | consumerProguardFiles("consumer-rules.pro")
18 |
19 | }
20 | buildFeatures {
21 | viewBinding = true
22 | buildConfig = true
23 | }
24 |
25 |
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_17
28 | targetCompatibility = JavaVersion.VERSION_17
29 | }
30 |
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 |
35 | publishing {
36 | singleVariant("release") {
37 | withSourcesJar()
38 | withJavadocJar()
39 | }
40 | }
41 | }
42 |
43 | dependencies {
44 | api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
45 | api(libs.material)
46 | api(libs.androidx.annotation)
47 | api(libs.androidx.lifecycle.extensions)
48 | api(libs.androidx.core.ktx)
49 | api(libs.androidx.lifecycle.viewmodel.ktx)
50 | api(libs.androidx.fragment.ktx)
51 | api(libs.org.jetbrains.kotlinx.coroutines.core)
52 | api(libs.androidx.appcompat)
53 | api(libs.androidx.constraintlayout)
54 | api(libs.androidx.multidex)
55 | api(libs.androidx.preference.ktx)
56 |
57 | implementation(libs.hilt.android)
58 | ksp(libs.hilt.compiler)
59 |
60 | // Third-party libraries
61 | api(libs.glide)
62 | ksp(libs.glide.ksp)
63 | api(libs.glide.transformations)
64 | api(libs.com.google.gson)
65 | api(libs.com.github.salomonbrys.kotson)
66 | api(libs.org.greenrobot.eventbus)
67 | api(libs.baseRecyclerViewAdapterHelper)
68 | api(libs.com.squareup.retrofit2.retrofit)
69 | api(libs.com.squareup.retrofit2.converter.gson)
70 | api(libs.com.squareup.okhttp3.logging.interceptor)
71 | api(libs.com.orhanobut.logger)
72 | api(libs.router)
73 | api(libs.com.gyf.immersionbar)
74 | implementation(libs.com.tencent.mmkv.static)
75 |
76 | api(libs.com.squareup.okhttp3)
77 | api(libs.com.squareup.okhttp3.mockwebserver)
78 | api(libs.com.tinder.scarlet)
79 | api(libs.com.tinder.scarlet.websocket.okhttp)
80 | api(libs.com.tinder.scarlet.message.adapter.gson)
81 | api(libs.com.tinder.scarlet.stream.adapter.rxjava2)
82 | api(libs.com.tinder.scarlet.lifecycle.android)
83 | }
84 |
85 |
86 | publishing { // 发布配置
87 | publications { // 发布的内容
88 | register("release") { // 注册一个名字为 release 的发布内容
89 | groupId = "com.github.PanPersonalProject"
90 | artifactId = "common_lib"
91 | version = "1.0.0"
92 |
93 | afterEvaluate { // 在所有的配置都完成之后执行
94 | // 从当前 module 的 release 包中发布
95 | from(components["release"])
96 | }
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/common_lib/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | #-------------------------------------------定制化区域----------------------------------------------
2 | #---------------------------------1.实体类---------------------------------
3 |
4 |
5 | #-------------------------------------------------------------------------
6 |
7 | #---------------------------------2.第三方包-------------------------------
8 |
9 |
10 |
11 | #eventbus
12 | -keepattributes *Annotation*
13 | -keepclassmembers class ** {
14 | @org.greenrobot.eventbus.Subscribe ;
15 | }
16 | -keep enum org.greenrobot.eventbus.ThreadMode { *; }
17 |
18 |
19 | #TheRouter
20 | -keep class androidx.annotation.Keep
21 | -keep @androidx.annotation.Keep class * {*;}
22 | -keepclassmembers class * {
23 | @androidx.annotation.Keep *;
24 | }
25 | -keepclasseswithmembers class * {
26 | @androidx.annotation.Keep ;
27 | }
28 | -keepclasseswithmembers class * {
29 | @androidx.annotation.Keep ;
30 | }
31 | -keepclasseswithmembers class * {
32 | @androidx.annotation.Keep (...);
33 | }
34 | -keepclasseswithmembers class * {
35 | @com.therouter.router.Autowired ;
36 | }
37 |
38 | # 保留 Scarlet 和rxjava相关的类和方法
39 | -keep class com.tinder.scarlet.** { *; }
40 | -keep class io.reactivex.** { *; }
41 |
42 |
43 | #ImmersionBar
44 | -keep class com.gyf.immersionbar.* {*;}
45 | -dontwarn com.gyf.immersionbar.**
46 |
47 | #-------------------------------------------------------------------------
48 |
49 | #---------------------------------3.与js互相调用的类------------------------
50 |
51 |
52 |
53 | #-------------------------------------------------------------------------
54 |
55 | #---------------------------------4.反射相关的类和方法-----------------------
56 |
57 |
58 | #----------------------------------------------------------------------------
59 | #---------------------------------------------------------------------------------------------------
60 |
61 | #-------------------------------------------基本不用动区域--------------------------------------------
62 | #指定压缩级别
63 | -optimizationpasses 5
64 |
65 | #不跳过非公共的库的类成员
66 | #-dontskipnonpubliclibraryclassmembers
67 |
68 | #混淆时采用的算法
69 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
70 |
71 | #把混淆类中的方法名也混淆了
72 | -useuniqueclassmembernames
73 |
74 | #优化时允许访问并修改有修饰符的类和类的成员
75 | -allowaccessmodification
76 |
77 | #将文件来源重命名为“SourceFile”字符串
78 | -renamesourcefileattribute SourceFile
79 | #保留行号
80 | -keepattributes SourceFile,LineNumberTable
81 |
82 | #保持所有实现 Serializable 接口的类成员
83 | -keepclassmembers class * implements java.io.Serializable {
84 | static final long serialVersionUID;
85 | private static final java.io.ObjectStreamField[] serialPersistentFields;
86 | private void writeObject(java.io.ObjectOutputStream);
87 | private void readObject(java.io.ObjectInputStream);
88 | java.lang.Object writeReplace();
89 | java.lang.Object readResolve();
90 | }
91 |
92 | #Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
93 | -keep public class * extends android.app.Fragment
94 | -keep public class * extends androidx.fragment.app.Fragment
95 |
--------------------------------------------------------------------------------
/common_lib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/common_lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.annotation.StringRes
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.gyf.immersionbar.ImmersionBar
9 | import com.therouter.TheRouter
10 | import pan.lib.common_lib.R
11 | import pan.lib.common_lib.databinding.ActivityBaseBinding
12 |
13 | abstract class BaseActivity: AppCompatActivity() {
14 |
15 | private lateinit var baseBinding: ActivityBaseBinding
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | TheRouter.inject(this)
19 | baseBinding=ActivityBaseBinding.inflate(layoutInflater)
20 | baseBinding.viewContent.addView(getLayout(layoutInflater))
21 | setContentView(baseBinding.root)
22 | initStatusBar()
23 | initToolbar()
24 |
25 | }
26 |
27 | private fun initToolbar() {
28 | setSupportActionBar(baseBinding.commonToolbar);
29 | supportActionBar?.setDisplayShowTitleEnabled(false)
30 | }
31 |
32 |
33 | abstract fun getLayout(layoutInflater: LayoutInflater): View
34 |
35 | open fun initStatusBar() {
36 | ImmersionBar.with(this)
37 | .statusBarColor(R.color.colorPrimary)
38 | .keyboardEnable(true) //解决软键盘与底部输入框冲突问题
39 | .init();
40 | }
41 |
42 | /**
43 | * 设置标题
44 | * @param title
45 | */
46 | fun setTitle(title: String?) {
47 | baseBinding.tvTitle.text = title
48 |
49 | }
50 |
51 | /**
52 | * 设置标题
53 | * @param resId
54 | */
55 | override fun setTitle(@StringRes resId: Int) {
56 | baseBinding.tvTitle.setText(resId)
57 | }
58 |
59 | fun enableBack() {
60 | supportActionBar?.setDisplayHomeAsUpEnabled(true)//左侧添加一个默认的返回图标
61 | supportActionBar?.setDisplayShowHomeEnabled(true)//设置返回键可用
62 | baseBinding.commonToolbar.setNavigationOnClickListener { finish() }
63 | }
64 |
65 |
66 | open fun showToolbar(isShowing: Boolean) {
67 | baseBinding.commonToolbar.visibility = if (isShowing) View.VISIBLE else View.GONE
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/base/BaseApplication.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.base
2 |
3 | import android.content.Context
4 | import androidx.multidex.MultiDexApplication
5 | import com.tencent.mmkv.MMKV
6 | import com.therouter.TheRouter
7 | import pan.lib.common_lib.utils.initLogger
8 |
9 |
10 | /**
11 | *
12 | * Author: pan qi
13 | * CreateDate: 2020/6/4 16:53
14 | */
15 |
16 | val applicationContext: Context
17 | get() = BaseApplication.instance.applicationContext
18 |
19 | abstract class BaseApplication : MultiDexApplication() {
20 |
21 | companion object {
22 | lateinit var instance: BaseApplication
23 | }
24 |
25 | override fun onCreate() {
26 | super.onCreate()
27 | instance = this
28 | initLogger()
29 | initMMKV()
30 | }
31 |
32 | override fun attachBaseContext(base: Context?) {
33 | TheRouter.isDebug=true
34 | super.attachBaseContext(base)
35 | }
36 |
37 | private fun initMMKV() {
38 | MMKV.initialize(this)
39 | }
40 |
41 |
42 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/base/BaseDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.base
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import android.view.Gravity
6 | import android.view.Window
7 | import android.view.WindowManager
8 | import androidx.fragment.app.DialogFragment
9 | import androidx.fragment.app.FragmentManager
10 | import pan.lib.common_lib.R
11 | import pan.lib.common_lib.utils.WindowUtil
12 |
13 | /**
14 | *
15 | * 背景透明的 DialogFragment基类
16 | */
17 | abstract class BaseDialogFragment : DialogFragment() {
18 |
19 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
20 | val dialog = super.onCreateDialog(savedInstanceState)
21 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
22 | dialog.setCanceledOnTouchOutside(true)
23 | val window = dialog.window
24 | window?.setBackgroundDrawableResource(android.R.color.transparent)
25 | return dialog
26 | }
27 |
28 | val isShowing: Boolean
29 | get() {
30 | val dialog = dialog
31 | return dialog != null && dialog.isShowing
32 | }
33 |
34 | fun show(fragmentManager: FragmentManager?) {
35 | if (fragmentManager == null || isShowing) return
36 | val tag = this.javaClass.simpleName
37 | if (fragmentManager.findFragmentByTag(tag) != null) return
38 | fragmentManager.beginTransaction()
39 | .add(this, tag).commitAllowingStateLoss()
40 | }
41 |
42 | fun setGravityBottom() {
43 | setWindowAttr(
44 | Gravity.BOTTOM, WindowManager.LayoutParams.MATCH_PARENT,
45 | WindowManager.LayoutParams.WRAP_CONTENT
46 | )
47 | }
48 |
49 | // 设置window属性
50 | fun setWindowAttr(gravity: Int, width: Int, height: Int) {
51 | val dialog = dialog ?: return
52 | // 设置宽度为屏宽, 靠近屏幕底部。
53 | val window = dialog.window ?: return
54 | WindowUtil.setWindowGravity(window, gravity)
55 | WindowUtil.setWindowSize(window, width, height)
56 | window.setWindowAnimations(R.style.Animation_CustomPopup)
57 | }
58 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/base/BaseRepository.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.base
2 |
3 | import pan.lib.common_lib.retrofit.ExceptionHandle
4 | import pan.lib.common_lib.retrofit.NetResult
5 | import pan.lib.common_lib.retrofit.Response
6 | import pan.lib.common_lib.retrofit.ResultException
7 |
8 | /**
9 | *
10 | * Author: pan qi
11 | * CreateDate: 2020/7/10
12 | */
13 | open class BaseRepository {
14 |
15 | protected suspend fun fetchApi(call: suspend () -> Response): NetResult {
16 | return try {
17 | checkResponse(call())
18 | } catch (e: Exception) {
19 | NetResult.Error(ExceptionHandle.handleException(e))
20 | }
21 | }
22 |
23 | protected suspend fun fetchRawApi(call: suspend () -> T): NetResult {
24 | return try {
25 | NetResult.Success(call())
26 | } catch (e: Exception) {
27 | NetResult.Error(ExceptionHandle.handleException(e))
28 | }
29 | }
30 |
31 |
32 | private fun checkResponse(response: Response): NetResult {
33 | return if (response.getCode() == 0) {
34 | NetResult.Success(response.getResponse())
35 | } else {
36 | NetResult.Error(
37 | ResultException(
38 | response.getCode(),
39 | response.getMessage()
40 | )
41 | )
42 | }
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/data/User.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.data
2 |
3 | /**
4 | *
5 | * Author: pan qi
6 | * CreateDate: 2020/6/9 11:26
7 | */
8 | class User {
9 | var userId: Int = 0
10 | var name: String = ""
11 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/retrofit/ExceptionHandle.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.retrofit
2 |
3 | import android.net.ParseException
4 | import com.google.gson.JsonParseException
5 | import com.google.gson.stream.MalformedJsonException
6 | import org.json.JSONException
7 | import retrofit2.HttpException
8 | import java.net.ConnectException
9 | import java.net.SocketTimeoutException
10 | import java.net.UnknownHostException
11 | import javax.net.ssl.SSLException
12 |
13 | /**
14 | * author: Pan
15 | * date: 2020/7/14
16 | */
17 | object ExceptionHandle {
18 |
19 | fun handleException(e: Throwable?): ResultException {
20 | return if (e is HttpException) {
21 | when (e.code()) {
22 | UNAUTHORIZED -> ResultException(e.code(), "操作未授权")
23 | FORBIDDEN -> ResultException(e.code(), "请求被拒绝")
24 | NOT_FOUND -> ResultException(e.code(), "资源不存在")
25 | REQUEST_TIMEOUT -> ResultException(e.code(), "服务器执行超时")
26 | INTERNAL_SERVER_ERROR -> ResultException(e.code(), "服务器内部错误")
27 | SERVICE_UNAVAILABLE -> ResultException(e.code(), "服务器不可用")
28 | else -> ResultException(e.code(), "网络错误")
29 | }
30 | } else if (e is JsonParseException
31 | || e is JSONException
32 | || e is ParseException || e is MalformedJsonException
33 | ) {
34 | ResultException(PARSE_ERROR, "解析错误")
35 | } else if (e is ConnectException) {
36 | ResultException(NETWORK_ERROR, "连接失败")
37 | } else if (e is SSLException) {
38 | ResultException(SSL_ERROR, "证书验证失败")
39 | } else if (e is SocketTimeoutException) {
40 | ResultException(TIMEOUT_ERROR, "连接超时")
41 | } else if (e is UnknownHostException) {
42 | ResultException(UNKNOWN_HOST_ERROR, "主机地址未知")
43 | } else {
44 | ResultException(UNKNOWN, "未知错误")
45 | }
46 | }
47 |
48 | private const val UNAUTHORIZED = 401
49 | private const val FORBIDDEN = 403
50 | private const val NOT_FOUND = 404
51 | private const val REQUEST_TIMEOUT = 408
52 | private const val INTERNAL_SERVER_ERROR = 500
53 | private const val SERVICE_UNAVAILABLE = 503
54 |
55 | /**
56 | * 未知错误
57 | */
58 | private const val UNKNOWN = 1000
59 |
60 | /**
61 | * 解析错误
62 | */
63 | private const val PARSE_ERROR = 1001
64 |
65 | /**
66 | * 网络错误
67 | */
68 | private const val NETWORK_ERROR = 1002
69 |
70 | /**
71 | * 主机地址错误
72 | */
73 | private const val UNKNOWN_HOST_ERROR = 1003
74 |
75 | /**
76 | * 证书出错
77 | */
78 | private const val SSL_ERROR = 1005
79 |
80 | /**
81 | * 连接超时
82 | */
83 | private const val TIMEOUT_ERROR = 1006
84 |
85 |
86 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/retrofit/NetResult.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.retrofit
2 |
3 | /**
4 | * author: Pan
5 | * date: 2020/7/13
6 | */
7 | sealed class NetResult {
8 |
9 | data class Success(val data: T) : NetResult()
10 | data class Error(val exception: ResultException) : NetResult()
11 |
12 |
13 | fun whenSuccess(block: (T) -> Unit) {
14 | if (this is Success) {
15 | block(this.data)
16 | }
17 | }
18 |
19 |
20 | fun whenFailure(block: (Error) -> Unit) {
21 | if (this is Error) {
22 | block(this)
23 | }
24 | }
25 |
26 |
27 | override fun toString(): String {
28 | return when (this) {
29 | is Success<*> -> "Success[data=$data]"
30 | is Error -> "Error[exception=$exception]"
31 | }
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/retrofit/Response.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.retrofit
2 |
3 | /**
4 | *
5 | * Author: pan qi
6 | * CreateDate: 2020/7/13
7 | * 适配多种格式的Response
8 | */
9 | interface Response {
10 | fun getCode(): Int
11 | fun getMessage(): String
12 | fun getResponse(): T
13 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/retrofit/ResultException.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.retrofit
2 |
3 | /**
4 | *
5 | * Author: pan qi
6 | * CreateDate: 2020/7/13
7 | */
8 | class ResultException(var errCode: Int, var msg: String?) : Exception(msg)
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/retrofit/RetrofitManager.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.retrofit
2 |
3 | import okhttp3.MediaType.Companion.toMediaType
4 | import okhttp3.OkHttpClient
5 | import okhttp3.RequestBody
6 | import okhttp3.RequestBody.Companion.toRequestBody
7 | import okhttp3.logging.HttpLoggingInterceptor
8 | import pan.lib.common_lib.BuildConfig
9 | import pan.lib.common_lib.utils.printNetLog
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.gson.GsonConverterFactory
12 | import java.util.concurrent.TimeUnit
13 |
14 |
15 | /**
16 | *
17 | * Author: pan qi
18 | * CreateDate: 2020/6/4 14:26
19 | */
20 | object RetrofitManager {
21 | private var retrofit = initRetrofit()
22 |
23 | /**
24 | * 用于生成post json的RequestBody
25 | * */
26 | fun getRequestBody(json: String): RequestBody {
27 | return json.toRequestBody("application/json; charset=utf-8".toMediaType())
28 | }
29 |
30 |
31 | private fun initRetrofit(): Retrofit {
32 | return Retrofit.Builder()
33 | .client(initOkHttpClient())
34 | .baseUrl("https://www.wanandroid.com")
35 | .addConverterFactory(GsonConverterFactory.create())
36 | .build()
37 | }
38 |
39 | private fun initOkHttpClient(): OkHttpClient {
40 | val builder = OkHttpClient().newBuilder()
41 | builder.readTimeout(10, TimeUnit.SECONDS);
42 | builder.connectTimeout(10, TimeUnit.SECONDS);
43 | if (BuildConfig.DEBUG) {
44 | builder.addInterceptor(getLoggingInterceptor())
45 | }
46 | return builder.build()
47 | }
48 |
49 | private fun getLoggingInterceptor(): HttpLoggingInterceptor {
50 | val loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
51 | private val mMessage = StringBuilder()
52 | override fun log(message: String) {
53 | mMessage.append(message).append("\n")
54 | // 响应结束,打印整条日志
55 | if (message.startsWith("<-- END HTTP")) {
56 | printNetLog(mMessage.toString())
57 | mMessage.clear()
58 | }
59 | }
60 |
61 | })
62 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
63 | return loggingInterceptor
64 | }
65 |
66 | fun getApiService(apiServiceClass: Class): T {
67 | return retrofit.create(apiServiceClass)
68 | }
69 |
70 |
71 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/BitmapUtil.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils
2 | import android.graphics.Bitmap
3 | import android.graphics.BitmapFactory
4 | import androidx.annotation.DrawableRes
5 | import pan.lib.common_lib.base.applicationContext
6 |
7 |
8 | /**
9 | * AUTHOR Pan Created on 2021/12/27
10 | */
11 |
12 | fun makeBitmap(@DrawableRes resId: Int, width: Int): Bitmap {
13 | val res = applicationContext.resources
14 | val options = BitmapFactory.Options()
15 | options.inJustDecodeBounds = true
16 | BitmapFactory.decodeResource(res, resId, options)
17 | options.inJustDecodeBounds = false
18 | options.inDensity = options.outWidth
19 | options.inTargetDensity = width
20 | return BitmapFactory.decodeResource(res, resId, options)
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/DESUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 GcsSloop
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Last modified 2017-09-07 17:30:16
17 | *
18 | * GitHub: https://github.com/GcsSloop
19 | * WeiBo: http://weibo.com/GcsSloop
20 | * WebSite: http://www.gcssloop.com
21 | */
22 |
23 | package pan.lib.common_lib.utils;
24 |
25 | import android.annotation.SuppressLint;
26 |
27 | import androidx.annotation.IntDef;
28 |
29 | import java.nio.charset.StandardCharsets;
30 | import java.security.InvalidKeyException;
31 | import java.security.NoSuchAlgorithmException;
32 | import java.security.SecureRandom;
33 | import java.security.spec.InvalidKeySpecException;
34 |
35 | import javax.crypto.BadPaddingException;
36 | import javax.crypto.Cipher;
37 | import javax.crypto.IllegalBlockSizeException;
38 | import javax.crypto.NoSuchPaddingException;
39 | import javax.crypto.SecretKeyFactory;
40 | import javax.crypto.spec.DESKeySpec;
41 |
42 |
43 | public class DESUtil {
44 | @IntDef({Cipher.ENCRYPT_MODE, Cipher.DECRYPT_MODE})
45 | @interface DESType {
46 | }
47 |
48 |
49 | public static final String DES_KEY = "INPUT YOR DES KEY";
50 |
51 | public static String des(String content, @DESType int type) {
52 | try {
53 | SecureRandom random = new SecureRandom();
54 | DESKeySpec desKey = new DESKeySpec(DES_KEY.getBytes());
55 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
56 | @SuppressLint("GetInstance") Cipher cipher = Cipher.getInstance("DES");
57 | cipher.init(type, keyFactory.generateSecret(desKey), random);
58 | if (type == Cipher.ENCRYPT_MODE) {
59 | byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);
60 | return parseByte2HexStr(cipher.doFinal(byteContent));
61 | } else {
62 | byte[] byteContent = parseHexStr2Byte(content);
63 | return new String(cipher.doFinal(byteContent));
64 | }
65 | } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException |
66 | InvalidKeyException | NoSuchPaddingException |
67 | InvalidKeySpecException e) {
68 | e.printStackTrace();
69 | }
70 | return null;
71 | }
72 |
73 | /**
74 | * 二进位组转十六进制字符串
75 | *
76 | * @param buf 二进位组
77 | * @return 十六进制字符串
78 | */
79 | public static String parseByte2HexStr(byte[] buf) {
80 | StringBuilder sb = new StringBuilder();
81 | for (byte b : buf) {
82 | String hex = Integer.toHexString(b & 0xFF);
83 | if (hex.length() == 1) {
84 | hex = '0' + hex;
85 | }
86 | sb.append(hex.toUpperCase());
87 | }
88 | return sb.toString();
89 | }
90 |
91 | /**
92 | * 十六进制字符串转二进位组
93 | *
94 | * @param hexStr 十六进制字符串
95 | * @return 二进位组
96 | */
97 | public static byte[] parseHexStr2Byte(String hexStr) {
98 | if (hexStr.length() < 1) return null;
99 | byte[] result = new byte[hexStr.length() / 2];
100 |
101 | for (int i = 0; i < hexStr.length() / 2; i++) {
102 | int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
103 | int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
104 | result[i] = (byte) (high * 16 + low);
105 | }
106 | return result;
107 | }
108 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/LogUtil.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils
2 |
3 | import android.util.Log
4 | import com.orhanobut.logger.AndroidLogAdapter
5 | import com.orhanobut.logger.FormatStrategy
6 | import com.orhanobut.logger.Logger
7 | import com.orhanobut.logger.PrettyFormatStrategy
8 | import pan.lib.common_lib.BuildConfig
9 |
10 |
11 | /**
12 | *
13 | * Author: pan qi
14 | * CreateDate: 2020/6/5 15:10
15 | */
16 |
17 | private const val LOGGER_GENERAL_TAG = "TAG_GENERAL"
18 | private const val LOGGER_NETWORK_TAG = "TAG_NETWORK"
19 | fun initLogger() {
20 | val formatStrategy: FormatStrategy = PrettyFormatStrategy.newBuilder()
21 | .showThreadInfo(true) // (Optional) Whether to show thread info or not. Default true
22 | .methodCount(2) // (Optional) How many method line to show. Default 2
23 | .methodOffset(5) // (Optional) Skips some method invokes in stack trace. Default 5
24 | .tag(LOGGER_GENERAL_TAG)
25 | .build()
26 |
27 | Logger.addLogAdapter(object : AndroidLogAdapter(formatStrategy) {
28 | override fun isLoggable(priority: Int, tag: String?): Boolean {
29 | return BuildConfig.DEBUG
30 | }
31 | })
32 |
33 | }
34 |
35 | fun printNetLog(response: String?) {
36 | printLogWithTag(LOGGER_NETWORK_TAG, response)
37 | }
38 |
39 | fun printLog(any: Any?) {
40 | Logger.d(any)
41 | }
42 |
43 | fun printLogWithTag(tag: String, any: Any?) {
44 | Logger.t(tag).d(any)
45 |
46 | }
47 |
48 | fun printSimpleLog(any: Any?) {
49 | Log.e(LOGGER_GENERAL_TAG, any.toString())
50 | }
51 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/PreferencesUtil.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils
2 |
3 | import android.os.Parcelable
4 | import com.tencent.mmkv.MMKV
5 | import java.util.*
6 |
7 | /**
8 | *
9 | * Author: pan qi
10 | * CreateDate: 2019/9/7 11:10
11 | */
12 |
13 | object PreferencesUtil {
14 |
15 | private val kv: MMKV?
16 | get() {
17 | return MMKV.defaultMMKV()
18 | }
19 |
20 |
21 | fun put(key: String, value: Any?) {
22 | when (value) {
23 | is String -> kv?.encode(key, value)
24 | is Float -> kv?.encode(key, value)
25 | is Boolean -> kv?.encode(key, value)
26 | is Int -> kv?.encode(key, value)
27 | is Long -> kv?.encode(key, value)
28 | is Double -> kv?.encode(key, value)
29 | is ByteArray -> kv?.encode(key, value)
30 | else -> return
31 | }
32 | }
33 |
34 | fun put(key: String, t: T?) {
35 | if (t == null) {
36 | return
37 | }
38 | kv?.encode(key, t)
39 | }
40 |
41 | fun put(key: String, sets: Set?) {
42 | if (sets == null) {
43 | return
44 | }
45 | kv?.encode(key, sets)
46 | }
47 |
48 | fun getParcelable(key: String, tClass: Class): T? {
49 | return kv?.decodeParcelable(key, tClass)
50 | }
51 |
52 | fun getString(key: String, defaultValue: String? = null): String? {
53 | return kv?.decodeString(key, defaultValue)
54 | }
55 |
56 | fun getInt(key: String, defaultValue: Int = 0): Int? {
57 | return kv?.decodeInt(key, 0)
58 | }
59 |
60 | fun getDouble(key: String, defaultValue: Double = 0.0): Double? {
61 |
62 | return kv?.decodeDouble(key, defaultValue)
63 | }
64 |
65 | fun getLong(key: String, defaultValue: Long = 0L): Long? {
66 | return kv?.decodeLong(key, defaultValue)
67 | }
68 |
69 | fun getBoolean(key: String, defaultValue: Boolean = false): Boolean? {
70 | return kv?.decodeBool(key, defaultValue)
71 | }
72 |
73 | fun getFloat(key: String, defaultValue: Float = 0.0f): Float? {
74 | return kv?.decodeFloat(key, defaultValue)
75 | }
76 |
77 | fun getByteArray(key: String): ByteArray? {
78 | return kv?.decodeBytes(key)
79 | }
80 |
81 |
82 | fun getStringSet(key: String): Set? {
83 | return kv?.decodeStringSet(key, Collections.emptySet())
84 | }
85 |
86 | fun clear() {
87 | kv?.clearAll()
88 | }
89 |
90 | fun remove(key: String) {
91 | kv?.removeValueForKey(key)
92 | }
93 | }
94 |
95 |
96 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/UserHelper.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils
2 |
3 | import android.text.TextUtils
4 | import pan.lib.common_lib.data.User
5 | import pan.lib.common_lib.utils.ext.toJson
6 | import pan.lib.common_lib.utils.ext.toObject
7 | import javax.crypto.Cipher
8 |
9 |
10 | /**
11 | *
12 | * Author: pan qi
13 | * CreateDate: 2019/9/21 15:20
14 | */
15 | object UserHelper {
16 | var userInfo: User? = null
17 | get() {
18 | return field ?: initUser()
19 | }
20 |
21 | private const val USER_SP_KEY = "USER_SP_KEY"
22 |
23 |
24 | fun saverUserInfo(user: User): Boolean {
25 | this.userInfo = user
26 |
27 | val userJson = user.toJson()
28 | if (TextUtils.isEmpty(userJson)) return false
29 | val encryptJson = DESUtil.des(userJson, Cipher.ENCRYPT_MODE)
30 | PreferencesUtil.put(USER_SP_KEY, encryptJson)
31 | return true
32 | }
33 |
34 | private fun initUser(): User? {
35 | val encryptUserJson = PreferencesUtil.getString(USER_SP_KEY)
36 | if (TextUtils.isEmpty(encryptUserJson)) return null
37 | val userJson = DESUtil.des(encryptUserJson, Cipher.DECRYPT_MODE)
38 | userInfo = userJson.toObject()
39 | return userInfo
40 | }
41 |
42 | fun deleteUserInfo() {
43 | PreferencesUtil.remove(USER_SP_KEY)
44 | }
45 |
46 | fun getUserIdx(): Int {
47 | return userInfo?.userId ?: 0
48 |
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/WindowUtil.java:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils;
2 |
3 | import android.content.res.Resources;
4 | import android.view.Window;
5 | import android.view.WindowManager;
6 |
7 | /**
8 |
9 | */
10 | public class WindowUtil {
11 |
12 | //设置window 大小
13 | public static void setWindowSize(Window window, int width, int height) {
14 | if (window == null) return;
15 | WindowManager.LayoutParams attributes = window.getAttributes();
16 | attributes.width = width;
17 | attributes.height = height;
18 | window.setAttributes(attributes);
19 | }
20 |
21 | //设置window 重心
22 | public static void setWindowGravity(Window window, int gravity) {
23 | if (window == null) return;
24 | WindowManager.LayoutParams attributes = window.getAttributes();
25 | attributes.gravity = gravity;
26 | window.setAttributes(attributes);
27 | }
28 |
29 | public static float getZForCamera() {
30 | return - 6 * Resources.getSystem().getDisplayMetrics().density;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/ext/DimensionExt.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils.ext
2 |
3 | import android.content.res.Resources
4 | import android.util.TypedValue
5 |
6 | /**
7 | *
8 | * Author: pan qi
9 | * CreateDate: 2020/6/16
10 | */
11 |
12 |
13 | /**
14 | *dp->px
15 | */
16 |
17 | val Number.dp2px: Float
18 | get() = TypedValue.applyDimension(
19 | TypedValue.COMPLEX_UNIT_DIP,
20 | this.toFloat(),
21 | Resources.getSystem().displayMetrics
22 | )
23 |
24 |
25 | val Number.px2dp: Int
26 | get() = (this.toFloat() / Resources.getSystem().displayMetrics.density).toInt()
27 |
28 |
29 | /**
30 | *sp->px
31 | */
32 | val Number.sp: Int
33 | get() = TypedValue.applyDimension(
34 | TypedValue.COMPLEX_UNIT_SP,
35 | this.toFloat(),
36 | Resources.getSystem().displayMetrics
37 | ).toInt()
38 |
39 |
40 | /**
41 | *px->px
42 | */
43 | val Number.px: Int
44 | get() = this.toInt()
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/ext/EditTextExt.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils.ext
2 |
3 | import android.text.Editable
4 | import android.text.TextWatcher
5 | import android.widget.EditText
6 |
7 | /**
8 | *
9 | * Author: pan qi
10 | * CreateDate: 2020/7/9
11 | */
12 |
13 | fun EditText.onTextChanged(block: (String) -> Unit) {
14 |
15 | this.addTextChangedListener(object : TextWatcher {
16 | override fun afterTextChanged(s: Editable?) {
17 | block(s.toString())
18 | }
19 |
20 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
21 |
22 | }
23 |
24 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
25 | }
26 |
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/ext/JsonExt.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils.ext
2 |
3 | import com.github.salomonbrys.kotson.fromJson
4 | import com.google.gson.Gson
5 |
6 | /**
7 | *
8 | * Author: pan qi
9 | * CreateDate: 2020/6/9 14:32
10 | * 扩展时参考 https://github.com/SalomonBrys/Kotson
11 | */
12 | fun Any.toJson(): String = Gson().toJson(this)
13 |
14 |
15 | inline fun String.toObject(): T = Gson().fromJson(this)
16 |
17 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/utils/ext/ViewModelExt.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.utils.ext
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.launch
8 |
9 | /**
10 | *
11 | * Author: pan qi
12 | * CreateDate: 2020/6/29
13 | */
14 |
15 | fun ViewModel.launchOnUI(block: suspend CoroutineScope.() -> Unit) {
16 | viewModelScope.launch {
17 | block()
18 | }
19 | }
20 |
21 | fun ViewModel.launchOnIO(block: suspend CoroutineScope.() -> Unit) {
22 | viewModelScope.launch(Dispatchers.IO) {
23 | block()
24 | }
25 | }
26 |
27 |
28 |
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/views/BottomDecoration.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.views
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Rect
7 | import android.view.View
8 | import androidx.annotation.ColorInt
9 | import androidx.recyclerview.widget.RecyclerView
10 |
11 | class BottomDecoration(
12 | context: Context,
13 | @ColorInt color: Int = 0xff666666.toInt(),
14 | private val width: Float = 1f,
15 | private val startPaddingDp: Float = 0f,
16 | private val endPaddingDp: Float = 0f
17 | ) : RecyclerView.ItemDecoration() {
18 |
19 | private val paint = Paint().apply {
20 | this.color = color
21 | this.strokeWidth = width
22 | }
23 |
24 | private val startPaddingPx = context.resources.displayMetrics.density * startPaddingDp
25 | private val endPaddingPx = context.resources.displayMetrics.density * endPaddingDp
26 |
27 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
28 | super.onDraw(c, parent, state)
29 | val left = parent.paddingLeft + startPaddingPx
30 | val right = parent.width - parent.paddingRight - endPaddingPx
31 |
32 | for (i in 0 until parent.childCount) {
33 | val child = parent.getChildAt(i)
34 | val params = child.layoutParams as RecyclerView.LayoutParams
35 | val top = (child.bottom + params.bottomMargin).toFloat()
36 | val bottom = top + width
37 |
38 | c.drawRect(left, top, right, bottom, paint)
39 | }
40 | }
41 |
42 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
43 | outRect.set(0, 0, 0, width.toInt())
44 | }
45 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/websocket/BaseLifecycle.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.websocket
2 |
3 | import com.tinder.scarlet.Lifecycle
4 | import com.tinder.scarlet.lifecycle.LifecycleRegistry
5 |
6 | /**
7 | * @author pan qi
8 | * @since 2024/7/9
9 | */
10 | class BaseLifecycle(
11 | private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry()
12 | ) : Lifecycle by lifecycleRegistry {
13 |
14 | init {
15 | start() // 初始化时默认为 Started 状态
16 | }
17 |
18 | fun start() {
19 | lifecycleRegistry.onNext(Lifecycle.State.Started)
20 | }
21 | fun stop() {
22 | lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason())
23 | }
24 | }
--------------------------------------------------------------------------------
/common_lib/src/main/java/pan/lib/common_lib/websocket/WSManager.kt:
--------------------------------------------------------------------------------
1 | package pan.lib.common_lib.websocket
2 |
3 | import com.tinder.scarlet.Lifecycle
4 | import com.tinder.scarlet.Scarlet
5 | import com.tinder.scarlet.lifecycle.android.AndroidLifecycle
6 | import com.tinder.scarlet.messageadapter.gson.GsonMessageAdapter
7 | import com.tinder.scarlet.retry.BackoffStrategy
8 | import com.tinder.scarlet.retry.ExponentialWithJitterBackoffStrategy
9 | import com.tinder.scarlet.streamadapter.rxjava2.RxJava2StreamAdapterFactory
10 | import com.tinder.scarlet.websocket.okhttp.newWebSocketFactory
11 | import okhttp3.OkHttpClient
12 | import okhttp3.logging.HttpLoggingInterceptor
13 | import pan.lib.common_lib.base.BaseApplication
14 |
15 | class WSManager {
16 |
17 | private lateinit var scarletInstance: Scarlet
18 | lateinit var service: Service
19 |
20 | /**
21 | * 初始化 WebSocket Manager.
22 | *
23 | * @param url WebSocket 服务器的 URL.
24 | * @param serviceClass 服务接口的类型.
25 | * @param lifecycle 可选的生命周期管理器, 默认使用 `BaseLifecycle` 实现,该实现允许 WebSocket 连接在应用进入后台时保持活跃,
26 | * @param backoffStrategy 重试策略,默认使用ExponentialWithJitterBackoffStrategy:有随机抖动的指数退避策略(Exponential With Jitter Backoff Strategy).
27 | * 此策略在遇到连接失败时,首先根据重试次数以指数级增加重试间隔,然后在此基础上添加随机抖动,
28 | * 以进一步分散重试请求的时间点,避免大量请求同时重试造成的服务器压力,提高重连成功率。
29 | */
30 | fun init(
31 | url: String,
32 | serviceClass: Class,
33 | backoffStrategy: BackoffStrategy = DEFAULT_RETRY_STRATEGY,
34 | lifecycle: Lifecycle = DEFAULT_LIFECYCLE
35 | ) {
36 |
37 | val okHttpClient = OkHttpClient.Builder()
38 | .addInterceptor(HttpLoggingInterceptor().apply {
39 | level = HttpLoggingInterceptor.Level.BODY
40 | })
41 | .build()
42 |
43 | scarletInstance = Scarlet.Builder()
44 | .webSocketFactory(okHttpClient.newWebSocketFactory(url))
45 | .addMessageAdapterFactory(GsonMessageAdapter.Factory())
46 | .addStreamAdapterFactory(RxJava2StreamAdapterFactory())
47 | .lifecycle(lifecycle)
48 | .backoffStrategy(backoffStrategy)
49 | .build()
50 |
51 | service = scarletInstance.create(serviceClass)
52 | }
53 |
54 | private companion object {
55 | val DEFAULT_LIFECYCLE by lazy { BaseLifecycle() }
56 | val APPLICATION_LIFECYCLE by lazy { AndroidLifecycle.ofApplicationForeground(BaseApplication.instance) } //默认使用应用前景生命周期,用于同步 WebSocket 连接状态与应用的生命周期.
57 | const val RETRY_BASE_DURATION = 1000L //起始间隔时间
58 | const val RETRY_MAX_DURATION = 10000L //最大间隔时间
59 | val DEFAULT_RETRY_STRATEGY =
60 | ExponentialWithJitterBackoffStrategy(RETRY_BASE_DURATION, RETRY_MAX_DURATION)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/common_lib/src/main/res/anim/popup_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
--------------------------------------------------------------------------------
/common_lib/src/main/res/anim/popup_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
--------------------------------------------------------------------------------
/common_lib/src/main/res/layout/activity_base.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
27 |
28 |
29 |
30 |
35 |
36 |
--------------------------------------------------------------------------------
/common_lib/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 | @android:color/background_light
7 |
8 |
--------------------------------------------------------------------------------
/common_lib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/common_lib/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # 该MODULE用于mock和example
2 |
--------------------------------------------------------------------------------
/demo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlinAndroid)
4 | alias(libs.plugins.kotlinKsp)
5 | alias(libs.plugins.daggerHiltAndroidPlugin)
6 | }
7 |
8 | android {
9 | namespace = "com.example.demo"
10 | compileSdk = libs.versions.compileSdkVersion.get().toInt()
11 |
12 |
13 | defaultConfig {
14 | minSdk = libs.versions.minSdk.get().toInt()
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildFeatures {
19 | viewBinding = true
20 | }
21 |
22 | compileOptions {
23 | sourceCompatibility = JavaVersion.VERSION_17
24 | targetCompatibility = JavaVersion.VERSION_17
25 | }
26 |
27 | kotlinOptions {
28 | jvmTarget = "17"
29 | }
30 | }
31 |
32 |
33 | dependencies {
34 | implementation(
35 | fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))
36 | )
37 | implementation(libs.junit)
38 | implementation(project(":common_lib"))
39 | // implementation(libs.baseAndroidFramework)
40 | implementation(libs.hilt.android)
41 | ksp(libs.hilt.compiler)
42 | ksp (libs.therouter.apt)
43 | }
--------------------------------------------------------------------------------
/demo/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | -keep public class com.example.demo.pojo.** { *; }
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/di/WanNetModule.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.di
2 |
3 | import com.example.demo.service.WanApiService
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import pan.lib.common_lib.retrofit.RetrofitManager
9 | import javax.inject.Singleton
10 |
11 | /**
12 | * author: Pan
13 | * date: 2020/7/9
14 | */
15 | @InstallIn(SingletonComponent::class)
16 | @Module
17 | object WanNetModule {
18 | @Provides
19 | @Singleton
20 | fun provideService(): WanApiService = RetrofitManager.getApiService(
21 | WanApiService::class.java)
22 |
23 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/mockwebserver/MockWebSocket.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.mockwebserver
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 | import okhttp3.Response
6 | import okhttp3.WebSocket
7 | import okhttp3.WebSocketListener
8 | import okhttp3.mockwebserver.MockResponse
9 | import okhttp3.mockwebserver.MockWebServer
10 | import pan.lib.common_lib.utils.ext.toJson
11 | import pan.lib.common_lib.utils.printSimpleLog
12 | import com.example.demo.pojo.Result
13 |
14 | class MockWebSocket {
15 |
16 | val mockWebServer by lazy { MockWebServer() }
17 |
18 | suspend fun startMockServer() = withContext(Dispatchers.IO) {
19 | // Create a MockResponse to simulate the WebSocket server response
20 | val mockResponse = MockResponse().withWebSocketUpgrade(object : WebSocketListener() {
21 | override fun onOpen(webSocket: WebSocket, response: Response) {
22 | webSocket.send("Hello, I'm WebSocket Server!")
23 | }
24 |
25 | override fun onMessage(webSocket: WebSocket, text: String) {
26 | // Handle received message
27 | printSimpleLog("Server Received message From Client: $text")
28 | val jsonResponse = Result("I''s Server, I''m received: $text")
29 | .toJson()
30 | webSocket.send(jsonResponse)
31 | }
32 |
33 | override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
34 | // Handle WebSocket closed
35 | printSimpleLog("WebSocket closed: $code - $reason")
36 | }
37 |
38 | override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
39 | // Handle WebSocket failure
40 | printSimpleLog("WebSocket failure: ${t.message}")
41 | }
42 | })
43 | mockWebServer.enqueue(mockResponse)
44 | mockWebServer.start()
45 | }
46 |
47 |
48 | suspend fun stopMockServer() =withContext(Dispatchers.IO) {
49 | mockWebServer.shutdown()
50 |
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/pojo/Article.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.pojo
2 |
3 | /**
4 | * author: Pan
5 | * date: 2020/7/12
6 | */
7 | data class Article(
8 | val adminAdd: Boolean,
9 | val apkLink: String,
10 | val audit: Int,
11 | val author: String,
12 | val canEdit: Boolean,
13 | val chapterId: Int,
14 | val chapterName: String,
15 | val collect: Boolean,
16 | val courseId: Int,
17 | val desc: String,
18 | val descMd: String,
19 | val envelopePic: String,
20 | val fresh: Boolean,
21 | val host: String,
22 | val id: Int,
23 | val isAdminAdd: Boolean,
24 | val link: String,
25 | val niceDate: String,
26 | val niceShareDate: String,
27 | val origin: String,
28 | val prefix: String,
29 | val projectLink: String,
30 | val publishTime: Long,
31 | val realSuperChapterId: Int,
32 | val selfVisible: Int,
33 | val shareDate: Long,
34 | val shareUser: String,
35 | val superChapterId: Int,
36 | val superChapterName: String,
37 | val tags: List,
38 | val title: String,
39 | val type: Int,
40 | val userId: Int,
41 | val visible: Int,
42 | val zan: Int
43 | ) {
44 | data class Tag(
45 | val name: String,
46 | val url: String
47 | )
48 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/pojo/ArticlesData.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.pojo
2 |
3 | /**
4 | * @author pan qi
5 | * @since 2024/7/8
6 | */
7 | data class ArticlesData(
8 | val curPage: Int,
9 | val datas: List,
10 | val offset: Int,
11 | val over: Boolean,
12 | val pageCount: Int,
13 | val size: Int,
14 | val total: Int
15 | )
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/pojo/Data.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.pojo
2 |
3 | data class Data(
4 | val `data`: Data,
5 | val errorCode: Int,
6 | val errorMsg: String
7 | ) {
8 | data class Data(
9 | val curPage: Int,
10 | val datas: List,
11 | val offset: Int,
12 | val over: Boolean,
13 | val pageCount: Int,
14 | val size: Int,
15 | val total: Int
16 | ) {
17 | data class Data(
18 | val adminAdd: Boolean,
19 | val apkLink: String,
20 | val audit: Int,
21 | val author: String,
22 | val canEdit: Boolean,
23 | val chapterId: Int,
24 | val chapterName: String,
25 | val collect: Boolean,
26 | val courseId: Int,
27 | val desc: String,
28 | val descMd: String,
29 | val envelopePic: String,
30 | val fresh: Boolean,
31 | val host: String,
32 | val id: Int,
33 | val isAdminAdd: Boolean,
34 | val link: String,
35 | val niceDate: String,
36 | val niceShareDate: String,
37 | val origin: String,
38 | val prefix: String,
39 | val projectLink: String,
40 | val publishTime: Long,
41 | val realSuperChapterId: Int,
42 | val selfVisible: Int,
43 | val shareDate: Long,
44 | val shareUser: String,
45 | val superChapterId: Int,
46 | val superChapterName: String,
47 | val tags: List,
48 | val title: String,
49 | val type: Int,
50 | val userId: Int,
51 | val visible: Int,
52 | val zan: Int
53 | ) {
54 | data class Tag(
55 | val name: String,
56 | val url: String
57 | )
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/pojo/Protocol.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.pojo
2 |
3 |
4 | //scarlet会把对象序列化成json,发送到服务器
5 | data class Protocol(val msg: String)
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/pojo/Result.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.pojo;
2 |
3 | public class Result{
4 | public String message;
5 |
6 | public Result(String message) {
7 | this.message = message;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/pojo/WanResponse.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.pojo
2 |
3 | import pan.lib.common_lib.retrofit.Response
4 |
5 | /**
6 | * author: Pan
7 | * date: 2020/7/12
8 | */
9 | data class WanResponse(val errorCode: Int, val errorMsg: String, val data: T) : Response {
10 |
11 | override fun getCode() = errorCode
12 |
13 |
14 | override fun getMessage() = errorMsg
15 |
16 |
17 | override fun getResponse() = data
18 |
19 |
20 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/repository/ArticleRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.repository
2 |
3 | import com.example.demo.service.WanApiService
4 | import com.example.demo.pojo.ArticlesData
5 | import pan.lib.common_lib.base.BaseRepository
6 | import pan.lib.common_lib.retrofit.NetResult
7 | import javax.inject.Inject
8 | import javax.inject.Singleton
9 |
10 | /**
11 | *
12 | * Author: pan qi
13 | * CreateDate: 2020/6/30
14 | */
15 |
16 | @Singleton
17 | class ArticleRepository @Inject constructor(private val wanApiService: WanApiService) :
18 | BaseRepository() {
19 | suspend fun fetchTopArticle(): NetResult {
20 | return fetchApi { wanApiService.articles(0) }
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/service/DemoWebsocketService.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.service
2 |
3 | import androidx.annotation.Keep
4 | import com.tinder.scarlet.ws.Receive
5 | import com.tinder.scarlet.ws.Send
6 | import io.reactivex.Flowable
7 | import com.example.demo.pojo.Protocol
8 | import com.example.demo.pojo.Result
9 | import com.tinder.scarlet.WebSocket
10 |
11 | /**
12 | * @author pan qi
13 | * @since 2024/7/9
14 | */
15 | @Keep
16 | interface DemoWebsocketService {
17 | @Send
18 | fun send(protocol: Protocol)
19 |
20 | @Receive
21 | fun observeCustomInfo(): Flowable
22 |
23 | @Receive
24 | fun observeWebSocketEvent(): Flowable
25 |
26 | @Send
27 | fun send(msg: String)
28 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/service/WanApiService.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.service
2 |
3 | import com.example.demo.pojo.ArticlesData
4 | import com.example.demo.pojo.WanResponse
5 | import retrofit2.http.GET
6 | import retrofit2.http.Path
7 |
8 | /**
9 | *
10 | * Author: pan qi
11 | * CreateDate: 2020/6/3 18:12
12 | */
13 | interface WanApiService {
14 |
15 | /**
16 | * 文章列表
17 | */
18 | @GET("article/list/{page}/json")
19 | suspend fun articles(
20 | @Path("page") page: Int
21 | ): WanResponse
22 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/ui/top/TopArticleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.ui.top
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.activity.viewModels
7 | import com.example.demo.R
8 | import com.example.demo.databinding.ActivityTopArticleBinding
9 | import com.therouter.router.Route
10 | import dagger.hilt.android.AndroidEntryPoint
11 | import pan.lib.common_lib.base.BaseActivity
12 |
13 | @Route(path = "/demo/TopArticleActivity")
14 | @AndroidEntryPoint
15 | class TopArticleActivity : BaseActivity() {
16 | private val topArticleViewModel: TopArticleViewModel by viewModels()
17 | private lateinit var binding: ActivityTopArticleBinding
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setTitle(R.string.article)
21 | enableBack()
22 | initView()
23 |
24 | topArticleViewModel.fetchTopArticle()
25 |
26 |
27 | }
28 |
29 | override fun getLayout(layoutInflater: LayoutInflater): View {
30 | binding = ActivityTopArticleBinding.inflate(layoutInflater)
31 | return binding.root
32 | }
33 |
34 | private fun initView() {
35 | val topArticleAdapter = TopArticleAdapter()
36 | binding.recyclerView.adapter = topArticleAdapter
37 |
38 | topArticleViewModel.articleList.observe(this) {
39 | topArticleAdapter.setNewInstance(it)
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/ui/top/TopArticleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.ui.top
2 |
3 | import com.chad.library.adapter.base.BaseQuickAdapter
4 | import com.chad.library.adapter.base.viewholder.BaseViewHolder
5 | import com.example.demo.pojo.Article
6 | import com.example.demo.R
7 |
8 | /**
9 | * author: Pan
10 | * date: 2020/7/12
11 | */
12 | class TopArticleAdapter : BaseQuickAdapter(R.layout.item_top_article) {
13 |
14 | override fun convert(holder: BaseViewHolder, item: Article) {
15 | holder.setText(R.id.item_title, item.title)
16 | holder.setText(R.id.item_author, item.author)
17 | holder.setText(R.id.item_date, item.niceDate)
18 | holder.setText(R.id.item_link, item.link)
19 | }
20 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/ui/top/TopArticleViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.ui.top
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.lifecycle.ViewModel
5 | import com.example.demo.pojo.Article
6 | import com.example.demo.repository.ArticleRepository
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import pan.lib.common_lib.utils.ext.launchOnUI
9 | import javax.inject.Inject
10 |
11 | /**
12 | *
13 | * Author: pan qi
14 | * CreateDate: 2020/6/4 15:45
15 | */
16 | @HiltViewModel
17 | class TopArticleViewModel @Inject constructor(private val articleRepository: ArticleRepository) :
18 | ViewModel() {
19 |
20 |
21 | val articleList = MutableLiveData>()
22 |
23 | fun fetchTopArticle() {
24 | launchOnUI {
25 | val response = articleRepository.fetchTopArticle()
26 |
27 | response.whenSuccess {
28 | articleList.value = it.datas.toMutableList()
29 | }
30 |
31 | }
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/ui/websocket_demo/WebsocketDemoActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.ui.websocket_demo
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.activity.viewModels
7 | import androidx.lifecycle.lifecycleScope
8 | import com.example.demo.R
9 | import com.example.demo.databinding.ActivityWebsocketDemoBinding
10 | import com.therouter.router.Route
11 | import kotlinx.coroutines.launch
12 | import pan.lib.common_lib.base.BaseActivity
13 |
14 | @Route(path = "/demo/WebsocketDemoActivity")
15 | class WebsocketDemoActivity : BaseActivity() {
16 | private val binding: ActivityWebsocketDemoBinding by lazy {
17 | ActivityWebsocketDemoBinding.inflate(layoutInflater)
18 | }
19 | private val websocketDemoViewModel: WebsocketDemoViewModel by viewModels()
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setTitle(R.string.websocket_demo)
24 | enableBack()
25 | lifecycleScope.launch {
26 | websocketDemoViewModel.startWebSocketServer()//启动模拟WebServer
27 | websocketDemoViewModel.connectWebSocket()//连接WebSocket
28 | }
29 |
30 | binding.buttonSend.setOnClickListener {
31 | val message = binding.editTextMessage.text.toString()
32 | if (message.isNotEmpty()) {
33 | websocketDemoViewModel.sendMessageToWebSocket(message)
34 | binding.editTextMessage.text.clear()
35 | }
36 | }
37 |
38 | websocketDemoViewModel.receivedMessages.observe(this) { message ->
39 | binding.textViewClientMessages.append("$message\n")
40 | }
41 |
42 |
43 | }
44 |
45 | override fun getLayout(layoutInflater: LayoutInflater): View {
46 | return binding.root
47 | }
48 |
49 | override fun onDestroy() {
50 | websocketDemoViewModel.stopWebSocketServer()
51 | super.onDestroy()
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/demo/ui/websocket_demo/WebsocketDemoViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.ui.websocket_demo
2 |
3 | import android.annotation.SuppressLint
4 | import android.util.Log
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.example.demo.service.DemoWebsocketService
9 | import com.example.demo.mockwebserver.MockWebSocket
10 | import com.tinder.scarlet.Message
11 | import com.tinder.scarlet.WebSocket
12 | import kotlinx.coroutines.Dispatchers
13 | import kotlinx.coroutines.launch
14 | import kotlinx.coroutines.withContext
15 | import pan.lib.common_lib.websocket.WSManager
16 |
17 | class WebsocketDemoViewModel : ViewModel() {
18 | private val websocketTag = "WebSocketTestServer"
19 | private val mockWebSocket = MockWebSocket()
20 |
21 | val receivedMessages = MutableLiveData()
22 |
23 | private val wsManager: WSManager = WSManager()
24 | suspend fun startWebSocketServer() {
25 | mockWebSocket.startMockServer()
26 | }
27 |
28 | @SuppressLint("CheckResult")
29 | suspend fun connectWebSocket() {
30 | val websocketUrl=withContext(Dispatchers.IO) {
31 | val mockWebServer = mockWebSocket.mockWebServer
32 | "ws://${mockWebServer.hostName}:${mockWebServer.port}/"
33 | }
34 | wsManager.init(websocketUrl, DemoWebsocketService::class.java)
35 |
36 |
37 | // 监听 WebSocket 消息
38 | wsManager.service.observeCustomInfo().subscribe {
39 | receivedMessages.postValue(it.message)
40 | }
41 |
42 | wsEventListen()
43 |
44 | }
45 |
46 | @SuppressLint("CheckResult")
47 | private fun wsEventListen() {
48 | wsManager.service.observeWebSocketEvent()
49 | .subscribe({
50 | when (it) {
51 | is WebSocket.Event.OnConnectionOpened<*> -> {
52 | Log.e(websocketTag, "WebSocket connection is opened: $it")
53 | }
54 |
55 | is WebSocket.Event.OnMessageReceived -> {
56 | if (it.message is Message.Text) {
57 | val textMessage = it.message as Message.Text
58 | Log.e(
59 | websocketTag,
60 | "WebSocket text message received: ${textMessage.value}"
61 | )
62 | } else if (it.message is Message.Bytes) {
63 | val bytesMessage = it.message as Message.Bytes
64 | // 如果需要,您可以将字节数组转换为字符串
65 | val text = bytesMessage.value.toString()
66 | Log.e(
67 | websocketTag,
68 | "WebSocket bytes message received: $text"
69 | )
70 | }
71 | }
72 |
73 | is WebSocket.Event.OnConnectionClosing -> {
74 | Log.e(websocketTag, "WebSocket connection is closing: $it")
75 | }
76 |
77 | is WebSocket.Event.OnConnectionClosed -> {
78 | Log.e(websocketTag, "WebSocket connection is closed: $it")
79 | }
80 |
81 | is WebSocket.Event.OnConnectionFailed -> {
82 | Log.e(websocketTag, "WebSocket connection failed: $it")
83 | }
84 | }
85 | }, {
86 | println("WebSocket connection failed: $it")
87 | })
88 | }
89 |
90 | fun sendMessageToWebSocket(message: String) {
91 | wsManager.service.send(message)
92 | }
93 |
94 | fun stopWebSocketServer() {
95 | viewModelScope.launch {
96 | mockWebSocket.stopMockServer()
97 | }
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_top_article.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_websocket_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
28 |
29 |
37 |
38 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item_top_article.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
17 |
25 |
26 |
27 |
32 |
33 |
34 |
41 |
42 |
43 |
46 |
47 |
48 |
55 |
56 |
57 |
58 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 文章
3 | Websocket Demo
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | android.nonTransitiveRClass=false
23 | android.nonFinalResIds=false
24 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.6.1"
3 | junit = "4.13.2"
4 | kotlin = "2.0.0"
5 | ksp = "2.0.0-1.0.22"
6 | hilt = "2.51.1"
7 | coreKtx = "1.13.1"
8 | appcompat = "1.7.0"
9 | material = "1.12.0"
10 | constraintlayout = "2.1.4"
11 | minSdk = "24"
12 | targetSdk = "33"
13 | compileSdkVersion = "34"
14 |
15 | glide = "4.16.0"
16 | glideTransformations = "4.3.0"
17 | lifecycleExtensions = "2.2.0"
18 | lifecycleViewmodelKtx = "2.8.6"
19 | fragmentKtx = "1.8.3"
20 | coroutinesCore = "1.7.3"
21 | multidex = "2.0.1"
22 | preferenceKtx = "1.2.1"
23 | gson = "2.10.1"
24 | kotson = "2.5.0"
25 | eventbus = "3.2.0"
26 | baseRecyclerViewAdapterHelper = "3.0.7"
27 | retrofit = "2.11.0"
28 | retrofitGsonConverter = "2.11.0"
29 | loggingInterceptor = "4.8.1"
30 | logger = "2.2.0"
31 | immersionbar = "3.2.2"
32 | mmkvStatic = "1.3.9"
33 | okhttp = "4.12.0"
34 | scarlet = "0.1.12"
35 | therouter = "1.2.2"
36 | baseAndroidFramework = "1.1.0"
37 |
38 | [libraries]
39 | androidx-annotation = { group = "androidx.annotation", name = "annotation", version = "1.8.0" }
40 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
41 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
42 | junit = { module = "junit:junit", version.ref = "junit" }
43 | therouter-apt = { module = "cn.therouter:apt", version.ref = "therouter" }
44 | glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
45 | glide-ksp = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" }
46 | glide-transformations = { module = "jp.wasabeef:glide-transformations", version.ref = "glideTransformations" }
47 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
48 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
49 | androidx-lifecycle-extensions = { group = "androidx.lifecycle", name = "lifecycle-extensions", version.ref = "lifecycleExtensions" }
50 | androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
51 | androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
52 | org-jetbrains-kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutinesCore" }
53 | androidx-multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" }
54 | androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" }
55 | com-google-gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
56 | com-github-salomonbrys-kotson = { group = "com.github.salomonbrys.kotson", name = "kotson", version.ref = "kotson" }
57 | org-greenrobot-eventbus = { group = "org.greenrobot", name = "eventbus", version.ref = "eventbus" }
58 | baseRecyclerViewAdapterHelper = { group = "com.github.CymChad", name = "BaseRecyclerViewAdapterHelper", version.ref = "baseRecyclerViewAdapterHelper" }
59 | com-squareup-retrofit2-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
60 | com-squareup-retrofit2-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofitGsonConverter" }
61 | com-squareup-okhttp3-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "loggingInterceptor" }
62 | com-orhanobut-logger = { group = "com.orhanobut", name = "logger", version.ref = "logger" }
63 | com-gyf-immersionbar = { group = "com.geyifeng.immersionbar", name = "immersionbar", version.ref = "immersionbar" }
64 | com-tencent-mmkv-static = { group = "com.tencent", name = "mmkv-static", version.ref = "mmkvStatic" }
65 | com-squareup-okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
66 | com-squareup-okhttp3-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" }
67 | com-tinder-scarlet = { group = "com.tinder.scarlet", name = "scarlet", version.ref = "scarlet" }
68 | com-tinder-scarlet-websocket-okhttp = { group = "com.tinder.scarlet", name = "websocket-okhttp", version.ref = "scarlet" }
69 | com-tinder-scarlet-message-adapter-gson = { group = "com.tinder.scarlet", name = "message-adapter-gson", version.ref = "scarlet" }
70 | com-tinder-scarlet-stream-adapter-rxjava2 = { group = "com.tinder.scarlet", name = "stream-adapter-rxjava2", version.ref = "scarlet" }
71 | com-tinder-scarlet-lifecycle-android = { group = "com.tinder.scarlet", name = "lifecycle-android", version.ref = "scarlet" }
72 | hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
73 | hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
74 | baseAndroidFramework = { group = "com.github.PanPersonalProject", name = "BaseAndroidFramework", version.ref = "baseAndroidFramework" }
75 | router = { module = "cn.therouter:router", version.ref = "therouter" }
76 |
77 | [plugins]
78 | androidApplication = { id = "com.android.application", version.ref = "agp" }
79 | kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
80 | androidLibrary = { id = "com.android.library", version.ref = "agp" }
81 | kotlinKsp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
82 | daggerHiltAndroidPlugin = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
83 | theRouter = { id = "cn.therouter.agp8", version.ref = "therouter" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PanPersonalProject/BaseAndroidFramework/938d17f4553576b827c844dacf6d7f9cbee88a07/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Dec 10 14:52:12 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk17
3 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | maven(url = "https://jitpack.io")
20 |
21 | }
22 | }
23 |
24 | rootProject.name = "BaseAndroidFramework"
25 | include(":app")
26 | include(":common_lib")
27 | include(":demo")
28 |
29 |
--------------------------------------------------------------------------------