Recently I upgrade my Android Studio to version 2.0, and try to build and run some app on devices. Unfortunately something goes wrong when app executing. When app invokes DexFile.entries() for the class names, the result between android 4 and 5+ is different.
Android version
Okay, let's do some simple test, here is a brand new project I've currently created. The build config and code is below:
App only invokes DexFile.entries() and show the result on a TextView.
On the result, there is a large difference between android 4.2.2 and android 5.0.0
On version 4.2.2, the listed class count is 1555, but on version 5.0.0, there are only 31 entries return.
build.gradle
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.prhythm.myapplication" minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' }
MainActivity.java
package com.prhythm.myapplication; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.widget.TextView; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import dalvik.system.DexFile; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView text = (TextView) findViewById(R.id.content); try { List<String> paths = new ArrayList<>(); DexFile dex = new DexFile(getApplicationInfo().sourceDir); for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) { paths.add(entries.nextElement()); } text.append(String.format("Total entry count: %d%n", paths.size())); text.append("* " + TextUtils.join("\n* ", paths)); } catch (IOException e) { e.printStackTrace(); } } }
Run Result on android 4.2.2 & 5.0.0
Solution:
Looks like this issue is related to the new InstantRun feature in the Android Plugin for Gradle 2.0.0. getPackageCodePath() gets a String pointing towards the base.apk file in the Android file system. If we unzip that apk we can find one or several .dex files inside its root folder. The entries obtained from the method df.entries() iterates over the .dex files found in that root folder in order to obtain all of its compiled classes. However, if we are using the new Android Plugin for Gradle, we will only find the .dex related to the android runtime and instant run (packages com.tools.android.fd.runtime, com.tools.android.fd.common and com.tools.android.tools.ir.api). Every other class will be compiled in several .dex files, zipped into a file called instant-run.zip and placed into the root folder of the apk. That's why the code posted in the question is not able to list all the classes within the app. Still, this will only affect Debug builds since the Release ones don't feature InstantRun.
List<String> getClasses() throws IOException { List<String> paths = new ArrayList<>(); File instantRunDir = new File(getBaseContext().getFilesDir(), "instant-run/dex"); if (instantRunDir.exists()) { for (File dexPath : instantRunDir.listFiles()) { DexFile dex = new DexFile(dexPath); for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) { paths.add(entries.nextElement()); } } } DexFile dex = new DexFile(getApplicationInfo().sourceDir); for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) { paths.add(entries.nextElement()); } return paths; }