1
0
forked from firka/firka

android: transform aabs

This commit is contained in:
2025-07-31 18:49:22 +02:00
parent 07ec1a9b16
commit 4f80ea6689
2 changed files with 96 additions and 32 deletions

View File

@@ -4,8 +4,8 @@ import java.security.MessageDigest
import java.util.Properties
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import java.util.zip.ZipOutputStream.STORED
import java.util.zip.ZipOutputStream.DEFLATED
import java.util.zip.ZipOutputStream.STORED
plugins {
id("com.android.application")
@@ -105,7 +105,7 @@ flutter {
tasks.register("transformAndResignDebugApk") {
group = "build"
description = "Transform and resign debug APK with debug key"
description = "Transform and resign APK with debug key"
dependsOn("assembleDebug")
@@ -116,7 +116,7 @@ tasks.register("transformAndResignDebugApk") {
tasks.register("transformAndResignReleaseApk") {
group = "build"
description = "Transform and resign debug APK with debug key"
description = "Transform and resign APK with release key"
dependsOn("assembleRelease")
@@ -125,26 +125,34 @@ tasks.register("transformAndResignReleaseApk") {
}
}
tasks.register("transformAndResignReleaseBundle") {
group = "build"
description = "Transform and resign bundle with release key"
dependsOn("bundleRelease")
doLast {
transformAppBundle()
}
}
afterEvaluate {
tasks.findByName("assembleDebug")?.finalizedBy("transformAndResignDebugApk")
tasks.findByName("assembleRelease")?.finalizedBy("transformAndResignReleaseApk")
tasks.findByName("bundleRelease")?.finalizedBy("transformAndResignReleaseBundle")
}
fun transformApks(debug: Boolean) {
val buildDir = project.buildDir
val apkDir = File(buildDir, "outputs/flutter-apk")
val apks = apkDir.listFiles()!!
val flavor = if (debug) { "debug" } else { "release" }
println("Starting APK transformation process...")
val buildDir = project.buildDir
val apkDir = File(buildDir, "outputs/flutter-apk")
val apks = getApks(debug)
var c = 0;
apks
.filter { apk -> apk.name.startsWith("app-") && apk.name.endsWith("-$flavor.apk") }
.forEach { c++; transformAndSignApk(apkDir, it.nameWithoutExtension, debug) }
println("Transformed: $c apks")
}
fun transformAndSignApk(apkDir: File, name: String, debug: Boolean) {
@@ -201,8 +209,6 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
into(tempDir)
}
val assetsDir = File(tempDir, "assets")
val metaInf = File(tempDir, "META-INF")
val metaInfFiles = metaInf.listFiles();
for (file in metaInfFiles!!) {
@@ -216,16 +222,12 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
val compressedLibs = mutableMapOf<String, String>()
for (arch in arches!!) {
val libFlutter = File(arch, "libflutter.so")
val libApp = File(arch, "libapp.so")
if (!libFlutter.exists()) continue
val compressedDir = File(assetsDir, "flutter-br-${arch.name}")
val compressedFlutter = File(compressedDir, "libflutter.so.br")
val compressedFlutter = File(arch, "libflutter-br.so")
if (!compressedDir.exists()) compressedDir.mkdirs()
compressedLibs["${arch.name}/libflutter.so"] = libFlutter.sha256()
compressedLibs["libflutter.so"] = libFlutter.sha256()
println("Compressing ${arch.name}/libflutter.so with brotli")
exec {
@@ -237,10 +239,10 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
)
}
libFlutter.delete()
}
val json = groovy.json.JsonBuilder(compressedLibs)
File(assetsDir, "flutter-br.json").writeText(json.toString())
val json = groovy.json.JsonBuilder(compressedLibs)
File(arch, "index.so").writeText(json.toString())
}
val topDirL = tempDir.absolutePath.length + 1
val zos = ZipOutputStream(output.outputStream());
@@ -275,12 +277,49 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
println("APK transformed successfully")
}
fun transformAppBundle() {
val buildDir = project.buildDir
val bundle = File(buildDir, "outputs/bundle/release/app-release.aab")
val apks = getApks(false)
val apkCount = apks.count { it.name.startsWith("app-") && it.name.endsWith("-release.apk") }
if (!bundle.exists()) {
throw Exception("Bundle not found at: $bundle")
}
if (apkCount < 3) {
throw Exception("Excepected 3 apks per abi but only found $apkCount")
}
val aabTempDir = File(project.buildDir, "tmp/aab-transform")
aabTempDir.deleteRecursively()
aabTempDir.mkdirs()
copy {
from(zipTree(bundle))
into(aabTempDir)
}
}
fun File.sha256(): String {
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(this.readBytes())
return digest.fold("") { str, it -> str + "%02x".format(it) }
}
fun getApks(debug: Boolean): List<File> {
val buildDir = project.buildDir
val apkDir = File(buildDir, "outputs/flutter-apk")
val apks = apkDir.listFiles()!!
val flavor = if (debug) { "debug" } else { "release" }
return apks
.filter { apk -> apk.name.startsWith("app-") && apk.name.endsWith("-$flavor.apk") }
.toList()
}
fun getDebugKeystorePath(): String {
val userHome = System.getProperty("user.home")
val debugKeystore = File(userHome, ".android/debug.keystore")

View File

@@ -5,11 +5,11 @@ import android.app.Application
import android.os.Build
import android.util.Log
import org.brotli.dec.BrotliInputStream
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.security.MessageDigest
import java.util.zip.ZipFile
class AppMain : Application() {
@@ -25,29 +25,54 @@ class AppMain : Application() {
override fun onCreate() {
super.onCreate()
val am = assets
val abi = Build.SUPPORTED_ABIS[0]
val assets = am.list("")
if (!assets?.contains("flutter-br-$abi")!!) {
throw Exception("Unsupported abi: $abi, try downloading an apk with a different abi")
val apks = File(applicationInfo.nativeLibraryDir, "../..").absoluteFile
.listFiles()!!
.filter { file -> file.name.endsWith(".apk") }
.toList()
var nativesApkN: ZipFile? = null
for (apk in apks) {
if (nativesApkN != null) break
val zip = ZipFile(apk)
val entries = zip.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
entry.name.endsWith("$abi/index.so")
zip.close()
nativesApkN = ZipFile(apk)
break
}
zip.close()
}
val compressedLibsIndex = am.open("flutter-br.json")
if (nativesApkN == null) {
throw Exception("Can't find native libraries")
}
val nativesApk: ZipFile = nativesApkN
val compressedLibsIndex = nativesApk.getInputStream(
nativesApk.getEntry("lib/$abi/index.so")
)
val compressedLibs = JSONObject(compressedLibsIndex.readBytes().toString(Charsets.UTF_8))
val natives = am.list("flutter-br-$abi")!!
for (lib in natives) {
val so = lib.substring(0, lib.length-".br".length)
for (so in compressedLibs.keys()) {
val soFile = File(cacheDir, so)
if (soFile.sha256() == compressedLibs.getString("${abi}/$so")) {
if (soFile.sha256() == compressedLibs.getString(so)) {
System.load(soFile.absolutePath)
return
}
Log.d("AppMain", "Decompressing: $so")
val brInput = am.open("flutter-br-$abi/$lib")
val brInput = nativesApk.getInputStream(
nativesApk.getEntry("lib/$abi/${so.replace(".so", "-br.so")}")
)
val soOutput = FileOutputStream(soFile)
val brIn = BrotliInputStream(brInput)