STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

App高性能优化

性能优秀的App标准

一个App体验好不好,关键一点在于性能,性能优秀的App,下面几个指标缺一不可:

  • 快速启动
  • 对用户点击事件的快速反应
  • 快速页面切换
  • 流畅的滚动
  • 流畅的动画

Cordova开发难点

使用Cordova开发App入门很容易,难点在于以下几点:
1.性能优化
我们来张图看看为何Android Cordova App、IOS Cordova App,Android Native App,IOS Native App存在性能差异。
cordova
从上图我们可以清晰的看出为何IOS Native App天生会比Android Native App快(理论上),因为它中间少了一层JAVA层(目前的Android5+系统已经跳过了这个中间层了(art模式))。
利用Cordova开发的App在两种系统上面又多加了一层JS层(其实还多加了一个渲染层(浏览器的渲染引擎)),所以Cordova App自然就会比Native App慢。
但是无论如何IOS上面的Cordova App天生还是会比Android上面的Cordova App快,因为Android多了一个JAVA层,更不用说IOS系统本身优化的就比Android好。
如果你前端水平还可以,比较有经验,又是个爱思考的孩子的话,写出的Cordova App在IOS上面基本不会有什么卡顿。因为IOS系统是很优秀的,IOS webview也没有什么严重的残疾,IPhone手机硬件本身配置也是很不错的。
综合上面可以知道Android Cordova App容易卡顿的原因有以下几个方面:
1.android机器配置参数不齐
市面上的Android手机种类很多,高、中、低端都有,Cordova App在中低端机器上面容易出现卡顿。
2.android系统碎片化
Android手机系统版本基本上是越高,系统优化的越好,尤其是webview的优化,4+以上版本系统的webview性能比2.3版本系统提升了一大截,Android4.4+比4.0~4.3版本系统的webview在性能上面又有明显提升。
问题在于android手机系统各个版本(4+以上)都占有不错的市场份额,所以开发的App必须兼顾到这些版本系统。
3.android webview天生残缺
例如android webview原生滚动功能就是个残废,滚动事件支持的非常不全。基于android webview原生的滚动,使用Cordova开发App是很难实现下拉刷新,无限滚动加载等App必备功能,通常只能利用JS滚动来模拟。
所以Cordova App绝大部分优化都是针对Android的,当然Android优化的越流畅,IOS上面自然就会更加流畅。
2.填坑
上面的属于性能坑,这里的坑是指不同系统,不同系统版本而造成的css bug、浏览器 bug等。
3.原生插件开发
这个没有什么好说的,如果你想做,你就老老实实学Andoid和IOS原生开发吧。


性能优化

Cordova App本质是一个浏览器壳子里面跑了一个网页,所以毫无疑问Cordova App首要优化工作在HTML、JS、CSS上面。在学习这篇文章之前,建议下看下这篇文章“浏览器内部工作原理”,了解一下浏览器的工作原理,注意里面的“DOM树”、“渲染树”、“布局”、“重绘”等概念。因为下文的讲解中我会反复提到这些概念。只有理解了这些概念,你才能理解下文中的优化原理。下面将从HTML、CSS、JS三方面讲解性能优化。


HTML优化

减少HTML的复杂性和页面的元素数量是构建极速网页,影响成功的关键性因素。在该节,将尽可能全面的介绍如何编写快速加载和易于维护的简洁干净的HTML。

1.正确使用HTML、CSS和JS
HTML是一种标记语言,用于表示结构和给内容赋予意义的。
HTML不应被用来样式化内容。不要只是为了显得更大就把文字写在h1~h6中。相反,使用CSS来改变元素的外观和布局。

2.基本原则
a.使用HTML确定结构;使用CSS确定展现;使用JavaScript确定行为。
b.使用HTML,必要时借助CSS,并且在不得已时再添加JS。例如:在多数情况下尽可能使用CSS3来代替JS动画。
c.将CSS和JS从你的HTML代码中分离。


1.使用正确的文档结构

使用HTML5的文档类型,下面是一个正确的文档结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<title>一起玩吧</title>
<link rel="stylesheet" type="text/css" href="css/css/font-awesome.min.css" />
<link rel="stylesheet" type="text/css" href="css/global.css" />
</head>
<body>
<-- ... -->
<script type="text/javascript" charset="utf-8" src="./js/lib/doT.js"></script>
<script type="text/javascript" charset="utf-8" src="./js/lib/banner.js"></script>
</body>
</html>

通常我们在head元素中引入CSS,这样浏览器就可以在解析HTML前预先加载样式而不会呈现一个混乱的页面布局。
把JavaScriptS放在页面的最底部,在body封闭之前。这将提供页面渲染时间,因为浏览器可以在JavaScript加载前将页面渲染出来。


2.书写正确的HTML代码

Web网页成功的一个主要因素就是浏览器可以处理无效的HTML,现代浏览器都有容错机制,HTML5标准设置还规定了浏览器应该能够处理哪几种错误。
也许你认为这不值得一提,但是尤其对于用Cordova开发的App,写合法的HTML是极其正确的。因为在低端Android手机上面每个新手写出来的App基本上都会卡。
有效的HTML更容易调试,占用资源更少,因为它们会跳过浏览器的容错机制所以渲染速度更快,无效的HTLM只会影响渲染速度。
因此在编写HTML代码的时候,请务必在非自封闭的元素后加上结束标签。


3.使用单页面应用架构(SPA)

这个我在前面已经讲过,Cordova App本质就是在浏览器上看网页,非SAP应用每次切换页面需要做各种重复的初始化工作(这个会严重影响性能),使用SPA架构可以省去这些重复的初始化工作。另外使用SPA架构可以简化DOM结构,因为App不管有多少个页面,都是跑在一个Html页面里面。
具体参见:“开发Cordova App的黄金守则”。


4.简化DOM结构

Cordova(本质就是增强型的浏览器)在加载完页面之后,首先解析HTML构建DOM树,然后解析CSS布局DOM树中可视化节点构建出Render树(渲染树),最后绘制页面到屏幕上。

tree

图1:渲染树及对应的DOM树

根据上图用最少的DOM去构建一个DOM元素,意味着浏览器创建DOM树越简单,构建就越快,占用内存越少。同理根据DOM树和CSS创建出来的渲染树也越简单,布局会越快,从而使Cordova绘制页面的速度也就越快。

简化HTML结构,可以从两方面入手:

  1. 去掉冗余的HTML代码(html不合理嵌套)
  2. 减少数据展现(根据业务)

5.其他注意事项

a.不要在HTML中添加事件处理
这样非常难以维护,如以下代码:

1
2
3
<body>
<button id='stop' onclick='stopTouch();'>stop</button>
</body>

在JS文件里添加事件处理,这样就好多了:

1
2
3
4
5
$(function(){
$('#stop').on('click',function(){
...
});
});

b.正确使用HTML标记
例如HTML5引入了一些新的“语义化元素”,像<header>、<footer>和<nav>。
使用正确的元素表达正确的内容对于可读性,可维护性是非常有帮助的。
使用<h1> ~ <h6>代表标题,<ul>或<ol>代表列表。
使用<header>、<footer>、<nav>和<aside>表示文档正确的模块。
使用<p>写正文。
使用<em>和<strong>代替<i>和<b>表示强调。
使用<label>元素为input类型添加标签。
混合文字和元素会导致布局的问题,如下:

1
<div>姓名:<input type="text"/></div>

最好用下面的表示:

1
2
3
4
<div>
<label>姓名:</label>
<input type="text"/>
</div>

c.不要用HTML来布局
HTML是用来组织结构和赋予内容一定意义,而不是用来设置页面显示成什么样子的。
Flex布局是CSS3的一种推荐响应式弹性布局,能用就用。
使用<p>元素代表文本,而不是用来布局。
避免使用<br>来换行,使用块级元素和CSS来代替。
避免使用水平分割线<hr>,使用CSS的border样式来控制。
不要使用table来布局,table渲染时间是div数倍以上。
不要使用不必要的Div。
要了解哪些元素是块级元素,避免在Div中放置不必要的块级元素。


CSS优化

如果你不是个有代码洁癖的人,可能会想CSS都要优化,是不是有点“”挢抂过正“了。也许你在桌面PC浏览器或者手机浏览器上面甚至利用Cordova开发IOS App都不太会觉察到一个CSS能带来多么大的性能影响,但是在中低端Android手机上面有时一个CSS就能导致你整个App性能直线下降,手机能烤鸡蛋。

浏览器解析CSS方式

浏览器解析选择器是从右到左的方式,和我们阅读的顺序是相反的。
如下面这个CSS代码:

1
#afui ul li a{color:#00f;}

浏览器会首先从最右边寻找a元素(可能会很多),然后沿着DOM树向上查找,筛选出在li元素里面的a元素,然后继续向上面筛选,最后筛选出匹配整条规则的a元素。


1.浏览器本身对CSS选择器的优化(只需了解)

如今的浏览器已经不是几年前傻乎乎的浏览器了,它们在CSS选择器优化方面已经做得很好了,从而使我们的CSS代码能够保持良好的结构,不必太担心性能问题。
下面我们看看WebKit为核心的浏览器主要做了哪些优化工作:

样式共享策略

”样式共享策略“使浏览器不必再为一个已经计算过的元素样式重新计算一次。请看下面的代码:

1
2
3
4
<div>
<p>WuWei</p>
<p>Hello,World!</p>
</div>

如果浏览器渲染引擎已经计算了第一个<p>元素的样式,那么”样式共享策略“就会让浏览器避免计算第二个<p>元素的样式,直接共享第一个<p>计算出来的样式结果。
既然我们知道了浏览器本身有这么个优化策略,那么我们在编写CSS代码的时候就要多多投其所好,提高页面渲染速度。

哈希规则策略

我们已经知道了浏览器是从右向左开发解析匹配样式规则的,所以最右边的选择器是非常重要的。
”哈希规则策略“根据选择器最右边的部分将样式表分为若干个组。
例如下面的代码:

1
2
3
4
5
a{}
div p{}
div p.legal{}
#sidebar a{}
#sidebar p{}

将被分为下图中的三个组:
three
这样在渲染的时候浏览器就不必检查样式表中的每个样式规则,而是从最有可能匹配到该元素的那个样式组里面开始查找,极大提高了效率。


2.书写精简的CSS代码

a.避免过度约束
一条普通规则,不要添加不必要的约束。

1
2
3
4
5
6
7
/*不良代码*/
ul#foot{..}
.footer#barid{..}

/*良好代码*/
#foot{..}
#barid{..}

b.使用紧凑语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*不良代码*/
.foot{
padding-top:20px;
padding-left:10px;
padding-bottom:20px;
padding-right:10px;
background:#000;
background-image:url(logo.png);
background-position:bottom;
background-repeat:no-repeat;
}

/*良好代码*/
.foot{
padding:20px 10px;
background:#000 url(logo.png) no-repeat bottom;
}

c.精简规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*不良代码*/
.some{
color:red;
background:blue;
height:150px;
width:150px;
font-size:16px;
}
.other{
color:red;
background:blue;
height:150px;
width:150px;
font-size:8px;
}
/*良好代码*/
.some, .other{
color:red;
background:blue;
height:150px;
width:150px;
}

.some{
font-size:16px;
}
.other{
font-size:8px;
}

3.删除无效的定义

无效的定义并不会影响页面功能显示,但会影戏渲染树的执行,从而影响页面展示的性能,增加代码量的同时,也增加了浏览器解析代码的时间。


4.简化Render结构

浏览器构建渲染树的时候要解析CSS来确定渲染对象的集合属性,从而完成最终渲染树的构建。
用最简单(注意简单不是少的意思,而是让浏览器以最快的方式计算出渲染对象布局信息的意思)的CSS去样式化一个DOM,越简单的CSS样式,意味着浏览器构建DOM渲染树越快越简单,绘制的速度也会越快。

简化HTML结构,可以从两方面入手:
a.去掉冗余的CSS
b.减少视觉样式

a.去掉冗余的CSS
演示代码如下:

1
<div class='info cfx'></div>
1
2
3
4
5
a{text-decoration:none;color:#333;}
#afui .wall_item .info{padding:4px 5px;overflow:hidden;}
#afui .cfx{clear:both;zoom:1;}
#afui .wall_item .info .price{color:#aaa;}
#afui .wall_item .info .likes{color:#ff5a00;}

在不改变展示样式的情况下,我们可以作如下处理:
去掉div.info元素的cfx样式class,cfx的作用是清楚浮动,但是info class里面有一条属性”overflow:hidden;”同样也是可以清除浮动的,所以重复了,并且后面这种清除浮动方式更高效,因为它不同cfx,cfx需要在元素的底部生成一个不可见元素来实现清除浮动。

1
<div class='info'></div>

结合上面去掉冗余HTML代码,我们可以简化一些CSS代码,如下:

1
2
3
4
a{text-decoration:none;color:#333;}
#afui .wall_item .info{padding:4px 5px;overflow:hidden;}
#afui .wall_item .info .price{color:#aaa;}
#afui .wall_item .info .likes{color:#ff5a00;}

b.减少视觉样式
这里的简化是指不要用过多的同种色彩,加粗等修饰性的css属性。
例如div.info元素里面的price class和likes class的color属性可以统一成a标签的color属性,简化后的代码如下:

1
2
a{text-decoration:none;color:#333;}
#afui .wall_item .info{padding:4px 5px;overflow:hidden;}

当然简化需要html和css的配合,且还需要根据UI设计师给的标注图定。


5.其他注意事项

a.避免让选择符看起来像正则表达式
CSS3高级选择器执行耗时长且不易读懂,请避免使用。

b.CSS类名做状态标记的注意事项(如:active伪元素)
如果在网页中使用CSS的类来对节点做状态标记,当这些节点的状态标记类修改时,可能会触发节点的重排和重绘。这时最好只改变颜色这样只会发生重绘,不要使用会触发重排或过多重绘的属性,这样你的页面在低端手机上面会发生抖动。

c.坚决不使用JS动画使用CSS3动画
JavaScript在浏览器的主线程中运行,而其中还有很多需要运行的JavaScript、样式计算、布局、绘制等对其干扰。这也就导致了线程可能出现阻塞,从而造成丢帧的情况。而浏览器可以对CSS3动画进行优化,它必要时可以创建图层,然后在主线程之外运行。
利用CSS3实现动画的时候最好用CSS类来实现,而不是通过JS来一句一句的修改元素的CSS,这样可以降低页面的重排与重绘的次数,排除强制刷新Cordova浏览器渲染引擎缓存队列的风险。


在移动开发中,下面的CSS特性是不建议用的,这样可以加快页面CSS渲染,提高页面滚动流畅度。
d.不滥用float
Float在渲染时计算量比较大,尽量减少使用,使用flex弹性布局替代。

e.不滥用border-radius
Border-radius属性是用来为元素添加圆角的,但是CSS圆角会产生严重的性能问题,尤其是在低端Android手机上面。

f.不滥用border-shadow
Border-shadow属性是用来为元素添加阴影的,和CSS圆角一样,也会产生严重的性能问题,尤其是在低端Android手机上面。


在移动开发中,下面的CSS特性是建议用的,而且要常用,这样可以加快页面CSS渲染,提高页面滚动流畅度。

g.使用transform属性
通过节点的transform可以修改节点的位置、旋转、大小等。使用绝对定位元素的left和top属性来修改节点的位置,仍然会发生局部重排,修改时的代价还是比较大,使用translate3d不会发生重排。
另外利用translate3d、scale3d、rotate3d做动画的DOM不要嵌套的太深,也不要包含太多的元素,因为如果元素太多,太深超过GPU内存时,你的页面性能会变得非常差。

h.使用display属性
这是个非常好的属性,display=none是命令浏览器不要将该元素加到渲染树中,这样该元素也就不会绘制到屏幕,可以提高GPU效率,减少内存占用,从而大大提高页面的性能。
所以对于页面上面看不到的元素根据情况尽量display none掉,注意不要使用visibility:hidden来隐藏元素,它对提高页面性能没有丝毫帮助,因为它还是会留在浏览器构建的渲染树中。

i.使用absolute或fixed定位
当元素的position为static和relative时,元素属于普通的文档流,当对元素的某个操作需要重新渲染时,浏览器会渲染整个页面。如将元素的position设置为absolute和fixed可以使元素从普通DOM文档流脱离出来而独立存在,而浏览器在需要渲染时只需要渲染该元素,其容器元素和其子元素即可,从而只发生局部重排与重绘。
举例,请看下面的代码:

1
2
3
4
5
6
.wall{
position:absolute;
top:479px;
left:11px;
width:130px;
}

可以看到我们固定了.wall元素的宽度,这样还有一个好处就是因为我们的元素使用了absolute定位,所以更新.wall里面的内容的时候不会发生全局性的重排。

j.使用opacity属性
透明度改变后,GPU在绘画时只是简单的降低之前已经画好的图层位图alpha值来达到效果,并不需要整体的重绘。

k.使用width属性(固定元素尺寸)
如果可能的话,我们应该利用JS来将元素交给浏览器渲染之前将元素的尺寸的宽和高都计算出来,写在内联CSS样式里,这样浏览器在渲染它们的时候就不用再计算元素的尺寸,这样做的目的是响应式设计和减少浏览器的“重排”时的计算。JS的计算是廉价的,浏览器的重排与重绘代价则是昂贵的。
举例,请看下面的代码:

1
<img width='130' height='130' class='pic' src='xxx.jpg'/>

从上面代码可以看出我们直接用JS写死高度和宽度。这样做的好处是图片没有加载之前就可以渲染页面,图片加载完成渲染到页面上面时,图片外的元素不会发生全局重排和重绘,浏览器只是将图片做了一下大小比例调整绘制到页面上。另外提取固定图片的尺寸将会极大提高滚动性能。


JS优化

JS优化这方面的东西不能泛泛而谈了,因为JS有很多框架,每个框架的优化也不尽相同。我开发“一起玩吧App”选择的框架是Appframework,所以下面写的东西都是围绕优化Appframework展开的,但是里面的很多理念也是很值得借鉴的。
有关“Appframework”的介绍,请参考之前写过的文章:Appframework


1.快速下载安装

“一起玩吧App”打包之后不到1.3M。1.3M?也许你觉得不可思议,因为一起玩吧App看起来内容不少。这就是利用Cordova开发App的先天优势,木有办法啊!如果把里面一些不用的东西全部剔除掉,再把文件都压缩一下,估计1M就解决了。
这对下载体验来说是非常好的—-秒下载!这听起来很酷,因为它为你的用户节省了很多时间和流量。


2.快速启动

在手机上面很多用户愿意用App而不愿意用手机版网站的原因大致有以下几个方面:

  • 用手机就用App,天经地义。这是一种习惯,和PC上网站就用浏览器一样。
  • 不愿意输入网站域名,手机上面打字很麻烦(也许你会说可以保持到桌面嘛,绝大多数非IT专业的人不知道这么做)。

但是如果你的App打开很慢的话,很可能用户就会投到其他App的怀抱里去了。
“一起玩吧App”看起来有很多页面,但是可以做到“秒启动”,即使在低端Android上面也可以做到3秒内冷启动(关闭刚刚打开的App叫做热启动,这时候系统一般不会立即释放App刚刚占用的内存,所以一般App热启动都很快)。
那么这是如何做到的呢?
a.真正的panel延迟加载
“一起玩吧App”是使用afui(appframework ui)框架来搭建应用的架构的,是一个SPA架构。App的一个个页面在afui框架中叫做panel,它被放到不同的页面模板文件中(views目录下)。panel切换时前一个panel会被隐藏(display:none)掉。
当用户第一次切换到一个新panel时,这个panel没有加载到内存中,所以也就没有构建到DOM树种去。这样做的好处是“一起玩吧App”刚启动时只需要加载第一个panel。
本来近百个panel需要一次加载进内存,然后浏览器进行DOM树构建,而现在只需加载一个panel就OK了。
下图是切换到一个新panel的流程图:

flow

图4:切换到新panel的流程

“一起玩吧App”就是通过这种“延迟加载panel”的方式来做到快速冷启动的。


3.快速页面切换

当你的页面比较简单的时候一般你碰不到“页面切换反应迟钝”的问题,尤其在高端Android手机上面,IOS上面更是不在话下。
当一个列表页比较长的时候,尤其在中低端Android手机上面,你能够比较快速地进入详情页,但是在后退到原来列表页的时候反应常常会很慢。这是因为后退到原来列表页时,浏览器要渲染,重绘原来的页面,当这个带有图片的列表页比较长的时候,这个重绘过程可能会非常的慢。
“一起玩吧App”从以下几个方面来解决这个问题:
a.快速响应用户点击事件
如果你用Cordova开发App,没有对“click”点击事件进行优化的话,那么手机操作系统本身就会强加300ms延迟才会响应你的点击事件。“一起玩吧App”使用了afui框架,该框架本身对“click”事件做了优化,所以不存在这个问题。

b.页面切换时不要做耗时操作
通常作为用户点击之后希望立即看到页面响应跳转,这时候最好其他的事情都停下来(不要去掉ajax请求),只作页面跳转,如果有特效切换,要等到切换完成时再进行新页面数据加载、渲染以及旧panel事件卸载处理等等。

c.少使用或不使用页面切换特效
在低端Android手机上页面切换特效常常会导致出现卡顿的情况,所以在Android上面尽量不要做特效切换。

d.隐藏部分列表元素
这是最关键一点,上面说了返回列表页面之所以会耗时很长,是因为重新渲染含有图片的大量列表所造成的。
那么很自然想到,跳到详情页的时候我们隐藏掉原来列表页的大部分元素就可以了,只留下可视区域的页面不就可以了。当返回到列表页的时候,延迟100ms再将原来隐藏的元素显示出来不久可以了,实际证明,这样做是OK的。
最后需要注意的是这种方式隐藏元素需要是绝对布局的,否则你就要注意滚动条的位置了。而且经过前面的介绍,绝对布局不会引起全局的重排重绘,对于利用JS操作这种布局的元素效率会很高。


3.使用好滚动插件

a.不要触发无谓的“scroll”事件
b.保持滚动容器里的元素简单
c.滚动过程中不要有立即点击的交互效果
d.请尽量使用原生滚动(IOS上使用元素滚动,Android上使用JS滚动)


4.使用快速的JS模板引擎

对于App和服务器之间最正确的做法就是只进行数据交互,就是不要从服务器加载大量html片段,最好只加载JSON数据。对于加载过来的数据你只需使用JS将其添加到页面文档中即可。
这时候你就需要拼接字符串了,利用JS拼接字符串是一件很痛苦的事情,也不方便维护和修改。但是你可以借助JS模板引擎,“一起玩吧App”中使用了小巧,高性能的JS模板引擎doT。

下面是各种常见的JS模板引擎的测试结果:

jsTemplate

通过对各模板引擎测试结果,可以看出
artTemplatejuicerdoT引擎模板整体性能要有绝对优势。


写到这里,关于“App高性能优化”的内容基本上就讲完了。但是还有一个东西值得说说——-Crosswalk。下一篇将着重介绍Crosswalk,请大家尽请期待。