提交 f6367ba8 编写于 作者: B Blankj

see 05/03 log

上级 93315a67
* `20/05/03` [add] Publish bus plugin v2.5. Publish api plugin v1.3. Publish. Publish 1.28.4.
* `20/04/30` [add] BaseItem support partialUpdate.
* `20/04/29` [add] Publish plugin lib com.blankj:base-transform:1.0.
* `20/04/28` [fix] LanguageUtils#applyLanguage.
* `20/04/27` [fix] BarUtils#isNavBarVisible.
* `20/04/26` [fix] Utils#init fit tinker. Publish 1.28.3.
* `20/04/25` [fix] UriUtils#uri2File Unknown URI. Publish 1.28.2.
* `20/04/24` [add] SnackbarUtils support show on the top; UriUtils#uri2InputStream.
......
......@@ -15,7 +15,7 @@ class Config {
static minSdkVersion = 14
static targetSdkVersion = 29
static versionCode = 1_028_002
static versionName = '1.28.3'// E.g. 1.9.72 => 1,009,072
static versionName = '1.28.4'// E.g. 1.9.72 => 1,009,072
// lib version
static gradlePluginVersion = '3.5.0'
......@@ -25,8 +25,9 @@ class Config {
static depConfig = [
/*Never delete this line*/
/*Generated by "config.json"*/
plugin_api_gradle_plugin : new DepConfig(true , true , ":plugin:api-gradle-plugin"),
plugin_bus_gradle_plugin : new DepConfig(true , true , ":plugin:bus-gradle-plugin"),
plugin_api_gradle_plugin : new DepConfig(false, true , ":plugin:api-gradle-plugin"),
plugin_bus_gradle_plugin : new DepConfig(false, true , ":plugin:bus-gradle-plugin"),
plugin_lib_base_transform : new DepConfig(false, true , ":plugin:lib:base-transform", "com.blankj:base-transform:1.0"),
feature_mock : new DepConfig(false, true , ":feature:mock"),
feature_launcher_app : new DepConfig(true , true , ":feature:launcher:app"),
feature_main_app : new DepConfig(false, true , ":feature:main:app"),
......@@ -52,12 +53,12 @@ class Config {
// 上传新版本插件更新 pluginPath 中的版本号,并设置 isApply = false
// 通过 mavenLocal 上传本地版本,设置 isApply = true 即可应用插件来调试,最后通过 bintrayUpload 来发布插件
plugin_api : new DepConfig(isApply: true, useLocal: false, pluginPath: "com.blankj:api-gradle-plugin:1.2", pluginId: "com.blankj.api"),
//./gradlew plugin:plugin_api-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew plugin:plugin_api-gradle-plugin:bintrayUpload // 上传到 jcenter
plugin_bus : new DepConfig(isApply: true, useLocal: false, pluginPath: "com.blankj:bus-gradle-plugin:2.4", pluginId: "com.blankj.bus"),
//./gradlew plugin:plugin_bus-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew plugin:plugin_bus-gradle-plugin:bintrayUpload // 上传到 jcenter
plugin_api : new DepConfig(isApply: true, useLocal: false, pluginPath: "com.blankj:api-gradle-plugin:1.3", pluginId: "com.blankj.api"),
//./gradlew clean plugin:plugin_api-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew clean plugin:plugin_api-gradle-plugin:bintrayUpload // 上传到 jcenter
plugin_bus : new DepConfig(isApply: true, useLocal: false, pluginPath: "com.blankj:bus-gradle-plugin:2.5", pluginId: "com.blankj.bus"),
//./gradlew clean plugin:plugin_bus-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew clean plugin:plugin_bus-gradle-plugin:bintrayUpload // 上传到 jcenter
support_appcompat_v7 : new DepConfig("com.android.support:appcompat-v7:$supportVersion"),
support_design : new DepConfig("com.android.support:design:$supportVersion"),
......
......@@ -47,7 +47,7 @@ class ConfigUtils {
void beforeEvaluate(Project project) {
// 在 project 的 build.gradle 前 do sth.
if (project.subprojects.isEmpty()) {
if (project.name.startsWith("plugin")) {
if (project.path.startsWith(":plugin")) {
return
}
if (project.name.endsWith("_app")) {
......
......@@ -32,7 +32,7 @@ class TaskDurationUtils {
@Override
void afterExecute(Task task, TaskState state) {
def exeDuration = System.currentTimeMillis() - task.ext.startTime
if (exeDuration >= 100) {
if (exeDuration >= 500) {
taskInfoList.add(new TaskInfo(task: task, exeDuration: exeDuration))
}
}
......
......@@ -5,8 +5,9 @@
"pkgConfig": [],
"proConfigDesc": "proConfig 配置的是使用本地还是仓库,优先级低于 appConfig 和 pkgConfig",
"proConfig": [
{"isApply": true, "useLocal": true, "localPath": ":plugin:api-gradle-plugin"},
{"isApply": true, "useLocal": true, "localPath": ":plugin:bus-gradle-plugin"},
{"isApply": false, "useLocal": true, "localPath": ":plugin:api-gradle-plugin"},
{"isApply": false, "useLocal": true, "localPath": ":plugin:bus-gradle-plugin"},
{"isApply": false, "useLocal": true, "localPath": ":plugin:lib:base-transform", "remotePath": "com.blankj:base-transform:1.0"},
{"isApply": true, "useLocal": true, "localPath": ":feature:mock"},
{"isApply": true, "useLocal": true, "localPath": ":feature:launcher:app"},
{"isApply": true, "useLocal": true, "localPath": ":feature:main:app"},
......
......@@ -86,6 +86,7 @@ object DialogHelper {
dialog.dialog.setOnShowListener(DialogInterface.OnShowListener {
KeyboardUtils.fixAndroidBug5497(dialog.dialog.window!!)
KeyboardUtils.showSoftInput()
})
}
......
......@@ -54,6 +54,7 @@ def configMaven(Project project, PublishExtension ext) {
}
project.tasks.create("mavenLocal", Upload) {
group("publishing")
configuration = project.configurations.archives
repositories.mavenDeployer {
......
......@@ -38,6 +38,9 @@ public abstract class BaseItem<T extends BaseItem> {
public abstract void bind(@NonNull final ItemViewHolder holder, final int position);
public void partialUpdate(List<Object> payloads) {
}
void bindViewHolder(@NonNull final ItemViewHolder holder, final int position) {
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
......
......@@ -53,6 +53,15 @@ public class BaseItemAdapter<Item extends BaseItem> extends RecyclerView.Adapter
mItems.get(position).bindViewHolder(holder, position);
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
return;
}
mItems.get(position).partialUpdate(payloads);
}
@Override
public int getItemCount() {
return mItems.size();
......@@ -151,14 +160,41 @@ public class BaseItemAdapter<Item extends BaseItem> extends RecyclerView.Adapter
///////////////////////////////////////////////////////////////////////////
public void updateItem(@NonNull final Item item) {
updateItems(item, 1, null);
}
public void updateItem(@IntRange(from = 0) final int index) {
updateItems(index, 1, null);
}
public void updateItem(@NonNull final Item item, Object payload) {
updateItems(item, 1, payload);
}
public void updateItem(@IntRange(from = 0) final int index, Object payload) {
updateItems(index, 1, payload);
}
public void updateItems(@NonNull final Item item, int itemCount) {
int itemIndex = mItems.indexOf(item);
if (itemIndex != -1) {
notifyItemChanged(itemIndex);
updateItems(itemIndex, itemCount);
}
}
public void updateItem(@IntRange(from = 0) final int index) {
notifyItemChanged(index);
public void updateItems(@IntRange(from = 0) final int index, int itemCount) {
updateItems(index, itemCount, null);
}
public void updateItems(@NonNull final Item item, int itemCount, Object payload) {
int itemIndex = mItems.indexOf(item);
if (itemIndex != -1) {
updateItems(itemIndex, itemCount, payload);
}
}
public void updateItems(@IntRange(from = 0) final int index, int itemCount, Object payload) {
notifyItemRangeChanged(index, itemCount, payload);
}
public void addItem(@NonNull final Item item) {
......
......@@ -2,10 +2,10 @@
Gradle:
```groovy
implementation 'com.blankj:utilcode:1.28.3'
implementation 'com.blankj:utilcode:1.28.4'
// if u use AndroidX, use the following
implementation 'com.blankj:utilcodex:1.28.3'
implementation 'com.blankj:utilcodex:1.28.4'
```
......
......@@ -2,10 +2,10 @@
Gradle:
```groovy
implementation 'com.blankj:utilcode:1.28.3'
implementation 'com.blankj:utilcode:1.28.4'
// if u use AndroidX, use the following
implementation 'com.blankj:utilcodex:1.28.3'
implementation 'com.blankj:utilcodex:1.28.4'
```
......
......@@ -515,7 +515,7 @@ public final class BarUtils {
final View child = decorView.getChildAt(i);
final int id = child.getId();
if (id != View.NO_ID) {
String resourceEntryName = Resources.getSystem().getResourceEntryName(id);
String resourceEntryName = getResNameById(id);
if ("navigationBarBackground".equals(resourceEntryName)) {
child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
}
......@@ -556,7 +556,7 @@ public final class BarUtils {
final View child = decorView.getChildAt(i);
final int id = child.getId();
if (id != View.NO_ID) {
String resourceEntryName = Resources.getSystem().getResourceEntryName(id);
String resourceEntryName = getResNameById(id);
if ("navigationBarBackground".equals(resourceEntryName)
&& child.getVisibility() == View.VISIBLE) {
isVisible = true;
......@@ -571,6 +571,14 @@ public final class BarUtils {
return isVisible;
}
private static String getResNameById(int id) {
try {
return Utils.getApp().getResources().getResourceEntryName(id);
} catch (Exception ignore) {
return "";
}
}
/**
* Set the navigation bar's color.
*
......
......@@ -97,14 +97,23 @@ public final class KeyboardUtils {
* @param activity The activity.
*/
public static void hideSoftInput(@NonNull final Activity activity) {
View view = activity.getCurrentFocus();
hideSoftInput(activity.getWindow());
}
/**
* Hide the soft input.
*
* @param window The window.
*/
public static void hideSoftInput(@NonNull final Window window) {
View view = window.getCurrentFocus();
if (view == null) {
View decorView = activity.getWindow().getDecorView();
View decorView = window.getDecorView();
View focusView = decorView.findViewWithTag("keyboardTagView");
if (focusView == null) {
view = new EditText(activity);
view = new EditText(window.getContext());
view.setTag("keyboardTagView");
((ViewGroup) decorView).addView(view, 1, 1);
((ViewGroup) decorView).addView(view, 0, 0);
} else {
view = focusView;
}
......
......@@ -69,7 +69,7 @@ public class LanguageUtils {
* @param locale The language of locale.
*/
public static void applyLanguage(@NonNull final Locale locale) {
if (isAppliedLanguage()) return;
if (isAppliedLanguage(locale)) return;
applyLanguage(locale, "", false, false);
}
......@@ -115,9 +115,7 @@ public class LanguageUtils {
if (isFollowSystem) {
UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, VALUE_FOLLOW_SYSTEM);
} else {
String localLanguage = locale.getLanguage();
String localCountry = locale.getCountry();
UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, localLanguage + "$" + localCountry);
UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, locale2String(locale));
}
updateLanguage(Utils.getApp(), locale);
......@@ -149,6 +147,25 @@ public class LanguageUtils {
return !TextUtils.isEmpty(UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE));
}
/**
* Return whether applied the language by {@link LanguageUtils}.
*
* @param locale The locale.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isAppliedLanguage(Locale locale) {
final String spLocale = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);
if (TextUtils.isEmpty(spLocale)) {
return false;
}
if (VALUE_FOLLOW_SYSTEM.equals(spLocale)) {
return false;
}
Locale settingLocale = string2Locale(spLocale);
if (settingLocale == null) return false;
return isSameLocale(settingLocale, locale);
}
/**
* Return the locale.
*
......@@ -159,7 +176,7 @@ public class LanguageUtils {
}
static void applyLanguage(@NonNull final Activity activity) {
final String spLocale = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);
String spLocale = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);
if (TextUtils.isEmpty(spLocale)) {
return;
}
......@@ -171,17 +188,28 @@ public class LanguageUtils {
return;
}
String[] language_country = spLocale.split("\\$");
if (language_country.length != 2) {
Log.e("LanguageUtils", "The string of " + spLocale + " is not in the correct format.");
return;
}
Locale settingLocale = new Locale(language_country[0], language_country[1]);
Locale settingLocale = string2Locale(spLocale);
if (settingLocale == null) return;
updateLanguage(Utils.getApp(), settingLocale);
updateLanguage(activity, settingLocale);
}
private static String locale2String(Locale locale) {
String localLanguage = locale.getLanguage();
String localCountry = locale.getCountry();
return localLanguage + "$" + localCountry;
}
private static Locale string2Locale(String str) {
String[] language_country = str.split("\\$");
if (language_country.length != 2) {
Log.e("LanguageUtils", "The string of " + str + " is not in the correct format.");
return null;
}
return new Locale(language_country[0], language_country[1]);
}
private static void updateLanguage(final Context context, Locale locale) {
Resources resources = context.getResources();
Configuration config = resources.getConfiguration();
......@@ -207,8 +235,8 @@ public class LanguageUtils {
resources.updateConfiguration(config, dm);
}
private static boolean isSameLocale(Locale locale, Locale contextLocale) {
return UtilsBridge.equals(contextLocale.getLanguage(), locale.getLanguage())
&& UtilsBridge.equals(contextLocale.getCountry(), locale.getCountry());
private static boolean isSameLocale(Locale l0, Locale l1) {
return UtilsBridge.equals(l1.getLanguage(), l0.getLanguage())
&& UtilsBridge.equals(l1.getCountry(), l0.getCountry());
}
}
......@@ -447,7 +447,6 @@ public final class PermissionUtils {
@Override
public void onDestroy(final UtilsTransActivity activity) {
//如果是unity调用,app从后台切回,当前activity会被强制关闭,此时正常流程不会触发,所以在这里检测
if (currentRequestCode != -1) {
checkRequestCallback(currentRequestCode);
currentRequestCode = -1;
......@@ -471,17 +470,12 @@ public final class PermissionUtils {
sSimpleCallback4WriteSettings = null;
} else if (requestCode == TYPE_DRAW_OVERLAYS) {
if (sSimpleCallback4DrawOverlays == null) return;
UtilsBridge.runOnUiThreadDelayed(new Runnable() {
@Override
public void run() {
if (isGrantedDrawOverlays()) {
sSimpleCallback4DrawOverlays.onGranted();
} else {
sSimpleCallback4DrawOverlays.onDenied();
}
sSimpleCallback4DrawOverlays = null;
}
}, 100);
if (isGrantedDrawOverlays()) {
sSimpleCallback4DrawOverlays.onGranted();
} else {
sSimpleCallback4DrawOverlays.onDenied();
}
sSimpleCallback4DrawOverlays = null;
}
}
}
......
# Change Log
## v1.2(2020/11/30)
## v1.3(2020/04/29)
重构使用 base-transform
## v1.2(2019/11/30)
去除 gradle 版本依赖的问题
## v1.1(2019/10/30)
......
plugins {
id 'com.gradle.plugin-publish' version "0.10.0"
}
apply {
plugin "groovy"
plugin "java-gradle-plugin"
......@@ -19,6 +15,7 @@ gradlePlugin {
dependencies {
compileOnly Config.depConfig.plugin_gradle.dep
implementation Config.depConfig.commons_io.dep
implementation Config.depConfig.plugin_lib_base_transform.dep
implementation gradleApi()
implementation localGroovy()
......@@ -34,20 +31,6 @@ sourceSets {
}
}
pluginBundle {
website = 'https://github.com/Blankj/AndroidUtilCode'
vcsUrl = 'https://github.com/Blankj/AndroidUtilCode.git'
description = 'Plugin for ApiUtils.'
tags = ['gradle', 'plugin', 'api', 'ApiUtils', 'asm']
plugins {
apiPlugin {
// id is captured from java-gradle-plugin configuration
displayName = 'Plugin for ApiUtils.'
}
}
}
apply from: "${rootDir.path}/gradle/publish.gradle"
publish {
name = "ApiPlugin"
......@@ -57,6 +40,5 @@ publish {
website = "https://github.com/Blankj/AndroidUtilCode"
}
//./gradlew plugin:plugin_api-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew plugin:plugin_api-gradle-plugin:bintrayUpload // 上传到 jcenter
//./gradlew plugin:plugin_api-gradle-plugin:publishPlugins // 上传到 gradle 插件库中
//./gradlew clean plugin:plugin_api-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew clean plugin:plugin_api-gradle-plugin:bintrayUpload // 上传到 jcenter
package com.blankj.api
import com.blankj.api.util.ZipUtils
import com.blankj.base_transform.util.ZipUtils
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
......
package com.blankj.api
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.blankj.api.util.LogUtils
import com.android.build.api.transform.JarInput
import com.blankj.base_transform.BaseTransformPlugin
import com.blankj.base_transform.util.JsonUtils
import org.apache.commons.io.FileUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
class ApiPlugin implements Plugin<Project> {
import java.util.regex.Pattern
class ApiPlugin extends BaseTransformPlugin<ApiExtension> {
String apiUtilsClass
File jsonFile
File apiUtilsTransformFile
Map<String, ApiInfo> apiImplMap = [:]
List<String> apiClasses = []
@Override
String getPluginName() {
return Config.EXT_NAME
}
@Override
void onScanStarted() {
apiUtilsClass = ext.apiUtilsClass
if (apiUtilsClass.trim().equals("")) {
throw new Exception("ApiExtension's apiUtilsClass is empty.")
}
jsonFile = new File(mProject.projectDir.getAbsolutePath(), "__api__.json")
FileUtils.write(jsonFile, "{}")
}
@Override
void apply(Project project) {
if (project.plugins.hasPlugin(AppPlugin)) {
LogUtils.init(project)
LogUtils.l('project(' + project.toString() + ') apply api gradle plugin!')
project.extensions.create(Config.EXT_NAME, ApiExtension)
def android = project.extensions.getByType(AppExtension)
android.registerTransform(new ApiTransform(project))
boolean isIgnoreScan(JarInput input) {
def jarName = input.name
if (jarName.contains("utilcode")) {
return false
}
if (ext.onlyScanLibRegex != null && ext.onlyScanLibRegex.trim().length() > 0) {
return !Pattern.matches(ext.onlyScanLibRegex, jarName)
}
if (ext.jumpScanLibRegex != null && ext.jumpScanLibRegex.trim().length() > 0) {
if (Pattern.matches(ext.jumpScanLibRegex, jarName)) {
return true
}
}
for (exclude in Config.EXCLUDE_LIBS_START_WITH) {
if (jarName.startsWith(exclude)) {
return true
}
}
return false
}
@Override
void scanClassFile(File classFile, String className, File originScannedJarOrDir) {
if (apiUtilsClass == className) {
apiUtilsTransformFile = originScannedJarOrDir
log("<ApiUtils transform file>: $originScannedJarOrDir")
}
ClassReader cr = new ClassReader(classFile.bytes);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ApiClassVisitor(cw, apiImplMap, apiClasses, apiUtilsClass);
try {
cr.accept(cv, ClassReader.SKIP_FRAMES);
} catch (Exception ignore) {
ignore.printStackTrace()
}
}
@Override
void onScanFinished() {
if (apiUtilsTransformFile != null) {
if (apiClasses.isEmpty()) {
log("no api.")
} else {
Map implApis = [:]
List<String> noImplApis = []
apiImplMap.each { key, value ->
implApis.put(key, value.toString())
}
apiClasses.each {
if (!apiImplMap.containsKey(it)) {
noImplApis.add(it)
}
}
Map apiDetails = [:]
apiDetails.put("ApiUtilsClass", apiUtilsClass)
apiDetails.put("implApis", implApis)
apiDetails.put("noImplApis", noImplApis)
String apiJson = JsonUtils.getFormatJson(apiDetails)
log(jsonFile.toString() + ": " + apiJson)
FileUtils.write(jsonFile, apiJson)
if (noImplApis.size() > 0) {
if (ext.abortOnError) {
throw new Exception("u should impl these apis: " + noImplApis +
"\n u can check it in file: " + jsonFile.toString())
}
}
ApiInject.start(apiImplMap, apiUtilsTransformFile, apiUtilsClass)
}
} else {
throw new Exception("No ApiUtils of ${apiUtilsClass} in $mProject.")
}
}
}
\ No newline at end of file
package com.blankj.api
import com.blankj.api.util.LogUtils
import com.blankj.api.util.ZipUtils
import groovy.io.FileType
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
class ApiScan {
Map<String, ApiInfo> apiImplMap = [:]
List<String> apiClasses = []
File apiUtilsTransformFile
String apiUtilsClass
ApiScan(String apiUtilsClass) {
this.apiUtilsClass = apiUtilsClass
}
void scanJar(File jar) {
File tmp = new File(jar.getParent(), "temp_" + jar.getName())
List<File> unzipFile = ZipUtils.unzipFile(jar, tmp)
if (unzipFile != null && unzipFile.size() > 0) {
scanDir(tmp, jar)
FileUtils.forceDelete(tmp)
}
}
void scanDir(File root) {
scanDir(root, root)
}
void scanDir(File root, File source) {
if (!root.isDirectory()) return
String rootPath = root.getAbsolutePath()
if (!rootPath.endsWith(Config.FILE_SEP)) {
rootPath += Config.FILE_SEP
}
root.eachFileRecurse(FileType.FILES) { File file ->
def fileName = file.name
if (!fileName.endsWith('.class')
|| fileName.startsWith('R$')
|| fileName == 'R.class'
|| fileName == 'BuildConfig.class') {
return
}
def filePath = file.absolutePath
def packagePath = filePath.replace(rootPath, '')
def className = packagePath.replace(Config.FILE_SEP, ".")
// delete .class
className = className.substring(0, className.length() - 6)
if (apiUtilsClass == className) {
apiUtilsTransformFile = source
LogUtils.l("<ApiUtils transform file>: $source")
}
ClassReader cr = new ClassReader(file.bytes);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ApiClassVisitor(cw, apiImplMap, apiClasses, apiUtilsClass);
try {
cr.accept(cv, ClassReader.SKIP_FRAMES);
} catch (Exception ignore) {
ignore.printStackTrace()
}
}
}
}
package com.blankj.api
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.blankj.api.util.JsonUtils
import com.blankj.api.util.LogUtils
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import java.util.regex.Pattern
class ApiTransform extends Transform {
Project mProject;
ApiTransform(Project project) {
mProject = project
}
@Override
String getName() {
return "apiTransform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
LogUtils.l(getName() + " started")
long stTime = System.currentTimeMillis()
def ext = mProject[Config.EXT_NAME] as ApiExtension
LogUtils.l("apiExtension: $ext")
if (ext.apiUtilsClass.trim().equals("")) {
throw new Exception("ApiExtension's apiUtilsClass is empty.")
}
File jsonFile = new File(mProject.projectDir.getAbsolutePath(), "__api__.json")
FileUtils.write(jsonFile, "{}")
def inputs = transformInvocation.getInputs()
def referencedInputs = transformInvocation.getReferencedInputs()
def outputProvider = transformInvocation.getOutputProvider()
def isIncremental = transformInvocation.isIncremental()
outputProvider.deleteAll()
ApiScan apiScan = new ApiScan(ext.apiUtilsClass)
inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput dirInput ->// 遍历文件夹
File dir = dirInput.file
def dest = outputProvider.getContentLocation(
dirInput.name,
dirInput.contentTypes,
dirInput.scopes,
Format.DIRECTORY
)
FileUtils.copyDirectory(dir, dest)
LogUtils.l("scan dir: ${dirInput.file} -> $dest")
apiScan.scanDir(dest)
}
input.jarInputs.each { JarInput jarInput ->// 遍历 jar 文件
File jar = jarInput.file
def jarName = jarInput.name
def dest = outputProvider.getContentLocation(
jarName,
jarInput.contentTypes,
jarInput.scopes,
Format.JAR
)
FileUtils.copyFile(jar, dest)
if (jumpScan(jarName, ext)) {
LogUtils.l("jump jar: $jarName -> $dest")
return
}
LogUtils.l("scan jar: $jarName -> $dest")
apiScan.scanJar(dest)
}
}
if (apiScan.apiUtilsTransformFile != null) {
if (apiScan.apiClasses.isEmpty()) {
LogUtils.l("no api.")
} else {
Map implApis = [:]
List<String> noImplApis = []
apiScan.apiImplMap.each { key, value ->
implApis.put(key, value.toString())
}
apiScan.apiClasses.each {
if (!apiScan.apiImplMap.containsKey(it)) {
noImplApis.add(it)
}
}
Map apiDetails = [:]
apiDetails.put("ApiUtilsClass", ext.apiUtilsClass)
apiDetails.put("implApis", implApis)
apiDetails.put("noImplApis", noImplApis)
String apiJson = JsonUtils.getFormatJson(apiDetails)
LogUtils.l(jsonFile.toString() + ": " + apiJson)
FileUtils.write(jsonFile, apiJson)
if (noImplApis.size() > 0) {
if (ext.abortOnError) {
throw new Exception("u should impl these apis: " + noImplApis +
"\n u can check it in file: " + jsonFile.toString())
}
}
ApiInject.start(apiScan.apiImplMap, apiScan.apiUtilsTransformFile, ext.apiUtilsClass)
}
} else {
throw new Exception("No ApiUtils of ${ext.apiUtilsClass} in $mProject.")
}
LogUtils.l(getName() + " finished: " + (System.currentTimeMillis() - stTime) + "ms")
}
private static jumpScan(String jarName, ApiExtension ext) {
if (jarName.contains("utilcode")) {
return false
}
if (ext.onlyScanLibRegex != null && ext.onlyScanLibRegex.trim().length() > 0) {
return !Pattern.matches(ext.onlyScanLibRegex, jarName)
}
if (ext.jumpScanLibRegex != null && ext.jumpScanLibRegex.trim().length() > 0) {
if (Pattern.matches(ext.jumpScanLibRegex, jarName)) {
return true
}
}
for (exclude in Config.EXCLUDE_LIBS_START_WITH) {
if (jarName.startsWith(exclude)) {
return true
}
}
return false
}
}
\ No newline at end of file
package com.blankj.api
import com.blankj.base_transform.BaseTransformConfig
class Config {
public static final String EXT_NAME = 'api'
......@@ -15,5 +17,5 @@ class Config {
'com.github.bumptech.glide'
]
public static final String FILE_SEP = System.getProperty("file.separator")
public static final String FILE_SEP = BaseTransformConfig.FILE_SEP
}
package com.blankj.api.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2018/10/08
* desc :
* </pre>
*/
final class JsonUtils {
static final Gson GSON = new GsonBuilder().setPrettyPrinting().create()
static String getFormatJson(Object object) {
return GSON.toJson(object)
}
}
package com.blankj.api.util
import org.gradle.api.Project
import org.gradle.api.logging.Logger
final class LogUtils {
private static Logger sLogger
private static String PREFIX = "PLUGIN-API >>> "
static void init(Project project) {
sLogger = project.getLogger()
}
static void l(Object content) {
sLogger.lifecycle(PREFIX + content)
}
static void d(Object content) {
sLogger.debug(PREFIX + content)
}
static void i(Object content) {
sLogger.info(PREFIX + content)
}
static void w(Object content) {
sLogger.warn(PREFIX + content)
}
static void e(Object content) {
sLogger.error(PREFIX + content)
}
}
\ No newline at end of file
# Change Log
## v2.5(2020/04/29)
重构使用 base-transform
## v2.4
去除 gradle 版本依赖的问题
......
plugins {
id 'com.gradle.plugin-publish' version "0.10.0"
}
apply {
plugin "groovy"
plugin "java-gradle-plugin"
......@@ -19,6 +15,7 @@ gradlePlugin {
dependencies {
compileOnly Config.depConfig.plugin_gradle.dep
implementation Config.depConfig.commons_io.dep
implementation Config.depConfig.plugin_lib_base_transform.dep
implementation gradleApi()
implementation localGroovy()
......@@ -34,20 +31,6 @@ sourceSets {
}
}
pluginBundle {
website = 'https://github.com/Blankj/AndroidUtilCode'
vcsUrl = 'https://github.com/Blankj/AndroidUtilCode.git'
description = 'Plugin for BusUtils used as EventBus.'
tags = ['gradle', 'plugin', 'bus', 'eventbus', 'BusUtils', 'asm']
plugins {
busPlugin {
// id is captured from java-gradle-plugin configuration
displayName = 'Plugin for BusUtils used as EventBus.'
}
}
}
apply from: "${rootDir.path}/gradle/publish.gradle"
publish {
name = "BusPlugin"
......@@ -57,6 +40,5 @@ publish {
website = "https://github.com/Blankj/AndroidUtilCode"
}
//./gradlew plugin:lib_bus-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew plugin:lib_bus-gradle-plugin:bintrayUpload // 上传到 jcenter
//./gradlew plugin:lib_bus-gradle-plugin:publishPlugins // 上传到 gradle 插件库中
\ No newline at end of file
//./gradlew clean plugin:plugin_bus-gradle-plugin:mavenLocal // 上传到本地 mavenLocal
//./gradlew clean plugin:plugin_bus-gradle-plugin:bintrayUpload // 上传到 jcenter
\ No newline at end of file
package com.blankj.bus
import com.blankj.bus.util.ZipUtils
import com.blankj.base_transform.util.ZipUtils
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
......@@ -26,20 +26,20 @@ class BusInject {
ZipUtils.zipFiles(Arrays.asList(decompressedJar.listFiles()), busUtilsTransformFile)
FileUtils.forceDelete(decompressedJar)
} else {
File apiUtilsFile = new File(
File busUtilsFile = new File(
busUtilsTransformFile.getAbsolutePath() + Config.FILE_SEP +
busUtilsClass.replace('.', Config.FILE_SEP) + '.class'
)
inject2BusUtils(apiUtilsFile, busMap, busUtilsClass)
inject2BusUtils(busUtilsFile, busMap, busUtilsClass)
}
}
private static void inject2BusUtils(File apiUtilsFile, Map<String, BusInfo> busMap, String busUtilsClass) {
ClassReader cr = new ClassReader(apiUtilsFile.bytes);
private static void inject2BusUtils(File busUtilsFile, Map<String, BusInfo> busMap, String busUtilsClass) {
ClassReader cr = new ClassReader(busUtilsFile.bytes);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new BusUtilsClassVisitor(cw, busMap, busUtilsClass);
cr.accept(cv, ClassReader.SKIP_FRAMES);
FileUtils.writeByteArrayToFile(apiUtilsFile, cw.toByteArray())
FileUtils.writeByteArrayToFile(busUtilsFile, cw.toByteArray())
}
}
\ No newline at end of file
package com.blankj.bus
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.blankj.bus.util.LogUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
import com.android.build.api.transform.JarInput
import com.blankj.base_transform.BaseTransformPlugin
import com.blankj.base_transform.util.JsonUtils
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
class BusPlugin implements Plugin<Project> {
import java.util.regex.Pattern
class BusPlugin extends BaseTransformPlugin<BusExtension> {
String busUtilsClass
File jsonFile
Map<String, List<BusInfo>> busMap = [:]
File busUtilsTransformFile
@Override
String getPluginName() {
return Config.EXT_NAME
}
@Override
void apply(Project project) {
if (project.plugins.hasPlugin(AppPlugin)) {
LogUtils.init(project)
LogUtils.l('project(' + project.toString() + ') apply bus gradle plugin!')
project.extensions.create(Config.EXT_NAME, BusExtension)
def android = project.extensions.getByType(AppExtension)
android.registerTransform(new BusTransform(project))
void onScanStarted() {
busUtilsClass = ext.busUtilsClass
if (busUtilsClass.trim().equals("")) {
throw new Exception("BusExtension's busUtilsClass is empty.")
}
jsonFile = new File(mProject.projectDir.getAbsolutePath(), "__bus__.json")
FileUtils.write(jsonFile, "{}")
}
@Override
boolean isIgnoreScan(JarInput input) {
def jarName = input.name
if (jarName.contains("utilcode")) {
return false
}
if (ext.onlyScanLibRegex != null && ext.onlyScanLibRegex.trim().length() > 0) {
return !Pattern.matches(ext.onlyScanLibRegex, jarName)
}
if (ext.jumpScanLibRegex != null && ext.jumpScanLibRegex.trim().length() > 0) {
if (Pattern.matches(ext.jumpScanLibRegex, jarName)) {
return true
}
}
for (exclude in Config.EXCLUDE_LIBS_START_WITH) {
if (jarName.startsWith(exclude)) {
return true
}
}
return false
}
@Override
void scanClassFile(File classFile, String className, File originScannedJarOrDir) {
if (busUtilsClass == className) {
busUtilsTransformFile = originScannedJarOrDir
log("<BusUtils transform file>: $originScannedJarOrDir")
}
ClassReader cr = new ClassReader(classFile.bytes);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new BusClassVisitor(cw, busMap, busUtilsClass);
try {
cr.accept(cv, ClassReader.SKIP_FRAMES);
} catch (Exception ignore) {
ignore.printStackTrace()
}
}
@Override
void onScanFinished() {
if (busUtilsTransformFile != null) {
if (busMap.isEmpty()) {
log("no bus.")
} else {
busMap.each { String tag, List<BusInfo> infoList ->
infoList.sort(new Comparator<BusInfo>() {
@Override
int compare(BusInfo t0, BusInfo t1) {
return t1.priority - t0.priority
}
})
}
Map<String, List<String>> rightBus = [:]
Map<String, List<String>> wrongBus = [:]
busMap.each { String tag, List<BusInfo> infoList ->
List<String> rightInfoString = []
List<String> wrongInfoString = []
infoList.each { BusInfo info ->
if (info.isParamSizeNoMoreThanOne) {
rightInfoString.add(info.toString())
} else {
wrongInfoString.add(info.toString())
}
}
if (!rightInfoString.isEmpty()) {
rightBus.put(tag, rightInfoString)
}
if (!wrongInfoString.isEmpty()) {
wrongBus.put(tag, wrongInfoString)
}
}
Map busDetails = [:]
busDetails.put("BusUtilsClass", ext.busUtilsClass)
busDetails.put("rightBus", rightBus)
busDetails.put("wrongBus", wrongBus)
String busJson = JsonUtils.getFormatJson(busDetails)
log(jsonFile.toString() + ": " + busJson)
FileUtils.write(jsonFile, busJson)
if (wrongBus.size() > 0) {
if (ext.abortOnError) {
throw new Exception("These buses is not right: " + wrongBus +
"\n u can check it in file: " + jsonFile.toString())
}
}
BusInject.start(busMap, busUtilsTransformFile, ext.busUtilsClass)
}
} else {
throw new Exception("No BusUtils of ${ext.busUtilsClass} in $mProject.")
}
}
}
\ No newline at end of file
package com.blankj.bus
import com.blankj.bus.util.LogUtils
import com.blankj.bus.util.ZipUtils
import groovy.io.FileType
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
class BusScan {
Map<String, List<BusInfo>> busMap = [:]
File busUtilsTransformFile
String busUtilsClass
BusScan(String busUtilsClass) {
this.busUtilsClass = busUtilsClass
}
void scanJar(File jar) {
File tmp = new File(jar.getParent(), "temp_" + jar.getName())
List<File> unzipFile = ZipUtils.unzipFile(jar, tmp)
if (unzipFile != null && unzipFile.size() > 0) {
scanDir(tmp, jar)
FileUtils.forceDelete(tmp)
}
}
void scanDir(File root) {
scanDir(root, root)
}
void scanDir(File root, File source) {
if (!root.isDirectory()) return
String rootPath = root.getAbsolutePath()
if (!rootPath.endsWith(Config.FILE_SEP)) {
rootPath += Config.FILE_SEP
}
root.eachFileRecurse(FileType.FILES) { File file ->
def fileName = file.name
if (!fileName.endsWith('.class')
|| fileName.startsWith('R$')
|| fileName == 'R.class'
|| fileName == 'BuildConfig.class') {
return
}
def filePath = file.absolutePath
def packagePath = filePath.replace(rootPath, '')
def className = packagePath.replace(Config.FILE_SEP, ".")
// delete .class
className = className.substring(0, className.length() - 6)
if (busUtilsClass == className) {
busUtilsTransformFile = source
LogUtils.l("<BusUtils transform file>: $source")
}
ClassReader cr = new ClassReader(file.bytes);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new BusClassVisitor(cw, busMap, busUtilsClass);
try {
cr.accept(cv, ClassReader.SKIP_FRAMES);
} catch (Exception ignore) {
ignore.printStackTrace()
}
}
}
}
package com.blankj.bus
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.blankj.bus.util.JsonUtils
import com.blankj.bus.util.LogUtils
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import java.util.regex.Pattern
class BusTransform extends Transform {
Project mProject;
BusTransform(Project project) {
mProject = project
}
@Override
String getName() {
return "busTransform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
LogUtils.l(getName() + " started")
long stTime = System.currentTimeMillis()
def ext = mProject[Config.EXT_NAME] as BusExtension
LogUtils.l("busExtension: $ext")
if (ext.busUtilsClass.trim().equals("")) {
throw new Exception("BusExtension's busUtilsClass is empty.")
}
File jsonFile = new File(mProject.projectDir.getAbsolutePath(), "__bus__.json")
FileUtils.write(jsonFile, "{}")
def inputs = transformInvocation.getInputs()
def referencedInputs = transformInvocation.getReferencedInputs()
def outputProvider = transformInvocation.getOutputProvider()
def isIncremental = transformInvocation.isIncremental()
outputProvider.deleteAll()
BusScan busScan = new BusScan(ext.busUtilsClass)
inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput dirInput ->// 遍历文件夹
File dir = dirInput.file
def dest = outputProvider.getContentLocation(
dirInput.name,
dirInput.contentTypes,
dirInput.scopes,
Format.DIRECTORY
)
FileUtils.copyDirectory(dir, dest)
LogUtils.l("scan dir: ${dirInput.file} -> $dest")
busScan.scanDir(dest)
}
input.jarInputs.each { JarInput jarInput ->// 遍历 jar 文件
File jar = jarInput.file
def jarName = jarInput.name
def dest = outputProvider.getContentLocation(
jarName,
jarInput.contentTypes,
jarInput.scopes,
Format.JAR
)
FileUtils.copyFile(jar, dest)
if (jumpScan(jarName, ext)) {
LogUtils.l("jump jar: $jarName -> $dest")
return
}
LogUtils.l("scan jar: $jarName -> $dest")
busScan.scanJar(dest)
}
}
if (busScan.busUtilsTransformFile != null) {
if (busScan.busMap.isEmpty()) {
LogUtils.l("no bus.")
} else {
busScan.busMap.each { String tag, List<BusInfo> infoList ->
infoList.sort(new Comparator<BusInfo>() {
@Override
int compare(BusInfo t0, BusInfo t1) {
return t1.priority - t0.priority
}
})
}
Map<String, List<String>> rightBus = [:]
Map<String, List<String>> wrongBus = [:]
busScan.busMap.each { String tag, List<BusInfo> infoList ->
List<String> rightInfoString = []
List<String> wrongInfoString = []
infoList.each { BusInfo info ->
if (info.isParamSizeNoMoreThanOne) {
rightInfoString.add(info.toString())
} else {
wrongInfoString.add(info.toString())
}
}
if (!rightInfoString.isEmpty()) {
rightBus.put(tag, rightInfoString)
}
if (!wrongInfoString.isEmpty()) {
wrongBus.put(tag, wrongInfoString)
}
}
Map busDetails = [:]
busDetails.put("BusUtilsClass", ext.busUtilsClass)
busDetails.put("rightBus", rightBus)
busDetails.put("wrongBus", wrongBus)
String busJson = JsonUtils.getFormatJson(busDetails)
LogUtils.l(jsonFile.toString() + ": " + busJson)
FileUtils.write(jsonFile, busJson)
if (wrongBus.size() > 0) {
if (ext.abortOnError) {
throw new Exception("These buses is not right: " + wrongBus +
"\n u can check it in file: " + jsonFile.toString())
}
}
BusInject.start(busScan.busMap, busScan.busUtilsTransformFile, ext.busUtilsClass)
}
} else {
throw new Exception("No BusUtils of ${ext.busUtilsClass} in $mProject.")
}
LogUtils.l(getName() + " finished: " + (System.currentTimeMillis() - stTime) + "ms")
}
private static jumpScan(String jarName, BusExtension ext) {
if (jarName.contains("utilcode")) {
return false
}
if (ext.onlyScanLibRegex != null && ext.onlyScanLibRegex.trim().length() > 0) {
return !Pattern.matches(ext.onlyScanLibRegex, jarName)
}
if (ext.jumpScanLibRegex != null && ext.jumpScanLibRegex.trim().length() > 0) {
if (Pattern.matches(ext.jumpScanLibRegex, jarName)) {
return true
}
}
for (exclude in Config.EXCLUDE_LIBS_START_WITH) {
if (jarName.startsWith(exclude)) {
return true
}
}
return false
}
}
\ No newline at end of file
package com.blankj.bus
import com.blankj.base_transform.BaseTransformConfig
class Config {
public static final String EXT_NAME = 'bus'
......@@ -15,5 +17,5 @@ class Config {
'com.github.bumptech.glide'
]
public static final String FILE_SEP = System.getProperty("file.separator")
public static final String FILE_SEP = BaseTransformConfig.FILE_SEP
}
package com.blankj.bus.util;
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.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2016/08/27
* desc : utils about zip or jar
* </pre>
*/
public final class ZipUtils {
private static final int BUFFER_LEN = 8192;
private ZipUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* Zip the files.
*
* @param srcFiles The source of files.
* @param zipFilePath The path of ZIP file.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFiles(final Collection<String> srcFiles,
final String zipFilePath)
throws IOException {
return zipFiles(srcFiles, zipFilePath, null);
}
/**
* Zip the files.
*
* @param srcFilePaths The paths of source files.
* @param zipFilePath The path of ZIP file.
* @param comment The comment.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFiles(final Collection<String> srcFilePaths,
final String zipFilePath,
final String comment)
throws IOException {
if (srcFilePaths == null || zipFilePath == null) return false;
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(new FileOutputStream(zipFilePath));
for (String srcFile : srcFilePaths) {
if (!zipFile(getFileByPath(srcFile), "", zos, comment)) return false;
}
return true;
} finally {
if (zos != null) {
zos.finish();
zos.close();
}
}
}
/**
* Zip the files.
*
* @param srcFiles The source of files.
* @param zipFile The ZIP file.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFiles(final Collection<File> srcFiles, final File zipFile)
throws IOException {
return zipFiles(srcFiles, zipFile, null);
}
/**
* Zip the files.
*
* @param srcFiles The source of files.
* @param zipFile The ZIP file.
* @param comment The comment.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFiles(final Collection<File> srcFiles,
final File zipFile,
final String comment)
throws IOException {
if (srcFiles == null || zipFile == null) return false;
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(new FileOutputStream(zipFile));
for (File srcFile : srcFiles) {
if (!zipFile(srcFile, "", zos, comment)) return false;
}
return true;
} finally {
if (zos != null) {
zos.finish();
zos.close();
}
}
}
/**
* Zip the file.
*
* @param srcFilePath The path of source file.
* @param zipFilePath The path of ZIP file.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFile(final String srcFilePath,
final String zipFilePath)
throws IOException {
return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), null);
}
/**
* Zip the file.
*
* @param srcFilePath The path of source file.
* @param zipFilePath The path of ZIP file.
* @param comment The comment.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFile(final String srcFilePath,
final String zipFilePath,
final String comment)
throws IOException {
return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), comment);
}
/**
* Zip the file.
*
* @param srcFile The source of file.
* @param zipFile The ZIP file.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFile(final File srcFile,
final File zipFile)
throws IOException {
return zipFile(srcFile, zipFile, null);
}
/**
* Zip the file.
*
* @param srcFile The source of file.
* @param zipFile The ZIP file.
* @param comment The comment.
* @return {@code true}: success<br>{@code false}: fail
* @throws IOException if an I/O error has occurred
*/
public static boolean zipFile(final File srcFile,
final File zipFile,
final String comment)
throws IOException {
if (srcFile == null || zipFile == null) return false;
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(new FileOutputStream(zipFile));
return zipFile(srcFile, "", zos, comment);
} finally {
if (zos != null) {
zos.close();
}
}
}
private static boolean zipFile(final File srcFile,
String rootPath,
final ZipOutputStream zos,
final String comment)
throws IOException {
rootPath = rootPath + (isSpace(rootPath) ? "" : File.separator) + srcFile.getName();
if (srcFile.isDirectory()) {
File[] fileList = srcFile.listFiles();
if (fileList == null || fileList.length <= 0) {
ZipEntry entry = new ZipEntry(rootPath + '/');
entry.setComment(comment);
zos.putNextEntry(entry);
zos.closeEntry();
} else {
for (File file : fileList) {
if (!zipFile(file, rootPath, zos, comment)) return false;
}
}
} else {
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(srcFile));
ZipEntry entry = new ZipEntry(rootPath);
entry.setComment(comment);
zos.putNextEntry(entry);
byte buffer[] = new byte[BUFFER_LEN];
int len;
while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
} finally {
if (is != null) {
is.close();
}
}
}
return true;
}
/**
* Unzip the file.
*
* @param zipFilePath The path of ZIP file.
* @param destDirPath The path of destination directory.
* @return the unzipped files
* @throws IOException if unzip unsuccessfully
*/
public static List<File> unzipFile(final String zipFilePath,
final String destDirPath)
throws IOException {
return unzipFileByKeyword(zipFilePath, destDirPath, null);
}
/**
* Unzip the file.
*
* @param zipFile The ZIP file.
* @param destDir The destination directory.
* @return the unzipped files
* @throws IOException if unzip unsuccessfully
*/
public static List<File> unzipFile(final File zipFile,
final File destDir)
throws IOException {
return unzipFileByKeyword(zipFile, destDir, null);
}
/**
* Unzip the file by keyword.
*
* @param zipFilePath The path of ZIP file.
* @param destDirPath The path of destination directory.
* @param keyword The keyboard.
* @return the unzipped files
* @throws IOException if unzip unsuccessfully
*/
public static List<File> unzipFileByKeyword(final String zipFilePath,
final String destDirPath,
final String keyword)
throws IOException {
return unzipFileByKeyword(getFileByPath(zipFilePath), getFileByPath(destDirPath), keyword);
}
/**
* Unzip the file by keyword.
*
* @param zipFile The ZIP file.
* @param destDir The destination directory.
* @param keyword The keyboard.
* @return the unzipped files
* @throws IOException if unzip unsuccessfully
*/
public static List<File> unzipFileByKeyword(final File zipFile,
final File destDir,
final String keyword)
throws IOException {
if (zipFile == null || destDir == null) return null;
List<File> files = new ArrayList<>();
ZipFile zip = new ZipFile(zipFile);
Enumeration<?> entries = zip.entries();
try {
if (isSpace(keyword)) {
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.contains("../")) {
System.err.println("entryName: " + entryName + " is dangerous!");
continue;
}
if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files;
}
} else {
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.contains("../")) {
System.out.println("entryName: " + entryName + " is dangerous!");
continue;
}
if (entryName.contains(keyword)) {
if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files;
}
}
}
} finally {
zip.close();
}
return files;
}
private static boolean unzipChildFile(final File destDir,
final List<File> files,
final ZipFile zip,
final ZipEntry entry,
final String name) throws IOException {
File file = new File(destDir, name);
files.add(file);
if (entry.isDirectory()) {
return createOrExistsDir(file);
} else {
if (!createOrExistsFile(file)) return false;
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(zip.getInputStream(entry));
out = new BufferedOutputStream(new FileOutputStream(file));
byte buffer[] = new byte[BUFFER_LEN];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
return true;
}
/**
* Return the files' path in ZIP file.
*
* @param zipFilePath The path of ZIP file.
* @return the files' path in ZIP file
* @throws IOException if an I/O error has occurred
*/
public static List<String> getFilesPath(final String zipFilePath)
throws IOException {
return getFilesPath(getFileByPath(zipFilePath));
}
/**
* Return the files' path in ZIP file.
*
* @param zipFile The ZIP file.
* @return the files' path in ZIP file
* @throws IOException if an I/O error has occurred
*/
public static List<String> getFilesPath(final File zipFile)
throws IOException {
if (zipFile == null) return null;
List<String> paths = new ArrayList<>();
ZipFile zip = new ZipFile(zipFile);
Enumeration<?> entries = zip.entries();
while (entries.hasMoreElements()) {
String entryName = ((ZipEntry) entries.nextElement()).getName();
if (entryName.contains("../")) {
System.out.println("entryName: " + entryName + " is dangerous!");
paths.add(entryName);
} else {
paths.add(entryName);
}
}
zip.close();
return paths;
}
/**
* Return the files' comment in ZIP file.
*
* @param zipFilePath The path of ZIP file.
* @return the files' comment in ZIP file
* @throws IOException if an I/O error has occurred
*/
public static List<String> getComments(final String zipFilePath)
throws IOException {
return getComments(getFileByPath(zipFilePath));
}
/**
* Return the files' comment in ZIP file.
*
* @param zipFile The ZIP file.
* @return the files' comment in ZIP file
* @throws IOException if an I/O error has occurred
*/
public static List<String> getComments(final File zipFile)
throws IOException {
if (zipFile == null) return null;
List<String> comments = new ArrayList<>();
ZipFile zip = new ZipFile(zipFile);
Enumeration<?> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
comments.add(entry.getComment());
}
zip.close();
return comments;
}
private static boolean createOrExistsDir(final File file) {
return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
}
private static boolean createOrExistsFile(final File file) {
if (file == null) return false;
if (file.exists()) return file.isFile();
if (!createOrExistsDir(file.getParentFile())) return false;
try {
return file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
private static File getFileByPath(final String filePath) {
return isSpace(filePath) ? null : new File(filePath);
}
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;
}
}
/build
ApiUtils2333.class
\ No newline at end of file
# Change Log
## v1.0(2020/04/28)
发布初版本
\ No newline at end of file
# 基础 transform 库
## 背景
api 和 bus 插件存在大量重复代码,所以抽出这么一个基础 transform 库。
## 使用
```groovy
implementation com.blankj:base-transform:1.0
```
写插件直接继承 BaseTransformPlugin 即可,比如 ApiPlugin:
```groovy
class ApiPlugin extends BaseTransformPlugin<ApiExtension> {
@Override
String getPluginName() {
// 获取插件名
}
@Override
void onScanStarted() {
// 扫描开始的处理
}
@Override
boolean isIgnoreScan(JarInput input) {
// 对 jar 包进行过滤扫描,
// 工程中的 module 就是以 : 开头的 jar 包
// 远端仓库也是 jar 包
}
@Override
void scanClassFile(File classFile, String className, File originScannedJarOrDir) {
// 扫描到类文件的处理
}
@Override
void onScanFinished() {
// 扫描结束的处理
}
}
```
更具体可以参考 ApiPlugin 及 BusPlugin 的源码。
## [Change Log](https://github.com/Blankj/AndroidUtilCode/blob/master/plugin/lib/base-transform/CHANGELOG.md)
\ No newline at end of file
apply {
plugin "groovy"
plugin "java-gradle-plugin"
}
dependencies {
compileOnly Config.depConfig.plugin_gradle.dep
implementation Config.depConfig.commons_io.dep
implementation gradleApi()
implementation localGroovy()
}
sourceSets {
main {
groovy {
srcDirs += 'src/main/java'
}
}
}
apply from: "${rootDir.path}/gradle/publish.gradle"
publish {
name = "BaseTransform"
groupId = Config.depConfig.plugin_lib_base_transform.groupId
artifactId = Config.depConfig.plugin_lib_base_transform.artifactId
version = Config.depConfig.plugin_lib_base_transform.version
website = "https://github.com/Blankj/AndroidUtilCode"
}
//./gradlew clean plugin:lib:plugin_lib_base-transform:mavenLocal // 上传到本地 mavenLocal
//./gradlew clean plugin:lib:plugin_lib_base-transform:bintrayUpload // 上传到 gradle 插件库中
package com.blankj.base_transform
import com.android.build.api.transform.JarInput
interface BaseTransformCallback<T> {
String getPluginName();
void onScanStarted();
boolean isIgnoreScan(JarInput input);
void scanClassFile(File classFile, String className, File originScannedJarOrDir);
void onScanFinished();
}
\ No newline at end of file
package com.blankj.base_transform
class BaseTransformConfig {
public static final String FILE_SEP = System.getProperty("file.separator")
}
\ No newline at end of file
package com.blankj.base_transform
import com.android.build.api.transform.*
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.internal.pipeline.TransformManager
import com.blankj.base_transform.util.LogUtils
import com.blankj.base_transform.util.ZipUtils
import groovy.io.FileType
import org.apache.commons.io.FileUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
import java.lang.reflect.ParameterizedType
abstract class BaseTransformPlugin<T> implements Plugin<Project>, BaseTransformCallback<T> {
Project mProject
T getExt() {
return mProject.getExtensions().getByName(getPluginName())
}
@Override
void apply(Project project) {
if (project.plugins.hasPlugin(AppPlugin)) {
mProject = project
LogUtils.init(project)
log('project(' + project.toString() + ') apply ' + getPluginName() + ' gradle plugin!')
project.extensions.create(getPluginName(), getGenericClass())
def android = project.extensions.getByType(AppExtension)
android.registerTransform(new BaseTransform())
}
}
Class<T> getGenericClass() {
return ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]
}
class BaseTransform extends Transform {
@Override
String getName() {
return "${getPluginName()}Transform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
log(getName() + " started")
long stTime = System.currentTimeMillis()
def inputs = transformInvocation.getInputs()
def referencedInputs = transformInvocation.getReferencedInputs()
def outputProvider = transformInvocation.getOutputProvider()
def isIncremental = transformInvocation.isIncremental()
outputProvider.deleteAll()
log("${getPluginName()}Extension: $ext")
onScanStarted()
inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput dirInput ->// 遍历文件夹
File dir = dirInput.file
File dest = outputProvider.getContentLocation(
dirInput.name,
dirInput.contentTypes,
dirInput.scopes,
Format.DIRECTORY
)
FileUtils.copyDirectory(dir, dest)
log("scan dir: ${dirInput.file} -> $dest")
scanDir(dest)
}
input.jarInputs.each { JarInput jarInput ->// 遍历 jar 文件
File jar = jarInput.file
def jarName = jarInput.name
def dest = outputProvider.getContentLocation(
jarName,
jarInput.contentTypes,
jarInput.scopes,
Format.JAR
)
FileUtils.copyFile(jar, dest)
if (isIgnoreScan(jarInput)) {
log("jump jar: $jarName -> $dest")
return
}
log("scan jar: $jarName -> $dest")
scanJar(dest)
}
}
onScanFinished()
log(getName() + " finished: " + (System.currentTimeMillis() - stTime) + "ms")
}
void scanJar(File jar) {
File tmp = new File(jar.getParent(), "temp_" + jar.getName())
List<File> unzipFile = ZipUtils.unzipFile(jar, tmp)
if (unzipFile != null && unzipFile.size() > 0) {
scanDir(tmp, jar)
FileUtils.forceDelete(tmp)
}
}
void scanDir(File root) {
scanDir(root, root)
}
void scanDir(File dir, File originScannedJarOrDir) {
if (!dir.isDirectory()) return
String rootPath = dir.getAbsolutePath()
if (!rootPath.endsWith(BaseTransformConfig.FILE_SEP)) {
rootPath += BaseTransformConfig.FILE_SEP
}
dir.eachFileRecurse(FileType.FILES) { File file ->
def fileName = file.name
if (!fileName.endsWith('.class')
|| fileName.startsWith('R$')
|| fileName == 'R.class'
|| fileName == 'BuildConfig.class') {
return
}
def filePath = file.absolutePath
def packagePath = filePath.replace(rootPath, '')
def className = packagePath.replace(BaseTransformConfig.FILE_SEP, ".")
// delete .class
className = className.substring(0, className.length() - 6)
scanClassFile(file, className, originScannedJarOrDir)
}
}
}
void log(Object obj) {
LogUtils.l(getPluginName(), obj)
}
}
\ No newline at end of file
package com.blankj.bus.util
package com.blankj.base_transform.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
......
package com.blankj.bus.util
package com.blankj.base_transform.util
import org.gradle.api.Project
import org.gradle.api.logging.Logger
......@@ -6,29 +6,55 @@ import org.gradle.api.logging.Logger
final class LogUtils {
private static Logger sLogger
private static String PREFIX = "PLUGIN-BUS >>> "
static void init(Project project) {
sLogger = project.getLogger()
}
static void l(Object content) {
sLogger.lifecycle(PREFIX + content)
l("", content)
}
static void d(Object content) {
sLogger.debug(PREFIX + content)
d("", content)
}
static void i(Object content) {
sLogger.info(PREFIX + content)
i("", content)
}
static void w(Object content) {
sLogger.warn(PREFIX + content)
w("", content)
}
static void e(Object content) {
sLogger.error(PREFIX + content)
e("", content)
}
static void l(String tag, Object content) {
sLogger.lifecycle(getTag(tag) + content)
}
static void d(String tag, Object content) {
sLogger.debug(getTag(tag) + content)
}
static void i(String tag, Object content) {
sLogger.info(getTag(tag) + content)
}
static void w(String tag, Object content) {
sLogger.warn(getTag(tag) + content)
}
static void e(String tag, Object content) {
sLogger.error(getTag(tag) + content)
}
private static String getTag(String tag) {
if (tag == null || tag.isEmpty()) {
return "LogUtils >>> "
}
return tag + " >>> "
}
}
\ No newline at end of file
package com.blankj.api.util;
package com.blankj.base_transform.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册