我已经习惯了Android Studio并开发了Android项目。
在Android Studio中,我把它放在build.gradle文件中:
defaultConfig {
applicationId "com.domain.myapp"
minSdkVersion 19
targetSdkVersion 19
versionCode 1
versionName "1.0"
setProperty("archivesBaseName", "myapp.$versionName.$versionCode")
signingConfig signingConfigs.config
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationVariants.all { variant ->
variant.outputs.each { output ->
def newName = output.outputFile.name
newName = newName.replace("-release", "")
output.outputFile = new File(output.outputFile.parent, newName)
}
}
signingConfig signingConfigs.config
}
debug {
signingConfig signingConfigs.config
}
}
当我构建时,我得到myapp.1.0.1.apk,效果非常好。
现在我正在使用IntelliJ开发一个java项目.jar,而不是Android Studio。
我怎样才能完成同样的事情?我找到了稀疏的信息......
答案 0 :(得分:0)
Android通过添加构建R.java
文件的任务来实现此目的。就像Android一样简单使它看起来复制行为需要花费一点力气。您可以创建自己的gradle任务来完成相同的任务。您将需要一个创建和扩展以及任务的gradle插件。该扩展程序将用于跟踪build.gradle
插件应该创建扩展和任务
// create DSL model
target.extensions.create('buildConfig', BuildConfigModel)
// create task to generate file
def buildConfigTask = target.tasks.create('generateBuildConfig', BuildConfigTask)
target.tasks.findByName('clean').configure {
delete new File("$project.projectDir/src/main", "generated-sources")
}
// this task must always run... it's never `upToDate`
buildConfigTask.outputs.upToDateWhen { false }
// java compiler depends on this task... allows us to reference generated code from
// human written code
target.tasks.getByName('compileJava').dependsOn buildConfigTask
以下是如何使用您的任务添加生成的源文件
project.configure(project, { Project configuredProject ->
// compilerJava task {@link JavaCompile}
def compileJava = configuredProject.compileJava
// add the created java file to source path so it gets compiled
compileJava.source(project.buildConfig.createBuildConfig(project, compileJava))
})
然后我们的扩展看起来像这样
package com.jbirdvegas.q41680813;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.compile.JavaCompile;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Handles creating the BuildConfig.java from a module project
* <p>
* Warning... Keep this in java, gradle doesn't have the streams api we are using
*/
public class BuildConfigModel {
/**
* Final java file output path pattern for {@link String#format(String, Object...) String#format} formatting of
* the file's path. Directory structure will be created if needed
*/
private static final String OUTPUT_PATH_FORMAT = "%s/src/main/generated-sources/%s/%s/BuildConfig.java";
/**
* List of DSL supplied {@link BuildValue buildConfig#add}
*/
private List<BuildValue> mBuildValues = new ArrayList<>();
/**
* Required... do not remove
*/
public BuildConfigModel() {
}
/**
* Create a new field to the project's generated `BuildConfig.java`'s inner class for each type
*
* @param clazz Type of value to be written (will be grouped by clazz)
* @param name field name to be created
* @param value value to be assigned to field's name
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public void add(Class clazz, String name, Object value) {
mBuildValues.add(new BuildValue(clazz, name, value));
}
/**
* Create `BuildConfig.java` and add it to the {@link JavaCompile#source(Object...)} compileJava#source(Object...)}
*
* @param project module to generate BuildConfig for
* @param javaCompile project's `compileJava` task
* @return generated `BuildConfig.java` file
*/
public File createBuildConfig(Project project, JavaCompile javaCompile) {
File buildConfigFile = getBuildConfigFile(project);
createJavaClass(project, buildConfigFile);
javaCompile.source(buildConfigFile);
return buildConfigFile;
}
/**
* Destination file for given project's `BuildConfig.java`
*
* @param project module to generate BuildConfig for
* @return File representing the destination of the created `BuildConfig.java`
*/
@SuppressWarnings("WeakerAccess")
public File getBuildConfigFile(Project project) {
return project.file(String.format(OUTPUT_PATH_FORMAT,
project.getProjectDir().getAbsolutePath(),
project.getGroup().toString().replaceAll("\\.", "/"),
project.getName()));
}
/**
* Create `BuildConfig.java` with a few default values and any values supplied
* to the `buildConfig`'s {@link #add(Class, String, Object) add} method.
* <p>
* Default BuildConfig fields will be generated by {@link #getDefaultFields}
* <p>
* Fields added via {@link #add(Class, String, Object) add} method will be grouped into inner classes
* named <pre>{@code Class#getSimpleName().toLowerCase() + "s"}</pre>
*
* @param project module to generate BuildConfig for
* @param buildConfigFile File representing the destination of the BuildConfig.java output
*/
@SuppressWarnings("WeakerAccess")
public void createJavaClass(Project project, File buildConfigFile) {
//noinspection unchecked
Collections.sort(mBuildValues);
// group our configs by their types into a map of groups
Map<Class, List<BuildValue>> groupedConfigs = mBuildValues.stream()
// put the values in groups based on the simple name of the class in lowercase
.collect(Collectors.groupingBy(BuildValue::getValueType));
// create the fully qualified class that will contain our build settings
TypeSpec.Builder buildConfigJavaBuilder = TypeSpec.classBuilder("BuildConfig")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
// note for javadoc
.addJavadoc("$S\n", "DO NOT MODIFY; this class is written automatically by the compiler")
// replace public constructor with private
.addMethod(createPrivateConstructor());
// add any fields that will be in all BuildConfig classes
buildConfigJavaBuilder.addFields(getDefaultFields(project));
groupedConfigs.forEach((aClass, buildValues) -> {
// create the inner class
String safeInnerClassName = aClass.getSimpleName().toLowerCase() + 's';
TypeSpec.Builder innerClass = TypeSpec.classBuilder(safeInnerClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
// make a constructor that's private since all the members of this class are public static final
.addMethod(createPrivateConstructor());
// for each inner class type create a field
// each object type gets it's own inner class
//noinspection SimplifyStreamApiCallChains
buildValues.stream().forEachOrdered(buildValue -> {
// create the requested field in the class
FieldSpec fieldSpec = FieldSpec.builder(buildValue.clazz, buildValue.name)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(getStringFormatter(buildValue.clazz), buildValue.value))
.build();
// add the field to the inner class
innerClass.addField(fieldSpec);
});
// add the inner class to the fully qualified class
buildConfigJavaBuilder.addType(innerClass.build());
});
// determine the package name from project.group + '.' + project.name
String packageName = project.getGroup() + "." + project.getName();
// create a java file writer
JavaFile.Builder builder = JavaFile.builder(packageName, buildConfigJavaBuilder.build());
// don't import java.lang.* it's redundant
builder.skipJavaLangImports(true);
// use four spaces for indent instead of default two spaces
builder.indent(" ");
// create the file in memory
JavaFile javaFile = builder.build();
// ensure file structure
if (!buildConfigFile.getParentFile().exists() && !buildConfigFile.getParentFile().mkdirs()) {
throw new GradleException("Failed to create directory structure for " + buildConfigFile.getAbsolutePath());
}
// write BuildConfig.java to location
try (FileWriter writer = new FileWriter(buildConfigFile)) {
javaFile.writeTo(writer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Strings require being treated specially in order to be encapsulated in quotes correctly
* All other classes are treated as literals... We may want to handle more {@link java.lang.reflect.Type Type}
*
* @param clazz Class formatter is needed for
* @return "$S" if the class is a {@link String} else a literal formatter is returned "$L"
*/
private String getStringFormatter(Class clazz) {
switch (clazz.getSimpleName().toLowerCase()) {
case "string":
// causes the formatter used to wrap the value in quotes correctly
case "date":
// date objects are serialized to a string
return "$S";
case "long":
return "$LL";
case "double":
return "$LD";
case "float":
return "$LF";
default:
// for the reset use literal
return "$L";
}
}
/**
* get project added build values
*
* @return List of build values added by project's `buildConfig` closure
*/
@SuppressWarnings("unused")
public List<BuildValue> collectBuildValues() {
return mBuildValues;
}
/**
* Make a private constructor for the class. Default is public but our classes only contain
* <pre>{@code public static final {@link Object}}</pre> so public constructors are redundant
*
* @return private constructor method
*/
private MethodSpec createPrivateConstructor() {
return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
}
/**
* Create default field references
*
* @param project module to generate BuildConfig for
* @return List of fields to write to generated BuildConfig
*/
private List<FieldSpec> getDefaultFields(Project project) {
List<FieldSpec> fields = new ArrayList<>();
// set current version
fields.add(FieldSpec.builder(String.class, "version")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(getStringFormatter(String.class), project.getVersion()))
.build());
return fields;
}
class BuildValue implements Comparable {
/**
* Type of field's value
*/
/* package */ Class clazz;
/**
* Field name
*/
/* package */ String name;
/**
* Field's value Value must be able to be serialized as a string
*/
/* package */ Object value;
/* package */ BuildValue(Class clazz, String name, Object value) {
this.clazz = clazz;
this.name = name;
this.value = value;
}
/* package */ Class getValueType() {
return clazz;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BuildValue)) return false;
BuildValue that = (BuildValue) o;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return value != null ? value.equals(that.value) : that.value == null;
}
@Override
public int hashCode() {
int result = clazz != null ? clazz.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
@Override
public int compareTo(Object o) {
return (name != null && o != null) ? name.compareTo(o.toString()) : -1;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("BuildValue{");
sb.append("class=").append(clazz.getCanonicalName());
sb.append(", name='").append(name).append('\'');
sb.append(", value=").append(value);
sb.append('}');
return sb.toString();
}
}
}
默认情况下,插件会创建BuildConfig.java
,其默认字段为version
,但您也可以添加自己的值
buildConfig {
add Boolean, 'myKey', false
}
然后在运行时,您可以使用BuildConfig.value
获取引用的值,对于上面的示例,您还可以将字段BuildConfig.myKey
作为Boolean
类型提供。
编辑:我的示例使用groovy作为插件类和任务类,但扩展名BuildConfigModel
是用java编写的。我的所有来源都位于src/main/groovy