前言
对于混合应用而言,性能问题一直被吐槽,虽然设备的内存的不断增大,很大程度上缓解了这个一问题,但是和原生应用来讲还是有很大区别,本人从Phonegap2.x开始,一直的探索和使用混合应用技术。
当时的2.x性能真是不怎么样,首次加载时间也比较长,后来phonegap被apache纳入旗下以后,更名为Cordova,可以说从此以后,性能问题得到了很大的改善,占用内存也越来越小,到如今使用的版本已经变为6.3.0,看得出cordova也一直在尝试缩小与native的差距。
不管一个平台如何,是好,是坏?很大程度上都取决开发人员的技术水平,所以如何开发一个高性能的混合应用成为了至关重要的问题。
对于大部分项目而言,都不是一锤子买卖,很多都是长期项目,长期项目就免不了版本的一次一次的更新迭代,可以业务逻辑变得越来越复杂,页面变的越来越多,动画越来越炫酷,数据量越来越大,面对种种问题,我们都需要解决,所以一个好的优化方案势在必得。
下面,就谈一下,这几年我再混合应用开发过程中尝试的优化方案。
部分内存数据移动至离线存储
影响速度的根本因素,几乎都在内存上,如果内存消耗过高,应用就会卡顿,甚至闪退现象。
所以建议内存方面应注意, 不要存大量的应用数据在内存中,我们可以选择离线数据库,比如pouchdb,当然部分必要数据还是要存的,看实际需求。
目前离线数据库框架都是基于websql,运行速度非常快, 所以必要情况下,可以选择数据离线存储,避免占用内存,因为混合应用的webview渲染本身已经消耗部分内存了。
加载速度优化
由于项目不断变大,势必会导致js bundle文件的不断扩充,文件大了,加载速度也就会变慢, 首要解决方案大概如下:
- JS压缩整合 (压缩所以js,整合一个文件)
- 图片压缩整合 (压缩图片,保证清晰度即可)
- CSS压缩整合 (压缩整合一个文件,减小大小)
- 巧用Splash 页面(设置3秒延迟,保证html加载完全)
如果不足以优化,就需要讲JS 文件整合成两个文件,从而使文件变小,不必要的的js bundle 可以使用h5 异步加载,提高效率。
例如:
HTML页面与原生View的无缝衔接
对于混合应用而言,很难达到htmL view和原生view的无缝对接,也就是说,使用起来恰似是同样的实现方式,在一个栈里边。
但事实, 原生view属于native的栈中,而html view属于另一栈中,两种形式的view分属于不同的世界, 而且在使用js调用原生应用,cordova存在线程阻塞问题。
如果下面这个需求,就不好实现了:
大概是,view1中点击按钮,调用扫描插件view2, 扫描出结果以后启动view3,当在view3点击back按钮,需要在返回扫描产检view2, 扫描插件view2点击back退回view1.
对于这个需求,无论是native 还是 html, 在一个栈中,都不是问题,非常简单,但是,不同栈里,就不太好实现了,当在view3点击back调用插件会阻塞掉线程,即便执行back,也无法回退的,最后,在view2点back 回退时候发现, view3闪一下,马上退回view1。
为了解决这个问题,需要用timeout简单实现一个异步的操作,问题就解决了:
setTimeout(function(){
// to execute the native plugin action. eg. call view2
},100)
动画的使用
动画效果,是用户交互体验的根本,如果动画处理不好,就会使很大的问题。
比如进入一个页面后,需要执行一个大概三秒的动画效果,在这三秒内,可能无法触发任何click事件。
比如transform动画。
所以,当你的app中在进入页面两秒内发现点击事件不生效,你就要考虑一下这个页面是否有没有执行完的动画了。
巧用Cache缓存
对于angular这样的框架而言是没有页面缓存技术的, 但是部分应用场景中,页面缓存又在所难免,比如像ios tab view这样的底部导航view,是存在缓存的,如果我们可以取消,会非常不符合ios的设计规范。
从用户角度而言,通常情况这中tab view navigation是不需要重新加载数据的,这样的主页面,几乎时刻存在,从新加载会浪费过多的时间。
这里附上一个ionic的缓存方案:
$stateProvider.state('demo', {
cache:true,
url: '/demo',
template: '<div></div>',
})
上面代码中,通过路由功能对这个页面做了一个cache功能,同样可以禁止掉。
注意:ionic默认是有页面缓存的哦。
避免过多使用监听
监听操作,非常的好用,开发中带来了很多便利,但是于此同时也带来了一些弊端,即为内存的消耗,如果过多的创建监听,势必会占用很多内存。
所以在某些情况下我们可以避免使用监听操作:
比如:我们需要监听一个input输入框数值的变化:
通常来讲会是如下方案:
$scope.$watch('', function(new , old){
})
但是这样就创建了一个监听操作。
我们也可以这样做:
<input type="text" ng-blur="action" />
或者
<input type="text" ng-change="action" />
合理使用HTTP
对于部分App, 在程序启动后,可能不止请求一个服务,可能会两个或者三个以上,如果需要等所有服务都请求完在进行页面跳转,实现起来就比较麻烦了。
如果一个请求完在请求另一个,时间势必会延长,影响用户体验。
我们可以采用如下解决方案,来自angular:
$q.all([async1, async2, async3...], function(res2, res2, res3 ....){
})
http请求即为异步请求,所以以上方案就是异步并发请求,而且是等所有返回结果结束在进行统一回调。
这大大提升了请求速度,提升用户体验。
合理使用Native功能弥补JS的短板
JS是万能,但又不是万能的,混合应用的有些功能如果用js实现性能上会存在很多问题,比如:
- 拍照压缩
- 文件上传下载
- 网络状态监听
为了能够有更好的体验,我们就需要调用原生api实现此功能,并合理创建多线程处理,从而提高运行速度。
采用轻量级组件化框架技术
这几年下来,我使用的框架,还是比较多的,比如vue.js, ionic, avalon, SAPUI5, backbone, durandal, angular还有最新的react系列。
目前框架都在主推,高效轻量级的框架,效率高,性能好,加载速度快,易上手。
这些特性对于移动端同样适用。
所以,我们在进行混合应用开发时候,要选择轻量级的框架,切稳定,社区活跃的,保证遇到问题,有章可循。
这里建议的有: angular, ionic, backbone。
http://www.voidcn.com/article/p-wulbqeao-bpt.html
实现本地View的加载渲染
目前混合主要实现方式有两种:
- webview 加载本地html css, js , 图片文件,也就是说所有文件存在于手机app本地。
- 还有一种即为,html, css, js 图片文件部署在远程服务器上,通过url形式被webview加载。
两种方式各有优缺点,第一种加载速度快,适用于离线app. 第二种加载速度慢,使用与web app。
看你选择了。
Infinate动态加载数据
对于前端无穷渲染list数据是存在一定问题的,angular最大的问题就在这个地方,angular的ng-repeat,无法渲染大量数据,否则的页面会出现假死的状态。
对此,同样存在解决方案,也就是Infinate无穷加载,当滑动到底部,会动态监测,然后load更多。
附上一个简单的实现方案,采用ionic技术框架:
<ion-content ng-controller="MyController">
<ion-list>
....
....
</ion-list>
<ion-infinite-scroll
on-infinite="loadMore()"
distance="1%">
</ion-infinite-scroll>
</ion-content>
function MyController($scope, $http) {
$scope.items = [];
$scope.loadMore = function() {
$http.get('/more-items').success(function(items) {
useItems(items);
$scope.$broadcast('scroll.infiniteScrollComplete');
});
};
$scope.$on('$stateChangeSuccess', function() {
$scope.loadMore();
});
}
请求数据并渲染DOM优化方案
现在几乎所有的系统都是前后端分离的,也就说需要通过ajax请求数据后在渲染到页面上。
目前主流的框架都会支持很多的绑定方式,比如:
- 双向数据绑定:
- 单向数据绑定
- 一次性数据绑定
如何使用合适的绑定方式决定了我们内存消耗的多少。
上面三种方式消耗状态: 双向> 单向> 一次性
那么什么时候该使用什么样的绑定方式呢?
举个例子:
- 用户登陆 (双向,因为要获取用户名和密码在controller)
- 新闻详细信息显示(一次性,因为不需要再更新数据)
- 表单验证结果显示信息(单向,因为需要根据controll信息动态更新检验结果状态)
避免创建过多的window全局对象
学过JS的同学都知道window即为全局对象,是任何一个地方都可以调用到的,也会有老程序员告诉新人说:“你不要污染了全局空间”。
例如Angular这样的框架,同样有个全局属性“$rootScope”, 也是我们在任何地方都可以使用的。
如果项目过多的使用了这样变量:
一. 是会导致全局变量的混乱不堪,无法维护。
二. 不使用时候,无法回收这个变量,垃圾变量占用内存。
尽量避免使用JS控制Dom样式
最近两年,Angular是非常火热的框架,所以本人使用angular也会比较多一点,在这个框架中,有一个属性是可以动态控制dom样式的,叫ng-class, 可以说非常的好用,刚开始时候变肆无忌惮的使用起来。
后来发现运行起来多个ng-class控制时,或者在ng-class中使用个属性,并使用===, 三目运算符进行判断,最终导致页面闪动的状态,也就是效果不佳。
附上一个简单的例子:
<div ng-class="{{'customStyle':flag ==== customFlag}}"> </div>
避免过度使用阴影和梯度和3D渲染
最近尝试了一下three.js这个框架,主要是想做3D图像,运行在浏览器端没有任何问题,但是在手机上,发现运行明显下降,运行一段时间后,手机发热,所以我们在项目中,尽量不要过多的使用3D这些特效,如果使用,要及时销毁,以免占用过多系统内存。
对于简单的CSS 3D旋转效果, 没有大问题, 大部分手机足以应付得了。
比如下面这段代码:
div
{ transform: rotateX(120deg); -webkit-transform: rotateX(120deg); /* Safari 和 Chrome */ -moz-transform: rotateX(120deg); /* Firefox */ }
避免使用setInterval对插件进行频繁调用
对于cordova插件运行机制,很多新人并不是很清楚,也并没有很多人去编写自定义插件, 一个插件的调用流程大概如下图所示:
所以,一次调用过程,会花费较长的时间, 这个过程中,由于JS的单线程异步操作,此时通过JS调取,会阻塞线程的继续运行。
如果此时,频繁的调用插件,应用内存会骤然上升,后果会crash掉。
及时升级优化Cordova Platform
我们都知道移动系统的更细速度异常的快,andorid每年一个大版本, IOS也是如此,每年都会推出一个大版本,每一个版本的改变可大可小,但是对我们混合应用还是有一定影响的。
其中webkit的更新最为明显,毕竟混合应用使用的是webkit做UI渲染嘛。
最近就遇到一个问题,当IOS 更新到10, android更新到6以后,总会有一些莫名的问题。显示的多少会有些差异。
解决方案是,对Cordova Platform 做一个升级。
ios-platform —> 4.2.0
android-platform —-> 5.2.0
于此同时也对JS库做了一个版本升级,以便支持最新的webkit.
尽量不去渲染不需要显示的DOM
有一部分app在设计过程中,view的部分信息是不需要或者不经常显示的,也就是这部分信息根据用户行为是可有可无的,但是这个部分信息在此页面可能dom元素又非常之多,层次也会比较深。
如果我们默认就绘制dom元素,肯定会花费一些时间。
这种情况下,我们往往不去渲染,等需要的时候在渲染,所以这种实现方式很大程度上提升了渲染的速度。
下面说一下我再angular工程中的实现方案, 采用了ng-if解决这个问题,简单容易:
<div>
this is need to show
<div ng-if="false">this is not need to show</div>
</div>
使用canvas优化图片加载速度
通常使用Img在移动端渲染图片,速度往往不是很理想,会显示空白,这种情况我们通常会使用canvas进行优化,具体代码如下:
使用过程要注意,只有图片加载完成后才能显示出来,否则会无效,这里用到了回调onload方法。
<head>
<title></title>
<script type="text/javascript" src="jquery-1.7.1.min.js"></script>
<script type="text/javascript"> $(function () { var c = document.getElementById("myCanvas"); var cxt = c.getContext("2d"); var img = new Image(); img.src = "./images/richard.jpg"; img.onload = function () { cxt.drawImage(img, 0, 0); } }); </script>
</head>
<body>
<canvas id="myCanvas" width="1024" height="768" style="border: 1px solid #f00;">Your browser does not support the canvas element.
</canvas>
</body>
清除不需要的历史View
如果你的app页面越来越多,运行起来对内存的占用多少会有一定的影响,对于移动app,就需要我们尽量清除已经不再需要的view,比如注册流程所有的页面,比如slide导航,注册信息页,一旦注册完成,就需要删除所有的view历史。
再比如登陆,如果登陆成功也需要remove.
还有需要定时显示的banner等页面,显示过了,可能就不再需要了,这时候也需要清除掉。
下面说一下,我用ionic做过工程的相关方案:
if(this.$ionicHistory.backView()){ //获取上一个是哪个页面
this.$ionicHistory.clearHistory(); //clear
}
动态刷新页面,为开发增添乐趣
对于多页面,复杂一些的项目工程,往往我们编译会花费较多的时间,记得我做过一个android native的项目,由于项目比较大,每次使用android studio编译都要花2分钟左右的时间,非熬人,对混合项目也不例外。
开发过程中,我们往往需要快速查看效果,实时浏览编译状态,所以就需要动态刷新了,说一下我采用的技术。
主要采用gulp + browserSync+ watchify来监听动态编译,实时加载,速度还是很快的,主要代码如下:
'use strict';
var config = require('../config');
var browserSync = require('browser-sync');
var gulp = require('gulp');
var url = require('url');
var proxy = require('proxy-middleware');
gulp.task('browserSync', function() {
var proxyOpts = url.parse(config.backendUrl);
proxyOpts.route = '/api';
var browserSyncConfig = {
port: config.serverport,
server: {
baseDir: config.dist,
middleware: [proxy(proxyOpts)]
},
logPrefix: 'MyDemo',
browser: ["google chrome"],
minify: false,
notify: false
};
browserSync(browserSyncConfig);
});
browserSync.reload({ stream: true, once: true })
gulp.watch('path', [task]);
前端代码工程化
前端代码工程化,今年被唱的很热,都在提这个概念,目前来看,一人独大的时代已经过去了,更多的团队协作开发,所以一个工程化代码是非常有必要的。
主要包括以下几个方面:
- 有意义的变量命名,遵从驼峰命名法或者团队内部命名法则,统一规范。
- 使用JSLint对代码进行检查,所以人统一一个标准。
- 采用构建工具对工程进行自动化部署,减少操作步骤,比如使用npm command, gulp ,grunt webpack等等。
- 合理编写注释代码,方便阅读及他人接手,编译后切记要remove掉,因为对于产品环境,这些注释没有任何意义。
- 使用标准的代码仓库,比如git, bitbuket等等, 提交代码进行comment, 合理创建子分之,保证多人开发协作无冲突。
- 代码组件化或者模块化,争取做到一人一模块。一模块一个文件包或者一个文件,这样可以保证彼此没有冲突。
- 保证团队每个人对代码工程使用,烂熟于心,方便管理及升级。
- 任命技术负责人,负责代码统一管理,版本合并,产品发布。
采用实时云更新技术对app升级
对于部分项目需求,用户不行频繁升级app, 的确这样也比较麻烦,这里我采用CodePush对混合应用代码进行云部署,不需要更新app, 既可以部署业务逻辑到用户的手机上,也可以根据用户需要定义所需的功能。
更多信息大家可以参考这篇文章:
http://www.voidcn.com/article/p-rqtmayua-bpt.html
使用Android Studio 和 Xcode性能查看工具
如果查看app内存消耗,是很多人关心的问题,这里我采用Android Studio 和Xcode性能工具进行查看测试,可以随时查看项目运行状态,并作出调整。
注意:项目需要的debug的运行状态。
Android:
IOS:
WebStorm工具使用优化
前端开发工具这几年最火爆的,无疑是Sublime, WebStorm, Atom, visual studio code, 其中Atom, Visual Studio code作为一款开源的IDE, 是当先最为流行的, 支持很多酷炫的样式和各种的自定义插件。
但是,说起来,这几年用的最多的,非常喜欢的一款工具就是WebStorm, 它可以说是一款集成的工具,支持很多H5框架,但确实收费的。
伴随着项目工程的越来越大, 项目更是用了npm bower这类库管理工具,到整个工程文件愈发庞大,有时候写着代码,工具就死掉了,非常不happy, 探索一番,找到如下解决方案。
打开我们的工程, 找到WebStorm的preference属性后,搜索Directories, 看到下面这个截图,右键Exclude一些文件包,比如node_modules, bower_components, platforms, www等等。
然后点击apply ⇒ ok,就好了,主要原理就是不要加载过大的工程文件,否则工具会吃不消的。
总结
混合应用的开发之路,并不是很容易,个人认为它的难点和复杂度要远远高于native和web,因为我们要掌握的东西实在太多了。