Popular Posts
Enable SSL connection for Jsoup import org.jsoup.Connection; import org.jsoup.Jsoup; import javax.net.ssl.*; import java.io.IOException; import java.security.KeyManagement... Change the AppDomain's Base Directory and Environment Directory // Update AppDomain's Base Directory string root_path = "c:\\temp\\"; AppDomain.CurrentDomain.SetData("APPBASE", roo... Word break tag : <wbr/> (HTML5) The  HTML  <wbr>  tag  is  used  defines  a  potential  line  break  point  if  needed.  This  stands  for  Word  BReak. This  is  u...
Stats
Problem on result of DexFile.entries()

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;
}