Android構建流程——篇三

Task5 checkDebugManifest

1. input/ouput

taskName:checkDebugManifest
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/check-manifest/debug

2. 核心類(CheckManifest)

@TaskAction
void check() {
    if (!isOptional && manifest != null && !manifest.isFile()) {
        throw new IllegalArgumentException(
                String.format(
                        "Main Manifest missing for variant %1$s. Expected path: %2$s",
                        getVariantName(), getManifest().getAbsolutePath()));
    }
}

該任務是校驗操作,就是校驗清單文件的合法性,不合法則中斷任務,拋出異常

配置階段操作

@Override
public void execute(@NonNull CheckManifest checkManifestTask) {
	//1. 設置任務實現類
    scope.getTaskContainer().setCheckManifestTask(checkManifestTask);
	
	//2. 設置相關入參
    checkManifestTask.setVariantName(
            scope.getVariantData().getVariantConfiguration().getFullName());
    checkManifestTask.setOptional(isManifestOptional);
    checkManifestTask.manifest =
            scope.getVariantData().getVariantConfiguration().getMainManifest();
	
	//3. 出參目錄配置
    checkManifestTask.fakeOutputDir =
            new File(
                    scope.getGlobalScope().getIntermediatesDir(),
                    "check-manifest/" + scope.getVariantConfiguration().getDirName());
}

依賴任務:preBuildTask

Task6 generateDebugBuildConfig

BuildConfig.java想必大家非常熟悉,該任務就是生成它

1. input/output

taskName:generateDebugBuildConfig
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/generated/source/buildConfig/debug
========================END==============================

2. 核心類(GenerateBuildConfig)

@TaskAction
void generate() throws IOException {
		
	  //1. 清理操作	
      // must clear the folder in case the packagename changed, otherwise,
      // there'll be two classes.
      File destinationDir = getSourceOutputDir();
      FileUtils.cleanOutputDir(destinationDir);
		
	  //2. 構建一個BuildConfig生成器,用於生成BuildConfig.java	
      BuildConfigGenerator generator = new BuildConfigGenerator(
              getSourceOutputDir(),
              getBuildConfigPackageName());

      // Hack (see IDEA-100046): We want to avoid reporting "condition is always true"
      // from the data flow inspection, so use a non-constant value. However, that defeats
      // the purpose of this flag (when not in debug mode, if (BuildConfig.DEBUG && ...) will
      // be completely removed by the compiler), so as a hack we do it only for the case
      // where debug is true, which is the most likely scenario while the user is looking
      // at source code.
      //map.put(PH_DEBUG, Boolean.toString(mDebug));
		
	  //3. 給生成器對象配置6個固定字段
	  // DEBUG、APPLICATION_ID、BUILD_TYPE、FLAVOR、VERSION_CODE、VERSION_NAME	
      generator
              .addField(
                      "boolean",
                      "DEBUG",
                      isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
              .addField("String", "APPLICATION_ID", '"' + appPackageName.get() + '"')
              .addField("String", "BUILD_TYPE", '"' + getBuildTypeName() + '"')
              .addField("String", "FLAVOR", '"' + getFlavorName() + '"')
              .addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
              .addField(
                      "String", "VERSION_NAME", '"' + Strings.nullToEmpty(getVersionName()) + '"')
              .addItems(getItems());//4. getItems,則是我們在build.gradle自定義的字段屬性值

	  //5. 生成flavor字段
      List<String> flavors = getFlavorNamesWithDimensionNames();
      int count = flavors.size();
      if (count > 1) {
          for (int i = 0; i < count; i += 2) {
              generator.addField(
                      "String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"');
          }
      }

      generator.generate();
  }

在build.gralde -> defaultConfig 添加

buildConfigField("String", "name", "\"xiaobaoyihao\"")

執行

./gradlew generateDebugBuildConfig

看下BuildConfig.java結構

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.gradle.task.demo;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.gradle.task.demo";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Fields from default config.
  public static final String name = "xiaobaoyihao";
}

可以看到自定義屬性字段成功寫入到BuildConfig.java類中了;至於類生成具體實現細節是在generate方法中,主要使用了文件輸出流配合JavaWriter類操作來協調完成的,有興趣自行研究

//BuildConfigGenerator.java
public void generate() throws IOException {
    File pkgFolder = getFolderPath();
    if (!pkgFolder.isDirectory()) {
        if (!pkgFolder.mkdirs()) {
            throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
        }
    }

    File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);

    Closer closer = Closer.create();
    try {
        FileOutputStream fos = closer.register(new FileOutputStream(buildConfigJava));
        OutputStreamWriter out = closer.register(new OutputStreamWriter(fos, Charsets.UTF_8));
        JavaWriter writer = closer.register(new JavaWriter(out));
		
		//1. 生成文件頭註釋說明文案
        writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
                .emitPackage(mBuildConfigPackageName)
                .beginType("BuildConfig", "class", PUBLIC_FINAL);
		
		//2. 就是上面提到的6個固定字段屬性
        for (ClassField field : mFields) {
            emitClassField(writer, field);
        }
		
		//3. 自定義屬性字段
        for (Object item : mItems) {
            if (item instanceof ClassField) {
                emitClassField(writer, (ClassField) item);
            } else if (item instanceof String) {
                writer.emitSingleLineComment((String) item);
            }
        }

        writer.endType();
    } catch (Throwable e) {
        throw closer.rethrow(e);
    } finally {
        closer.close();
    }
}

Task7 prepareLintJar

1. input/ouput

taskName:prepareLintJar
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/lint_jar/global/prepareLintJar/lint.jar

2. 核心類(PrepareLintJar)

/**
 * Task that takes the configuration result, and check that it's correct.
 *
 * <p>Then copies it in the build folder to (re)publish it. This is not super efficient but because
 * publishing is done at config time when we don't know yet what lint.jar file we're going to
 * publish, we have to do this.
 */
public class PrepareLintJar extends DefaultTask {
	...
	@TaskAction
	public void prepare() throws IOException {
	    // there could be more than one files if the dependency is on a sub-projects that
	    // publishes its compile dependencies. Rather than query getSingleFile and fail with
	    // a weird message, do a manual check
	    //1. 獲取項目中依賴的自定義lint規則jar,此處gradle插件對其做了限制,只能有一個lint.jar超過一個會build失敗
	    Set<File> files = lintChecks.getFiles();
	    if (files.size() > 1) {
	        throw new RuntimeException(
	                "Found more than one jar in the '"
	                        + VariantDependencies.CONFIG_NAME_LINTCHECKS
	                        + "' configuration. Only one file is supported. If using a separate Gradle project, make sure compilation dependencies are using compileOnly");
	    }
		
		//2. 如果項目中沒有自定義lint規則,則清理文件,否則copyt lint.jar到指定目錄
	    if (files.isEmpty()) {
	        if (outputLintJar.isFile()) {
	            FileUtils.delete(outputLintJar);
	        }
	    } else {
	        FileUtils.mkdirs(outputLintJar.getParentFile());
	        Files.copy(Iterables.getOnlyElement(files), outputLintJar);
	    }
	}
	
	//ConfigAction.java
	@Override
	public void execute(@NonNull PrepareLintJar task) {	
		//指定輸出位置
	    task.outputLintJar =
	            scope.getArtifacts()
	                    .appendArtifact(InternalArtifactType.LINT_JAR, task, FN_LINT_JAR);
	    //讀取自定義規則文件集合                
	    task.lintChecks = scope.getLocalCustomLintChecks();
	}
}

從上可以看出這個任務就是copy功能,lint.jar不能超過1個,也就是說如果項目中存在混合語言時,如果你要對其進行各自自定義一套check規則(每套規則會生成一個lint.jar),那對不起,執行到這任務直接中斷,這應該算Android插件一個缺陷吧,高版本已修復;

關於如何自定義check規則,可以參考如下官網相關鏈接

https://developer.android.com/studio/write/lint?hl=zh-cn
https://github.com/googlesamples/android-custom-lint-rules.git
http://tools.android.com/tips/lint-custom-rules

Task8 mainApkListPersistenceDebug

1. input/output

taskName:mainApkListPersistenceDebug
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/apk_list/debug/mainApkListPersistenceDebug/apk-list.gson

格式化後的apk-list.gson

[
  {
    "type": "MAIN",
    "splits": [],
    "versionCode": 1,
    "versionName": "1.0",
    "enabled": true,
    "outputFile": "app-debug.apk",
    "fullName": "debug",
    "baseName": "debug"
  }
]

這個任務其實就是生成一個apk信息的gson文件,看下核心入口代碼

2. 核心類(MainApkListPersistence)

這個類非常簡短,我直接全部貼出來吧,有意思的是它是kotlin寫的

/**
 * Task to persist the {@see OutputScope#apkdatas} which allows downstream tasks to depend
 * on the {@see InternalArtifactType#APK_LIST} rather than on various complicated data structures.
 * This also allow to record the choices made during configuration time about what APKs will be
 * produced and which ones are enabled.
 */
open class MainApkListPersistence : AndroidVariantTask() {

    @get:OutputFile
    lateinit var outputFile: File
        private set

    @get:Input
    lateinit var apkData : Collection<ApkData>
        private set

    @TaskAction
    fun fullTaskAction() {
		//1. 清理操作
        FileUtils.deleteIfExists(outputFile)
        
		//2. 返回一個apk信息的字符串
        val apkDataList = ExistingBuildElements.persistApkList(apkData)
		
		//3. 寫入文件
        FileUtils.createFile(outputFile, apkDataList)
    }

    class ConfigAction(
            val scope: VariantScope) :
            TaskConfigAction<MainApkListPersistence> {
        override fun getName() = scope.getTaskName("mainApkListPersistence")

        override fun getType() = MainApkListPersistence::class.java

        override fun execute(task: MainApkListPersistence) {

            task.variantName = scope.fullVariantName
            task.apkData = scope.outputScope.apkDatas

            task.outputFile = scope.artifacts.appendArtifact(
                InternalArtifactType.APK_LIST,
                task,
                SdkConstants.FN_APK_LIST)
        }
    }
}

可以看到關鍵代碼其實是在第二部操作中,我們看看內部如何實現的,

//ExistingBuildElements.kt
@JvmStatic
fun persistApkList(apkInfos: Collection<ApkInfo>): String {
    val gsonBuilder = GsonBuilder()
    gsonBuilder.registerTypeHierarchyAdapter(ApkInfo::class.java, ApkInfoAdapter())
    val gson = gsonBuilder.create()
    return gson.toJson(apkInfos)
}

//ApkInfoAdapter.kt
override fun write(out: JsonWriter, value: ApkInfo?) {
    if (value == null) {
        out.nullValue()
        return
    }
    out.beginObject()
    out.name("type").value(value.type.toString())
    out.name("splits").beginArray()
    for (filter in value.filters) {
        out.beginObject()
        out.name("filterType").value(filter.filterType)
        out.name("value").value(filter.identifier)
        out.endObject()
    }
    out.endArray()
    out.name("versionCode").value(value.versionCode.toLong())
    if (value.versionName != null) {
        out.name("versionName").value(value.versionName)
    }
    out.name("enabled").value(value.isEnabled)
    if (value.filterName != null) {
        out.name("filterName").value(value.filterName)
    }
    if (value.outputFileName != null) {
        out.name("outputFile").value(value.outputFileName)
    }
    out.name("fullName").value(value.fullName)
    out.name("baseName").value(value.baseName)
    out.endObject()
}

json字符串的構造其實是依賴於goole自定義的ApkInfoAdapter適配器類來實現的,具體不說啦。
今天就到這吧。。。

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