GeorgeYang'Blog

my technology blog

解决ActiveAndroid出现65536后表操作奔溃问题

阅读:293 创建时间:16-03-25 14:00:46 tags:android,ActiveAndroid

当ActiveAndroid随着application初始化创建表,将类名缓存到一个静态变量里面(其加载方法是读取一个dex的所有class,枚举判断是否是表,如果是则创建并缓存),但是如果app超过65535个方法数,编译的dex就会被拆分成两个,第二个以上的dex内部的class就不会被加载缓存,假设A.class被编译到第一个类,那么框架能对它读取并缓存,在app运行期间使用这个类是没问题的,如果此时有个B.class由于方法数超出范围,被编译到classes2.dex文件,那么ActiveAndroid就不会加载B.class,初始化后没有被缓存起来,当用ActiveAndroid对B进行表操作的时候,程序就会奔溃。

该bug出现在:ModelInfo.java文件第124行scanForModel方法

 DexFile dexfile = new DexFile(sourcePath);
 Enumeration<String> entries = dexfile.entries();

 while (entries.hasMoreElements()) {
    paths.add(entries.nextElement());
 }

该代码只能获取一个dex里面的所有class,但无法获取第二个dex的class

解决方法: 使用MultiDexHelper加载所有dex的所有class: http://xudshen.info/2014/11/12/list-all-classes-after-multidex/

最终可以正确使用的代码:

 private void scanForModel(Context context) throws Exception {
        String packageName = context.getPackageName();
        List<String> paths = getAllClasses(context);
        for (String path : paths) {
            File file = new File(path);
            scanForModelClasses(file, packageName, context.getClassLoader());
        }
    }


    /**
     * get all the classes name in "classes.dex", "classes2.dex", ....
     *
     * @param context the application context
     * @return all the classes name
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     */
    public static List<String> getAllClasses(Context context) throws PackageManager.NameNotFoundException, IOException {
        List<String> classNames = new ArrayList<String>();
        for (String path : getSourcePaths(context)) {
            try {
                DexFile dexfile = null;
                if (path.endsWith(EXTRACTED_SUFFIX)) {
                    //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                    dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                } else {
                    dexfile = new DexFile(path);
                }
                Enumeration<String> dexEntries = dexfile.entries();
                while (dexEntries.hasMoreElements()) {
                    classNames.add(dexEntries.nextElement());
                }
            } catch (IOException e) {
                throw new IOException("Error at loading dex file '" +
                        path + "'");
            }
        }
        return classNames;
    }


    private static final String EXTRACTED_NAME_EXT = ".classes";
    private static final String EXTRACTED_SUFFIX = ".zip";

    private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator +
            "secondary-dexes";

    private static final String PREFS_FILE = "multidex.version";
    private static final String KEY_DEX_NUMBER = "dex.number";
    /**
     * get all the dex path
     *
     * look:http://stackoverflow.com/questions/26623905/android-multidex-list-all-classes
     * @param context the application context
     * @return all the dex path
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     */
    public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        File sourceApk = new File(applicationInfo.sourceDir);
        File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

        List<String> sourcePaths = new ArrayList<String>();
        sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

        //the prefix of extracted file, ie: test.classes
        String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
        //the total dex numbers
        int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);

        for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
            //for each dex file, ie: test.classes2.zip, test.classes3.zip...
            String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
            File extractedFile = new File(dexDir, fileName);
            Log.d("george","extractedFile:" + extractedFile.getAbsolutePath());
            Log.d("george","extractedFile exist:" + extractedFile.exists());
            if (extractedFile.isFile()) {
                sourcePaths.add(extractedFile.getAbsolutePath());
                //we ignore the verify zip part
            } else {
                throw new IOException("Missing extracted secondary dex file '" +
                        extractedFile.getPath() + "'");
            }
        }
        return sourcePaths;
    }
    private static SharedPreferences getMultiDexPreferences(Context context) {
        return context.getSharedPreferences(PREFS_FILE,
                Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
                        ? Context.MODE_PRIVATE
                        : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
    }

最后,本人已经向github反馈该问题,同时发现这个框架已经多年没有更新了,最后的更新时间是两年前,建议读者尽快丢弃该框架,使用其他较新的框架。