Popular Posts
ADD ... THEN ... UNTIL ... data: begin of SERIES, N1 type I value 10, N2 type I value 20, N3 type I value 30, N4 type I value 40, ... jQuery serialize form to json $.fn.serializeObject = function() {     var o = {};     var a = this.serializeArray();     $.each(a, function() {         if (o[this.name]) ... abap naming rule 命名規則 報表程式(以列表格式輸出資料分析):Yaxxxxxx或Zaxxxxxx。用應用程式區的分類字母替換a。 任何有效字元替換x。注意SAP報表程式遵守相似的命名約定:Raxxxxxx。 任何其他ABAP/4程式(培訓程式或事務程式):SAPMYxxx或SAPMZxxx...
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;
}