Gradle的基本操作:配置同一应用不同的Application
建议先看一下
Gradle的基本操作:AndroidManifest.xml中的meta-data标签、gradle中的manifestPlaceholder
一篇文章理解groovy中闭包closure的使用,不仅仅限于groovy的文章
ApplicationId 与 PackageName
每一个Android应用都一个唯一的ID,这个ID在Android系统中是唯一的,成为应用程序的ID,就像一个人的身份证号一样,作为应用的唯一标识。
应用的ID在build.gradle
脚本中配置,默认配置如下:
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "command.ion.multiapps"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
在默认配置块defaultConfig
中有一个默认的应用程序ID的配置:
applicationId "command.ion.multiapps"
defaultConfig
是android
中的一个默认的配置块,用来定义一些默认配置。它是一个ProductFlavor
,如果一个ProductFlavor
没有为某些属性指定特定的配置的话,就会采用默认配置块中的默认配置。比如包名、版本号、版本名等。
通过查看源码,可以知道我们可以配置那些信息。
defaultConfig {
applicationId "command.ion.multiapps"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
这一部分,其实是一个函数调用.
public void defaultConfig(Action<DefaultConfig> action) {
checkWritability();
action.execute(defaultConfig);
}
可以通过查看DefaultConfig
这个类来得知可以配置那些信息。这个类位于comm.android.build.gradle.internal.dsl
这个包下面。其原码为:
package command.android.build.gradle.internal.dsl;
import command.android.annotations.NonNull;
import command.android.build.gradle.internal.errors.DeprecationReporter;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.model.ObjectFactory;
/** DSL object for the defaultConfig object. */
@SuppressWarnings({"WeakerAccess", "unused"}) // Exposed in the DSL.
public class DefaultConfig extends BaseFlavor {
@Inject
public DefaultConfig(
@NonNull String name,
@NonNull Project project,
@NonNull ObjectFactory objectFactory,
@NonNull DeprecationReporter deprecationReporter,
@NonNull Logger logger) {
super(name, project, objectFactory, deprecationReporter, logger);
}
}
有源码可知,其继承BaseFlavor
,继续追踪源码,会在DefaultProductFlavor
中发现我们需要的配置属性。
从图中可以用来配置不同ProductFlavor
的资源的方法。
/**
* Adds a new generated resource.
*
* <p>This is equivalent to specifying a resource in res/values.
*
* <p>See <a
* href="http://developer.android.com/guide/topics/resources/available-resources.html">Resource
* Types</a>.
*
* @param type the type of the resource
* @param name the name of the resource
* @param value the value of the resource
*/
public void resValue(@NonNull String type, @NonNull String name, @NonNull String value) {
ClassField alreadyPresent = getResValues().get(name);
if (alreadyPresent != null) {
String flavorName = getName();
if (BuilderConstants.MAIN.equals(flavorName)) {
logger.info(
"DefaultConfig: resValue '{}' value is being replaced: {} -> {}",
name,
alreadyPresent.getValue(),
value);
} else {
logger.info(
"ProductFlavor({}): resValue '{}' value is being replaced: {} -> {}",
flavorName,
name,
alreadyPresent.getValue(),
value);
}
}
addResValue(new ClassFieldImpl(type, name, value));
}
查看上述实现接口,可以发现,这样一个接口
/*
* 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 command.android.builder.model;
import command.android.annotations.NonNull;
import command.android.annotations.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.Map;
/**
* Base config object for Build Type and Product flavor.
*/
public interface BaseConfig {
@NonNull
String getName();
/**
* Returns the application id suffix applied to this base config.
* To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
*
* @return the application id
*/
@Nullable
String getApplicationIdSuffix();
/**
* Returns the version name suffix of this flavor or null if none have been set.
* This is only the value set on this product flavor, not necessarily the actual
* version name suffix used.
*
* @return the version name suffix, or {@code null} if not specified
*/
@Nullable
String getVersionNameSuffix();
/**
* Map of Build Config Fields where the key is the field name.
*
* @return a non-null map of class fields (possibly empty).
*/
@NonNull
Map<String, ClassField> getBuildConfigFields();
/**
* Map of generated res values where the key is the res name.
*
* @return a non-null map of class fields (possibly empty).
*/
@NonNull
Map<String, ClassField> getResValues();
/**
* Specifies the ProGuard configuration files that the plugin should use.
*
* <p>There are two ProGuard rules files that ship with the Android plugin and are used by
* default:
*
* <ul>
* <li>proguard-android.txt
* <li>proguard-android-optimize.txt
* </ul>
*
* <p><code>proguard-android-optimize.txt</code> is identical to <code>proguard-android.txt
* </code>, exccept with optimizations enabled. You can use <code>
* getDefaultProguardFile(String filename)</code> to return the full path of the files.
*
* @return a non-null collection of files.
* @see #getTestProguardFiles()
*/
@NonNull
Collection<File> getProguardFiles();
/**
* Returns the collection of proguard rule files for consumers of the library to use.
*
* @return a non-null collection of files.
*/
@NonNull
Collection<File> getConsumerProguardFiles();
/**
* Returns the collection of proguard rule files to use for the test APK.
*
* @return a non-null collection of files.
*/
@NonNull
Collection<File> getTestProguardFiles();
/**
* Returns the map of key value pairs for placeholder substitution in the android manifest file.
*
* This map will be used by the manifest merger.
* @return the map of key value pairs.
*/
@NonNull
Map<String, Object> getManifestPlaceholders();
/**
* Returns whether multi-dex is enabled.
*
* This can be null if the flag is not set, in which case the default value is used.
*/
@Nullable
Boolean getMultiDexEnabled();
@Nullable
File getMultiDexKeepFile();
@Nullable
File getMultiDexKeepProguard();
}
其中就有我们需要的信息。
/**
* Returns the application id suffix applied to this base config.
* To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
*
* @return the application id
*/
@Nullable
String getApplicationIdSuffix();
这样我们可以再不同的ProductFlavor
中配置不同applicationIdSuffix
,从而得到不同的应用ID了。
前面说过android
就是productFlavor
,那么在其闭包中可以调用
/**
* Encapsulates all product flavors configurations for this project.
*
* <p>For more information about the properties you can configure in this block, see {@link
* ProductFlavor}
*/
public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
checkWritability();
action.execute(productFlavors);
}
来实现不同productFlavor
的属性配置
// Specifies the flavor dimensions you want to use. The order in which you
// list each dimension determines its priority, from highest to lowest,
// when Gradle merges variant sources and configurations. You must assign
// each product flavor you configure to one of the flavor dimensions.
flavorDimensions 'shanghai', 'beijing'// 定义 dimensions,这是一列表,顺序表示优先级
productFlavors{
//配不同的 productFlavor
xiaomi{
dimension 'shanghai'//每一个flavor都需要一个 Dimension
}
huawei{
dimension 'shanghai'
}
apple{
dimension 'beijing'
}
}
我这里使用一个dimension
:
flavorDimensions 'shanghai'// 定义 dimensions,这是一列表,顺序表示优先级
productFlavors{
//配不同的 productFlavor
xiaomi{
dimension 'shanghai'//每一个flavor都需要一个 Dimension
}
huawei{
dimension 'shanghai'
}
apple{
dimension 'shanghai'
}
}
然后在为其配置不同的applicationIdSuffix
,就可以在同一Android设备中安装三个相同应用了。
前面分析可知,还可以在这里配置不同资源,那么来改变一下应用的名字吧;有两种方式,一是通过占位符,关于占位符的讲解,二是通过前面说的resValue(@NonNull String type, @NonNull String name, @NonNull String value)
:
resValue "string", "app_name", ""
这种方式配置资源ID,可以再java代码中使用,比如为不同的产品配置不同的接口域名,不如测试环境还是开发环境
productFlavors{
//配不同的 productFlavor
xiaomi{
dimension 'shanghai'//每一个flavor都需要一个 Dimension
applicationIdSuffix 'first'//配置不同的应用id
resValue "string", "app_name", "first"//配置不同的应用的名字
resValue "string", "host", "http://www.baidu.com"//配置不同的服务器接口
}
huawei{
dimension 'shanghai'
applicationIdSuffix 'second'
resValue "string", "app_name", "second"
resValue "string", "host", "http://www.google.com"
}
apple{
dimension 'shanghai'
applicationIdSuffix 'third'
resValue "string", "app_name", "third"
resValue "string", "host", "first"
resValue "string", "host", "http://www.souhu.com"
}
}
名字的使用方式跟strings.xml中的属性的使用方式是一样的。
android:label="@string/app_name"
配置不同名字,如果脚本中的属性名字与xml中的冲突,删除xml中的名字,因为这个时候我们用的是脚本中的名字。
TextView textView = findViewById(R.id.host);
textView.setText(getString(R.string.host));
配置完成后,在不同的构建变体中切换不同变体,就可以将它们同时安装到同一台Android手机上了。
image.png如果AndroidManifest.xml
中有provider
等问题,可以用过占位符来解决。关于占位符的讲解