add printDependencesPermissions

master
Omooo 5 years ago
parent 596be60c7b
commit b0ac2acbec
  1. 2
      README.md
  2. 119
      blogs/Android/Gradle/Android Gradle Plugin 流程分析.md
  3. 244
      blogs/Android/Gradle/依赖权限信息.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 文件格式

@ -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<BuildType> buildTypes,
@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
@NonNull NamedDomainObjectContainer<SigningConfig> 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.

@ -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
*
* <uses-permission android:name="android.permission.INTERNET" />
* <uses-permission android:name="android.permission.CALL_PHONE" />
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.RECEIVE_SMS" />
*/
/**
* Path: /Users/omooo/AndroidStudioProjects/Projects/MyApplication/mylibrary/src/main/AndroidManifest.xml
* ModuleName: mylibrary
*
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.INTERNET" />
*/
```
#### 入门
较早之前,我们 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("<uses-permission.+./>")
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 = '<uses-permission android:name="android.permission.RECEIVE_SMS" />'
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<ResolvedArtifactResult> 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 源码了。
(逃~
Loading…
Cancel
Save