├── .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/v/EspoirX/ShatterManager.svg)](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