一、高级自定义
1.1、使用共享库
Android的包(比如android.app、android.content、android.view、android.widget等)默认都是包含在Android SDK里面的,所有的应用都可以直接使用它们,系统会帮我们自动链接它们,不会出现找不到相关类的情况。还有一些库,比如com.google.android.maps、android.test.runner等,这些库是独立的,并不会被系统自动链接,所以我们使用它们的话,需要单独的进行生成使用,这些类库我们称为共享库。
在AndroidManifest.xml文件中,指定要使用的库:
<uses-library
android:name="com.google.android.maps"
android:required="true"/>
这样就声明了需要使用maps这个共享库。声明之后,在安装生成的apk的时候,系统会根据我们的定义,帮助我们检测手机系统是否有我们需要的共享库。因为我们设置了android:required="true",如果手机系统不满足,将不能安装该应用。
在Android中,除了标准的SDK,还存在两种库:
- add-ons附件库,位于add-ons目录下,这些库大部分是第三方厂商或者公司开发的,一般是为了让开发者使用,但是又不愿意暴露具体标准实现的;对于该类库,Android Gradle会自动解析,帮我们添加到classpath里。
- 另一类是optional可选库,位于platforms/android-xx/optional目录下,一般是为了兼容旧版本的api,比如org.apache.http.legacy,这是一个HttpClient库。对于该类库,则不会帮我们添加到classpath里。
如何自己把这个可选库添加到classpath中?Android Gradle提供了useLibrary方法,让我们可以把一个库添加到classpath中。
android{
useLibrary 'org.apache.http.legacy'
}
除了上述配置,最好在AndroidManifest文件中配置一下use-library标签,以防出现问题。通过PackageManager.getSystemSharedLibraryNames()获取共享库的所有名字。
1.2、批量生成Apk的文件名
Android相对于Java工程要复杂的多,Java工程通过project.tasks.xx就可以访问xx任务,但是对Android来说,因为它有很多相同的任务,这些任务的名字都是通过buildTypes和Product Flavors生成的,是动态创建和生成的,而且生成的时间是比较靠后的,如果你还是像原来一样在某个闭包里通过project.tasks获取一个任务,会提示找不到任务,因为还没有生成。
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.test.gradletest"
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
zipAlignEnabled true
}
debug {
}
}
productFlavors {
google {
applicationIdSuffix '.google'
flavorDimensions "default"
}
baidu {
applicationIdSuffix '.baidu'
flavorDimensions "default"//新增
}
}
//如下方法在gradle升级到3.x之后发生了变化,如下所示
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
if (output.outputFile != null
&& output.outputFile.name.endsWith('.apk')
&& 'debug' == variant.buildType.name) {
def flavorName = variant.flavorName.startsWith("_") ? variant.flavorName.substring(1) : variant.flavorName
outputFileName = "Example_${flavorName}_v${variant.versionName}_${buildTime()}.apk"
}
}
}
}
def buildTime() {
def date = new Date()
def formattedDate = date.format('yyyymmdd')
return formattedDate
}
--------------------------------------------------------------------------
buildVariants = buildTyps x productFlavors
打包命令:
只打Release包 -
./gradlew assembleRelease
只打Debug包 -
./gradlew assembleDebug
只打baidu的渠道包 -
./gradlew assemblebaidu
只打baidu的release渠道包
./gradlew assemblebaiduRelease
----------------------------------------------------------------------------
variant与variant输出产物列表:
baiduDebug --- variant.name
app-baidu-debug.apk --- output.outputFile.name
baiduRelease --- variant.name
app-baidu-release-unsigned.apk --- output.outputFile.name
googleDebug --- variant.name
app-google-debug.apk --- output.outputFile.name
googleRelease --- variant.name
app-google-release-unsigned.apk --- output.outputFile.name
如上,即可完成批量修改生成的apk文件名的任务。
Android对象为我们提供了3个属性:applicationVariants、libraryVariants、testVariants。对应了Android三种不同类型的项目。通俗的讲,它们都是Android构建的产物。它们基于BuildTypes和ProductFlavors生成的产物。
applicationVariants是一个DomainObjectCollection集合,通过其all方法可以遍历所有的variant。比如googleDebug、googleRelease、baiduDebug、baiduRelease。applicationVariants中的每一个都是ApplicationVariant,它有一个outputs作为它的输出。每一个ApplicationVariant至少有一个输出,也可以有多个,所以这里的outputs属性是一个List集合。
版本名:major.minjor.patch,即主版本号.副版本号.补丁号
1.3、动态配置AndroidManifest文件
通过ProductFlavor的ManifestPlaceHolder属性(是一个Map类型)就可以配置多个占位符。
productFlavors {
google {
applicationIdSuffix '.google'
flavorDimensions "default"
manifestPlaceholders.put("UMENG_CHANNEL", "google")
}
baidu {
applicationIdSuffix '.baidu'
flavorDimensions "default"
manifestPlaceholders.put("UMENG_CHANNEL", "baidu")
}
}
--------------------------------------------
如果有几十个渠道要发布,每个渠道都这么写太多太累,可以通过如下的方式,几行代码解决:
productFlavors.all { flavor ->
manifestPlaceholders.put("UMENG_CHANNEL", name)
}
--------------------------------------------
使用:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data android:value="${UMENG_CHANNEL}" android:name="UMENG_CHANNEL"/>
......
</application>
在构建的时候,会把AndroidManifest文件中的占位符变量替换为对应的value的值。
1.4、自定义BuildConfig
BuildConfig这个类是由Android Gradle构建脚本在编译后生成的,自动生成的,不能修改。默认情况下一般是这样的:
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.example.test.gradletest";
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";
}
你会发现这些差不多就是我们当前构建渠道的基本信息了。它们都是常量,相比我们获取这些信息的其他方式,无疑是最方便的。例如获取当前的包名,一般我们都会使用context.getPackageName()函数,这个函数又有很多的实现,很麻烦,很复杂,性能也不高。如果直接引用BuildConfig.APPLICATION_ID就方便多了,性能也非常快。
还有,BuildConfig.DEBUG,在debug模式下它的值是true,在release模式下是false,不用我们每次都去改变这个值,Android Gradle会帮我们自动生成修改,非常方便。通过BuildConfig.DEBUG,很容易区分当前的开发模式,以便控制日志的打印。
如何自定义BuildConfig,新增一些常量呢?
BaseFlavor - 函数原型
public void buildConfigField(
@NonNull String type, @NonNull String name, @NonNull String value)
使用方式如下:
productFlavors {
google {
applicationIdSuffix '.google'
flavorDimensions "default"
manifestPlaceholders.put("UMENG_CHANNEL", "google")
buildConfigField 'String', 'WEB_URL', '"https://www.google.com"'//注意:别丢了双引号
}
baidu {
applicationIdSuffix '.baidu'
flavorDimensions "default"
manifestPlaceholders.put("UMENG_CHANNEL", "baidu")
buildConfigField 'String', 'WEB_URL', '"https://www.baidu.com"'
}
}
这样当我们需要使用的时候,直接使用这个BuildConfig.WEB_URL即可。这种用法不仅可以在productFlavor中,buildType中同样适用。
1.5、动态添加自定义的资源
这里讲的自定义资源是专门针对的res/values类型资源的,它们不光可以在res/values文件夹里使用xml的方式定义,还可以在Android Gradle中定义,这大大增加了构建的灵活性。
BaseFlavor - 函数原型
public void resValue(@NonNull String type, @NonNull String name, @NonNull String value)
实现这一功能的正式resValue方法,它在BuildType和ProductFlavor这两个对象中都存在,也就是说可以针对不同的渠道或者构建类型来自定义特有的资源。和BuildConfig的用法非常类似。
productFlavors {
google {
applicationIdSuffix '.google'
flavorDimensions "default"
manifestPlaceholders.put("UMENG_CHANNEL", "google")
buildConfigField 'String', 'WEB_URL', '"https://www.google.com"'
resValue 'String', 'channel_tips', '"这是google渠道"'
}
baidu {
applicationIdSuffix '.baidu'
flavorDimensions "default"
manifestPlaceholders.put("UMENG_CHANNEL", "baidu")
buildConfigField 'String', 'WEB_URL', '"https://www.baidu.com"'
resValue 'String', 'channel_tips', '"这是baidu渠道"'
}
}
当我们使用resValue方法时,Android Gradle帮我们生成的资源在哪里呢?其实都在我们的工程中。
app\build\generated\res\resValues\baidu\debug\values\generated.xml
generated.xml具体内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Automatically generated file. DO NOT MODIFY -->
<!-- Values from product flavor: baidu -->
<item name="channel_tips" type="String">"这是baidu渠道"</item>
</resources>
1.6、Java编译选项
Android对象提供了一个compileOptions方法,接受一个CompileOptions类型的闭包作为参数,来对Java编译选项进行配置。
------BaseExtension----
public void compileOptions(Action<CompileOptions> action)
------CompileOptions---
public class CompileOptions {
private static final String VERSION_PREFIX = "VERSION_";
@Nullable
private JavaVersion sourceCompatibility;//Java 源代码的编译级别
@Nullable
private JavaVersion targetCompatibility;//配置生成的Java字节码版本
@NonNull
private String encoding = Charsets.UTF_8.name(); //编码
......
}
具体用法:
android {
.......
compileOptions {
encoding 'utf-8'
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
}
}
1.7、adb操作选项配置
在Android Gradle中,预留了对adb的一些选项的控制配置,它就是adbOptions{}闭包。和compileOptions用法类似,查看源码就可以知道哪些可以配置的选项了。
adbOptions的原理是什么呢?
我们知道adb这个命令可以帮助我们连接android手机,对于Android Gradle这个插件,也不例外,比如运行调试的时候,Android Gradle插件的底层还是调用adb命令,Android Gradle只不过在其上做了一层包装。在Android Gradle的脚本中,可以通过adbOptions闭包对adb的选项进行配置,然后实例化手机到Android对象中一个叫AdbOptions类型的变量adbOptions中。最后Android Gradle调用adb命令的时候,把这些配置作为adb命令的参数传递给adb即可。
1.8、其他可配置Option
dexOptions具体用法类似adbOptions,具体可配置项可查看源码。
1.9、65535方法限制 - 为啥是65535?
Java源文件都被打包成了一个DEX文件,这个文件就是优化过的、Dalvik虚拟机可执行的文件,Dalvik虚拟机在执行DEX文件的时候,使用了short这个类型来索引DEX文件中的方法,这意味着单个DEX文件可以被定义的方法最多是65535个,当我们定义的方法数超过这个数量时,就会出现错误的提示信息。
MultiDex:对于Android 5.0之后的版本,使用了ART的运行时方式,可以天然支持App有多个DEX文件。ART在安装App的时候执行预编译,把多个DEX文件合并成一个oat文件执行。5.0之前的版本,则需要使用MultiDex库。
二、多项目构建
Android App项目不光可以引用Android Lib项目,还可以引用Java Lib项目。Android Lib是打包成aar包,Java Lib是打包成jar包。引用Android库项目是引用的一个库项目发布出来的aar包,默认情况下,Android库项目发布出来的包都是Release版本。可以在android{}闭包中通过defaultPublishConfig进行配置。
库项目也可以单独发布到自己的Maven私服,推荐Nexus Repository Manager。然后就可以在依赖配置里引用刚发布的aar包了。