plugin选择性重载基础plugin扩张功能

This commit is contained in:
yummylau
2019-10-25 11:00:50 +08:00
26 changed files with 1804 additions and 368 deletions

7
.gitignore vendored
View File

@@ -1,12 +1,7 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea
.DS_Store
/build
/captures

View File

@@ -2,7 +2,7 @@ apply from: "./gradleScript/config.gradle"
buildscript {
ext.kotlin_version = '1.3.50'
ext.component_version = '1.0.4'
ext.component_version = '1.0.4-beta'
repositories {
google()

View File

@@ -4,7 +4,7 @@ apply plugin: 'maven'
group = 'com.effective.plugins'
archivesBaseName = 'component'
version = '1.0.4'
version = '1.0.4-beta'
//设置本地发布路径
uploadArchives {

View File

@@ -1,7 +1,7 @@
package com.plugin.component
import com.plugin.component.extension.ComponentExtension
import com.plugin.component.plugin.BasePlugin
import com.plugin.component.plugin.AbsPlugin
import com.plugin.component.plugin.DebugPlugin
import com.plugin.component.plugin.PinPlugin
import com.plugin.component.plugin.SdkPlugin
@@ -16,11 +16,11 @@ import org.gradle.api.Project
* 组件插件入口
* created by yummylau 2019/08/09
*/
class ComponentPlugin implements Plugin<Project>,BasePlugin{
class ComponentPlugin extends AbsPlugin implements Plugin<Project> {
private BasePlugin sdk = new SdkPlugin()
private BasePlugin debug = new DebugPlugin()
private BasePlugin pins = new PinPlugin()
private AbsPlugin sdk = new SdkPlugin()
private AbsPlugin debug = new DebugPlugin()
private AbsPlugin pins = new PinPlugin()
private ComponentExtension componentExtension
@Override
@@ -28,21 +28,35 @@ class ComponentPlugin implements Plugin<Project>,BasePlugin{
if (project == project.rootProject) {
componentExtension = project.getExtensions().create(Constants.COMPONENT, ComponentExtension, project)
initExtension(componentExtension)
evaluateRoot(project)
evaluate(project, true)
project.afterEvaluate {
afterEvaluateRoot(project)
afterEvaluate(project, true)
}
project.gradle.projectsEvaluated {
afterAllEvaluate()
}
} else {
evaluateChild(project)
evaluate(project, false)
project.afterEvaluate {
afterEvaluateChild(project)
afterEvaluate(project, false)
}
}
}
@Override
void evaluate(Project project, boolean isRoot) {
sdk.evaluate(project, isRoot)
debug.evaluate(project, isRoot)
pins.evaluate(project, isRoot)
}
@Override
void afterEvaluate(Project project, boolean isRoot) {
sdk.afterEvaluate(project, isRoot)
debug.afterEvaluate(project, isRoot)
pins.afterEvaluate(project, isRoot)
}
@Override
void initExtension(ComponentExtension componentExtension) {
sdk.initExtension(componentExtension)
@@ -50,34 +64,6 @@ class ComponentPlugin implements Plugin<Project>,BasePlugin{
pins.initExtension(componentExtension)
}
@Override
void evaluateChild(Project child) {
sdk.evaluateChild(child)
debug.evaluateChild(child)
pins.evaluateChild(child)
}
@Override
void afterEvaluateChild(Project child) {
sdk.afterEvaluateChild(child)
debug.afterEvaluateChild(child)
pins.afterEvaluateChild(child)
}
@Override
void evaluateRoot(Project root) {
sdk.evaluateRoot(root)
debug.evaluateRoot(root)
pins.evaluateRoot(root)
}
@Override
void afterEvaluateRoot(Project root) {
Runtimes.initRuntimeConfiguration(root, componentExtension)
sdk.afterEvaluateRoot(root)
debug.afterEvaluateRoot(root)
pins.afterEvaluateRoot(root)
}
@Override
void afterAllEvaluate() {

View File

@@ -24,6 +24,7 @@ class Constants {
public static String PUBLISHING = 'publishing'
public static String LOCAL_PROPERTIES = "local.properties"
public static String CLEAN = 'clean'
public static String PLUGIN_CACHE = "scan_cache"
//file
public static String DEFAULT_MAIN_MODULE_NAME = "app"

View File

@@ -42,19 +42,19 @@ class PinConfiguration {
productFlavorInfo = new ProductFlavorInfo(project)
PinUtils.clearOriginSourceSet(project, productFlavorInfo)
for (String include : include) {
PinInfo pin = PinUtils.buildPin(project, include)
if (microModule == null) {
throw new GradleException("PinInfo with path '${include}' could not be found in ${project.getDisplayName()}.")
for (String name : include) {
PinInfo pin = PinUtils.buildPin(project, name)
if (pin == null) {
throw new GradleException("PinInfo with path '${name}' could not be found in ${project.getDisplayName()}.")
}
addIncludePin(pin)
PinUtils.addMicroModuleSourceSet(project, pin, productFlavorInfo)
}
for (String export : export) {
PinInfo pin = PinUtils.buildPin(project, export)
for (String name : export) {
PinInfo pin = PinUtils.buildPin(project, name)
if (pin == null) {
throw new GradleException("PinInfo with path '${export}' could not be found in ${project.getDisplayName()}.")
throw new GradleException("PinInfo with path '${name}' could not be found in ${project.getDisplayName()}.")
}
addExportPin(pin)
PinUtils.addMicroModuleSourceSet(project, pin, productFlavorInfo)
@@ -79,12 +79,8 @@ class PinConfiguration {
includePins.put(pin.name, pin)
}
void addExportPin(String name) {
PinInfo pin = PinUtils.buildPin(project, name)
if (pin == null) {
throw new GradleException("PinInfo with path '${name}' could not be found in ${project.getDisplayName()}.")
}
exportPins.put(name, null)
void addExportPin(PinInfo pin) {
exportPins.put(pin.name, null)
}
PinInfo getIncludePin(String name) {
@@ -124,14 +120,14 @@ class PinConfiguration {
void export(String... pinPaths) {
int size = pinPaths.size()
for (int i = 0; i < size; i++) {
include.add(pinPaths[i])
export.add(pinPaths[i])
}
}
void include(String... pinPaths) {
int size = pinPaths.size()
for (int i = 0; i < size; i++) {
export.add(pinPaths[i])
include.add(pinPaths[i])
}
}

View File

@@ -0,0 +1,15 @@
package com.plugin.component.plugin
import com.plugin.component.extension.ComponentExtension
import org.gradle.api.Project
abstract class AbsPlugin {
abstract void initExtension(ComponentExtension componentExtension)
abstract void evaluate(Project project, boolean isRoot)
abstract void afterEvaluate(Project project, boolean isRoot)
abstract void afterAllEvaluate()
}

View File

@@ -3,17 +3,27 @@ package com.plugin.component.plugin
import com.plugin.component.extension.ComponentExtension
import org.gradle.api.Project
abstract interface BasePlugin {
class BasePlugin extends AbsPlugin {
void initExtension(ComponentExtension componentExtension)
ComponentExtension componentExtension
void evaluateChild(Project child)
@Override
void initExtension(ComponentExtension componentExtension) {
this.componentExtension = componentExtension
}
void afterEvaluateChild(Project child)
@Override
void evaluate(Project project, boolean isRoot) {
void evaluateRoot(Project root)
}
void afterEvaluateRoot(Project root)
@Override
void afterEvaluate(Project project, boolean isRoot) {
void afterAllEvaluate()
}
}
@Override
void afterAllEvaluate() {
}
}

View File

@@ -2,28 +2,15 @@ package com.plugin.component.plugin
import com.plugin.component.Logger
import com.plugin.component.Runtimes
import com.plugin.component.extension.ComponentExtension
import com.plugin.component.extension.module.ProjectInfo
import com.plugin.component.utils.ProjectUtil
import org.gradle.api.Project
class DebugPlugin implements BasePlugin {
private ComponentExtension componentExtension;
class DebugPlugin extends BasePlugin {
@Override
void initExtension(ComponentExtension componentExtension) {
this.componentExtension = componentExtension
}
@Override
void evaluateChild(Project child) {
}
@Override
void afterEvaluateChild(Project child) {
ProjectInfo projectInfo = Runtimes.getProjectInfo(child.name)
void afterEvaluate(Project project, boolean isRoot) {
ProjectInfo projectInfo = Runtimes.getProjectInfo(project.name)
//调整debugModule结构
if (ProjectUtil.isProjectSame(projectInfo.name, Runtimes.getDebugModuleName())) {
Logger.buildOutput("")
@@ -33,19 +20,4 @@ class DebugPlugin implements BasePlugin {
Logger.buildOutput("")
}
}
@Override
void evaluateRoot(Project root) {
}
@Override
void afterEvaluateRoot(Project root) {
}
@Override
void afterAllEvaluate() {
}
}

View File

@@ -8,7 +8,6 @@ import com.android.build.gradle.TestedExtension
import com.android.build.gradle.api.BaseVariant
import com.android.builder.model.ProductFlavor
import com.plugin.component.Runtimes
import com.plugin.component.extension.ComponentExtension
import com.plugin.component.extension.module.PinInfo
import com.plugin.component.extension.option.pin.PinConfiguration
import com.plugin.component.utils.PinUtils
@@ -22,7 +21,7 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.initialization.Settings
import org.gradle.api.invocation.Gradle
class PinPlugin implements BasePlugin {
class PinPlugin extends BasePlugin {
private final static String NORMAL = 'normal'
private final static String ASSEMBLE_OR_GENERATE = 'assemble_or_generate'
@@ -36,26 +35,24 @@ class PinPlugin implements BasePlugin {
String applyScriptState
boolean appliedLibraryPlugin
ComponentExtension componentExtension
PinConfiguration pinConfiguration
PinInfo currentPin
Project project
@Override
void initExtension(ComponentExtension componentExtension) {
this.componentExtension = componentExtension
}
boolean isSupportPins() {
return pinConfiguration != null
}
@Override
void evaluateChild(Project child) {
this.project = child
pinConfiguration = Runtimes.getPinConfiguration(child.name)
void evaluate(Project project, boolean isRoot) {
if (isRoot) {
return
}
this.project = project
pinConfiguration = Runtimes.getPinConfiguration(project.name)
if (!isSupportPins()) {
return
}
@@ -67,7 +64,7 @@ class PinPlugin implements BasePlugin {
startTaskState = ASSEMBLE_OR_GENERATE
}
//非sync情况下在configuration添加的时候
//非sync情况下在configuration添加的时候添加依赖的时候进行处理
if (startTaskState != NORMAL) {
project.getConfigurations().whenObjectAdded {
Configuration configuration = it
@@ -104,7 +101,7 @@ class PinPlugin implements BasePlugin {
def result = []
if (startTaskState == ASSEMBLE_OR_GENERATE) {
PinUtils.addMicroModuleSourceSet(project, microModule, pinConfiguration.productFlavorInfo)
PinUtils.applyMicroModuleScript(child, microModule, currentPin)
PinUtils.applyMicroModuleScript(project, microModule, currentPin)
microModule.appliedScript = true
}
return result
@@ -145,9 +142,9 @@ class PinPlugin implements BasePlugin {
for (ProductFlavor productFlavor : variant.productFlavors) {
sourceFolders.add(productFlavor.name)
}
PinUtils.checkMicroModuleBoundary(child, pinConfiguration, taskNamePrefix, variant.buildType.name, variant.flavorName, sourceFolders)
PinUtils.checkMicroModuleBoundary(project, pinConfiguration, taskNamePrefix, variant.buildType.name, variant.flavorName, sourceFolders)
} else {
PinUtils.checkMicroModuleBoundary(child, pinConfiguration, taskNamePrefix, variant.buildType.name, null, sourceFolders)
PinUtils.checkMicroModuleBoundary(project, pinConfiguration, taskNamePrefix, variant.buildType.name, null, sourceFolders)
}
}
}
@@ -157,7 +154,60 @@ class PinPlugin implements BasePlugin {
}
@Override
void afterEvaluateChild(Project child) {
void afterEvaluate(Project project, boolean isRoot) {
if (isRoot) {
if (Runtimes.hasPinModule()) {
project.gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
}
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
@Override
void buildFinished(BuildResult buildResult) {
// generate microModules.xml for PinInfo IDEA plugin.
def ideaFile = new File(buildResult.gradle.rootProject.rootDir, '.idea')
if (!ideaFile.exists()) return
def pininfos = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<modules>\n'
Map<String, PinConfiguration> allPins = Runtimes.getPinConfigurations()
Set<String> moduleNames = allPins.keySet()
for (String moduleName : moduleNames) {
PinConfiguration pins = allPins.get(moduleName)
def displayName = pins.name
pininfos += ' <module name=\"' + displayName.substring(displayName.indexOf("'") + 1, displayName.lastIndexOf("'")) + '\" path=\"' + it.projectDir.getCanonicalPath() + '\">\n'
pins.includePins.each {
PinInfo microModule = it.value
pininfos += ' <microModule name=\"' + microModule.name + '\" path=\"' + microModule.pinDir.getCanonicalPath() + '\" />\n'
}
pininfos += ' </module>\n'
}
pininfos += '</modules>'
def pins = new File(ideaFile, 'microModules.xml')
pins.write(pininfos, 'utf-8')
}
})
}
return
}
if (!isSupportPins()) {
return
}
@@ -173,11 +223,11 @@ class PinPlugin implements BasePlugin {
pinConfiguration.includePins.each {
PinInfo microModule = it.value
pinConfiguration.dependencyGraph.add(microModule.name)
applyMicroModuleScript(child, microModule)
applyMicroModuleScript(project, microModule)
}
//清除所有 srouceSet 信息
PinUtils.clearOriginSourceSet(child, pinConfiguration.productFlavorInfo)
PinUtils.clearOriginSourceSet(project, pinConfiguration.productFlavorInfo)
//如果非 sync
if (startTaskState == ASSEMBLE_OR_GENERATE) {
@@ -200,7 +250,7 @@ class PinPlugin implements BasePlugin {
if (microModule.appliedScript) return
PinUtils.addMicroModuleSourceSet(project, microModule, pinConfiguration.productFlavorInfo)
applyMicroModuleScript(child, microModule)
applyMicroModuleScript(project, microModule)
microModule.appliedScript = true
}
}
@@ -213,7 +263,7 @@ class PinPlugin implements BasePlugin {
pinConfiguration.includePins.each {
PinInfo microModule = it.value
PinUtils.addMicroModuleSourceSet(project, microModule, pinConfiguration.productFlavorInfo)
applyMicroModuleScript(child, microModule)
applyMicroModuleScript(project, microModule)
}
}
currentPin = null
@@ -238,67 +288,6 @@ class PinPlugin implements BasePlugin {
}
}
@Override
void evaluateRoot(Project root) {
}
@Override
void afterEvaluateRoot(Project root) {
if (Runtimes.hasPinModule()) {
root.gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
}
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
@Override
void buildFinished(BuildResult buildResult) {
// generate microModules.xml for PinInfo IDEA plugin.
def ideaFile = new File(buildResult.gradle.rootProject.rootDir, '.idea')
if (!ideaFile.exists()) return
def pininfos = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<modules>\n'
Map<String, PinConfiguration> allPins = Runtimes.getPinConfigurations()
Set<String> moduleNames = allPins.keySet()
for (String moduleName : moduleNames) {
PinConfiguration pins = allPins.get(moduleName)
def displayName = pins.name
pininfos += ' <module name=\"' + displayName.substring(displayName.indexOf("'") + 1, displayName.lastIndexOf("'")) + '\" path=\"' + it.projectDir.getCanonicalPath() + '\">\n'
pins.includePins.each {
PinInfo microModule = it.value
pininfos += ' <microModule name=\"' + microModule.name + '\" path=\"' + microModule.pinDir.getCanonicalPath() + '\" />\n'
}
pininfos += ' </module>\n'
}
pininfos += '</modules>'
def pins = new File(ideaFile, 'microModules.xml')
pins.write(pininfos, 'utf-8')
}
})
}
}
@Override
void afterAllEvaluate() {
}
void applyMicroModuleScript(Project project, PinInfo microModule) {
def pinBuild = new File(microModule.pinDir, 'build.gradle')

View File

@@ -1,75 +1,247 @@
package com.plugin.component.plugin
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import com.android.build.gradle.TestedExtension
import com.plugin.component.Constants
import com.plugin.component.Logger
import com.plugin.component.Runtimes
import com.plugin.component.extension.ComponentExtension
import com.plugin.component.extension.PublicationManager
import com.plugin.component.extension.module.ProjectInfo
import com.plugin.component.extension.option.debug.DebugDependenciesOption
import com.plugin.component.extension.option.sdk.PublicationDependenciesOption
import com.plugin.component.extension.option.sdk.PublicationOption
import com.plugin.component.transform.InjectCodeTransform
import com.plugin.component.transform.ScanCodeTransform
import com.plugin.component.transform.ComponentTransform
import com.plugin.component.utils.JarUtil
import com.plugin.component.utils.ProjectUtil
import com.plugin.component.utils.PublicationUtil
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
class SdkPlugin implements BasePlugin{
private ComponentExtension mComponentExtension
@Override
void initExtension(ComponentExtension componentExtension) {
this.mComponentExtension = componentExtension
}
class SdkPlugin extends BasePlugin {
@Override
void evaluateChild(Project child) {
ProjectInfo projectInfo = Runtimes.getProjectInfo(child.name)
void evaluate(Project project, boolean isRoot) {
if (isRoot) {
Runtimes.sSdkDir = new File(project.projectDir, Constants.SDK_DIR)
Runtimes.sImplDir = new File(project.projectDir, Constants.IMPL_DIR)
Logger.buildOutput("sdk目录 File[" + Runtimes.sSdkDir.name + "]")
Logger.buildOutput("impl目录 File[" + Runtimes.sSdkDir.name + "]")
//解析: component
//example component(':library')
//规则:
// 1. component 对象必须为实现 component 插件的project
// 2. component(<project>) project 逻辑上被划分为 impl / debug / sdk其中 sdk 通过 api 暴露给上层impl 直接打包
child.dependencies.metaClass.component { String value ->
return PublicationUtil.parseComponent(projectInfo, value)
}
//project 需要使用 api 依赖并暴露 sdk解决 project 被依赖的时候,依赖者可以引用到 project sdk
List<PublicationOption> publications = PublicationManager.getInstance().getPublicationByProject(child)
child.dependencies {
publications.each {
api PublicationUtil.getPublication(it)
if (!Runtimes.sSdkDir.exists()) {
Runtimes.sSdkDir.mkdirs()
Logger.buildOutput("create File[" + Runtimes.sSdkDir.name + "]")
}
}
/**
* Syncing Gradle will evaluate the build files by comparing the current files to the project state that Gradle and Android Studio maintain.
* If it finds any changes it will execute just those specific tasks.
*/
//project 需要依赖 sdk 中声明的依赖意味着同步的时候component.gradle 中 sdk { dependencies { <xxxxx> }} 内容需要同步迁移到 project
if (projectInfo.isSync()) {
Logger.buildOutput("Syncing gradle...")
publications.each {
PublicationUtil.addPublicationDependencies(projectInfo, it)
if (!Runtimes.sImplDir.exists()) {
Runtimes.sImplDir.mkdirs()
Logger.buildOutput("create File[" + Runtimes.sImplDir.name + "]")
}
ProjectUtil.getTasks(project).each {
if (it == Constants.CLEAN) {
if (!Runtimes.sSdkDir.deleteDir()) {
throw new RuntimeException("unable to delete dir " + Runtimes.sSdkDir.absolutePath)
}
if (!Runtimes.sImplDir.deleteDir()) {
throw new RuntimeException("unable to delete dir " + Runtimes.sImplDir.absolutePath)
}
Runtimes.sSdkDir.mkdirs()
Logger.buildOutput("reset File[" + Runtimes.sSdkDir.name + "]")
Runtimes.sImplDir.mkdirs()
Logger.buildOutput("reset File[" + Runtimes.sImplDir.name + "]")
}
}
project.repositories {
flatDir {
dirs Runtimes.sSdkDir.absolutePath
Logger.buildOutput("flatDir Dir[" + Runtimes.sSdkDir.absolutePath + "]")
dirs Runtimes.sImplDir.absolutePath
Logger.buildOutput("flatDir Dir[" + Runtimes.sImplDir.absolutePath + "]")
}
}
Logger.buildOutput("读取 sdk/impl manifest 配置文件...")
PublicationManager.getInstance().loadManifest(project)
Logger.buildOutput("读取 component.gradle 信息...")
//todo sdk中依赖sdk需要特别区分预留后续逻辑
PublicationDependenciesOption.metaClass.component { String value ->
return Constants.COMPONENT_PRE + value
}
DebugDependenciesOption.metaClass.component { String value ->
return Constants.DEBUG_COMPONENT_PRE + value
}
} else {
ProjectInfo projectInfo = Runtimes.getProjectInfo(project.name)
//解析: component
//example component(':library')
//规则:
// 1. component 对象必须为实现 component 插件的project
// 2. component(<project>) project 逻辑上被划分为 impl / debug / sdk其中 sdk 通过 api 暴露给上层impl 直接打包
project.dependencies.metaClass.component { String value ->
return PublicationUtil.parseComponent(projectInfo, value)
}
//project 需要使用 api 依赖并暴露 sdk解决 project 被依赖的时候,依赖者可以引用到 project sdk
List<PublicationOption> publications = PublicationManager.getInstance().getPublicationByProject(project)
project.dependencies {
publications.each {
api PublicationUtil.getPublication(it)
}
}
/**
* Syncing Gradle will evaluate the build files by comparing the current files to the project state that Gradle and Android Studio maintain.
* If it finds any changes it will execute just those specific tasks.
*/
//project 需要依赖 sdk 中声明的依赖意味着同步的时候component.gradle 中 sdk { dependencies { <xxxxx> }} 内容需要同步迁移到 project
if (projectInfo.isSync()) {
Logger.buildOutput("Syncing gradle...")
publications.each {
PublicationUtil.addPublicationDependencies(projectInfo, it)
}
}
}
}
@Override
void afterEvaluateChild(Project child) {
ProjectInfo projectInfo = Runtimes.getProjectInfo(child.name)
ProjectUtil.modifySourceSets(projectInfo)
void afterEvaluate(Project project,boolean isRoot) {
if(isRoot){
Logger.buildOutput("")
Logger.buildOutput("=====> 处理 sdk/impl jar <=====")
List<String> topSort = PublicationManager.getInstance().dependencyGraph.topSort()
Collections.reverse(topSort)
topSort.each {
PublicationOption publication = PublicationManager.getInstance().publicationDependencies.get(it)
if (publication == null) {
return
}
Project childProject = project.findProject(publication.project)
PublicationUtil.filterPublicationDependencies(publication)
if (publication.version != null) {
JarUtil.handleMavenJar(childProject, publication)
} else {
JarUtil.handleLocalJar(childProject, publication)
}
PublicationManager.getInstance().hitPublication(publication)
}
Logger.buildOutput("=====> 处理 sdk/impl jar <=====")
Logger.buildOutput("")
//todo 发布
project.allprojects.each {
if (it == project) return
if (!Runtimes.shouldApplyComponentPlugin(it)) return
Project childProject = it
Logger.buildOutput("")
Logger.buildOutput("=====> project[" + childProject.name + "]配置信息 <=====")
ProjectInfo projectInfo = new ProjectInfo(childProject)
childProject.repositories {
flatDir {
dirs Runtimes.sSdkDir.absolutePath
Logger.buildOutput("add flatDir Dir[" + Runtimes.sSdkDir.absolutePath + "]")
dirs Runtimes.sImplDir.absolutePath
Logger.buildOutput("add flatDir Dir[" + Runtimes.sImplDir.absolutePath + "]")
}
}
Runtimes.addProjectInfo(childProject.name, projectInfo)
Logger.buildOutput("compileModuleName", projectInfo.compileModuleName)
Logger.buildOutput("projectName", projectInfo.name)
Logger.buildOutput("isDebugModule", projectInfo.isDebugModule())
Logger.buildOutput("taskNames", projectInfo.taskNames)
Logger.buildOutput("isSyncTask", projectInfo.isSync())
Logger.buildOutput("isAssemble", projectInfo.isAssemble)
Logger.buildOutput("isDebug", projectInfo.isDebug)
Logger.buildOutput("=====> project[" + childProject.name + "]配置信息 <=====")
Logger.buildOutput("")
childProject.plugins.all {
Class extensionClass
if (it instanceof AppPlugin) {
extensionClass = AppExtension
} else if (it instanceof LibraryPlugin) {
extensionClass = LibraryExtension
} else {
return
}
//plugin afterEvaluate 之后调用
childProject.extensions.configure(extensionClass, new Action<? extends TestedExtension>() {
@Override
void execute(TestedExtension testedExtension) {
Logger.buildOutput("")
}
})
}
/**
* plugin afterEvaluate 之后调用
*/
childProject.plugins.whenObjectAdded {
if (it instanceof AppPlugin || it instanceof LibraryPlugin) {
Logger.buildOutput("")
Logger.buildOutput("=====> project[" + childProject.name + "]注入插件 <=====")
Logger.buildOutput("project[" + childProject.name + "] whenObjectAdded(AppPlugin or LibraryPlugin)")
Logger.buildOutput("apply plugin: com.android.component")
childProject.pluginManager.apply(Constants.PLUGIN_COMPONENT)
childProject.dependencies {
Logger.buildOutput("add dependency: " + Constants.CORE_DEPENDENCY)
implementation Constants.CORE_DEPENDENCY
// implementation childProject.project(":component-core")
}
if (it instanceof AppPlugin) {
if (projectInfo.isDebugModule() || projectInfo.isCompileModuleAndAssemble()) {
Logger.buildOutput("plugin is AppPlugin")
Logger.buildOutput("registerTransform", "ScanCodeTransform")
Logger.buildOutput("registerTransform", "InjectCodeTransform")
childProject.extensions.findByType(BaseExtension.class).registerTransform(new ComponentTransform(childProject))
}
}
Logger.buildOutput("=====> project[" + childProject.name + "]注入插件 <=====")
Logger.buildOutput("")
}
}
/**
* 介于 evaluate 与 afterEvaluate 中间
*/
childProject.getPluginManager().withPlugin("com.android.library", new Action<AppliedPlugin>() {
@Override
void execute(AppliedPlugin appliedPlugin) {
Logger.buildOutput("")
}
})
childProject.getPluginManager().withPlugin("com.android.application", new Action<AppliedPlugin>() {
@Override
void execute(AppliedPlugin appliedPlugin) {
Logger.buildOutput("")
}
})
//评估之后after之前
childProject.getPluginManager().withPlugin("com.android.component", new Action<AppliedPlugin>() {
@Override
void execute(AppliedPlugin appliedPlugin) {
Logger.buildOutput("")
}
})
}
}else{
ProjectInfo projectInfo = Runtimes.getProjectInfo(project.name)
ProjectUtil.modifySourceSets(projectInfo)
//todo 发布
// List<PublicationOption> publicationList = PublicationManager.getInstance().getPublicationByProject(project)
// List<PublicationOption> publicationPublishList = new ArrayList<>()
// publicationList.each {
@@ -89,143 +261,9 @@ class SdkPlugin implements BasePlugin{
// PublicationUtil.createPublishTask(project, it)
// }
// }
}
@Override
void evaluateRoot(Project root) {
Runtimes.sSdkDir = new File(root.projectDir, Constants.SDK_DIR)
Runtimes.sImplDir = new File(root.projectDir, Constants.IMPL_DIR)
Logger.buildOutput("sdk目录 File[" + Runtimes.sSdkDir.name + "]")
Logger.buildOutput("impl目录 File[" + Runtimes.sSdkDir.name + "]")
if (!Runtimes.sSdkDir.exists()) {
Runtimes.sSdkDir.mkdirs()
Logger.buildOutput("create File[" + Runtimes.sSdkDir.name + "]")
}
if (!Runtimes.sImplDir.exists()) {
Runtimes.sImplDir.mkdirs()
Logger.buildOutput("create File[" + Runtimes.sImplDir.name + "]")
}
ProjectUtil.getTasks(root).each {
if (it == Constants.CLEAN) {
if (!Runtimes.sSdkDir.deleteDir()) {
throw new RuntimeException("unable to delete dir " + Runtimes.sSdkDir.absolutePath)
}
if (!Runtimes.sImplDir.deleteDir()) {
throw new RuntimeException("unable to delete dir " + Runtimes.sImplDir.absolutePath)
}
Runtimes.sSdkDir.mkdirs()
Logger.buildOutput("reset File[" + Runtimes.sSdkDir.name + "]")
Runtimes.sImplDir.mkdirs()
Logger.buildOutput("reset File[" + Runtimes.sImplDir.name + "]")
}
}
root.repositories {
flatDir {
dirs Runtimes.sSdkDir.absolutePath
Logger.buildOutput("flatDir Dir[" + Runtimes.sSdkDir.absolutePath + "]")
dirs Runtimes.sImplDir.absolutePath
Logger.buildOutput("flatDir Dir[" + Runtimes.sImplDir.absolutePath + "]")
}
}
Logger.buildOutput("读取 sdk/impl manifest 配置文件...")
PublicationManager.getInstance().loadManifest(root)
Logger.buildOutput("读取 component.gradle 信息...")
//todo sdk中依赖sdk需要特别区分预留后续逻辑
PublicationDependenciesOption.metaClass.component { String value ->
return Constants.COMPONENT_PRE + value
}
DebugDependenciesOption.metaClass.component { String value ->
return Constants.DEBUG_COMPONENT_PRE + value
}
}
@Override
void afterEvaluateRoot(Project root) {
Logger.buildOutput("")
Logger.buildOutput("=====> 处理 sdk/impl jar <=====")
List<String> topSort = PublicationManager.getInstance().dependencyGraph.topSort()
Collections.reverse(topSort)
topSort.each {
PublicationOption publication = PublicationManager.getInstance().publicationDependencies.get(it)
if (publication == null) {
return
}
Project childProject = root.findProject(publication.project)
PublicationUtil.filterPublicationDependencies(publication)
if (publication.version != null) {
JarUtil.handleMavenJar(childProject, publication)
} else {
JarUtil.handleLocalJar(childProject, publication)
}
PublicationManager.getInstance().hitPublication(publication)
}
Logger.buildOutput("=====> 处理 sdk/impl jar <=====")
Logger.buildOutput("")
root.allprojects.each {
if (it == root) return
if (!Runtimes.shouldApplyComponentPlugin(it)) return
Project childProject = it
Logger.buildOutput("")
Logger.buildOutput("=====> project[" + childProject.name + "]配置信息 <=====")
ProjectInfo projectInfo = new ProjectInfo(childProject)
childProject.repositories {
flatDir {
dirs Runtimes.sSdkDir.absolutePath
Logger.buildOutput("add flatDir Dir[" + Runtimes.sSdkDir.absolutePath + "]")
dirs Runtimes.sImplDir.absolutePath
Logger.buildOutput("add flatDir Dir[" + Runtimes.sImplDir.absolutePath + "]")
}
}
Runtimes.addProjectInfo(childProject.name, projectInfo)
Logger.buildOutput("compileModuleName", projectInfo.compileModuleName)
Logger.buildOutput("projectName", projectInfo.name)
Logger.buildOutput("isDebugModule", projectInfo.isDebugModule())
Logger.buildOutput("taskNames", projectInfo.taskNames)
Logger.buildOutput("isSyncTask", projectInfo.isSync())
Logger.buildOutput("isAssemble", projectInfo.isAssemble)
Logger.buildOutput("isDebug", projectInfo.isDebug)
Logger.buildOutput("=====> project[" + childProject.name + "]配置信息 <=====")
Logger.buildOutput("")
childProject.plugins.whenObjectAdded {
if (it instanceof AppPlugin || it instanceof LibraryPlugin) {
Logger.buildOutput("")
Logger.buildOutput("=====> project[" + childProject.name + "]注入插件 <=====")
Logger.buildOutput("project[" + childProject.name + "] whenObjectAdded(AppPlugin or LibraryPlugin)")
Logger.buildOutput("apply plugin: com.android.component")
childProject.pluginManager.apply(Constants.PLUGIN_COMPONENT)
childProject.dependencies {
Logger.buildOutput("add dependency: " + Constants.CORE_DEPENDENCY)
implementation Constants.CORE_DEPENDENCY
// implementation childProject.project(":component-core")
}
if (it instanceof AppPlugin) {
if (projectInfo.isDebugModule() || projectInfo.isCompileModuleAndAssemble()) {
Logger.buildOutput("plugin is AppPlugin")
Logger.buildOutput("registerTransform", "ScanCodeTransform")
Logger.buildOutput("registerTransform", "InjectCodeTransform")
childProject.extensions.findByType(BaseExtension.class).registerTransform(new ScanCodeTransform(childProject))
childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject))
}
}
Logger.buildOutput("=====> project[" + childProject.name + "]注入插件 <=====")
Logger.buildOutput("")
}
}
}
}
@Override
void afterAllEvaluate() {

View File

@@ -0,0 +1,134 @@
package com.plugin.component.transform;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.attribute.FileTime;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* author : linzheng
* e-mail : linzheng@corp.netease.com
* time : 2019/10/21
* desc :
* version: 1.0
*/
public class CodeInjectProcessor {
private static final FileTime ZERO = FileTime.fromMillis(0);
private static final String FILE_SEP = File.separator;
public void injectCode(String inputPath, String outputPath) {
try {
if (inputPath != null && inputPath.endsWith(".jar")) {
weaveJar(inputPath, outputPath);
} else if (inputPath != null && inputPath.endsWith(".class")) {
weaveSingleClassToFile(inputPath, outputPath);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean isWeavableClass(String fullQualifiedClassName) {
return fullQualifiedClassName.endsWith(".class") && !fullQualifiedClassName.contains("R$") && !fullQualifiedClassName.contains("R.class") && !fullQualifiedClassName.contains("BuildConfig.class");
}
public final void weaveSingleClassToFile(String inputPath, String outputPath) throws IOException {
File inputFile = new File(inputPath);
File outputFile = new File(outputPath);
if (outputFile.exists()) {
outputFile.delete();
}
FileUtils.touch(outputFile);
InputStream inputStream = new FileInputStream(inputFile);
byte[] bytes = doGenerateCode(inputStream);
if (bytes != null) {
FileOutputStream fos = new FileOutputStream(outputFile);
fos.write(bytes);
fos.close();
inputStream.close();
}
}
public final void weaveJar(String inputPath, String outputPath) throws IOException {
File outputJar = new File(outputPath);
if (outputJar.exists()) {
outputJar.delete();
}
ZipFile inputZip = new ZipFile(new File(inputPath));
ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath())));
Enumeration<? extends ZipEntry> inEntries = inputZip.entries();
while (inEntries.hasMoreElements()) {
ZipEntry entry = inEntries.nextElement();
InputStream originalFile = new BufferedInputStream(inputZip.getInputStream(entry));
ZipEntry outEntry = new ZipEntry(entry.getName());
byte[] newEntryContent;
// seperator of entry name is always '/', even in windows
String className = outEntry.getName().replace("/", ".");
if (!isWeavableClass(className)) {
newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile);
} else {
newEntryContent = doGenerateCode(originalFile);
}
CRC32 crc32 = new CRC32();
crc32.update(newEntryContent);
outEntry.setCrc(crc32.getValue());
outEntry.setMethod(ZipEntry.STORED);
outEntry.setSize(newEntryContent.length);
outEntry.setCompressedSize(newEntryContent.length);
outEntry.setLastAccessTime(ZERO);
outEntry.setLastModifiedTime(ZERO);
outEntry.setCreationTime(ZERO);
outputZip.putNextEntry(outEntry);
outputZip.write(newEntryContent);
outputZip.closeEntry();
}
outputZip.flush();
outputZip.close();
inputZip.close();
}
private byte[] doGenerateCode(InputStream inputStream) {
try {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
ClassVisitor classVisitor = new InjectCodeAdapter(classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,323 @@
package com.plugin.component.transform;
import com.android.build.api.transform.Context;
import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.Format;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.Status;
import com.android.build.api.transform.Transform;
import com.android.build.api.transform.TransformException;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformOutputProvider;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.ide.common.internal.WaitableExecutor;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.plugin.component.Constants;
import com.plugin.component.transform.info.ScanRuntime;
import com.plugin.component.transform.info.ScanSummaryInfo;
import com.plugin.component.utils.CacheDiskUtils;
import com.quinn.hunter.transform.RunVariant;
import com.quinn.hunter.transform.asm.ClassLoaderHelper;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* author : linzheng
* e-mail : linzheng@corp.netease.com
* time : 2019/10/18
* desc :
* version: 1.0
*/
public class ComponentTransform extends Transform {
private final Logger logger;
private static final Set<QualifiedContent.Scope> SCOPES = new HashSet<>();
static {
SCOPES.add(QualifiedContent.Scope.PROJECT);
SCOPES.add(QualifiedContent.Scope.SUB_PROJECTS);
SCOPES.add(QualifiedContent.Scope.EXTERNAL_LIBRARIES);
}
private Project project;
protected ComponentWeaver bytecodeWeaver;
private WaitableExecutor waitableExecutor;
private boolean emptyRun = false;
public ComponentTransform(Project project) {
this.project = project;
this.logger = project.getLogger();
this.waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool();
this.bytecodeWeaver = new ComponentWeaver();
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<QualifiedContent.Scope> getScopes() {
return SCOPES;
}
@Override
public boolean isIncremental() {
return true;
}
@SuppressWarnings("deprecation")
@Override
public void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException {
RunVariant runVariant = getRunVariant();
if ("debug".equals(context.getVariantName())) {
emptyRun = runVariant == RunVariant.RELEASE || runVariant == RunVariant.NEVER;
} else if ("release".equals(context.getVariantName())) {
emptyRun = runVariant == RunVariant.DEBUG || runVariant == RunVariant.NEVER;
}
logger.warn(getName() + " isIncremental = " + isIncremental + ", runVariant = " + runVariant + ", emptyRun = " + emptyRun + ", inDuplcatedClassSafeMode = " + inDuplcatedClassSafeMode());
long startTime = System.currentTimeMillis();
if (!isIncremental) {
outputProvider.deleteAll();
}
URLClassLoader urlClassLoader = ClassLoaderHelper.getClassLoader(inputs, referencedInputs, project);
this.bytecodeWeaver.setClassLoader(urlClassLoader);
boolean flagForCleanDexBuilderFolder = false;
for (TransformInput input : inputs) {
for (JarInput jarInput : input.getJarInputs()) {
Status status = jarInput.getStatus();
String filePath = jarInput.getFile().getAbsolutePath();
File dest = outputProvider.getContentLocation(jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR);
if (isIncremental && !emptyRun) {
switch (status) {
case NOTCHANGED:
break;
case ADDED:
case CHANGED:
transformJar(jarInput.getFile(), dest, status);
break;
case REMOVED:
if (dest.exists()) {
FileUtils.forceDelete(dest);
}
ScanRuntime.removedFile(filePath);
break;
}
} else {
//Forgive me!, Some project will store 3rd-party aar for serveral copies in dexbuilder folder,,unknown issue.
if (inDuplcatedClassSafeMode() & !isIncremental && !flagForCleanDexBuilderFolder) {
cleanDexBuilderFolder(dest);
flagForCleanDexBuilderFolder = true;
}
transformJar(jarInput.getFile(), dest, status);
}
}
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY);
FileUtils.forceMkdir(dest);
if (isIncremental && !emptyRun) {
String srcDirPath = directoryInput.getFile().getAbsolutePath();
String destDirPath = dest.getAbsolutePath();
Map<File, Status> fileStatusMap = directoryInput.getChangedFiles();
for (Map.Entry<File, Status> changedFile : fileStatusMap.entrySet()) {
Status status = changedFile.getValue();
File inputFile = changedFile.getKey();
String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath);
File destFile = new File(destFilePath);
switch (status) {
case NOTCHANGED:
break;
case REMOVED:
if (destFile.exists()) {
//noinspection ResultOfMethodCallIgnored
destFile.delete();
}
ScanRuntime.removedFile(inputFile.getAbsolutePath());
break;
case ADDED:
case CHANGED:
try {
FileUtils.touch(destFile);
} catch (IOException e) {
//maybe mkdirs fail for some strange reason, try again.
Files.createParentDirs(destFile);
}
transformSingleFile(inputFile, destFile, srcDirPath);
break;
}
}
} else {
transformDir(directoryInput.getFile(), dest);
}
}
}
waitableExecutor.waitForTasksWithQuickFail(true);
ScanSummaryInfo cacheSummary = readPluginCache(); // 读取缓存
ScanSummaryInfo scanSummaryInfo = ScanRuntime.updateSummaryInfo(cacheSummary); // 整理本次扫码结果,返回最新的模块结构
ScanRuntime.buildComponentSdkInfo();
ScanRuntime.logScanInfo();
updatePluginCache(cacheSummary); // 保存缓存
long costTime = System.currentTimeMillis() - startTime;
logger.warn((getName() + "scan code costed " + costTime + "ms"));
injectCode(scanSummaryInfo);
}
private void injectCode(@NotNull ScanSummaryInfo scanSummaryInfo) {
long startTime = System.currentTimeMillis();
String injectInputPath = scanSummaryInfo.inputFilePath; // 输入文件
String injectOutputPath = scanSummaryInfo.outputFilePath; // 输出文件
if (injectInputPath != null) {
logger.warn(" inject file find : file = " + injectInputPath);
try {
CodeInjectProcessor codeInjectProcessor = new CodeInjectProcessor();
codeInjectProcessor.injectCode(injectInputPath, injectOutputPath);
} catch (Exception e) {
e.printStackTrace();
logger.warn("inject Code error");
}
logger.warn("inject Code end");
}
long injectCostTime = System.currentTimeMillis() - startTime;
logger.warn((getName() + " inject code costed " + injectCostTime + "ms"));
ScanRuntime.clearScanInfo();
ScanRuntime.clearSummaryInfo();
}
private void updatePluginCache(ScanSummaryInfo cacheSummary) {
try {
String json = new Gson().toJson(cacheSummary);
CacheDiskUtils.getInstance(project.getBuildDir()).put(Constants.PLUGIN_CACHE, json);
logger.warn((getName() + "save cache success"));
} catch (Exception e) {
e.printStackTrace();
}
}
@NotNull
private ScanSummaryInfo readPluginCache() {
ScanSummaryInfo cacheSummary = null;
try {
String json = CacheDiskUtils.getInstance(project.getBuildDir()).getString(Constants.PLUGIN_CACHE);
cacheSummary = new Gson().fromJson(json, ScanSummaryInfo.class);
} catch (Exception e) {
e.printStackTrace();
}
if (cacheSummary == null) {
cacheSummary = new ScanSummaryInfo();
}
return cacheSummary;
}
private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) {
waitableExecutor.execute(() -> {
bytecodeWeaver.weaveSingleClassToFile(inputFile, outputFile, srcBaseDir);
return null;
});
}
private void transformDir(final File inputDir, final File outputDir) throws IOException {
if (emptyRun) {
FileUtils.copyDirectory(inputDir, outputDir);
return;
}
final String inputDirPath = inputDir.getAbsolutePath();
final String outputDirPath = outputDir.getAbsolutePath();
if (inputDir.isDirectory()) {
for (final File file : com.android.utils.FileUtils.getAllFiles(inputDir)) {
waitableExecutor.execute(() -> {
String filePath = file.getAbsolutePath();
File outputFile = new File(filePath.replace(inputDirPath, outputDirPath));
bytecodeWeaver.weaveSingleClassToFile(file, outputFile, inputDirPath);
return null;
});
}
}
}
private void transformJar(final File srcJar, final File destJar, Status status) {
waitableExecutor.execute(() -> {
if (emptyRun) {
FileUtils.copyFile(srcJar, destJar);
return null;
}
bytecodeWeaver.weaveJar(srcJar, destJar);
return null;
});
}
private void cleanDexBuilderFolder(File dest) {
waitableExecutor.execute(() -> {
try {
String dexBuilderDir = replaceLastPart(dest.getAbsolutePath(), getName(), "dexBuilder");
//intermediates/transforms/dexBuilder/debug
File file = new File(dexBuilderDir).getParentFile();
project.getLogger().warn("clean dexBuilder folder = " + file.getAbsolutePath());
if (file.exists() && file.isDirectory()) {
com.android.utils.FileUtils.deleteDirectoryContents(file);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
}
private String replaceLastPart(String originString, String replacement, String toreplace) {
int start = originString.lastIndexOf(replacement);
StringBuilder builder = new StringBuilder();
builder.append(originString.substring(0, start));
builder.append(toreplace);
builder.append(originString.substring(start + replacement.length()));
return builder.toString();
}
@Override
public boolean isCacheable() {
return true;
}
protected RunVariant getRunVariant() {
return RunVariant.ALWAYS;
}
protected boolean inDuplcatedClassSafeMode() {
return false;
}
}

View File

@@ -0,0 +1,163 @@
package com.plugin.component.transform;
import com.plugin.component.transform.info.ScanRuntime;
import com.quinn.hunter.transform.asm.ExtendClassWriter;
import com.quinn.hunter.transform.asm.IWeaver;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.attribute.FileTime;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* author : linzheng
* e-mail : linzheng@corp.netease.com
* time : 2019/10/18
* desc :
* version: 1.0
*/
public class ComponentWeaver implements IWeaver {
private static final FileTime ZERO = FileTime.fromMillis(0);
private static final String FILE_SEP = File.separator;
private String injectClassFile;
protected ClassLoader classLoader;
public ComponentWeaver() {
}
public String getInjectClassFile() {
return injectClassFile;
}
public final void weaveJar(File inputJar, File outputJar) throws IOException {
String inputPath = inputJar.getAbsolutePath();
String outputPath = outputJar.getAbsolutePath();
ZipFile inputZip = new ZipFile(inputJar);
ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath())));
Enumeration<? extends ZipEntry> inEntries = inputZip.entries();
while (inEntries.hasMoreElements()) {
ZipEntry entry = inEntries.nextElement();
InputStream originalFile = new BufferedInputStream(inputZip.getInputStream(entry));
ZipEntry outEntry = new ZipEntry(entry.getName());
byte[] newEntryContent;
// seperator of entry name is always '/', even in windows
String className = outEntry.getName().replace("/", ".");
beforeWeaveClass(inputPath, outputPath, className);
if (!isWeavableClass(className)) {
newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile);
} else {
newEntryContent = weaveSingleClassToByteArray(inputPath, originalFile);
}
CRC32 crc32 = new CRC32();
crc32.update(newEntryContent);
outEntry.setCrc(crc32.getValue());
outEntry.setMethod(ZipEntry.STORED);
outEntry.setSize(newEntryContent.length);
outEntry.setCompressedSize(newEntryContent.length);
outEntry.setLastAccessTime(ZERO);
outEntry.setLastModifiedTime(ZERO);
outEntry.setCreationTime(ZERO);
outputZip.putNextEntry(outEntry);
outputZip.write(newEntryContent);
outputZip.closeEntry();
}
outputZip.flush();
outputZip.close();
}
public final void weaveSingleClassToFile(File inputFile, File outputFile, String inputBaseDir) throws IOException {
if (!inputBaseDir.endsWith(FILE_SEP)) inputBaseDir = inputBaseDir + FILE_SEP;
String className = inputFile.getAbsolutePath().replace(inputBaseDir, "").replace(FILE_SEP, ".");
String inputPath = inputFile.getAbsolutePath();
String outputPath = outputFile.getAbsolutePath();
beforeWeaveClass(inputPath, outputPath, className);
if (isWeavableClass(className)) {
FileUtils.touch(outputFile);
InputStream inputStream = new FileInputStream(inputFile);
byte[] bytes = weaveSingleClassToByteArray(inputPath, inputStream);
FileOutputStream fos = new FileOutputStream(outputFile);
fos.write(bytes);
fos.close();
inputStream.close();
} else {
if (inputFile.isFile()) {
FileUtils.touch(outputFile);
FileUtils.copyFile(inputFile, outputFile);
}
}
}
public final void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public byte[] weaveSingleClassToByteArray(InputStream inputStream) throws IOException {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS);
ClassVisitor classWriterWrapper = wrapClassWriter(classWriter);
classReader.accept(classWriterWrapper, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
}
public byte[] weaveSingleClassToByteArray(String filePath, InputStream inputStream) throws IOException {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS);
ScanCodeAdapter classWriterWrapper = new ScanCodeAdapter(classWriter);
classWriterWrapper.setFilePath(filePath);
classReader.accept(classWriterWrapper, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
}
private static final String sComponentManagerPath = "com.plugin.component.ComponentManager";
public void beforeWeaveClass(String inputPath, String outputFile, String className) {
if (injectClassFile == null && className != null && className.contains(sComponentManagerPath)) {
System.out.println("find class ComponentManager : file is : " + outputFile);
injectClassFile = outputFile;
ScanRuntime.getsSummaryInfo().inputFilePath = inputPath;
ScanRuntime.getsSummaryInfo().outputFilePath = outputFile;
}
}
public void setExtension(Object extension) {
}
protected ClassVisitor wrapClassWriter(ClassWriter classWriter) {
return new ScanCodeAdapter(classWriter);
}
@Override
public boolean isWeavableClass(String fullQualifiedClassName) {
return fullQualifiedClassName.endsWith(".class") && !fullQualifiedClassName.contains("R$") && !fullQualifiedClassName.contains("R.class") && !fullQualifiedClassName.contains("BuildConfig.class");
}
}

View File

@@ -13,11 +13,16 @@ import org.objectweb.asm.Type
class ScanCodeAdapter extends ClassVisitor {
private String className
private String filePath
ScanCodeAdapter(ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor)
}
void setFilePath(String filePath) {
this.filePath = filePath
}
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
className = name
@@ -68,11 +73,13 @@ class ScanCodeAdapter extends ClassVisitor {
@Override
void visitEnd() {
if (scanComponentInfo != null) {
ScanRuntime.addComponentInfo(scanComponentInfo)
// ScanRuntime.addComponentInfo(scanComponentInfo)
ScanRuntime.addComponentInfo(filePath, scanComponentInfo)
scanComponentInfo = null
}
if (scanSdkInfo != null) {
ScanRuntime.addSdkInfo(scanSdkInfo)
// ScanRuntime.addSdkInfo(scanSdkInfo)
ScanRuntime.addSdkInfo(filePath, scanSdkInfo)
scanSdkInfo = null
}
super.visitEnd()

View File

@@ -10,6 +10,9 @@ class ScanRuntime {
static List<ScanSdkInfo> sSdkInfo = new ArrayList<>()
static List<ComponentSdkInfo> componentSdkInfoList = new ArrayList<>()
static ScanSummaryInfo sSummaryInfo = new ScanSummaryInfo()
static void addComponentInfo(@NonNull ScanComponentInfo scanComponentInfo) {
sComponentInfo.add(scanComponentInfo)
}
@@ -19,10 +22,12 @@ class ScanRuntime {
}
static void logScanInfo() {
Logger.buildOutput("Dodge sdk info size = " + sSdkInfo.size())
for (ScanSdkInfo sdkInfo : sSdkInfo) {
Logger.buildOutput(sdkInfo.toString())
}
Logger.buildOutput("Dodge Component Info size = " + sComponentInfo.size())
for (ScanComponentInfo scanComponentInfo : sComponentInfo) {
Logger.buildOutput(scanComponentInfo.toString())
}
@@ -68,4 +73,74 @@ class ScanRuntime {
static List<ComponentSdkInfo> getComponentSdkInfoList() {
return componentSdkInfoList
}
static void addSdkInfo(String filePath, @NonNull ScanSdkInfo scanSdkInfo) {
Set<ScanSdkInfo> set = sSummaryInfo.updateSdkMap.get(filePath)
if (set == null) {
set = new HashSet<>()
sSummaryInfo.updateSdkMap.put(filePath, set)
}
set.add(scanSdkInfo)
}
static void addComponentInfo(String filePath, @NonNull ScanComponentInfo componentInfo) {
Set<ScanComponentInfo> set = sSummaryInfo.updateComponentMap.get(filePath)
if (set == null) {
set = new HashSet<>()
sSummaryInfo.updateComponentMap.put(filePath, set)
}
set.add(componentInfo)
}
static void removedFile(String path) {
sSummaryInfo.removedFileSet.add(path)
}
static ScanSummaryInfo updateSummaryInfo(ScanSummaryInfo cacheSummary) {
if (sSummaryInfo.inputFilePath != null) {
cacheSummary.inputFilePath = sSummaryInfo.inputFilePath
}
if (sSummaryInfo.outputFilePath != null) {
cacheSummary.outputFilePath = sSummaryInfo.outputFilePath
}
sSummaryInfo.removedFileSet.each {
cacheSummary.updateSdkMap.remove(it)
cacheSummary.updateComponentMap.remove(it)
}
sSummaryInfo.updateSdkMap.each {
cacheSummary.updateSdkMap.put(it.key, it.value)
}
sSummaryInfo.updateComponentMap.each {
cacheSummary.updateComponentMap.put(it.key, it.value)
}
cacheSummary.updateSdkMap.each {
if (it != null && !it.value.isEmpty()) {
sSdkInfo.addAll(it.value)
}
}
cacheSummary.updateComponentMap.each {
if (it != null && !it.value.isEmpty()) {
sComponentInfo.addAll(it.value)
}
}
return cacheSummary
}
static void clearSummaryInfo() {
// TODO clear data
sSummaryInfo = new ScanSummaryInfo()
}
}

View File

@@ -0,0 +1,35 @@
package com.plugin.component.transform.info;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* author : linzheng
* e-mail : linzheng@corp.netease.com
* time : 2019/10/22
* desc :
* version: 1.0
*/
public class ScanSummaryInfo {
public String inputFilePath;
public String outputFilePath;
Map<String, Set<ScanSdkInfo>> updateSdkMap = new HashMap<>();
Map<String, Set<ScanComponentInfo>> updateComponentMap = new HashMap<>();
Set<String> removedFileSet = new HashSet<>();
}

View File

@@ -0,0 +1,694 @@
package com.plugin.component.utils;
import com.android.annotations.NonNull;
import com.android.ddmlib.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2017/05/24
* desc : utils about disk cache
* </pre>
*/
public final class CacheDiskUtils {
private static final long DEFAULT_MAX_SIZE = Long.MAX_VALUE;
private static final int DEFAULT_MAX_COUNT = Integer.MAX_VALUE;
private static final String CACHE_PREFIX = "cdu_";
private static final String TYPE_BYTE = "by_";
private static final String TYPE_STRING = "st_";
private static final String TYPE_JSON_OBJECT = "jo_";
private static final String TYPE_JSON_ARRAY = "ja_";
private static final String TYPE_BITMAP = "bi_";
private static final String TYPE_DRAWABLE = "dr_";
private static final String TYPE_PARCELABLE = "pa_";
private static final String TYPE_SERIALIZABLE = "se_";
private static final Map<String, CacheDiskUtils> CACHE_MAP = new HashMap<>();
private final String mCacheKey;
private final File mCacheDir;
private final long mMaxSize;
private final int mMaxCount;
private DiskCacheManager mDiskCacheManager;
/**
* Return the single {@link CacheDiskUtils} instance.
* <p>cache size: unlimited</p>
* <p>cache count: unlimited</p>
*
* @param cacheDir The directory of cache.
* @return the single {@link CacheDiskUtils} instance
*/
public static CacheDiskUtils getInstance(@NonNull final File cacheDir) {
return getInstance(cacheDir, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);
}
/**
* Return the single {@link CacheDiskUtils} instance.
*
* @param cacheDir The directory of cache.
* @param maxSize The max size of cache, in bytes.
* @param maxCount The max count of cache.
* @return the single {@link CacheDiskUtils} instance
*/
public static CacheDiskUtils getInstance(@NonNull final File cacheDir,
final long maxSize,
final int maxCount) {
final String cacheKey = cacheDir.getAbsoluteFile() + "_" + maxSize + "_" + maxCount;
CacheDiskUtils cache = CACHE_MAP.get(cacheKey);
if (cache == null) {
synchronized (CacheDiskUtils.class) {
cache = CACHE_MAP.get(cacheKey);
if (cache == null) {
cache = new CacheDiskUtils(cacheKey, cacheDir, maxSize, maxCount);
CACHE_MAP.put(cacheKey, cache);
}
}
}
return cache;
}
private CacheDiskUtils(final String cacheKey,
final File cacheDir,
final long maxSize,
final int maxCount) {
mCacheKey = cacheKey;
mCacheDir = cacheDir;
mMaxSize = maxSize;
mMaxCount = maxCount;
}
private DiskCacheManager getDiskCacheManager() {
if (mCacheDir.exists()) {
if (mDiskCacheManager == null) {
mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);
}
} else {
if (mCacheDir.mkdirs()) {
mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);
} else {
Log.e("CacheDiskUtils", "can't make dirs in " + mCacheDir.getAbsolutePath());
}
}
return mDiskCacheManager;
}
@Override
public String toString() {
return mCacheKey + "@" + Integer.toHexString(hashCode());
}
///////////////////////////////////////////////////////////////////////////
// about bytes
///////////////////////////////////////////////////////////////////////////
/**
* Put bytes in cache.
*
* @param key The key of cache.
* @param value The value of cache.
*/
public void put(@NonNull final String key, final byte[] value) {
put(key, value, -1);
}
/**
* Put bytes in cache.
*
* @param key The key of cache.
* @param value The value of cache.
* @param saveTime The save time of cache, in seconds.
*/
public void put(@NonNull final String key, final byte[] value, final int saveTime) {
realPutBytes(TYPE_BYTE + key, value, saveTime);
}
private void realPutBytes(final String key, byte[] value, int saveTime) {
if (value == null) return;
DiskCacheManager diskCacheManager = getDiskCacheManager();
if (diskCacheManager == null) return;
if (saveTime >= 0) value = DiskCacheHelper.newByteArrayWithTime(saveTime, value);
File file = diskCacheManager.getFileBeforePut(key);
writeFileFromBytes(file, value);
diskCacheManager.updateModify(file);
diskCacheManager.put(file);
}
/**
* Return the bytes in cache.
*
* @param key The key of cache.
* @return the bytes if cache exists or null otherwise
*/
public byte[] getBytes(@NonNull final String key) {
return getBytes(key, null);
}
/**
* Return the bytes in cache.
*
* @param key The key of cache.
* @param defaultValue The default value if the cache doesn't exist.
* @return the bytes if cache exists or defaultValue otherwise
*/
public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {
return realGetBytes(TYPE_BYTE + key, defaultValue);
}
private byte[] realGetBytes(@NonNull final String key) {
return realGetBytes(key, null);
}
private byte[] realGetBytes(@NonNull final String key, final byte[] defaultValue) {
DiskCacheManager diskCacheManager = getDiskCacheManager();
if (diskCacheManager == null) return defaultValue;
final File file = diskCacheManager.getFileIfExists(key);
if (file == null) return defaultValue;
byte[] data = readFile2Bytes(file);
if (DiskCacheHelper.isDue(data)) {
diskCacheManager.removeByKey(key);
return defaultValue;
}
diskCacheManager.updateModify(file);
return DiskCacheHelper.getDataWithoutDueTime(data);
}
///////////////////////////////////////////////////////////////////////////
// about String
///////////////////////////////////////////////////////////////////////////
/**
* Put string value in cache.
*
* @param key The key of cache.
* @param value The value of cache.
*/
public void put(@NonNull final String key, final String value) {
put(key, value, -1);
}
/**
* Put string value in cache.
*
* @param key The key of cache.
* @param value The value of cache.
* @param saveTime The save time of cache, in seconds.
*/
public void put(@NonNull final String key, final String value, final int saveTime) {
realPutBytes(TYPE_STRING + key, string2Bytes(value), saveTime);
}
/**
* Return the string value in cache.
*
* @param key The key of cache.
* @return the string value if cache exists or null otherwise
*/
public String getString(@NonNull final String key) {
return getString(key, null);
}
/**
* Return the string value in cache.
*
* @param key The key of cache.
* @param defaultValue The default value if the cache doesn't exist.
* @return the string value if cache exists or defaultValue otherwise
*/
public String getString(@NonNull final String key, final String defaultValue) {
byte[] bytes = realGetBytes(TYPE_STRING + key);
if (bytes == null) return defaultValue;
return bytes2String(bytes);
}
/**
* Put serializable in cache.
*
* @param key The key of cache.
* @param value The value of cache.
*/
public void put(@NonNull final String key, final Serializable value) {
put(key, value, -1);
}
/**
* Put serializable in cache.
*
* @param key The key of cache.
* @param value The value of cache.
* @param saveTime The save time of cache, in seconds.
*/
public void put(@NonNull final String key, final Serializable value, final int saveTime) {
realPutBytes(TYPE_SERIALIZABLE + key, serializable2Bytes(value), saveTime);
}
/**
* Return the serializable in cache.
*
* @param key The key of cache.
* @return the bitmap if cache exists or null otherwise
*/
public Object getSerializable(@NonNull final String key) {
return getSerializable(key, null);
}
/**
* Return the serializable in cache.
*
* @param key The key of cache.
* @param defaultValue The default value if the cache doesn't exist.
* @return the bitmap if cache exists or defaultValue otherwise
*/
public Object getSerializable(@NonNull final String key, final Object defaultValue) {
byte[] bytes = realGetBytes(TYPE_SERIALIZABLE + key);
if (bytes == null) return defaultValue;
return bytes2Object(bytes);
}
/**
* Return the size of cache, in bytes.
*
* @return the size of cache, in bytes
*/
public long getCacheSize() {
DiskCacheManager diskCacheManager = getDiskCacheManager();
if (diskCacheManager == null) return 0;
return diskCacheManager.getCacheSize();
}
/**
* Return the count of cache.
*
* @return the count of cache
*/
public int getCacheCount() {
DiskCacheManager diskCacheManager = getDiskCacheManager();
if (diskCacheManager == null) return 0;
return diskCacheManager.getCacheCount();
}
/**
* Remove the cache by key.
*
* @param key The key of cache.
* @return {@code true}: success<br>{@code false}: fail
*/
public boolean remove(@NonNull final String key) {
DiskCacheManager diskCacheManager = getDiskCacheManager();
if (diskCacheManager == null) return true;
return diskCacheManager.removeByKey(TYPE_BYTE + key)
&& diskCacheManager.removeByKey(TYPE_STRING + key)
&& diskCacheManager.removeByKey(TYPE_JSON_OBJECT + key)
&& diskCacheManager.removeByKey(TYPE_JSON_ARRAY + key)
&& diskCacheManager.removeByKey(TYPE_BITMAP + key)
&& diskCacheManager.removeByKey(TYPE_DRAWABLE + key)
&& diskCacheManager.removeByKey(TYPE_PARCELABLE + key)
&& diskCacheManager.removeByKey(TYPE_SERIALIZABLE + key);
}
/**
* Clear all of the cache.
*
* @return {@code true}: success<br>{@code false}: fail
*/
public boolean clear() {
DiskCacheManager diskCacheManager = getDiskCacheManager();
if (diskCacheManager == null) return true;
return diskCacheManager.clear();
}
private static final class DiskCacheManager {
private final AtomicLong cacheSize;
private final AtomicInteger cacheCount;
private final long sizeLimit;
private final int countLimit;
private final Map<File, Long> lastUsageDates
= Collections.synchronizedMap(new HashMap<File, Long>());
private final File cacheDir;
private final Thread mThread;
private DiskCacheManager(final File cacheDir, final long sizeLimit, final int countLimit) {
this.cacheDir = cacheDir;
this.sizeLimit = sizeLimit;
this.countLimit = countLimit;
cacheSize = new AtomicLong();
cacheCount = new AtomicInteger();
mThread = new Thread(new Runnable() {
@Override
public void run() {
int size = 0;
int count = 0;
final File[] cachedFiles = cacheDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(CACHE_PREFIX);
}
});
if (cachedFiles != null) {
for (File cachedFile : cachedFiles) {
size += cachedFile.length();
count += 1;
lastUsageDates.put(cachedFile, cachedFile.lastModified());
}
cacheSize.getAndAdd(size);
cacheCount.getAndAdd(count);
}
}
});
mThread.start();
}
private long getCacheSize() {
wait2InitOk();
return cacheSize.get();
}
private int getCacheCount() {
wait2InitOk();
return cacheCount.get();
}
private File getFileBeforePut(final String key) {
wait2InitOk();
File file = new File(cacheDir, getCacheNameByKey(key));
if (file.exists()) {
cacheCount.addAndGet(-1);
cacheSize.addAndGet(-file.length());
}
return file;
}
private void wait2InitOk() {
try {
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private File getFileIfExists(final String key) {
File file = new File(cacheDir, getCacheNameByKey(key));
if (!file.exists()) return null;
return file;
}
private String getCacheNameByKey(final String key) {
return CACHE_PREFIX + key.substring(0, 3) + key.substring(3).hashCode();
}
private void put(final File file) {
cacheCount.addAndGet(1);
cacheSize.addAndGet(file.length());
while (cacheCount.get() > countLimit || cacheSize.get() > sizeLimit) {
cacheSize.addAndGet(-removeOldest());
cacheCount.addAndGet(-1);
}
}
private void updateModify(final File file) {
Long millis = System.currentTimeMillis();
file.setLastModified(millis);
lastUsageDates.put(file, millis);
}
private boolean removeByKey(final String key) {
File file = getFileIfExists(key);
if (file == null) return true;
if (!file.delete()) return false;
cacheSize.addAndGet(-file.length());
cacheCount.addAndGet(-1);
lastUsageDates.remove(file);
return true;
}
private boolean clear() {
File[] files = cacheDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(CACHE_PREFIX);
}
});
if (files == null || files.length <= 0) return true;
boolean flag = true;
for (File file : files) {
if (!file.delete()) {
flag = false;
continue;
}
cacheSize.addAndGet(-file.length());
cacheCount.addAndGet(-1);
lastUsageDates.remove(file);
}
if (flag) {
lastUsageDates.clear();
cacheSize.set(0);
cacheCount.set(0);
}
return flag;
}
/**
* Remove the oldest files.
*
* @return the size of oldest files, in bytes
*/
private long removeOldest() {
if (lastUsageDates.isEmpty()) return 0;
Long oldestUsage = Long.MAX_VALUE;
File oldestFile = null;
Set<Map.Entry<File, Long>> entries = lastUsageDates.entrySet();
synchronized (lastUsageDates) {
for (Map.Entry<File, Long> entry : entries) {
Long lastValueUsage = entry.getValue();
if (lastValueUsage < oldestUsage) {
oldestUsage = lastValueUsage;
oldestFile = entry.getKey();
}
}
}
if (oldestFile == null) return 0;
long fileSize = oldestFile.length();
if (oldestFile.delete()) {
lastUsageDates.remove(oldestFile);
return fileSize;
}
return 0;
}
}
private static final class DiskCacheHelper {
static final int TIME_INFO_LEN = 14;
private static byte[] newByteArrayWithTime(final int second, final byte[] data) {
byte[] time = createDueTime(second).getBytes();
byte[] content = new byte[time.length + data.length];
System.arraycopy(time, 0, content, 0, time.length);
System.arraycopy(data, 0, content, time.length, data.length);
return content;
}
/**
* Return the string of due time.
*
* @param seconds The seconds.
* @return the string of due time
*/
private static String createDueTime(final int seconds) {
return String.format(
Locale.getDefault(), "_$%010d$_",
System.currentTimeMillis() / 1000 + seconds
);
}
private static boolean isDue(final byte[] data) {
long millis = getDueTime(data);
return millis != -1 && System.currentTimeMillis() > millis;
}
private static long getDueTime(final byte[] data) {
if (hasTimeInfo(data)) {
String millis = new String(copyOfRange(data, 2, 12));
try {
return Long.parseLong(millis) * 1000;
} catch (NumberFormatException e) {
return -1;
}
}
return -1;
}
private static byte[] getDataWithoutDueTime(final byte[] data) {
if (hasTimeInfo(data)) {
return copyOfRange(data, TIME_INFO_LEN, data.length);
}
return data;
}
private static byte[] copyOfRange(final byte[] original, final int from, final int to) {
int newLength = to - from;
if (newLength < 0) throw new IllegalArgumentException(from + " > " + to);
byte[] copy = new byte[newLength];
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}
private static boolean hasTimeInfo(final byte[] data) {
return data != null
&& data.length >= TIME_INFO_LEN
&& data[0] == '_'
&& data[1] == '$'
&& data[12] == '$'
&& data[13] == '_';
}
}
///////////////////////////////////////////////////////////////////////////
// other utils methods
///////////////////////////////////////////////////////////////////////////
private static byte[] string2Bytes(final String string) {
if (string == null) return null;
return string.getBytes();
}
private static String bytes2String(final byte[] bytes) {
if (bytes == null) return null;
return new String(bytes);
}
private static byte[] serializable2Bytes(final Serializable serializable) {
if (serializable == null) return null;
ByteArrayOutputStream baos;
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(baos = new ByteArrayOutputStream());
oos.writeObject(serializable);
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Object bytes2Object(final byte[] bytes) {
if (bytes == null) return null;
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void writeFileFromBytes(final File file, final byte[] bytes) {
FileOutputStream outputStream = null;
FileChannel fc = null;
try {
outputStream = new FileOutputStream(file, false);
fc = outputStream.getChannel();
fc.write(ByteBuffer.wrap(bytes));
fc.force(true);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fc != null) {
fc.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static byte[] readFile2Bytes(final File file) {
RandomAccessFile accessFile = null;
FileChannel fc = null;
FileInputStream inputStream = null;
try {
accessFile = new RandomAccessFile(file, "r");
fc = accessFile.getChannel();
int size = (int) fc.size();
byte[] data = new byte[size];
fc.read(ByteBuffer.wrap(data));
return data;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if (fc != null) {
fc.close();
}
if (accessFile != null) {
accessFile.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static boolean isSpace(final String s) {
if (s == null) return true;
for (int i = 0, len = s.length(); i < len; ++i) {
if (!Character.isWhitespace(s.charAt(i))) {
return false;
}
}
return true;
}
}

View File

@@ -61,8 +61,8 @@ class PinUtils {
}
}
pinConfiguration.productFlavorInfo.combinedProductFlavors.each {
mergeAndroidManifest(androidTest + Utils.upperCase(it), pinConfiguration, startTaskState)
mergeAndroidManifest(androidTest + Utils.upperCase(it) + 'Debug', pinConfiguration, startTaskState)
mergeAndroidManifest(project,androidTest + Utils.upperCase(it), pinConfiguration, startTaskState)
mergeAndroidManifest(project,androidTest + Utils.upperCase(it) + 'Debug', pinConfiguration, startTaskState)
}
}

View File

@@ -1,2 +0,0 @@
class Base {
}

View File

@@ -0,0 +1,3 @@
package com.plugin.pin.base;
class Base {
}

View File

@@ -1,2 +0,0 @@
class Common {
}

View File

@@ -0,0 +1,3 @@
package com.plugin.pin.common;
class Common {
}

View File

@@ -1,2 +0,0 @@
class Home {
}

View File

@@ -0,0 +1,3 @@
package com.plugin.pin.home;
class Home {
}

View File

@@ -1,5 +1,5 @@
//发布调试 core
//include ':component-core'
include ':component-core'
//
//发布插件
//include ':component-plugin'