标签: 性能优化

Android面试题内存&性能篇

Android面试题内存&性能篇,由本人整理汇总,后续将继续推出系列篇,如果喜欢请持续关注和推荐

内存分配

RAM(random access memory)随机存取存储器。说白了就是内存。 一般Java在内存分配时会涉及到以下区域:

寄存器(Registers):速度*快的存储场所,因为寄存器位于处理器内部,我们在程序中无法控制
栈(Stack):存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
堆(Heap):堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器(GC)来管理。
静态域(static field): 静态存储区域就是指在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量
常量池(constant pool):虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。
非RAM存储:硬盘等永久存储空间
堆栈存储特点对比:

栈:当定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆:当堆中的new产生数组和对象超出其作用域后,它们不会被释放,只有在没有引用变量指向它们的时候才变成垃圾,不能再被使用。即使这样,所占内存也不会立即释放,而是等待被垃圾回收器收走。这也是Java比较占内存的原因。
堆栈运行特点对比:

栈:存取速度比堆要快,仅次于寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆:堆是一个运行时数据区,可以动态地分配内存大小,因此存取速度较慢。也正因为这个特点,堆的生存期不必事先告诉编译器,而且Java的垃圾收集器会自动收走这些不再使用的数据。
对象引用类型:

引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。

强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;
弱引用(WeakReference)
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
UI优化

a.合理选择RelativeLayout、LinearLayout、FrameLayout,RelativeLayout会让子View调用2次onMeasure,而且布局相对复杂时,onMeasure相对比较复杂,效率比较低,LinearLayout在weight>0时也会让子View调用2次onMeasure。LinearLayout weight测量分配原则。
b.使用标签<include><merge><ViewStub>
c.减少布局层级,可以通过手机开发者选项>GPU过渡绘制查看,一般层级控制在4层以内,超过5层时需要考虑是否重新排版布局。
d.自定义View时,重写onDraw()方法,不要在该方法中新建对象,否则容易触发GC,导致性能下降
e.使用ListView时需要复用contentView,并使用Holder减少findViewById加载View。
f.去除不必要背景,getWindow().setBackgroundDrawable(null)
g.使用TextView的leftDrawabel/rightDrawable代替ImageView+TextView布局
内存优化

主要为了避免OOM和频繁触发到GC导致性能下降

a.Bitmap.recycle(),Cursor.close,inputStream.close()
b.大量加载Bitmap时,根据View大小加载Bitmap,合理选择inSampleSize,RGB_565编码方式;使用LruCache缓存
c.使用 静态内部类+WeakReference 代替内部类,如Handler、线程、AsyncTask
d.使用线程池管理线程,避免线程的新建
e.使用单例持有Context,需要记得释放,或者使用全局上下文
f.静态集合对象注意释放
g.属性动画造成内存泄露
h.使用webView,在Activity.onDestory需要移除和销毁,webView.removeAllViews()和webView.destory()
备:使用LeakCanary检测内存泄露

响应速度优化

Activity如果5秒之内无法响应屏幕触碰事件和键盘输入事件,就会出现ANR,而BroadcastReceiver如果10秒之内还未执行操作也会出现ANR,Serve20秒会出现ANR 为了避免ANR,可以开启子线程执行耗时操作,但是子线程不能更新UI,因此需要Handler消息机制、AsyncTask、IntentService进行线程通信。

备:出现ANR时,adb pull data/anr/tarces.txt 结合log分析

其他性能优化

a.常量使用static final修饰
b.使用SparseArray代替HashMap
c.使用线程池管理线程
d.ArrayList遍历使用常规for循环,LinkedList使用foreach
e.不要过度使用枚举,枚举占用内存空间比整型大
f.字符串的拼接优先考虑StringBuilder和StringBuffer
g.数据库存储是采用批量插入+事务
Android内存泄露及管理

(1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。 (2)引起内存泄露的原因
(3)内存泄露检测工具—->LeakCanary

内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。

内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光

内存泄露原因:

Handler 引起的内存泄漏。
解决:将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,如果Handler里面需要context的话,可以通过弱引用方式引用外部类
单例模式引起的内存泄漏。
解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏
非静态内部类创建静态实例引起的内存泄漏。
解决:把内部类修改为静态的就可以避免内存泄漏了
非静态匿名内部类引起的内存泄漏。
解决:将匿名内部类设置为静态的。
注册/反注册未成对使用引起的内存泄漏。
注册广播接受器、EventBus等,记得解绑。
资源对象没有关闭引起的内存泄漏。
在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。
集合对象没有及时清理引起的内存泄漏。
通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。
JAVA相关性能优化

1 不用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。

在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:
public static Credit getNewCredit() {
return new Credit();
}
改进后的代码使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路对于数组处理同样很有用。

2 使用非阻塞I/O
版本较低的JDK不支持非阻塞I/O API。为避免I/O阻塞,一些应用采用了创建大量线程的办法(在较好的情况下,会使用一个缓冲池)。这种技术可以在许多必须支持并发I/O流的应用中见 到,如Web服务器、报价和拍卖应用等。然而,创建Java线程需要相当可观的开销。 JDK 1.4引入了非阻塞的I/O库(java.nio)。如果应用要求使用版本较早的JDK,在这里有一个支持非阻塞I/O的软件包。

3 慎用异常
异 常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native) 方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新 的对象。 异常只能用于错误处理,不应该用来控制程序流程。

4 不要重复初始化变量
默认情况下,调用类的构造函数时, Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和 double变可柚贸?.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中 的所有构造函数都会被自动调用。

5 尽量指定类的final修饰符
带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止了人们覆盖length()方法。 另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。

6 尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。

7 乘法和除法
考虑下面的代码: for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; } 用移位操作替代乘法操作可以*大地提高性能。下面是修改后的代码: for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }

8.字符串拼接
不要随意的使用stingA=StringB+StringC的写法,有大量拼接操作的地方用StringBuilder代替

本文完~

【前端面试题】关于性能优化的面试题(附答案)

%title插图%num

随着前端项目不断扩大,浏览器渲染的压力变得越来越重。配置好一点的计算机可以顺利地展现页面;配置低一些的计算机渲染页面的性能就不那么可观了。

性能优化部分的面试题主要考察应试者对网站性能优化的了解。如何做好性能优化,哪些操作会引起性能优化的问题,性能优化指标是什么等,都值得应试者关注。

因为性能优化变得越来越重要,所以很多企业专门建立团队去做性能优化。

1、谈谈你对重构的理解。

网站重构是指在不改变外部行为的前提下,简化结构、添加可读性,且在网站前端保持一致的行为。也就是说,在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。

对于传统的网站来说,重构通常包括以下方面。

  • 把表格( table)布局改为DV+CSS。
  • 使网站前端兼容现代浏览器。
  • 对移动平台进行优化。
  • 针对搜索引擎进行优化。

深层次的网站重构应该考虑以下方面。

  • 减少代码间的耦合
  • 让代码保持弹性。
  • 严格按规范编写代码。
  • 设计可扩展的API。
  • 代替旧的框架、语言(如VB)
  • 增强用户体验。
  • 对速度进行优化。
  • 压缩 JavaScript、CSS、 image等前端资源(通常由服务器来解决)。
  • 优化程序的性能(如数据读写)。
  • 采用CDN来加速资源加载。
  • 优化 JavaScript DOM。
  • 缓存HTTP服务器的文件。

2、如果一个页面上有大量的图片(大型电商网站),网页加载很慢,可以用哪些方法优化这些图片的加载,从而提升用户体验?

对于图片懒加载,可以为页面添加一个滚动条事件,判断图片是否在可视区域内或者即将进入可视区域,优先加载。

如果为幻灯片、相册文件等,可以使用图片预加载技术,对于当前展示图片的前一张图片和后一张图片优先下载。

如果图片为CSS图片,可以使用 CSS Sprite、SVG sprite、 Icon font、Base64等技术。

如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩得特别小的缩略图,以提高用户体验。

如果图片展示区域小于图片的真实大小,则应在服务器端根据业务需要先行进行图片压缩,图片压缩后,图片大小与展示的就一致了。

3、谈谈性能优化问题。

可以在以下层面优化性能。

  • 缓存利用:缓存Ajax,使用CDN、外部 JavaScript和CSS文件缓存,添加 Expires头,在服务器端配置Etag,减少DNS查找等。
  • 请求数量:合并样式和脚本,使用CSS图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载。
  • 请求带宽:压缩文件,开启GZIP 。
  • CSS代码:避免使用CSS表达式、高级选择器、通配选择器。
  • JavaScript代码:用散列表来优化查找,少用全局变量,用 innerHTML代替DOM操作,减少DOM操作次数,优化 JavaScript性能,用 setTimeout避免页面失去响应,缓存DOM节点查找的结果,避免使用with(with会创建自己的作用域,增加作用域链的长度),多个变量声明合并。
  • HTML代码:避免图片和 iFrame等src属性为空。src属性为空,会重新加载当前页面,影响速度和效率,尽量避免在HTML标签中写 Style属性

4、移动端性能如何优化?

优化方式如下。

  • 尽量使用CSS3动画,开启硬件加速。
  • 适当使用 touch事件代替 click事件。
  • 避免使用CSS3渐变阴影效果。
  • 可以用 transform:translateZ(0)来开启硬件加速。
  • 不滥用 Float, Float在渲染时计算量比较大,尽量少使用。
  • 不滥用Web字体,Web字体需要下载、解析、重绘当前页面,尽量少使用。
  • 合理使用requestAnimation Frame动画代替 setTimeout。
  • 合理使用CSS中的属性(CSS3 transitions、CSS3 3D transforms、 Opacity、 Canvas、 WebGL、Video)触发GPU渲染。过度使用会使手机耗电量増加。

5、如何对网站的文件进行优化?

可以进行文件合并、文件压缩使文件*小化;可以使用CDN托管文件,让用户更快速地访问;可以使用多个域名来缓存静态文件。

6、请说出几种缩短页面加载时间的方法。

具体方法如下。

(1)优化图片

(2)选择图像存储格式(比如,GIF提供的颜色较少,可用在一些对颜色要求不高的地方)

(3)优化CSS(压缩、合并CSS)

(4)在网址后加斜杠

(5)为图片标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小。如果图片很多,浏览器需要不断地调整页面。这不但影响速度,而且影响浏览体验。当浏览器知道高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容,从而优化加载时间,提升浏览体验)。

7、哪些方法可以提升网站前端性能?

精灵图合并,减少HTTP请求;压缩HTML、CSS、JavaScript文件;使用CDN托管静态文件;使用 localstorage缓存和 mainfest应用缓存。

8、你知道哪些优化性能的方法?

具体方法如下。

(1)减少HTTP请求次数,控制CSS Sprite、JavaScript与CSS源码、图片的大小,使用网页Gzip、CDN托管、data缓存、图片服务器

(2)通过前端模板 JavaScript和数据,减少由于HTML标签导致的带宽浪费,在前端用变量保存Ajax请求结果,每次操作本地变量时,不用请求,减少请求次数。

(3)用 innerhTML代替DOM操作,减少DOM操作次数,优化 JavaScript性能。

(4)当需要设置的样式很多时,设置 className而不是直接操作 Style。

(5)少用全局变量,缓存DOM节点查找的结果,减少I/O读取操作

(6)避免使用CSS表达式,它又称动态属性,

(7)预加载图片,将样式表放在顶部,将脚本放在底部,加上时间戳。

(8)避免在页面的主体布局中使用表,表要在其中的内容完全下载之后才会显示出来,显示的速度比DIV+CSS布局慢。

9、列举你知道的Web性能优化方法。

具体优化方法如下。

(1)压缩源码和图片( JavaScript采用混淆压缩,CSS进行普通压缩,JPG图片根据具体质量压缩为50%~70%,把PNG图片从24色压缩成8色以去掉一些PNG格式信息等)。

(2)选择合适的图片格式(颜色数多用JPG格式,而很少使用PNG格式,如果能通过服务器端判断浏览器支持WebP就用WebP或SVG格式)。

(3)合并静态资源(减少HTTP请求)

(4)把多个CSS合并为一个CSS,把图片组合成雪碧图。

(5)开启服务器端的Gzip压缩(对文本资源非常有效)。

(6)使用CDN(对公开库共享缓存)。

(7)延长静态资源缓存时间。

(8)把CSS放在页面头部把 JavaScript代码放在页面底部(这样避免阻塞页面渲染而使页面出现长时间的空白)

10、平时你是如何对代码进行性能优化的?

利用性能分析工具监测性能,包括静态 Analyze工具和运行时的 Profile工具(在Xcode工具栏中依次单击 Product→ Profile项可以启动)。

比如测试程序的运行时间,当单击 Time Profiler项时,应用程序开始运行,这就能获取到运行整个应用程序所消耗时间的分布和百分比。为了保证数据分析在同一使用场景下的真实性,一定要使用真机,因为此时模拟器在Mac上运行,而Mac上的CPU往往比iOS设备要快。

11、针对CSS,如何优化性能?

具体优化方法如下。

(1)正确使用 display属性, display属性会影响页面的渲染,因此要注意以下几方面。

display:inline后不应该再使用 width、 height、 margin、 padding和float 。

display:inline- block后不应该再使用 float。

display:block后不应该再使用 vertical-align。

display:table-*后不应该再使用 margin或者float。

(2)不滥用 float。

(3)不声明过多的font-size。

(4)当值为0时不需要单位。

(5)标准化各种浏览器前缀,并注意以下几方面。

  • 浏览器无前缀应放在*后。
  • CSS动画只用( -webkit-无前缀)两种即可。
  • 其他前缀包括 -webkit-、-moz-、-ms-、无前缀( Opera浏览器改用 blink内核,所以-0-被淘汰)

(6)避免让选择符看起来像是正则表达式。高级选择器不容易读懂,执行时间也长。

(7)尽量使用id、 class选择器设置样式(避免使用 style属性设置行内样式)

(8)尽量使用CSS3动画。

(9)减少重绘和回流。

12、针对HTML,如何优化性能?

具体方法如下。

(1)对于资源加载,按需加载和异步加载

(2)首次加载的资源不超过1024KB,即越小越好。

(3)压缩HTML、CSS、 JavaScript文件。

(4)减少DOM节点。

(5)避免空src(空src在部分浏览器中会导致无效请求)。

(6)避免30*、40*、50*请求错误

(7)添加 Favicon.ico,如果没有设置图标ico,则默认的图标会导致发送一个404或者500请求。

13、针对 JavaScript,如何优化性能?

具体方法如下。

(1)缓存DOM的选择和计算。

(2)尽量使用事件委托模式,避免批量绑定事件。

(3)使用 touchstart、 touchend代替 click。

(4)合理使用 requestAnimationFrame动画代替 setTimeOut。

(5)适当使用 canvas动画。

(6)尽量避免在高频事件(如 TouchMove、 Scroll事件)中修改视图,这会导致多次渲染。

14、如何优化服务器端?

具体方法如下。

(1)启用Gzip压缩。

(2)延长资源缓存时间,合理设置资源的过期时间,对于一些长期不更新的静态资源过期时间设置得长一些。

(3)减少 cookie头信息的大小,头信息越大,资源传输速度越慢。

(4)图片或者CSS、 JavaScript文件均可使用CDN来加速。

15、如何优化服务器端的接口?

具体方法如下。

(1)接口合并:如果一个页面需要请求两部分以上的数据接口,则建议合并成一个以减少HTTP请求数。

(2)减少数据量:去掉接口返回的数据中不需要的数据。

(3)缓存数据:首次加载请求后,缓存数据;对于非首次请求,优先使用上次请求的数据,这样可以提升非首次请求的响应速度。

16、如何优化脚本的执行?

脚本处理不当会阻塞页面加载、渲染,因此在使用时需注意。

(1)把CSS写在页面头部,把 JavaScript程序写在页面尾部或异步操作中。

(2)避免图片和 iFrame等的空src,空src会重新加载当前页面,影响速度和效率。

(3)尽量避免重设图片大小。重设图片大小是指在页面、CSS、 JavaScript文件等中多次重置图片大小,多次重设图片大小会引发图片的多次重绘,影响性能

(4)图片尽量避免使用 DataURL。DataURL图片没有使用图片的压缩算法,文件会变大,并且要在解码后再渲染,加载慢,耗时长。

17、如何优化渲染?

具体方法如下。

通过HTML设置 Viewport元标签, Viewport可以加速页面的渲染,如以下代码所示。

<meta name="viewport" content="width=device=width,initial-scale=1">

(2)减少DOM节点数量,DOM节点太多会影响页面的渲染,应尽量减少DOM节点数量。

(3)尽量使用CSS3动画,合理使用 requestAnimationFrame动画代替 setTimeout,适当使用 canvas动画(5个元素以内使用CSS动画,5个元素以上使用 canvas动画(iOS 8中可使用 webGL))。

(4)对于高频事件优化 Touchmove, Scroll事件可导致多次渲染。

使用 requestAnimationFrame监听帧变化,以便在正确的时间进行渲染,增加响应变化的时间间隔,减少重绘次数。

使用节流模式(基于操作节流,或者基于时间节流),减少触发次数。

(5)提升GPU的速度,用CSS中的属性(CSS3 transitions、CSS3 3D transforms、 Opacity、 Canvas、 WebGL、Video)来触发GPU渲染.

18、如何设置DNS缓存?

在浏览器地址栏中输入URL以后,浏览器首先要查询域名( hostname)对应服务器的IP地址,一般需要耗费20~120ms的时间。

DNS查询完成之前,浏览器无法识别服务器IP,因此不下载任何数据。基于性能考虑,ISP运营商、局域网路由、操作系统、客户端(浏览器)均会有相应的DNS缓存机制。

(1)正IE缓存30min,可以通过注册表中 DnsCacheTimeout项设置。

(2) Firefox混存1 min,通过 network.dnsCacheExpiration配置。

(3)在 Chrome中通过依次单击“设置”→“选项”→“高级选项”,并勾选“用预提取DNS提高网页载入速度”选项来配置缓存时间。

19、什么时候会出现资源访问失败?

开发过程中,发现很多开发者没有设置图标,而服务器端根目录也没有存放默认的 Favicon.ico,从而导致请求404出现。通常在App的 webview里打开 Favicon.ico,不会加载这个 Favicon.ico,但是很多页面能够分享。

如果用户在浏览器中打开 Favicon. ico,就会调取失败,一般尽量保证该图标默认存在,文件尽可能小,并设置一个较长的缓存过期时间。另外,应及时清理缓存过期导致岀现请求失败的资源。

20、jQuery性能优化如何做?

优化方法如下。

(1)使用*新版本的 jQuery类库。

JQuery类库每一个新的版本都会对上一个版本进行Bug修复和一些优化,同时也会包含一些创新,所以建议使用*新版本的 jQuery类库提高性能。不过需要注意的是,在更换版本之后,不要忘记测试代码,毕竟有时候不是完全向后兼容的。

(2)使用合适的选择器。

jQuery提供非常丰富的选择器,选择器是开发人员*常使用的功能,但是使用不同选择器也会带来性能问题。建议使用简凖选择器,如i选择器、类选择器,不要将i选择器嵌套等。

(3)以数组方式使用 jQuery对象。

使用 jQuery选择器获取的结果是一个 jQuery对象。然而, jQuery类库会让你感觉正在使用一个定义了索引和长度的数组。在性能方面,建议使用简单的for或者 while循环来处理,而不是$. each(),这样能使代码更快。

(4)每一个 JavaScript事件(例如 click、 mouseover等)都会冒泡到父级节点。当需要给多个元素绑定相同的回调函数时,建议使用事件委托模式。

(5)使用join( )来拼接字符串。

使用 join( )拼接长字符串,而不要使用“+”拼接字符串,这有助于性能优化,特别是处理长字符串的时候。

(6)合理利用HTML5中的data属性。

HTML5中的data属性有助于插入数据,特别是前、后端的数据交换;jQuery的 data( )方法能够有效地利用HTML5的属性来自动获取数据。

21、哪些方法能提升移动端CSS3动画体验?

(1)尽可能多地利用硬件能力,如使用3D变形来开启GPU加速,例如以下代码。

  1. -webkit-transform: translate 3d(0, 0, 0);
  2. -moz-transform : translate3d(0,0, 0);
  3. -ms-transform : translate 3d(0,0,0);
  4. transform: translate3d(0,0,0);

一个元素通过 translate3d右移500X的动画流畅度会明显优于使用left属性实现的动画移动,原因是CSS动画属性会触发整个页面重排、重绘、重组。paint通常是*耗性能的,尽可能避免使用触发 paint的CSS动画属性。

如果动画执行过程中有闪烁(通常发生在动画开始的时候),可以通过如下方式处理。

  1. -webkit-backface-visibility:hidden;
  2. -moz-backface-visibility:hidden;
  3. -ms-backface-visibility:hidden ;
  4. backface-visibility:hidden;
  5. -webkit-perspective:1000;
  6. -moz-perspective:1000;
  7. -ms-perspective:1000;
  8. perspective:1000;

(2)尽可能少使用box- shadows和 gradients,它们往往严重影响页面的性能,尤其是在一个元素中同时都使用时。

(3)尽可能让动画元素脱离文档流,以减少重排,如以下代码所示。

  1. position:fixed
  2. position:absolute;

本文完〜

iOS-性能优化深入探究

iOS-性能优化深入探究

上图是几种时间复杂度的关系,性能优化一定程度上是为了降低程序执行效率减低时间复杂度。 如下是几种时间复杂度的实例:

O(1)
  1. return array[index] == value;
  2. 复制代码
O(n)
  1. for (int i = 0, i < n, i++) {
  2. if (array[i] == value)
  3. return YES;
  4. }
  5. 复制代码
O(n2)
  1. /// 找数组中重复的值
  2. for (int i = 0, i < n, i++) {
  3. for (int j = 0, j < n, j++) {
  4. if (i != j && array[i] == array[j]) {
  5. return YES;
  6. }
  7. }
  8. }
  9. 复制代码

1. OC 中几种常见集合对象接口方法的时间复杂度

NSArray / NSMutableArray

  • containsObject; indexOfObject; removeObject 均会遍历元素查看是否匹配,复杂度等于或小于 O(n)
  • objectAtIndex;firstObject;lastObject; addObject; removeLastObject 这些只针对栈顶,栈底的操作时间复杂度都是 O(1)
  • indexOfObject:inSortedRange:options:usingComparator: 使用的是二分查找,时间复杂度是O(log n)

NSSet / NSMutableSet / NSCountedSet

集合类型是无序并且没有重复元素的。这样可以使用hash table 进行快速的操作。比如,addObject; removeObject; containsObject 都是按照 O(1) 来的。需要注意的是将数组转成Set 时,会将重复元素合并为一个,并且失去排序。

NSDictionary / NSMutableDictionary

和 Set 一样都可以使用 hash table ,多了键值对应。添加和删除元素都是 O(1)。

containsObject 方法在数组和Set里的不同的实现

containsObject 在数组中的实现
  1. ///GUNSTEP NSArray indexOfObject: 方法的实现
  2. – (BOOL)containsObject:(id)anObject {
  3. return [self indexOfObject:anObject] != NSNotFound;
  4. }
  5. – (NSUInteger) indexOfObject: (id)anObject
  6. {
  7. unsigned c = [self count];
  8. if (c > 0 && anObject != nil)
  9. {
  10. unsigned i;
  11. IMP get = [self methodForSelector: oaiSel];
  12. BOOL (*eq)(id, SEL, id)
  13. = (BOOL (*)(id, SEL, id))[anObject methodForSelector: eqSel];
  14. for (i = 0; i < c; i++)
  15. if ((*eq)(anObject, eqSel, (*get)(self, oaiSel, i)) == YES)
  16. return i;
  17. }
  18. return NSNotFound;
  19. }
  20. 复制代码
containsObject 在 Set 里的实现:
  1. – (BOOL) containsObject: (id)anObject
  2. {
  3. return (([self member: anObject]) ? YES : NO);
  4. }
  5. //在 GSSet,m 里有对 member 的实现
  6. – (id) member: (id)anObject
  7. {
  8. if (anObject != nil)
  9. {
  10. GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)anObject);
  11. if (node != 0)
  12. {
  13. return node->key.obj;
  14. }
  15. }
  16. return nil;
  17. }
  18. 复制代码
在数组中会遍历所有元素查找到结果后返回,在Set中查找元素是通过键值的方式从map映射表中取出,因为S儿童里的元素是唯一的,所以可以hash元素对象作为key达到快速查找的目的。

2. 使用GCD进行性能优化

可以通过GCD提供的方法将一些耗时操作放到非主线程进行,使得App 能够运行的更加流畅,响应更快,但是使用GCD 时需要注意避免可能引起的线程爆炸和死锁的情况。在非主线程处理任务也不是万能的,如果一个处理需要消耗大量内存或者大量CPU操作,GCD也不合适,需要将大任务拆分成不同的阶段任务分时间进行处理。

避免线程爆炸的方法:

  • 使用串行队列
  • 控制 NSOperationQueue 的并发数 – NSOperationQueue.maxConcurrentOperationCount

举个会造成线程爆炸和死锁的例子:

  1. for (int i = 0, i < 999; i++) {
  2. dispatch_async(q,^{…});
  3. }
  4. dispatch_barrier_sync(q,^{…});
  5. 复制代码

如何避免上述的的线程爆炸和死锁呢? 首先使用 dispatch_apply

  1. dispatch_apply(999,q,^(size_t i){…});
  2. 复制代码

或者使用 dispatch_semaphore

  1. #define CONCURRENT_TASKS 4
  2. dispatch_queue_t q = dispatch_queue_create(“com.qiuxuewei.gcd”, nil);
  3. dispatch_semaphore_t sema = dispatch_semaphore_create(CONCURRENT_TASKS);
  4. for (int i = 0; i < 999; i++) {
  5. dispatch_async(q, ^{
  6. dispatch_semaphore_signal(sema);
  7. });
  8. dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  9. }
  10. 复制代码

3. I/O 性能优化

I/O 操作是性能消耗大户,任何的I/O操作都会使低功耗状态被打破。所以减少 I/O 操作次数是性能优化关键。如下是优化的一些方法:

  • 将零碎的内容作为一个整体进行写入
  • 使用合适的 I/O 操作 API
  • 使用合适的线程
  • 使用 NSCache 做缓存减少 I/O 次数

NSCache

为何使用 NSCache 而不适应 NSMutableDictionary 呢?相交字典 NSCache 有以下优点:

  • 自动清理系统所占内存(在接收到内存警告⚠️时)
  • NSCache 是线程安全的
  • - (void)cache:(NSCache *)cache willEvictObject:(id)obj; 缓存对象在即将被清理时回调。
  • evictsObjectWithDiscardedContent 可以控制是否可被清理。

SDWebImage 在设置图片时就使用 NSCache 进行了性能优化:

  1. – (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
  2. return [self.memCache objectForKey:key];
  3. }
  4. – (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
  5. // 检查 NSCache 里是否有
  6. UIImage *image = [self imageFromMemoryCacheForKey:key];
  7. if (image) {
  8. return image;
  9. }
  10. // 从磁盘里读
  11. UIImage *diskImage = [self diskImageForKey:key];
  12. if (diskImage && self.shouldCacheImagesInMemory) {
  13. NSUInteger cost = SDCacheCostForImage(diskImage);
  14. [self.memCache setObject:diskImage forKey:key cost:cost];
  15. }
  16. return diskImage;
  17. }
  18. 复制代码

利用 NSCache 自动释放内存的特点将图片放到 NSCache 里,这样在内存警告时会自动清理掉不常用的图片,在读取 Cache 里内容时,如果没有被清理直接返回图片数据,清理了会执行 I/O 从磁盘中读取图片,通过这种方式减少磁盘操作,空间也会更加有效的控制释放。

4. 控制 App 的唤醒次数

通知,Voip, 定位,蓝牙 等都会使设备从 Standby 状态唤起。唤起这个过程会有比较大的消耗。应该避免频繁发生。 以 定位 API 举例:

连续的位置更新

[locationManager startUpdatingLocation] 这个方法会使设备一直处于活跃状态。

延时有效定位

[locationManager allowDeferredLocationUpdatesUntilTraveled:<#(CLLocationDistance)#> timeout:<#(NSTimeInterval)#>] 高效节能的定位方式,数据会缓存在位置硬件上。适合跑步应用。

重大位置变化

[locationManager startMonitoringSignificantLocationChanges] 会更节能,对于那些只有在位置有很大变化的时候才需要回调的应用需要采用这种方式,比如天气应用。

区域监测

[locationManager startMonitoringForRegion:<#(nonnull CLRegion *)#>] 也是一种节能的定位方式,比如在博物馆内按照不同区域监测展示不同信息之类的应用。

频繁定位
  1. // start monitoring location
  2. [locationManager startUpdatingLocation]
  3. // Stop monitoring when no longer needed
  4. [locationManager stopUpdatingLocation]
  5. 复制代码

不要轻易使用 startUpdatingLocation() 除非万不得已,尽快的使用 stopUpdatingLocation() 来结束定位还用户一个节能设备。

5. 预防性能问题

坚持几个编码原则:

  • 优化计算的复杂度从而减少CPU的使用
  • 在应用响应交互的时候停止没有必要的任务处理
  • 设置合适的 Qos
  • 将定时器任务合并,让CPU更多时候处于 idle 状态

6. 性能优化技巧篇

1. 复用机制

在 UICollectionView 和 UITableView 会使用到 代码复用的机制,在所展示的item数量超过屏幕所容纳的范围时,只创建少量的条目(通常是屏幕*大容纳量 + 1),通过复用来展示所有数据。这种机制不会为每一条数据都创建 Cell .增强效率和交互流畅性。 在iOS6以后,不仅可以复用cell,也可以复用每个section 的 header 和 footer。 在复用UITableView 会用到的 API:

  1. // 复用 Cell:
  2. – [UITableView dequeueReusableCellWithIdentifier:];
  3. – [UITableView registerNib:forCellReuseIdentifier:];
  4. – [UITableView registerClass:forCellReuseIdentifier:];
  5. – [UITableView dequeueReusableCellWithIdentifier:forIndexPath:];
  6. // 复用 Section 的 Header/Footer:
  7. – [UITableView registerNib:forHeaderFooterViewReuseIdentifier:];
  8. – [UITableView registerClass:forHeaderFooterViewReuseIdentifier:];
  9. – [UITableView dequeueReusableHeaderFooterViewWithIdentifier:];
  10. 复制代码

在使用代码复用需要注意在设置Cell 属性是,条件判断需要覆盖所有可能,避免因为复用导致数据错误的问题。例如在 cellForRowAtIndexPath: 方法内部:

  1. if (indexPath %2 == 0) {
  2. cell.backgroundColor = [UIColor redColor];
  3. }else{
  4. cell.backgroundColor = [UIColor clearColor];
  5. }
  6. 复制代码

2. 设置View为不透明

UIView 又一个 opaque 属性, 在不需要透明效果的时候,应该尽量设置它为 YES, 可以提高绘图效率。 在静态视图作用可能不明显,但在 UITableVeiw 或 UICollectionView 这种滚动 的 Scroll View 或是一个复杂动画中,透明效果对程序性能有较大的影响!

3. 避免使用臃肿的 Xib 文件

当加载一个 Xib 时,它所有的内容都会被加载,如歌这个 Xib 中有的View 你不会马上用到,加载就是浪费资源。而加载 StoryBoard 时,并不会把所有的ViewController 都加载,只会按需加载。

4. 不要阻塞主线程

UIKit 会把它所有的工作放在主线程执行,比如:绘制界面,管理手势,响应输入等。当把所有代码逻辑都放在主线程时,有可能因为耗时太长而卡住主线程造成程序无法响应,流畅性差等问题。所以一些 I/O 操作,网络数据解析都需要异步在非主线程处理。

5. 使用尺寸匹配的UIImage

当从 App bundle 中加载图片到 UIImageView 时,*好确保图片的尺寸和 UIImageView 相对应。否则会使UIImageView 对图片进行拉伸,这样会影响性能。如果图片时从网络加载,需要手动进行 scale。在UIImageView 中使用resize 后的图片

6. 选择合适的容器

在使用 NSArray / NSDictionary / NSSet 时,了解他们的特点便于在合适的时机选择他们。

  • Array:数组。有序的,通过 index 查找很快,通过 value 查找很慢,插入和删除较慢。
  • Dictionary:字典。存储键值对,通过键查找很快。
  • Set:集合。无序的,通过 value 查找很快,插入和删除较快。

7. 启用 GZIP 数据压缩

在网络请求的数据量较大时,可以将数据进行压缩再进行传输。可以降低延迟,缩短网络交互时间。

8. 懒加载视图 / 视图隐藏

展现视图的两种形式一种是懒加载,当用到的时候去创建并展现给用户,另外一种提前分配内存创建出视图,不用的时候将其隐藏,等用到的时候将其透明度变为1,两种方案各有利弊。懒加载更合理的使用内存,视图隐藏让视图的展现更迅速。在选择时需要权衡两者利弊做出*优选择。

9. 缓存

开发需要秉承一个原则,对于一些更新频率低,访问频率高的内容进行缓存,例如:

  • 服务器响应数据
  • 图片
  • 计算值 (UITableView 的 row height)

10. 处理 Memory Warning

处理 Memory Warning 的几种方式:

  • 在 AppDelegate 中实现 - [AppDelegate applicationDidReceiveMemoryWarning:] 代理方法。
  • 在 UIViewController 中重载 didReceiveMemoryWarning 方法。
  • 监听 UIApplicationDidReceiveMemoryWarningNotification 通知。

当通过这些方式监听到内存警告时,你需要马上释放掉不需要的内存从而避免程序被系统杀掉。

比如,在一个 UIViewController 中,你可以清除那些当前不显示的 View,同时可以清除这些 View 对应的内存中的数据,而有图片缓存机制的话也可以在这时候释放掉不显示在屏幕上的图片资源。

但是需要注意的是,你这时清除的数据,必须是可以在重新获取到的,否则可能因为必要数据为空,造成程序出错。在开发的时候,可以使用 iOS Simulator 的 Simulate memory warning 的功能来测试你处理内存警告的代码。

11. 复用高开销对象

高开销对象,顾名思义就是初始化很耗性能的对象。比如:NSDateFormatter , NSCalendar .为了避免频繁创建,我们可以使用一个全局单例强引用着这个对象,保证整个App 的生命周期只被初始化一次。

  1. // no property is required anymore. The following code goes inside the implementation (.m)
  2. – (NSDateFormatter *)dateFormatter {
  3. static NSDateFormatter *dateFormatter;
  4. static dispatch_once_t onceToken;
  5. dispatch_once(&onceToken, ^{
  6. dateFormatter = [[NSDateFormatter alloc] init];
  7. [dateFormatter setDateFormat:@”yyyy-MM-dd a HH:mm:ss EEEE”];
  8. });
  9. return dateFormatter;
  10. }
  11. 复制代码

设置 NSDateFormatter 的 date format 跟创建一个新的 NSDateFormatter 对象一样慢,因此当你的程序中要用到多种格式的 date format,而每种又会用到多次的时候,你可以尝试为每种 date format 创建一个可复用的 NSDateFormatter 对象来提供程序的性能。

12. 选择正确的网络返回数据格式

通常用到的有两种: JSON 和 XML。 JSON 优点:

  • 能够更快的被解析
  • 在承载相同数据时,体积比XML更小,传输的数据量更小。

缺点:

  • 需要整个JSON数据全部加载完成后才能开始解析

而XML的优缺点恰好相反。解析数据不需要全部读取完才解析,可以变加载边解析,这样在处理大数据集时可以有效提高性能。 选择哪种格式取决于应用场景。

13. 合理设置背景图片

为一个View 设置背景图,我们想到的方案有两种

  • 为视图加一个 UIImageView 设置 UIImage 作为背景
  • 通过 [UIColor colorWithPatternImage:<#(nonnull UIImage *)#>] 将一张图转化为 UIColor, 直接为 View 设置 backgroundColor。

两种方案各有优缺点:若使用一个全尺寸图片作为背景图使用 UIImageView 会节省内存。 当你计划采用一个小块的模板样式图片,就像贴瓷砖那样来重复填充整个背景时,你应该用 [UIColor colorWithPatternImage:<#(nonnull UIImage *)#>] 这个方法,因为这时它能够绘制的更快,并且不会用到太多的内存。

14. 减少离屏渲染

离屏渲染:GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。 离屏渲染需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动作。

设置如下属性均会造成离屏渲染:

  • shouldRasterize(光栅化)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等
  • 渐变

例如给一个View设置阴影,通常我们会使用这种方式:

  1. imageView.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);
  2. imageView.layer.shadowRadius = 5.0f;
  3. imageView.layer.shadowOpacity = 0.6;
  4. 复制代码

这种方式会触发离屏渲染,造成不必要的内存开销,我们完全可以使用如下方式代替:

  1. imageView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:CGRectMake(imageView.bounds.origin.x+5, imageView.bounds.origin.y+5, imageView.bounds.size.width, imageView.bounds.size.height)] CGPath];
  2. imageView.layer.shadowOpacity = 0.6;
  3. 复制代码

不会造成离屏渲染。

15. 光栅化

CALayer 有一个属性是 shouldRasterize 通过设置这个属性为 YES 可以将图层绘制到一个屏幕外的图像,然后这个图像将会被缓存起来并绘制到实际图层的 contents 和子图层,如果很很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧来更加高效。但是光栅化原始图像需要时间,而且会消耗额外的内存。

  1. cell.layer.shouldRasterize = YES;
  2. cell.layer.rasterizationScale = [[UIScreen mainScreen] scale];
  3. 复制代码

使用光栅化的一个前提是视图不会频繁变化,若一个频繁变化的视图,例如 排版多变,高度不同的 Cell, 光栅化的意义就不大了,反而造成必要的内存损耗。

16. 优化 UITableView

  • 通过正确的设置 reuseIdentifier 来重用 Cell。
  • 尽量减少不必要的透明 View。
  • 尽量避免渐变效果、图片拉伸和离屏渲染。
  • 当不同的行的高度不一样时,尽量缓存它们的高度值。
  • 如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
  • 使用 shadowPath 来设置阴影效果。
  • 尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
  • 尽量优化 – [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。
  • 选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
  • 对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。

17.选择合适数据存储方式

iOS 中数据存储方案有以下几种:

  • NSUserDefaults。只适合用来存小数据。
  • XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
  • 使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
  • 使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
  • 使用 CoreData。 Apple 提供的对于SQLite 的封装,性能不如使用原生 SQLite, 不推荐使用。

18. 减少应用启动时间

在启动时的一些网络配置,数据库配置,数据解析的工作放在异步线程进行。

19. 使用 Autorelease Pool

当需要在代码中创建许多临时对象时,你会发现内存消耗激增直到这些对象被释放,一个问题是这些内存只会到 UIKit 销毁了它对应的 Autorelease Pool 后才会被释放,这就意味着这些内存不必要地会空占一些时间。这时候就是我们显式的使用 Autorelease Pool 的时候了,一个示例如下:

  1. //一个很大数组
  2. NSArray *urls = <# An array of file URLs #>;
  3. for (NSURL *url in urls) {
  4. @autoreleasepool {
  5. NSError *error;
  6. NSString *fileContents = [NSString stringWithContentsOfURL:url
  7. encoding:NSUTF8StringEncoding error:&error];
  8. /* Process the string, creating and autoreleasing more objects. */
  9. }
  10. }
  11. 复制代码

添加 Autorelease Pool 会在每一次循环中释放掉临时对象,提高性能。

20. 合理选择 imageNamed 和 imageWithContentsOfFile

  • imageNamed 会对图片进行缓存,适合多次使用某张图片
  • imageWithContentsOfFile 从bundle中加载图片文件,不会进行缓存,适用于加载一张较大的并且只使用一次的图片,例如引导图等

今年的 WWDC 2018 Apple 向我们推荐了一种性能比较高的大图加载方案:

  1. func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
  2. let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
  3. // 其他场景可以用createwithdata (data并未decode,所占内存没那么大),
  4. let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
  5. let maxDimension = max(pointSize.width, pointSize.height) * scale
  6. let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
  7. kCGImageSourceShouldCacheImmediately : true ,
  8. kCGImageSourceCreateThumbnailWithTransform : true,
  9. kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
  10. let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
  11. return UIImage(cgImage: downsampleImage)
  12. }
  13. 作者:知识小集
  14. 链接:https://juejin.im/post/5b396fece51d4558a3055131
  15. 来源:掘金
  16. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  17. 复制代码

详细关于两者的分析可参照笔者的另外一篇博客:iOS-UIImage imageWithContentsOfFile 和 imageName 对比

21. 合理进行线程分配

GCD 很轻易的可以开辟一个异步线程(不会100%开辟新线程),若不加以控制,会导致开辟的子线程越来越多浪费内存。并且在多线程情况下因为网络时序会造成数据处理错乱,所以可以:

  • UI 操作和 DataSource 操作在主线程
  • DB 操作,日志记录,网络回调在各自固定线程
  • 不同业务,通过使用队列保持数据一致性。

22. 预处理和延时加载

预处理:初次展示需要消耗大量内存的数据需提前在后台线程处理完毕,需要时将处理好的数据进行展现 延时加载:提前加载下级界面的数据内容。举个栗子:类似抖音视频滑动,在播放当前视频的时候就提前将下个视频的数据加载好,等滑到下个视频时直接进行展示!

23. 在合适的时机使用 CALayer 替代 UIView

若视图无需和用户交互,类似绘制线条,单纯展示一张图片,可以将图片对象赋值给 layer 的 content 属性,以提高性能。 但是不能滥用,否则会造成代码难以维护的恶果。

以上。

Android性能优化之启动加速

一、前言

随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关,从本篇文章开始,我将开启一个Android应用性能优化的专题,从理论到实战,从入门到深挖,手把手将性能优化实践到项目中,欢迎持续关注!

那么*篇文章我就从应用的启动优化开始,根据实际案例,打造闪电般的App启动速度。

二、初识启动加速

来看一下Google官方文档《Launch-Time Performance》对应用启动优化的概述;

应用的启动分为冷启动、热启动、温启动,而启动*慢、挑战*大的就是冷启动:系统和App本身都有更多的工作要从头开始!
应用在冷启动之前,要执行三个任务:

  1. 加载启动App;
  2. App启动之后立即展示出一个空白的Window;
  3. 创建App的进程;

而这三个任务执行完毕之后会马上执行以下任务:

  1. 创建App对象;
  2. 启动Main Thread;
  3. 创建启动的Activity对象;
  4. 加载View;
  5. 布置屏幕;
  6. 进行*次绘制;

而一旦App进程完成了*次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。

%title插图%num

应用冷启动流程图

作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程。

同样,Google也给出了启动加速的方向:

  1. 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
  2. 避免在启动时做密集沉重的初始化(Heavy app initialization);
  3. 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。

备注:方向1属于治标不治本,只是表面上快;方向2、3可以真实的加快启动速度。
接下来我们就在项目中实际应用。

三、启动加速之主题切换

按照官方文档的说明:使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。
Layout XML file:

        %title插图%num

资源文件配置

Manifest file:

%title插图%num

Manifest文件中

%title插图%num

Activity中

这样在启动的时候,会先展示一个界面,这个界面就是Manifest中设置的Style,等Activity加载完毕后,再去加载Activity的界面,而在Activity的界面中,我们将主题重新设置为正常的主题,从而产生一种快的感觉。不过如上文总结这种方式其实并没有真正的加速启动过程,而是通过交互体验来优化了展示的效果。
备注:截图同样来自官方文档《Launch-Time Performance》。

四、启动加速之Avoid Heavy App Initialization

通过代码分析我们可以得到App启动的业务工作流程图:

%title插图%num

App冷启动业务工作流程图

这一章节我们重点关注初始化的部分:在Application以及首屏Activity中我们主要做了:

  • MultiDex以及Tinker的初始化,*先执行;关于MultiDex的优化本文不再赘述,参考我之前Multidex的系列文章。
  • Application中主要做了各种三方组件的初始化;

项目中除听云之外其余所有三方组件都抢占先机,在Application主线程初始化。这样的初始化方式肯定是过重的:

  • 考虑异步初始化三方组件,不阻塞主线程;
  • 延迟部分三方组件的初始化;实际上我们粗粒度的把所有三方组件都放到异步任务里,可能会出现WorkThread中尚未初始化完毕但MainThread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化;
  • 而如何开启WorkThread同样也有讲究,这个话题在下文详谈。

项目修改:

  1. 将友盟、Bugly、听云、GrowingIO、BlockCanary等组件放在WorkThread中初始化;
  2. 延迟地图定位、ImageLoader、自有统计等组件的初始化:地图及自有统计延迟4秒,此时应用已经打开;而ImageLoader
    因为调用关系不能异步以及过久延迟,初始化从Application延迟到SplashActivity;而EventBus因为再Activity中使用所以必须在Application中初始化。

%title插图%num

三方组件调用优化示例代码

注意:闪屏页的2秒停留可以利用,把耗时操作延迟到这个时间间隔里。

五、启动加速之Diagnosing The Problem

本节我们实际定位耗时的操作,在开发阶段我们一般使用BlockCanary或者ANRWatchDog找耗时操作,简单明了,但是无法得到每一个方法的执行时间以及更详细的对比信息。我们可以通过Method Tracing或者DDMS来获得更全面详细的信息。
启动应用,点击 Start Method Tracing,应用启动后再次点击,会自动打开刚才操作所记录下的.trace文件,建议使用DDMS来查看,功能更加方便全面。

%title插图%num

%title插图%num

优化之前应用启动trace文件分析图

左侧为发生的具体线程,右侧为发生的时间轴,下面是发生的具体方法信息。注意两列:Real Time/Call(实际发生时间),Calls+RecurCalls/Total(发生次数);
上图我们可以得到以下信息:

  • 可以直观看到MainThread的时间轴很长,说明大多数任务都是在MainThread中执行;
  • 通过Real Time/Call 降序排列可以看到程序中的部分代码确实非常耗时;
  • 在下一页可以看出来部分三方SDK也比较耗时;

即便是耗时操作,但是只要正确发生在WorkThread就没问题。因此我们需要确认这些方法执行的线程以及发生的时机。这些操作如果发生在主线程,可能不构成ANR的发生条件,但是卡顿是再算难免的!结合上章节图App冷启动业务工作流程图中业务操作以及分析图,再次查看代码我们可以看到:部分耗时操作例如IO读取等确实发生在主线程。事实上在traceview里点击执行函数的名称不仅可以跟踪到父类及子类的方法耗时,也可以在方法执行时间轴中看到具体在哪个线程以及耗时的界面闪动。

分析到部分耗时操作发生在主线程,那我们把耗时操作都改到子线程是不是就万事大吉了?非也!!

  • 卡顿不能都靠异步来解决,错误的使用工程线程不仅不能改善卡顿,反而可能加剧卡顿。是否需要开启工作线程需要根据具体的性能瓶颈根源具体分析,对症下药,不可一概而论;
  • 而如何开启线程同样也有学问:Thread、ThreadPoolExecutor、AsyncTask、HandlerThread、IntentService等都各有利弊;例如通常情况下ThreadPoolExecutor比Thread更加高效、优势明显,但是特定场景下单个时间点的表现Thread会比ThreadPoolExecutor好:同样的创建对象,ThreadPoolExecutor的开销明显比Thread大;
  • 正确的开启线程也不能包治百病,例如执行网络请求会创建线程池,而在Application中正确的创建线程池势必也会降低启动速度;因此延迟操作也必不可少。

通过对traceview的详细跟踪以及代码的详细比对,我发现卡顿发生在:

  • 部分数据库及IO的操作发生在首屏Activity主线程;
  • Application中创建了线程池;
  • 首屏Activity网络请求密集;
  • 工作线程使用未设置优先级;
  • 信息未缓存,重复获取同样信息;
  • 流程问题:例如闪屏图每次下载,当次使用;

以及其它细节问题:

  • 执行无用老代码;
  • 执行开发阶段使用的代码;
  • 执行重复逻辑;
  • 调用三方SDK里或者Demo里的多余代码;

项目修改:
1. 数据库及IO操作都移到工作线程,并且设置线程优先级为THREAD_PRIORITY_BACKGROUND,这样工作线程*多能获取到10%的时间片,优先保证主线程执行。

2. 流程梳理,延后执行;
实际上,这一步对项目启动加速*有效果。通过流程梳理发现部分流程调用时机偏早、失误等,例如:

  • 更新等操作无需在首屏尚未展示就调用,造成资源竞争;
  • 调用了IOS为了规避审核而做的开关,造成网络请求密集;
  • 自有统计在Application的调用里创建数量固定为5的线程池,造成资源竞争,在上图traceview功能说明图中*后一行可以看到编号12执行5次,耗时排名前列;此处线程池的创建是必要但可以延后的。
  • 修改广告闪屏逻辑为下次生效。

3.其它优化;

  • 去掉无用但被执行的老代码;
  • 去掉开发阶段使用但线上被执行的代码;
  • 去掉重复逻辑执行代码;
  • 去掉调用三方SDK里或者Demo里的多余代码;
  • 信息缓存,常用信息只在*次获取,之后从缓存中取;
  • 项目是多进程架构,只在主进程执行Application的onCreate();

%title插图%num

业务代码优化示例

通过以上三步及三方组件的优化:Application以及首屏Activity回调期间主线程就没有耗时、争抢资源等情况了。此外还涉及布局优化、内存优化等部分技术,因对于应用冷启动一般不是瓶颈点,这里不展开详谈,可根据实际项目实际处理。

六、对比效果:

通过ADB命令统计应用的启动时间:adb shell am start -W 首屏Activity。
同等条件下使用MX3及Nexus6P,启动5次,比较优化前与优化后的启动时间;

优化前:
MX3

ThisTime TotalTime WaitTime
1237 2205 2214
1280 2181 2189
1622 2508 2513
1485 2434 2443
1442 2418 2429

Nexus6P

ThisTime TotalTime WaitTime
1229 1832 1868
1268 1849 1880
1184 1780 1812
1262 1845 1876
1164 1766 1807

优化后:
MX3

ThisTime TotalTime WaitTime
865 1516 1523
911 1565 1573
812 1406 1418
962 1564 1574
925 1566 1577

Nexus6P

ThisTime TotalTime WaitTime
603 1192 1243
614 1076 1115
650 1120 1163
642 1107 1139
624 1084 1124

对比:
MX3提升35%

ThisTime平均数 TotalTime平均数 WaitTime平均数
优化前 1413 2349 2357
优化后 895 1523 1533

Nexus6P提升39%

ThisTime平均数 TotalTime平均数 WaitTime平均数
优化前 1221 1814 1848
优化后 626 1115 1156
  • 命令含义:
    ThisTime:*后一个启动的Activity的启动耗时;
    TotalTime:自己的所有Activity的启动耗时;
    WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)。

七、问题:

1、还可以继续优化的方向?

  • 项目里使用Retrofit网络请求库,FastConverterFactory做Json解析器,TraceView中看到FastConverterFactory在创建过程中也比较耗时,考虑将其换为GsonConverterFactory。但是因为类的继承关系短时间内无法直接替换,作为优化点暂时遗留;
  • 可以考虑根据实际情况将启动时部分接口合并为一,减少网络请求次数,降低频率;
  • 相同功能的组件只保留一个,例如:友盟、GrowingIO、自有统计等功能重复;
  • 使用ReDex进行优化;实验Redex发现Apk体积确实是小了一点,但是启动速度没有变化,或许需要继续研究。

2、异步、延迟初始化及操作的依据?
注意一点:并不是每一个组件的初始化以及操作都可以异步或延迟;是否可以取决组件的调用关系以及自己项目具体业务的需要。保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。

3、通用应用启动加速套路?

  • 利用主题快速显示界面;
  • 异步初始化组件;
  • 梳理业务逻辑,延迟初始化组件、操作;
  • 正确使用线程;
  • 去掉无用代码、重复逻辑等。

4、其它

  • 将启动速度加快了35%不代表之前的代码都是问题,从业务角度上将,代码并没有错误,实现了业务需求。但是在启动时这个注重速度的阶段,忽略的细节就会导致性能的瓶颈。
  • 开发过程中,对核心模块与应用阶段如启动时,使用TraceView进行分析,尽早发现瓶颈。
友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速