Android Gradle Plugin源碼分析

本文由玉剛說寫作平臺提供寫作贊助,版權(quán)歸玉剛說微信公眾號所有
原作者:ShinyZeng
版權(quán)聲明:未經(jīng)玉剛說許可睡毒,不得以任何形式轉(zhuǎn)載

前言:這篇文章在一個月之前已經(jīng)發(fā)布到玉剛說微信公眾號。Gradle 這一塊對于我們Android開發(fā)來說冗栗,一直是比較無人問津的一塊演顾,平日的開發(fā)中也比較少涉及,即使涉及也是網(wǎng)上查查資料就可以解決隅居,并不會深入理解原理钠至,即使深入了,過一段時間不接觸也容易忘記胎源,譬如我今日再看這篇文章棉钧,就發(fā)現(xiàn)有很多已經(jīng)忘記了...真是汗顏啊...所以簡書中再發(fā)出來,做個備忘吧涕蚤。

一宪卿、源碼依賴

本文基于:
android gradle plugin版本:com.android.tools.build:gradle:2.3.0
gradle 版本:4.1

Gradle源碼總共30個G的诵,為簡單起見,方便大家看源碼佑钾,此處通過gradle依賴的形式來查看源碼西疤,依賴源碼姿勢:

創(chuàng)建一個新工程,app 項目目錄中刪除所有文件休溶,僅留下gradle文件代赁,依賴

apply plugin: 'java'
sourceCompatibility = 1.8

dependencies {
    compile gradleApi()
    compile 'com.android.tools.build:gradle:2.3.0'
}

將跟目錄下的gradle文件,刪除掉gradle依賴

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
//        compile 'com.android.tools.build:gradle:2.3.0'
    }
}

然后rebuild一下邮偎,就可以在External Libraries中查看到android gradle的源碼已經(jīng)依賴了


二管跺、Android Gradle Plugin簡介

我們知道Android gradle plugin是用來構(gòu)建Android工程的gradle插件,在Android gradle 插件中禾进,可以看到app工程和library工程所依賴的plugin是不一樣的

// app 工程
apply plugin: 'com.android.application'
// library 工程
apply plugin: 'com.android.library'

而對應填寫andorid塊中所填寫的配置也不同豁跑,這就是區(qū)分Application和Library的插件的extension塊
分別為:

app工程 -> AppPlugin -> AppExtension
librar工程 -> LibraryPlugin -> LibraryExtension

對應的是AppPlugin和AppExtension,這兩個插件構(gòu)建的流程大抵是相同的泻云,只是各自插件生成的任務不同艇拍,接下來我們著重分析Application插件是如何構(gòu)建我們的Android應用的

三、AppPlugin的構(gòu)建流程

我們先看下app工程中gradle的文件格式

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '26.0.2'
    defaultConfig {
        applicationId "com.zengshaoyi.gradledemo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode project.ext.versionCode
        versionName project.ext.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    lintOptions {
        abortOnError false
    }
}

跟蹤apply方法宠纯,其實是進入到
AppPlugin的apply的方法卸夕,我們可以看到內(nèi)部實現(xiàn)是直接調(diào)用父類BasePlugin的apply方法

protected void apply(@NonNull Project project) {
        checkPluginVersion();

        this.project = project;
        ExecutionConfigurationUtil.setThreadPoolSize(project);
        checkPathForErrors();
        checkModulesForErrors();

        ProfilerInitializer.init(project);
        threadRecorder = ThreadRecorder.get();

        ProcessProfileWriter.getProject(project.getPath())
                .setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
                .setAndroidPlugin(getAnalyticsPluginType())
                .setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST);

        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
                project.getPath(),
                null,
                this::configureProject);

        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
                project.getPath(),
                null,
                this::configureExtension);

        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
                project.getPath(),
                null,
                this::createTasks);

        // Apply additional plugins
        for (String plugin : AndroidGradleOptions.getAdditionalPlugins(project)) {
            project.apply(ImmutableMap.of("plugin", plugin));
        }
    }

threadRecoirder.recode()是記錄最后一個參數(shù)的路徑和執(zhí)行的時間點,前面做了一些必要性的信息檢測之前婆瓜,其實主要做了以下幾件事情:

// 配置項目快集,設置構(gòu)建回調(diào)
this::configureProject
// 配置Extension
this::configureExtension
// 創(chuàng)建任務
this::createTasks

::是java 8引入的特性,詳情可以查看java8特性 廉白,這里就是方法的調(diào)用

configureProject

直接來看源碼

private void configureProject() {
        extraModelInfo = new ExtraModelInfo(project);
        checkGradleVersion();
        AndroidGradleOptions.validate(project);
        // Android SDK處理類
        sdkHandler = new SdkHandler(project, getLogger());
        // 設置項目評估階段回調(diào)
        project.afterEvaluate(p -> {
            // TODO: Read flag from extension.
            if (!p.getGradle().getStartParameter().isOffline()
                    && AndroidGradleOptions.getUseSdkDownload(p)) {
                // 相關配置依賴的下載處理 
                SdkLibData sdkLibData =
                        SdkLibData.download(getDownloader(), getSettingsController());
                dependencyManager.setSdkLibData(sdkLibData);
                sdkHandler.setSdkLibData(sdkLibData);
            }
        });
        // 創(chuàng)建AndroidBuilder
        androidBuilder = new AndroidBuilder(
                project == project.getRootProject() ? project.getName() : project.getPath(),
                creator,
                new GradleProcessExecutor(project),
                new GradleJavaProcessExecutor(project),
                extraModelInfo,
                getLogger(),
                isVerbose());
        // dataBinding的相關處理
        dataBindingBuilder = new DataBindingBuilder();
        dataBindingBuilder.setPrintMachineReadableOutput(
                extraModelInfo.getErrorFormatMode() ==
                        ExtraModelInfo.ErrorFormatMode.MACHINE_PARSABLE);

        // Apply the Java and Jacoco plugins.
        project.getPlugins().apply(JavaBasePlugin.class);
        project.getPlugins().apply(JacocoPlugin.class);
        // 給assemble任務添加描述
        project.getTasks()
                .getByName("assemble")
                .setDescription(
                        "Assembles all variants of all applications and secondary packages.");
        ...

可以看到 configureProject 方法中在 project.afterEvaluate 設置了回調(diào)个初,當項目評估結(jié)束時,根據(jù)項目配置情況猴蹂,設置 dependece 依賴院溺;創(chuàng)建了 AndroidBuilder 對象,這個對象是用來合并manifest 和創(chuàng)建 dex 等作用磅轻,后面在創(chuàng)建任務的過程中會使用到珍逸,結(jié)下來繼續(xù)看 configureProject 的源碼

    // call back on execution. This is called after the whole build is done (not
    // after the current project is done).
    // This is will be called for each (android) projects though, so this should support
    // being called 2+ times.
    // 設置構(gòu)建回調(diào)
    project.getGradle()
            .addBuildListener(
                    new BuildListener() {
                        private final LibraryCache libraryCache = LibraryCache.getCache();

                        @Override
                        public void buildStarted(Gradle gradle) {}

                        @Override
                        public void settingsEvaluated(Settings settings) {}

                        @Override
                        public void projectsLoaded(Gradle gradle) {}

                        @Override
                        public void projectsEvaluated(Gradle gradle) {}

                        @Override
                        public void buildFinished(BuildResult buildResult) {
                            ExecutorSingleton.shutdown();
                            sdkHandler.unload();
                            threadRecorder.record(
                                    ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
                                    project.getPath(),
                                    null,
                                    () -> {
                                        // 當任務執(zhí)行完成時,清楚dex緩存
                                        PreDexCache.getCache()
                                                .clear(
                                                        FileUtils.join(
                                                                project.getRootProject()
                                                                        .getBuildDir(),
                                                                FD_INTERMEDIATES,
                                                                "dex-cache",
                                                                "cache.xml"),
                                                        getLogger());
                                        JackConversionCache.getCache()
                                                .clear(
                                                        FileUtils.join(
                                                                project.getRootProject()
                                                                        .getBuildDir(),
                                                                FD_INTERMEDIATES,
                                                                "jack-cache",
                                                                "cache.xml"),
                                                        getLogger());
                                        libraryCache.unload();
                                        Main.clearInternTables();
                                    });
                        }
                    });
    // 設置創(chuàng)建有向圖任務回調(diào)
    project.getGradle()
            .getTaskGraph()
            .addTaskExecutionGraphListener(
                    taskGraph -> {
                        for (Task task : taskGraph.getAllTasks()) {
                            //  TransformTask是class編譯成dex的重要任務
                            if (task instanceof TransformTask) {
                                Transform transform = ((TransformTask) task).getTransform();
                                if (transform instanceof DexTransform) {
                                    PreDexCache.getCache()
                                            .load(
                                                    FileUtils.join(
                                                            project.getRootProject()
                                                                    .getBuildDir(),
                                                            FD_INTERMEDIATES,
                                                            "dex-cache",
                                                            "cache.xml"));
                                    break;
                                } else if (transform instanceof JackPreDexTransform) {
                                    JackConversionCache.getCache()
                                            .load(
                                                    FileUtils.join(
                                                            project.getRootProject()
                                                                    .getBuildDir(),
                                                            FD_INTERMEDIATES,
                                                            "jack-cache",
                                                            "cache.xml"));
                                    break;
                                }
                            }
                        }
                    });

這里在添加了 BuildListener聋溜,在 buildFinished 的時候清楚了dex緩存谆膳,而在任務有向圖創(chuàng)建的回調(diào)中,判斷是否是 DexTransfrom勤婚,從而從緩存中加載dex摹量。

總結(jié)一下 configureProject 做的事情,主要是進行版本有效性的判斷馒胆,創(chuàng)建了 AndroidBuilder 對象缨称,并設置了構(gòu)建流程的回調(diào)來處理依賴和dex的加載和緩存清理。

configureExtension

這個階段就是配置 extension 的階段祝迂,就是創(chuàng)建我們 android 塊中的可配置的對象

private void configureExtension() {
        final NamedDomainObjectContainer<BuildType> buildTypeContainer =
                project.container(
                        BuildType.class,
                        new BuildTypeFactory(instantiator, project, project.getLogger()));
        final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =
                project.container(
                        ProductFlavor.class,
                        new ProductFlavorFactory(
                                instantiator, project, project.getLogger(), extraModelInfo));
        final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =
                project.container(SigningConfig.class, new SigningConfigFactory(instantiator));

        extension =
                createExtension(
                        project,
                        instantiator,
                        androidBuilder,
                        sdkHandler,
                        buildTypeContainer,
                        productFlavorContainer,
                        signingConfigContainer,
                        extraModelInfo);
        ...

首先創(chuàng)建了 BuildType睦尽、ProductFlavor、SigningConfig 三個類型的Container型雳,接著傳入到了createExtension方法中当凡,點入查看是個抽象的方法,各自的實現(xiàn)在子類中纠俭,這里也就是我們的AppPlugin 中

@NonNull
    @Override
    protected BaseExtension createExtension(
            @NonNull Project project,
            @NonNull Instantiator instantiator,
            @NonNull AndroidBuilder androidBuilder,
            @NonNull SdkHandler sdkHandler,
            @NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,
            @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,
            @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,
            @NonNull ExtraModelInfo extraModelInfo) {
        return project.getExtensions()
                .create(
                        "android",
                        AppExtension.class,
                        project,
                        instantiator,
                        androidBuilder,
                        sdkHandler,
                        buildTypeContainer,
                        productFlavorContainer,
                        signingConfigContainer,
                        extraModelInfo);
    }

這里也就是可以看到我們android塊配置是如何來的了沿量,對應的Extension也確實是AppExtension,繼續(xù)查看 configureExtension 的源碼

        dependencyManager = new DependencyManager(
                project,
                extraModelInfo,
                sdkHandler);

        ndkHandler = new NdkHandler(
                project.getRootDir(),
                null, /* compileSkdVersion, this will be set in afterEvaluate */
                "gcc",
                "" /*toolchainVersion*/);

        taskManager =
                createTaskManager(
                        project,
                        androidBuilder,
                        dataBindingBuilder,
                        extension,
                        sdkHandler,
                        ndkHandler,
                        dependencyManager,
                        registry,
                        threadRecorder);

        variantFactory = createVariantFactory(instantiator, androidBuilder, extension);

        variantManager =
                new VariantManager(
                        project,
                        androidBuilder,
                        extension,
                        variantFactory,
                        taskManager,
                        instantiator,
                        threadRecorder);

        // Register a builder for the custom tooling model
        ModelBuilder modelBuilder = new ModelBuilder(
                androidBuilder,
                variantManager,
                taskManager,
                extension,
                extraModelInfo,
                ndkHandler,
                new NativeLibraryFactoryImpl(ndkHandler),
                getProjectType(),
                AndroidProject.GENERATION_ORIGINAL);
        registry.register(modelBuilder);

        // Register a builder for the native tooling model
        NativeModelBuilder nativeModelBuilder = new NativeModelBuilder(variantManager);
        registry.register(nativeModelBuilder);

這一部分主要是創(chuàng)建一些管理類冤荆,其中 createTaskManager朴则、createVariantFactory 都是抽象方法,對應的實現(xiàn)類

createTaskManager
AppPlugin -> ApplicationTaskManager
LibraryPlugin -> LibraryTaskManager

createVariantFactory
AppPlugin -> ApplicationVariantFactory
LibraryPlugin -> LibraryVariantFactory

這里簡單介紹一下 TaskManager 就是創(chuàng)建具體任務的管理類钓简,app 工程和庫 library 工程所需的構(gòu)建任務是不同的乌妒,后面我們會介紹 app 工程創(chuàng)建的構(gòu)建任務;VariantFactory 就是我們常說的構(gòu)建變體的工廠類外邓,主要是生成Variant(構(gòu)建變體)的對象撤蚊。我們回到 createExtension 的源碼中

        // map the whenObjectAdded callbacks on the containers.
        signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);

        buildTypeContainer.whenObjectAdded(
                buildType -> {
                    SigningConfig signingConfig =
                            signingConfigContainer.findByName(BuilderConstants.DEBUG);
                    buildType.init(signingConfig);
                    variantManager.addBuildType(buildType);
                });

        productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);

        ...

        // create default Objects, signingConfig first as its used by the BuildTypes.
        variantFactory.createDefaultComponents(
                buildTypeContainer, productFlavorContainer, signingConfigContainer);

這一部分做得事情,配置了 BuildTypeContainer损话、ProductFlavorContainer侦啸、SigningConfigContainer 這三個配置項的 whenObjectAdded 的回調(diào)过椎,每個配置的添加都會加入到 variantManager 中喳资;創(chuàng)建默認配置音念,下面是 ApplicationVariantFactory 的 createDefaultComponents 代碼

    @Override
    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);
    }

總結(jié)一下 configureExtension 方法的作用果漾,主要是創(chuàng)建 Android 插件的擴展對象奏路,對配置項 BuildType霉囚、ProductFlavor蝶念、SigningConfig 做了統(tǒng)一的創(chuàng)建和回調(diào)處理翰蠢, 創(chuàng)建taskManager屎篱、variantFactory服赎、variantManager。

createTasks

private void createTasks() {
        threadRecorder.record(
                ExecutionType.TASK_MANAGER_CREATE_TASKS,
                project.getPath(),
                null,
                () -> // 在項目評估之前創(chuàng)建任務  
                        taskManager.createTasksBeforeEvaluate(
                                new TaskContainerAdaptor(project.getTasks())));

        project.afterEvaluate(
                project ->
                        threadRecorder.record(
                                ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
                                project.getPath(),
                                null,
                                // 在項目評估完成之后創(chuàng)建 androidTask
                                () -> createAndroidTasks(false)));
    }

這里主要是分兩塊交播,一個是在 beforeEvaluate 創(chuàng)建任務重虑;一個是在 afterEvaluate 創(chuàng)建任務。這里的區(qū)別是 AndroidTask 是依賴配置項的配置才能生成相應任務秦士,所以是需要在 afterEvaluate 之后創(chuàng)建缺厉,如果對項目評估回調(diào)不理解的話,可以查閱Project文檔。beforeEvaluate 創(chuàng)建的任務跟我們編譯沒有太大關系提针,我們重點查看一下 afterEvaluate 創(chuàng)建的任務 createAndroidTasks

    @VisibleForTesting
    final void createAndroidTasks(boolean force) {
        ...
        threadRecorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
                project.getPath(),
                null,
                () -> {
                    // 創(chuàng)建AndroidTasks
                    variantManager.createAndroidTasks();
                    ApiObjectFactory apiObjectFactory =
                            new ApiObjectFactory(
                                    androidBuilder, extension, variantFactory, instantiator);
                    for (BaseVariantData variantData : variantManager.getVariantDataList()) {
                        apiObjectFactory.create(variantData);
                    }
                });
        ...
      }

我們主要看下variantManager的createAndroidTasks的方法

    /**
     * Variant/Task creation entry point.
     *
     * Not used by gradle-experimental.
     */
    public void createAndroidTasks() {
        variantFactory.validateModel(this);
        variantFactory.preVariantWork(project);

        final TaskFactory tasks = new TaskContainerAdaptor(project.getTasks());
        if (variantDataList.isEmpty()) {
            recorder.record(
                    ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
                    project.getPath(),
                    null /*variantName*/,
                    this::populateVariantDataList);
        }

        // Create top level test tasks.
        recorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS,
                project.getPath(),
                null /*variantName*/,
                () -> taskManager.createTopLevelTestTasks(tasks, !productFlavors.isEmpty()));

        for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {
            recorder.record(
                    ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
                    project.getPath(),
                    variantData.getName(),
                    () -> createTasksForVariantData(tasks, variantData));
        }

        taskManager.createReportTasks(tasks, variantDataList);
    }

首先判斷 variantDataList 是否是空命爬,如果是空的就會進入到 populateVariantDataList 方法中

/**
     * Create all variants.
     */
    public void populateVariantDataList() {
        if (productFlavors.isEmpty()) {
            createVariantDataForProductFlavors(Collections.emptyList());
        } else {
            List<String> flavorDimensionList = extension.getFlavorDimensionList();

            // Create iterable to get GradleProductFlavor from ProductFlavorData.
            Iterable<CoreProductFlavor> flavorDsl =
                    Iterables.transform(
                            productFlavors.values(),
                            ProductFlavorData::getProductFlavor);

            // Get a list of all combinations of product flavors.
            List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
                    ProductFlavorCombo.createCombinations(
                            flavorDimensionList,
                            flavorDsl);

            for (ProductFlavorCombo<CoreProductFlavor>  flavorCombo : flavorComboList) {
                //noinspection unchecked
                createVariantDataForProductFlavors(
                        (List<ProductFlavor>) (List) flavorCombo.getFlavorList());
            }
        }
    }

從方法注釋可以看到,這個方法主要的作用就是創(chuàng)建所有的 variants辐脖,試想一下該段代碼會做哪些事情饲宛,是否是解析 buildType、productFlavor 配置嗜价?

創(chuàng)建構(gòu)建變體(BuildVariant)

繼續(xù)觀察上面的代碼艇抠,可以看到無論是否有配置productFlavor 子項,都會進入到 createVariantDataForProductFlavors 方法久锥。如果有配置的話家淤,通過獲取配置的 flavorDimension 和 productFlavor 數(shù)組,調(diào)用 ProductFlavorCombo.createCombinations 組合出最后的產(chǎn)品風味數(shù)組 flavorComboList 瑟由,最后通過遍歷調(diào)用 createVariantDataForProductFlavors 方法

    /**
     * Creates VariantData for a specified list of product flavor.
     *
     * This will create VariantData for all build types of the given flavors.
     *
     * @param productFlavorList the flavor(s) to build.
     */
    private void createVariantDataForProductFlavors(
              @NonNull List<ProductFlavor> productFlavorList) {
        ...
        for (BuildTypeData buildTypeData : buildTypes.values()) {
            boolean ignore = false;
            ...
            if (!ignore) {
                BaseVariantData<?> variantData = createVariantData(
                        buildTypeData.getBuildType(),
                        productFlavorList);
                variantDataList.add(variantData);
            ...
            }
        }
      ...
}

看上述代碼絮重,通過 creatVariantData 方法,將 buildType 和 productFlavor 的作為參數(shù)傳入错妖,創(chuàng)建了 variantData绿鸣,并且加入到了 variantDataList 集合中,這里我們就是將所有的構(gòu)建變體集合到了 variantDataList 中暂氯。

接著我們返回繼續(xù)看 createAndroidTasks 方法

    /**
     * Variant/Task creation entry point.
     *
     * Not used by gradle-experimental.
     */
    public void createAndroidTasks() {
        ...
        for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {
            recorder.record(
                    ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
                    project.getPath(),
                    variantData.getName(),
                    () -> createTasksForVariantData(tasks, variantData));
        }
        ...
    }

通過上面拿到的variantDataList潮模,遍歷該集合來創(chuàng)建任務

    /**
     * Create tasks for the specified variantData.
     */
    public void createTasksForVariantData(
            final TaskFactory tasks,
            final BaseVariantData<? extends BaseVariantOutputData> variantData) {

        final BuildTypeData buildTypeData = buildTypes.get(
                variantData.getVariantConfiguration().getBuildType().getName());
        if (buildTypeData.getAssembleTask() == null) {
            // 創(chuàng)建assemble + buildType任務
            buildTypeData.setAssembleTask(taskManager.createAssembleTask(tasks, buildTypeData));
        }

        // Add dependency of assemble task on assemble build type task.
        tasks.named("assemble", new Action<Task>() {
            @Override
            public void execute(Task task) {
                assert buildTypeData.getAssembleTask() != null;
                // 將 assemble 任務依賴于我們的 assemble + buildType 任務
                task.dependsOn(buildTypeData.getAssembleTask().getName());
            }
        });

        VariantType variantType = variantData.getType();
        // 根據(jù) variantData 創(chuàng)建 assemble + flavor + buildType 任務
        createAssembleTaskForVariantData(tasks, variantData);
        if (variantType.isForTesting()) {
            ...
        } else {
            // 根據(jù) variantData 創(chuàng)建一系列任務
            taskManager.createTasksForVariantData(tasks, variantData);
        }
    }

首先會先根據(jù) buildType 信息創(chuàng)建 assemble + buildType 的任務,可以看下taskManager. createAssembleTask里的代碼

    @NonNull
    public AndroidTask<DefaultTask> createAssembleTask(
            @NonNull TaskFactory tasks,
            @NonNull VariantDimensionData dimensionData) {
        final String sourceSetName =
                StringHelper.capitalize(dimensionData.getSourceSet().getName());
        return androidTasks.create(
                tasks,
                // 設置任務名字為 assembleXXX
                "assemble" + sourceSetName,
                assembleTask -> {
                    // 設置描述和任務組
                    assembleTask.setDescription("Assembles all " + sourceSetName + " builds.");
                    assembleTask.setGroup(BasePlugin.BUILD_GROUP);
                });
    }

創(chuàng)建完任務之后痴施,將assemble任務依賴于我們的assembleXXX任務擎厢,隨后調(diào)用 createAssembleTaskForVariantData 方法,此方法是創(chuàng)建 assemble + flavor + buildType 任務辣吃,流程多了 productFlavor 任務的創(chuàng)建动遭,這里就不贅述了。后面會執(zhí) createTasksForVariantData神得,這個方法就是根據(jù) variant 生成一系列 Android 構(gòu)建所需任務(后面會詳細介紹)厘惦,回到 createAndroidTasks 方法中

threadRecorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
                project.getPath(),
                null,
                () -> {
                    variantManager.createAndroidTasks();
                    ApiObjectFactory apiObjectFactory =
                            new ApiObjectFactory(
                                    androidBuilder, extension, variantFactory, instantiator);
                    for (BaseVariantData variantData : variantManager.getVariantDataList()) {
                        // 創(chuàng)建variantApi,添加到extensions中
                        apiObjectFactory.create(variantData);
                    }
                });

最后就遍歷 variantDataList 通過 ApiObjectFactory 創(chuàng)建 variantApi哩簿,添加到 extensions 中宵蕉;
至此,我們就已經(jīng)將配置的構(gòu)建變種任務已經(jīng)添加到我們的任務列表中节榜,并形成了相關依賴羡玛。

Application 的編譯任務

我們繼續(xù)查看createTasksForVariantData的最后一行,
taskManager.createTasksForVariantData宗苍,發(fā)現(xiàn) createTasksForVariantData 是抽象方法稼稿,這里的 taskManager 具體實現(xiàn)是 ApplicationTaskManager薄榛,查看 ApplicationTaskManager 的 createTasksForVariantData 方法

    /**
     * Creates the tasks for a given BaseVariantData.
     */
    @Override
    public void createTasksForVariantData(
            @NonNull final TaskFactory tasks,
            @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
        assert variantData instanceof ApplicationVariantData;
        final VariantScope variantScope = variantData.getScope();
        //create sourceGenTask, resGenTask, assetGenTask
        createAnchorTasks(tasks, variantScope);
        createCheckManifestTask(tasks, variantScope);

        handleMicroApp(tasks, variantScope);

        // Create all current streams (dependencies mostly at this point)
        createDependencyStreams(tasks, variantScope);
        // Add a task to process the manifest(s)
        // Add a task to create the res values
        // Add a task to compile renderscript files.
        // Add a task to merge the resource folders
        // Add a task to merge the asset folders
        // Add a task to create the BuildConfig class
        // Add a task to process the Android Resources and generate source files
        // Add a task to process the java resources                   
        // Add a task to process this aidl file
        // Add a task to process shader source
        // Add NDK tasks
        // Add external native build tasks
        // Add a task to merge the jni libs folders
        // Add a compile task
        // Add data binding tasks if enabled
        // create packaging task      
        // create the lint tasks.
        ...
    }

代碼實在太長了,我只留下了每段代碼的注釋让歼,注釋也已經(jīng)非常清楚了敞恋,這個主要就是生成 variantData 的一系列像 compileXXX、generateXXX是越、processXXX耳舅、mergeXXX的任務碌上,這一系列 task 就是構(gòu)建一個可運行的完整APK的所需的所有task倚评。下面介紹在編譯dex中的過程,涉及的幾個task馏予。

Dex的編譯過程

        // Add a compile task
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    CoreJackOptions jackOptions =
                            variantData.getVariantConfiguration().getJackOptions();
                    // create data binding merge task before the javac task so that it can
                    // parse jars before any consumer
                    createDataBindingMergeArtifactsTaskIfNecessary(tasks, variantScope);
                    AndroidTask<? extends JavaCompile> javacTask =
                            // 創(chuàng)建 javac 任務
                            createJavacTask(tasks, variantScope);
                    if (jackOptions.isEnabled()) {
                        AndroidTask<TransformTask> jackTask =
                                createJackTask(tasks, variantScope, true /*compileJavaSource*/);
                        setJavaCompilerTask(jackTask, tasks, variantScope);
                    } else {
                        ...
                        addJavacClassesStream(variantScope);
                        setJavaCompilerTask(javacTask, tasks, variantScope);
                        getAndroidTasks()
                                .create(
                                        tasks,
                                        // 創(chuàng)建 AndroidJarTask 天梧,生成classes.jar
                                        new AndroidJarTask.JarClassesConfigAction(variantScope));
                        createPostCompilationTasks(tasks, variantScope);
                    }
                });

我們直接查看 Add a compile task 注釋下的代碼,在執(zhí)行 createPostCompilationTasks 之前霞丧,先創(chuàng)建了 javac 任務呢岗,任務名稱為 compileXXXJavaWithJavac ,該任務是將 java 源文件編譯成 class 文件蛹尝,具體實現(xiàn)是在 JavaCompileConfigAction 類中后豫。創(chuàng)建 javac 任務之后,接著創(chuàng)建了 AndroidJarTask 任務突那,該任務是將 class 文件整合輸出 jar 包挫酿,具體實現(xiàn)就是在 AndroidJarTask 類中。

緊接著我們來看一下 createPostCompilationTasks 的方法

    /**
     * Creates the post-compilation tasks for the given Variant.
     *
     * These tasks create the dex file from the .class files, plus optional intermediary steps like
     * proguard and jacoco
     *
     */
    public void createPostCompilationTasks(
            @NonNull TaskFactory tasks,
            @NonNull final VariantScope variantScope) {

        checkNotNull(variantScope.getJavacTask());

        variantScope.getInstantRunBuildContext().setInstantRunMode(
                getIncrementalMode(variantScope.getVariantConfiguration()) != IncrementalMode.NONE);

        final BaseVariantData<? extends BaseVariantOutputData> variantData = variantScope.getVariantData();
        final GradleVariantConfiguration config = variantData.getVariantConfiguration();

        TransformManager transformManager = variantScope.getTransformManager();

        ...

        boolean isMinifyEnabled = isMinifyEnabled(variantScope);
        boolean isMultiDexEnabled = config.isMultiDexEnabled();
        // Switch to native multidex if possible when using instant run.
        boolean isLegacyMultiDexMode = isLegacyMultidexMode(variantScope);

        AndroidConfig extension = variantScope.getGlobalScope().getExtension();

        // ----- External Transforms -----
        // apply all the external transforms.
        ...
        // ----- Minify next -----
        if (isMinifyEnabled) {
            boolean outputToJarFile = isMultiDexEnabled && isLegacyMultiDexMode;
            // 內(nèi)部會判斷是否使用 proguard 來創(chuàng)建 proguard 任務和 shrinkResources 任務
            createMinifyTransform(tasks, variantScope, outputToJarFile);
        }
        // ----- 10x support
        ...
        // ----- Multi-Dex support

        Optional<AndroidTask<TransformTask>> multiDexClassListTask;
        // non Library test are running as native multi-dex
        if (isMultiDexEnabled && isLegacyMultiDexMode) {
            ...
        } else {
            multiDexClassListTask = Optional.empty();
        }
        // create dex transform
        // 從 extension 中獲取 dexOptions 項的配置
        DefaultDexOptions dexOptions = DefaultDexOptions.copyOf(extension.getDexOptions());

        ...
        // 創(chuàng)建 DexTransform
        DexTransform dexTransform = new DexTransform(
                dexOptions,
                config.getBuildType().isDebuggable(),
                isMultiDexEnabled,
                isMultiDexEnabled && isLegacyMultiDexMode ? variantScope.getMainDexListFile() : null,
                variantScope.getPreDexOutputDir(),
                variantScope.getGlobalScope().getAndroidBuilder(),
                getLogger(),
                variantScope.getInstantRunBuildContext(),
                AndroidGradleOptions.getBuildCache(variantScope.getGlobalScope().getProject()));
        // 創(chuàng)建 dexTask
        Optional<AndroidTask<TransformTask>> dexTask =
                transformManager.addTransform(tasks, variantScope, dexTransform);
        // need to manually make dex task depend on MultiDexTransform since there's no stream
        // consumption making this automatic
        dexTask.ifPresent(t -> {
            t.optionalDependsOn(tasks, multiDexClassListTask.orElse(null));
            variantScope.addColdSwapBuildTask(t);
        });

        ...
    }

為了講述主流程愕难,我將一些 mutiDex 和 instantRun 判斷的源碼省略了早龟,這里我們關注非mutiDex和非instantRun的情況。我們看到猫缭,如果我們設置了 minifyEnabled 為 true葱弟,那么這里就會去創(chuàng)建 createMinifyTransform ,如果use proguard猜丹,這里會創(chuàng)建 progruad 的任務和 shrinkResources 的任務芝加。后面將創(chuàng)建 dexTask, 這個是 transfromTask 類型的任務射窒,我們先來看下 transFromTask 類

/**
 * A task running a transform.
 */
@ParallelizableTask
public class TransformTask extends StreamBasedTask implements Context {

    private Transform transform;
    ...
    public Transform getTransform() {
        return transform;
    }

   ...

    @TaskAction
    void transform(final IncrementalTaskInputs incrementalTaskInputs)
            throws IOException, TransformException, InterruptedException {

        ...
        recorder.record(
                ExecutionType.TASK_TRANSFORM,
                executionInfo,
                getProject().getPath(),
                getVariantName(),
                new Recorder.Block<Void>() {
                    @Override
                    public Void call() throws Exception {

                        transform.transform(
                                new TransformInvocationBuilder(TransformTask.this)
                                        .addInputs(consumedInputs.getValue())
                                        .addReferencedInputs(referencedInputs.getValue())
                                        .addSecondaryInputs(changedSecondaryInputs.getValue())
                                        .addOutputProvider(
                                                outputStream != null
                                                        ? outputStream.asOutput()
                                                        : null)
                                        .setIncrementalMode(isIncremental.getValue())
                                        .build());
                        return null;
                    }
                });
    }
}

我們知道藏杖,自定義任務中,在任務執(zhí)行階段會去執(zhí)行被 @TaskAction 注解的方法轮洋,這里也就是執(zhí)行 transfrom 方法制市,而 transfrom 方法中最后又會調(diào)用到 transform 的 transfrom 方法,在我們 dexTask 中傳入的 transfrom 是DexTransfrom弊予,那我們就去看下 DexTransfrom 的 transfrom 具體實現(xiàn)

public class DexTransform extends Transform {
    @Override
    public void transform(@NonNull TransformInvocation transformInvocation)
            throws TransformException, IOException, InterruptedException {
        ...
        try {
            // if only one scope or no per-scope dexing, just do a single pass that
            // runs dx on everything.
            if ((jarInputs.size() + directoryInputs.size()) == 1
                    || !dexOptions.getPreDexLibraries()) {

                // since there is only one dex file, we can merge all the scopes into the full
                // application one.
                File outputDir = outputProvider.getContentLocation("main",
                        getOutputTypes(),
                        TransformManager.SCOPE_FULL_PROJECT,
                        Format.DIRECTORY);
                FileUtils.mkdirs(outputDir);

                // first delete the output folder where the final dex file(s) will be.
                FileUtils.cleanOutputDir(outputDir);

                // gather the inputs. This mode is always non incremental, so just
                // gather the top level folders/jars
                final List<File> inputFiles =
                        Stream.concat(
                                jarInputs.stream().map(JarInput::getFile),
                                directoryInputs.stream().map(DirectoryInput::getFile))
                        .collect(Collectors.toList());
                // 通過 AndroidBuilder 轉(zhuǎn)化為 byte
                androidBuilder.convertByteCode(
                        inputFiles,
                        outputDir,
                        multiDex,
                        mainDexListFile,
                        dexOptions,
                        outputHandler);

                for (File file : Files.fileTreeTraverser().breadthFirstTraversal(outputDir)) {
                    if (file.isFile()) {
                        instantRunBuildContext.addChangedFile(FileType.DEX, file);
                    }
                }
            } else {
            ...
    }

最后執(zhí)行到androidBuilder.convertByteCode

    /**
     * Converts the bytecode to Dalvik format
     * @param inputs the input files
     * @param outDexFolder the location of the output folder
     * @param dexOptions dex options
     * @throws IOException
     * @throws InterruptedException
     * @throws ProcessException
     */
    public void convertByteCode(
            @NonNull Collection<File> inputs,
            @NonNull File outDexFolder,
            boolean multidex,
            @Nullable File mainDexList,
            @NonNull DexOptions dexOptions,
            @NonNull ProcessOutputHandler processOutputHandler)
            throws IOException, InterruptedException, ProcessException {checkNotNull(inputs, "inputs cannot be null.");
        checkNotNull(outDexFolder, "outDexFolder cannot be null.");
        checkNotNull(dexOptions, "dexOptions cannot be null.");
        checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
        checkState(mTargetInfo != null,
                "Cannot call convertByteCode() before setTargetInfo() is called.");

        ImmutableList.Builder<File> verifiedInputs = ImmutableList.builder();
        for (File input : inputs) {
            if (checkLibraryClassesJar(input)) {
                verifiedInputs.add(input);
            }
        }
        //創(chuàng)建 DexProcessBuilder
        DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);

        builder.setVerbose(mVerboseExec)
                .setMultiDex(multidex)
                .setMainDexList(mainDexList)
                .addInputs(verifiedInputs.build());

        runDexer(builder, dexOptions, processOutputHandler);
    }

創(chuàng)建了 DexProcessBuilder 祥楣,隨后執(zhí)行到了 runDexer 方法中

public void runDexer(
            @NonNull final DexProcessBuilder builder,
            @NonNull final DexOptions dexOptions,
            @NonNull final ProcessOutputHandler processOutputHandler)
            throws ProcessException, IOException, InterruptedException {
        initDexExecutorService(dexOptions);

        if (dexOptions.getAdditionalParameters().contains("--no-optimize")) {
            mLogger.warning(DefaultDexOptions.OPTIMIZE_WARNING);
        }
       
        if (shouldDexInProcess(dexOptions)) {
            dexInProcess(builder, dexOptions, processOutputHandler);
        } else {
            dexOutOfProcess(builder, dexOptions, processOutputHandler);
        }
    }

進入到dexInProcess方法

private void dexInProcess(
            @NonNull final DexProcessBuilder builder,
            @NonNull final DexOptions dexOptions,
            @NonNull final ProcessOutputHandler outputHandler)
            throws IOException, ProcessException {
        final String submission = Joiner.on(',').join(builder.getInputs());
        mLogger.verbose("Dexing in-process : %1$s", submission);
        try {
            sDexExecutorService.submit(() -> {
                Stopwatch stopwatch = Stopwatch.createStarted();
                ProcessResult result = DexWrapper.run(builder, dexOptions, outputHandler);
                result.assertNormalExitValue();
                mLogger.verbose("Dexing %1$s took %2$s.", submission, stopwatch.toString());
                return null;
            }).get();
        } catch (Exception e) {
            throw new ProcessException(e);
        }
    }
/**
 * Wrapper around the real dx classes.
 */
public class DexWrapper {

    /**
     * Runs the dex command.
     *
     * @return the integer return code of com.android.dx.command.dexer.Main.run()
     */
    public static ProcessResult run(
            @NonNull DexProcessBuilder processBuilder,
            @NonNull DexOptions dexOptions,
            @NonNull ProcessOutputHandler outputHandler) throws IOException, ProcessException {
        ProcessOutput output = outputHandler.createOutput();
        int res;
        try {
            DxContext dxContext = new DxContext(output.getStandardOutput(), output.getErrorOutput());
            // 構(gòu)建 Main.Arguments 參數(shù)
            Main.Arguments args = buildArguments(processBuilder, dexOptions, dxContext);
            res = new Main(dxContext).run(args);
        } finally {
            output.close();
        }

        outputHandler.handleOutput(output);
        return new DexProcessResult(res);
    }
    ...
}

buildArguments方法通過傳入的DexProcessBuilder、dexOptions、dxContext構(gòu)建 arguments误褪,后面使用的args的參數(shù)fileNames责鳍,outName,jarOutput都是從DexProcessBuilder來的兽间,然后執(zhí)行Main的run方法

package com.android.dx.command.dexer;
...
/**
 * Main class for the class file translator.
 */
public class Main {
    /**
     * Run and return a result code.
     * @param arguments the data + parameters for the conversion
     * @return 0 if success &gt; 0 otherwise.
     */
    public int run(Arguments arguments) throws IOException {

        // Reset the error count to start fresh.
        errors.set(0);
        // empty the list, so that  tools that load dx and keep it around
        // for multiple runs don't reuse older buffers.
        libraryDexBuffers.clear();

        args = arguments;
        args.makeOptionsObjects(context);

        OutputStream humanOutRaw = null;
        if (args.humanOutName != null) {
            humanOutRaw = openOutput(args.humanOutName);
            humanOutWriter = new OutputStreamWriter(humanOutRaw);
        }

        try {
            if (args.multiDex) {
                return runMultiDex();
            } else {
                return runMonoDex();
            }
        } finally {
            closeOutput(humanOutRaw);
        }
    }
}

這里我們關注非multiDex的情況历葛,即執(zhí)行了runMonoDex的方法

private int runMonoDex() throws IOException {

        ...
        // 內(nèi)部會創(chuàng)建dexFile,并填充class
        if (!processAllFiles()) {
            return 1;
        }

        if (args.incremental && !anyFilesProcessed) {
            return 0; // this was a no-op incremental build
        }

        // this array is null if no classes were defined
        byte[] outArray = null;

        if (!outputDex.isEmpty() || (args.humanOutName != null)) {
            // 內(nèi)部通過Dex類toDex 方法將 class 文件轉(zhuǎn)化dex byte[]
            outArray = writeDex(outputDex);

            if (outArray == null) {
                return 2;
            }
        }

        if (args.incremental) {
            outArray = mergeIncremental(outArray, incrementalOutFile);
        }

        outArray = mergeLibraryDexBuffers(outArray);

        if (args.jarOutput) {
            // Effectively free up the (often massive) DexFile memory.
            outputDex = null;

            if (outArray != null) {
                // 輸出的文件名為 classes.dex
                outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
            }
            if (!createJar(args.outName)) {
                return 3;
            }
        } else if (outArray != null && args.outName != null) {
            OutputStream out = openOutput(args.outName);
            out.write(outArray);
            closeOutput(out);
        }

        return 0;
    }

上面的代碼中嘀略,填充class以及dex流轉(zhuǎn)換恤溶,內(nèi)部流程較為復雜,就不再繼續(xù)深入帜羊,簡單做下總結(jié):
1.通過執(zhí)行 processAllFiles 咒程,內(nèi)部創(chuàng)建 DexFile 也就是outputDex,并且填充 class 文件
2.通過 writeDex 方法讼育,將 outputDex 傳入帐姻,方法內(nèi)部執(zhí)行的是 outputDex.toDex 方法,將 outputDex 內(nèi)部填充的 class 轉(zhuǎn)化為 dex 的 byte[] 返回
3.最后將 byte[] 數(shù)組創(chuàng)建 classes.dex 輸出

總結(jié):
Android Gradle Plugin源碼繁多奶段,以上文章只是對整體流程的簡單梳理饥瓷,其中簡要介紹了構(gòu)建變體任務的解析和添加 ,最后對編譯dex流程做了簡單分析痹籍。個人精力有限呢铆,這里的源碼解析也只是九牛一毛,如有紕漏词裤,歡迎大家拍磚刺洒,希望這篇文章能幫助到想了解 Android Gradle Plguin 原理的同學。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吼砂,一起剝皮案震驚了整個濱河市逆航,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渔肩,老刑警劉巖因俐,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異周偎,居然都是意外死亡抹剩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門蓉坎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澳眷,“玉大人,你說我怎么就攤上這事蛉艾∏唬” “怎么了衷敌?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拓瞪。 經(jīng)常有香客問我缴罗,道長,這世上最難降的妖魔是什么祭埂? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任面氓,我火速辦了婚禮,結(jié)果婚禮上蛆橡,老公的妹妹穿的比我還像新娘舌界。我一直安慰自己,他們只是感情好航罗,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布禀横。 她就那樣靜靜地躺著,像睡著了一般粥血。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酿箭,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天复亏,我揣著相機與錄音,去河邊找鬼缭嫡。 笑死缔御,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的妇蛀。 我是一名探鬼主播耕突,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼评架!你這毒婦竟也來了眷茁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤纵诞,失蹤者是張志新(化名)和其女友劉穎上祈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浙芙,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡登刺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗡呼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纸俭。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖南窗,靈堂內(nèi)的尸體忽然破棺而出揍很,到底是詐尸還是另有隱情廊宪,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布女轿,位于F島的核電站箭启,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛉迹。R本人自食惡果不足惜傅寡,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望北救。 院中可真熱鬧荐操,春花似錦、人聲如沸珍策。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攘宙。三九已至屯耸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹭劈,已是汗流浹背疗绣。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铺韧,地道東北人多矮。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像哈打,于是被迫代替她去往敵國和親塔逃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,074評論 25 707
  • Gradle 是一款構(gòu)建系統(tǒng)工具料仗,它的 DSL 基于 Groovy 實現(xiàn)湾盗。Gradle 構(gòu)建的大部分功能都是通過插...
    任教主來也閱讀 3,054評論 3 6
  • 說明 本文主要介紹和Gradle關系密切、相對不容易理解的配置罢维,偏重概念介紹淹仑。部分內(nèi)容是Android特有的(例如...
    jzj1993閱讀 15,623評論 1 62
  • 經(jīng)過師生一上午緊張勞作,新溝已漸有雛形肺孵。為趕在天黑前完成所有任務匀借,午飯后略作休息,老師就招呼同學們立即開工...
    泉潁頤頤閱讀 455評論 0 0
  • 枚舉普通使用所有定義的枚舉類型均繼承至抽象類Enum平窘,Enum的構(gòu)造方法是私有化的吓肋,只有編譯器可以調(diào)用。 輸出 枚...
    Sandy_678f閱讀 369評論 1 3