android 5.0以下系统首次启动黑屏问题

文章深入探讨了Android应用在4.4系统上首次运行时出现黑屏问题的原因,归因于MultiDex安装过程中的dex合并与优化操作耗时过长。通过源码分析,详细解释了MultiDex的工作原理及dex文件的处理流程,提出了优化建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

android 5.0以下系统首次启动黑屏问题

最近在android 机顶盒上发现app首次运行的时候启动黑屏,看日志发现是 MultiDex.install(this)在4.4系统上首次安装时合并dex和进行dexOpt操作导致。整个操作大概耗时15s,下面通过源码分析下原因。

首先在4.4的系统上,安装app后系统做了点什么事呢?
1更新PMS的版本信息,以方便系统对app进行管理。
2 在app对应的目录下面创建目录以及将dex文件和资源文件复制到相应的位置。通过adb shell我们看下。
在这里插入图片描述
在data/data/package/目录下面创建了lib,将app中的so导入到这里。
在这里插入图片描述
将app放在到/data/app目录下面。
在这里插入图片描述
将app的主dex放置到/data/dalvik-cache目录下面。

安装之后在首次点击的时候发生了什么?app启动的时候会首先加载系统的主题界面,之后执行application 的oncreate()方法, 这个时候是没有界面显示,application 的oncreate()方法执行接收后才会显示splash界面。如果application 的oncreate()方法执行时间较长,就会出现黑屏。
在这里插入图片描述
首先会执行attachBaseContext,这个方法早于application 的oncreate()方法执行,那么看下这个方法做了点什么。

这里我将MultiDex方法复制后重新命名使用,这样做的好处是可以自由增加日志。

 public static void install(Context context) {
        Log.i("MultiDex", "Installing application");
        //如果是5.0以上的系统,会走这里,也就是只打印日志就返回
        if (IS_VM_MULTIDEX_CAPABLE) {
            Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
        } else if (Build.VERSION.SDK_INT < 4) {//版本较低,基本不考虑了
            throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
        } else {
            try {
            //获取app的信息
                ApplicationInfo applicationInfo = getApplicationInfo(context);
                Log.d("MultiDex","applicationInfo = " + applicationInfo.toString());
                if (applicationInfo == null) {
                    Log.i("MultiDex", "No ApplicationInfo available, i.e. running on a test Context: MultiDex support library is disabled.");
                    return;
                }
				//开始合并dex,new File(applicationInfo.sourceDir) = /data/app/xxx.apk, new File(applicationInfo.dataDir), "secondary-dexes", "", true) = /data/data/xxx/code_cache/secondary-dexes
                doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "", true);
            } catch (Exception var2) {
                Log.e("MultiDex", "MultiDex installation failure", var2);
                throw new RuntimeException("MultiDex installation failed (" + var2.getMessage() + ").");
            }

            Log.i("MultiDex", "install done");
        }
    }
private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException {
        synchronized(installedApk) {
            if (!installedApk.contains(sourceApk)) {
                installedApk.add(sourceApk);
                if (Build.VERSION.SDK_INT > 20) {
                    Log.w("MultiDex", "MultiDex is not guaranteed to work in SDK version " + Build.VERSION.SDK_INT + ": SDK version higher than " + 20 + " should be backed by " + "runtime with built-in multidex capabilty but it's not the " + "case here: java.vm.version=\"" + System.getProperty("java.vm.version") + "\"");
                }

                ClassLoader loader;
                try {
                //获取app的classLoader
                    loader = mainContext.getClassLoader();
                } catch (RuntimeException var25) {
                    Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", var25);
                    return;
                }

                if (loader == null) {
                    Log.e("MultiDex", "Context class loader is null. Must be running in test mode. Skip patching.");
                } else {
                    try {
                    //清除/data/data/xxx/files/secondary-dexes下的dex缓存
                        clearOldDexDir(mainContext);
                    } catch (Throwable var24) {
                        Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var24);
                    }
					///data/data/xxx/code_cache/secondary-dexes
                    File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
                    Log.d("MultiDex", "dexDir = " + dexDir.toString());
                    //从/data/app/xxx.apk中提取dex到/data/data/xxx/code_cache/secondary-dexes
                    MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
                    IOException closeException = null;

                    try {
                    //先看提取结果, loadFiles = [/data/data/xxx/code_cache/secondary-dexes/xxx-1.apk.classes2.zip, /data/data/xxx/code_cache/secondary-dexes/xxx-1.apk.classes3.zip]
                   
                        List files = extractor.load(mainContext, prefsKeyPrefix, false);
                        Log.d("MultiDex","loadFiles = " + files);
                        try {
                        //装配提取的dex路径
                            installSecondaryDexes(loader, dexDir, files);
                        } catch (IOException var26) {
                            if (!reinstallOnPatchRecoverableException) {
                                throw var26;
                            }

                            Log.w("MultiDex", "Failed to install extracted secondary dex files, retrying with forced extraction", var26);
                            files = extractor.load(mainContext, prefsKeyPrefix, true);
                            installSecondaryDexes(loader, dexDir, files);
                        }
                    } finally {
                        try {
                            extractor.close();
                        } catch (IOException var23) {
                            closeException = var23;
                        }

                    }

                    if (closeException != null) {
                        throw closeException;
                    }
                }
            }
        }
    }
 List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException {
        Log.i("MultiDex", "MultiDexExtractor.load(" + this.sourceApk.getPath() + ", " + forceReload + ", " + prefsKeyPrefix + ")");
        if (!this.cacheLock.isValid()) {
            throw new IllegalStateException("MultiDexExtractor was closed");
        } else {
            List files;
            //校验dex文件是否有更改,没有更改的话就直接loadExistingExtractions,因此,第二次点击app的时候就直接走的这里,不需要重新提取dex以及dexopt
            if (!forceReload && !isModified(context, this.sourceApk, this.sourceCrc, prefsKeyPrefix)) {
                try {
                    files = this.loadExistingExtractions(context, prefsKeyPrefix);
                } catch (IOException var6) {
                    Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var6);
                    files = this.performExtractions();
                    putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
                }
            } else {
                if (forceReload) {
                    Log.i("MultiDex", "Forced extraction must be performed.");
                } else {
                    Log.i("MultiDex", "Detected that extraction must be performed.");
                }
				//从/data/app/xxx.apk中提取dex到/data/data/xxx/code_cache/secondary-dexes
                files = this.performExtractions();
                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
            }

            Log.i("MultiDex", "load found " + files.size() + " secondary dex files");
            return files;
        }
    }
    private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException, SecurityException, ClassNotFoundException, InstantiationException {
        if (!files.isEmpty()) {
            if (Build.VERSION.SDK_INT >= 19) {
            //4.4系统走这里
               V19.install(loader, files, dexDir);
            } else if (Build.VERSION.SDK_INT >= 14) {
                V14.install(loader, files);
            } else {
                V4.install(loader, files);
            }
        }

    }
  static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
            Field pathListField = findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList();
            Log.d("MultiDex","installV9 and optimizedDirectory = " + optimizedDirectory.toString());
         
            //替换classLoader里面的pathList为dex路径,路径是[/data/data/xxx/code_cache/secondary-dexes/xxx-1.apk.classes2.zip, /data/data/xxx/code_cache/secondary-dexes/xxx-1.apk.classes3.zip]
            expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
            Log.d("MultiDex","makeDexElements end");
          
            if (suppressedExceptions.size() > 0) {
                Iterator var6 = suppressedExceptions.iterator();

                while(var6.hasNext()) {
                    IOException e = (IOException)var6.next();
                    Log.w("MultiDex", "Exception in makeDexElement", e);
                }

                Field suppressedExceptionsField = findField(dexPathList, "dexElementsSuppressedExceptions");
                IOException[] dexElementsSuppressedExceptions = (IOException[])((IOException[])suppressedExceptionsField.get(dexPathList));
                if (dexElementsSuppressedExceptions == null) {
                    dexElementsSuppressedExceptions = (IOException[])suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
                } else {
                    IOException[] combined = new IOException[suppressedExceptions.size() + dexElementsSuppressedExceptions.length];
                    suppressedExceptions.toArray(combined);
                    System.arraycopy(dexElementsSuppressedExceptions, 0, combined, suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
                    dexElementsSuppressedExceptions = combined;
                }

                suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
                IOException exception = new IOException("I/O exception during makeDexElement");
                exception.initCause((Throwable)suppressedExceptions.get(0));
                throw exception;
            }
        }

        private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            Log.d("MultiDex","makeDexElements");
            if(optimizedDirectory.isDirectory()){
                File[] files1= optimizedDirectory.listFiles();
                for(File file : files1){
                    Log.d("MultiDex",file.toString());
                }
            }
            
            Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
            //这个方法调用会会出发dexopt操作,在/data/data/xxx/code_cache/secondary-dexes/下面生成xxx-1.apk.classes2.dex和xxx-1.apk.classes3.dex
            Object[] objs =  (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
            if(optimizedDirectory.isDirectory()){
                File[] files2= optimizedDirectory.listFiles();
                for(File file : files2){
                    Log.d("MultiDex",file.toString());
                }
            }
            return objs;
        }

总结下结论,在 MultiDex.install调用后,会检测/data/data/xxx/code_cache/secondary-dexes/下面有没有dex文件并且dex文件没有被修改,就直接通过反射将/data/data/xxx/code_cache/secondary-dexes/下面的dex路径添加到classLoader里面的pathList。
如果发现/data/data/xxx/code_cache/secondary-dexes/下面没有dex文件或者dex文件被修改了,则从
/data/app/xxx.apk提取dex文件到/data/data/xxx/code_cache/secondary-dexes,通过反射将/data/data/xxx/code_cache/secondary-dexes/下面的dex路径添加到classLoader里面的pathList。

因此,首次的时候会黑屏,第二次点击的时候就不会。

解决方法:可以参考https://juejin.im/post/5d95f4a4f265da5b8f10714b#heading-10。

这里面遇到一个坑,就是在application oncreate里面执行ARouter.init()的时候会触发dexOpt操作,因此,最好在子线程里面执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值