├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── eastwood │ │ └── demo │ │ ├── App.java │ │ ├── EventBusAutoBow.java │ │ ├── MainActivity.java │ │ └── ToastAutoBow.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.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 ├── auto-inject-core ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── eastwood │ │ └── common │ │ └── autoinject │ │ ├── AutoArrow.java │ │ ├── AutoBow.java │ │ ├── AutoBowArrow.java │ │ ├── AutoTarget.java │ │ ├── IAutoArrow.java │ │ ├── IAutoBow.java │ │ └── IAutoBowArrow.java │ └── res │ └── values │ └── strings.xml ├── auto-inject-plugin ├── build.gradle ├── settings.gradle └── src │ └── main │ ├── groovy │ └── com │ │ └── eastwood │ │ └── tools │ │ └── plugins │ │ └── autoinject │ │ ├── AutoClassInfo.groovy │ │ ├── AutoClassInfoComparator.groovy │ │ ├── AutoInjectExtension.groovy │ │ ├── AutoInjectPlugin.groovy │ │ ├── AutoInjectTransform.groovy │ │ ├── AutoInjector.groovy │ │ ├── AutoType.groovy │ │ ├── Logger.groovy │ │ └── adapter │ │ ├── AnnotationAdapter.groovy │ │ ├── BowArrowClassAdapter.groovy │ │ ├── OnAnnotationValueListener.groovy │ │ ├── OnMethodInjectListener.groovy │ │ ├── TargetClassAdapter.groovy │ │ └── TargetClassMethodAdapter.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── auto-inject.properties ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── modules ├── moduleB │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── eastwood │ │ │ └── demo │ │ │ └── module │ │ │ └── b │ │ │ ├── ModuleBActivity.java │ │ │ └── ModuleBAutoArrow.java │ │ └── res │ │ └── values │ │ └── strings.xml └── moduleC │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── eastwood │ │ └── demo │ │ └── module │ │ └── c │ │ ├── ModuleCActivity.java │ │ └── ModuleCAutoArrow.java │ └── res │ └── values │ └── strings.xml ├── picture └── 1.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Android Studio Navigation editor temp files 24 | .navigation/ 25 | .externalNativeBuild 26 | 27 | # Android Studio captures folder 28 | captures/ 29 | 30 | # Intellij 31 | *.iml 32 | .idea 33 | 34 | # Keystore files 35 | *.jks 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoInject 2 | Android 通用的组件自动注册、自动初始化解决方案 3 | 4 | ## 背景问题 5 | 我们在组件化的过程,业务被拆分至独立的Module中,一些公用组件会在各个Module中通过APT生成一些需要被注册至组件中的信息类,比如EventBus生成的Index类。我们这边RN定制的Plugin是跟随各自module,需要被注册。还有,各Module对外提供的api接口的话,也需要被注册。 6 | 7 | 另外,有些组件为某些Module特有,需要在App启动的时候就要初始化,有些需要在主线程中初始化,有些为不阻塞主线程可以在非主线程中初始化。 8 | 9 | 在组件化之前,我们是在Main Module通过硬编码来进行注册,在Application中堆积各个组件的初始化逻辑。 10 | 11 | **有没有更好的解决方式?** 12 | 13 | ## 解决思路 14 | 首先,将问题分解抽象: 15 | 16 | * 把注册行为进行抽象化,可以把一个类(需要被注册的信息)看作方法函数的入参,那方法函数就可以看作是对注册相关逻辑的实现。那注册问题可以进一步转化为各模块如何把相关类(需要被注册的信息)转化为方法函数的入参,组件定义方法函数,获取入参来实现注册逻辑。比如: 17 | 18 | 19 | A a = new A() 20 | B b = new B() 21 | b.shoot(a.get()) 22 | 23 | A为Module定义的一个类,通过get方法可以获得被注册的信息,B为组件定义的一个类,在shoot方法中实现注册逻辑。 24 | 25 | * 接下来的问题是,AB分处不同模块,如何把AB按上述代码逻辑组合起来? 26 | 27 | AB组合意味着需要代码注入,代码注入使用的技术方案是Gradle Transform + ASM。AB分别实现约定的接口,再用注解标记。在编译时,通过Gradle Transform + ASM,通过注解找到AB,生成上述格式的代码并注入到合适的位置。 28 | 29 | * 剩下的问题就是,在什么位置注入? 30 | 31 | 采用的方式是预先定义一个空方法,通过注解标记,并在适当时机调用这个空方法。在编译时通过注解找到AB和空方法,生成上述格式的代码并注入到这个空方法中。 32 | 33 | 以上是有关组件注册方面的解决思路,而模块中的组件初始化有点不同,因为其不需要入参。 但可以直接在方法函数中实现初始化逻辑,比如: 34 | 35 | 36 | A a = new A() 37 | a.shoot() 38 | 39 | 模块定义一个类A,实现约定的接口,在shoot方法中做实现初始化逻辑。 40 | 41 | 42 | 其余和组件注册方式相类似,主要在注入的代码逻辑上有所不同。 43 | 44 | 45 | ## 设计模型 46 | 47 | 48 | 具体的实现方式其实是借鉴了弓箭耙的模式。 49 | 50 | 弓箭耙模式: 51 | * 箭 Arrow:对应一种型号 52 | 53 | 提供模块相关类(需要被注册的信息)的载体。 54 | 55 | * 弓 Bow:适配一种型号的箭,射向唯一的耙。 56 | 57 | 方法函数,即实现注册逻辑的载体。 58 | 59 | * 耙 Target:位置 60 | 61 | 将被注入的空方法。 62 | 63 | ## Usage 64 | 在根项目的build.gradle中添加插件依赖: 65 | 66 | buildscript { 67 | ... 68 | dependencies { 69 | ... 70 | classpath 'com.eastwood.tools.plugins:auto-inject:1.0.3' 71 | } 72 | 73 | } 74 | 75 | 在模块的build.gradle中添加注解库依赖: 76 | 77 | dependencies { 78 | ... 79 | implementation 'com.eastwood.common:auto-inject:1.0.0' 80 | 81 | } 82 | 83 | 在主模块的build.gradle中引用插件: 84 | 85 | 86 | apply plugin: 'auto-inject' 87 | 88 | autoInject { 89 | showLog = true 90 | ignorePackages = ['android', 'com/google'] 91 | } 92 | 93 | #### @AutoTarget 94 | 预先定义一个空方法并调用,在方法上标记@AutoTarget,例如: 95 | 96 | public class App extends Application { 97 | 98 | public EventBusBuilder eventBusBuilder; 99 | 100 | @Override 101 | public void onCreate() { 102 | super.onCreate(); 103 | 104 | eventBusBuilder = EventBus.builder(); 105 | // add config to eventBusBuilder 106 | addIndex2EventBus(); 107 | eventBusBuilder.build(); 108 | 109 | } 110 | 111 | @AutoTarget 112 | void addIndex2EventBus() {} 113 | 114 | } 115 | 116 | `addIndex2EventBus` 方法将被注入代码。 117 | 118 | #### @AutoArrow 119 | 新建一个类,并实现IAutoArrow接口,在get方法中返回需要被注册的信息类。例如: 120 | 121 | @AutoArrow(model = "eventBusIndex") 122 | public class ModuleBAutoArrow implements IAutoArrow { 123 | 124 | @Override 125 | public SubscriberInfoIndex get() { 126 | return new ModuleBEventBusIndex(); 127 | } 128 | 129 | } 130 | 131 | #### @AutoBow 132 | 新建一个类,并实现IAutoBow接口,在shoot方法中获取入参并执行具体的注册逻辑。例如: 133 | 134 | @AutoBow(target = "addIndex2EventBus", model = "eventBusIndex", context = true) 135 | public class EventBusAutoBow implements IAutoBow { 136 | 137 | private App app; 138 | 139 | EventBusAutoBow(Application application) { 140 | app = (App) application; 141 | } 142 | 143 | @Override 144 | public void shoot(SubscriberInfoIndex index) { 145 | app.eventBusBuilder.addIndex(index); 146 | } 147 | 148 | } 149 | 150 | 其中 `context` 用于声明EventBusAutoBow被实例化时是否需要上下文,这个上下文是被`@AutoTarget`标记的方法在运行时的上下文。为 `true`时,该类需定义一个以上下文做为唯一入参的构造函数。 151 | 152 | #### @AutoBowArrow 153 | 新建一个类,并实现IAutoBowArrow接口,在shoot方法中执行相关逻辑。 154 | 155 | @AutoBowArrow(target = "init") 156 | public class InitAutoBowArrow implements IAutoBowArrow { 157 | 158 | @Override 159 | public void shoot() { 160 | // ... 161 | } 162 | 163 | } 164 | 165 | ### 两种组合方式 166 | * @AutoArrow + @AutoBow + @AutoTarget,三者比例关系为 **n:1:1** 167 | 168 | * @AutoBowArrow + @AutoTarget ,两者比例关系为 **1:1** 169 | 170 | ### 编译后,被注入的代码样式 171 | 打包成apk后,@AutoTarget标记的方法将会被注入具有固定结构的代码,例如: 172 | 173 | 174 | // @AutoArrow + @AutoBow + @AutoTarget 组合 175 | 176 | @AutoTarget 177 | void addIndex2EventBus() { 178 | ModuleBAutoArrow moduleBAutoArrow = new ModuleBAutoArrow(); 179 | EventBusAutoBow eventBusAutoBow = new EventBusAutoBow(this); 180 | eventBusAutoBow.shoot(moduleBAutoArrow.get()); 181 | ... 182 | eventBusAutoBow.shoot(***.get()); 183 | } 184 | 185 | // @AutoBowArrow + @AutoTarget 组合 186 | 187 | @AutoTarget 188 | void init() { 189 | InitAutoBowArrow initAutoBowArrow = new InitAutoBowArrow(); 190 | initAutoBowArrow.shoot(); 191 | ... 192 | } 193 | 194 | ## Question or Idea 195 | 有问题或想法可以直接加我微信: EastWoodYang 196 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.eastwood.demo" 7 | minSdkVersion 14 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation project(':auto-inject-core') // or implementation 'com.eastwood.common:auto-inject:1.0.0' 25 | 26 | implementation 'org.greenrobot:eventbus:3.1.1' 27 | 28 | } 29 | 30 | apply plugin: 'auto-inject' 31 | 32 | autoInject { 33 | showLog = true 34 | ignorePackages = ['android', 'com/google', 'com/hianalytics/android'] 35 | } -------------------------------------------------------------------------------- /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 | 23 | -keep @interface com.eastwood.common.autoinject.AutoArrow { *; } 24 | -keep @interface com.eastwood.common.autoinject.AutoTarget { *; } 25 | -keep @interface com.eastwood.common.autoinject.AutoBow { *; } 26 | -keep @com.eastwood.common.autoinject.AutoArrow public class * { *; } 27 | -keep @com.eastwood.common.autoinject.AutoBow public class * { *; } 28 | -keepclassmembers class * { 29 | @com.eastwood.common.autoinject.AutoTarget *; 30 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/eastwood/demo/App.java: -------------------------------------------------------------------------------- 1 | package com.eastwood.demo; 2 | 3 | import android.app.Application; 4 | 5 | import com.eastwood.common.autoinject.AutoTarget; 6 | 7 | import org.greenrobot.eventbus.EventBus; 8 | import org.greenrobot.eventbus.EventBusBuilder; 9 | 10 | /** 11 | * @author eastwood 12 | * createDate: 2018-06-19 13 | */ 14 | public class App extends Application { 15 | 16 | public EventBusBuilder eventBusBuilder; 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | 22 | eventBusBuilder = EventBus.builder(); 23 | // add config to eventBusBuilder 24 | addIndex2EventBus(); 25 | eventBusBuilder.build(); 26 | 27 | } 28 | 29 | @AutoTarget 30 | void addIndex2EventBus() { 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/eastwood/demo/EventBusAutoBow.java: -------------------------------------------------------------------------------- 1 | package com.eastwood.demo; 2 | 3 | import android.app.Application; 4 | 5 | import com.eastwood.common.autoinject.AutoBow; 6 | import com.eastwood.common.autoinject.IAutoBow; 7 | 8 | import org.greenrobot.eventbus.meta.SubscriberInfoIndex; 9 | 10 | @AutoBow(target = "addIndex2EventBus", model = "eventBusIndex", context = true) 11 | public class EventBusAutoBow implements IAutoBow { 12 | 13 | private App app; 14 | 15 | EventBusAutoBow(Application application) { 16 | app = (App) application; 17 | } 18 | 19 | @Override 20 | public void shoot(SubscriberInfoIndex index) { 21 | app.eventBusBuilder.addIndex(index); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/eastwood/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.eastwood.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | 7 | import com.eastwood.common.autoinject.AutoTarget; 8 | 9 | /** 10 | * @author eastwood 11 | * createDate: 2018-09-18 12 | */ 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | 20 | findViewById(R.id.btn_show_toast).setOnClickListener(new View.OnClickListener() { 21 | @Override 22 | public void onClick(View v) { 23 | showToast(); 24 | } 25 | }); 26 | 27 | } 28 | 29 | @AutoTarget(name = "toast") 30 | void showToast() { 31 | 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/eastwood/demo/ToastAutoBow.java: -------------------------------------------------------------------------------- 1 | package com.eastwood.demo; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | import com.eastwood.common.autoinject.AutoBow; 7 | import com.eastwood.common.autoinject.AutoBowArrow; 8 | import com.eastwood.common.autoinject.IAutoBow; 9 | import com.eastwood.common.autoinject.IAutoBowArrow; 10 | 11 | @AutoBowArrow(target = "toast", context = true) 12 | public class ToastAutoBow implements IAutoBowArrow { 13 | 14 | private Context context; 15 | 16 | ToastAutoBow(Context context) { 17 | this.context = context; 18 | } 19 | 20 | @Override 21 | public void shoot() { 22 | Toast.makeText(context, "Hi", Toast.LENGTH_LONG).show(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /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 | 6 | 7 |