Android Studio build gradle - android

I want to replace a string token(version) in a html file from assets folder.
My question is how I can accomplish this task with build gradle?
Thank you.

THe first thing you need is to know how to replace your token with the desired value with command line (with sed or anything else you like).
Then you can add task in your gradle build logic :
task replaceToken {
commandline "your command line"
}
Then you add dependance on this task :
tasks.withType(JavaCompile) {
compileTask -> compileTask.depensOn replaceToken
}
In this scenario, after each build your asset file will have the version name in it. So your command line should be able to insert the token value even if there is already a value (you will have to use regexp to replace <balise>whatever</balise> by <balise>token value</balise>.

I put this in build.gradle and works
applicationVariants.all{ variant ->
variant.mergeResources.doFirst{
def buildNumber = env.BUILD_NUMBER
if (null != buildNumber) {
File valuesFile = file("${buildDir}/intermediates/assets/${variant.dirName}/about.html")
String content = valuesFile.getText('UTF-8')
content = content.replaceAll('#VERSION#', buildNumber)
valuesFile.write(content, 'UTF-8')
}
}
}

Related

How can I reduce the size of xml assets when building my apk

In my app I use the assets directory to store some XML files (some are large).
In my design time I want the files to use indentation and also put some comments in it.
This is enlarging my xml files and can add up to a large size.
Is it possible to add a task to the gradle build to remove all indentation and comments for the xml files before packaging it in the apk? If so how?
This will not only shrink my apk, but will also assist at run time with the xml processing.
EDIT
The answer by fhomovc was correct, but was missing some part.
I will mark it as correct but if anyone else will need it, here are the details:
In general I need a task that will run the minify utility and it should look like:
task minifyAssets(type:Exec) {
workingDir dirName // the directory of the merged assets under the build directory
commandLine 'minify', '-r', '-o', '.', '.'
doFirst {
println 'minifyAssets...'
}
}
This task should only be executed after the merged assets task is executed and before the package task is executed.
The main problem is that there should be a dedicated task for each variant, so I needed to do it dynamically:
First create the exec task and make it dependent on the merge task
applicationVariants.all { variant ->
// dynamically add minify task for specific variant
def dirName = new File("app\\build\\intermediates\\merged_assets\\" + variant.name + "\\out\\levels").getAbsolutePath()
def minifyTaskName = "minifyAssets" + variant.name
def mergeAssetsTaskName = "merge" + variant.name + "Assets"
def myTask = tasks.register(minifyTaskName, Exec) {
workingDir dirName
// require that minify utility will be in the path. Download from https://github.com/tdewolff/minify/tree/master/cmd/minify
commandLine 'minify', '-r', '-o', '.', '.'
doFirst {
println 'minifyAssets...' + workingDir
}
}
// set the minify task dependant on the merge assets task
myTask.get().dependsOn mergeAssetsTaskName
}
Now we need to make the specific package task depend on the minify task:
// when the package task is added make it dependant on the minify task
tasks.whenTaskAdded { theTask ->
if (theTask.name.startsWith("package") && (theTask.name.endsWith("Debug") || theTask.name.endsWith("Release"))) {
def minifyTaskName = theTask.name.replace("package", "minifyAssets")
theTask.dependsOn minifyTaskName
}
}
You can run a custom script with an xml minifier.
For the minifier: you can install minify following the installation steps provided.
For the script: you can refer to this answer. Essentially your task would look something like this
task executeScript(type:Exec) {
println 'Minifying xmls...'
//on linux
commandLine 'minify -r -o ./ --match=\.xml ./values' // ./values should be the path to your resources directory
}
Check the documentation to understand better how minify works. I haven't tested this solution myself, so it may need a few adjustments but you get the general idea. If you are using a Windows machine then the Script (commandLine) should be different, if I can find any examples online I'll add them.

Gradle: How to run custom task after an Android Library is built?

I have an Android Library, it's generating a debug.aar and a release.aar, I need to copy the release.aar to another folder as a reference to other part of the project.
What I've done now is in this Android Library build.gradle I defined a task:
task copyAARToCommonLibs(type: Copy) {
from('../build/outputs/aar') {
include '*-release.arr'
}
into '../SomeSampleApps/libs'
}
I'm trying to run this task after the arr is generated, which I assume is assembleRelease stage, so I tried do this in this build.gradle
assembleRelease.doLast{
copyAARToCommonLibs
}
I build the overall project using
gradle build
But this task is running at the very beginning of the whole process.
I also tried this:
applicationVariants.all { variant ->
variant.assemble.doLast {
copyAARToCommonLibs
}
}
inside android{} property(I guess that's what it's called?)
Running gradle build, got this error: Could not find property 'applicationVariants'
I then came across this snippet:
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyAARToCommonLibs }
But it seems this makes the task to run after compiling, I don't know exactly how to modify this to run after assemble.
Could someone please correct me where I did wrong and how can I get this copy task work after the .arr file is generated?
It seems that finalizedBy might be helpful.
assembleRelease.finalizedBy(copyAARToCommonLibs)
Mind the fact that in the following way you won't define a dependency:
assembleRelease.doLast {
copyAARToCommonLibs
}
actually.. it does exactly nothing. You need to execute the task:
assembleRelease.doLast {
copyAARToCommonLibs.execute()
}
but running task in the following way is discouraged and very bad practice.
You can also try:
assembleRelease.doLast {
copy {
from('../build/outputs/aar') {
include '*-release.aar'
}
into '../AscendonSDKSamples/libs'
}
}
I went with finalizedBy() but had to include it within an afterEvaluate...
afterEvaluate {
if (gradle.startParameter.taskNames.contains(":app:assembleFatReleaseInternal")) {
play.enabled = true
play.commit = true
play.track = "internal"
play.releaseStatus = "completed"
play.releaseName = versionName
generateFatReleaseInternalBuildConfig.dependsOn set_build_date
assembleFatReleaseInternal.finalizedBy(uploadCrashlyticsSymbolFileFatReleaseInternal)
uploadCrashlyticsSymbolFileFatReleaseInternal.finalizedBy(publishFatReleaseInternal)
}
}
This worked well for automating the upload of native symbols to Fabric / Crashlytics and other things such as automated play store publishing.
Because android studio add task by dynamic,so assembleRelease will not be recognized.
Just add hook after task added event happens.
tasks.whenTaskAdded {
theTask ->
if (theTask.name.contains('externalNativeBuild')) {
theTask.doLast{
println "[*] begin to copy file."
}
}
// println theTask.name
}

Android/Gradle: Add an asset after the compile phase

I have a task that generates a metadata file based off the compiled classes in an Android Gradle build. I can get it to run by executing it after the compile task:
android.applicationVariants.all { variant ->
def variantName = variant.name.capitalize()
def compileSourcesTaskName = "compile${variantName}Sources"
def compileSourcesTask = project.tasks.findByName(compileSourcesTaskName)
compileSourcesTask.finalizedBy "myTaskThatGeneratesAssets"
}
Unfortunately, Android has already processed the assets at this point. The new file won't get included in the assembled APK.
An answer to a similar question suggests calling aapt add to add the file to the APK before alignment/signing. This seems like it could work, but the post doesn't go into implementation details. The code to call aapt in the Android Gradle plugin looks fairly complicated for a build script, and I'm not sure how to get access to the IAndroidTarget it references.
I'd appreciate suggestions on how to implement this, or any other solutions!
Okay, here's what I ended up with. It makes two assumptions that may break on later versions of the android gradle plugin (I'm using 1.3.0):
The path to the platform tools is ${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.buildToolsVersion}/
The path to the intermediate (resources) APK is ${buildDir}/intermediates/res/resources-${variant.baseName}.ap_
So long as those are true, this should generate a task to add the new asset file using aapt after the resources APK has already been built:
def overlayDir = ... // path to a resources overlay directory that contains "assets/my.json"
def addMyAssetTaskName = "add${variantName}MyAsset"
task "${addMyAssetTaskName}" (type: Exec) {
dependsOn myTaskThatGeneratesAssets
workingDir overlayDir
def aaptCommand = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.buildToolsVersion}/aapt"
def apkPath = "${buildDir}/intermediates/res/resources-${variant.baseName}.ap_"
commandLine aaptCommand, 'add', apkPath, "assets/my.json"
}
Then I use finalizedBy like in the question above to addMyAssetTaskName.

Android Gradle Read App Name from strings.xml

I am trying to rename my APK files for each build variant to include the application name, versionName, versionCode and build number when present. So far I have everything working except the application name.
I want to use the same value that the AndroidManifest.xml file uses for android:label. This comes from a string resource #string/app_name. I have seen the ability to replace the resource values by using:
resValue "string", "app_name", "Some new value"
But I would just like to read this value and use it to name my APK file.
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
renameApk(variant, output)
}
}
def renameApk(variant, output) {
def apkPath = output.outputFile.parent
def baseName = project.archivesBaseName
baseName += "-${variant.buildType.name}"
// add version name and version code
baseName += "-v${variant.mergedFlavor.versionName}-${variant.mergedFlavor.versionCode}"
// if built on jenkins ci, add jenkins build number:
def buildNumber = System.getenv('BUILD_NUMBER')
if (buildNumber && buildNumber.size() > 0) {
baseName += "-b${buildNumber}"
}
// if the variant will not be zipAligned, specify that
if (!output.zipAlign) {
baseName += '-unaligned'
}
// set the output file
output.outputFile = new File(apkPath, "${baseName}.apk");
}
I don't see any method in Android Plugin docs for accessing resources, so here is the code you can use to find your app's name by searching resources:
def getAppName() {
def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
return new XmlParser().parse(stringsFile).string.find { it.#name.equals 'app_name' }.text()
}
BUT I completely agree with #Samuil Yanovski in that it is not worth it - better hardcode a string. I don't think it will slow down building process, but it is just unnecessary.
I don't think this can be done easily. Resource resolution is done on the mobile device to accommodate for things like screen orientation, localization and so on. The Gradle build system has no way of knowing which locale to use for example. If you insist on getting the value from the resources, you can open the specific strings.xml file you'd like to use, parse the XML and get the value yourself. In my opinion this is a huge overkill and would be pretty slow and ugly.
App name is not changed often, so I would be comfortable with having it hardcoded (especially since the apk file name is not visible to the end user, so even if mistakes happen, the impact would be minimal). If you are working on a white label application and have to support dynamic app name, extracting the value to the gradle.properties file (or some other type of configuration file, you are using) should be a better option rather than using the app's resources.
I have create method using #Yaroslav's answer (https://stackoverflow.com/a/37432654/6711554).
def getApplicationName() {
try {
def stringsFile = file("./src/main/res/values/string.xml")
return new XmlParser().parse(stringsFile).string.find { it.#name.equals 'your_app_name' }.text()
}catch(e){
println(e)
return "Default App Name"
}
}
You can read any string in your gradle from your any resource file.

Execute .bat from Android gradle after APK build

I`m moving my project to Gradle build system. After APK build I need to sign it with manufacturer certificate.
How to execute .bat file by Gradle after APK was built?
task runSign(type:Exec) {
println "Sign apk..."
commandLine = ['cmd','/c','sign.bat']
}
I know just how to run .bat before build (but I need after):
preBuild.doLast {
runSign.execute()
}
I've found the solution.
Go to Run -> Edit Configurations...
Choose module where you want to run task after APK build. Add new configuration after "Gradle-aware Make".
Click on icon at picture below to choose module where task is implemented and write name of it.
After this steps your custom Gradle task will be executed after APK build.
I needed to perform something similar, but additionally to that I needed to know with which product favour was built, and with what configuration.
I've ended up adding following line into build.gradle:
android {
applicationVariants.all { variant -> variant.assemble.doLast { signAndInstall.execute() } }
...
And with following helper function:
//
// Returns array for CommandLine, path, variant (arm7), configuration (debug / release)
//
def getCommandLine(path)
{
String taskReqStr = getGradle().getStartParameter().getTaskRequests().toString()
Pattern pattern = Pattern.compile("(assemble|generate)(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(taskReqStr)
if (!matcher.find())
return [ path ]
String flavor = matcher.group(2).toLowerCase() + " " + matcher.group(3).toLowerCase()
return [ path, matcher.group(2).toLowerCase(), matcher.group(3).toLowerCase() ]
}
task signAndInstall(type: Exec) {
def batch = projectDir.toString() + '\\postbuild.bat'
commandLine = getCommandLine(batch)
}
With following postbuild.bat:
#echo off
rem echo %0 %*
if %1. == . exit /b 0
if %2. == . exit /b 0
set InPath=%~dp0build\outputs\apk\%1\%2\app-%1-%2.apk
set OutPath=%~dp0build\outputs\apk\app-%1-%2.apk
copy /y %InPath% %OutPath% 1>NUL
You can of course configure this batch to perform anything what you like, %1 receives your product favour (e.g. arm7, arm8, fat...), and %2 receives 'debug' or 'release' as configuration.

Categories

Resources