plugin选择性重载基础plugin扩张功能
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,7 +4,7 @@ apply plugin: 'maven'
|
||||
|
||||
group = 'com.effective.plugins'
|
||||
archivesBaseName = 'component'
|
||||
version = '1.0.4'
|
||||
version = '1.0.4-beta'
|
||||
|
||||
//设置本地发布路径
|
||||
uploadArchives {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class Base {
|
||||
}
|
||||
3
pins/p_base/src/main/java/com/plugin/pin/base/Base.java
Normal file
3
pins/p_base/src/main/java/com/plugin/pin/base/Base.java
Normal file
@@ -0,0 +1,3 @@
|
||||
package com.plugin.pin.base;
|
||||
class Base {
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
class Common {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.plugin.pin.common;
|
||||
class Common {
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
class Home {
|
||||
}
|
||||
3
pins/p_home/src/main/java/com/plugin/pin/home/Home.java
Normal file
3
pins/p_home/src/main/java/com/plugin/pin/home/Home.java
Normal file
@@ -0,0 +1,3 @@
|
||||
package com.plugin.pin.home;
|
||||
class Home {
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//发布调试 core
|
||||
//include ':component-core'
|
||||
include ':component-core'
|
||||
//
|
||||
//发布插件
|
||||
//include ':component-plugin'
|
||||
|
||||
Reference in New Issue
Block a user