I'm working on a game being exported from Unity as an Android Studio project. When I assemble and run as a debug build, it works as expected. Since we are using multiple 3rd party libraries, there are more than 65K methods, and it generates quite a few DEX files (11 files). Looking in the contents of these DEX files, they are not all full. In fact, most of them contain only a single BuildConfig class, or a bunch of related R classes. In fact, only 2 of the DEX files have anything appreciable in them, classes7.dex and classes11.dex. I don't know how the app even runs; I thought the main activity needed to be in classes.dex for it to work. But in any case everything actually works fine.
However, in release builds, the situation is much, much worse. I'm talking about 109 (one hundred and nine!) DEX files. This appears to simply be much more granular separation of the classes that were originally in the 11 DEX files before, for some reason. And here, things start to break down. On launch, ClassNotFoundExceptions start appearing on some devices, but on others it works fine. The common factor I have seen indicating whether it will work is OS version. All the devices are running Android OS 5.0+, so multidexing is supported natively, but the stable devices are mostly running 6.0+.
The main activity is in classes54.dex, which extends from a class in classes30.dex, which extends from a class in classes106.dex, which extends from Activity. Those classes it can find just fine though. The first class it complains it can't find is over in classes91.dex, for example.
I assume the problem is within the gradle process, since the issue occurs when exporting directly to an APK from Unity or when building within Android Studio. So my question is how do I either:
convince Unity/Android Studio/Gradle to output a sensible number of DEX files, or
Get all devices to look at all the dex files, even when there are 100+, when looking for classes?
Current build.gradle created when exporting from Unity:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.google.gms:google-services:3.0.0'
}
}
allprojects {
repositories {
flatDir {
dirs 'libs'
}
google()
}
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(name: 'GoogleAIDL', ext:'aar')
implementation(name: 'GooglePlay', ext:'aar')
implementation(name: 'android.arch.lifecycle.runtime-1.0.0', ext:'aar')
//...
//Also included: Google Play, Facebook, Crashlytics, AdMob, Firebase, and more, redacted for convenience
//...
implementation project(':Firebase')
implementation project(':GoogleMobileAdsIronSourceMediation')
implementation project(':GoogleMobileAdsMediationTestSuite')
implementation project(':GoogleMobileAdsPlugin')
implementation project(':GoogleMobileAdsTapjoyMediation')
implementation project(':GooglePlayGamesManifest.plugin')
implementation project(':unity-android-resources')
}
android {
compileSdkVersion 27
buildToolsVersion '28.0.3'
defaultConfig {
targetSdkVersion 27
applicationId 'redacted'
multiDexEnabled true
ndk {
abiFilters 'armeabi-v7a'
}
versionCode 0
versionName '1.0.8'
}
dexOptions {
incremental true
javaMaxHeapSize "4g"
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress '.unity3d', '.ress', '.resource', '.obb', 'crashlytics-build.properties', 'google-services-desktop.json', 'someotherfiles'
}
signingConfigs {
release {
storeFile file('/path/to/key.keystore')
storePassword 'redacted'
keyAlias 'key'
keyPassword 'redacted'
}
}
buildTypes {
debug {
minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'
jniDebuggable true
//Explicitly sign with release key anyway
signingConfig signingConfigs.release
}
release {
minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'
signingConfig signingConfigs.release
}
}
packagingOptions {
doNotStrip '*/armeabi-v7a/*.so'
}
}
Use multiDex when minSdkVersion before 21,should be config multiDexKeepProguard.
link https://developer.android.com/studio/build/multidex#keep
like this
...
multiDexEnabled true
multiDexKeepProguard file("keep_in_main_dex.pro")
...
keep_in_main_dex.pro
-keep class android.support.multidex.** { *; }
# Those classes or methods used in the Application init
....
If use "Run app" button generate apk, the apk may be contains many dex files.
Use "Build->Make Module 'app'" or command line.
I have discovered a... less than optimal solution. My app is currently targeting 21+ as the min SDK. If I drop it down to 20 or lower, the build process apparently changes. Only 2 DEX files come out. Of course this means I need to support Android 4.4+ instead of 5.0+.
To be clear, the only change I made is to add the line
minSdkVersion 20
above targetSdkVersion 27, which changes how it builds. If I change it to minSdkVersion 21 or any number higher, it goes back to being broken.
Use of Pre-dexing is usually the reason for large number of dex files.
Pre-dexing is the process of iterating through all of the project’s modules and converting them from Java bytecode into Android bytecode. It builds each app module and each dependency as a separate DEX file. This dexOption is used in order to build in an incremental way and speed up the build process as change in one module leads to dexing of that module only.
Please try using following dexOptions in your build.gradle file
android {
...
dexOptions {
preDexLibraries = false
}
}
The above should solve your problem and you won't need to support Android 4.4 for your application.
Looks like there is limit of 100 dex files that can be read while targeting minSdkVersion 21: https://android.googlesource.com/platform/art/+/lollipop-release/runtime/dex_file.cc#303. That is the reason your app is working fine after downgrading the api level to 20.
Related
I am using AS 3.1 with gradle-4.5-all.zip and main build.gradle:
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
}
}
allprojects {
repositories {
jcenter()
google()
}
}
An app-level build.gradle looks like following:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
useLibrary 'org.apache.http.legacy'
defaultConfig {
applicationId "com.example"
minSdkVersion 14
targetSdkVersion 27
versionCode 6
versionName "1.00"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
implementation 'ch.acra:acra:4.6.1'
implementation 'commons-validator:commons-validator:1.5.0'
implementation 'com.android.support:support-v13:27.1.1'
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'com.nineoldandroids:library:2.4.0'
implementation 'com.google.zxing:core:3.3.0'
}
and works fine when I set up a debug version under AS 3.1 to my phone, but when I try to make release apk it shows me an error:
Lint found fatal errors while assembling a release target.
To proceed, either fix the issues identified by lint, or modify your build
script as follows:
...
android {
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
}
As I can see in the lint-results-release-fatal.html the reason is:
I would not like to change lintOptions to supress this error because it doesn't solve the problem, it just hide it. More over, when I use
implementation files('libs/commons-validator-1.5.0.jar')
instead of
implementation 'commons-validator:commons-validator:1.5.0'
the release apk is compiled without any error messages. Is it a some gradle bug or what!?
P.S. I have attached a file androidDependencies.txt. Package commons-logging doesn't appears in the dependencies at all! How is it possible to get the solution of above problem analysing this file?
the release apk is compiled without any error messages. Is it a some
gradle bug or what!?
It seems like that dependency has a package which conflicts with the Android itself. The reason why it works without implementation and adding it manually, it might be that it downloads needed packages when you add it to be downloaded from maven repository and that's when the issue came up.
Anyways, the solution at these situations might be using the latest version:
implementation 'commons-validator:commons-validator:1.6'
Or, exclude it as follows:
implementation ('commons-validator:commons-validator:1.5.0') {
exclude group: 'commons-logging', module: 'commons-logging'
}
Note: The following part can't be helpful (for this issue) since the error says:
Commons-logging defines classes that conflict with classes now
provided by Android
You could go deeply by running ./gradlew app:dependencies in the IDE terminal to see which one conflicts with the Android itself and then excluding it as above.
Whether I create the "release" APK by:
Using Generate Signed APK in Android Studio
Select the Release build variant and use Tools -> Build APK
Run the assembleRelease task
... the APK produced always has debuggable=true which I've confirmed by trying to upload them to Google Play, which says:
"Upload failed. You uploaded a debuggable APK. For security reasons you need to disable debugging before it can be published in Google Play."
The (only) manifest has no debuggable attribute specified. Gradle specifies debuggable=false for release, and true for debug, see below.
What am I missing? Where is the debuggable state coming from, and why is the debuggable=false in the release build type declaration being ignored? I do not want to add debuggable=false to the manifest and to have to keep manually enabling/disabling it.
app/build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion '26.0.0'
defaultConfig {
applicationId "com.myapp.android"
minSdkVersion 14
targetSdkVersion 26
versionCode 5
versionName 5
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile rootProject.file("keystore.jks")
if (storeFile.exists()) {
def config = new Properties()
config.load(new FileInputStream(rootProject.file("keystore.passwords")))
storePassword config.KeystorePassword
keyAlias config.KeyAlias
keyPassword config.KeyPassword
}
}
}
buildTypes {
release {
debuggable false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
debuggable true
applicationIdSuffix ".debug"
}
}
dataBinding {
enabled = true
}
lintOptions {
disable 'RtlHardcoded'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// Copy release APK to project root
task copyReleaseApk(type: Copy) {
from 'build/outputs/apk'
into '..'
include '**/*release.apk'
}
afterEvaluate {
if (tasks.findByPath("packageRelease") == null) {tasks.create("packageRelease")}
tasks.findByPath("packageRelease").finalizedBy(copyReleaseApk)
}
}
ext {
// Single place to specify the support library version
supportLibraryVersion = '26.0.0-beta2'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.google.code.findbugs'
exclude module: 'espresso-idling-resource'
exclude group: "javax.inject"
})
implementation 'com.android.support.test.espresso:espresso-contrib:2.2.2'
// Dagger dependency injection
implementation 'com.google.dagger:dagger:2.10'
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
implementation 'com.google.dagger:dagger-android:2.10'
implementation 'com.google.dagger:dagger-android-support:2.10'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.10'
implementation "com.android.support:appcompat-v7:$supportLibraryVersion"
implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support.constraint:constraint-layout:1.0.2"
implementation "com.jakewharton.timber:timber:4.5.1"
implementation "com.squareup.phrase:phrase:1.1.0"
implementation "com.squareup.retrofit2:retrofit:2.2.0"
implementation "com.squareup.retrofit2:converter-gson:2.2.0"
implementation "com.squareup.okhttp3:logging-interceptor:3.7.0"
implementation 'net.danlew:android.joda:2.9.9'
testImplementation 'junit:junit:4.12'
implementation 'com.google.firebase:firebase-crash:11.0.0'
androidTestImplementation 'junit:junit:4.12'
}
apply plugin: 'com.google.gms.google-services'
Update 1: I tried adding debuggable=false to the manifest and it makes no difference, the APK produced still cannot be uploaded to Google Play.
Update 2: I loaded the APKs back into Android Studio using the APK Analyzer that makes it easy to see the manifest, and they all include.... debuggable=true. Where is it coming from?
Update 3: assembleRelease produces a debuggable APK on both my local machine AND on the CI server (BuddyBuild).
Update 4: A clean rebuild (including deleting the build folders) and restarting Android Studio with its caches cleared makes no difference.
Update 5: It seems reasonable to assume that the debuggable=true state could be coming from one of the dependencies, but if that is the case which, and how can that be overridden?
As the project is targeting API 26 and using 3.0.0-alpha4 of the android gradle plugin, 26.0.0-beta2 build tools, and gradle 4.0-rc1 I thought I should check that the issue does not relate to an issue with these pre-release tools. So I reverted to API 25 and the stable releases of gradle 3.3, gradle plugin 2.3.3 and build tools 25.0.3. This was a little tedious as I had to downgrade all the Java 8 syntax from the source to Java 7. But having done that, the build process now works as expected and produces release APK artifacts that do not contain the debuggable="true" flag and can be uploaded to Google Play. 👍
I'm not clear specifically where the cause is but I've logged this in the Android tools bug tracker as it seems possible it is a bug:
https://issuetracker.google.com/issues/62899843
UPDATE: The response from the tools team is that this is expected behaviour because the app targets API 26 and that is in preview. I thought as the 26 APIs were final that APKs could be built against it and released to Google Play but clearly not.
This is because you probably have incremental builds, by default all incremental builds are assumed debuggable.
Under the General Notes of the SDK Tools Revision 8, it states:
Support for a true debug build. Developers no longer need to add the
android:debuggable attribute to the tag in the manifest
— the build tools add the attribute automatically. In Eclipse/ADT, all
incremental builds are assumed to be debug builds, so the tools insert
android:debuggable="true". When exporting a signed release build, the
tools do not add the attribute. In Ant, a ant debug command
automatically inserts the android:debuggable="true" attribute, while
ant release does not. If android:debuggable="true" is manually set,
then ant release will actually do a debug build, rather than a release
build.
We use multidex in our app for a long time but recently with latest update it fails on android API <19
e.g. emulator with api 16
It is standard java.lang.NoClassDefFoundError.
If I define multidexKeepProguard for missing class e.g.
java.lang.NoClassDefFoundError. rx.plugins.RxJavaHooks exception
-keep class rx.plugins.**{*;}
then it will just fail in a different place with the same reason NoClassDefFound
Here is the runner, app and manifest setup:
https://gist.github.com/originx/1890599b57b0ee3e14a85a4732301cd9
Logcat:
https://gist.github.com/originx/887f80d405334f1903b3024eb5cd1024
Build enviroment setup:
Android Studio 2.2.2
Build #AI-145.3360264, built on October 18, 2016
JRE: 1.8.0_112-release-b05 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Compile options
compile 'com.android.support:multidex:1.0.1'
build tools info:
classpath 'com.android.tools.build:gradle:2.2.2'
compileSdkVersion 25
buildToolsVersion '25'
defaultConfig {
applicationId "app.packagename.com"
minSdkVersion 16
targetSdkVersion 25
testInstrumentationRunner "de.payback.app.CustomAndroidJUnitRunner"
multiDexEnabled true
}
dexOptions {
jumboMode true
preDexLibraries false
javaMaxHeapSize "4g"
maxProcessCount = 8
}
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-debug'
signingConfig signingConfigs.debug
minifyEnabled false
shrinkResources debugShrinkResourcesEnabled
proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguardRules/proguard-rules.pro', '../proguardRules/proguard-debug-rules.pro'
// multiDexKeepProguard file('../proguardRules/multidex-proguard.pro')
testProguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguardRules/proguard-rules.pro', '../proguardRules/proguard-debug-test-rules.pro'
testCoverageEnabled false
}
release {
minifyEnabled true
shrinkResources true
testProguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguardRules/proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguardRules/proguard-rules.pro'
// multiDexKeepProguard file('../proguardRules/multidex-proguard.pro')
}
I tried everything from extending MultiDexApplication, to custom MultiDex.install(context) to using MultiDexRunner
same results always
if using multidexkeepproguard file for classes which are usually not found then they are in main dex file but of course something else is missing which indicates that multidex was not properly installed and initialized
Google bug report:
https://code.google.com/p/android/issues/detail?id=228449
repo to reproduce the issue can be found here:
https://github.com/originx/multidex/tree/master
To run please disable instant run
To reproduce multidex issue please run following command
./gradlew clean connectedPayGermanyCompatDebugAndroidTest
run on any device or API 16 emulator Tests on GTI8190 4.1.2 failed Instrumentation run failed due to java.lang.NoClassDefFoundError
Any suggestions how to work around this until I get more info from the Google team?
Explanation by Google dev:
The issue is that the rx.plugins.RxJavaHooks class referenced from the
CustomJunitRunner.onCreate() method is in the secondary dex file of
the main app, and you are accessing it before the class loaders get
fully patched.
When the main application and test code share a dependency, we will
remove it from the test's dependencies (as we expect it to be
available in the main application). However, with legacy multidex,
this is causing problems.
Currently, there are 2 workarounds:
Option 1 Ensure the rx.plugins.RxJavaHooks is in the main dex by
creating a file multidexKeepProguard.pro and adding "-keep class
rx.plugins.**"
Option 2 Remove references to RxJavaHooks from onCreate(), and move
them to onStart() (not sure if this accomplishes when you want
though):
#Override
public void onStart() {
super.onStart();
//hook up schedulers to rxjava so espresso idling resouces can fetch it properly
RxJavaHooks.setOnComputationScheduler(current -> Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR));
RxJavaHooks.setOnIOScheduler(current -> Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR));
RxJavaHooks.setOnNewThreadScheduler(current -> Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR));
}
Solution
Workaround
So current workaround would be either use
multidexKeepProguard.pro file and in your debug config point to that file:
debug {
applicationIdSuffix '.debug'
multiDexKeepProguard file('../proguardRules/multidex-proguard.pro')
}
Your multidex proguard file should contain classes which are not being found in the main dex file, in my case it was RxJavaPlugin, so my multidexproguard file contains:
-keep class rx.** { *; }
I made an basic app containing spinner and button only,but its release mode size is 1.4 MB which i think is too big,cause lots of good apps available below 200kB also
So i tried in gradle file
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
First i added shrinkrsources true but nothing happened then i added minifyEnabled true size reduced to 800 KB ,
Then I tried Android lint by pressing
ctrl + alt + shift +i
and typing Unused Resources ,nothing happened,
then i Tried in android studio
Refactor > Remove Unused Resources
, still nothing happened
so i change extention from .apk to .zip and then extracted it then i found a res folder which is having lot of folders ,which are having lot of icons which are never used,I think those are reason for large size
So how to remove those icons during build so as to reduce apk size,
res folder inside apk file
unused icons inside res > drawable folder
build.gradle Module
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
applicationId "com.example.com"
minSdkVersion 16
targetSdkVersion 24
versionCode 1
versionName "1.0"
resConfigs "en"
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:24.2.0'
}
build.gradle project
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
You can only reduce the size to around 200KB by removing compile 'com.android.support:appcompat-v7:24.2.1 from your build.gradle. This library is responsible for making app backward compatible so add bunch of extra code, style and assets which add up to around 4.5 MB on disk as shown.
This library due to proguard compression end up being around 700 KB to your app size. So the 800KB you are getting is mostly support library.
If you remove this library you can have 200KB app but it might not be compatible with all android versions.
I am trying to developing an application that work in all android version like API level 23 as well as API level 8.While debugging the application it is working perfectly on latest version api, but not working on lower version like Gingerbread.
I try to change minSdkVersion, but this did not solve the issue.
While debugging in lower version it showing error
"Installation failed since the device possibly has stale dexed jars
that don't match the current version (dexopt error). In order to
proceed, you have to uninstall the existing application."
Build.gradle
apply plugin: 'android'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.google.android.gms:play-services:7.8.0'
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:support-v4:21.0.3'
}
android {
compileSdkVersion 22
buildToolsVersion "21.0.0"
defaultConfig {
applicationId "org.linphone"
minSdkVersion 9
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
sourceSets {
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
android {
defaultConfig {
multiDexEnabled = true
minSdkVersion
targetSdkVersion
}
}
configurations.all {
exclude group: 'com.android.support', module: 'support-annotations'
}
}
Manifest.xml
<uses-sdk
android:minSdkVersion="9"
/>
You are using gradle, so you should remove uses-sdk declaration form the manifest.xml - gradle adds it it self. Then change minSdkVersion in the build.gradle to equal or lower than your desired one.
Enable your proguard, its probably caused by dex file. It happens when dex file gets larger then buffer size( i.e. contains more than 65k methods).
android {
...
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Your app has reached 65k methods limit. There are few solutions:
Shrink your code with ProGuard. From the docs:
The ProGuard tool shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. The result is a smaller sized .apk file that is more difficult to reverse engineer. Because ProGuard makes your application harder to reverse engineer, it is important that you use it when your application utilizes features that are sensitive to security like when you are Licensing Your Applications.
Enable it on build.gradle of your app module:
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
You can add your own ProGuard custom rule, read here.
Update SDK Build Tools to the latest version.
Build your app with MultiDex technique.
Additionally, your apply plugin: 'android' must be changed to apply plugin: 'com.android.application'.
I ran into this trying to test on an emulator. My solution was to give the emulator more memory and storage. It appears to be an issue when building with new (ART) tools and deploying to Dalvik.