Android启动优化方案

1 启动优化方案概括

在Android应用开发中,应用启动速度对用户体验非常重要,也是一个应用给用户的第一个性能方面的体验。应用启动优化的核心思想就是要快,在启动过程中尽量做少的事。但是应用功能越丰富,模块越多,需要初始化的地方也就越多,导致了应用启动变慢。

启动主要完成了三件事:UI布局,绘制和数据准备。因此启动速度优化也是需要优化这三个过程。接下来会通过这几个方面进行分析。

2 UI布局优化

启动过程为:启动应用→Application 初始化→AppstartActivity→HomePageActivity。AppstartActivity 是应用的闪屏页(也叫启动页),我们看到大部分应用都有这么一个页面,为什么要有闪屏页呢?闪屏页的存在主要有两个好处:一是可以作为品牌宣传展示,如节日运营或热点事件运营,也可以做广告展示(不要太低端);其二,因为闪屏一般需要停留一段时间,在这段时间可以做很多事情,比如底层模块的初始化、数据的预拉取等。首先需要优化 AppstartActivity 的布局,从前面的章节可以知道,要提高显示的效率,一是减少布局层级,二是避免过度绘制,因为前面已经有很详细的例子了,这里不做过多介绍,优化的步骤如下:
1.使用 Profile GPU Rendering 检查启动时是否有严重的掉帧
2.使用 Hierarchy View 检查布局文件,分析布局并优化

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);
    }

接下来我们使用TraceView来分析一下性能如何

在这里插入图片描述

数据送到显示器上更新需要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。

  • Application级别

在 Android Manifest 文件中添加属性,整个应用全局使用硬件加速

<application android:hardwareAccelerated="true/false">
  • Activity级别

对 Activity 进行单独控制。可以启动或者禁用一个 Activity 的硬件加速,即使设置过全局打开硬件加速,也可以在某个 Activity 中禁用。

<activity android:hardwareAccelerated="true/false">
  • Window级别

更细粒度的控制,通过如下代码可以对某个 window 进行加速:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  • View级别

对单独的 View 控制硬件加速。

View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

在编写代码中可以检查当前View是否已经硬件加速,可以使用
view.isHardware-Accelerated(),或者 Canvas.isHardwareAccelerated()来得到结果。即使 View是存在于一个已经加速的 Windows 上,仍然可以使用没有硬件加速的 Canvas 进行绘制。

layerType 如果设置成 Software,会将整个 View 绘制到一个 Bitmap 中,然后依然是通过硬件加速的方式将这个 Bitmap 绘制到 Canvas。

6.3 在动画上使用硬件加速

如果每秒没有达到 60 帧,就有可能发生卡顿现象,对一个 View实现动画,本身就有很多绘制的操作,再加上 View 本身很复杂,不一定能保持达到 60FPS。
因此可以通过硬件加速来渲染提高动画的性能,实现更为快速、平滑的效果。硬件纹理操作对一个 View 进行动画绘制,如果不调用 invalidate()方法,就可以减少对 View 自身频繁的重绘。同时 Android 3.0 的属性动画也减小了重绘,当 View 通过硬件层返回时,最终所有的层叠画面显示到屏幕,View 的属性同时被处理好,因此只要设置这些属性,就可以明显提高绘制的效率,它们不需要 View 重绘,设置属性后,View 会自动刷新,这也是为什么属性动画中绘制的递归比补间动画少很多的原因,而打开硬件加速,动画会更平滑、流畅。

Android 3.0 以前的版本也有能力对离屏缓冲进行渲染,可以使用 View 的绘制缓冲,或者 Canvas.saveLayer()函数。离屏缓冲或者 Layer 能够更高效地处理复杂 view 的动画效果或者 UI 合成效果。Android 3.0 开始对何时以及如何使用层有了更多的控制,也就是新的离屏缓冲方式 View.setLayerType(type,paint)方法,这个 API 所带的两个参数一个是使用的层类型,另外一个是可选参数 Paint。可以把 Paint 参数应用到颜色过滤上,特别是混合模式或者是对一个 Layer 进行不透明处理。一个 View 可以使用如下三种 Layer 类型之一

  • LAYER_TYPE_NONE:普通渲染方式,不会返回一个离屏的缓冲,默认值。
  • LAYER_TYPE_HARDWARE:如果这个应用使用了硬件加速,这个 View 将会在硬件中 渲 染 为 硬 件 纹 理 , 如 果 应 用 程 序 并 没 有 被 硬 件 加 速 , 则 其 效 果 和LAYER_TYPE_SOFTWARE 相同。
  • LAYER_TYPE_SOFTWARE:此 View 通过软件渲染为一个 Bitmap
  • 为上面的属性动画添加硬件加速
imageView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 1);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0, 1);
        ObjectAnimator rotation = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
        rotation.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
				imageView.setLayerType(View.LAYER_TYPE_NONE, null);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(3000);
        animatorSet.play(scaleX).with(scaleY).with(rotation);
        animatorSet.start();
  • 根据前面的描述,总结出设计一个动画的流程如下:
  • 将要执行动画的 View 的 LayerType 设置为 LAYER_TYPE_HARDWARE。
  • 计算动画 View 的属性等信息,更新 View 的属性。
  • 若动画结束,将 LayerType 设置为 NONE。
  • 虽然硬件加速可以带来更好的绘制效果,但也有一些问题,以下几点需要注意:
  • 在软件渲染时,可以使用重用 Bitmap 的方法来节省内存,但是如果开启了硬件加速,这个方案就不起作用。
  • 开启硬件加速的 View 在前台运行时,需要耗费额外的内存,加速的 UI 切换到后台时,产生的额外内存有可能不释放。
  • 当 UI 中存在过渡绘制时,硬性加速会比较容易发生问题,所以尽量减少过度绘制
发布了119 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/100084544