Search code examples
androidnativescriptglade

ABI Split failed in NativeScript 2.3.0


I’ve asked for help in the general group but most people didnt get it work as well. Im having a huge problem ridiculous apk size of my simple app. I used @markosko nativescript filter to reduce the app to 14MB from 17.2MB, even nativescript-snapshot couldn't help still 17MB for release version.

I tried using the ABI split sample in the nativescipt documentation but what I noticed is that it’s trying to split but the glade is using same name for all the apks so I came up with this in my app.glade

def tnsBuildMultipleApks=true;

android {  
    defaultConfig {  
        generatedDensities = []
        applicationId = "com.maliyo.oneclick"  
        versionCode Integer.parseInt("" + "1" + "0")
    }  
    aaptOptions {  
        additionalParameters "--no-version-vectors"  
    }
    if (Boolean.valueOf(tnsBuildMultipleApks)) {
        splits {
            abi {
                enable true
                reset()
                include  'armeabi', 'armeabi-v7a', 'x86', 'mips'
                universalApk true
            }
        }
    }
} 

def getDate() {
  def date = new Date()
  def formattedDate = date.format('yyyyMMdd')
  return formattedDate
}

// map for the version code that gives each ABI a value
ext.versionCodes = [
    'F0F1F2X86Debug':1, 'F0F1F2ArmeabiDebug':2, 'F0F1F2Armeabi-v7aDebug':3, 'F0F1F2MipsDebug':4,
    'F0F1F2X86Release':1, 'F0F1F2ArmeabiRelease':2, 'F0F1F2Armeabi-v7aRelease':3, 'F0F1F2MipsRelease':4
    ]

// For each APK output variant, override versionCode with a combination of
// ABI APK value * 100 + defaultConfig.versionCode
android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
            println("******************************************************")
            println(output);
            println(output.getVariantOutputData().getFullName())

            if (Boolean.valueOf(tnsBuildMultipleApks)) {
                def file = output.outputFile
                // version at the end of each built apk
                //output.outputFile = new File(file.parent, file.name.replace(".apk", "-" + android.defaultConfig.versionName + "-" + getDate() + ".apk"))
                output.outputFile = new File(file.parent, file.name.replace(".apk", "-" + output.getVariantOutputData().getFullName() + "-" + getDate() + ".apk"))
                output.versionCodeOverride =
                    //(assd++) * 100
                    project.ext.versionCodes.get(output.getVariantOutputData().getFullName(), 0) * 100
                    + android.defaultConfig.versionCode
            }
        }
    }
}
/**/

Fine, it splits but I think because I hacked the filename at output the adb couldn't find one to push the apk to the device or emulator due to the naming pattern, maybe, just saying apk not found.

I tried to manually send the appropriate apk to the device via USB, it app installed successfully but it crashes after splashscreen saying metadata/treeNodeStream.dat could not be loaded

UPDATE

@plamen-petkov thanks so much for your contribution, I agree with you that it work fine, when you build one after another changing the abi filter. But with this in my app.gradle, I managed to build multiple apks successfully and tested and OK.

but is like the the tns is only pushing appname-debug.apk or appname-release.apk to the adb. I can toggle this splitting off with tnsBuildMultipleApks and maybe when Im still testing I can turn it off and use tns run android and when I want to make final build and it turn it one again as it works fine with tns build android --release ....

// Add your native dependencies here:

// Uncomment to add recyclerview-v7 dependency
//dependencies {
//  compile 'com.android.support:recyclerview-v7:+'
//}
import groovy.json.JsonSlurper //used to parse package.json

def tnsBuildMultipleApks=true;
String content = new File("$projectDir/../../app/package.json").getText("UTF-8")
def jsonSlurper = new JsonSlurper()
def appPackageJson = jsonSlurper.parseText(content)

android {  
    defaultConfig {  
        generatedDensities = []
        applicationId = appPackageJson.nativescript.id  
        versionCode = appPackageJson.version_code ?: 1
    }

    aaptOptions {  
        additionalParameters "--no-version-vectors"  
    }
    if (Boolean.valueOf(tnsBuildMultipleApks)) {
        splits {
            abi {
                enable true
                reset()
                include  'x86', 'armeabi-v7a', 'arm64-v8a'
                universalApk true
            }
        }
    }
}

// map for the version code that gives each ABI a value
ext.versionCodes = [
    'x86':1, 'armeabi-v7a':2, 'arm64-v8a':3
]

// For each APK output variant, override versionCode with a combination of
// ABI APK value * 100 + android.defaultConfig.versionCode
// getAbiFilter() not working for me so I extracted it from getFullname()
if (Boolean.valueOf(tnsBuildMultipleApks)) {
    android.applicationVariants.all { variant ->
        println(appPackageJson)
        println(android.defaultConfig.versionCode)
        println(android.defaultConfig.applicationId)

        def name
        def flavorNamesConcat = ""

        variant.productFlavors.each() { flavor ->
            flavorNamesConcat += flavor.name
        }
        flavorNamesConcat = flavorNamesConcat.toLowerCase()
        println(flavorNamesConcat)

        variant.outputs.each { output ->
            if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
                //You may look for this path in your console to see what the values are
                println("******************************************************")
                println(output); println(output.getVariantOutputData().getFullName())

                def abiName = output.getVariantOutputData().getFullName().toLowerCase().replace(flavorNamesConcat, "").replace(project.ext.selectedBuildType, "")
                println(abiName)


                def file = output.outputFile
                output.versionCodeOverride =
                    project.ext.versionCodes.get(abiName, 0) * 100
                    + android.defaultConfig.versionCode

                def apkDirectory = output.packageApplication.outputFile.parentFile
                def apkNamePrefix = output.outputFile.name.replace(".apk", "-" + abiName)

                if (output.zipAlign) {
                    name = apkNamePrefix + ".apk"
                    output.outputFile = new File(apkDirectory, name);
                }

                name = apkNamePrefix + "-unaligned.apk"
                output.packageApplication.outputFile = new File(apkDirectory, name);
            }
        }
    }
}

Solution

  • This work so well for me now, both generating apks and tns run android runs fine now, thanks.

        // Add your native dependencies here:
    
    // Uncomment to add recyclerview-v7 dependency
    //dependencies {
    //  compile 'com.android.support:recyclerview-v7:+'
    //}
    import groovy.json.JsonSlurper //used to parse package.json
    
    def tnsBuildMultipleApks=true;
    String content = new File("$projectDir/../../app/package.json").getText("UTF-8")
    def jsonSlurper = new JsonSlurper()
    def appPackageJson = jsonSlurper.parseText(content)
    
    android {  
        defaultConfig {  
            generatedDensities = []
            applicationId = appPackageJson.nativescript.id  
            versionCode = appPackageJson.version_code ?: 1
        }
    
        aaptOptions {  
            additionalParameters "--no-version-vectors"  
        }
        if (Boolean.valueOf(tnsBuildMultipleApks)) {
            splits {
                abi {
                    enable true
                    reset()
                    include  'x86', 'armeabi-v7a', 'arm64-v8a'
                    universalApk true
                }
            }
        }
    }
    
    // map for the version code that gives each ABI a value
    ext.versionCodes = [
        'x86':1, 'armeabi-v7a':2, 'arm64-v8a':3
    ]
    
    // For each APK output variant, override versionCode with a combination of
    // ABI APK value * 100 + android.defaultConfig.versionCode
    // getAbiFilter() not working for me so I extracted it from getFullname()
    if (Boolean.valueOf(tnsBuildMultipleApks)) {
        android.applicationVariants.all { variant ->
            println(appPackageJson)
            println(android.defaultConfig.versionCode)
            println(android.defaultConfig.applicationId)
    
            def name
            def flavorNamesConcat = ""
    
            variant.productFlavors.each() { flavor ->
                flavorNamesConcat += flavor.name
            }
            flavorNamesConcat = flavorNamesConcat.toLowerCase()
            println(flavorNamesConcat)
    
            variant.outputs.each { output ->
                if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
                    //You may look for this path in your console to see what the values are
                    println("******************************************************")
                    println(output); println(output.getVariantOutputData().getFullName())
    
                    def abiName = output.getVariantOutputData().getFullName().toLowerCase().replace(flavorNamesConcat, "").replace(project.ext.selectedBuildType, "")
                    println(abiName)
    
    
                    def file = output.outputFile
                    def versionCode = project.ext.versionCodes.get(abiName, 0);
                    output.versionCodeOverride =
                        project.ext.versionCodes.get(abiName, 0) * 100
                        + android.defaultConfig.versionCode
    
                    def apkDirectory = output.packageApplication.outputFile.parentFile
                    println(output.outputFile.name)
                    def apkNamePrefix = ""
                    if(versionCode){
                        apkNamePrefix = output.outputFile.name.replace(".apk", "-" + abiName)
                    }
                    else {
                        apkNamePrefix = output.outputFile.name.replace(".apk", "")
                    }
    
                    if (output.zipAlign) {
                        name = apkNamePrefix + ".apk"
                        output.outputFile = new File(apkDirectory, name);
                    }
    
                    name = apkNamePrefix + "-unaligned.apk"
                    output.packageApplication.outputFile = new File(apkDirectory, name);
                }
            }
        }
    }