• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

面试官今日头条启动很快你觉得可能是做了哪些优化

android 搞代码 3年前 (2022-03-01) 43次浏览 已收录 0个评论
文章目录[隐藏]

前言

网上对于启动优化的文章多不胜数,内容千篇一律,大都是列举一些耗时操作,采纳异步加载、懒加载等。

而在面试过程中,对于启动优化的问题,如果只是很外表地答复耗时操作应该放在子线程,显然太过于一般,无奈跟竞争者拉开差距。如何让面试官晓得你的“内功深厚”,那必定是要往原理层面去答复。

本文重点还是关注原理,冷启动优化这个问题能延长到很多原理层面的知识点,本文比拟有意思的中央是通过反编译今日头条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增加进去呢? 答案就是反射DexPathListdexElements字段,而后把咱们的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了。

所以这种计划的毛病很显著:

  1. MultiDex加载逻辑放在闪屏页的话,闪屏页中援用到的类都要配置在主dex。
  2. 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优化,把整个流程,原理说分明。当然,前提是本人要去实际,了解为什么要这样做。

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

相干视频:

【2021最新版】Android studio装置教程+Android(安卓)零基础教程视频(适宜Android 0根底,Android初学入门)_哔哩哔哩_bilibili

Android架构设计原理与实战——Jetpack联合MVP组合利用开发一个优良的APP!_哔哩哔哩_bilibili

Android进阶必学:jetpack架构组件—Navigation_哔哩哔哩_bilibili

Android进阶零碎学习——Jetpack先天优良的基因能够防止数据内存透露_哔哩哔哩_bilibili

本文转自 https://juejin.cn/post/6844903958113157128,如有侵权,请分割删除。


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:面试官今日头条启动很快你觉得可能是做了哪些优化

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址