├── .gitignore
├── .idea
└── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── espoir
│ │ └── shattermanager
│ │ ├── App.kt
│ │ └── MainActivity.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_background.xml
│ └── ic_launcher_foreground.xml
│ ├── layout
│ ├── activity_main.xml
│ └── layout_shatter_a.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── shatter
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
└── java
└── com
└── espoir
└── shatter
├── GlobalShatterActivity.kt
├── IShatterActivity.kt
├── Shatter.kt
├── ShatterActivityWatcher.kt
├── ShatterCache.kt
├── ShatterEvent.kt
├── ShatterLifecycleListener.kt
└── ShatterManager.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shatter
2 | 复杂业务碎片化管理框架
3 |
4 | Shatter 的思想是将复杂的业务和布局拆分成一个个的碎片,每个碎片又可以继续拆出多个子碎片。让复杂的业务变成
5 | 一个个相对独立简单的功能,达到解耦的效果(拆分粒度由开发者自己把握)。
6 | 每个碎片的使用方式跟你使用 Activity 一样,简单方便。
7 |
8 | Shatter 的意义在于:
9 | 1. 将一个复杂业务拆成多个简单业务,解耦,使你的代码易于维护。
10 | 2. 在多人协作的项目中,避免不同人员开发同一个业务模块时代码冲突的发生
11 | 3. 代码复用率可大大提高
12 |
13 | 使用方式:
14 |
15 | [](https://jitpack.io/#EspoirX/ShatterManager)
16 | ```groovy
17 | dependencies {
18 | implementation 'com.github.EspoirX:ShatterManager:vTAG'
19 | }
20 | ```
21 |
22 | ## 基本概念
23 | 如何让复杂的业务进行解耦,除了各种设计模式外,有一点就行将复杂的业务拆散,因为再复杂的功能也是由一个个简单的
24 | 功能组装起来的。
25 | 每个简单功能,我称之为 “碎片” ,当你的粒度合理时,每个碎片之负责一到几个功能,最后再将这些 “碎片”
26 | 组合起来,就能达到解耦的效果了。
27 | 而当两个页面之间用到相同或相似的功能模块时,就可以直接复用,都不用重新写。
28 |
29 | 本框架中,每个碎片叫做 **Shatter** 它们统一由 **ShatterManager** 去管理。
30 |
31 |
32 | ## 使用
33 | 首先在 Application 中初始化一下:
34 | ```kotlin
35 | class App : Application() {
36 | override fun onCreate() {
37 | super.onCreate()
38 | ShatterManager.init(this)
39 | }
40 | }
41 | ```
42 |
43 | 在你的 Activity 中,实现 IShatterActivity 接口:
44 | ```kotlin
45 | class MainActivity : AppCompatActivity(), IShatterActivity {
46 | private val shatterManager = ShatterManager(this)
47 |
48 | override fun getShatterManager(): ShatterManager = shatterManager
49 | }
50 | ```
51 |
52 | 在 Shatter 内部是通过 ActivityLifecycleCallbacks 监听生命周期的,有些生命周期回调在这个 callback
53 | 里面没有,如果你想用的话,需要手动调用一下,比如:
54 | ```kotlin
55 | override fun onNewIntent(intent: Intent?) {
56 | super.onNewIntent(intent)
57 | getShatterManager().onNewIntent(intent)
58 | }
59 |
60 | override fun onRestart() {
61 | super.onRestart()
62 | getShatterManager().onRestart()
63 | }
64 |
65 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
66 | super.onActivityResult(requestCode, resultCode, data)
67 | getShatterManager().onActivityResult(requestCode, resultCode, data)
68 | }
69 |
70 | override fun onBackPressed() {
71 | if (getShatterManager().enableOnBackPressed()) {
72 | super.onBackPressed()
73 | }
74 | }
75 | ```
76 |
77 | 然后就可以编写你的 Shatter 了。 Shatter 写完后通过 ShatterManager 去添加管理
78 | ```kotlin
79 | override fun onCreate(savedInstanceState: Bundle?) {
80 | super.onCreate(savedInstanceState)
81 | setContentView(R.layout.activity_main)
82 |
83 | getShatterManager()
84 | .addShatter(R.id.shatterALayout, ShatterA())
85 | .addShatter(ShatterAChild())
86 | .addShatter(ShatterB())
87 | .addShatter(ShatterC())
88 | .start()
89 | }
90 | ```
91 |
92 | ## Shatter用法
93 |
94 | Shatter 拥有和 Activity 相同的生命周期,你可以重写它们:onCreate() ,onStart() ... 等等。
95 | 在 onCreate() 里面,还分出了两个方法:initView(),initData() 。合理的重写对应方法去完成逻辑。
96 |
97 | ### 1. 当 Fragment 来用
98 | Shatter 不止可以拆分代码逻辑,也可以拆分布局(和Fragment有点像)。
99 | 通过重写 **getLayoutResId()** 方法传入布局。然后在 addShatter 的时候将 Shatter 添加到对应放这个布局的 Layout 中即可:
100 | ```kotlin
101 | getShatterManager()
102 | .addShatter(R.id.shatterALayout, ShatterA())
103 | .start()
104 |
105 | class ShatterA : Shatter() {
106 | override fun getLayoutResId(): Int = R.layout.layout_shatter_a
107 |
108 | override fun initView(view: View?, intent: Intent?) {
109 | //......
110 | }
111 | }
112 | ```
113 |
114 | 在 initView() 中,你可以通过 view.findViewById 去得到相关的控件,同时如果你的项目支持 ViewBinding,也可以直接
115 | 通过 getBinding 方法获取到对应的 binding 去操作控件。
116 |
117 | ### 2. 只当功能组件使用
118 | 如果我的 Shatter 只是有单纯的代码逻辑,不需要布局,比如我就请求个接口等,那么就不需要重写 getLayoutResId() 方法。
119 | addShatter 的时候直接添加即可:
120 | ```kotlin
121 | getShatterManager()
122 | .addShatter(ShatterA())
123 | .start()
124 |
125 | class ShatterA : Shatter() {
126 |
127 | fun requestUserInfo(){
128 | //......
129 | }
130 | }
131 | ```
132 |
133 | ### 3. Shatter 间的通讯
134 | 在 ShatterA 中如何获取到 ShatterB 的实例,然后调用 ShatterB 里面的方法?
135 | 可以通过 **findShatter(ShatterB::class.java)** 去获取。
136 |
137 | 同样的,ShatterA 中如果要使用 ShatterB 中的布局控件也是可以通过先获取到实例,然后再获取 ViewBinding 得到:
138 |
139 | val binding = findShatter(ShatterB::class.java).binding
140 |
141 | **事件通讯:**
142 | 除了主动获取对象,Shatter 还支持相互间的事件通讯(类似EventBus)。
143 | 可以通过 **fun sendShatterEvent(key: String, data: Any? = null)** 方法发送事件。
144 | 有两个参数, key 主要用于区分事件,data 就是事件带的数据,可为 null
145 |
146 | 接收事件需要重写 **fun onShatterEvent(key: String, data: Any?)** 方法。可以看到参数
147 | 是和发送方法对齐的。
148 |
149 | ### 4. 参数存储功能
150 | 有时候需要存储一些临时参数或者页面参数等,比如 intent 的数据,为了方便,可以使用 saveData 方法去存储
151 | 通过 getSaveData 方法去获取,这两个方法由 ShatterManager 管理,所以可以全局使用。
152 |
153 | ### 5. 代码复用和 findShatter 支持接口
154 | 有时候我们可能遇到这种情况,两个 Shatter 之间大部分代码逻辑都相同,只是其中一小部分不一样。
155 | 比如我们写了个评论的 Shatter,有两个页面都有评论,它们的逻辑都一样,区别只是调用的接口参数不一样等。
156 | 这时候 Shatter 应该是可以直接复用的,只需要将不一样的抽出来即可。
157 |
158 | 那么抽出来自然想到是接口。
159 |
160 | 下面举个例子看这种情况怎么做(比如点击按钮,toast不一样):
161 | 首先编写接口抽出功能:
162 | ```kotlin
163 | interface IShowToast{
164 | fun show()
165 | }
166 | ```
167 |
168 | 然后我们各自实现它,同时实现类本身也是一个 Shatter:
169 | ```kotlin
170 | /** 页面 A 的 Show Toast*/
171 | class ShowToastShatterAImpl : Shatter() , IShowToast{
172 | fun show(){
173 | Toast.show("fuck you")
174 | }
175 | }
176 |
177 | /** 页面 B 的 Show Toast */
178 | class ShowToastShatterBImpl : Shatter() , IShowToast{
179 | fun show(){
180 | Toast.show("fuck you too")
181 | }
182 | }
183 | ```
184 |
185 | 然我我们的业务模块这样写:
186 | ```kotlin
187 | class BtnShatter : Shatter {
188 | val showToast get() = findShatter(IShowToast::class.java)
189 |
190 | //......
191 | showToast?.show()
192 | }
193 | ```
194 | 在 findShatter 的时候只需要传入对应的接口,内部会自动找到对应的实现类,这样 BtnShatter 我们就可以完全复用,
195 | 不相同的地方只需要关注对应的实现类即可。
196 |
197 | 添加的时候这样添加:
198 |
199 | ```kotlin
200 | /** 页面 A 的 碎片管理*/
201 | shatterManager
202 | .addShatter(BtnShatter())
203 | .addShatter(ShowToastShatterAImpl())
204 | .start()
205 |
206 | /** 页面 B 的 碎片管理*/
207 | shatterManager
208 | .addShatter(BtnShatter())
209 | .addShatter(ShowToastShatterBImpl())
210 | .start()
211 | ```
212 |
213 | 更多详情请看 Demo 或者源码,或者通过文章 [《Android 复杂业务碎片化管理方案》](https://juejin.cn/post/7336750909508206604) 了解更多背景。
214 |
215 | 有问题可随时交流。
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 31
6 |
7 | defaultConfig {
8 | applicationId = "com.espoir.shattermanager"
9 | minSdkVersion 16
10 | targetSdkVersion 31
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 |
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 |
22 | }
23 |
24 | dependencies {
25 | implementation("androidx.appcompat:appcompat:1.2.0")
26 | implementation("androidx.core:core-ktx:1.5.0")
27 | implementation project(':shatter')
28 | }
29 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/espoir/shattermanager/App.kt:
--------------------------------------------------------------------------------
1 | package com.espoir.shattermanager
2 |
3 | import android.app.Application
4 | import com.espoir.shatter.ShatterManager
5 |
6 | class App : Application() {
7 | override fun onCreate() {
8 | super.onCreate()
9 | ShatterManager.init(this)
10 | }
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/espoir/shattermanager/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.espoir.shattermanager
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.View
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.appcompat.app.AppCompatActivity
11 | import com.espoir.shatter.IShatterActivity
12 | import com.espoir.shatter.Shatter
13 | import com.espoir.shatter.ShatterManager
14 |
15 |
16 | class MainActivity : AppCompatActivity(), IShatterActivity {
17 |
18 | private val shatterManager = ShatterManager(this)
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | setContentView(R.layout.activity_main)
23 |
24 | getShatterManager()
25 | .addShatter(R.id.shatterALayout, ShatterA())
26 | .addShatter(ShatterAChild())
27 | .addShatter(ShatterB())
28 | .addShatter(ShatterC())
29 | .start()
30 | }
31 |
32 | override fun getShatterManager(): ShatterManager = shatterManager
33 |
34 | override fun onNewIntent(intent: Intent?) {
35 | super.onNewIntent(intent)
36 | getShatterManager().onNewIntent(intent)
37 | }
38 |
39 | override fun onRestart() {
40 | super.onRestart()
41 | getShatterManager().onRestart()
42 | }
43 |
44 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
45 | super.onActivityResult(requestCode, resultCode, data)
46 | getShatterManager().onActivityResult(requestCode, resultCode, data)
47 | }
48 |
49 | override fun onBackPressed() {
50 | if (getShatterManager().enableOnBackPressed()) {
51 | super.onBackPressed()
52 | }
53 | }
54 | }
55 |
56 | class ShatterA : Shatter() {
57 | override fun getLayoutResId(): Int = R.layout.layout_shatter_a
58 |
59 | var textView: TextView? = null
60 |
61 | override fun initView(view: View?, intent: Intent?) {
62 | super.initView(view, intent)
63 | Log.i("MainActivity", "ShatterA initView")
64 | textView = view?.findViewById(R.id.textView)
65 |
66 | //接口支持
67 | view?.findViewById