美文网首页
DEX分包引起的apk首次启动时间过长问题

DEX分包引起的apk首次启动时间过长问题

作者: 0411121464e2 | 来源:发表于2018-09-26 13:50 被阅读0次

    在android apk中如果项目引用的方法数超过64k(包括android框架方法、库方法、自己代码中的方法)的限制,就需要对apk进行dex分包处理。当第一次启动app的时候,系统会执行复杂的计算来确定主dex文件所需要的类,往往这里会花费很多时间。解决的方案就是通过mutildex.keep文件来告诉主dex文件所需要包含的类。这样app首次启动的时候,就不需要进行复杂的计算来确定主dex文件所需要包含的类了。需要做的步骤如下:

    1. 在build.gradle同目录中新建multidex.keep文件

    2. 在build.gradle文件中指明主dex文件该包含的class文件应该从multidex.keep文件获取

    android.applicationVariants.all { variant ->

            task "fix${variant.name.capitalize()}MainDexClassList" << {

                logger.info "Fixing main dex keep file for $variant.name"

                File keepFile = new File("$buildDir/intermediates/multi-dex/$variant.buildType.name/maindexlist.txt")

                keepFile.withWriterAppend { w ->

                    // Get a reader for the input file

                    w.append('\n')

                    new File("${projectDir}/multidex.keep").withReader { r ->

                        // And write data from the input into the output

                        w << r << '\n'

                    }

                    logger.info "Updated main dex keep file for ${keepFile.getAbsolutePath()}\n$keepFile.text"

                }

            }

        }

        tasks.whenTaskAdded { task ->

            android.applicationVariants.all { variant ->

                if (task.name == "create${variant.name.capitalize()}MainDexClassList") {

                    task.finalizedBy "fix${variant.name.capitalize()}MainDexClassList"

                }

            }

        }

    3.在multidex.keep文件中添加主dex文件所需的class文件。主dex文件所需的class文件可以通过以下方法获取。调用MultiDexUtils的getLoadedExternalDexClasses方法即可获取所需的class文件的list。

    import android.content.Context;

    import android.content.SharedPreferences;

    import android.content.pm.ApplicationInfo;

    import android.content.pm.PackageManager;

    import android.os.Build;

    import java.io.File;

    import java.io.IOException;

    import java.util.ArrayList;

    import java.util.Enumeration;

    import java.util.List;

    import dalvik.system.DexFile;

    public class MultiDexUtils {

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

        private 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);

        }

        /**

        * get all the dex path

        *

        * @param context the application context

        * @return all the dex path

        * @throws PackageManager.NameNotFoundException

        * @throws IOException

        */

        public List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {

            final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);

            final File sourceApk = new File(applicationInfo.sourceDir);

            final File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

            final List<String> sourcePaths = new ArrayList<>();

            sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

            //the prefix of extracted file, ie: test.classes

            final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;

            //the total dex numbers

            final 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...

                final String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;

                final File extractedFile = new File(dexDir, fileName);

                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;

        }

        /**

        * get all the external classes name in "classes2.dex", "classes3.dex" ....

        *

        * @param context the application context

        * @return all the classes name in the external dex

        * @throws PackageManager.NameNotFoundException

        * @throws IOException

        */

        public List<String> getExternalDexClasses(Context context) throws PackageManager.NameNotFoundException, IOException {

            final List<String> paths = getSourcePaths(context);

            if(paths.size() <= 1) {

                // no external dex

                return null;

            }

            // the first element is the main dex, remove it.

            paths.remove(0);

            final List<String> classNames = new ArrayList<>();

            for (String path : paths) {

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

                    }

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

        }

        /**

        * Get all loaded external classes name in "classes2.dex", "classes3.dex" ....

        * @param context

        * @return get all loaded external classes

        */

        public List<String> getLoadedExternalDexClasses(Context context) {

            try {

                final List<String> externalDexClasses = getExternalDexClasses(context);

                if (externalDexClasses != null && !externalDexClasses.isEmpty()) {

                    final ArrayList<String> classList = new ArrayList<>();

                    final java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[]{String.class});

                    m.setAccessible(true);

                    final ClassLoader cl = context.getClassLoader();

                    for (String clazz : externalDexClasses) {

                        if (m.invoke(cl, clazz) != null) {

                            classList.add(clazz.replaceAll("\\.", "/").replaceAll("$", ".class"));

                        }

                    }

                    return classList;

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

            return null;

        }

    }

    4.再把list数组写到手机sd卡的txt文件中,把txt文件的内容复制到multidex.keep即可。

    MultiDexUtils dexUtils = new MultiDexUtils();

            List<String> des = dexUtils.getLoadedExternalDexClasses(this);

            String sdCardDir = Environment.getExternalStorageDirectory().getAbsolutePath();

            File saveFile = new File(sdCardDir, "aaaa.txt");

            try {

                FileOutputStream outStream = new FileOutputStream(saveFile);

                outStream.write(listToString(des).getBytes());

                outStream.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

    public static String listToString(List<String> stringList){

            if(stringList==null) {

                return null;

            }

            StringBuilder result = new StringBuilder();

            boolean flag=false;

            for(String string : stringList) {

                if(flag) {

                    result.append("\n");

                }else{

                    flag=true;

                }

                result.append(string);

            }

            return result.toString();

        }

    经过我公司的app测试,可优化app首次启动从4秒左右减到1秒左右。优化还是挺明显的。

    相关文章

      网友评论

          本文标题:DEX分包引起的apk首次启动时间过长问题

          本文链接:https://www.haomeiwen.com/subject/lgsbpftx.html