第三篇:gradle 編譯 Android app 概覽


引言:經過上兩篇的論述,我們已經從代碼到架構都簡單的熟悉了一遍,理論上,只要知道android app的編譯過程,我們大可以自己寫一份用gradle編譯app的插件,插件內將將整個流程用Task的依賴串聯起來。現在我們看看gradle是怎么做的。

 

復習,android 編譯流程

面試中經常出現的問題,android的編譯流程。這個問題十分簡單和好記。android是用java來開發的,所以最基本的流程和java一致:.java -> .class。android有着自己獨特的虛擬機環境dalvik,畢竟arm架構寄存器比較多,而是內存較小,所以要轉換一下.class -> .dex。再說資源那條線,第一件事當然是生成R.java文件了,這個java文件和我們一般的java文件並沒有區別,不然我們代碼怎么用呢,然后是將資源文件和dex文件打包成apk:rec+.dex=.apk。有了.apk,簽名.signed.apk,壓縮.aligned.signed.apk。當然還有其他的分支,比如 .aidl,可以理解成一種特殊的資源文件,同樣生成.java文件和我們的代碼對接,jni也是同樣,只是生成順序是我們寫java端的,當然這無關緊要,符合接口約定就可以。

主線: .java  --(java)-->.class--(dex)-->.dex +rec --(apkbuild)-->.apk--(jarSigne)-->.Singed.apk--(zipalign)-->.aligned.signed.apk   

 

設置屬性即可

我們用android studio 創建一個android app程序,打開他的build.gradle。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "23.0.0 rc2"

    defaultConfig {
        applicationId "suning.com.myapplication"
        minSdkVersion 15
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    sourceSets{

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
}

android studio的好處是我們能方便的看到源碼,鑒於上兩篇把整個代碼組織流程已經推敲過,所以整個腳本並沒有什么難以理解的地方。

開頭第一句導入 com.android.application 的編譯插件,下面則是對整個編譯的參數進行設置(和第二篇的最后一個例子如出一轍,不過例子上的代碼很簡化)。我們按住ctr,鼠標左擊第三行的android,就可以進入這個屬性的源碼:AppExtension.groovy,它繼承於TestedExtension.groovy,又繼承於BaseExtension.groovy。看這一長串的extension,結合我們上一篇的例子,不難猜出這是需要綁定在project里的extension 屬性。我們看下BaseExtension的源碼:

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.build.gradle

import com.android.SdkConstants
import com.android.annotations.NonNull
import com.android.annotations.Nullable
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.TestVariant
import com.android.build.gradle.api.VariantFilter
import com.android.build.gradle.internal.CompileOptions
import com.android.build.gradle.internal.ExtraModelInfo
import com.android.build.gradle.internal.LoggingUtil
import com.android.build.gradle.internal.SdkHandler
import com.android.build.gradle.internal.SourceSetSourceProviderWrapper
import com.android.build.gradle.internal.coverage.JacocoExtension
import com.android.build.gradle.internal.dsl.AaptOptions
import com.android.build.gradle.internal.dsl.AdbOptions
import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory
import com.android.build.gradle.internal.dsl.BuildType
import com.android.build.gradle.internal.dsl.DexOptions
import com.android.build.gradle.internal.dsl.GroupableProductFlavor
import com.android.build.gradle.internal.dsl.LintOptions
import com.android.build.gradle.internal.dsl.PackagingOptions
import com.android.build.gradle.internal.dsl.ProductFlavor
import com.android.build.gradle.internal.dsl.SigningConfig
import com.android.build.gradle.internal.dsl.Splits
import com.android.build.gradle.internal.dsl.TestOptions
import com.android.builder.core.AndroidBuilder
import com.android.builder.core.BuilderConstants
import com.android.builder.model.SourceProvider
import com.android.builder.sdk.TargetInfo
import com.android.builder.testing.api.DeviceProvider
import com.android.builder.testing.api.TestServer
import com.android.sdklib.repository.FullRevision
import com.google.common.collect.Lists
import groovy.transform.CompileStatic
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.SourceSet
import org.gradle.internal.reflect.Instantiator

/**
 * Base 'android' extension for all android plugins.
 *
 * <p>This is never used directly. Instead,
 *<ul>
 * <li>Plugin <code>com.android.application</code> uses {@link AppExtension}</li>
 * <li>Plugin <code>com.android.library</code> uses {@link LibraryExtension}</li>
 * </ul>
 */
public abstract class BaseExtension {

    private String target
    private FullRevision buildToolsRevision

    /** Default config, shared by all flavors. */
    final ProductFlavor defaultConfig

    /** Options for aapt, tool for packaging resources. */
    final AaptOptions aaptOptions

    /** Lint options. */
    final LintOptions lintOptions

    /** Dex options. */
    final DexOptions dexOptions

    /** Options for running tests. */
    final TestOptions testOptions

    /** Compile options */
    final CompileOptions compileOptions

    /** Packaging options. */
    final PackagingOptions packagingOptions

    /** JaCoCo options. */
    final JacocoExtension jacoco

    /**
     * APK splits options.
     *
     * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
     */
    final Splits splits

    /** All product flavors used by this project. */
    final NamedDomainObjectContainer<GroupableProductFlavor> productFlavors

    /** Build types used by this project. */
    final NamedDomainObjectContainer<BuildType> buildTypes

    /** Signing configs used by this project. */
    final NamedDomainObjectContainer<SigningConfig> signingConfigs

    private ExtraModelInfo extraModelInfo

    protected Project project

    /** Adb options */
    final AdbOptions adbOptions;

    /** A prefix to be used when creating new resources. Used by Studio */
    String resourcePrefix

    List<String> flavorDimensionList

    private String defaultPublishConfig = "release"
    private boolean publishNonDefault = false

    private Closure<Void> variantFilter

    private final List<DeviceProvider> deviceProviderList = Lists.newArrayList();
    private final List<TestServer> testServerList = Lists.newArrayList();

    private final AndroidBuilder androidBuilder

    private final SdkHandler sdkHandler

    protected Logger logger

    private boolean isWritable = true;

    /**
     * The source sets container.
     */
    final NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer

    BaseExtension(
            @NonNull ProjectInternal project,
            @NonNull Instantiator instantiator,
            @NonNull AndroidBuilder androidBuilder,
            @NonNull SdkHandler sdkHandler,
            @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
            @NonNull NamedDomainObjectContainer<GroupableProductFlavor> productFlavors,
            @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
            @NonNull ExtraModelInfo extraModelInfo,
            boolean isLibrary) {
        this.androidBuilder = androidBuilder
        this.sdkHandler = sdkHandler
        this.buildTypes = buildTypes
        this.productFlavors = productFlavors
        this.signingConfigs = signingConfigs
        this.extraModelInfo = extraModelInfo
        this.project = project

        logger = Logging.getLogger(this.class)

        defaultConfig = instantiator.newInstance(ProductFlavor, BuilderConstants.MAIN,
                project, instantiator, project.getLogger())

        aaptOptions = instantiator.newInstance(AaptOptions)
        dexOptions = instantiator.newInstance(DexOptions)
        lintOptions = instantiator.newInstance(LintOptions)
        testOptions = instantiator.newInstance(TestOptions)
        compileOptions = instantiator.newInstance(CompileOptions)
        packagingOptions = instantiator.newInstance(PackagingOptions)
        jacoco = instantiator.newInstance(JacocoExtension)
        adbOptions = instantiator.newInstance(AdbOptions)
        splits = instantiator.newInstance(Splits, instantiator)

        sourceSetsContainer = project.container(AndroidSourceSet,
                new AndroidSourceSetFactory(instantiator, project, isLibrary))

        sourceSetsContainer.whenObjectAdded { AndroidSourceSet sourceSet ->
            ConfigurationContainer configurations = project.getConfigurations()

            createConfiguration(
                    configurations,
                    sourceSet.getCompileConfigurationName(),
                    "Classpath for compiling the ${sourceSet.name} sources.")

            String packageConfigDescription
            if (isLibrary) {
                packageConfigDescription = "Classpath only used when publishing '${sourceSet.name}'."
            } else {
                packageConfigDescription = "Classpath packaged with the compiled '${sourceSet.name}' classes."
            }
            createConfiguration(
                    configurations,
                    sourceSet.getPackageConfigurationName(),
                    packageConfigDescription)

            createConfiguration(
                    configurations,
                    sourceSet.getProvidedConfigurationName(),
                    "Classpath for only compiling the ${sourceSet.name} sources.")

            createConfiguration(
                    configurations,
                    sourceSet.getWearAppConfigurationName(),
                    "Link to a wear app to embed for object '${sourceSet.name}'.")

            sourceSet.setRoot(String.format("src/%s", sourceSet.getName()))
        }

        sourceSetsContainer.create(defaultConfig.name)
    }

    /**
     * Disallow further modification on the extension.
     */
    public void disableWrite() {
        isWritable = false
    }

    protected checkWritability() {
        if (!isWritable) {
            throw new GradleException(
                    "Android tasks have already been created.\n" +
                            "This happens when calling android.applicationVariants,\n" +
                            "android.libraryVariants or android.testVariants.\n" +
                            "Once these methods are called, it is not possible to\n" +
                            "continue configuring the model.")
        }
    }

    protected void createConfiguration(
            @NonNull ConfigurationContainer configurations,
            @NonNull String configurationName,
            @NonNull String configurationDescription) {
        logger.info("Creating configuration ${configurationName}.")

        Configuration configuration = configurations.findByName(configurationName)
        if (configuration == null) {
            configuration = configurations.create(configurationName)
        }
        configuration.setVisible(false);
        configuration.setDescription(configurationDescription)
    }

    /**
     * Sets the compile SDK version, based on full SDK version string, e.g.
     * <code>android-21</code> for Lollipop.
     */
    void compileSdkVersion(String version) {
        checkWritability()
        this.target = version
    }

    /**
     * Sets the compile SDK version, based on API level, e.g. 21 for Lollipop.
     */
    void compileSdkVersion(int apiLevel) {
        compileSdkVersion("android-" + apiLevel)
    }

    void setCompileSdkVersion(int apiLevel) {
        compileSdkVersion(apiLevel)
    }

    void setCompileSdkVersion(String target) {
        compileSdkVersion(target)
    }

    void buildToolsVersion(String version) {
        checkWritability()
        buildToolsRevision = FullRevision.parseRevision(version)
    }

    /**
     * <strong>Required.</strong> Version of the build tools to use.
     *
     * <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
     * back may give a slightly different string.
     */
    String getBuildToolsVersion() {
        return buildToolsRevision.toString()
    }

    void setBuildToolsVersion(String version) {
        buildToolsVersion(version)
    }

    /**
     * Configures the build types.
     */
    void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
        checkWritability()
        action.execute(buildTypes)
    }

    /**
     * Configures the product flavors.
     */
    void productFlavors(Action<? super NamedDomainObjectContainer<GroupableProductFlavor>> action) {
        checkWritability()
        action.execute(productFlavors)
    }

    /**
     * Configures the signing configs.
     */
    void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
        checkWritability()
        action.execute(signingConfigs)
    }

    public void flavorDimensions(String... dimensions) {
        checkWritability()
        flavorDimensionList = Arrays.asList(dimensions)
    }

    /**
     * Configures the source sets. Note that the Android plugin uses its own implementation of
     * source sets, {@link AndroidSourceSet}.
     */
    void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> action) {
        checkWritability()
        action.execute(sourceSetsContainer)
    }

    /**
     * All source sets. Note that the Android plugin uses its own implementation of
     * source sets, {@link AndroidSourceSet}.
     */
    NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
        sourceSetsContainer
    }

    /**
     * The default configuration, inherited by all build flavors (if any are defined).
     */
    void defaultConfig(Action<ProductFlavor> action) {
        checkWritability()
        action.execute(defaultConfig)
    }

    /**
     * Configures aapt options.
     */
    void aaptOptions(Action<AaptOptions> action) {
        checkWritability()
        action.execute(aaptOptions)
    }

    /**
     * Configures dex options.
     * @param action
     */
    void dexOptions(Action<DexOptions> action) {
        checkWritability()
        action.execute(dexOptions)
    }

    /**
     * Configure lint options.
     */
    void lintOptions(Action<LintOptions> action) {
        checkWritability()
        action.execute(lintOptions)
    }

    /** Configures the test options. */
    void testOptions(Action<TestOptions> action) {
        checkWritability()
        action.execute(testOptions)
    }

    /**
     * Configures compile options.
     */
    void compileOptions(Action<CompileOptions> action) {
        checkWritability()
        action.execute(compileOptions)
    }

    /**
     * Configures packaging options.
     */
    void packagingOptions(Action<PackagingOptions> action) {
        checkWritability()
        action.execute(packagingOptions)
    }

    /**
     * Configures JaCoCo options.
     */
    void jacoco(Action<JacocoExtension> action) {
        checkWritability()
        action.execute(jacoco)
    }

    /**
     * Configures adb options.
     */
    void adbOptions(Action<AdbOptions> action) {
        checkWritability()
        action.execute(adbOptions)
    }

    /**
     * Configures APK splits.
     */
    void splits(Action<Splits> action) {
        checkWritability()
        action.execute(splits)
    }

    void deviceProvider(DeviceProvider deviceProvider) {
        checkWritability()
        deviceProviderList.add(deviceProvider)
    }

    @NonNull
    List<DeviceProvider> getDeviceProviders() {
        return deviceProviderList
    }

    void testServer(TestServer testServer) {
        checkWritability()
        testServerList.add(testServer)
    }

    @NonNull
    List<TestServer> getTestServers() {
        return testServerList
    }

    public void defaultPublishConfig(String value) {
        setDefaultPublishConfig(value)
    }

    public void publishNonDefault(boolean value) {
        publishNonDefault = value
    }

    /**
     * Name of the configuration used to build the default artifact of this project.
     *
     * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
     * Referencing a Library</a>
     */
    public String getDefaultPublishConfig() {
        return defaultPublishConfig
    }

    public void setDefaultPublishConfig(String value) {
        defaultPublishConfig = value
    }

    /**
     * Whether to publish artifacts for all configurations, not just the default one.
     *
     * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
     * Referencing a Library</a>
     */
    public boolean getPublishNonDefault() {
        return publishNonDefault
    }

    void variantFilter(Closure<Void> filter) {
        setVariantFilter(filter)
    }

    void setVariantFilter(Closure<Void> filter) {
        variantFilter = filter
    }

    /**
     * A variant filter to control which variants are excluded.
     * <p>The filter is a closure which is passed a single object of type
     * {@link com.android.build.gradle.internal.api.VariantFilter}. It should set the
     * {@link VariantFilter#setIgnore(boolean)} flag to filter out the given variant.
     */
    public Closure<Void> getVariantFilter() {
        return variantFilter;
    }

    void resourcePrefix(String prefix) {
        resourcePrefix = prefix
    }

    abstract void addVariant(BaseVariant variant)

    public void registerArtifactType(@NonNull String name,
                                     boolean isTest,
                                     int artifactType) {
        extraModelInfo.registerArtifactType(name, isTest, artifactType)
    }

    public void registerBuildTypeSourceProvider(
            @NonNull String name,
            @NonNull BuildType buildType,
            @NonNull SourceProvider sourceProvider) {
        extraModelInfo.registerBuildTypeSourceProvider(name, buildType, sourceProvider)
    }

    public void registerProductFlavorSourceProvider(
            @NonNull String name,
            @NonNull ProductFlavor productFlavor,
            @NonNull SourceProvider sourceProvider) {
        extraModelInfo.registerProductFlavorSourceProvider(name, productFlavor, sourceProvider)
    }

    @CompileStatic
    public void registerJavaArtifact(
            @NonNull String name,
            @NonNull BaseVariant variant,
            @NonNull String assembleTaskName,
            @NonNull String javaCompileTaskName,
            @NonNull Collection<File> generatedSourceFolders,
            @NonNull Iterable<String> ideSetupTaskNames,
            @NonNull Configuration configuration,
            @NonNull File classesFolder,
            @NonNull File javaResourceFolder,
            @Nullable SourceProvider sourceProvider) {
        extraModelInfo.registerJavaArtifact(name, variant, assembleTaskName,
                javaCompileTaskName, generatedSourceFolders, ideSetupTaskNames,
                configuration, classesFolder, javaResourceFolder, sourceProvider)
    }

    public void registerMultiFlavorSourceProvider(
            @NonNull String name,
            @NonNull String flavorName,
            @NonNull SourceProvider sourceProvider) {
        extraModelInfo.registerMultiFlavorSourceProvider(name, flavorName, sourceProvider)
    }

    @NonNull
    public SourceProvider wrapJavaSourceSet(@NonNull SourceSet sourceSet) {
        return new SourceSetSourceProviderWrapper(sourceSet)
    }

    /**
     * <strong>Required.</strong> Compile SDK version.
     *
     * <p>Your code will be compiled against the android.jar from this API level. You should
     * generally use the most up-to-date SDK version here. Use the Lint tool to make sure you don't
     * use APIs not available in earlier platform version without checking.
     *
     * <p>Setter can be called with a string like "android-21" or a number.
     *
     * <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
     * back may give a slightly different string.
     */
    public String getCompileSdkVersion() {
        return target
    }

    public FullRevision getBuildToolsRevision() {
        return buildToolsRevision
    }

    public File getSdkDirectory() {
        return sdkHandler.getSdkFolder()
    }

    public File getNdkDirectory() {
        return sdkHandler.getNdkFolder()
    }

    public List<File> getBootClasspath() {
        ensureTargetSetup()
        return androidBuilder.getBootClasspath()
    }

    public File getAdbExe() {
        return sdkHandler.getSdkInfo().adb
    }

    public File getDefaultProguardFile(String name) {
        File sdkDir = sdkHandler.getAndCheckSdkFolder()
        return new File(sdkDir,
                SdkConstants.FD_TOOLS + File.separatorChar
                        + SdkConstants.FD_PROGUARD + File.separatorChar
                        + name);
    }

    // ---------------
    // TEMP for compatibility
    // STOPSHIP Remove in 1.0

    // by default, we do not generate pure splits
    boolean generatePureSplits = false;

    void generatePureSplits(boolean flag) {
        if (flag) {
            logger.warn("Pure splits are not supported by PlayStore yet.")
        }
        this.generatePureSplits = flag;
    }

    private boolean enforceUniquePackageName = true

    public void enforceUniquePackageName(boolean value) {
        if (!value) {
            LoggingUtil.displayDeprecationWarning(logger, project, "Support for libraries with same package name is deprecated and will be removed in 1.0")
        }
        enforceUniquePackageName = value
    }

    public void setEnforceUniquePackageName(boolean value) {
        enforceUniquePackageName(value)
    }

    public getEnforceUniquePackageName() {
        return enforceUniquePackageName
    }

    private void ensureTargetSetup() {
        // check if the target has been set.
        TargetInfo targetInfo = androidBuilder.getTargetInfo()
        if (targetInfo == null) {
            sdkHandler.initTarget(
                    getCompileSdkVersion(),
                    buildToolsRevision,
                    androidBuilder)
        }
    }
}

在整個android app的編譯流程用的工具,在這個類中基本都可以設置屬性,設置的方式當然是我們一直用的閉包。隨便舉個例子,我們想設置下apk簽名,工具當然是jarSign,搜索下jarSign,沒有,換個sign,就能找到import com.android.build.gradle.internal.dsl.SigningConfig,進入SigningConfig類,所有和簽名有關的代碼一目了然了。用閉包的方法設置就是:

  

android{  
     signingConfigs {
            releaseConfig {
                keyAlias 'stone'
                keyPassword 'mypwd'
                storeFile file('/Users/stone/Documents/project_AS/myapplication/stone.keystore')
                storePassword 'mypwd'
            }
     }
}

  

 

建議把BaseExtension的代碼大致瀏覽下,基本的編譯設置都能找到。

 

我認為學習技術開始無需太過關注細節,主流程通了,實現技術了然於心,下面的事查下幫助文檔就可以了。額,貼個幫助文檔的地址把:

http://avatarqing.github.io/Gradle-Plugin-User-Guide-Chinese-Verision/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM