一款App,启动速度是最最大的门面,也是用户接触app的第一印象,试想,第一次进入app,就打不开,或者过了N秒才能进入主界面,会有多少用户有耐心继续使用下去。
所以这篇文章会从Android和iOS两个维度,根据启动机制的差异介绍一下启动速度的优化方式。
对于Android的启动分析,有一张很经典的图:
image.png
如果看懂了上述流程,我们可以理出一些优化点:
如果禁用了预览窗口,那么用户到T2才能看到闪屏,之前都是桌面,这个体验是非常不好的,尤其是对于中低端机型,这时候可以加一个与闪屏界面相同的主题。
上面提到了组件注入,以及三方库加载,这个是一个耗时很高的地方,也是一个最大的优化点,合理安排业务,将一些不是马上要用的三方库放到后面再去加载。或者使用懒加载的方式。
但是这里要特别注意一件事,之前遇到了一种情况,讲一个三方库变成了懒加载,但是由于使用的地方太多,一些case未回归,导致个别地方使用的时候未加载成功,所以功能失效了。使用这种方式一定要切记回归case
优化业务,为什么说要优化业务呢,启动的每一毫秒都很重要,很多app一启动,会弹出N多弹窗,常见于各类电商软件,这些都是需要预加载的,都是需要时间的,所以这里需要合理衡量业务,砍掉无用的预加载,放到首页加载完成之后再去弹。
还有一些情况,如需要监听首次启动的各类广播,或者其他类型的监听器,当事件触发回调,可能出现大量代码并发。
当然还有其他类型的情况,都是业务太重导致的,这就需要梳理业务,让启动(重要的是首次启动)变得更清晰一些。
还有根据部分业务需求,可能出现在首页加载大的动画,这类需求,需要根据机型进行降级,低端机型低端处理,高端机型高端处理。
线程优化包括两个方面,一是优化启动的线程数,这主要是减少CPU的压力。另一方面就是减少子线程和主线程交互时的一些block问题,比如虽然我们把耗时操作放到子线程了,但是主线程执行的一些任务可能等待子线程的锁(或者以回调形式执行),这就尴尬了,尽量避免这种逻辑发生。之前在iOS时发生过类似的事情,启动之后需要根据一个变量判断后续执行,但是这个变量是在子线程赋值的,有时候还会出现同时读写的问题。
启动的过程中尽量避免大量的字符串操作,尤其是序列化,反序列化等等,防止出现较多的GC。这时候我们可以尽量复用一些对象,可以频繁赋值,但是不要频繁创建。
在负载过高的时候,I/O 性能下降得会比较快,一定要清楚启动的时候进行了哪些I/O操作,读了什么文件,进行了什么样的网络请求,请求回来什么样的内容,大小是多少等等。如果文件大小内容都是固定的还好,但是可能出现内容不定的情况。如果xx聊天工具,启动的时候需要加载聊天记录,这个文件可能很大,可能很小,需要根据不同情况进行处理。
这个思路是之前在网上看到的,感觉很新颖,也记录下来了。这里要先介绍一下linux读取文件的机制。Linux读取文件会以block为单位,一次性在磁盘上读取4kb的内容,并且放到Page Cache中,这时候我们进行读取,实际不会发生真正的磁盘I/O。但是我们可能只有许多零碎的小文件,都是1K左右的,这时候我们可以考虑把数据进行重排,在同一时间需要用的数据放到一个文件中。只要4k以下,是可以一次读取的,不会浪费磁盘I/O的时间。
这也是一个新颖的概念,我们可以通过重写ClassLoader,看一下启动的过程中类的加载顺序,然后通过FaceBook提供的ReDex进行类重排,将启动过程中用的类往前排。
启动速度.png
iOS的启动分析包含两个部分,一部分是pre-main,一部分是main,这个分类很好理解。
pre-main中有个重要的步骤就是加载动态库,如果动态库过多,可以将动态库进行合并。非系统的动态库,可以支持合并成一个动态库。
减少加载启动后不需要的类,有时候业务冗余,或者代码年久失修,会有很多类其实不用了,但是仍然出现在工程中,再或者可能几个类能够合并成一个类。
+load方法是在main函数之前调用的,遵从先父类后子类,先本类后列类别的顺序调用,+initialize方法是在main函数之后调用的,+initialize方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的.+initialize只调用一次,init可多次调用。所以少在类的+load方法里做事情,尽量把这些事情推迟到+initiailize
这主要是针对在函数外生命的变量,尽量减少这样的声明
优化业务,为什么说要优化业务呢,启动的每一毫秒都很重要,很多app一启动,会弹出N多弹窗,常见于各类电商软件,这些都是需要预加载的,都是需要时间的,所以这里需要合理衡量业务,砍掉无用的预加载,放到首页加载完成之后再去弹。
还有一些情况,如需要监听首次启动的各类广播,或者其他类型的监听器,当事件触发回调,可能出现大量代码并发。
当然还有其他类型的情况,都是业务太重导致的,这就需要梳理业务,让启动(重要的是首次启动)变得更清晰一些。
还有根据部分业务需求,可能出现在首页加载大的动画,这类需求,需要根据机型进行降级,低端机型低端处理,高端机型高端处理。
线程优化包括两个方面,一是优化启动的线程数,这主要是减少CPU的压力。另一方面就是减少子线程和主线程交互时的一些block问题,比如虽然我们把耗时操作放到子线程了,但是主线程执行的一些任务可能等待子线程的锁(或者以回调形式执行),这就尴尬了,尽量避免这种逻辑发生。之前在iOS时发生过类似的事情,启动之后需要根据一个变量判断后续执行,但是这个变量是在子线程赋值的,有时候还会出现同时读写的问题。
之前的代码中,有这样的逻辑,在加载首屏的同时,还要加载第二屏的内容(TabBarViewController的第二个Tab),这个是没有必要的,如果需要预加载,可以将预加载放到首屏加载完成,可交互之后再去执行。
只加载与首屏相关的内容,其他的配置内容可以放到首屏加载完成后去读取。
在进行首屏数据加载的时候,有很多方法可能造成耗时较长,之前遇到过一个这样的事情,首页加载时间较长,使用TimeProfiler看了一下时间,主要集中在首页图片加载中,首页有很多各式各样的图片,甚至于一些动画,这个时候就要看看耗时主要在哪,比如,UIImage存在延迟解压的问题。+imageNamed这个方法会在加载图片之后立刻进行解压,如果图片过大过大,这个在首屏展示的时候肯定会有性能影响,所以可以考虑使用imageWithContentsOfFile进行异步加载。当然还有其它方法的耗时,需要根据情况进行优化。
与Android类似,很多三方库不一定要在初始化的时候进行加载,需要梳理各类三方库的作用域,将不重要的三方库,放到首屏的viewDidAppear中去加载。这里看似简单,但是是需要认真梳理的,需要扣一下所有的三方库的应用场景,延迟加载会有什么样的影响。
iOS优化建议.png
好了,大致就这些内容,里面很多方法,已经在实际工作中投入使用,还有一些,是对网上主流优化方法的整理,也打算进一步的试用,如果您有更好地方式,欢迎给我留言
推荐阅读:江西热线