├── .gitignore ├── 2.0升级指南.MD ├── LICENSE ├── README-en-US.md ├── README.md ├── android_internal ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── android │ └── app │ ├── ActivityThread.java │ └── AppGlobals.java ├── bintray.gradle ├── bintrayUpload.sh ├── build.gradle ├── cc-register ├── .gitignore ├── build.gradle └── src │ └── main │ ├── groovy │ └── com │ │ └── billy │ │ └── android │ │ └── register │ │ ├── CodeScanner.groovy │ │ ├── RegisterCache.groovy │ │ ├── RegisterExtension.groovy │ │ ├── RegisterInfo.groovy │ │ ├── RegisterPlugin.groovy │ │ ├── RegisterTransform.groovy │ │ ├── ScanHarvest.groovy │ │ └── cc │ │ ├── DefaultRegistryHelper.groovy │ │ ├── ProjectModuleManager.groovy │ │ └── generator │ │ ├── ManifestGenerator.groovy │ │ ├── ProviderGenerator.groovy │ │ └── RegistryCodeGenerator.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── cc-register.properties ├── cc-settings-2.gradle ├── cc-settings-demo.gradle ├── cc-settings.gradle ├── cc.keystore ├── cc ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── billy │ │ └── cc │ │ └── core │ │ └── component │ │ └── remote │ │ ├── IRemoteCCService.aidl │ │ ├── IRemoteCallback.aidl │ │ ├── RemoteCC.aidl │ │ └── RemoteCCResult.aidl │ └── java │ └── com │ └── billy │ └── cc │ └── core │ └── component │ ├── BaseForwardInterceptor.java │ ├── CC.java │ ├── CCMonitor.java │ ├── CCResult.java │ ├── CCUtil.java │ ├── Chain.java │ ├── ChainProcessor.java │ ├── ComponentManager.java │ ├── GlobalCCInterceptorManager.java │ ├── ICCInterceptor.java │ ├── IComponent.java │ ├── IComponentCallback.java │ ├── IDynamicComponent.java │ ├── IGlobalCCInterceptor.java │ ├── IMainThread.java │ ├── IParamJsonConverter.java │ ├── LocalCCInterceptor.java │ ├── RemoteCCInterceptor.java │ ├── RemoteCCService.java │ ├── SubProcessCCInterceptor.java │ ├── ValidateInterceptor.java │ ├── Wait4ResultInterceptor.java │ ├── annotation │ ├── AllProcess.java │ └── SubProcess.java │ └── remote │ ├── BinderWrapper.java │ ├── RemoteCC.java │ ├── RemoteCCResult.java │ ├── RemoteConnection.java │ ├── RemoteConnectionActivity.java │ ├── RemoteCursor.java │ ├── RemoteParamUtil.java │ └── RemoteProvider.java ├── demo-debug.apk ├── demo ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── billy │ │ └── cc │ │ └── demo │ │ ├── JsonFormat.java │ │ ├── MainActivity.java │ │ ├── MissYouInterceptor.java │ │ ├── MyApp.java │ │ └── lifecycle │ │ ├── LifecycleActivity.java │ │ └── LifecycleComponent.java │ └── res │ ├── anim │ ├── enter_from_right.xml │ └── exit_to_left.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_lifecycle.xml │ └── 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-zh-rCN │ └── strings.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── demo_base ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── billy │ │ └── cc │ │ └── demo │ │ └── base │ │ ├── GsonParamConverter.java │ │ └── bean │ │ └── User.java │ └── res │ └── values │ └── strings.xml ├── demo_component_a ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── debug │ ├── AndroidManifest.xml │ ├── java │ │ └── debug │ │ │ └── MyApp.java │ └── res │ │ ├── mipmap-hdpi │ │ ├── demo_a_ic_launcher.png │ │ └── demo_a_ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── demo_a_ic_launcher.png │ │ └── demo_a_ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── demo_a_ic_launcher.png │ │ └── demo_a_ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── demo_a_ic_launcher.png │ │ └── demo_a_ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── demo_a_ic_launcher.png │ │ └── demo_a_ic_launcher_round.png │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── java │ └── com │ │ └── billy │ │ └── cc │ │ └── demo │ │ └── component │ │ └── a │ │ ├── ActivityA.java │ │ ├── ComponentA.java │ │ ├── JsonFormat.java │ │ └── LifecycleFragment.java │ └── res │ ├── values-zh-rCN │ └── strings.xml │ └── values │ └── strings.xml ├── demo_component_b-debug.apk ├── demo_component_b ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── debug │ ├── AndroidManifest.xml │ ├── java │ │ └── debug │ │ │ └── MyApp.java │ └── res │ │ └── mipmap-xhdpi │ │ ├── demo_b_ic_launcher.png │ │ └── demo_b_ic_launcher_round.png │ ├── java │ └── com │ │ └── billy │ │ └── cc │ │ └── demo │ │ └── component │ │ └── b │ │ ├── ActivityB.java │ │ ├── ComponentB.java │ │ ├── LoginActivity.java │ │ ├── UserStateManager.java │ │ └── processor │ │ ├── CheckAndLoginProcessor.java │ │ ├── GetDataProcessor.java │ │ ├── GetLoginUserProcessor.java │ │ ├── GetNetworkDataProcessor.java │ │ ├── IActionProcessor.java │ │ ├── LoginObserverAddProcessor.java │ │ ├── LoginObserverRemoveProcessor.java │ │ ├── LoginProcessor.java │ │ ├── LogoutProcessor.java │ │ └── ShowActivityProcessor.java │ └── res │ ├── values-zh-rCN │ └── strings.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── demo_component_jsbridge ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── WebViewJavascriptBridge.js │ └── demo.html │ ├── debug │ ├── AndroidManifest.xml │ ├── java │ │ └── debug │ │ │ └── jsbridge │ │ │ ├── DebugWebActivity.java │ │ │ └── MyApp.java │ └── res │ │ ├── layout │ │ └── demo_jsbridge_demo_activity.xml │ │ ├── mipmap-xhdpi │ │ ├── demo_jsbridge_ic_launcher.png │ │ └── demo_jsbridge_ic_launcher_round.png │ │ └── values │ │ └── strings.xml │ ├── java │ └── com │ │ └── billy │ │ └── cc │ │ └── demo │ │ └── component │ │ └── jsbridge │ │ ├── BridgeWebViewHelper.java │ │ ├── JsBridgeComponent.java │ │ ├── WebActivity.java │ │ └── WebComponent.java │ └── res │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── demo_component_kt ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── debug │ ├── AndroidManifest.xml │ └── java │ │ └── debug │ │ └── kt │ │ └── MyApp.java │ └── java │ └── com │ └── billy │ └── cc │ └── demo │ └── component │ └── kt │ ├── KtComponent.kt │ └── MainActivity.kt ├── demo_interceptors ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── billy │ │ └── cc │ │ └── demo │ │ └── interceptors │ │ └── LogInterceptor.java │ └── res │ └── values │ └── strings.xml ├── docs ├── ChangeLog.md ├── Q&A.md └── Usage.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── CC.gif ├── CC_QQ.png ├── icon.png ├── lct │ ├── lct_cross_app.png │ └── lct_in_app.png ├── shoukuan_alipay.png ├── shoukuan_wechat.png └── sst │ ├── sst_call_remote_component.png │ ├── sst_cc_interceptors.png │ └── sst_get_remote_service.png ├── pools ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── billy │ └── android │ └── pools │ └── ObjPool.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | repo-local 10 | *.pyc 11 | -------------------------------------------------------------------------------- /2.0升级指南.MD: -------------------------------------------------------------------------------- 1 | # CC 1.x.x 升级到CC 2.x.x升级指南 2 | 3 | ### 1. 修改根目录build.gradle 4 | 5 | 从2.0.0开始,CC的自动注册插件从通用的[AutoRegister](https://github.com/luckybilly/AutoRegister)改为定制化的CCRegister 6 | 在根目录需要将插件地址换一下: 7 | 8 | ```groovy 9 | buildscript { 10 | dependencies { 11 | classpath 'com.billy.android:autoregister:x.x.x' //使用最新版 12 | } 13 | } 14 | ``` 15 | 改为 16 | ```groovy 17 | buildscript { 18 | dependencies { 19 | classpath 'com.billy.android:cc-register:x.x.x' //使用最新版 20 | } 21 | } 22 | ``` 23 | 24 | ### 2. 修改cc-settings.gradle 25 | 由于2.0.0开始使用定制化的插件,插件名称发生了变化,并且将许多原来在cc-settings.gradle中的配置已放在插件中完成,减少配置文件的复杂度 26 | 27 | __为了兼容以前直接apply github上cc-settings.gradle文件的用户,2.0版该gradle文件命名为cc-settings-2.gradle__ 28 | 29 | 从2.0.0版本开始,不再推荐直接依赖github上的cc-settings-2.gradle文件,推荐下载到工程根目录下使用,主要原因有: 30 | 31 | - github上的文件偶尔会在编译时出现下载失败的情况 32 | - cc-settings-2.gradle内容只包含了使用插件和添加对CC库的依赖,其它内容都已移至cc-register插件中维护 33 | - 此文件是为了给用户避免在每个组件中添加重复的代码使用,可在文件中添加自己的配置逻辑,建议在本地维护 34 | 35 | ##### 2.1 之前依赖的是github上的cc-settings.gradle文件 36 | 之前是通过`apply from: 'https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle'`方式接入,按如下方式修改: 37 | 38 | - 未新增/修改任何配置: 39 | - 下载`cc-settings-2.gradle`文件到工程根目录,并使用该文件:`apply from: rootProject.file('cc-settings-2.gradle')` 40 | - 有类似于cc-settings-demo.gradle的扩展配置文件: 41 | - 下载`cc-settings-2.gradle`文件到工程根目录,并使用该文件:`apply from: rootProject.file('cc-settings-2.gradle')` 42 | - 将之前在文件中添加的`project.ext.registerInfoList.add`改为:`ccregister.registerInfo.add`放到`cc-settings-2.gradle`文件中 43 | 44 | ##### 2.2 之前依赖的是本地cc-settings.gradle文件(从github上下载的文件放在工程根目录) 45 | 之前是通过`apply from: rootProject.file('cc-settings.gradle')`方式接入,按如下方式修改: 46 | - 未新增/修改任何配置: 47 | - 重新下载`cc-settings-2.gradle`文件并替换原来的`cc-settings.gradle`文件(或者复制文件中的内容覆盖原来的内容) 48 | - 有类似于cc-settings-demo.gradle的扩展配置文件: 49 | - 重新下载`cc-settings-2.gradle`文件并替换原来的`cc-settings.gradle`文件(或者复制文件中的内容覆盖原来的内容) 50 | - 将文件中的`project.ext.registerInfoList.add`改为:`ccregister.registerInfo.add` 51 | 52 | ### 3. 调试组件(组件独立以application方式编译运行) 53 | 2.0以前,组件切换app/lib运行方式的步骤为: 54 | - 在local.properties中修改配置: module_name=true 55 | - sync 或 clean 56 | 工程比较大的情况下,sync/clean耗时比较长,影响开发效率 57 | 借鉴[DDComponentForAndroid](https://github.com/luojilab/DDComponentForAndroid)的思路: 58 | 让所有组件可直接在Android Studio中点击绿色Run按钮独立运行,无需sync和clean 59 | 60 | 在CC 2.0.0及以上版本中: 61 | - 不在兼容在module/build.gradle中添加`ext.runAsApp=true`来实现组件独立运行 62 | - 但仍然可以通过`if (project.ext.runAsApp) { ... }`来判断当前module当前是否以app方式编译运行 63 | - 所有组件可直接在Android Studio中点击绿色Run按钮直接运行 64 | - 需要注意的是,编译主app时,需要在local.properties添加配置:`module_name=true` 将单独运行的组件从主app打包依赖项中排除 65 | - 主app module一直以application方式编译,可以用如下方式来标记: 66 | - 在module/build.gradle中添加`ext.mainApp=true` 67 | - 依赖CC的公共库module(如demo_base/demo_interceptors)一直以library方式编译,可选如下方式中的一种来实现: 68 | - 直接将CC添加到dependencies列表`api 'com.billy.android:cc:x.x.x'`,而不是apply cc-settings-2.gradle文件 69 | - 或者在apply cc-settings-2.gradle之前添加`ext.alwaysLib=true` 70 | - 默认情况下通过assemble命令打包是打apk包,若要打aar包,可用如下方式来实现: 71 | - 在local.properties中添加`assemble_aar_for_cc_component=true` 72 | 73 | ### 4. 启用App内部多进程组件调用功能(如果有需求的话) 74 | 75 | 注:默认情况下未开启App内部多进程的支持 76 | ##### 4.1 启用多进程支持 77 | 可下载`cc-settings-2.gradle`文件到本地根目录,并在文件最后添加: 78 | ```groovy 79 | ccregister.multiProcessEnabled = true 80 | ``` 81 | 并在组件类(`IComponent`实现类)上添加一个注解,标明其所在进程(在主进程运行组件无需添加注解) 82 | 83 | __注意:这样做并不是创建新的进程,而是指定此组件在哪个进程运行(如果AndroidManifest.xml中没有对应的进程,此组件无效)__ 84 | ```java 85 | public class DemoComponent implements IComponent{} //DemoComponent组件在主进程运行 86 | @SubProcess(":yourProcessName") //指定DemoComponentA组件所在的进程名称为 'packageName:yourProcessName' 87 | public class DemoComponentA implements IComponent{} 88 | @SubProcess("a.b.c") //指定DemoComponentB组件所在的进程名称为 'a.b.c' 89 | public class DemoComponentB implements IComponent{} 90 | @AllProcess //指定DemoComponentC组件在主进程和所有子进程内都存在,每个进程调用进程内部的DemoComponentC组件 91 | public class DemoComponentC implements IComponent{} 92 | ``` 93 | 94 | ##### 4.2 排除App中没有组件的进程名称 95 | 为了支持多进程通信,CCRegister插件会在编译时扫描合并后的`AndroidManifest.xml`文件中所有四大组件 96 | 收集所有子进程名称,为每个子进程生成一个`RemoteProvider`的子类并注册到`AndroidManifest.xml`文件中 97 | 98 | 这样做是因为: 99 | 如果放在Transform.transform方法中修改,在扫描完class代码之后再修改AndroidManifest.xml,无效 100 | 欢迎提PR优化 101 | 102 | 这将导致一些不含有子进程组件的进程也会生成一个没有任何作用的`RemoteProvider`的子类,这会额外带来一点点内存消耗。 103 | 虽然这种内存消耗是可以基本忽略的,但是还是可以通过如下方式添加配置来避免: 104 | ```groovy 105 | ccregister.excludeProcessNames = [':processNameA', ':processNameB'] 106 | ``` 107 | ##### 4.3 动态组件不支持`@SubProcess`及`@AllProcess`注解 108 | 动态组件在其被注册的进程中运行,如:在主进程中调用`CC.registerComponent(dynamicComponent);`,dynamicComponent将在主进程中运行 109 | 110 | ### 5 关于性能 111 | 5.1 关于CC调用日志 112 | 113 | 开启verbose日志(`CC.enableVerboseLog(true);`)后 114 | 由于会频繁调用`cc.toString()`、`ccResult.toString()`、`remoteCC.toString()`、`remoteCCResult.toString()` 115 | 会带来一定的性能损耗 116 | 故在打正式上线包时,一定要将其关闭`CC.enableVerboseLog(false);`(默认是关闭状态) 117 | 118 | 5.2 关于跨App调用 119 | 120 | 如果要调用的app不是存活状态,或者刚刚重新安装该app或杀掉重启app(这种情况常见于单组件调试时修改代码重新打包运行) 121 | 跨进程调用之前,会先唤醒该app,并通过`RemoteProvider`获取调用该进程组件的句柄:`RemoteCCService` 122 | 由于(app唤醒 -> ContentProvider初始化完成 -> 获取句柄)这个过程需要一定的时间:根据app初始化耗时不同而有差异 123 | 所以:跨app调用时,初次调用会消耗大约100ms左右的时间,但这只在开发期间有影响,不会影响正式产品 124 | 125 | -------------------------------------------------------------------------------- /android_internal/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android_internal/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileVersion 5 | buildToolsVersion rootProject.buildVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.minVersion 9 | targetSdkVersion rootProject.compileVersion 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | } 14 | compileOptions { 15 | sourceCompatibility JavaVersion.VERSION_1_7 16 | targetCompatibility JavaVersion.VERSION_1_7 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | } 29 | -------------------------------------------------------------------------------- /android_internal/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 | -------------------------------------------------------------------------------- /android_internal/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /android_internal/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | /** 4 | * 模拟系统源码,供CC中调用 5 | * @author billy.qi 6 | * @since 18/8/13 13:58 7 | */ 8 | public class ActivityThread { 9 | 10 | public static Application currentApplication() { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android_internal/src/main/java/android/app/AppGlobals.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | /** 4 | * 模拟系统源码,供CC中调用 5 | * @author billy.qi 6 | * @since 18/8/13 13:57 7 | */ 8 | public class AppGlobals { 9 | 10 | public static Application getInitialApplication() { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bintray.gradle: -------------------------------------------------------------------------------- 1 | 2 | Properties properties = new Properties() 3 | try { 4 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 5 | } catch(Exception ignored) { 6 | return 7 | } 8 | 9 | if (!properties.getProperty("is_repo_owner")) { 10 | return 11 | } 12 | 13 | if (project.hasProperty("android")) { // Android libraries 14 | task sourcesJar(type: Jar) { 15 | classifier = 'sources' 16 | from android.sourceSets.main.java.srcDirs 17 | } 18 | 19 | task javadoc(type: Javadoc) { 20 | source = android.sourceSets.main.java.srcDirs 21 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 22 | } 23 | } else { // Java libraries 24 | task sourcesJar(type: Jar, dependsOn: classes) { 25 | classifier = 'sources' 26 | from sourceSets.main.allSource 27 | } 28 | } 29 | 30 | task javadocJar(type: Jar, dependsOn: javadoc) { 31 | classifier = 'javadoc' 32 | from javadoc.destinationDir 33 | } 34 | 35 | artifacts { 36 | archives javadocJar 37 | archives sourcesJar 38 | } 39 | 40 | project.tasks.withType(Javadoc) { 41 | options.addStringOption('Xdoclint:none', '-quiet') 42 | options.addStringOption('encoding', 'UTF-8') 43 | } 44 | 45 | // Bintray 46 | apply plugin: 'com.novoda.bintray-release' 47 | publish { 48 | repoName = 'android' 49 | userOrg = properties.getProperty("bintray.userOrg") 50 | groupId = publishedGroupId 51 | artifactId = artifact 52 | publishVersion = libraryVersion 53 | desc = libraryDescription 54 | website = siteUrl 55 | } 56 | 57 | apply plugin: 'maven' 58 | uploadArchives { 59 | repositories { 60 | mavenDeployer { 61 | repository(url: uri('../repo-local')) //deploy到本地仓库 62 | pom.groupId = publishedGroupId 63 | pom.artifactId = artifact 64 | pom.version = libraryVersion + '-SNAPSHOT' 65 | } 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /bintrayUpload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | MODULE=${1} 4 | echo $MODULE 5 | 6 | . local.properties 7 | 8 | NAME=$bintray_user 9 | KEY=$bintray_apikey 10 | 11 | ./gradlew :$MODULE:bintrayUpload -PbintrayUser=$NAME -PbintrayKey=$KEY -PdryRun=false -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.51' 5 | 6 | repositories { 7 | maven{ url rootProject.file("repo-local") } 8 | maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} 9 | google() 10 | jcenter() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.0.1' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | 16 | classpath 'com.novoda:bintray-release:0.9.2' 17 | classpath 'com.billy.android:cc-register:1.1.2' 18 | 19 | // NOTE: Do not place your application dependencies here; they belong 20 | // in the individual module build.gradle files 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | maven{ url rootProject.file("repo-local") } 27 | maven { url "https://jitpack.io" } 28 | google() 29 | jcenter() 30 | } 31 | } 32 | 33 | ext { 34 | compileVersion = 28 35 | buildVersion = '28.0.3' 36 | minVersion = 8 37 | 38 | // support v7(28.0.0) minSdkVersion is 14 39 | demoMinSdkVersion = 14 40 | supportVersion = '28.0.0' 41 | } 42 | task clean(type: Delete) { 43 | delete rootProject.buildDir 44 | } 45 | -------------------------------------------------------------------------------- /cc-register/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /cc-register/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | 3 | 4 | dependencies { 5 | compile gradleApi() 6 | compile localGroovy() 7 | } 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | dependencies { 13 | compile 'com.android.tools.build:gradle:2.2.0' 14 | } 15 | 16 | ext { 17 | bintrayRepo = 'android' 18 | bintrayName = 'cc-register' 19 | 20 | publishedGroupId = 'com.billy.android' 21 | libraryName = 'CcRegister' 22 | artifact = 'cc-register' 23 | 24 | libraryDescription = 'android component caller' 25 | 26 | siteUrl = 'https://github.com/luckybilly/CC' 27 | gitUrl = 'https://github.com/luckybilly/CC.git' 28 | 29 | libraryVersion = '1.1.2' 30 | 31 | developerId = 'billy' 32 | developerName = 'billy' 33 | developerEmail = 'okkanan@hotmail.com' 34 | 35 | licenseName = 'The Apache Software License, Version 2.0' 36 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 37 | allLicenses = ["Apache-2.0"] 38 | } 39 | 40 | apply from: rootProject.file('bintray.gradle') -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/RegisterCache.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register 2 | 3 | import com.android.builder.model.AndroidProject 4 | import com.google.gson.Gson 5 | import org.gradle.api.Project 6 | 7 | import java.lang.reflect.Type 8 | 9 | /** 10 | * 文件操作辅助类 11 | * @author zhangkb 12 | * @since 2018/04/13 13 | */ 14 | class RegisterCache { 15 | final static def CACHE_INFO_DIR = "cc-register" 16 | 17 | /** 18 | * 缓存自动注册配置的文件 19 | * @param project 20 | * @return file 21 | */ 22 | static File getRegisterInfoCacheFile(Project project) { 23 | return getCacheFile(project, "register-info.json") 24 | } 25 | 26 | /** 27 | * 缓存扫描到结果的文件 28 | * @param project 29 | * @return File 30 | */ 31 | static File getRegisterCacheFile(Project project) { 32 | return getCacheFile(project, "register-cache.json") 33 | } 34 | 35 | static File getBuildTypeCacheFile(Project project) { 36 | return getCacheFile(project, "build-type.json") 37 | } 38 | 39 | private static File getCacheFile(Project project, String fileName) { 40 | String baseDir = getCacheFileDir(project) 41 | if (mkdirs(baseDir)) { 42 | return new File(baseDir + fileName) 43 | } else { 44 | throw new FileNotFoundException("Not found path:" + baseDir) 45 | } 46 | } 47 | 48 | static boolean isSameAsLastBuildType(Project project, boolean isApp) { 49 | File cacheFile = getCacheFile(project, "build-type.json") 50 | if (cacheFile.exists()) { 51 | return (cacheFile.text == 'true') == isApp 52 | } 53 | return false 54 | } 55 | 56 | static void cacheBuildType(Project project, boolean isApp) { 57 | File cacheFile = getCacheFile(project, "build-type.json") 58 | cacheFile.getParentFile().mkdirs() 59 | if (!cacheFile.exists()) 60 | cacheFile.createNewFile() 61 | cacheFile.write(isApp.toString()) 62 | } 63 | 64 | 65 | /** 66 | * 将扫描到的结果缓存起来 67 | * @param cacheFile 68 | * @param harvests 69 | */ 70 | static void cacheRegisterHarvest(File cacheFile, String harvests) { 71 | if (!cacheFile || !harvests) 72 | return 73 | cacheFile.getParentFile().mkdirs() 74 | if (!cacheFile.exists()) 75 | cacheFile.createNewFile() 76 | cacheFile.write(harvests) 77 | } 78 | 79 | private static String getCacheFileDir(Project project) { 80 | return project.getBuildDir().absolutePath + File.separator + AndroidProject.FD_INTERMEDIATES + File.separator + CACHE_INFO_DIR + File.separator 81 | } 82 | 83 | /** 84 | * 读取文件内容并创建Map 85 | * @param file 缓存文件 86 | * @param type map的类型 87 | * @return 88 | */ 89 | static Map readToMap(File file, Type type) { 90 | Map map = null 91 | if (file.exists()) { 92 | if (type) { 93 | def text = file.text 94 | if (text) { 95 | try { 96 | map = new Gson().fromJson(text, type) 97 | } catch (Exception e) { 98 | e.printStackTrace() 99 | } 100 | } 101 | } 102 | } 103 | if (map == null) { 104 | map = new HashMap() 105 | } 106 | return map 107 | } 108 | 109 | /** 110 | * 创建文件夹 111 | * @param dirPath 112 | * @return boolean 113 | */ 114 | static boolean mkdirs(String dirPath) { 115 | def baseDirFile = new File(dirPath) 116 | def isSuccess = true 117 | if (!baseDirFile.isDirectory()) { 118 | isSuccess = baseDirFile.mkdirs() 119 | } 120 | return isSuccess 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/RegisterExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register 2 | 3 | import org.gradle.api.Project 4 | 5 | /** 6 | * aop的配置信息 7 | * @author billy.qi 8 | * @since 17/3/28 11:48 9 | */ 10 | class RegisterExtension { 11 | 12 | static final String PLUGIN_NAME = RegisterPlugin.PLUGIN_NAME 13 | 14 | public ArrayList> registerInfo = [] 15 | 16 | ArrayList list = new ArrayList<>() 17 | 18 | Project project 19 | def cacheEnabled = true 20 | def multiProcessEnabled = false 21 | ArrayList excludeProcessNames = [] 22 | 23 | RegisterExtension() {} 24 | 25 | void convertConfig() { 26 | registerInfo.each { map -> 27 | RegisterInfo info = new RegisterInfo() 28 | info.interfaceName = map.get('scanInterface') 29 | def superClasses = map.get('scanSuperClasses') 30 | if (!superClasses) { 31 | superClasses = new ArrayList() 32 | } else if (superClasses instanceof String) { 33 | ArrayList superList = new ArrayList<>() 34 | superList.add(superClasses) 35 | superClasses = superList 36 | } 37 | info.superClassNames = superClasses 38 | info.initClassName = map.get('codeInsertToClassName') //代码注入的类 39 | info.initMethodName = map.get('codeInsertToMethodName') //代码注入的方法(默认为static块) 40 | info.registerMethodName = map.get('registerMethodName') //生成的代码所调用的方法 41 | info.registerClassName = map.get('registerClassName') //注册方法所在的类 42 | info.paramType = map.get('paramType') //注册方法参数类型:'object':参数为对象,'class':参数为Class类型 43 | info.include = map.get('include') 44 | info.exclude = map.get('exclude') 45 | info.init() 46 | if (info.validate()) 47 | list.add(info) 48 | else { 49 | project.logger.error(PLUGIN_NAME + ' extension error: scanInterface, codeInsertToClassName and registerMethodName should not be null\n' + info.toString()) 50 | } 51 | 52 | } 53 | 54 | if (cacheEnabled) { 55 | checkRegisterInfo() 56 | } else { 57 | deleteFile(RegisterCache.getRegisterInfoCacheFile(project)) 58 | deleteFile(RegisterCache.getRegisterCacheFile(project)) 59 | } 60 | } 61 | 62 | private void checkRegisterInfo() { 63 | def registerInfo = RegisterCache.getRegisterInfoCacheFile(project) 64 | def listInfo = list.toString() 65 | def sameInfo = false 66 | 67 | if (!registerInfo.exists()) { 68 | registerInfo.createNewFile() 69 | } else if(registerInfo.canRead()) { 70 | def info = registerInfo.text 71 | sameInfo = info == listInfo 72 | if (!sameInfo) { 73 | project.logger.error("${PLUGIN_NAME} registerInfo has been changed since project(':$project.name') last build") 74 | } 75 | } else { 76 | project.logger.error("${PLUGIN_NAME} read registerInfo error--------") 77 | } 78 | if (!sameInfo) { 79 | deleteFile(RegisterCache.getRegisterCacheFile(project)) 80 | } 81 | if (registerInfo.canWrite()) { 82 | registerInfo.write(listInfo) 83 | } else { 84 | project.logger.error("${PLUGIN_NAME} write registerInfo error--------") 85 | } 86 | } 87 | 88 | private void deleteFile(File file) { 89 | if (file.exists()) { 90 | //registerInfo 配置有改动就删除緩存文件 91 | file.delete() 92 | } 93 | } 94 | 95 | void reset() { 96 | list.each { info -> 97 | info.reset() 98 | } 99 | } 100 | 101 | @Override 102 | String toString() { 103 | StringBuilder sb = new StringBuilder(RegisterPlugin.EXT_NAME).append(' = {') 104 | .append('\n cacheEnabled = ').append(cacheEnabled) 105 | .append('\n registerInfo = [\n') 106 | def size = list.size() 107 | for (int i = 0; i < size; i++) { 108 | sb.append('\t' + list.get(i).toString().replaceAll('\n', '\n\t')) 109 | if (i < size - 1) 110 | sb.append(',\n') 111 | } 112 | sb.append('\n ]\n}') 113 | return sb.toString() 114 | } 115 | } -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/RegisterInfo.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register 2 | 3 | import java.util.regex.Pattern 4 | /** 5 | * aop的配置信息 6 | * @author billy.qi 7 | * @since 17/3/28 11:48 8 | */ 9 | class RegisterInfo { 10 | public static final String PARAM_TYPE_OBJECT = 'object' 11 | public static final String PARAM_TYPE_CLASS = 'class' 12 | public static final String PARAM_TYPE_CLASS_NAME = 'string' 13 | 14 | static final DEFAULT_EXCLUDE = [ 15 | '.*/R(\\$[^/]*)?' 16 | , '.*/BuildConfig$' 17 | ] 18 | //以下是可配置参数 19 | String interfaceName = '' 20 | ArrayList superClassNames = [] 21 | String initClassName = '' 22 | String initMethodName = '' 23 | String registerClassName = '' 24 | String registerMethodName = '' 25 | String paramType = '' //注册方法参数类型:'object':(默认值)参数为对象,'class':参数为Class类型 26 | ArrayList include = [] 27 | ArrayList exclude = [] 28 | 29 | //以下不是可配置参数 30 | ArrayList includePatterns = [] 31 | ArrayList excludePatterns = [] 32 | File fileContainsInitClass //initClassName的class文件或含有initClassName类的jar文件 33 | Set classList = new HashSet<>() 34 | 35 | RegisterInfo(){} 36 | 37 | void reset() { 38 | fileContainsInitClass = null 39 | classList.clear() 40 | } 41 | 42 | boolean validate() { 43 | return interfaceName && registerClassName && registerMethodName 44 | } 45 | 46 | //用于在console中输出日志 47 | @Override 48 | String toString() { 49 | StringBuilder sb = new StringBuilder('{') 50 | sb.append('\n\t').append('scanInterface').append('\t\t\t=\t').append(interfaceName) 51 | sb.append('\n\t').append('scanSuperClasses').append('\t\t=\t[') 52 | for (int i = 0; i < superClassNames.size(); i++) { 53 | if (i > 0) sb.append(',') 54 | sb.append(' \'').append(superClassNames.get(i)).append('\'') 55 | } 56 | sb.append(' ]') 57 | sb.append('\n\t').append('codeInsertToClassName').append('\t=\t').append(initClassName) 58 | sb.append('\n\t').append('codeInsertToMethodName').append('\t=\t').append(initMethodName) 59 | sb.append('\n\t').append('registerMethodName').append('\t\t=\tpublic static void ') 60 | .append(registerClassName).append('.').append(registerMethodName) 61 | sb.append('\n\t').append('paramType').append('\t\t\t=\t\'').append(paramType).append('\'') 62 | sb.append('\n\t').append('include').append(' = [') 63 | include.each { i -> 64 | sb.append('\n\t\t\'').append(i).append('\'') 65 | } 66 | sb.append('\n\t]') 67 | sb.append('\n\t').append('exclude').append(' = [') 68 | exclude.each { i -> 69 | sb.append('\n\t\t\'').append(i).append('\'') 70 | } 71 | sb.append('\n\t]\n}') 72 | return sb.toString() 73 | } 74 | 75 | void init() { 76 | if (include == null) include = new ArrayList<>() 77 | if (include.empty) include.add(".*") //如果没有设置则默认为include所有 78 | if (exclude == null) exclude = new ArrayList<>() 79 | if (!registerClassName) 80 | registerClassName = initClassName 81 | 82 | //将interfaceName中的'.'转换为'/' 83 | if (interfaceName) 84 | interfaceName = convertDotToSlash(interfaceName) 85 | //将superClassName中的'.'转换为'/' 86 | if (superClassNames == null) superClassNames = new ArrayList<>() 87 | for (int i = 0; i < superClassNames.size(); i++) { 88 | def superClass = convertDotToSlash(superClassNames.get(i)) 89 | superClassNames.set(i, superClass) 90 | } 91 | //注册和初始化的方法所在的类默认为同一个类 92 | initClassName = convertDotToSlash(initClassName) 93 | //默认插入到static块中 94 | if (!initMethodName) 95 | initMethodName = "" 96 | registerClassName = convertDotToSlash(registerClassName) 97 | if (!paramType) 98 | paramType = PARAM_TYPE_OBJECT 99 | //添加默认的排除项 100 | DEFAULT_EXCLUDE.each { e -> 101 | if (!exclude.contains(e)) 102 | exclude.add(e) 103 | } 104 | initPattern(include, includePatterns) 105 | initPattern(exclude, excludePatterns) 106 | } 107 | 108 | static String convertDotToSlash(String str) { 109 | return str ? str.replaceAll('\\.', '/').intern() : str 110 | } 111 | 112 | private static void initPattern(ArrayList list, ArrayList patterns) { 113 | list.each { s -> 114 | patterns.add(Pattern.compile(s)) 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/RegisterPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.billy.android.register.cc.DefaultRegistryHelper 5 | import com.billy.android.register.cc.ProjectModuleManager 6 | import com.billy.android.register.cc.generator.ManifestGenerator 7 | import org.apache.commons.io.FileUtils 8 | import org.gradle.api.Plugin 9 | import org.gradle.api.Project 10 | /** 11 | * 自动注册插件入口 12 | * @author billy.qi 13 | * @since 17/3/14 17:35 14 | */ 15 | public class RegisterPlugin implements Plugin { 16 | public static final String PLUGIN_NAME = 'cc-register' 17 | public static final String EXT_NAME = 'ccregister' 18 | 19 | @Override 20 | public void apply(Project project) { 21 | println "project(${project.name}) apply ${PLUGIN_NAME} plugin" 22 | project.extensions.create(EXT_NAME, RegisterExtension) 23 | def isApp = ProjectModuleManager.manageModule(project) 24 | performBuildTypeCache(project, isApp) 25 | if (isApp) { 26 | println "project(${project.name}) register ${PLUGIN_NAME} transform" 27 | def android = project.extensions.getByType(AppExtension) 28 | def transformImpl = new RegisterTransform(project) 29 | android.registerTransform(transformImpl) 30 | project.afterEvaluate { 31 | RegisterExtension config = init(project, transformImpl)//此处要先于transformImpl.transform方法执行 32 | if (config.multiProcessEnabled) { 33 | ManifestGenerator.generateManifestFileContent(project, config.excludeProcessNames) 34 | } 35 | } 36 | } 37 | } 38 | 39 | private static void performBuildTypeCache(Project project, boolean isApp) { 40 | if (!RegisterCache.isSameAsLastBuildType(project, isApp)) { 41 | RegisterCache.cacheBuildType(project, isApp) 42 | //兼容gradle3.0以上组件独立运行时出现的问题:https://github.com/luckybilly/CC/issues/62 43 | //切换app/lib编译时,将transform目录清除 44 | def cachedJniFile = project.file("build/intermediates/transforms/") 45 | if (cachedJniFile && cachedJniFile.exists() && cachedJniFile.isDirectory()) { 46 | FileUtils.deleteDirectory(cachedJniFile) 47 | } 48 | } 49 | } 50 | 51 | static RegisterExtension init(Project project, RegisterTransform transformImpl) { 52 | RegisterExtension extension = project.extensions.findByName(EXT_NAME) as RegisterExtension 53 | extension.project = project 54 | extension.convertConfig() 55 | DefaultRegistryHelper.addDefaultRegistry(extension.list) 56 | transformImpl.extension = extension 57 | return extension 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/ScanHarvest.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register 2 | /** 3 | * 已扫描到接口或者codeInsertToClassName jar的信息 4 | * @author zhangkb 5 | * @since 2018/04/17 6 | */ 7 | class ScanHarvest { 8 | List harvestList = new ArrayList<>() 9 | class Harvest { 10 | String className 11 | String interfaceName 12 | boolean isInitClass 13 | String processName 14 | } 15 | } -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/cc/DefaultRegistryHelper.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register.cc 2 | 3 | import com.billy.android.register.RegisterInfo 4 | 5 | /** 6 | * CC框架自身默认的注册配置辅助类 7 | */ 8 | class DefaultRegistryHelper { 9 | 10 | static void addDefaultRegistry(ArrayList list) { 11 | def exclude = ['com/billy/cc/core/component/.*'] 12 | addDefaultRegistryFor(list, 13 | 'com.billy.cc.core.component.IComponent', 14 | 'com.billy.cc.core.component.ComponentManager', 15 | 'registerComponent', 16 | RegisterInfo.PARAM_TYPE_OBJECT, 17 | exclude) 18 | addDefaultRegistryFor(list, 19 | 'com.billy.cc.core.component.IGlobalCCInterceptor', 20 | 'com.billy.cc.core.component.GlobalCCInterceptorManager', 21 | 'registerGlobalInterceptor', 22 | RegisterInfo.PARAM_TYPE_OBJECT, 23 | exclude) 24 | addDefaultRegistryFor(list, 25 | 'com.billy.cc.core.component.IParamJsonConverter', 26 | 'com.billy.cc.core.component.remote.RemoteParamUtil', 27 | 'initRemoteCCParamJsonConverter', 28 | RegisterInfo.PARAM_TYPE_OBJECT, 29 | exclude) 30 | } 31 | 32 | static void addDefaultRegistryFor(ArrayList list, String interfaceName, 33 | String codeInsertToClassName, String registerMethodName, 34 | String paramType, 35 | List exclude) { 36 | if (!list.find { it.interfaceName == RegisterInfo.convertDotToSlash(interfaceName) }) { 37 | RegisterInfo info = new RegisterInfo() 38 | info.interfaceName = interfaceName 39 | info.superClassNames = [] 40 | info.initClassName = codeInsertToClassName //代码注入的类 41 | info.registerMethodName = registerMethodName //生成的代码所调用的方法 42 | info.paramType = paramType //注册方法的类型 43 | info.exclude = exclude 44 | info.init() 45 | list.add(info) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /cc-register/src/main/groovy/com/billy/android/register/cc/generator/ProviderGenerator.groovy: -------------------------------------------------------------------------------- 1 | package com.billy.android.register.cc.generator 2 | 3 | import com.billy.android.register.RegisterTransform 4 | import org.objectweb.asm.* 5 | 6 | /** 7 | * 生成provider类 8 | */ 9 | class ProviderGenerator implements Opcodes { 10 | public static final String MULTI_PROCESS_PROVIDER_CLASS = "com/billy/cc/core/component/remote/RemoteProvider" 11 | 12 | public static final String MAIN_CC_SUB_PROCESS_FOLDER = "com/billy/cc/core/providers" 13 | 14 | static String getSubProcessProviderClassName(String processName) { 15 | processName = getSubProcessRealName(processName).replaceAll("\\.", "_") 16 | return "${MAIN_CC_SUB_PROCESS_FOLDER}/CC_Provider_${processName}" 17 | } 18 | 19 | static String getSubProcessRealName(String processName) { 20 | if (processName && processName.startsWith(":")) 21 | processName = processName.substring(1) 22 | return processName 23 | } 24 | 25 | /** 26 | * 生成RemoteProvider的子类 27 | * @param className 子类名称(根据 28 | * @param dir 29 | */ 30 | static void generateProvider(String processName, File dir) { 31 | String className = getSubProcessProviderClassName(processName) 32 | File file = new File(dir, className + ".class") 33 | if (file.exists()) { 34 | return 35 | } 36 | println("${RegisterTransform.PLUGIN_NAME} generated a provider: ${file.absolutePath}") 37 | if (!file.getParentFile().exists()) { 38 | file.getParentFile().mkdirs() 39 | } 40 | file.createNewFile() 41 | byte[] bytes = generate(className, MULTI_PROCESS_PROVIDER_CLASS) 42 | FileOutputStream fos = new FileOutputStream(file) 43 | fos.write(bytes) 44 | fos.close() 45 | } 46 | 47 | /** 48 | * 用ASM生成指定类的子类 49 | * @param className 要生成的子类名称 50 | * @param superClassName 指定的父类名称 51 | * @return 生成的字节码 52 | * @throws Exception 53 | */ 54 | static byte[] generate(String className, String superClassName) throws Exception { 55 | ClassWriter cw = new ClassWriter(0) 56 | cw.visit(52, ACC_PUBLIC + ACC_SUPER, className, null, superClassName, null) 57 | MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null) 58 | mv.visitCode() 59 | mv.visitVarInsn(ALOAD, 0) 60 | mv.visitMethodInsn(INVOKESPECIAL, superClassName, "", "()V", false) 61 | mv.visitInsn(RETURN) 62 | mv.visitMaxs(1, 1) 63 | mv.visitEnd() 64 | cw.visitEnd() 65 | return cw.toByteArray() 66 | } 67 | } -------------------------------------------------------------------------------- /cc-register/src/main/resources/META-INF/gradle-plugins/cc-register.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.billy.android.register.RegisterPlugin -------------------------------------------------------------------------------- /cc-settings-2.gradle: -------------------------------------------------------------------------------- 1 | 2 | //注: 从CC 1.x升级到CC 2.x的用户,用此文件替换原来的cc-settings.gradle的同时,需要在根目录build.gradle中将插件地址按照如下方式更换一下: 3 | // classpath 'com.billy.android:autoregister:x.x.x' -> classpath 'com.billy.android:cc-register:x.x.x' 4 | //cc-register extension: 5 | // 功能介绍: 6 | // 完成组件、拦截器及跨进程json解释器等CC库自身需要的自动注册功能 7 | // 支持新增自定义的其它自动注册功能,参考AutoRegister,用法参考cc-settings-demo.gradle 8 | project.apply plugin: 'cc-register' 9 | project.dependencies.add('api', "com.billy.android:cc:2.1.5") //用最新版 10 | 11 | //此文件是作为组件化配置的公共gradle脚本文件,在每个组件中都apply此文件,下载到工程根目录后,可以在下方添加一些自己工程中通用的配置 12 | // 可参考cc-settings-demo.gradle 13 | // 例如: 14 | // 1. 添加全局拦截器、下沉的公共类库等一些公共基础库的依赖; 15 | // 2. 添加自定义的通过cc-register实现的自动注册配置 16 | // 3. 开启app内部多进程支持 17 | // 4. 其它公共配置信息 18 | 19 | //开启app内部多进程组件调用时启用下面这行代码 20 | //文档地址:https://luckybilly.github.io/CC-website/#/manual-multi-process 21 | //ccregister.multiProcessEnabled = true 22 | 23 | //开启app内部多进程组件调用时,可以启用下方的配置排除一些进程 24 | //ccregister.excludeProcessNames = [':pushservice', ':processNameB'] 25 | 26 | //按照如下格式添加自定义注册项,可添加多个(也可每次add一个,add多次) 27 | // 文档地址: https://luckybilly.github.io/CC-website/#/manual-IActionProcessor 28 | //ccregister.registerInfo.add([ 29 | // //在自动注册组件的基础上增加:自动注册组件B的processor 30 | // 'scanInterface' : 'com.billy.cc.demo.component.b.processor.IActionProcessor' 31 | // , 'codeInsertToClassName' : 'com.billy.cc.demo.component.b.ComponentB' 32 | // , 'codeInsertToMethodName' : 'initProcessors' 33 | // , 'registerMethodName' : 'add' 34 | //]) -------------------------------------------------------------------------------- /cc-settings-demo.gradle: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // 3 | // demo使用的自定义cc-settings文件 4 | // 主要是为了演示: 5 | // 1. 自动注册组件B的processor 6 | // 2. 全局拦截器的依赖 7 | // 8 | //----------------------------------------------------------- 9 | 10 | project.apply plugin: 'cc-register' 11 | //project.dependencies.add('api', project(':cc')) //用最新版 12 | project.dependencies.add('api', "com.billy.android:cc:2.1.6") //用最新版 13 | 14 | dependencies { 15 | //其它apply当前gradle文件的module统一添加对demo_base的依赖 16 | if (project.name != 'demo_base'){ 17 | implementation project(':demo_base') 18 | } 19 | //2018-06-03新增:这里是为了示例添加全局拦截器 20 | if (project.ext.has('runAsApp') && project.ext.runAsApp) { 21 | //说明:需要兼容的情况有3种(单独组件作为app运行、打包在主app内、组件在多个app上复用但全局拦截器不同) 22 | // 为了兼容以上3种情况,建议将全局拦截器作为一个单独的module,在此处给不同app添加不同拦截器module 23 | implementation project(':demo_interceptors') 24 | } 25 | } 26 | //auto register extension: 27 | // 源码地址:https://github.com/luckybilly/AutoRegister 28 | // 功能介绍: 29 | // 在编译期扫描将打到apk包中的所有类 30 | // 将 scanInterface的实现类 或 scanSuperClasses的子类 31 | // 并在 codeInsertToClassName 类的 codeInsertToMethodName 方法中生成如下代码: 32 | // codeInsertToClassName.registerMethodName(scanInterface) 33 | // 要点: 34 | // 1. codeInsertToMethodName 若未指定,则默认为static块 35 | // 2. codeInsertToMethodName 与 registerMethodName 需要同为static或非static 36 | // 自动生成的代码示例: 37 | /* 38 | 在com.billy.app_lib_interface.CategoryManager.class文件中 39 | static 40 | { 41 | register(new CategoryA()); //scanInterface的实现类 42 | register(new CategoryB()); //scanSuperClass的子类 43 | } 44 | */ 45 | ccregister.registerInfo.add([ 46 | //在自动注册组件的基础上增加:自动注册组件B的processor 47 | 'scanInterface' : 'com.billy.cc.demo.component.b.processor.IActionProcessor' 48 | , 'codeInsertToClassName' : 'com.billy.cc.demo.component.b.ComponentB' 49 | , 'codeInsertToMethodName' : 'initProcessors' 50 | , 'registerMethodName' : 'add' 51 | ]) 52 | //也可以按照上述格式继续添加你自己的自动注册需求,俗称:搭顺风车 53 | 54 | //开启app内部多进程组件调用 55 | ccregister.multiProcessEnabled = true 56 | -------------------------------------------------------------------------------- /cc.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckybilly/CC/368b8dd029b997ee5a36fc3c6e2b51d4fd82021b/cc.keystore -------------------------------------------------------------------------------- /cc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /cc/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileVersion 5 | buildToolsVersion rootProject.buildVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.minVersion 9 | targetSdkVersion rootProject.compileVersion 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | consumerProguardFiles 'proguard-rules.pro' 14 | } 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_7 17 | targetCompatibility JavaVersion.VERSION_1_7 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | lintOptions { 27 | abortOnError false 28 | } 29 | 30 | } 31 | 32 | dependencies { 33 | //发布到bintray时,要写成compile才行 34 | compile "com.billy.android:pools:0.0.6" 35 | compileOnly "com.android.support:appcompat-v7:${rootProject.supportVersion}" 36 | compileOnly project(':android_internal') 37 | } 38 | 39 | sourceCompatibility = 1.7 40 | targetCompatibility = 1.7 41 | 42 | ext { 43 | bintrayRepo = 'android' 44 | bintrayName = 'cc' 45 | 46 | publishedGroupId = 'com.billy.android' 47 | libraryName = 'CC' 48 | artifact = 'cc' 49 | 50 | libraryDescription = 'android component caller' 51 | 52 | siteUrl = 'https://github.com/luckybilly/CC' 53 | gitUrl = 'git@github.com:luckybilly/CC.git' 54 | 55 | libraryVersion = '2.1.6' 56 | 57 | developerId = 'billy' 58 | developerName = 'billy' 59 | developerEmail = 'okkanan@hotmail.com' 60 | 61 | licenseName = 'The Apache Software License, Version 2.0' 62 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 63 | allLicenses = ["Apache-2.0"] 64 | } 65 | 66 | apply from: rootProject.file('bintray.gradle') 67 | -------------------------------------------------------------------------------- /cc/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/billy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | 27 | 28 | -dontwarn android.app.** 29 | -dontwarn android.support.** 30 | 31 | #保持实现Parcelable的类不被混淆 32 | -keep class * implements android.os.Parcelable { 33 | public static final android.os.Parcelable$Creator *; 34 | } -------------------------------------------------------------------------------- /cc/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /cc/src/main/aidl/com/billy/cc/core/component/remote/IRemoteCCService.aidl: -------------------------------------------------------------------------------- 1 | // com.billy.core.component.IProcessCrossCCService.aidl 2 | package com.billy.cc.core.component.remote; 3 | 4 | // Declare any non-default types here with import statements 5 | import com.billy.cc.core.component.remote.RemoteCC; 6 | import com.billy.cc.core.component.remote.RemoteCCResult; 7 | import com.billy.cc.core.component.remote.IRemoteCallback; 8 | 9 | interface IRemoteCCService { 10 | 11 | void call(in RemoteCC remoteCC, in IRemoteCallback callback); 12 | 13 | void cancel(String callId); 14 | 15 | void timeout(String callId); 16 | 17 | String getComponentProcessName(String componentName); 18 | } 19 | -------------------------------------------------------------------------------- /cc/src/main/aidl/com/billy/cc/core/component/remote/IRemoteCallback.aidl: -------------------------------------------------------------------------------- 1 | // com.billy.core.component.IProcessCrossCCService.aidl 2 | package com.billy.cc.core.component.remote; 3 | 4 | // Declare any non-default types here with import statements 5 | import com.billy.cc.core.component.remote.RemoteCCResult; 6 | 7 | interface IRemoteCallback { 8 | 9 | void callback(in RemoteCCResult remoteCCResult); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cc/src/main/aidl/com/billy/cc/core/component/remote/RemoteCC.aidl: -------------------------------------------------------------------------------- 1 | // IProcessCrossCC.aidl 2 | package com.billy.cc.core.component.remote; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | parcelable RemoteCC; 7 | -------------------------------------------------------------------------------- /cc/src/main/aidl/com/billy/cc/core/component/remote/RemoteCCResult.aidl: -------------------------------------------------------------------------------- 1 | // IProcessCrossCC.aidl 2 | package com.billy.cc.core.component.remote; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | parcelable RemoteCCResult; 7 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/BaseForwardInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * 转发组件调用
7 | * 注:如果需要做成全局拦截器,需要额外实现 {@link IGlobalCCInterceptor}接口 8 | * @author billy.qi 9 | * @since 18/9/2 13:40 10 | */ 11 | public abstract class BaseForwardInterceptor implements ICCInterceptor { 12 | @Override 13 | public CCResult intercept(Chain chain) { 14 | CC cc = chain.getCC(); 15 | String forwardComponentName = shouldForwardCC(cc, cc.getComponentName()); 16 | if (!TextUtils.isEmpty(forwardComponentName)) { 17 | cc.forwardTo(forwardComponentName); 18 | } 19 | return chain.proceed(); 20 | } 21 | 22 | /** 23 | * 根据当前组件调用对象获取需要转发到的组件名称 24 | * @param cc 当前组件调用对象 25 | * @param componentName 当前调用的组件名称 26 | * @return 转发的目标组件名称(为null则不执行转发) 27 | */ 28 | protected abstract String shouldForwardCC(CC cc, String componentName); 29 | } 30 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/Chain.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | /** 8 | * 组件调用链,用于管理拦截器的运行顺序 9 | * @author billy.qi 10 | */ 11 | public class Chain { 12 | private final List interceptors = new ArrayList<>(); 13 | private final CC cc; 14 | private int index; 15 | 16 | Chain(CC cc) { 17 | this.cc = cc; 18 | this.index = 0; 19 | } 20 | 21 | void addInterceptors(Collection interceptors) { 22 | if (interceptors != null && !interceptors.isEmpty()) { 23 | this.interceptors.addAll(interceptors); 24 | } 25 | } 26 | void addInterceptor(ICCInterceptor interceptor) { 27 | if (interceptor != null) { 28 | this.interceptors.add(interceptor); 29 | } 30 | } 31 | 32 | public CCResult proceed() { 33 | if (index >= interceptors.size()) { 34 | return CCResult.defaultNullResult(); 35 | } 36 | ICCInterceptor interceptor = interceptors.get(index++); 37 | //处理异常情况:如果为拦截器为null,则执行下一个 38 | if (interceptor == null) { 39 | return proceed(); 40 | } 41 | String name = interceptor.getClass().getName(); 42 | String callId = cc.getCallId(); 43 | CCResult result; 44 | if (cc.isFinished()) { 45 | //timeout, cancel, CC.sendCCResult(callId, ccResult), cc.setResult, etc... 46 | result = cc.getResult(); 47 | } else { 48 | if (CC.VERBOSE_LOG) { 49 | CC.verboseLog(callId, "start interceptor:" + name + ", cc:" + cc); 50 | } 51 | try { 52 | result = interceptor.intercept(this); 53 | } catch(Throwable e) { 54 | //防止拦截器抛出异常 55 | result = CCResult.defaultExceptionResult(e); 56 | } 57 | if (CC.VERBOSE_LOG) { 58 | CC.verboseLog(callId, "end interceptor:" + name + ".CCResult:" + result); 59 | } 60 | } 61 | //拦截器理论上不应该返回null,但为了防止意外(自定义拦截器返回null,此处保持CCResult不为null 62 | //消灭NPE 63 | if (result == null) { 64 | result = CCResult.defaultNullResult(); 65 | } 66 | cc.setResult(result); 67 | return result; 68 | } 69 | 70 | public CC getCC() { 71 | return cc; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/ChainProcessor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | 6 | /** 7 | * 启动拦截器调用链 8 | * @author billy.qi 9 | */ 10 | class ChainProcessor implements Callable { 11 | 12 | private final Chain chain; 13 | 14 | ChainProcessor(Chain chain) { 15 | this.chain = chain; 16 | } 17 | 18 | @Override 19 | public CCResult call() throws Exception { 20 | CC cc = chain.getCC(); 21 | String callId = cc.getCallId(); 22 | //从开始调用的时候就开始进行监控,也许时间设置的很短,可能都不需要执行拦截器调用链 23 | CCMonitor.addMonitorFor(cc); 24 | CCResult result; 25 | try { 26 | if (CC.VERBOSE_LOG) { 27 | int poolSize = ((ThreadPoolExecutor) ComponentManager.CC_THREAD_POOL).getPoolSize(); 28 | CC.verboseLog(callId, "process cc at thread:" 29 | + Thread.currentThread().getName() + ", pool size=" + poolSize); 30 | } 31 | if (cc.isFinished()) { 32 | //timeout, cancel, CC.sendCCResult(callId, ccResult) 33 | result = cc.getResult(); 34 | } else { 35 | try { 36 | CC.verboseLog(callId, "start interceptor chain"); 37 | result = chain.proceed(); 38 | if (CC.VERBOSE_LOG) { 39 | CC.verboseLog(callId, "end interceptor chain.CCResult:" + result); 40 | } 41 | } catch(Exception e) { 42 | result = CCResult.defaultExceptionResult(e); 43 | } 44 | } 45 | } catch(Exception e) { 46 | result = CCResult.defaultExceptionResult(e); 47 | } finally { 48 | CCMonitor.removeById(callId); 49 | } 50 | //返回的结果,永不为null,默认为CCResult.defaultNullResult() 51 | if (result == null) { 52 | result = CCResult.defaultNullResult(); 53 | } 54 | //调用请求处理完成后,CC对象中不存储CCResult 55 | cc.setResult(null); 56 | performCallback(cc, result); 57 | return result; 58 | } 59 | 60 | private static void performCallback(CC cc, CCResult result) { 61 | IComponentCallback callback = cc.getCallback(); 62 | if (CC.VERBOSE_LOG) { 63 | CC.verboseLog(cc.getCallId(), "perform callback:" + cc.getCallback() 64 | + ", CCResult:" + result); 65 | } 66 | if (callback == null) { 67 | return; 68 | } 69 | if (cc.isCallbackOnMainThread()) { 70 | ComponentManager.mainThread(new CallbackRunnable(callback, cc, result)); 71 | } else { 72 | try { 73 | callback.onResult(cc, result); 74 | } catch(Exception e) { 75 | CCUtil.printStackTrace(e); 76 | } 77 | } 78 | } 79 | private static class CallbackRunnable implements Runnable { 80 | private final CC cc; 81 | private IComponentCallback callback; 82 | private CCResult result; 83 | 84 | CallbackRunnable(IComponentCallback callback, CC cc, CCResult result) { 85 | this.cc = cc; 86 | this.callback = callback; 87 | this.result = result; 88 | } 89 | 90 | @Override 91 | public void run() { 92 | try { 93 | callback.onResult(cc, result); 94 | } catch(Exception e) { 95 | CCUtil.printStackTrace(e); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/GlobalCCInterceptorManager.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | import java.util.concurrent.CopyOnWriteArrayList; 4 | 5 | /** 6 | * 全局拦截器{@link IGlobalCCInterceptor}的管理类 7 | * @author billy.qi 8 | * @since 18/5/26 14:07 9 | */ 10 | class GlobalCCInterceptorManager { 11 | static final CopyOnWriteArrayList INTERCEPTORS = new CopyOnWriteArrayList<>(); 12 | 13 | /* 14 | 加载类时自动调用初始化:注册所有全局拦截器 (实现 IGlobalCCInterceptor 接口的类) 15 | 通过auto-register插件生成拦截器注册代码 16 | 生成的代码如下: 17 | static { 18 | registerGlobalInterceptor(new InterceptorA()); 19 | registerGlobalInterceptor(new InterceptorB()); 20 | } 21 | */ 22 | 23 | /** 24 | * 提前初始化所有全局拦截器 25 | */ 26 | static void init(){ 27 | //调用此方法时,虚拟机会加载GlobalCCInterceptorManager类 28 | //会自动执行static块中的全局拦截器注册,调用拦截器类的无参构造方法 29 | //如果不提前调用此方法,static块中的代码将在第一次进行组件调用时(cc.callXxx())执行 30 | } 31 | 32 | static void registerGlobalInterceptor(IGlobalCCInterceptor interceptor) { 33 | if (interceptor == null) { 34 | if (CC.DEBUG) { 35 | CC.logError("register global interceptor is null!"); 36 | } 37 | } else { 38 | Class clazz = interceptor.getClass(); 39 | synchronized (INTERCEPTORS) { 40 | int index = 0; 41 | for (IGlobalCCInterceptor it : INTERCEPTORS) { 42 | if (it.getClass() == clazz) { 43 | if (CC.DEBUG) { 44 | CC.logError("duplicate global interceptor:" + clazz.getName()); 45 | } 46 | return; 47 | } 48 | if (it.priority() > interceptor.priority()) { 49 | index++; 50 | } 51 | } 52 | INTERCEPTORS.add(index,interceptor); 53 | } 54 | if (CC.DEBUG) { 55 | CC.log("register global interceptor success! priority = " 56 | + interceptor.priority() + ", class = " + clazz.getName()); 57 | } 58 | } 59 | } 60 | 61 | static void unregisterGlobalInterceptor(Class clazz) { 62 | synchronized (INTERCEPTORS) { 63 | for (IGlobalCCInterceptor next : INTERCEPTORS) { 64 | if (next.getClass() == clazz) { 65 | INTERCEPTORS.remove(next); 66 | if (CC.DEBUG) { 67 | CC.log("unregister global interceptor success! class = " + clazz.getName()); 68 | } 69 | break; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/ICCInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 拦截器接口 5 | * 6 | * @author billy.qi 7 | */ 8 | public interface ICCInterceptor { 9 | 10 | /** 11 | * 拦截器方法 12 | * chain.getCC() 来获取cc对象 13 | * 调用chain.proceed()来传递调用链 14 | * 也可通过不调用chain.proceed()来中止调用链的传递 15 | * 通过cc.getParams()等方法来获取参数信息,并可以修改params 16 | * @param chain 链条 17 | * @return 调用结果 18 | */ 19 | CCResult intercept(Chain chain); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/IComponent.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 组件接口 5 | * 注意: 6 | * 1. 此接口的实现类代表的是一个组件暴露给外部调用的入口 7 | * 2. 实现类必须含有一个无参构造方法,以供自动注册插件进行代码注入 8 | * 3. 实现类有且只有一个对象会被注册到组件库中,故不能为Activity、Fragment等(可以改用动态组件注册{@link IDynamicComponent}) 9 | * 动态组件: {@link IDynamicComponent} 10 | * @author billy.qi 11 | */ 12 | public interface IComponent { 13 | 14 | /** 15 | * 定义组件名称 16 | * @return 组件的名称 17 | */ 18 | String getName(); 19 | 20 | /** 21 | * 调用此组件时执行的方法(此方法只在LocalCCInterceptor中被调用) 22 | * 注:执行完成后必须调用CC.sendCCResult(callId, CCResult.success(result)); 23 | * cc.getContext() android的context 24 | * cc.getAction() 调用的action 25 | * cc.getParams() 调用参数 26 | * cc.getCallId() 调用id,用于通过CC向调用方发送调用结果 27 | * @param cc 调用信息 28 | * @return 是否延迟回调结果 {@link CC#sendCCResult(String, CCResult)} 29 | * false:否(同步实现,在return之前回调结果) 30 | * true:是(异步实现,本次CC调用将等待回调结果) 31 | */ 32 | boolean onCall(CC cc); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/IComponentCallback.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 组件回调 5 | * @author billy.qi 6 | * @since 17/6/29 11:34 7 | */ 8 | public interface IComponentCallback { 9 | /** 10 | * call when cc is received CCResult 11 | * @param cc cc 12 | * @param result the CCResult 13 | */ 14 | void onResult(CC cc, CCResult result); 15 | } 16 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/IDynamicComponent.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 动态组件 5 | * 静态组件会在编译时代码扫描生成注册代码,将以单例方式运行; 6 | * 动态组件则可以在运行时动态地通过以下代码来进行注册与反注册 7 | * {@link CC#registerComponent(IDynamicComponent)} {@link CC#unregisterComponent(IDynamicComponent)} 8 | * @author billy.qi 9 | * @since 17/7/24 16:34 10 | */ 11 | public interface IDynamicComponent extends IComponent { 12 | } 13 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/IGlobalCCInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 全局拦截器 5 | * 注:为了防止开发阶段跨app调用组件时全局拦截器重复执行,全局拦截器应全部放在公共库中,供所有组件依赖, 6 | * 跨app调用组件时,只会执行调用方app的全局拦截器,被调用方app内的全局拦截器不执行 7 | * @author billy.qi 8 | * @since 18/5/26 10:05 9 | */ 10 | public interface IGlobalCCInterceptor extends ICCInterceptor { 11 | /** 12 | * 优先级,(可重复,相同的优先级其执行顺序将得不到保障) 13 | * @return 全局拦截器的优先级,按从大到小的顺序执行 14 | */ 15 | int priority(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/IMainThread.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 指定是否在主线程运行 5 | * @author billy.qi 6 | * @since 18/9/19 11:31 7 | */ 8 | public interface IMainThread { 9 | /** 10 | * 根据当前actionName确定组件的{@link IComponent#onCall(CC)} 方法是否在主线程运行 11 | * @param actionName 当前CC的action名称 12 | * @param cc 当前CC对象 13 | * @return 3种返回值:
14 | * null:默认状态,不固定运行的线程(在主线程同步调用时在主线程运行,其它情况下在子线程运行)
15 | * true:固定在主线程运行
16 | * false:固定子线程运行
17 | */ 18 | Boolean shouldActionRunOnMainThread(String actionName, CC cc); 19 | } 20 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/IParamJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * app间传递参数时,用于将自定义类型转换为json进行传递,并在传递成功后复原为自定义类型 5 | * @author billy.qi 6 | * @since 18/5/28 16:11 7 | */ 8 | public interface IParamJsonConverter { 9 | 10 | /** 11 | * 将json字符串转换为对象 12 | * @param json json字符串 13 | * @param clazz 类型 14 | * @param 泛型 15 | * @return json转换的对象 16 | */ 17 | T json2Object(String json, Class clazz); 18 | 19 | /** 20 | * Object to json 21 | * 22 | * @param instance 要跨app传递的对象 23 | * @return json字符串 24 | */ 25 | String object2Json(Object instance); 26 | } 27 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/ValidateInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * 检查cc是否合法 7 | * @author billy.qi 8 | */ 9 | class ValidateInterceptor implements ICCInterceptor { 10 | 11 | //-------------------------单例模式 start -------------- 12 | /** 单例模式Holder */ 13 | private static class ValidateInterceptorHolder { 14 | private static final ValidateInterceptor INSTANCE = new ValidateInterceptor(); 15 | } 16 | private ValidateInterceptor (){} 17 | /** 获取ValidateInterceptor的单例对象 */ 18 | static ValidateInterceptor getInstance() { 19 | return ValidateInterceptorHolder.INSTANCE; 20 | } 21 | //-------------------------单例模式 end -------------- 22 | 23 | @Override 24 | public CCResult intercept(Chain chain) { 25 | CC cc = chain.getCC(); 26 | String componentName = cc.getComponentName(); 27 | int code = 0; 28 | Boolean notFoundInCurApp = null; 29 | if (TextUtils.isEmpty(componentName)) { 30 | //没有指定要调用的组件名称,中止运行 31 | code = CCResult.CODE_ERROR_COMPONENT_NAME_EMPTY; 32 | } else if (cc.getContext() == null) { 33 | //context为null (没有设置context 且 CC中获取application失败) 34 | code = CCResult.CODE_ERROR_CONTEXT_NULL; 35 | } else { 36 | if (!ComponentManager.hasComponent(componentName)) { 37 | //当前进程中不包含此组件,查看一下其它进程中是否包含此组件 38 | notFoundInCurApp = TextUtils.isEmpty(ComponentManager.getComponentProcessName(componentName)); 39 | if (notFoundInCurApp && !CC.isRemoteCCEnabled()) { 40 | //本app内所有进程均没有指定的组件,并且设置了不会调用外部app的组件 41 | code = CCResult.CODE_ERROR_NO_COMPONENT_FOUND; 42 | CC.verboseLog(cc.getCallId(),"componentName=" + componentName 43 | + " is not exists and CC.enableRemoteCC is " + CC.isRemoteCCEnabled()); 44 | } 45 | } 46 | } 47 | if (code != 0) { 48 | return CCResult.error(code); 49 | } 50 | //执行完自定义拦截器,并且通过有效性校验后,再确定具体调用组件的方式 51 | if (ComponentManager.hasComponent(componentName)) { 52 | //调用当前进程中的组件 53 | chain.addInterceptor(LocalCCInterceptor.getInstance()); 54 | } else { 55 | if (notFoundInCurApp == null) { 56 | notFoundInCurApp = TextUtils.isEmpty(ComponentManager.getComponentProcessName(componentName)); 57 | } 58 | if (notFoundInCurApp) { 59 | //调用设备上安装的其它app(组件单独运行的app)中的组件 60 | chain.addInterceptor(RemoteCCInterceptor.getInstance()); 61 | } else { 62 | //调用app内部子进程中的组件 63 | chain.addInterceptor(SubProcessCCInterceptor.getInstance()); 64 | } 65 | } 66 | chain.addInterceptor(Wait4ResultInterceptor.getInstance()); 67 | // 执行上面添加的拦截器,开始执行组件调用 68 | return chain.proceed(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/Wait4ResultInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component; 2 | 3 | /** 4 | * 等待异步调用CC.sendCCResult(callId, ccResult) 5 | * @author billy.qi 6 | */ 7 | class Wait4ResultInterceptor implements ICCInterceptor { 8 | 9 | //-------------------------单例模式 start -------------- 10 | /** 单例模式Holder */ 11 | private static class Wait4ResultInterceptorHolder { 12 | private static final Wait4ResultInterceptor INSTANCE = new Wait4ResultInterceptor(); 13 | } 14 | private Wait4ResultInterceptor (){} 15 | /** 获取Wait4ResultInterceptor的单例对象 */ 16 | static Wait4ResultInterceptor getInstance() { 17 | return Wait4ResultInterceptorHolder.INSTANCE; 18 | } 19 | //-------------------------单例模式 end -------------- 20 | 21 | @Override 22 | public CCResult intercept(Chain chain) { 23 | CC cc = chain.getCC(); 24 | cc.wait4Result(); 25 | return cc.getResult(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/annotation/AllProcess.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 标记组件在所有进程内各有一个该组件对象,每个进程调用自身进程内部的对象,从而达到所有进程共用的目的(不会导致跨进程调用) 10 | * @author billy.qi 11 | * @since 18/9/14 23:21 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.TYPE}) 15 | public @interface AllProcess { 16 | } 17 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/annotation/SubProcess.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author billy.qi 10 | * @since 18/6/28 23:00 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.TYPE}) 14 | public @interface SubProcess { 15 | String value() default ""; 16 | } 17 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/BinderWrapper.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.os.IBinder; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | 7 | import com.billy.cc.core.component.RemoteCCService; 8 | 9 | /** 10 | * 封装IBinder 11 | * 用于在{@link RemoteProvider}中通过{@link RemoteCursor}跨进程传递{@link RemoteCCService}对象 12 | * @author billy.qi 13 | */ 14 | public class BinderWrapper implements Parcelable { 15 | 16 | private final IBinder binder; 17 | 18 | public BinderWrapper(IBinder binder) { 19 | this.binder = binder; 20 | } 21 | 22 | public BinderWrapper(Parcel in) { 23 | this.binder = in.readStrongBinder(); 24 | } 25 | 26 | public IBinder getBinder() { 27 | return binder; 28 | } 29 | 30 | @Override 31 | public int describeContents() { 32 | return 0; 33 | } 34 | 35 | @Override 36 | public void writeToParcel(Parcel dest, int flags) { 37 | dest.writeStrongBinder(binder); 38 | } 39 | 40 | public static final Creator CREATOR = new Creator() { 41 | @Override 42 | public BinderWrapper createFromParcel(Parcel source) { 43 | return new BinderWrapper(source); 44 | } 45 | 46 | @Override 47 | public BinderWrapper[] newArray(int size) { 48 | return new BinderWrapper[size]; 49 | } 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/RemoteCC.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.billy.cc.core.component.CC; 7 | import com.billy.cc.core.component.CCUtil; 8 | 9 | import org.json.JSONObject; 10 | 11 | import java.util.Map; 12 | 13 | import static com.billy.cc.core.component.CCUtil.put; 14 | 15 | /** 16 | * 跨进程传递的CC对象 17 | * @author billy.qi 18 | * @since 18/6/24 11:29 19 | */ 20 | public class RemoteCC implements Parcelable { 21 | 22 | private Map params; 23 | 24 | private String componentName; 25 | private String actionName; 26 | private String callId; 27 | private boolean isMainThreadSyncCall; 28 | 29 | private Map localParams; 30 | 31 | public RemoteCC(CC cc) { 32 | this(cc, false); 33 | } 34 | 35 | public RemoteCC(CC cc, boolean isMainThreadSyncCall) { 36 | this.componentName = cc.getComponentName(); 37 | this.actionName = cc.getActionName(); 38 | this.callId = cc.getCallId(); 39 | this.params = RemoteParamUtil.toRemoteMap(cc.getParams()); 40 | this.isMainThreadSyncCall = isMainThreadSyncCall; 41 | } 42 | 43 | public Map getParams() { 44 | if (localParams == null) { 45 | localParams = RemoteParamUtil.toLocalMap(params); 46 | } 47 | return localParams; 48 | } 49 | 50 | protected RemoteCC(Parcel in) { 51 | componentName = in.readString(); 52 | actionName = in.readString(); 53 | callId = in.readString(); 54 | isMainThreadSyncCall = in.readByte() != 0; 55 | params = in.readHashMap(getClass().getClassLoader()); 56 | } 57 | 58 | @Override 59 | public void writeToParcel(Parcel dest, int flags) { 60 | dest.writeString(componentName); 61 | dest.writeString(actionName); 62 | dest.writeString(callId); 63 | dest.writeByte((byte) (isMainThreadSyncCall ? 1 : 0)); 64 | dest.writeMap(params); 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | JSONObject json = new JSONObject(); 70 | put(json, "componentName", componentName); 71 | put(json, "actionName", actionName); 72 | put(json, "callId", callId); 73 | put(json, "isMainThreadSyncCall", isMainThreadSyncCall); 74 | put(json, "params", CCUtil.convertToJson(params)); 75 | return json.toString(); 76 | } 77 | 78 | @Override 79 | public int describeContents() { 80 | return 0; 81 | } 82 | 83 | public static final Creator CREATOR = new Creator() { 84 | @Override 85 | public RemoteCC createFromParcel(Parcel in) { 86 | return new RemoteCC(in); 87 | } 88 | 89 | @Override 90 | public RemoteCC[] newArray(int size) { 91 | return new RemoteCC[size]; 92 | } 93 | }; 94 | 95 | public String getComponentName() { 96 | return componentName; 97 | } 98 | 99 | public void setComponentName(String componentName) { 100 | this.componentName = componentName; 101 | } 102 | 103 | public String getActionName() { 104 | return actionName; 105 | } 106 | 107 | public void setActionName(String actionName) { 108 | this.actionName = actionName; 109 | } 110 | 111 | public String getCallId() { 112 | return callId; 113 | } 114 | 115 | public void setCallId(String callId) { 116 | this.callId = callId; 117 | } 118 | 119 | public boolean isMainThreadSyncCall() { 120 | return isMainThreadSyncCall; 121 | } 122 | 123 | public void setMainThreadSyncCall(boolean mainThreadSyncCall) { 124 | isMainThreadSyncCall = mainThreadSyncCall; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/RemoteCCResult.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.billy.cc.core.component.CCResult; 7 | import com.billy.cc.core.component.CCUtil; 8 | 9 | import org.json.JSONObject; 10 | 11 | import java.util.Map; 12 | 13 | import static com.billy.cc.core.component.CCUtil.put; 14 | 15 | /** 16 | * 用于跨进程传递的CCResult 17 | * @author billy.qi 18 | * @since 18/6/3 02:22 19 | */ 20 | public class RemoteCCResult implements Parcelable { 21 | 22 | private Map data; 23 | 24 | private boolean success; 25 | private String errorMessage; 26 | private int code; 27 | 28 | public RemoteCCResult(CCResult result) { 29 | setCode(result.getCode()); 30 | setErrorMessage(result.getErrorMessage()); 31 | setSuccess(result.isSuccess()); 32 | data = RemoteParamUtil.toRemoteMap(result.getDataMap()); 33 | } 34 | 35 | public CCResult toCCResult() { 36 | CCResult result = new CCResult(); 37 | result.setCode(getCode()); 38 | result.setErrorMessage(getErrorMessage()); 39 | result.setSuccess(isSuccess()); 40 | result.setDataMap(RemoteParamUtil.toLocalMap(data)); 41 | return result; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | JSONObject json = new JSONObject(); 47 | put(json, "success", success); 48 | put(json, "code", code); 49 | put(json, "errorMessage", errorMessage); 50 | put(json, "data", CCUtil.convertToJson(data)); 51 | return json.toString(); 52 | } 53 | 54 | public boolean isSuccess() { 55 | return success; 56 | } 57 | 58 | public void setSuccess(boolean success) { 59 | this.success = success; 60 | } 61 | 62 | public String getErrorMessage() { 63 | return errorMessage; 64 | } 65 | 66 | public void setErrorMessage(String errorMessage) { 67 | this.errorMessage = errorMessage; 68 | } 69 | 70 | public int getCode() { 71 | return code; 72 | } 73 | 74 | public void setCode(int code) { 75 | this.code = code; 76 | } 77 | 78 | @Override 79 | public int describeContents() { 80 | return 0; 81 | } 82 | 83 | @Override 84 | public void writeToParcel(Parcel dest, int flags) { 85 | dest.writeByte((byte) (success ? 1 : 0)); 86 | dest.writeString(errorMessage); 87 | dest.writeInt(code); 88 | dest.writeMap(data); 89 | } 90 | 91 | private RemoteCCResult(Parcel in) { 92 | success = in.readByte() != 0; 93 | errorMessage = in.readString(); 94 | code = in.readInt(); 95 | data = in.readHashMap(getClass().getClassLoader()); 96 | } 97 | 98 | public static final Creator CREATOR = new Creator() { 99 | @Override 100 | public RemoteCCResult createFromParcel(Parcel in) { 101 | return new RemoteCCResult(in); 102 | } 103 | 104 | @Override 105 | public RemoteCCResult[] newArray(int size) { 106 | return new RemoteCCResult[size]; 107 | } 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/RemoteConnection.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.app.Application; 4 | import android.content.Intent; 5 | import android.content.pm.ActivityInfo; 6 | import android.content.pm.PackageManager; 7 | import android.content.pm.ResolveInfo; 8 | import android.os.SystemClock; 9 | 10 | import com.billy.cc.core.component.CC; 11 | import com.billy.cc.core.component.CCUtil; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * @author billy.qi 18 | * @since 18/7/3 22:39 19 | */ 20 | public class RemoteConnection { 21 | 22 | /** 23 | * 获取当前设备上安装的可供跨app调用组件的App列表 24 | * @return 包名集合 25 | */ 26 | public static List scanComponentApps() { 27 | Application application = CC.getApplication(); 28 | String curPkg = application.getPackageName(); 29 | PackageManager pm = application.getPackageManager(); 30 | // 查询所有已经安装的应用程序 31 | Intent intent = new Intent("action.com.billy.cc.connection"); 32 | List list = pm.queryIntentActivities(intent, 0); 33 | List packageNames = new ArrayList<>(); 34 | for (ResolveInfo info : list) { 35 | ActivityInfo activityInfo = info.activityInfo; 36 | String packageName = activityInfo.packageName; 37 | if (curPkg.equals(packageName)) { 38 | continue; 39 | } 40 | if (tryWakeup(packageName)) { 41 | packageNames.add(packageName); 42 | } 43 | } 44 | return packageNames; 45 | } 46 | 47 | /** 48 | * 检测组件App是否存在,并顺便唤醒App 49 | * @param packageName app的包名 50 | * @return 成功与否(true:app存在,false: 不存在) 51 | */ 52 | public static boolean tryWakeup(String packageName) { 53 | long time = SystemClock.elapsedRealtime(); 54 | Intent intent = new Intent(); 55 | intent.setClassName(packageName, RemoteConnectionActivity.class.getName()); 56 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 57 | try { 58 | CC.getApplication().startActivity(intent); 59 | CC.log("wakeup remote app '%s' success. time=%d", packageName, (SystemClock.elapsedRealtime() - time)); 60 | return true; 61 | } catch(Exception e) { 62 | CCUtil.printStackTrace(e); 63 | CC.log("wakeup remote app '%s' failed. time=%d", packageName, (SystemClock.elapsedRealtime() - time)); 64 | return false; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/RemoteConnectionActivity.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | 7 | /** 8 | * 用于跨app探索组件及唤醒app的activity 9 | * @author billy.qi 10 | * @since 18/7/2 23:38 11 | */ 12 | public class RemoteConnectionActivity extends Activity { 13 | 14 | @Override 15 | protected void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | finish(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/RemoteCursor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.database.Cursor; 4 | import android.database.MatrixCursor; 5 | import android.os.Bundle; 6 | import android.os.IBinder; 7 | 8 | import com.billy.cc.core.component.RemoteCCService; 9 | 10 | /** 11 | * 用于跨进程通信的游标,通过Extras跨进程传递bundle,在bundle中传递IBinder 12 | * @author billy.qi 13 | * @since 18/6/24 11:40 14 | */ 15 | public class RemoteCursor extends MatrixCursor { 16 | private static final String KEY_BINDER_WRAPPER = "BinderWrapper"; 17 | 18 | static final String[] DEFAULT_COLUMNS = {"cc"}; 19 | 20 | //-------------------------单例模式 start -------------- 21 | /** 单例模式Holder */ 22 | private static class CCCursorHolder { 23 | private static final RemoteCursor INSTANCE = new RemoteCursor(DEFAULT_COLUMNS, RemoteCCService.getInstance()); 24 | } 25 | private RemoteCursor(String[] columnNames, IBinder binder) { 26 | super(columnNames); 27 | binderExtras.putParcelable(KEY_BINDER_WRAPPER, new BinderWrapper(binder)); 28 | } 29 | /** 获取CCCursor在当前进程中的单例对象 */ 30 | public static RemoteCursor getInstance() { 31 | return RemoteCursor.CCCursorHolder.INSTANCE; 32 | } 33 | //-------------------------单例模式 end -------------- 34 | 35 | private Bundle binderExtras = new Bundle(); 36 | 37 | @Override 38 | public Bundle getExtras() { 39 | return binderExtras; 40 | } 41 | 42 | public static IRemoteCCService getRemoteCCService(Cursor cursor) { 43 | if (null == cursor) { 44 | return null; 45 | } 46 | Bundle bundle = cursor.getExtras(); 47 | bundle.setClassLoader(BinderWrapper.class.getClassLoader()); 48 | BinderWrapper binderWrapper = bundle.getParcelable(KEY_BINDER_WRAPPER); 49 | if (binderWrapper != null) { 50 | IBinder binder = binderWrapper.getBinder(); 51 | return IRemoteCCService.Stub.asInterface(binder); 52 | } 53 | return null; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /cc/src/main/java/com/billy/cc/core/component/remote/RemoteProvider.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.core.component.remote; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentValues; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Process; 8 | 9 | import com.billy.cc.core.component.CC; 10 | 11 | import static android.os.Binder.getCallingUid; 12 | 13 | /** 14 | * 通过ContentProvider实现跨进程通信
15 | * 通信原理:
16 | * 1. 通过ContentProvider的query获取RemoteCursor单例对象
17 | * 2. 通过RemoteCursor.getExtras()获取bundle
18 | * 3. 通过bundle传递Parcelable
19 | * 4. 通过Parcelable封装IBinder (RemoteCCService)
20 | * 5. 最终跨进程将RemoteCCService对象传递给调用方
21 | * @author billy.qi 22 | * @since 18/6/24 11:38 23 | */ 24 | public class RemoteProvider extends ContentProvider { 25 | 26 | public static final String[] PROJECTION_MAIN = {"cc"}; 27 | 28 | public static final String URI_SUFFIX = "com.billy.cc.core.remote"; 29 | 30 | @Override 31 | public boolean onCreate() { 32 | CC.log("RemoteProvider onCreated! class:%s", this.getClass().getName()); 33 | return false; 34 | } 35 | 36 | @Override 37 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { 38 | if (CC.isRemoteCCEnabled() || getCallingUid() == Process.myUid()) { 39 | //获取当前ContentProvider所在进程中的RemoteCursor单例对象 40 | return RemoteCursor.getInstance(); 41 | } 42 | return null; 43 | } 44 | 45 | @Override 46 | public String getType(Uri uri) { 47 | return null; 48 | } 49 | 50 | @Override 51 | public Uri insert(Uri uri, ContentValues values) { 52 | return null; 53 | } 54 | 55 | @Override 56 | public int delete(Uri uri, String selection, String[] selectionArgs) { 57 | return 0; 58 | } 59 | 60 | @Override 61 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 62 | return 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /demo-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckybilly/CC/368b8dd029b997ee5a36fc3c6e2b51d4fd82021b/demo-debug.apk -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | ext.mainApp = true //设置为true,表示此module为主app module,一直以application方式编译 2 | apply from: rootProject.file('cc-settings-demo.gradle') 3 | 4 | android { 5 | compileSdkVersion rootProject.compileVersion 6 | buildToolsVersion rootProject.buildVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.demoMinSdkVersion // support v7(28.0.0) minSdkVersion is 14 10 | targetSdkVersion rootProject.compileVersion 11 | applicationId "com.billy.cc.demo" 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | 16 | signingConfigs { 17 | releaseconfig { 18 | storeFile rootProject.file('cc.keystore') 19 | storePassword 'cc-demo' 20 | keyAlias 'cc-demo' 21 | keyPassword 'cc-demo' 22 | } 23 | } 24 | buildTypes { 25 | 26 | debug { 27 | minifyEnabled false 28 | debuggable true 29 | signingConfig signingConfigs.releaseconfig 30 | } 31 | 32 | release { 33 | minifyEnabled true 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | zipAlignEnabled true 36 | signingConfig signingConfigs.releaseconfig 37 | debuggable false 38 | shrinkResources true 39 | } 40 | } 41 | lintOptions { 42 | abortOnError false 43 | } 44 | } 45 | 46 | dependencies { 47 | implementation "com.android.support:appcompat-v7:${rootProject.supportVersion}" 48 | implementation 'com.google.android:flexbox:0.3.0' 49 | 50 | //Notice:组件之间不要互相依赖,只在主app module依赖其它组件 51 | //使用addComponent(源码在cc-settings-2.gradle中)添加对组件的依赖可以达到如下效果: 52 | // 1. 组件切换library和application方式编译时只需在local.properties中进行设置,不需要修改app module中的依赖列表 53 | // 且运行主app module时会自动将【设置为以app方式编译的组件module】从依赖列表中排除 54 | // 2. 避免调试时切换library和application方式修改主app中的依赖项被误提交到代码仓库,导致jenkins集成打包时功能缺失 55 | // 3. 避免直接调用组件中的代码及资源 56 | //对组件库的依赖格式: addComponent dependencyName [, realDependency] 57 | // dependencyName: 组件库的名称,推荐直接使用使用module的名称 58 | // realDependency(可选): 组件库对应的实际依赖,可以是module依赖,也可以是maven依赖 59 | // 如果未配置realDependency,将自动依赖 project(":$dependencyName") 60 | // realDependency可以为如下2种中的一种: 61 | // module依赖 : project(':demo_component_b') //如果module名称跟dependencyName相同,可省略(推荐) 62 | // maven依赖 : 'com.billy.demo:demoB:1.1.0' //如果使用了maven私服,请使用此方式 63 | addComponent 'demo_component_a' 64 | addComponent 'demo_component_kt' 65 | addComponent 'demo_component_jsbridge' 66 | 67 | //单独运行demo_component_b时,只需在local.properties中添加demo_component_b=true即可 68 | // 此处无需修改,再次运行demo:assembleXxx时将不会把demo_component_b打包进apk 69 | addComponent 'demo_component_b', project(':demo_component_b') // 这里参数2可以省略 70 | } 71 | -------------------------------------------------------------------------------- /demo/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 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/src/main/java/com/billy/cc/demo/JsonFormat.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.demo; 2 | 3 | /** 4 | * json格式化工具 5 | */ 6 | public class JsonFormat { 7 | 8 | /** 9 | * 默认每次缩进两个空格 10 | */ 11 | private static final String EMPTY = " "; 12 | 13 | public static String format(String json){ 14 | try { 15 | int empty=0; 16 | char[]chs=json.toCharArray(); 17 | StringBuilder stringBuilder=new StringBuilder(); 18 | for (int i = 0; i < chs.length;) { 19 | //若是双引号,则为字符串,下面if语句会处理该字符串 20 | if (chs[i]=='\"') { 21 | 22 | stringBuilder.append(chs[i]); 23 | i++; 24 | //查找字符串结束位置 25 | for ( ; i < chs.length;) { 26 | //如果当前字符是双引号,且前面有连续的偶数个\,说明字符串结束 27 | if ( chs[i]=='\"'&&isDoubleSerialBackslash(chs,i-1)) { 28 | stringBuilder.append(chs[i]); 29 | i++; 30 | break; 31 | } else{ 32 | stringBuilder.append(chs[i]); 33 | i++; 34 | } 35 | 36 | } 37 | }else if (chs[i]==',') { 38 | stringBuilder.append(',').append('\n').append(getEmpty(empty)); 39 | 40 | i++; 41 | }else if (chs[i]=='{'||chs[i]=='[') { 42 | empty++; 43 | stringBuilder.append(chs[i]).append('\n').append(getEmpty(empty)); 44 | 45 | i++; 46 | }else if (chs[i]=='}'||chs[i]==']') { 47 | empty--; 48 | stringBuilder.append('\n').append(getEmpty(empty)).append(chs[i]); 49 | 50 | i++; 51 | }else { 52 | stringBuilder.append(chs[i]); 53 | i++; 54 | } 55 | } 56 | return stringBuilder.toString(); 57 | } catch (Exception e) { 58 | e.printStackTrace(); 59 | return json; 60 | } 61 | 62 | } 63 | private static boolean isDoubleSerialBackslash(char[] chs, int i) { 64 | int count=0; 65 | for (int j = i; j >-1; j--) { 66 | if (chs[j]=='\\') { 67 | count++; 68 | }else{ 69 | return count%2==0; 70 | } 71 | } 72 | 73 | return count%2==0; 74 | } 75 | /** 76 | * 缩进 77 | * @param count 缩进等级 78 | * @return 缩进的空格 79 | */ 80 | private static String getEmpty(int count){ 81 | StringBuilder stringBuilder=new StringBuilder(); 82 | for (int i = 0; i < count; i++) { 83 | stringBuilder.append(EMPTY) ; 84 | } 85 | 86 | return stringBuilder.toString(); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /demo/src/main/java/com/billy/cc/demo/MissYouInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.demo; 2 | 3 | import android.util.Log; 4 | 5 | import com.billy.cc.core.component.CC; 6 | import com.billy.cc.core.component.CCResult; 7 | import com.billy.cc.core.component.Chain; 8 | import com.billy.cc.core.component.ICCInterceptor; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * 将返回结果中的字符串"billy"替换为"billy miss you" 14 | * @author billy.qi 15 | */ 16 | public class MissYouInterceptor implements ICCInterceptor { 17 | 18 | /** 19 | * 拦截器的拦截方法 20 | * 调用chain.proceed()方法让调用链继续向下执行 21 | * 在调用chain.proceed()方法之前,可以修改cc的参数 22 | * 在调用chain.proceed()方法之后,可以修改返回结果 23 | * @param chain 拦截器调用链 24 | * @return 调用结果对象 25 | */ 26 | @Override 27 | public CCResult intercept(Chain chain) { 28 | //获取调用链处理的cc对象 29 | CC cc = chain.getCC(); 30 | Map params = cc.getParams(); 31 | //可以在拦截器中修改params,此处只是仅仅用来打印一下 32 | Log.i("MissYouInterceptor", "callId=" + cc.getCallId() + ", params=" + params); 33 | //传递拦截器调用链 34 | // 不调用chain.proceed()方法, 可以中止调用链的继续传递(中止组件调用) 35 | // 譬如:埋点组件调用网络请求组件发送埋点信息 36 | // 1. 可以添加一个本地缓存拦截器:无网络时直接缓存本地数据库,不调用埋点组件; 37 | // 2. 埋点返回结果 ccResult.isSuccess()为false,也缓存到本地数据库 38 | CCResult result = chain.proceed(); 39 | //对返回的结果进行修改 40 | if (result.isSuccess()) { 41 | Map data = result.getDataMap(); 42 | if (data != null) { 43 | for (String key : data.keySet()) { 44 | Object value = data.get(key); 45 | //将字符串中的"billy"替换为"billy miss you" 46 | if (value != null && value instanceof String) { 47 | String str = (String) value; 48 | str = str.replaceAll("billy", "billy miss you"); 49 | data.put(key, str); 50 | } 51 | } 52 | } 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /demo/src/main/java/com/billy/cc/demo/MyApp.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.demo; 2 | 3 | import android.app.Application; 4 | 5 | import com.billy.cc.core.component.CC; 6 | 7 | /** 8 | * @author billy.qi 9 | * @since 17/11/20 19:28 10 | */ 11 | public class MyApp extends Application { 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | CC.enableVerboseLog(true); 17 | CC.enableDebug(true); 18 | CC.enableRemoteCC(true); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleActivity.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.demo.lifecycle; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.IdRes; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentTransaction; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.billy.cc.core.component.CC; 14 | import com.billy.cc.core.component.CCResult; 15 | import com.billy.cc.core.component.IComponentCallback; 16 | import com.billy.cc.demo.JsonFormat; 17 | import com.billy.cc.demo.R; 18 | 19 | /** 20 | * 测试activity.onDestroy和fragment.onDestroy时,CC自动cancel 21 | * @author billy.qi 22 | * @since 17/12/9 11:06 23 | */ 24 | public class LifecycleActivity extends AppCompatActivity implements View.OnClickListener { 25 | 26 | private TextView textView; 27 | Fragment fragment; 28 | 29 | @Override 30 | protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_lifecycle); 33 | textView = (TextView) findViewById(R.id.console); 34 | addOnClickListeners(R.id.start_cc 35 | , R.id.finish_activity 36 | , R.id.replace_fragment 37 | , R.id.call_fragment_method 38 | ); 39 | 40 | } 41 | private void showResult(CCResult result) { 42 | String text = JsonFormat.format(result.toString()); 43 | textView.setText(text); 44 | Toast.makeText(CC.getApplication(), text, Toast.LENGTH_SHORT).show(); 45 | } 46 | 47 | private void addOnClickListeners(@IdRes int... ids) { 48 | if (ids != null) { 49 | for (@IdRes int id : ids) { 50 | findViewById(id).setOnClickListener(this); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | public void onClick(View v) { 57 | 58 | textView.setText(""); 59 | switch (v.getId()) { 60 | case R.id.start_cc: 61 | CC.obtainBuilder("ComponentB") 62 | .setActionName("getNetworkData") 63 | .cancelOnDestroyWith(this) 64 | .build() 65 | .callAsyncCallbackOnMainThread(printResultCallback); 66 | break; 67 | case R.id.finish_activity: 68 | finish(); 69 | break; 70 | case R.id.replace_fragment: 71 | //demo for get fragment from other component 72 | CC.obtainBuilder("demo.ComponentA") 73 | .setActionName("getLifecycleFragment") 74 | .build() 75 | .callAsyncCallbackOnMainThread(fragmentCallback); 76 | break; 77 | case R.id.call_fragment_method: 78 | //send message to current fragment 79 | CC.obtainBuilder("demo.ComponentA") 80 | .setActionName("lifecycleFragment.addText") 81 | .addParam("fragment", fragment) 82 | .addParam("text", "text from LifecycleActivity") 83 | .build() 84 | .callAsyncCallbackOnMainThread(printResultCallback); 85 | break; 86 | default: 87 | break; 88 | } 89 | } 90 | 91 | IComponentCallback fragmentCallback = new IComponentCallback() { 92 | @Override 93 | public void onResult(CC cc, CCResult result) { 94 | if (result.isSuccess()) { 95 | //call component a for LifecycleFragment success 96 | Fragment fragment = result.getDataItemWithNoKey(); 97 | if (fragment != null) { 98 | showFragment(fragment); 99 | } 100 | } else { 101 | showResult(result); 102 | } 103 | } 104 | }; 105 | private void showFragment(Fragment fragment) { 106 | if (fragment != null) { 107 | this.fragment = fragment; 108 | FragmentTransaction trans = getSupportFragmentManager().beginTransaction(); 109 | trans.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left); 110 | trans.replace(R.id.fragment, fragment); 111 | trans.commit(); 112 | } 113 | } 114 | 115 | IComponentCallback printResultCallback = new IComponentCallback() { 116 | @Override 117 | public void onResult(CC cc, CCResult result) { 118 | showResult(result); 119 | } 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleComponent.java: -------------------------------------------------------------------------------- 1 | package com.billy.cc.demo.lifecycle; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.billy.cc.core.component.CC; 8 | import com.billy.cc.core.component.CCResult; 9 | import com.billy.cc.core.component.IComponent; 10 | import com.billy.cc.core.component.IComponentCallback; 11 | 12 | /** 13 | * demo for AOP
14 | * login first before startActivity 15 | * @author billy.qi 16 | */ 17 | public class LifecycleComponent implements IComponent { 18 | @Override 19 | public String getName() { 20 | return "demo.lifecycle"; 21 | } 22 | 23 | @Override 24 | public boolean onCall(CC cc) { 25 | checkLoginAndTurnToLifecycle(cc); 26 | return true; 27 | } 28 | 29 | private void checkLoginAndTurnToLifecycle(final CC cc) { 30 | CC.obtainBuilder("ComponentB") 31 | .setActionName("checkAndLogin") 32 | .build() 33 | .callAsync(new IComponentCallback() { 34 | @Override 35 | public void onResult(CC loginCC, CCResult result) { 36 | CCResult ccResult; 37 | if (result.isSuccess()) { 38 | ccResult = CCResult.success(); 39 | openLifecycleActivity(cc); 40 | } else { 41 | ccResult = result; 42 | } 43 | CC.sendCCResult(cc.getCallId(), ccResult); 44 | } 45 | }); 46 | } 47 | 48 | private void openLifecycleActivity(CC cc) { 49 | Context context = cc.getContext(); 50 | Intent intent = new Intent(context, LifecycleActivity.class); 51 | if (!(context instanceof Activity)) { 52 | // context maybe an application object if caller dose not setContext 53 | // or call across apps 54 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 55 | } 56 | intent.putExtra("callId", cc.getCallId()); 57 | context.startActivity(intent); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /demo/src/main/res/anim/enter_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /demo/src/main/res/anim/exit_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_lifecycle.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 21 | 22 |
23 | 24 | 25 | 86 | -------------------------------------------------------------------------------- /demo_component_jsbridge/src/main/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo_component_jsbridge/src/main/debug/java/debug/jsbridge/DebugWebActivity.java: -------------------------------------------------------------------------------- 1 | package debug.jsbridge; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.text.TextUtils; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | import android.widget.Toast; 10 | 11 | import com.billy.cc.core.component.CC; 12 | import com.billy.cc.demo.component.jsbridge.R; 13 | 14 | /** 15 | * jsBridge组件的开发调试页面 16 | * @author billy.qi 17 | * @since 18/9/15 10:48 18 | */ 19 | public class DebugWebActivity extends AppCompatActivity { 20 | 21 | private EditText urlEditText; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.demo_jsbridge_demo_activity); 27 | urlEditText = (EditText) findViewById(R.id.et_url); 28 | findViewById(R.id.btn_load).setOnClickListener(new View.OnClickListener() { 29 | @Override 30 | public void onClick(View v) { 31 | String url = urlEditText.getText().toString().trim(); 32 | if (TextUtils.isEmpty(url)) { 33 | Toast.makeText(DebugWebActivity.this, "please input url!", Toast.LENGTH_SHORT).show(); 34 | } else { 35 | CC.obtainBuilder("webComponent") 36 | .setActionName("openUrl") 37 | .setContext(DebugWebActivity.this) 38 | .addParam("url", url) 39 | .build().call(); 40 | } 41 | } 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo_component_jsbridge/src/main/debug/java/debug/jsbridge/MyApp.java: -------------------------------------------------------------------------------- 1 | package debug.jsbridge; 2 | 3 | import android.app.Application; 4 | 5 | import com.billy.cc.core.component.CC; 6 | 7 | /** 8 | * @author billy.qi 9 | * @since 18/9/15 10:38 10 | */ 11 | public class MyApp extends Application { 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | CC.enableVerboseLog(true); 16 | CC.enableDebug(true); 17 | CC.enableRemoteCC(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo_component_jsbridge/src/main/debug/res/layout/demo_jsbridge_demo_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 |