Android启动优化⽅案
canvas动画⽂章⽬录
1 启动优化⽅案概括
在Android应⽤开发中,应⽤启动速度对⽤户体验⾮常重要,也是⼀个应⽤给⽤户的第⼀个性能⽅⾯的体验。应⽤启动优化的核⼼思想就是要快,在启动过程中尽量做少的事。但是应⽤功能越丰富,模块越多,需要初始化的地⽅也就越多,导致了应⽤启动变慢。
启动主要完成了三件事:UI布局,绘制和数据准备。因此启动速度优化也是需要优化这三个过程。接下来会通过这⼏个⽅⾯进⾏分析。
2 UI布局优化
启动过程为:启动应⽤→Application 初始化→AppstartActivity→HomePageActivity。AppstartActivity 是应⽤的闪屏页(也叫启动页),我们看到⼤部分应⽤都有这么⼀个页⾯,为什么要有闪屏页呢?闪屏页的存在主要有两个好处:⼀是可以作为品牌宣传展⽰,如节⽇运营或热点事件运营,也可以做⼴告展⽰(不要太低端);其⼆,因为闪屏⼀般需要停留⼀段时间,在这段时间可以做很多事情,⽐如底层模块的初始化、数据的预拉取等。⾸先需要优化 AppstartActivity 的布局,从前⾯的章节可以知道,要提⾼显
⽰的效率,⼀是减少布局层级,⼆是避免过度绘制,因为前⾯已经有很详细的例⼦了,这⾥不做过多介绍,优化的步骤如下:
3 启动加载逻辑优化
⼀个应⽤越⼤,涉及的模块越多,包含的服务甚⾄进程就会越多,如⽹络模块的初始化、底层数据初始化等,这些加载都需要提前准备好,有些不必要的就不要放到应⽤中。可以⽤以下四个维度分整理启动的各个点:
必要且耗时:启动初始化, 考虑⽤线程来初始化。
必要不耗时:⾸页绘制。
⾮必要耗时:数据上报、 插件初始化。
⾮必要不耗时:不⽤想,这块直接去掉,在需要⽤的时再加载。
把数据整理出来后,按需实现加载逻辑,采取分步加载、异步加载、延期加载策略
提⾼应⽤的启动速度,核⼼思想是在启动过程中少做事情,越少越好。
提⽰ 在应⽤中,增加启动默认图或者⾃定义⼀个 Theme,在 Activity ⾸先使⽤
⼀个默认的界⾯可以解决部分启动短暂⿊屏问题,如:
<application
android:name=".KApplication"
android:theme="@style/..."
…
4 合理的刷新机制
在应⽤开发的过程中,因为数据的变化,需要刷新页⾯来展⽰新的数据,但频繁刷新会增加资源开销,并且可能导致卡顿发⽣,所以,需要⼀个合理的刷新机制来提⾼整体的 UI流畅度。合理的刷新需要注意以下⼏点:
尽量减少刷新次数。
尽量避免后台有⾼CPU线程运⾏
缩⼩刷新区域
4.1 减少刷新次数
毫⽆疑问,减少刷新次数可以减少系统的开销,在功耗和页⾯的性能上可以表现得更优秀,但不刷新就不能及时让⽤户看到最新的数据,可以从以下⼏个⽅⾯减少刷新次数
控制刷新频率
在有些功能上需要频繁刷新某个控件(View),⽐如下载进度条或者播放进度条,没有必要在数据每次变化时都更新对应的控件,需要注意的是⼀定不能出现过度刷新(进度刷新频率⼤于系统显⽰的刷新频率)的情况。可以通过定时控制刷新频率,相同的刷新只做⼀次,⽐如播放进度条的刻度是100,如果数据变化没有 1%,完全没有必要刷新,这样可以减少 UI的刷新负担。
避免没有必要的刷新
⾸先需要判断是否需要刷新,⽐如数据没有变化、需要刷新的控(View)不在可见区域,就没有必要刷新。但是需要注意,如果⼀个View 从不可见到可见,⼀定要刷新⼀次。⼀般来说,在第⼀⽚数据(从⽆到有)和最后⼀⽚数据(结束了⼀个刷新周期)时,⼀定要刷新⼀次,以保证完整性。
避免后台线程影响
后台线程虽然不会直接影响到主线程的⼯作,但如果后台线程开销很⼤,占⽤ CPU 过⾼,导致系统 GC 频繁和 CPU 时间⽚资源紧张,还是有可能会导致页⾯的卡顿。因此在需要迅速刷新的情况下避免这类线程在⾼峰⼯作。⽐如 ListView 的滚动,如果 ListView 中的 Item需要下载图⽚显⽰,在 ListView 滚动时可以暂停其他 UI 的操作
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
// pause true
} else {
// pause false
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
4.2 缩⼩刷新区域
⼀是在⾃定义 View 中。⾃定义 View ⼀般采⽤ invalidata ⽅法刷新。如果需要更新的数据只是在某⼀个区域内改变,在调⽤invalidata ⽅法更新这个区域时,也会更新整个视图,这就浪费了不需要更新区域资源。Android 提供系统了两个局部更新数据的⽅法
invalidate(Rect dirty);
invalidate(int left,int top,int right,int bottom);
不过在API23以后舍弃了这两个⽅法,开启硬件加速后,不需要关注脏区域;在21以上版本invalidate(Rect)等效于invalidate()全局刷新,并且更推荐使⽤invalidate();
第⼆种是容器中的某个 Item 发⽣了变化,只需要更新这⼀个 Item 即可。⽐如在 ListView中,如果是单条操作,就必须调⽤Adapter 的notifyDataSetChanged()刷新。
5 提升动画性能
在打造优秀体验的应⽤和实现酷炫效果的过程中,动画是不可或缺的重要组成部分。Android 平台提供了三个动画框架:帧动画(Frame Animation)、补间动画(Tween Animation)和属性动画(Property Animation)。属性动画在 Android 3.0 开始⽀持,开发者使⽤这些动画框架来实现各种动画效果,这三个框架都有其优势和局限性,要深⼊了解就需要明⽩它们的实现原理。
虽然通过动画可以实现很多酷炫的动画,但带来的性能开销也有不同程度的影响。在实现动画的过程中,主要从以下三个纬度来对⽐性能
流畅度:流畅度是动画的核⼼,控制每⼀帧动画在 16ms 以内完成。
内存:避免内存泄漏,减⼩内存开销。
耗电:减⼩运算量,优化算法,减⼩ CPU 占⽤。
5.1 帧动画
帧动画很简单,就是将⼀组图⽚按顺序显⽰出来,可以使⽤AnimationDrawable 来定义使⽤帧动画。但由于帧动画要求图⽚过多,如果图⽚较⼤,内存占⽤就⾮常⼤,效果也⽐较差,除⾮图⽚⾜够多。所以帧动画是消耗资源最多,效果最差的⼀种,这⾥就不再介绍使⽤⽅法,能不⽤就不⽤。
5.2 补间动画
补间动画是通过对某个 View 进⾏⼀系列的操作来改变显⽰效果,对⽐逐帧动画,它的使⽤更简单⽅便。补间动画不需要定义时间频率内的每⼀帧,只需要定义开始和结束关键帧的内容,两个关键帧之间的效果⾃动⽣成,使⽤时不需要另写代码控制,⽽逐帧动画的帧与帧之间的过渡并不连贯,并且消耗更多的内存。补间动画⽀持淡⼊淡出(AlphaAnimation)、缩放(ScaleAnimation)、平移(TranslationAnimation)和旋转(RotateAnimation)4 种动画模式。
以缩放和旋转两种动画来实现ImageView由⼩到⼤并同步旋转出来的效果代码如下:
private void tweenAni() {
Animation ani = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
ScaleAnimation scaleAnimation = new ScaleAnimation(0f, 1f, 0f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(ani);
animationSet.addAnimation(scaleAnimation);
animationSet.setDuration(3000);
imageView.startAnimation(animationSet);
}
接下来我们使⽤
数据送到显⽰器上更新需要DisplayList,所以我们来看⼀下android.view.View.updateDisplayListIfDirty()⽅法,通过
Calls+Recur Calls参数我们可以看到,这个⽅法调⽤了42次递归调⽤了167次,说明补间动画导致View重绘⾮常频繁。
5.3 属性动画
属性动画由 Android 3.0(API 11)及更⾼版本⽀持,通过修改动画的实际属性来实现动画效果。属性动画系统是⼀个⾮常全⾯的框架,⼏乎允许把任何对象变成动画。可以根据时间的推移通过改变任何对象的属性来定义⼀个动画,并且不⽤关⼼该对象是否要绘制在屏幕上,在指定的时间长度改变⼀个属性的值。属性动画只是表⽰⼀个值在⼀段时间内的改变,当值改变时要做什么事情完全由⽤户⾃⼰决定。相⽐于补间动画,属性动画不仅可以应⽤于View,还可以应⽤于任何对象。
属性动画系统可以定义以下动画特性:
持续时间(Duration):指定动画从开始到结束的持续时间。默认长度是 300ms
时间插值(Time interpolation):这个值能够指定为计算当前动画运⾏时间的函数的属性值它决定动画时间范围内的变化频率。
重复次数和⾏为(Repeat count and behavior):指定在动画结束时是否重新播放动画,以及重复播放的次数。还能够指定动画是否能够反向回播,如果设置了反向回播,动画就会先向前再向后,重复播放,在达到播放次数时结束。
动画集合(Animator sets):把动画组织到⼀个逻辑集合中,可以同时、指定播放顺序的或者延迟播放它们。
帧刷新延迟(Frame refresh delay):指定动画帧的刷新频率。默认是每 10 秒刷新⼀次,但是应⽤程序最终的刷新帧的速度取决于系统的繁忙程度以及系统能够提供的底层定时器的反应速度。
运⾏后启动动画,为了与补间动画进⾏对⽐,再使⽤TraceView 来看看 android.view.View.updateDisplayListIfDirty()
通过Calls + Recur Calls参数可以看到android.view.View.updateDisplayListIfDirty()⽅法被调⽤了8次递归20次,这说明相对于补间动画,属性动画明显重绘次数少很多,性能也更优秀,所以在实际使⽤中应该优先考虑属性动画。
虽然属性动画很好,但是还有重绘的情况,那么想再次提⾼动画性能,可以通过硬件加速概念,可以提⾼渲染速度,提⾼性能。
6 硬件加速
6.1 硬件加速原理
在硬件加速的渲染模型中有⼀个重要的核⼼类:DisplayList,每个 View 内部都会维护⼀个 DisplayList。在不⽀持硬件渲染的Android 版本中,系统是通过 draw()⽅法或 invalidate()⽅法去通知屏幕更新并重新渲染,这两个⽅法的区别只是实际处理绘制的⽅式不同;⽽在⽀持硬件渲染的 Android 版本中,在打开硬件渲染后绘制 View 时,其中执⾏绘制的 draw()⽅法会把所有绘制命令记录到⼀个新的显⽰列表(DisplayList),这个显⽰列表(DisplayList)包含了输出的 View 层级的绘制代码,但并不是加⼊到显⽰列表(DisplayList)就⽴刻执⾏,当这个ViewTree的DisplayList全都记录完毕后,由OpenGLRender负责将Root View中的DisplayList渲染到屏幕上。⽽ invalidate()⽅法只是在显⽰列表中记录和更新显⽰层级,去标记不需要绘制的 View
通过上图所⽰的流程图可以看出,使⽤ DisplayList,如果只有⼀个 View 有变化,只需要重绘这个 View,因此可以减少很多的绘图操作次数,仅仅需要重绘⼀个 View 的DisplayList,就能明显提⾼渲染效率。硬件渲染模型同时也存在⼀个问题,当⼀个 View 和脏区域有交集时,不⼀定会执⾏ draw()
⽅法。确保 Android 系统记录了⼀个 View 的显⽰列表,如果没有调⽤ invalidate()⽅法,即使 View 发⽣了变化,看起来也会相同,这是需要注意的地⽅。
6.2 硬件加速控制级别
使⽤⾃定义控件造成影响,为了避免这个问题,Android 提供了⼀个级别控制,这样可以选择启动或者禁⽤以下不同级别的硬件加速,⽬前可以控制Application、Activity、Window 和View。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论