Android-5.0以下MultiDex加载时间优化
本文最后更新于 653 天前
1. 思路
  • 将MultiDex加载多dex合并的耗时操作放在一个新的进程进行加载。新建进程创建一个临时文件tempFile,加载完成后删除这个文件并结束当前新建进程。主进程轮询判断tempFile是否存在-不存在说明加载完成,进行后续的步骤。

  • MultiDex 会将apk转为zip,并遍历zip获取每个dex 并也转为zip (根据DexPathList源码 文件后缀判断,这些转换并无必要。)

private static final class V19 {
    private V19() {
    }

    static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
        //1 反射ClassLoader 的 pathList 字段
        Field pathListField = MultiDex.findField(loader, "pathList");
        Object dexPathList = pathListField.get(loader);
        ArrayList<IOException> suppressedExceptions = new ArrayList();
        // 2 扩展数组 就是将dex2 dex3一起加到dexElements数组内,Tinker热修复原理就是这个
        MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
        ...
        }

    private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
        return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
    }
}

2. Application

对象第一次创建的时候,java虚拟机首先检查类对应的Class 对象是否已经加载。如果没有加载,jvm会根据类名查找.class文件,将其Class对象载入。同一个类第二次new的时候就不需要加载类对象,而是直接实例化,创建时间就缩短了。

public class MyApplication extends Application {
    private static final String TAG = "lxb-MyApplication";
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        Log.d(TAG, "attachBaseContext-getPackageName: " + base.getPackageName());
        Log.d(TAG, "attachBaseContext-getProcessName: " + SystemUtil.getProcessName(base));

        boolean isMainProcess = isMainProcess(base);
        Log.d(TAG, "attachBaseContext-isMainProcess: " + isMainProcess);

        //主进程并且vm不支持多dex的情况下才使用 MultiDex
        if (isMainProcess && !SystemUtil.isVMMultidexCapable()){
            loadMultiDex(base);
        }
    }

    private boolean isMainProcess(Context context) {
        return context.getPackageName().equals(SystemUtil.getProcessName(context));
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (!isMainProcess(this)){
            Log.d(TAG, "onCreate: 非主进程,return");
            return;
        }
        Log.d(TAG, "主进程 onCreate: 一些初始化操作");
    }

    private void loadMultiDex(Context context) {
        newTempFile(context); //创建临时文件

        //启动另一个进程去加载MultiDex
        Intent intent = new Intent(context, LoadMultiDexActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        //检查MultiDex是否安装完(安装完会删除临时文件)
        checkUntilLoadDexSuccess(context);

        //另一个进程以及加载 MultiDex,有缓存了,所以主进程再加载就很快了。
        //为什么主进程要再加载,因为每个进程都有一个ClassLoader
        long startTime = System.currentTimeMillis();
        MultiDex.install(context);
        Log.d(TAG, "第二次 MultiDex.install 结束,耗时: " + (System.currentTimeMillis() - startTime));
        // 优化点-提前new Activity,后续加载就很快了,涉及到类加载机制
        preNewActivity();
    }

    private void preNewActivity() {
        long startTime = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        Log.d(TAG, "preNewActivity 耗时: " + (System.currentTimeMillis() - startTime));
    }

    //创建一个临时文件,MultiDex install 成功后删除
    private void newTempFile(Context context) {
        try {
            File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
            if (!file.exists()) {
                Log.d(TAG, "newTempFile: ");
                file.createNewFile();
            }
        } catch (Throwable th) {
            th.printStackTrace();
        }
    }

    /**
     * 检查MultiDex是否安装完,通过判断临时文件是否被删除
     * @param context
     * @return
     */
    private void checkUntilLoadDexSuccess(Context context) {
        File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
        int i = 0;
        int waitTime = 100; //睡眠时间
        try {
            Log.d(TAG, "checkUntilLoadDexSuccess: >>> ");
            while (file.exists()) {
                Thread.sleep(waitTime);
                Log.d(TAG, "checkUntilLoadDexSuccess: sleep count = " + ++i);
                if (i > 40) {
                    Log.d(TAG, "checkUntilLoadDexSuccess: 超时,等待时间: " + (waitTime * i));
                    break;
                }
            }

            Log.d(TAG, "checkUntilLoadDexSuccess: 轮循结束,等待时间 " +(waitTime * i));

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
3. 新进程Activity
<activity android:name="com.lanshifu.launchtest.LoadMultiDexActivity"
     android:process=":multi_dex"
         android:launchMode="singleTask"
             android:excludeFromRecents="true"
                 ></activity>
public class LoadMultiDexActivity extends Activity {
    private static final String TAG = "lxb-LoadMultiDexActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_load_multi_dex);

        Thread thread = new Thread() {
            @Override
            public void run() {
                loadMultiDex();
            }
        };
        thread.setName("multi_dex");
        thread.start();

        showLoadingDialog();
    }

    private void loadMultiDex(){
        Log.d(TAG, "MultiDex.install 开始: ");
        long startTime = System.currentTimeMillis();
        MultiDex.install(LoadMultiDexActivity.this);
        Log.d(TAG, "MultiDex.install 结束,耗时: " + (System.currentTimeMillis() - startTime));

        try {
            //模拟MultiDex耗时很久的情况
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        aftetMultiDex();
    }

    private void aftetMultiDex() {
        deleteTempFile(this);

        //将这个进程杀死
        Log.d(TAG, "aftetMultiDex: ");
        finish();
        Process.killProcess(Process.myPid());
    }

    private void deleteTempFile(Context context) {
        try {
            File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
            if (file.exists()) {
                file.delete();
                Log.d(TAG, "deleteTempFile: ");
            }
        } catch (Throwable th) {
            th.printStackTrace();
        }
    }

    private void showLoadingDialog(){
        new AlertDialog.Builder(this)
                .setMessage("加载中,请稍后...")
                .show();
    }
}
4. Utils
public class SystemUtil {
    public static String getProcessName(Context context){
        ActivityManager activityManager = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager
                .getRunningAppProcesses();

        int myPid = Process.myPid();

        if(appProcesses == null || appProcesses.size() == 0){
            return null;
        }

        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.processName.equals(context.getPackageName())) {
                if (appProcess.pid == myPid){
                    return appProcess.processName;
                }
            }
        }
        return null;
    }

    public static boolean isVMMultidexCapable(){
        return isVMMultidexCapable(System.getProperty("java.vm.version"));
    }

    //MultiDex 拷出来的的方法,判断VM是否支持多dex
    public static boolean isVMMultidexCapable(String versionString) {
        boolean isMultidexCapable = false;
        if (versionString != null) {
            Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
            if (matcher.matches()) {
                try {
                    int major = Integer.parseInt(matcher.group(1));
                    int minor = Integer.parseInt(matcher.group(2));
                    isMultidexCapable = major > 2 || major == 2 && minor >= 1;
                } catch (NumberFormatException var5) {
                }
            }
        }

        Log.i("MultiDex", "VM with version " + versionString + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
        return isMultidexCapable;
    }
}

原文章链接
源码fork地址

iichen:https://iichen.cn/?p=473
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇