From b0ac2acbec3660719399fc515aa0e5f9d008b62b Mon Sep 17 00:00:00 2001 From: Omooo <869759698@qq.com> Date: Thu, 22 Aug 2019 00:33:56 +0800 Subject: [PATCH] add printDependencesPermissions --- README.md | 2 + .../Android Gradle Plugin 流程分析.md | 119 ++++++++- blogs/Android/Gradle/依赖权限信息.md | 244 ++++++++++++++++++ 3 files changed, 359 insertions(+), 6 deletions(-) create mode 100644 blogs/Android/Gradle/依赖权限信息.md diff --git a/README.md b/README.md index 1b9d69e..ee302f8 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ Android Notes - [基础知识相关](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Gradle/Android%20Gradle%20%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E4%B9%8B%E4%B8%80.md) - [进阶知识相关](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Gradle/Android%20Gradle%20%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E4%B9%8B%E4%BA%8C.md) +[Gradle 练习之一 --- 输出项目第三方库以及本地依赖库的权限信息](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Gradle/%E4%BE%9D%E8%B5%96%E6%9D%83%E9%99%90%E4%BF%A1%E6%81%AF.md) + ##### JVM、ART 相关 - Class 文件格式 diff --git a/blogs/Android/Gradle/Android Gradle Plugin 流程分析.md b/blogs/Android/Gradle/Android Gradle Plugin 流程分析.md index ba9fb5b..048dff6 100644 --- a/blogs/Android/Gradle/Android Gradle Plugin 流程分析.md +++ b/blogs/Android/Gradle/Android Gradle Plugin 流程分析.md @@ -32,12 +32,12 @@ AppPlugin 里面没有做过多的操作,主要是重写了 createTaskManager ```java protected void apply(@NonNull Project project) { - // 检查插件版本 + // 检查插件版本 checkPluginVersion(); // 检查 module 是否重名 checkModulesForErrors(); - // 插件初始化信息 + // 插件初始化信息 PluginInitializer.initialize(project, projectOptions); ProfilerInitializer.init(project, projectOptions); @@ -59,7 +59,7 @@ AppPlugin 里面没有做过多的操作,主要是重写了 createTaskManager ```java // BasePlugin private void configureProject(){ - // 1. + // 1. checkGradleVersion(); // 2. androidBuilder = new AndroidBuilder(); @@ -68,7 +68,7 @@ private void configureProject(){ project.getPlugins().apply(JavaBasePlugin.class); project.getPlugins().apply(JacocoPlugin.class); // 4. - PreDexCache.getCache().clear(); + PreDexCache.getCache().clear(); } ``` @@ -87,10 +87,117 @@ private void configureProject(){ 2. 创建依赖管理、ndk 管理、任务管理、variant 管理 + ```java + private void configureExtension() { + ndkHandler = new NdkHandler(); + variantFactory = createVariantFactory(); + taskManager = createTaskManager(); + variantManager = new VariantManager(); + } ``` + +3. 注册新增配置的回调函数,包括 signingConfig、buildType、productFlavor + + ```java + // BasePlugin#configureExtension() + signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig); + buildTypeContainer.whenObjectAdded( + buildType -> { + SigningConfig signingConfig = + signingConfigContainer.findByName(BuilderConstants.DEBUG); + buildType.init(signingConfig); + // addBuildType,会检查命名是否合法,然后创建 BuildTypeData + variantManager.addBuildType(buildType); + }); + // addProductFlavor 会检查命名是否合法,然后创建 ProductFlavor + productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor); + + // map whenObjectRemoved on the containers to throw an exception. + signingConfigContainer.whenObjectRemoved( + new UnsupportedAction("Removing signingConfigs is not supported.")); + buildTypeContainer.whenObjectRemoved( + new UnsupportedAction("Removing build types is not supported.")); + productFlavorContainer.whenObjectRemoved( + new UnsupportedAction("Removing product flavors is not supported.")); ``` - +4. 创建默认的 debug 签名,创建 debug 和 release 两个 buildType + + ```java + public void createDefaultComponents( + @NonNull NamedDomainObjectContainer buildTypes, + @NonNull NamedDomainObjectContainer productFlavors, + @NonNull NamedDomainObjectContainer signingConfigs) { + // must create signing config first so that build type 'debug' can be initialized + // with the debug signing config. + signingConfigs.create(DEBUG); + buildTypes.create(DEBUG); + buildTypes.create(RELEASE); + } + ``` + +#### 创建不依赖 flavor 的 Task + +```java + private void createTasks() { + // createTasksBeforeEvaluate + // 创建不依赖 flavor 的 Task + threadRecorder.record( + ExecutionType.TASK_MANAGER_CREATE_TASKS, + project.getPath(), + null, + () -> + taskManager.createTasksBeforeEvaluate( + new TaskContainerAdaptor(project.getTasks()))); + + // createAndroidTasks + // 创建构建的 Task + project.afterEvaluate( + project -> + threadRecorder.record( + ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS, + project.getPath(), + null, + () -> createAndroidTasks(false))); + } +``` + +上述准备、配置阶段完成以后,就开始创建构建需要的 Task 了,是在 BasePlugin.createTasks() 里实现的,主要有两步,**创建不依赖 flavor 的 task 和创建构建 task。** + +先看不依赖 flavor 的 task,其实先在 TaskManager.createTaskBeforeEvaluate()。这里主要创建了几个 task,包括 uninstallAll、deviceCheck、connectedCheck、preBuild、extractProguardFiles、sourcesSets、assemebleAndroidTest、compileLint、lint、lintChecks、cleanBuildCache、resolveConfigAttr、consumeConfigAttr。 + +这些 task 都是不需要依赖 flavor 数据的公共 task。 + +#### 创建构建 Task + +在介绍下面的流程之前,先明确几个概念,flavor、dimension、variant。 + +在 Android Gradle Plugin 3.x 之后,每个 Flavor 必须对应一个 Dimension,可以理解为 Flavor 分组,然后不同 Dimension 里的 Flavor 组合成一个 Variant。 + +举个例子: + +```java + flavorDimensions("size", "color") + productFlavors { + big { + dimension "size" + } + small { + dimension "size" + } + blue { + dimension "color" + } + red { + dimension "color" + } + } +``` + +上面配置对应生成的 variant 就是 bigBlue、bigRed、smallBlue、smallRead,在这个基础上,再加上 buildType,就是 bigBlueDebug、bigRedDebug、smallBlueDebug、smallRedDebug、bigBlueRelease、bigRedRelease、smallBlueRelease、smallRedRelease。 + +createAndroidTasks 的调用时机和上面不一样,是在 project.afterEvaluate 里调用的,还记得之前文章里说道的 afterEvaluate 回调嘛?这个时候所有模块配置已经完成了。所以在这个阶段可以获取对应的 flavor 以及其他配置了。 + +在 BasePlugin.createAndroidTasks 里,是调用 VariantManager.createAndroidTasks 完成工作的。 -3. \ No newline at end of file diff --git a/blogs/Android/Gradle/依赖权限信息.md b/blogs/Android/Gradle/依赖权限信息.md new file mode 100644 index 0000000..8f72df7 --- /dev/null +++ b/blogs/Android/Gradle/依赖权限信息.md @@ -0,0 +1,244 @@ +--- +Gradle 练习之一 --- 输出项目第三方库以及本地依赖库的权限信息 +--- + +#### 前言 + +标题看起来可能有点恍惚,简单来说就是对于 app module 下面的依赖,即: + +```groovy +dependencies { + // 第三方库 + implementation 'com.android.support:appcompat-v7:28.0.0' + // 本地依赖库 + implementation project(":mylibrary") +} +``` + +获取它们的 Manifest 文件里面的权限信息。 + +输出结果示例: + +```groovy +/** + * Path: /Users/omooo/AndroidStudioProjects/Projects/MyApplication/app/src/main/AndroidManifest.xml + * ModuleName: app + * + * + * + * + * + */ + + +/** + * Path: /Users/omooo/AndroidStudioProjects/Projects/MyApplication/mylibrary/src/main/AndroidManifest.xml + * ModuleName: mylibrary + * + * + * + */ + +``` + +#### 入门 + +较早之前,我们 App 因为私自获取用户敏感权限被警告了。组长就让我看看能不能获取每个 module 的权限信息,然后格式化输出。当时心想还不是简单的一批,终于到了秀操作的时候了。 + +写一个 writePermissionToFile.gradle 脚本,放在根目录,如下: + +```groovy +import java.util.regex.Matcher +import java.util.regex.Pattern + +boolean firstWrite = true + +project.afterEvaluate { + + if (!firstWrite || !project.hasProperty('android')) return + def extension = project.android + if (extension.class.name.contains('BaseAppModuleExtension')) { + project.android.applicationVariants.all { variant -> + writeFile(variant.getCheckManifest().manifest, project.name) + } + } else if (extension.class.name.contains('LibraryExtension')) { + project.android.libraryVariants.all { variant -> + writeFile(variant.getCheckManifest().manifest, project.name) + } + } + firstWrite = false +} + +static def writeFile(File manifestFile, String moduleName) { + def fileT = new File("allPermissionOfModuleFile.txt") + if (!fileT.exists()) { + fileT.createNewFile() + } + if (fileT.getText().contains(manifestFile.path)) return + Pattern pattern = Pattern.compile("") + Matcher matcher = pattern.matcher(manifestFile.getText()) + String firstMatch = "" + if (!matcher.find()) { + return + } else { + firstMatch = matcher.group() + } + fileT.append("/**" + "\n") + fileT.append(" * Path: " + manifestFile.path + "\n") + fileT.append(" * ModuleName: " + moduleName + "\n") + fileT.append(" * " + "\n") + fileT.append(" * " + firstMatch + "\n") + while (matcher.find()) { + fileT.append(" * " + matcher.group() + "\n") + } + fileT.append(" */" + "\n") + fileT.append("\n") + fileT.append("\n") +} +``` + +然后在项目根目录的 build.gradle 中添加: + +```groovy +boolean enablePrintPers = true +subprojects { + if (enablePrintPers) { + apply from: '../writePermissionToFile.gradle' + } +} +``` + +同步一下,在项目根目录就会生成一个 allPermissionOfModuleFile,内容如前言所示。 + +总体来说还是很简单的,甚至你还可以拿到 merge 之后的 Manifest 文件,然后剔除指定权限,代码如下: + +```groovy +/** + * 以下注释掉的代码是个骚操作 + * 它可以在打包的时候移除掉指定的权限 + * 注意:这个时候的 manifestFile 已经是 merge 之后的了 + * 其实就是字符串替换 + */ +variant.outputs.each { output -> + output.processResources.doFirst { pm -> + String manifestPath = output.processResources.manifestFile + def file = new File(manifestPath) + def manifestContent = file.getText() + String permission = '' + manifestContent = manifestContent.replace(permission, '') + file.delete() + new File(manifestPath).write(manifestContent) + } +} +``` + +开开心心的给我们组长汇报,组长说要是能把第三方库里面的权限也一起拿了就好了。 + +我... + +#### 进阶 + +那怎么才能在 Gradle 构建的时候,拿到第三方库呢?其实拿到第三方库的下载的绝对路径就好了。 + +其实也很简单,直接在 app module 的 build.gradle 添加: + +```groovy +configurations.implementation.setCanBeResolved(true) +configurations.implementation.resolve().each { + if (it.name.endsWith(".aar")) { + println(it.name) + println(it.path) + + File aarFile = it + copy { + from zipTree(aarFile) + into getBuildDir().path + "/destAAR/" + aarFile.name + } + String manifestFilePath = getBuildDir().path + "/destAAR/" + aarFile.name + "/AndroidManifest.xml" + writeFile(new File(manifestFilePath), it.name) + } +} +``` + +这里的拿到的路径如下: + +``` +appcompat-v7-28.0.0.aar +/Users/omooo/.gradle/caches/modules-2/files-2.1/com.android.support/appcompat-v7/28.0.0/132586ec59604a86703796851a063a0ac61f697b/appcompat-v7-28.0.0.aar +constraint-layout-1.1.3.aar +/Users/omooo/.gradle/caches/modules-2/files-2.1/com.android.support.constraint/constraint-layout/1.1.3/2f88a748b5e299029c65a126bd718b2c2ac1714/constraint-layout-1.1.3.aar +support-fragment-28.0.0.aar +/Users/omooo/.gradle/caches/modules-2/files-2.1/com.android.support/support-fragment/28.0.0/f21c8a8700b30dc57cb6277ae3c4c168a94a4e81/support-fragment-28.0.0.aar +animated-vector-drawable-28.0.0.aar +``` + +可以看到,是 gradle caches 里面下载好的缓存的第三方库的绝对路径,所以也就有一个致命的问题,那就是 app module 不能有 implementation project(":mylibrary") 这种本地源码依赖的方式,不然以上代码执行到这一步就会直接挂掉。 + +```groovy +configurations.implementation.resolve().each {} +``` + +那能不能在执行这个把 implementation project(":mylibrary") 这种方式依赖的给过滤掉呢?毕竟这种方式依赖的我们在初级阶段就做好了。 + +但是很可惜,我并没找到办法。 + +#### 再进阶 + +这里就要祭出杀手锏了,来自 [@海海](https://github.com/HiWong) 大佬的思路,直接抄 Gradle 源码。 + +既然是抄源码,那就要首先在 app module 依赖一下 Gralde 源码了: + +```java +implementation 'com.android.tools.build:gradle:3.4.2' +``` + +然后在 app module 的 build.gralde 添加: + +```groovy +import com.android.build.gradle.internal.dependency.ArtifactCollectionWithExtraArtifact +import com.android.build.gradle.internal.publishing.AndroidArtifacts +import org.gradle.internal.component.local.model.OpaqueComponentArtifactIdentifier + +project.afterEvaluate { + boolean isFirst = true + project.android.applicationVariants.all { variant -> + if (!isFirst) return + ArtifactCollection manifests = variant.getVariantData().getScope().getArtifactCollection( + AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, + AndroidArtifacts.ArtifactScope.ALL, + AndroidArtifacts.ArtifactType.MANIFEST) + final Set artifacts = manifests.getArtifacts() + + for (ResolvedArtifactResult artifact : artifacts) { + println(artifact.getFile().absolutePath) + println(getArtifactName(artifact)) + writeFile(artifact.getFile(), getArtifactName(artifact)) + } + isFirst = false + } +} + +static String getArtifactName(ResolvedArtifactResult artifact) { + ComponentIdentifier id = artifact.getId().getComponentIdentifier() + if (id instanceof ProjectComponentIdentifier) { + return ((ProjectComponentIdentifier) id).getProjectPath() + + } else if (id instanceof ModuleComponentIdentifier) { + ModuleComponentIdentifier mID = (ModuleComponentIdentifier) id + return mID.getGroup() + ":" + mID.getModule() + ":" + mID.getVersion() + + } else if (id instanceof OpaqueComponentArtifactIdentifier) { + return id.getDisplayName() + } else if (id instanceof ArtifactCollectionWithExtraArtifact.ExtraComponentIdentifier) { + return id.getDisplayName() + } else { + throw new RuntimeException("Unsupported type of ComponentIdentifier") + } +} +``` + +代码是参考 ProcessApplicationManifest 这个 Task 来写的,如果在较低版本的 Gradle 则名为 MergeTasks,看名字就知道这个 Task 是干什么的了。 + +剩下还有一个需要完善的,那就是把整个流程封装成一个 Task 就好了,这就需要涉及到动态添加依赖了,毕竟这种方法必须要依赖 Gradle 源码了。 + +(逃~ \ No newline at end of file