diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..61a9130
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..6c5f767
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..6560a98
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..0380d8d
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d5d35ec
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..797acea
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/.gitignore b/api/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/api/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..1b11f83
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,83 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
+}
+
+apply plugin: 'com.github.dcendents.android-maven'
+group='com.github.SJF20'
+
+//ext {
+// GENERATE_DIRECTORY = "api"
+// GROUP_ID = "com.shijingfeng.module_event_dispatcher"
+// ARTICFCT_ID = "api"
+// VERSION = "1.0.0"
+//}
+//
+//apply plugin: 'maven'
+//
+//// 本地依赖打包
+//uploadArchives {
+// repositories {
+// mavenDeployer{
+// repository(url: "file://" + rootDir.path + File.separator + "generate" + File.separator + GENERATE_DIRECTORY)
+// pom.project {
+// groupId = GROUP_ID
+// artifactId = ARTICFCT_ID
+// version = VERSION
+// }
+// }
+// }
+//}
+//
+//// 源代码一起打包(不需要打包源代码的不要添加这几行)
+//task sourcesJar(type: Jar) {
+// classifier = 'sources'
+// from android.sourceSets.main.java.sourceFiles
+//}
+//
+//artifacts {
+// archives sourcesJar
+//}
\ No newline at end of file
diff --git a/api/consumer-rules.pro b/api/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/api/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/api/src/main/AndroidManifest.xml b/api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..39ed719
--- /dev/null
+++ b/api/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
\ No newline at end of file
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/manager/ModuleEventManager.java b/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/manager/ModuleEventManager.java
new file mode 100644
index 0000000..931b3e8
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/manager/ModuleEventManager.java
@@ -0,0 +1,238 @@
+package com.shijingfeng.module_event_dispatcher.api.manager;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+
+import com.shijingfeng.module_event_dispatcher.api.util.ClassUtilKt;
+import com.shijingfeng.module_event_dispatcher.data.constant.Constant;
+import com.shijingfeng.module_event_dispatcher.data.entity.ModuleEventReceiverData;
+import com.shijingfeng.module_event_dispatcher.data.interfaces.IModuleEventDataLoader;
+import com.shijingfeng.module_event_dispatcher.data.interfaces.ModuleEventListener;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Function: 模块事件 管理器 (暴露的接口使用Java写有许多好处: 1. Java工程中有类继承该暴露接口能复制文档 2. Java工程可以看到该暴露接口源码)
+ * Date: 2021/3/11 21:10
+ * Description:
+ * Author: ShiJingFeng
+ */
+public class ModuleEventManager {
+
+ /** 是否初始化过了 true:已经初始化过了 */
+ private static boolean mHasInit = false;
+
+ /**
+ * 分组后的 模块事件接收器数据[ModuleEventReceiverData] 列表
+ * Key: 组名 [ModuleEventReceiverData.group]
+ * Value: 模块事件接收器数据[ModuleEventReceiverData] 列表
+ */
+ private static final Map> MODULE_EVENT_RECEIVER_DATA_LIST_MAP = new HashMap<>();
+
+ /**
+ * Key: 组名 [ModuleEventReceiverData.group]
+ * Value: 模块事件监听器[ModuleEventListener] 列表
+ */
+ private static final Map> MODULE_EVENT_LISTENER_LIST_MAP = new HashMap<>();
+
+ /**
+ * 使用 Gradle Plugin 通过 ASM 在 class文件的当前方法中插入要执行的逻辑
+ */
+ private static void loadByGradlePlugin() {
+ /**
+ * 通过反编译, 此处会插入Java代码举例(针对于ASM没有换行, 是因为织入代码时没有指定行号)
+ * load("{@link AUTO_GENERATE_FILE_PACKAGE_NAME}.{@link MODULE_DATA_LOADER_PREFIX}$$app");load("{@link AUTO_GENERATE_FILE_PACKAGE_NAME}.{@link MODULE_DATA_LOADER_PREFIX}$$base")
+ */
+ }
+
+ /**
+ * 加载生成的类数据
+ */
+ private static void load(String moduleDataLoaderClassName) {
+ if (TextUtils.isEmpty(moduleDataLoaderClassName)) {
+ return;
+ }
+ try {
+ final Class> clz = Class.forName(moduleDataLoaderClassName);
+ final Object instance = clz.newInstance();
+
+ if (instance instanceof IModuleEventDataLoader) {
+ final IModuleEventDataLoader moduleEventDataLoader = (IModuleEventDataLoader) instance;
+ final List dataList = new ArrayList<>();
+
+ // 加载某个模块中的所有ModuleEventReceiverData数据
+ moduleEventDataLoader.load(dataList);
+ for (ModuleEventReceiverData data : dataList) {
+ final String group = data.getGroup();
+ final List moduleEventReceiverDataList = MODULE_EVENT_RECEIVER_DATA_LIST_MAP.get(group);
+
+ if (moduleEventReceiverDataList == null) {
+ final List newModuleEventReceiverDataList = new ArrayList<>();
+
+ newModuleEventReceiverDataList.add(data);
+ MODULE_EVENT_RECEIVER_DATA_LIST_MAP.put(group, newModuleEventReceiverDataList);
+ MODULE_EVENT_LISTENER_LIST_MAP.put(group, new ArrayList<>());
+ } else {
+ moduleEventReceiverDataList.add(data);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 初始化
+ */
+ private static void init() {
+ for (Map.Entry> entry : MODULE_EVENT_RECEIVER_DATA_LIST_MAP.entrySet()) {
+ final String group = entry.getKey();
+ final List moduleEventReceiverDataList = entry.getValue();
+ final List moduleEventListenerList = Objects.requireNonNull(MODULE_EVENT_LISTENER_LIST_MAP.get(group));
+
+ // 分组后的列表 按照优先级从大到小排序
+ Collections.sort(moduleEventReceiverDataList, (o1, o2) -> {
+ final int priority1 = o1.getPriority();
+ final int priority2 = o2.getPriority();
+
+ return priority1 - priority2;
+ });
+ // 添加分组后的模块事件数据列表
+ final List newModuleEventListenerList = new ArrayList<>();
+
+ for (ModuleEventReceiverData data : moduleEventReceiverDataList) {
+ try {
+ final Class> clz = Class.forName(data.getClassQualifiedName());
+ final Constructor> constructor = clz.getDeclaredConstructor();
+ final Object instance = constructor.newInstance();
+
+ // 设置私有构造方法可执行和修改
+ constructor.setAccessible(true);
+ if (instance instanceof ModuleEventListener) {
+ newModuleEventListenerList.add((ModuleEventListener) instance);
+ } else {
+ newModuleEventListenerList.add(null);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new IllegalArgumentException("${data.classQualifiedName}必须要有一个无参构造方法!");
+ }
+ }
+ moduleEventListenerList.addAll(newModuleEventListenerList);
+ }
+ }
+
+ /**
+ * 动态初始化 (耗时操作, 需要通过解压dex包来查找到所有需要的类)
+ */
+ @WorkerThread
+ public static void dynamicInit(Context context) {
+ if (mHasInit) {
+ return;
+ }
+
+ final Set classNameSet = ClassUtilKt.getClassNameSetByPackageName(context, Constant.AUTO_GENERATE_FILE_PACKAGE_NAME);
+
+ for (String moduleDataLoaderClassName : classNameSet) {
+ load(moduleDataLoaderClassName);
+ }
+ init();
+ mHasInit = true;
+ }
+
+ /**
+ * 静态初始化 (打包时使用Gradle插件查找到所有需要的类)
+ */
+ @AnyThread
+ public static void staticInit() {
+ if (mHasInit) {
+ return;
+ }
+ loadByGradlePlugin();
+ init();
+ mHasInit = true;
+ }
+
+ /**
+ * 分发事件
+ *
+ * @param group 组名
+ */
+ public static void dispatch(@NonNull String group) {
+ dispatch(group, 0x0, new HashMap<>());
+ }
+
+ /**
+ * 分发事件
+ *
+ * @param group 组名
+ * @param flag 标志(建议使用位标志, int类型可用32个位标志)
+ */
+ public static void dispatch(@NonNull String group, int flag) {
+ dispatch(group, flag, new HashMap<>());
+ }
+
+ /**
+ * 分发事件
+ *
+ * @param group 组名
+ * @param data 携带的数据
+ */
+ public static void dispatch(@NonNull String group, @NonNull Map data) {
+ dispatch(group, 0x0, data);
+ }
+
+ /**
+ * 分发事件
+ *
+ * @param group 组名
+ * @param flag 标志(建议使用位标志, int类型可用32个位标志)
+ * @param data 携带的数据
+ */
+ public static void dispatch(@NonNull String group, int flag, @NonNull Map data) {
+ final List moduleEventReceiverDataList = MODULE_EVENT_RECEIVER_DATA_LIST_MAP.get(group);
+ final List moduleEventListenerList = MODULE_EVENT_LISTENER_LIST_MAP.get(group);
+
+ if (moduleEventReceiverDataList == null || moduleEventListenerList == null) {
+ return;
+ }
+ if (flag == Constant.ALL_FLAG) {
+ for (ModuleEventListener moduleEventListener : moduleEventListenerList) {
+ final boolean stopDispatch = moduleEventListener != null && moduleEventListener.onReceive(data);
+
+ if (stopDispatch) {
+ // 阻断接下来的事件执行(借鉴Broadcast)
+ break;
+ }
+ }
+ } else {
+ for (int index = 0; index < moduleEventListenerList.size(); ++index) {
+ final ModuleEventListener moduleEventListener = moduleEventListenerList.get(index);
+ final ModuleEventReceiverData moduleEventReceiverData = moduleEventReceiverDataList.get(index);
+ final int curFlag = moduleEventReceiverData.getFlag();
+
+ if (curFlag == flag) {
+ final boolean stopDispatch = moduleEventListener != null && moduleEventListener.onReceive(data);
+
+ if (stopDispatch) {
+ // 阻断接下来的事件执行(借鉴Broadcast)
+ break;
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/util/CastUtil.kt b/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/util/CastUtil.kt
new file mode 100644
index 0000000..fcccdcf
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/util/CastUtil.kt
@@ -0,0 +1,19 @@
+/** 生成的 Java 类名 */
+@file:JvmName("CastUtil")
+package com.shijingfeng.module_event_dispatcher.api.util
+
+/**
+ * Function: 转换工具类
+ * Date: 2020/1/17 20:27
+ * Description:
+ * Author: ShiJingFeng
+ */
+
+/**
+ * 用于消除类型装换警告
+ * @param obj 原类型数据
+ * @param 泛型
+ * @return 装换类型后的数据
+ */
+@Suppress("UNCHECKED_CAST")
+internal fun cast(obj : Any?) : T = obj as T
\ No newline at end of file
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/util/ClassUtil.kt b/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/util/ClassUtil.kt
new file mode 100644
index 0000000..590593b
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/api/util/ClassUtil.kt
@@ -0,0 +1,381 @@
+package com.shijingfeng.module_event_dispatcher.api.util
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Context.MODE_MULTI_PROCESS
+import android.content.Context.MODE_PRIVATE
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import android.util.Log
+import androidx.annotation.WorkerThread
+import dalvik.system.DexFile
+import kotlinx.coroutines.*
+import java.io.File
+import java.io.IOException
+import java.util.*
+import java.util.regex.Pattern.compile
+
+/**
+ * Function: 类相关工具类 (参考 ARouter 的 ClassUtils 工具类)
+ * Date: 2020/12/6 16:42
+ * Description:
+ * Author: ShiJingFeng
+ */
+
+private const val EXTRACTED_NAME_EXT = ".classes"
+private const val EXTRACTED_SUFFIX = ".zip"
+
+private val SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes"
+
+private const val PREFS_FILE = "multidex.version"
+private const val KEY_DEX_NUMBER = "dex.number"
+
+private const val VM_WITH_MULTIDEX_VERSION_MAJOR = 2
+private const val VM_WITH_MULTIDEX_VERSION_MINOR = 1
+
+/**
+ * 通过包名获取App内文件名
+ * 注意: 该操作是耗时操作
+ * 如果获取的文件是Java类文件,则可以直接通过发射加载类
+ * 如果是Kotlin文件, 则需要特殊处理
+ */
+@WorkerThread
+@Suppress("DEPRECATION")
+internal fun getClassNameSetByPackageName(
+ context: Context,
+ packageNameSet: Set
+): Map> {
+ val classNameSetMap = mutableMapOf>()
+ val sourcePathList = getSourcePathList(context)
+
+ if (sourcePathList.isEmpty()) {
+ return classNameSetMap
+ }
+ sourcePathList.forEach { path ->
+ var dexFile: DexFile? = null
+
+ try {
+ dexFile = if (path.endsWith(EXTRACTED_SUFFIX)) {
+ DexFile.loadDex(path, "$path.tmp", 0)
+ } else {
+ DexFile(path)
+ }
+
+ dexFile?.entries()?.run {
+ while (hasMoreElements()) {
+ val className = nextElement()
+
+ packageNameSet.forEach { packageName ->
+ if (className.startsWith(packageName)) {
+ val classNameSet = classNameSetMap[packageName]
+
+ if (classNameSet === null) {
+ classNameSetMap[packageName] = mutableSetOf(className)
+ } else {
+ classNameSet.add(className)
+ }
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ } finally {
+ try {
+ dexFile?.close()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ return classNameSetMap
+}
+
+/**
+ * 使用协程
+ * 通过包名获取App内文件名
+ * 注意: 该操作是耗时操作
+ * 如果获取的文件是Java类文件,则可以直接通过发射加载类
+ * 如果是Kotlin文件, 则需要特殊处理
+ */
+@WorkerThread
+@Suppress("DEPRECATION")
+internal suspend fun getClassNameSetByPackageNameUseCoroutine(
+ context: Context,
+ packageNameSet: Set,
+): Map> = withContext(context = Dispatchers.IO) {
+ val classNameSetMap = mutableMapOf>()
+ val sourcePathList = getSourcePathList(context)
+
+ if (sourcePathList.isEmpty()) {
+ return@withContext classNameSetMap
+ }
+
+ val deferredList = mutableListOf>()
+
+ sourcePathList.forEach { path ->
+ // 注意: context 使用 getCustomThreadExecutorInstance().asCoroutineDispatcher() 会导致弹出警告 Inappropriate blocking method call
+ // 故使用 Dispatchers.IO
+ // 注意: async{}.await() 和 val result = async{} result.await() 不一样
+ // 前者会阻塞, 导致后面的代码暂停执行, 而后者不会
+ val deferred = async(context = Dispatchers.IO) {
+ var dexFile: DexFile? = null
+
+ try {
+ dexFile = if (path.endsWith(EXTRACTED_SUFFIX)) {
+ DexFile.loadDex(path, "$path.tmp", 0)
+ } else {
+ DexFile(path)
+ }
+
+ dexFile?.entries()?.run {
+ while (hasMoreElements()) {
+ val className = nextElement()
+
+ packageNameSet.forEach { packageName ->
+ if (className.startsWith(packageName)) {
+ val classNameSet = classNameSetMap[packageName]
+
+ if (classNameSet === null) {
+ classNameSetMap[packageName] = mutableSetOf(className)
+ } else {
+ classNameSet.add(className)
+ }
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ } finally {
+ try {
+ dexFile?.close()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return@async
+ }
+ }
+
+ deferredList.add(deferred)
+ }
+
+ deferredList.forEach { deferred ->
+ deferred.await()
+ }
+ return@withContext classNameSetMap
+}
+
+/**
+ * 通过包名获取App内文件名
+ * 注意: 该操作是耗时操作
+ * 如果获取的文件是Java类文件,则可以直接通过发射加载类
+ * 如果是Kotlin文件, 则需要特殊处理
+ */
+@WorkerThread
+@Suppress("DEPRECATION")
+internal fun getClassNameSetByPackageName(
+ context: Context,
+ packageName: String,
+): Set {
+ val classNameSet = mutableSetOf()
+ val sourcePathList = getSourcePathList(context)
+
+ if (sourcePathList.isEmpty()) {
+ return classNameSet
+ }
+
+ sourcePathList.forEach { path ->
+ var dexFile: DexFile? = null
+
+ try {
+ dexFile = if (path.endsWith(EXTRACTED_SUFFIX)) {
+ DexFile.loadDex(path, "$path.tmp", 0)
+ } else {
+ DexFile(path)
+ }
+
+ dexFile?.entries()?.run {
+ while (hasMoreElements()) {
+ val className = nextElement()
+
+ if (className.startsWith(packageName)) {
+ classNameSet.add(className)
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ } finally {
+ try {
+ dexFile?.close()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ return classNameSet
+}
+
+/**
+ * 使用协程
+ * 通过包名获取App内文件名
+ * 注意: 该操作是耗时操作
+ * 如果获取的文件是Java类文件,则可以直接通过发射加载类
+ * 如果是Kotlin文件, 则需要特殊处理
+ */
+@WorkerThread
+@Suppress("DEPRECATION")
+internal suspend fun getClassNameSetByPackageNameUseCoroutine(
+ context: Context,
+ packageName: String,
+): Set = withContext(context = Dispatchers.IO) {
+ val classNameSet = mutableSetOf()
+ val sourcePathList = getSourcePathList(context)
+
+ if (sourcePathList.isEmpty()) {
+ return@withContext classNameSet
+ }
+
+ val deferredList = mutableListOf>()
+
+ sourcePathList.forEach { path ->
+ // 注意: context 使用 getCustomThreadExecutorInstance().asCoroutineDispatcher() 会导致弹出警告 Inappropriate blocking method call
+ // 故使用 Dispatchers.IO
+ // 注意: async{}.await() 和 val result = async{} result.await() 不一样
+ // 前者会阻塞, 导致后面的代码暂停执行, 而后者不会
+ val deferred = async(context = Dispatchers.IO) {
+ var dexFile: DexFile? = null
+
+ try {
+ dexFile = if (path.endsWith(EXTRACTED_SUFFIX)) {
+ DexFile.loadDex(path, "$path.tmp", 0)
+ } else {
+ DexFile(path)
+ }
+
+ dexFile?.entries()?.run {
+ while (hasMoreElements()) {
+ val className = nextElement()
+
+ if (className.startsWith(packageName)) {
+ classNameSet.add(className)
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ } finally {
+ try {
+ dexFile?.close()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return@async
+ }
+ }
+
+ deferredList.add(deferred)
+ }
+
+ deferredList.forEach { deferred ->
+ deferred.await()
+ }
+ return@withContext classNameSet
+}
+
+/**
+ * 获取apk路径列表(分包后会有多个文件)
+ */
+internal fun getSourcePathList(context: Context): List {
+ val sourcePathList = mutableListOf()
+ val appInfo: ApplicationInfo
+
+ try {
+ appInfo = context.packageManager.getApplicationInfo(context.packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ return sourcePathList
+ }
+ val sourceApk = File(appInfo.sourceDir)
+ // 其他的文件前缀名称(dex分包后其他额外的包) 例如: base.apk.classes
+ val extraFilePrefix = sourceApk.name + EXTRACTED_NAME_EXT
+
+ // 添加默认apk路径
+ sourcePathList.add(appInfo.sourceDir)
+ // 注意: 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
+ if (!isVMMultidexCapable) {
+ // VM不支持dex分包(Api21以下), 使用Multidex框架不影响
+ val totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1)
+ val dexDir = File(appInfo.dataDir, SECONDARY_FOLDER_NAME)
+
+ for (secondaryNumber in 2..totalDexNumber) {
+ val extraFileName = extraFilePrefix + secondaryNumber + EXTRACTED_SUFFIX
+ val extraFile = File(dexDir, extraFileName)
+
+ if (extraFile.isFile) {
+ sourcePathList.add(extraFile.absolutePath)
+ } else {
+ throw IOException("Missing extracted secondary dex file '" + extraFile.absolutePath + "'")
+ }
+ }
+ }
+ return sourcePathList
+}
+
+/**
+ * VM是否可以有dex分包(multidex)能力, 注: Android5.0(API 21)以上 VM 有此能力,以下需要使用 Multidex 框架
+ *
+ * @return true: VM有dex分包能力
+ */
+private val isVMMultidexCapable: Boolean
+ get() = try {
+ if (isAliYunOS()) {
+ val sdkVersion = System.getProperty("ro.build.version.sdk")?.toInt() ?: Build.VERSION_CODES.BASE
+
+ sdkVersion >= Build.VERSION_CODES.LOLLIPOP
+ } else {
+ val vmVersion = System.getProperty("java.vm.version")
+
+ if (!vmVersion.isNullOrEmpty()) {
+ val matcher = compile("(\\\\d+)\\\\.(\\\\d+)(\\\\.\\\\d+)?").matcher(vmVersion)
+
+ if (matcher.matches()) {
+ try {
+ val major = matcher.group(1)?.toInt() ?: 0
+ val minor = matcher.group(2)?.toInt() ?: 0
+
+ (major > VM_WITH_MULTIDEX_VERSION_MAJOR) || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR) && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR))
+ } catch (e: Exception) {
+ false
+ }
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+ } catch (e: Exception) { false }
+
+/**
+ * 判断是不是阿里云操作系统 (基于Android, 车辆网和物联网上可能有用到的)
+ *
+ * @return true: 是 阿里云OS
+ */
+private fun isAliYunOS() = try {
+ val version = System.getProperty("ro.yunos.version")
+ val vmName = System.getProperty("java.vm.name")
+
+ (vmName != null && vmName.toLowerCase(Locale.getDefault()).contains("lemur")) || (version != null && version.trim().isNotEmpty())
+} catch (e: Exception) {
+ false
+}
+
+/**
+ * 获取 MultiDex 存储在本地的 SharedPreferences
+ */
+@SuppressLint("ObsoleteSdkInt")
+private fun getMultiDexPreferences(
+ context: Context
+) = context.getSharedPreferences(PREFS_FILE, if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) MODE_PRIVATE else (MODE_PRIVATE or MODE_MULTI_PROCESS))
\ No newline at end of file
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/annotations/ModuleEventReceiver.java b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/annotations/ModuleEventReceiver.java
new file mode 100644
index 0000000..2fadcb5
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/annotations/ModuleEventReceiver.java
@@ -0,0 +1,29 @@
+package com.shijingfeng.module_event_dispatcher.data.annotations;
+
+import com.shijingfeng.module_event_dispatcher.data.constant.Constant;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Function: 模块事件接收器 注解 (暴露的接口使用Java写有许多好处: 1. Java工程中有类继承该暴露接口能复制文档 2. Java工程可以看到该暴露接口源码)
+ * Date: 2021/3/11 22:20
+ * Description:
+ * Author: ShiJingFeng
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface ModuleEventReceiver {
+
+ /** 组名 */
+ String group();
+
+ /** 优先级 越小优先级越大 */
+ int priority() default Constant.PRIORITY_MEDIUM;
+
+ /** 标志(注意: {@link Constant#ALL_FLAG} (0 或 0x0) 代表所有) 最好使用位标志(int类型占4个字节,可以用32个位进行组合) 例如: 00000000 00000000 00000000 00000000 */
+ int flag() default Constant.ALL_FLAG;
+
+}
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/constant/Constant.java b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/constant/Constant.java
new file mode 100644
index 0000000..0575c8c
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/constant/Constant.java
@@ -0,0 +1,31 @@
+package com.shijingfeng.module_event_dispatcher.data.constant;
+
+/**
+ * Function: 静态常量 (暴露的接口使用Java写有许多好处: 1. Java工程中有类继承该暴露接口能复制文档 2. Java工程可以看到该暴露接口源码)
+ * Date: 2021/3/11 22:26
+ * Description:
+ * Author: ShiJingFeng
+ */
+public class Constant {
+
+ /** 优先级: 高 */
+ public static final int PRIORITY_HIGH = 0;
+ /** 优先级: 中 */
+ public static final int PRIORITY_MEDIUM = 1;
+ /** 优先级: 低 */
+ public static final int PRIORITY_LOW = 2;
+
+ /** 指定组的所有的标志的接收器都会接收到分发事件 */
+ public static final int ALL_FLAG = 0x0;
+
+ /** 自动生成的模块数据加载器 名字前缀 */
+ public static final String MODULE_DATA_LOADER_PREFIX = "ModuleDataLoader$$";
+
+ /**
+ * 自动生成的文件 包名
+ * 以后本工程通过注解执行器生成的所有类文件都在此包中(包含不同业务), 这样可以一次性加载(加载是耗时操作)
+ * 然后再转到各个业务处理类中
+ */
+ public static final String AUTO_GENERATE_FILE_PACKAGE_NAME = "com.shijingfeng.module_event_dispatcher.auto_generate";
+
+}
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/entity/ModuleEventReceiverData.kt b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/entity/ModuleEventReceiverData.kt
new file mode 100644
index 0000000..85728dd
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/entity/ModuleEventReceiverData.kt
@@ -0,0 +1,26 @@
+package com.shijingfeng.module_event_dispatcher.data.entity
+
+/**
+ * Function: Activity 数据
+ * Date: 2020/12/8 16:55
+ * Description:
+ * Author: ShiJingFeng
+ */
+class ModuleEventReceiverData(
+
+ /** 模块名称 */
+ var moduleName: String,
+
+ /** 生成的加载类全限定名称 */
+ var classQualifiedName: String,
+
+ /** 组名 */
+ var group: String,
+
+ /** 优先级 */
+ var priority: Int,
+
+ /** 标志 */
+ var flag: Int
+
+)
\ No newline at end of file
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/interfaces/IModuleEventDataLoader.kt b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/interfaces/IModuleEventDataLoader.kt
new file mode 100644
index 0000000..b41709d
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/interfaces/IModuleEventDataLoader.kt
@@ -0,0 +1,20 @@
+package com.shijingfeng.module_event_dispatcher.data.interfaces
+
+import com.shijingfeng.module_event_dispatcher.data.entity.ModuleEventReceiverData
+
+/**
+ * Function: 模块数据 加载接口 (参考 ARouter)
+ * Date: 2020/12/8 16:40
+ * Description:
+ * Author: ShiJingFeng
+ */
+interface IModuleEventDataLoader {
+
+ /**
+ * 执行加载
+ *
+ * @param dataList 已加载的数据
+ */
+ fun load(dataList: List)
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/interfaces/ModuleEventListener.java b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/interfaces/ModuleEventListener.java
new file mode 100644
index 0000000..e281bfe
--- /dev/null
+++ b/api/src/main/java/com/shijingfeng/module_event_dispatcher/data/interfaces/ModuleEventListener.java
@@ -0,0 +1,23 @@
+package com.shijingfeng.module_event_dispatcher.data.interfaces;
+
+import java.util.Map;
+
+/**
+ * Function: 模块事件 监听器 (暴露的接口使用Java写有许多好处: 1. Java工程中有类继承该暴露接口能复制文档 2. Java工程可以看到该暴露接口源码)
+ * Date: 2021/3/11 22:36
+ * Description:
+ * Author: ShiJingFeng
+ */
+public interface ModuleEventListener {
+
+ /**
+ * 事件接收回调函数
+ *
+ * @param data 携带的数据
+ * @return 是否中断接下来的其他模块该方法的执行 true: 中断
+ */
+ default boolean onReceive(Map data) {
+ return false;
+ }
+
+}
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..b604e60
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,46 @@
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+}
+
+android {
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ applicationId "com.shijingfeng.module_event_dispatcher_api"
+ minSdkVersion 21
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.3.1'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'com.google.android.material:material:1.2.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+
+ implementation project(":api")
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/shijingfeng/module_event_dispatcher_api/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/shijingfeng/module_event_dispatcher_api/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..7a0ba15
--- /dev/null
+++ b/app/src/androidTest/java/com/shijingfeng/module_event_dispatcher_api/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.shijingfeng.module_event_dispatcher_api
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.shijingfeng.module_event_dispatcher_api", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e38eb6e
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/shijingfeng/module_event_dispatcher_api/MainActivity.kt b/app/src/main/java/com/shijingfeng/module_event_dispatcher_api/MainActivity.kt
new file mode 100644
index 0000000..53f1c7e
--- /dev/null
+++ b/app/src/main/java/com/shijingfeng/module_event_dispatcher_api/MainActivity.kt
@@ -0,0 +1,11 @@
+package com.shijingfeng.module_event_dispatcher_api
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..4fc2444
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..556bcf5
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c9c2ba7
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ module_event_dispatcher_api
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..0a6e604
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/shijingfeng/module_event_dispatcher_api/ExampleUnitTest.kt b/app/src/test/java/com/shijingfeng/module_event_dispatcher_api/ExampleUnitTest.kt
new file mode 100644
index 0000000..4066166
--- /dev/null
+++ b/app/src/test/java/com/shijingfeng/module_event_dispatcher_api/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.shijingfeng.module_event_dispatcher_api
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..2dde2ae
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = "1.5.0"
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.2.1"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ jcenter() // Warning: this repository is going to shut down soon
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar
new file mode 100644
index 0000000..0a5a4b1
Binary files /dev/null and b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar differ
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar.md5 b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar.md5
new file mode 100644
index 0000000..d080880
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar.md5
@@ -0,0 +1 @@
+79eed1172a88db1fbc973f4ed9d112bd
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar.sha1 b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar.sha1
new file mode 100644
index 0000000..c3dae67
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0-sources.jar.sha1
@@ -0,0 +1 @@
+be7dcdc0cb0a7dd5c8b28b7898808e35e326dc8b
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar
new file mode 100644
index 0000000..5dc076c
Binary files /dev/null and b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar differ
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar.md5 b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar.md5
new file mode 100644
index 0000000..e5ecb0a
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar.md5
@@ -0,0 +1 @@
+b7fb0ad00dddcd902006f18415ddbcf7
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar.sha1 b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar.sha1
new file mode 100644
index 0000000..595f011
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.aar.sha1
@@ -0,0 +1 @@
+24ab86903ea55acebcb00f8966fe03aebac5a973
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom
new file mode 100644
index 0000000..e730f6f
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+ com.shijingfeng.module_event_dispatcher
+ api
+ 1.0.0
+ aar
+
+
+ org.jetbrains.kotlin
+ kotlin-android-extensions-runtime
+ 1.5.0
+ compile
+
+
+ androidx.core
+ core-ktx
+ 1.3.2
+ compile
+
+
+ androidx.appcompat
+ appcompat
+ 1.2.0
+ compile
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ 1.5.0
+ compile
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+ 1.3.9
+ compile
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-android
+ 1.3.9
+ compile
+
+
+
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom.md5 b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom.md5
new file mode 100644
index 0000000..bb4f97e
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom.md5
@@ -0,0 +1 @@
+0466c027ae9266fd33f33a9df89d6d19
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom.sha1 b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom.sha1
new file mode 100644
index 0000000..9f17d4d
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/1.0.0/api-1.0.0.pom.sha1
@@ -0,0 +1 @@
+13e417f5121c8d0d774f80b090cfb9a5309c21c5
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml b/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml
new file mode 100644
index 0000000..04d91fb
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml
@@ -0,0 +1,12 @@
+
+
+ com.shijingfeng.module_event_dispatcher
+ api
+
+ 1.0.0
+
+ 1.0.0
+
+ 20210516031903
+
+
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml.md5 b/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml.md5
new file mode 100644
index 0000000..9aa4831
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml.md5
@@ -0,0 +1 @@
+4ef1ebbbef10306833840601118cfb1f
\ No newline at end of file
diff --git a/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml.sha1 b/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml.sha1
new file mode 100644
index 0000000..5a53321
--- /dev/null
+++ b/generate/api/com/shijingfeng/module_event_dispatcher/api/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+c8c28232bf586c2c371f417d13b732ba79551c32
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..2521752
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..90e49ca
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun May 16 11:13:15 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..aa9a0b0
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,3 @@
+rootProject.name = "module_event_dispatcher_api"
+include ':app'
+include ':api'