一般建网站多少钱:今天头条打开如此丝滑,是做了哪些优化呢?
本文摘要: 前言网上关于启动优化的文章多不堪数,内容千人一面,大都是罗列一些耗时操作,选用异步加载、懒加载等。而在面试过程当中,关于启动优化的问题,假如只是很表面地答复耗时操作应该放在子线程,显然太过于普通,无法跟竞争者拉开

前言


网上关于启动优化的文章多不堪数,内容千人一面,大都是罗列一些耗时操作,选用异步加载、懒加载等。

而在面试过程当中,关于启动优化的问题,假如只是很表面地答复耗时操作应该放在子线程,显然太过于普通,无法跟竞争者拉开差距。怎么让面试官知道你的“内功深沉”,那肯定是要往原理层面去答复。

本文重点仍是重视原理,冷启动优化这个问题能延伸到很多原理层面的常识点,本文比较有意思的当地是通过反编译今天头条App,研讨大厂的启动优化方案。

讲启动优化之前,先看下应用的启动流程

一、应用启动流程


应用进程不存在的状况下,从点击桌面应用图标,到应用启动(冷启动),大约会阅历以下贱程:
  1. Launcher startActivity
  2. AMS startActivity
  3. Zygote fork 进程
  4. ActivityThread main()
    4.1. ActivityThread attach
    4.2. handleBindApplication
    4.3 attachBaseContext
    4.4. installContentProviders
    4.5. Application onCreate
  5. ActivityThread 进入loop循环
  6. Activity生命周期回调,onCreate、onStart、onResume...

整个启动流程我们精干预的主要是 4.3、4.5 和6,应用启动优化主要从这三个当地下手。抱负状况下,这三个当地假如不做任何耗时操作,那么应用启动速度就是最快的,可是现实很骨感,很多开源库接入第一步一般都是在Application onCreate方法初始化,有的乃至直接内置ContentProvider,直接在ContentProvider中初始化框架,不给你优化的时机。

二、启动优化


直奔主题,常见的启动优化方式大约有这些:
  • 闪屏页优化
  • MultipDex优化(本文重点)
  • 第三方库懒加载
  • WebView优化
  • 线程优化
  • 体系调用优化

2.1 闪屏页优化


消除启动时的白屏/黑屏,市道上大部分App都选用了这种方法,十分简略,是一个障眼法,不会缩短实践冷启动时间,简略贴下完成方式吧。
<application    android:name=".MainApplication"    ...    android:theme="@style/AppThemeWelcome>复制代码

styles.xml 添加一个主题叫AppThemeWelcome
<style name="AppThemeWelcome" parent="Theme.AppCompat.NoActionBar">    ...    <item name="android:windowBackground">@drawable/logo</item>  <!-- 默许布景--></style>复制代码

闪屏页设置这个主题,或者全局给Application设置
        <activity android:name=".ui.activity.DemoSplashActivity"            android:configChanges="orientation|screenSize|keyboardHidden"            android:theme="@style/AppThemeWelcome"            android:screenOrientation="portrait">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>复制代码

这样的话启动Activity之后布景会一直在,所以在Activity的onCreate方法中切换成正常主题
protected void onCreate(@Nullable Bundle savedInstanceState) {    setTheme(R.style.AppTheme); //切换正常主题    super.onCreate(savedInstanceState);复制代码

这样打开桌面图标会马上显示logo,不会呈现黑/白屏,直到Activity启动完成,替换主题,logo消失,可是总的启动时间并没有改变。

2.2 MultiDex 优化(本文重点)


说MultiDex之前,先梳理下apk编译流程

2.2.1 apk编译流程


Android Studio 按下编译按钮后发生了什么?
  1. 打包资源文件,生成R.java文件(使用东西AAPT)
  2. 处理AIDL文件,生成java代码(没有AIDL则疏忽)
  3. 编译 java 文件,生成对应.class文件(java compiler)
  4. .class 文件转换成dex文件(dex)
  5. 打包成没有签名的apk(使用东西apkbuilder)
  6. 使用签名东西给apk签名(使用东西Jarsigner)
  7. 对签名后的.apk文件进行对齐处理,不进行对齐处理不能发布到Google Market(使用东西zipalign)

在第4步,将class文件转换成dex文件,默许只会生成一个dex文件,单个dex文件中的方法数不能超过65536,不然编译会报错:

Unable to execute dex: method ID not in [0, 0xffff]: 65536

App集成一堆库之后,方法数一般都是超过65536的,解决方法就是:一个dex装不下,用多个dex来装,gradle添加一行配置即可。

multiDexEnabled true

这样解决了编译问题,在5.0以上手机运转正常,可是5.0以下手机运转直接crash,报错 Class NotFound xxx。

Android 5.0以下,ClassLoader加载类的时分只会从class.dex(主dex)里加载,ClassLoader不知道其它的class2.dex、class3.dex、...,当拜访到不在主dex中的类的时分,就会报错:Class NotFound xxx,因此谷歌给出兼容方案,MultiDex。

2.2.2 MultiDex 本来这么耗时


在Android 4.4的机器打印MultiDex.install(context)耗时如下:
MultiDex.install 耗时:1320复制代码

均匀耗时1秒以上,现在大部分应用应该仍是会兼容5.0以下手机,那么MultiDex优化是冷启动优化的大头。

为何MultiDex会这么耗时?老例子,分析一下MultiDex原理~

2.2.3 MultiDex 原理


下面看下MultiDex的install 方法做了什么事
public static void install(Context context) {        Log.i("MultiDex", "Installing application");        if (IS_VM_MULTIDEX_CAPABLE) { //5.0 以上VM根本支撑多dex,啥事都不用干            Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");        } else if (VERSION.SDK_INT < 4) { //             throw new RuntimeException("MultiDex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");        } else {            ...            doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "", true);            ...            Log.i("MultiDex", "install done");        }    }复制代码

从进口的判断来看,假如虚拟机本身就支撑加载多个dex文件,那就啥都不用做;假如是不支撑加载多个dex(5.0以下是不支撑的),则走到 doInstallation 方法。
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 {...                    //获取非主dex文件                    File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);                    MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);                    IOException closeException = null;                    try {                        // 1. 这个load方法,第一次没有缓存,会十分耗时                        List files = extractor.load(mainContext, prefsKeyPrefix, false);                        try {                            //2. 装置dex                            installSecondaryDexes(loader, dexDir, files);                        }                         ...                }            }        }    }复制代码

先看注释1,MultiDexExtractor#load
    List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException {        if (!this.cacheLock.isValid()) {            throw new IllegalStateException("MultiDexExtractor was closed");        } else {            List files;            if (!forceReload && !isModified(context, this.sourceApk, this.sourceCrc, prefsKeyPrefix)) {                try {                    //读缓存的dex                    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);                    //读取缓存的dex失败,多是损坏了,那就从头去解压apk读取,跟else代码块一样                    files = this.performExtractions();                    //保存标志位到sp,下次进来就走if了,不走else                    putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);                }            } else {                //没有缓存,解压apk读取                files = this.performExtractions();                //保存dex信息到sp,下次进来就走if了,不走else                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);            }            Log.i("MultiDex", "load found " + files.size() + " secondary dex files");            return files;        }    }复制代码

查找dex文件,有两个逻辑,有缓存就调用loadExistingExtractions方法,没有缓存或者缓存读取失败就调用performExtractions方法,然后再缓存起来。使用到缓存,那么performExtractions 方法想必应该是很耗时的,分析一下代码:
private List<MultiDexExtractor.ExtractedDex> performExtractions() throws IOException {        //先确定命名格局        String extractedFilePrefix = this.sourceApk.getName() + ".classes";        this.clearDexDir();        List<MultiDexExtractor.ExtractedDex> files = new ArrayList();        ZipFile apk = new ZipFile(this.sourceApk); // apk转为zip格局        try {            int secondaryNumber = 2;            //apk现已是改为zip格局了,解压遍历zip文件,里边是dex文件,            //名字有规律,如classes1.dex,class2.dex            for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {                //文件名:xxx.classes1.zip                String fileName = extractedFilePrefix + secondaryNumber + ".zip";                //创建这个classes1.zip文件                MultiDexExtractor.ExtractedDex extractedFile = new MultiDexExtractor.ExtractedDex(this.dexDir, fileName);                //classes1.zip文件添加到list                files.add(extractedFile);                Log.i("MultiDex", "Extraction is needed for file " + extractedFile);                int numAttempts = 0;                boolean isExtractionSuccessful = false;                while(numAttempts < 3 && !isExtractionSuccessful) {                    ++numAttempts;                    //这个方法是将classes1.dex文件写到紧缩文件classes1.zip里去,最多重试三次                    extract(apk, dexFile, extractedFile, extractedFilePrefix);                 ...                }        //返回dex的紧缩文件列表        return files;    }复制代码

这里的逻辑就是解压apk,遍历出里边的dex文件,例如class1.dex,class2.dex,然后又紧缩成class1.zip,class2.zip...,然后返回zip文件列表。

考虑为何这里要紧缩呢? 后边触及到ClassLoader加载类原理的时分会分析ClassLoader支撑的文件格局。

第一次加载才会执行解压和紧缩过程,第二次进来读取sp中保存的dex信息,直接返回file list,所以第一次启动的时分比较耗时。

dex文件列表找到了,回到上面MultiDex#doInstallation方法的注释2,找到的dex文件列表,然后调用installSecondaryDexes方法进行装置,怎么装置呢?方法点进去看SDK 19 以上的完成
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 {            Field pathListField = MultiDex.findField(loader, "pathList");//1 反射ClassLoader 的 pathList 字段            Object dexPathList = pathListField.get(loader);            ArrayList<IOException> suppressedExceptions = new ArrayList();            // 2 扩展数组            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));        }    }复制代码
  1. 反射ClassLoader 的 pathList 字段
  2. 找到pathList 字段对应的类的makeDexElements 方法
  3. 通过MultiDex.expandFieldArray 这个方法扩展 dexElements 数组,怎么扩展?看下代码:
    private static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {        Field jlrField = findField(instance, fieldName);        Object[] original = (Object[])((Object[])jlrField.get(instance)); //取出本来的dexElements 数组        Object[] combined = (Object[])((Object[])Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length)); //新的数组        System.arraycopy(original, 0, combined, 0, original.length); //本来数组内容拷贝到新的数组        System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); //dex2、dex3...拷贝到新的数组        jlrField.set(instance, combined); //将dexElements 从头赋值为新的数组    }复制代码

就是创建一个新的数组,把本来数组内容(主dex)和要添加的内容(dex2、dex3...)拷贝进去,反射替换本来的dexElements为新的数组,如下图


看起来有点眼熟,Tinker热修复的原理也是通过反射将修复后的dex添加到这个dex数组去,不同的是热修复是添加到数组最前面,而MultiDex是添加到数组后边。这样讲可能还不是很好了解?来看看ClassLoader怎么加载一个类的就理解了~

2.2.4 ClassLoader 加载类原理


不论是 PathClassLoader仍是DexClassLoader,都继承自BaseDexClassLoader,加载类的代码在 BaseDexClassLoader中

4.4 源码

/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java



  1. 构造方法通过传入dex途径,创建了DexPathList。
  2. ClassLoader的findClass方法最终是调用DexPathList 的findClass方法

接着看DexPathList源码 /dalvik/src/main/java/dalvik/system/DexPathList.java



DexPathList里边界说了一个dexElements 数组,findClass方法顶用到,看下



findClass方法逻辑很简略,就是遍历dexElements 数组,拿到里边的DexFile对象,通过DexFile的loadClassBinaryName方法加载一个类。



最终创建Class是通过native方法,就不追下去了,我们有爱好可以看下native层是怎么创建Class对象的。DexFile.cpp

那么问题来了,5.0以下这个dexElements 里边只有主dex(可以认为是一个bug),没有dex2、dex3...,MultiDex是怎么把dex2添加进去呢? 答案就是反射DexPathList的dexElements字段,然后把我们的dex2添加进去,当然,dexElements里边放的是Element对象,我们只有dex2的途径,有必要转换成Element格局才行,所以反射DexPathList里边的makeDexElements 方法,将dex文件转换成Element对象即可。



dex2、dex3...通过makeDexElements方法转换成要新增的Element数组,终究一步就是反射DexPathList的dexElements字段,将本来的Element数组和新增的Element数组合并,然后反射赋值给dexElements变量,终究DexPathList的dexElements变量就包括我们新加的dex在里边了。

makeDexElements方法会判断file类型,上面讲dex提取的时分解压apk得到dex,然后又将dex紧缩成zip,紧缩成zip,就会走到第二个判断里去。细心想一想,其实dex不紧缩成zip,走第一个判断也没啥问题吧,那谷歌的MultiDex为何要将dex紧缩成zip呢?在Android开发高手课中看到张绍文也提到这一点


然后我在反编译头条App的时分,发现头条参考谷歌的MultiDex,自己写了一套,猜想多是优化这个多余的紧缩过程,头条的方案下面会介绍。

2.2.5 原理小结


ClassLoader 加载类原理:

ClassLoader.loadClass -> DexPathList.loadClass -> 遍历dexElements数组 ->DexFile.loadClassBinaryName

通俗点说就是:ClassLoader加载类的时分是通过遍历dex数组,从dex文件里边去加载一个类,加载成功就返回,加载失败则抛出Class Not Found 异常。

MultiDex原理:

在理解ClassLoader加载类原理之后,我们可以通过反射dexElements数组,将新增的dex添加到数组后边,这样就保证ClassLoader加载类的时分可以从新增的dex中加载到方针类,通过分析后最终MultipDex原理图如下:

2.2.6 MultiDex 优化(两种方案)


知道了MultiDex原理之后,可以了解install过程为何耗时,因为触及到解压apk取出dex、紧缩dex、将dex文件通过反射转换成DexFile对象、反射替换数组。

那么MultiDex究竟应该怎么优化呢,放子线程可行吗?
方案1:子线程install(不引荐)

这个方法我们很容易就可以想到,在闪屏页开一个子线程去执行MultiDex.install,然后加载完才跳转到主页。需要留意的是闪屏页的Activity,包括闪屏页中引用到的其它类有必要在主dex中,不然在MultiDex.install之前加载这些不在主dex中的类会报错Class Not Found。这个可以通过gradle配置,如下:
    defaultConfig {        //分包,指定某个类在main dex        multiDexEnabled true        multiDexKeepProguard file('multiDexKeep.pro') // 打包到main dex的这些类的稠浊规制,没特殊需求就给个空文件        multiDexKeepFile file('maindexlist.txt') // 指定哪些类要放到main dex    }复制代码

maindexlist.txt 文件指定哪些类要打包到主dex中,内容格局如下
com/lanshifu/launchtest/SplashActivity.class复制代码

在已有项目顶用这种方式,一顿操作猛如虎之后,编译运转在4.4的机器上,启动闪屏页,加载完准备进入主页直接崩掉了。



报错NoClassDefFoundError,一般都是该类没有在主dex中,要在maindexlist.txt 将配置指定在主dex。 **第三方库中的ContentProvider有必要指定在主dex中,不然也会找不到,为何?**文章最初说过应用的启动流程,ContentProvider 初始化机遇如下图:


ContentProvider初始化太早了,假如不在主dex中,还没启动闪屏页就现已crash了。

所以这种方案的缺点很显着:

MultiDex加载逻辑放在闪屏页的话,闪屏页中引用到的类都要配置在主dex。 ContentProvider有必要在主dex,一些第三方库自带ContentProvider,维护比较麻烦,要一个一个配置。

这时候候就考虑一下,有无其它更好的方案呢?大厂是怎么做的?今天头条肯定要对MultiDex进行优化吧,反编译瞧瞧?



点了一根烟之后,开始偷代码...
MultiDex优化方案2:今天头条方案

今天头条没有加固,反编译后很容易通过要害字查找找到MultidexApplication这个类,



看注释1的d.a(this);这个方法,代码虽然稠浊了,可是方法内部的代码仍是可以看出是干嘛的,继续跟这个方法,为了不影响阅读,我对稠浊做了一些处理,改成正常的方法名。



每一个方法最初都有PatchProxy.isSupport这个if判断,这个是美团Robust热修复生成的代码,今天头条没有自己的热修复框架,没有用Tinker,而是用美团的,想了解关于Robust细节可以参考文末链接。Robust直接跳过,看else代码块即可。

继续看loadMultiDex方法


逻辑如下: 1. 创建暂时文件,作为判断MultiDex是否加载完的条件2. 启动LoadDexActivity去加载MultiDex(LoadDexActivity在单独进程),加载完会删除暂时文件3. 开启while循环,直到暂时文件不存在才跳出循环,进入Application的onCreate

创建暂时文件代码


while循环代码


LoadDexActivity 只有一个加载框,加载完再跳转到闪屏页


dex加载完应该要finish掉其时Activity


依照上面代码分析,今天头条在5.0以下手机初次启动应该是这样:
  1. 打开桌面图标
  2. 显示默许布景
  3. 跳转到加载dex的界面,展示一个loading的加载框几秒钟
  4. 跳转到闪屏页

实践上是否是这样呢,用4.4机器试下?


看起来完全跟猜想的一致,撸几行代码验证应该不难吧?


点了一根烟之后,开始撸代码,最终完成效果如下


效果跟今天头条是一致的,不再重复分析代码了,源码上传到github,感爱好的同学可以参考参考,头条的方案,值得尝试~ github.com/lanshifu/Mu…

再次梳理一下这种方式:
  1. 在主进程Application 的 attachBaseContext 方法中判断假如需要使用MultiDex,则创建一个暂时文件,然后开一个进程(LoadDexActivity),显示Loading,异步执行MultiDex.install 逻辑,执行完就删除暂时文件并finish自己。
  2. 主进程Application 的 attachBaseContext 进入while代码块,定时轮循暂时文件是否被删除,假如被删除,说明MultiDex现已执行完,则跳出循环,继续正常的应用启动流程。
  3. 留意LoadDexActivity 有必要要配置在main dex中。

有些同学可能会问,启动仍是很久啊,冷启动时间有变化吗? 冷启动时间是点拨击桌面图标到第一个Activity显示这段时间。
MultiDex优化总结

方案1:直接在闪屏页开个子线程去执行MultiDex逻辑,MultiDex不影响冷启动速度,可是难维护。

方案2:今天头条的MultiDex优化方案:
  1. 在Application 的attachBaseContext 方法里,启动另外一个进程的LoadDexActivity去异步执行MultiDex逻辑,显示Loading。
  2. 然后主进程Application进入while循环,不断检测MultiDex操作是否完成
  3. MultiDex执行完之后主进程Application继续走,ContentProvider初始化和Application onCreate方法,也就是执行主进程正常的逻辑。

其实应该还有方案3,因为我发现头条并没有直接使用Google的MultiDex,而是参考谷歌的MultiDex,自己写了一套,耗时应该会少一些,我们有爱好可以去研讨一下。

2.3 预创建Activity





这段代码是今天头条里边的,Activity对象预先new出来,

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

头条真是把启动优化做到极致。

2.4 第三方库懒加载


很多第三方开源库都说在Application中进行初始化,十几个开源库都放在Application中,肯定对冷启动会有影响,所以可以考虑按需初始化,例如Glide,可以放在自己封装的图片加载类中,调用到再初始化,其它库也是同理,让Application变得更轻。

2.5 WebView启动优化。


WebView启动优化文章也比较多,这里只说一下大约优化思路。
  1. WebView第一次创建比较耗时,可以预先创建WebView,提前将其内核初始化。
  2. 使用WebView缓存池,用到WebView的当地都从缓存池取,缓存池中没有缓存再创建,留意内存泄漏问题。
  3. 本地预置html和css,WebView创建的时分先预加载本地html,之后通过js脚本填充内容部分。

这一部分可以参考: mp.weixin.qq.com/s/KwvWURD5W…

2.6 数据预加载


这种方式通常为在主页闲暇的时分,将其它页面的数据加载好,保存到内存或数据库,等到打开该页面的时分,判断现已预加载过,直接从内存或数据库读取数据并显示。

2.7 线程优化


线程是程序运转的根本单位,线程的频频创建是耗性能的,所以我们应该都会用线程池。单个cpu状况下,即便是开多个线程,同时也只有一个线程可以工作,所以线程池的巨细要依据cpu个数来确定。

启动优化方式就先介绍到这里,常见的就是这些,其它的可以作为补充。

三、启动耗时分析方法


TraceView性能损耗太大,得到的成果不真实。 Systrace 可以便利追踪要害体系调用的耗时状况,如 Choreographer,可是不支撑应用程序代码的耗时分析。

3.1 Systrace + 函数插桩


结合Systrace 和 函数插桩,就是将如下代码刺进到每一个方法的进口和出口
class Trace{    public static void i(String tag){        android.os.Trace.beginSection(tag);    }    public static void o(){        android.os.Trace.endSection();    }}复制代码

插桩后的代码如下
void test(){    Trace.i("test");    System.out.println("doSomething");    Trace.o();}复制代码

插桩东西参考: github.com/AndroidAdva…

mac下systrace途径在

/Users/{xxx}/Library/Android/sdk/platform-tools/systrace/

编译运转app,执行命令

python2 /Users/lanshifu/Library/Android/sdk/platform-tools/systrace/systrace.py gfx view wm am pm ss dalvik app sched -b 90960 -a com.sample.systrace -o test.log.html




终究按下Enter停止捕获trace信息,在目录下生成陈述test.log.html,直接可以用谷歌阅读器打开查看。

3.2 BlockCanary 也能够检测


BlockCanary 可以监听主线程耗时的方法,将阈值设置低一点,比如200毫秒,这样的话假如一个方法执行时间超过200毫秒,获取仓库信息并告诉开发者。

BlockCanary 原理在之前那篇卡顿优化的文章里边讲过一些,这里就不再重复。

总结


文章有点长,看到这里,是否是忘掉最初讲什么了?总结一下这篇文章主要触及到哪些内容:
  1. 应用启动流程
  2. 闪屏页优化
  3. MultiDex 原理分析
  4. ClassLoader 加载一个类的流程分析
  5. 热修复原理
  6. MultiDex优化: 介绍了两种方式,一种是直接在闪屏页开个子线程去加载dex,难维护,不引荐;一种是今天头条的方案,在单独一个进程加载dex,加载完主进程再继续。
  7. 快速启动Activity的方式:预创建Activity,预加载数据。
  8. 启动时间监控的方式:Systrace+插桩、BlockCanary。

面试问到启动优化问题,不要简略一两句话答复,可以说说自己在实践项目中做了哪些优化,比如Multidex优化,把整个流程,原理说清楚。当然,条件是自己要去实践,了解为何要这样做。

就这样,有问题请留言,更多文章,敬请期待。

作者:蓝师傅_Android

链接:https://juejin.im/post/5d95f4a4f265da5b8f10714b

【免责声明】本文仅代表作者或发布者个人观念,不代表(www.lmnkf.cn)及其所属公司官方发声,对文章观念有疑义请先联络作者或发布者自己修正,若内容触及侵权或违法信息,请先联络发布者或作者删除,若需我们协助请联络平台管理员,Emailcxb5918(本平台不支撑其他投诉反馈渠道,谢谢合作)。若需要学习以上相关常识请到巨推学院观看视频教程,网站地址www.tsllg.cn。

相关内容