AS--›Gradle4.0 修改APK生成路徑和文件名(附AAR修改方式以及分析過程)

2020-5-29
AS4.0正式發佈了. 下載地址

2019-01-15
跟上時代的步伐, AS3.3正式發佈了. 下載地址

Gradle4.0 修改apk輸出目錄和文件名

android {
    applicationVariants.all { variant ->
        if (variant.buildType.name != "debug") {
            variant.packageApplicationProvider.get().outputDirectory = rootProject.file("/apk")
        }
        variant.outputs.forEach {
            it.apkData.outputFileName = "test.apk"
        }
    }
}

流程分析方法:點擊查看

Gradle3.3 修改apk輸出目錄和文件名

Gradle3.3 以上的方法

/*Gradle3.3 以上的方法*/
applicationVariants.all { variant ->
   if (variant.buildType.name != "debug") {
       variant.getPackageApplicationProvider().get().outputDirectory = new File(project.rootDir.absolutePath + "/apk")
   }

   variant.getPackageApplicationProvider().get().outputScope.apkDatas.forEach { apkData ->
       apkData.outputFileName = ((project.name != "app") ? project.name : rootProject.name) + "-" +
               variant.versionName + "_" +
               variant.flavorName + "_" +
               variant.buildType.name + "_" +
               ".apk"
   }
}

以下是舊文:

同樣的, Gradle 插件也更新到了3.0, 但是當我們更新到3.0的時候, 很多dsl 無法使用了, 其中就有一個本人比較喜歡的api改變了, 就是修改打包生成的APK文件名.

在未更新之前:

  getApplicationVariants().all { variant ->
      variant.outputs.each { output ->
          def fileName = "AppName-${defaultConfig.versionName}_" +
                  "${releaseTime()}_" +
                  "${variant.productFlavors[0].name}_" +
                  "${variant.buildType.name}_" +
                  "${if (variant.signingConfig == null) "unsigned" else variant.signingConfig.name}.apk"
          //可以通過這個方法修改輸出文件名        
          output.outputFile = new File(output.outputFile.parent, fileName)
      }
  }

更新3.0之後, 上面的方法就會報錯.

Gradle3.0 以下方法修改

android {
	...
	applicationVariants.all { variant ->
	    //這個修改輸出的APK路徑
	 if (variant.buildType.name != "debug") {//防止AS無法安裝debug包(apk)
	    variant.getPackageApplication().outputDirectory = new File(project.rootDir.absolutePath + "/apk")
	 }
	    variant.getPackageApplication().outputScope.apkDatas.forEach { apkData ->
	        //這個修改輸出APK的文件名
	        apkData.outputFileName = "AppName-" +
	                variant.versionName + "_" +
	                apk_time + "_" +
	                variant.flavorName + "_" +
	                variant.buildType.name + "_" +
	                variant.signingConfig.name +
	                ".apk"
	    }
	}
	...
}

具體的API更改說明可以查看:
https://developer.android.google.cn/studio/build/gradle-plugin-3-0-0-migration.html#variant_api
據說是爲了加快編譯速度.


同時
更新到Gradle 3.0之後,如果你有 productFlavors, 那麼必須定義 flavorDimensions, 其次生成的apk name會根據productFlavors.name命名.

android {
	...
	flavorDimensions "type" //這個是必須的
	...
	productFlavors{
		dev{
            dimension "type"  //並且必須使用這個dimension
		}
		pre{
            dimension "type" //並且必須使用這個dimension
		}
		apk{
            dimension "type" //並且必須使用這個dimension
		}
	}
}

apk名如下:
dev-debug.apk
dev-release.apk
pre-debug.apk
pre-release.apk
apk-debug.apk
apk-release.apk

如果你聲明瞭多個 productFlavors, 那麼每個都要使用, 否則會編譯不過.

android {
	...
	flavorDimensions "type", "type2"
	...
	productFlavors{
		dev{
            dimension "type" 
		}
		pre{
            dimension "type" 
		}
		apk{
            dimension "type2" //type2 也必須使用
		}
	}
}

這樣生成的apk名字就會是 (type 對應的 productFlavors.name )+ (type2 對應的 productFlavors.name) .
上面的就會是:
devApk-debug.apk
devApk-release.apk
preApk-debug.apk
preApk-release.apk
這樣就可以通過productFlavors.name達到修改APK的文件名.


修改AAR輸出路徑,和AAR文件名.

分析步驟:

  1. 通過DSL語句, 拿到對應的Java處理類
  2. 通過Java處理類, 找到對應的成員變量進行修改.

a.LibraryPlugin

//DSL語句
apply plugin: 'com.android.library'

//拿到插件對應的Java類
println project.plugins.findPlugin("com.android.library").class

輸出結果:

//找到Java類LibraryPlugin
class com.android.build.gradle.LibraryPlugin

b.LibraryExtension

android {
    compileSdkVersion 28
    defaultConfig {
       ...
    }

    buildTypes {
       ...
    }
    //DSL語句
    println it.class
}

輸出結果:

//找到Java類LibraryExtension
class com.android.build.gradle.LibraryExtension_Decorated

c.LibraryVariantImpl

通過查看LibraryExtension類源碼, 找到方法.

 public DefaultDomainObjectSet<LibraryVariant> getLibraryVariants() {
    return libraryVariantList;
 }

打印此方法所有值:

android {
    compileSdkVersion 28
    defaultConfig {
       ...
    }

    buildTypes {
       ...
    }
    
    //DSL語句
    libraryVariants.all { variant ->
        println variant.class
    }
}

輸出結果:

//找到關鍵類LibraryVariantImpl
class com.android.build.gradle.internal.api.LibraryVariantImpl_Decorated

d.LibraryVariantOutputImpl

通過查看類LibraryVariantImpl的源碼和繼承關係, 找到方法:

@NonNull
@Override
public DomainObjectCollection<BaseVariantOutput> getOutputs() {
    return outputs;
}

枚舉打印輸出:

variant.outputs.all { output ->
    println  output.class
}
//找到關鍵類LibraryVariantOutputImpl
class com.android.build.gradle.internal.api.LibraryVariantOutputImpl_Decorated

e.apkData

通過查看LibraryVariantOutputImpl源碼, 找到方法和成員變量:

//關鍵成員
@NonNull protected final ApkData apkData;

//方法
@Override
@NonNull
protected ApkData getApkData() {
    return apkData;
}

f.outputFileName

通過查看ApkData源碼, 找到成員變量:

//找到關鍵點, 見名知意.肯定是用來修改文件名的.
private String outputFileName;

通過查看LibraryVariantOutputImpl源碼, 還找到方法:

//關鍵方法getOutputFile, 
//在老版本的android build gradle中,
//可以直接通過outputFile成員變量, 修改路徑和文件名.
//新版本不行了, 區別就在下面的方法中.
@NonNull
@Override
public File getOutputFile() {
    Zip packageTask = getPackageLibrary();
    if (packageTask != null) {
        return new File(packageTask.getDestinationDir(), apkData.getOutputFileName());
    } else {
        return super.getOutputFile();
    }
}

通過上面方法的getOutputFile, 能知道.
文件名由apkData.getOutputFileName()決定;
文件路徑由packageTask.getDestinationDir()決定.

so

文件路徑修改

output.packageLibrary.destinationDir = new File(project.rootDir.absolutePath + "/aar")

仔細觀察會發現packageLibraryLibraryVariantImpl類中, 就有方法可以獲取:

//AS 3.0版本
@Override
@Nullable
public Zip getPackageLibrary() {
    variantData
            .getScope()
            .getGlobalScope()
            .getDslScope()
            .getDeprecationReporter()
            .reportDeprecatedApi(
                    "variant.getPackageLibraryProvider()",
                    "variant.getPackageLibrary()",
                    TASK_ACCESS_DEPRECATION_URL,
                    DeprecationReporter.DeprecationTarget.TASK_ACCESS_VIA_VARIANT);
    return variantData.getTaskContainer().getBundleLibraryTask().getOrNull();
}

//AS 3.3版本
@Nullable
@Override
public TaskProvider<Zip> getPackageLibraryProvider() {
    //noinspection unchecked
    return (TaskProvider<Zip>) variantData.getTaskContainer().getBundleLibraryTask();
}

最終修改方式如下:

android {
	...
	libraryVariants.all { variant ->
	    if (variant.buildType.name != "debug") {
	        variant.getPackageLibraryProvider().get().destinationDir = new File(project.rootDir.absolutePath + "/apk")
	    }
	
	    variant.outputs.all { output ->
	        output.apkData.outputFileName = ((project.name != "app") ? project.name : rootProject.name) + "-" +
	                defaultConfig.versionName + "_" +
	                variant.buildType.name +
	                ".aar"
	    }
	}
	...
}

如果對分析過程感興趣的童學可以 點擊此處閱讀


羣內有各(pian)種(ni)各(jin)樣(qun)的大佬,等你來撩.

聯繫作者

點此快速加羣

請使用QQ掃碼加羣, 小夥伴們在等着你哦!

關注我的公衆號, 每天都能一起玩耍哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章