精华内容
下载资源
问答
  • 确定货币系统典范性的一种有效方法,张生,梁国宏,给出了确定货币系统 典范性的一种新方法,其基本思想是对满足 的每个 , 由贪婪算法分别求出 关于货币系统 的贪婪表示 .若对满足上�
  • 多输入线性系统的广义可控性典范形式
  • 直销制度管理典范范本拥有着完美的一致、社会、明确和层次的特性,喜欢直销制度管理典范范本的朋...该文档为直销制度管理典范范本,是一份很不错的参考资料,具有较高参考价值,感兴趣的可以下载看看
  • Android性能优化典范(五)

    千次阅读 2016-04-29 19:38:31
    作者简介: 胡凯(@胡凯me),腾讯Android工程师,热爱开源与分享,维护Android官方培训课程协作项目,关注Android应用性能优化的...序言这是Android性能优化典范第5季的课程学习笔记,拖拖拉拉很久,记录分享给...

    作者简介:

    胡凯(@胡凯me),腾讯Android工程师,热爱开源与分享,维护Android官方培训课程协作项目,关注Android应用性能优化的总结与分享,推崇Android官方最佳实践。个人博客:http://hukai.me,Github:https://github.com/kesenhoo

    序言

    这是Android性能优化典范第5季的课程学习笔记,拖拖拉拉很久,记录分享给大家,请多多包涵、担待指正!文章共有10个段落,涉及的内容有:多线程并发的性能问题,介绍了AsyncTask、HandlerThread、IntentService与ThreadPool分别适合的使用场景以及各自的使用注意事项。这是一篇了解Android多线程编程不可多得的基础文章,清楚地了解这些Android系统提供的多线程基础组件之间的差异以及优缺点,才能够在项目实战中做出最恰当的选择。

    系列阅读

    • Android 性能优化典范(一):主要从 Android 的渲染机制、内存与 GC、电量优化三个方面展开,介绍了 Android 中性能问题的底层工作原理,以及如何通过工具来找出性能问题及提升性能的建议。

    • Android 性能优化典范(二):20 个短视频,主要内容为:电量优化、网络优化、Android Wear 上如何做优化、使用对象池来提高效率、LRU Cache、Bitmap 的缩放、缓存、重用、PNG 压缩、自定义 View 的性能、提升设置 alpha 之后 View 的渲染性能,以及 Lint、StictMode 等工具的使用技巧。

    • Android 性能优化典范(三):更高效的 ArrayMap 容器,使用 Android 系统提供的特殊容器来避免自动装箱,避免使用枚举类型,注意onLowMemoryonTrimMemory的回调,避免内存泄漏,高效的位置更新操作,重复 layout 操作的性能影响,以及使用 Batching,Prefetching 优化网络请求,压缩传输数据等使用技巧。

    • Android 性能优化典范(四):优化网络请求的行为,优化安装包的资源文件,优化数据传输的效率,性能优化的几大基础原理等。

    • Android 性能优化典范(五):文章共有10个段落,涉及的内容有:多线程并发的性能问题,介绍了 AsyncTask、HandlerThread、IntentService 与 ThreadPool 分别适合的使用场景以及各自的使用注意事项。这是一篇了解 Android 多线程编程不可多得的基础文章,清楚地了解这些 Android 系统提供的多线程基础组件之间的差异以及优缺点,才能够在项目实战中做出最恰当的选择。

    • Android 性能优化典范(六):文章共 6 个段落,涉及的内容主要有程序启动时间性能优化的三个方面:优化 activity 的创建过程,优化 Application 对象的启动过程,正确使用启动显屏达到优化程序启动性能的目的。另外还介绍了减少安装包大小的 checklist 以及如何使用 VectorDrawable 来减少安装包的大小。


    1) Threading Performance

    在程序开发的实践当中,为了让程序表现得更加流畅,我们肯定会需要使用到多线程来提升程序的并发执行性能。但是编写多线程并发的代码一直以来都是一个相对棘手的问题,所以想要获得更佳的程序性能,我们非常有必要掌握多线程并发编程的基础技能。

    众所周知,Android程序的大多数代码操作都必须执行在主线程,例如系统事件(例如设备屏幕发生旋转),输入事件(例如用户点击滑动等),程序回调服务,UI绘制以及闹钟事件等等。那么我们在上述事件或者方法中插入的代码也将执行在主线程。

    android_perf_5_threading_main_thread

    一旦我们在主线程里面添加了操作复杂的代码,这些代码就很可能阻碍主线程去响应点击/滑动事件,阻碍主线程的UI绘制等等。我们知道,为了让屏幕的刷新帧率达到60fps,需要确保16ms内完成单次刷新的操作。一旦我们在主线程里面执行的任务过于繁重就可能导致接收到刷新信号的时候,会因为资源被占用而无法完成这次刷新操作。由此导致产生掉帧的现象,刷新帧率自然也就跟着下降了(一旦刷新帧率降到20fps左右,用户就可以明显感知到卡顿不流畅了)。

    android_perf_5_threading_dropframe

    为了避免上面提到的掉帧问题,我们需要使用多线程的技术方案,把那些操作复杂的任务移动到其他线程当中执行,这样就不容易阻塞主线程的操作,也就减小了出现掉帧的可能性。

    android_perf_5_threading_workthread

    那么问题来了,为主线程减轻负的多线程方案有哪些呢?这些方案分别适合在什么场景下使用?Android系统为我们提供了若干组工具类来帮助解决这个问题。

    • AsyncTask: 为UI线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
    • HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
    • ThreadPool: 把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
    • IntentService: 适合于执行由UI触发的后台Service任务,并可以把后台任务执行的情况通过一定的机制反馈给UI。

    了解这些系统提供的多线程工具类分别适合在什么场景下,可以帮助我们选择合适的解决方案,避免出现不可预期的麻烦。虽然使用多线程可以提高程序的并发量,但是我们需要特别注意因为引入多线程而可能伴随而来的内存问题。举个例子,在Activity内部定义的一个AsyncTask,它属于一个内部类,该类本身和外面的Activity是有引用关系的,如果Activity要销毁的时候,AsyncTask还仍然在运行,这会导致Activity没有办法完全释放,从而引发内存泄漏。所以说,多线程是提升程序性能的有效手段之一,但是使用多线程却需要十分谨慎小心,如果不了解背后的执行机制以及使用的注意事项,很可能引起严重的问题。

    2) Understanding Android Threading

    通常来说,一个线程需要经历三个生命阶段:开始、执行、结束。线程会在任务执行完毕之后结束,那么为了确保线程的存活,我们会在执行阶段给线程赋予不同的任务,然后在里面添加退出的条件从而确保任务能够执行完毕后退出。

    android_perf_5_thread_lifecycle

    在很多时候,线程不仅仅是线性执行一系列的任务就结束那么简单的,我们会需要增加一个任务队列,让线程不断的从任务队列中获取任务去进行执行,另外我们还可能在线程执行的任务过程中与其他的线程进行协作。如果这些细节都交给我们自己来处理,这将会是件极其繁琐又容易出错的事情。

    android_perf_5_thread_thread

    所幸的是,Android系统为我们提供了Looper、Handler、MessageQueue来帮助实现上面的线程任务模型:

    Looper: 能够确保线程持续存活并且可以不断的从任务队列中获取任务并进行执行。

    android_perf_5_thread_looper

    Handler: 能够帮助实现队列任务的管理,不仅仅能够把任务插入到队列的头部,尾部,还可以按照一定的时间延迟来确保任务从队列中能够来得及被取消掉。

    android_perf_5_thread_handler

    MessageQueue: 使用Intent,Message,Runnable作为任务的载体在不同的线程之间进行传递。

    android_perf_5_thread_messagequeue

    把上面三个组件打包到一起进行协作,这就是HandlerThread

    android_perf_5_thread_handlerthread

    我们知道,当程序被启动,系统会帮忙创建进程以及相应的主线程,而这个主线程其实就是一个HandlerThread。这个主线程会需要处理系统事件、输入事件、系统回调、UI绘制等等任务,为了避免主线程任务过重,我们就会需要不断地开启新的工作线程来处理那些子任务。

    3) Memory & Threading

    增加并发的线程数会导致内存消耗的增加,平衡好这两者的关系是非常重要的。我们知道,多线程并发访问同一块内存区域有可能带来很多问题,例如读写的权限争夺问题,ABA问题等等。为了解决这些问题,我们会需要引入的概念。

    在Android系统中,也无法避免因为多线程的引入,而导致出现诸如上文提到的种种问题。Android UI对象的创建、更新、销毁等等操作都默认是执行在主线程,但是如果我们在非主线程对UI对象进行操作,程序将可能出现异常甚至是崩溃。

    android_perf_5_memory_thread_update

    另外,在非UI线程中直接持有UI对象的引用也很可能出现问题。例如Work线程中持有某个UI对象的引用,在Work线程执行完毕之前,UI对象在主线程中被从ViewHierarchy中移除了,这个时候UI对象的任何属性都已经不再可用了,另外对这个UI对象的更新操作也都没有任何意义了,因为它已经从ViewHierarchy中被移除,不再绘制到画面上了。

    android_perf_5_memory_view_remove

    不仅如此,View对象本身对所属的Activity是有引用关系的,如果工作线程持续保有View的引用,这就可能导致Activity无法完全释放。除了直接显式的引用关系可能导致内存泄露之外,我们还需要特别留意隐式的引用关系也可能导致泄露。例如通常我们会看到在Activity里面定义的一个AsyncTask,这种类型的AsyncTask与外部的Activity是存在隐式引用关系的,只要Task没有结束,引用关系就会一直存在,这很容易导致Activity的泄漏。更糟糕的情况是,它不仅仅发生了内存泄漏,还可能导致程序异常或者崩溃。

    android_perf_5_memory_asynctask

    为了解决上面的问题,我们需要谨记的原则就是:不要在任何非UI线程里面去持有UI对象的引用。系统为了确保所有的UI对象都只会被UI线程所进行创建,更新,销毁的操作,特地设计了对应的工作机制(当Activity被销毁的时候,由该Activity所触发的非UI线程都将无法对UI对象进行操作,否者就会抛出程序执行异常的错误)来防止UI对象被错误的使用。

    4) Good AsyncTask Hunting

    AsyncTask是一个让人既爱又恨的组件,它提供了一种简便的异步处理机制,但是它又同时引入了一些令人厌恶的麻烦。一旦对AsyncTask使用不当,很可能对程序的性能带来负面影响,同时还可能导致内存泄露。

    举个例子,常遇到的一个典型的使用场景:用户切换到某个界面,触发了界面上的图片的加载操作,因为图片的加载相对来说耗时比较长,我们需要在子线程中处理图片的加载,当图片在子线程中处理完成之后,再把处理好的图片返回给主线程,交给UI更新到画面上。

    android_perf_5_asynctask_main

    AsyncTask的出现就是为了快速的实现上面的使用场景,AsyncTask把在主线程里面的准备工作放到onPreExecute()方法里面进行执行,doInBackground()方法执行在工作线程中,用来处理那些繁重的任务,一旦任务执行完毕,就会调用onPostExecute()方法返回到主线程。

    android_perf_5_asynctask_mode

    使用AsyncTask需要注意的问题有哪些呢?请关注以下几点:

    • 首先,默认情况下,所有的AsyncTask任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。假设你按照顺序启动20个AsyncTask,一旦其中的某个AsyncTask执行时间过长,队列中的其他剩余AsyncTask都处于阻塞状态,必须等到该任务执行完毕之后才能够有机会执行下一个任务。情况如下图所示:

    android_perf_5_asynctask_single_queue

    为了解决上面提到的线性队列等待的问题,我们可以使用AsyncTask.executeOnExecutor()强制指定AsyncTask使用线程池并发调度任务。

    android_perf_5_asynctask_thread_pool

    • 其次,如何才能够真正的取消一个AsyncTask的执行呢?我们知道AsyncTaks有提供cancel()的方法,但是这个方法实际上做了什么事情呢?线程本身并不具备中止正在执行的代码的能力,为了能够让一个线程更早的被销毁,我们需要在doInBackground()的代码中不断的添加程序是否被中止的判断逻辑,如下图所示:

    android_perf_5_asynctask_cancel

    一旦任务被成功中止,AsyncTask就不会继续调用onPostExecute(),而是通过调用onCancelled()的回调方法反馈任务执行取消的结果。我们可以根据任务回调到哪个方法(是onPostExecute还是onCancelled)来决定是对UI进行正常的更新还是把对应的任务所占用的内存进行销毁等。

    • 最后,使用AsyncTask很容易导致内存泄漏,一旦把AsyncTask写成Activity的内部类的形式就很容易因为AsyncTask生命周期的不确定而导致Activity发生泄漏。

    android_perf_5_memory_asynctask

    综上所述,AsyncTask虽然提供了一种简单便捷的异步机制,但是我们还是很有必要特别关注到他的缺点,避免出现因为使用错误而导致的严重系统性能问题。

    5) Getting a HandlerThread

    大多数情况下,AsyncTask都能够满足多线程并发的场景需要(在工作线程执行任务并返回结果到主线程),但是它并不是万能的。例如打开相机之后的预览帧数据是通过onPreviewFrame()的方法进行回调的,onPreviewFrame()open()相机的方法是执行在同一个线程的。

    android_perf_5_handlerthread_camera_open

    如果这个回调方法执行在UI线程,那么在onPreviewFrame()里面将要执行的数据转换操作将和主线程的界面绘制,事件传递等操作争抢系统资源,这就有可能影响到主界面的表现性能。

    android_perf_5_handlerthread_main_thread2

    我们需要确保onPreviewFrame()执行在工作线程。如果使用AsyncTask,会因为AsyncTask默认的线性执行的特性(即使换成并发执行)会导致因为无法把任务及时传递给工作线程而导致任务在主线程中被延迟,直到工作线程空闲,才可以把任务切换到工作线程中进行执行。

    android_perf_5_handlerthread_asynctask

    所以我们需要的是一个执行在工作线程,同时又能够处理队列中的复杂任务的功能,而HandlerThread的出现就是为了实现这个功能的,它组合了Handler,MessageQueue,Looper实现了一个长时间运行的线程,不断的从队列中获取任务进行执行的功能。

    android_perf_5_handlerthread_outline

    回到刚才的处理相机回调数据的例子,使用HandlerThread我们可以把open()操作与onPreviewFrame()的操作执行在同一个线程,同时还避免了AsyncTask的弊端。如果需要在onPreviewFrame()里面更新UI,只需要调用runOnUiThread()方法把任务回调给主线程就够了。

    android_perf_5_handlerthread_camera

    HandlerThread比较合适处理那些在工作线程执行,需要花费时间偏长的任务。我们只需要把任务发送给HandlerThread,然后就只需要等待任务执行结束的时候通知返回到主线程就好了。

    另外很重要的一点是,一旦我们使用了HandlerThread,需要特别注意给HandlerThread设置不同的线程优先级,CPU会根据设置的不同线程优先级对所有的线程进行调度优化。

    android_perf_5_handlerthread_priority

    掌握HandlerThread与AsyncTask之间的优缺点,可以帮助我们选择合适的方案。

    6) Swimming in Threadpools

    线程池适合用在把任务进行分解,并发进行执行的场景。通常来说,系统里面会针对不同的任务设置一个单独的守护线程用来专门处理这项任务。例如使用Networking Thread用来专门处理网络请求的操作,使用IO Thread用来专门处理系统的I\O操作。针对那些场景,这样设计是没有问题的,因为对应的任务单次执行的时间并不长而且可以是顺序执行的。但是这种专属的单线程并不能满足所有的情况,例如我们需要一次性decode 40张图片,每个线程需要执行4ms的时间,如果我们使用专属单线程的方案,所有图片执行完毕会需要花费160ms(40*4),但是如果我们创建10个线程,每个线程执行4个任务,那么我们就只需要16ms就能够把所有的图片处理完毕。

    android_perf_5_threadpool_1

    为了能够实现上面的线程池模型,系统为我们提供了ThreadPoolExecutor帮助类来简化实现,剩下需要做的就只是对任务进行分解就好了。

    android_perf_5_threadpool_2

    使用线程池需要特别注意同时并发线程数量的控制,理论上来说,我们可以设置任意你想要的并发数量,但是这样做非常的不好。因为CPU只能同时执行固定数量的线程数,一旦同时并发的线程数量超过CPU能够同时执行的阈值,CPU就需要花费精力来判断到底哪些线程的优先级比较高,需要在不同的线程之间进行调度切换。

    android_perf_5_threadpool_3

    一旦同时并发的线程数量达到一定的量级,这个时候CPU在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降。另外需要关注的一点是,每开一个新的线程,都会耗费至少64K+的内存。为了能够方便的对线程数量进行控制,ThreadPoolExecutor为我们提供了初始化的并发线程数量,以及最大的并发数量进行设置。

    android_perf_5_threadpool_4

    另外需要关注的一个问题是:Runtime.getRuntime().availableProcesser()方法并不可靠,他返回的值并不是真实的CPU核心数,因为CPU会在某些情况下选择对部分核心进行睡眠处理,在这种情况下,返回的数量就只能是激活的CPU核心数。

    7) The Zen of IntentService

    默认的Service是执行在主线程的,可是通常情况下,这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的AsyncTask与HandlerThread,我们还可以选择使用IntentService来实现异步操作。IntentService继承自普通Service同时又在内部创建了一个HandlerThread,在onHandlerIntent()的回调里面处理扔到IntentService的任务。所以IntentService就不仅仅具备了异步线程的特性,还同时保留了Service不受主页面生命周期影响的特点。

    android_perf_5_intentservice_outline

    如此一来,我们可以在IntentService里面通过设置闹钟间隔性的触发异步任务,例如刷新数据,更新缓存的图片或者是分析用户操作行为等等,当然处理这些任务需要小心谨慎。

    使用IntentService需要特别留意以下几点:

    • 首先,因为IntentService内置的是HandlerThread作为异步线程,所以每一个交给IntentService的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

    • 其次,通常使用到IntentService的时候,我们会结合使用BroadcastReceiver把工作线程的任务执行结果返回给主UI线程。使用广播容易引起性能问题,我们可以使用LocalBroadcastManager来发送只在程序内部传递的广播,从而提升广播的性能。我们也可以使用runOnUiThread()快速回调到主UI线程。

    • 最后,包含正在运行的IntentService的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。

    8) Threading and Loaders

    当启动工作线程的Activity被销毁的时候,我们应该做点什么呢?为了方便的控制工作线程的启动与结束,Android为我们引入了Loader来解决这个问题。我们知道Activity有可能因为用户的主动切换而频繁的被创建与销毁,也有可能是因为类似屏幕发生旋转等被动原因而销毁再重建。在Activity不停的创建与销毁的过程当中,很有可能因为工作线程持有Activity的View而导致内存泄漏(因为工作线程很可能持有View的强引用,另外工作线程的生命周期还无法保证和Activity的生命周期一致,这样就容易发生内存泄漏了)。除了可能引起内存泄漏之外,在Activity被销毁之后,工作线程还继续更新视图是没有意义的,因为此时视图已经不在界面上显示了。

    android_perf_5_loader_bad

    Loader的出现就是为了确保工作线程能够和Activity的生命周期保持一致,同时避免出现前面提到的问题。

    android_perf_5_loader_good

    LoaderManager会对查询的操作进行缓存,只要对应Cursor上的数据源没有发生变化,在配置信息发生改变的时候(例如屏幕的旋转),Loader可以直接把缓存的数据回调到onLoadFinished(),从而避免重新查询数据。另外系统会在Loader不再需要使用到的时候(例如使用Back按钮退出当前页面)回调onLoaderReset()方法,我们可以在这里做数据的清除等等操作。

    在Activity或者Fragment中使用Loader可以方便的实现异步加载的框架,Loader有诸多优点。但是实现Loader的这套代码还是稍微有点点复杂,Android官方为我们提供了使用Loader的示例代码进行参考学习。

    9) The Importance of Thread Priority

    理论上来说,我们的程序可以创建出非常多的子线程一起并发执行的,可是基于CPU时间片轮转调度的机制,不可能所有的线程都可以同时被调度执行,CPU需要根据线程的优先级赋予不同的时间片。

    android_perf_5_threadpriority_CPU

    Android系统会根据当前运行的可见的程序和不可见的后台程序对线程进行归类,划分为forground的那部分线程会大致占用掉CPU的90%左右的时间片,background的那部分线程就总共只能分享到5%-10%左右的时间片。之所以设计成这样是因为forground的程序本身的优先级就更高,理应得到更多的执行时间。

    android_perf_5_threadpriority_90

    默认情况下,新创建的线程的优先级默认和创建它的母线程保持一致。如果主UI线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占CPU资源,需要把这些线程的优先级进行降低处理,这样才能给帮组CPU识别主次,提高主线程所能得到的系统资源。

    android_perf_5_threadpriority_less

    在Android系统里面,我们可以通过android.os.Process.setThreadPriority(int)设置线程的优先级,参数范围从-20到24,数值越小优先级越高。Android系统还为我们提供了以下的一些预设值,我们可以通过给不同的工作线程设置不同数值的优先级来达到更细粒度的控制。

    android_perf_5_threadpriority_const

    大多数情况下,新创建的线程优先级会被设置为默认的0,主线程设置为0的时候,新创建的线程还可以利用THREAD_PRIORITY_LESS_FAVORABLE或者THREAD_PRIORITY_MORE_FAVORABLE来控制线程的优先级。

    android_perf_5_threadpriority_value

    Android系统里面的AsyncTask与IntentService已经默认帮助我们设置线程的优先级,但是对于那些非官方提供的多线程工具类,我们需要特别留意根据需要自己手动来设置线程的优先级。

    android_perf_5_threadpriority_asynctask

    android_perf_5_threadpriority_intentservice

    10) Profile GPU Rendering : M Update

    从Android M系统开始,系统更新了GPU Profiling的工具来帮助我们定位UI的渲染性能问题。早期的CPU Profiling工具只能粗略的显示出Process、Execute、Update三大步骤的时间耗费情况。

    android_perf_5_gpu_profiling_old

    但是仅仅显示三大步骤的时间耗费情况,还是不太能够清晰帮助我们定位具体的程序代码问题,所以在Android M版本开始,GPU Profiling工具把渲染操作拆解成如下8个详细的步骤进行显示。

    android_perf_5_gpu_profiling_8steps

    旧版本中提到的Process、Execute、Update还是继续得到了保留,他们的对应关系如下:

    android_perf_5_gpu_profiling_3steps

    接下去我们看下其他5个步骤分别代表了什么含义:

    • Sync & Upload:通常表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片本身的大小。

    • Measure & Layout:这里表示的是布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题。

    • Animation:表示的是计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等。

    • Input Handling:表示的是系统处理输入事件所耗费的时间,粗略等于对于的事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作。

    • Misc/Vsync Delay:如果稍加注意,我们可以在开发应用的Log日志里面看到这样一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。这意味着我们在主线程执行了太多的任务,导致UI渲染跟不上vSync的信号而出现掉帧的情况。

    上面8种不同的颜色区分了不同的操作所耗费的时间,为了便于我们迅速找出那些有问题的步骤,GPU Profiling工具会显示16ms的阈值线,这样就很容易找出那些不合理的性能问题。再仔细看对应具体哪个步骤相对来说耗费时间比例更大,结合上面介绍的细化步骤,从而快速定位问题,修复问题。

    本文为CSDN特约文章,未经允许不得转载,如需转载请联系mobile#csdn.net(#换成@)

    2016年5月13日-15日,由CSDN重磅打造的2016中国云计算技术大会(CCTC 2016)将于5月13日-15日在北京举办,今年大会特设“中国Spark技术峰会”、“Container技术峰会”、“OpenStack技术峰会”、“大数据核心技术与应用实战峰会”四大技术主题峰会,以及“云计算核心技术架构”、“云计算平台构建与实践”等专场技术论坛。大会讲师阵容囊括Intel、微软、IBM、AWS、Hortonworks、Databricks、Elastic、百度、阿里、腾讯、华为、乐视、京东、小米、微博、迅雷、国家电网、中国移动、长安汽车、广发证券、民生银行、国家超级计算广州中心等60+顶级技术讲师,CCTC必将是中国云计算技术开发者的顶级盛会,详情请访问CCTC 2016官网

    展开全文
  • Android性能优化典范(四)

    千次阅读 2016-01-12 17:46:25
    作者简介:胡凯(@胡凯me),腾讯Android工程师,热爱开源与分享,维护Android官方培训课程协作项目,关注Android应用性能优化的...Android性能优化典范第4季的课程学习笔记终于在2015年的最后一天完成了(并于2016...

    作者简介:胡凯(@胡凯me),腾讯Android工程师,热爱开源与分享,维护Android官方培训课程协作项目,关注Android应用性能优化的总结与分享,推崇Android官方最佳实践。个人博客:http://hukai.me,Github:https://github.com/kesenhoo

    图片描述

    Android性能优化典范第4季的课程学习笔记终于在2015年的最后一天完成了(并于2016年1月12日正式发布在CSDN上),文章共17个段落,包含的内容大致有:优化网络请求的行为,优化安装包的资源文件,优化数据传输的效率,性能优化的几大基础原理等等。因为学习认知水平有限,肯定存在不少理解偏差甚至错误的地方,请多多交流指正!

    系列阅读

    • Android 性能优化典范(一):主要从 Android 的渲染机制、内存与 GC、电量优化三个方面展开,介绍了 Android 中性能问题的底层工作原理,以及如何通过工具来找出性能问题及提升性能的建议。

    • Android 性能优化典范(二):20 个短视频,主要内容为:电量优化、网络优化、Android Wear 上如何做优化、使用对象池来提高效率、LRU Cache、Bitmap 的缩放、缓存、重用、PNG 压缩、自定义 View 的性能、提升设置 alpha 之后 View 的渲染性能,以及 Lint、StictMode 等工具的使用技巧。

    • Android 性能优化典范(三):更高效的 ArrayMap 容器,使用 Android 系统提供的特殊容器来避免自动装箱,避免使用枚举类型,注意onLowMemoryonTrimMemory的回调,避免内存泄漏,高效的位置更新操作,重复 layout 操作的性能影响,以及使用 Batching,Prefetching 优化网络请求,压缩传输数据等使用技巧。

    • Android 性能优化典范(四):优化网络请求的行为,优化安装包的资源文件,优化数据传输的效率,性能优化的几大基础原理等。

    • Android 性能优化典范(五):文章共有10个段落,涉及的内容有:多线程并发的性能问题,介绍了 AsyncTask、HandlerThread、IntentService 与 ThreadPool 分别适合的使用场景以及各自的使用注意事项。这是一篇了解 Android 多线程编程不可多得的基础文章,清楚地了解这些 Android 系统提供的多线程基础组件之间的差异以及优缺点,才能够在项目实战中做出最恰当的选择。

    • Android 性能优化典范(六):文章共 6 个段落,涉及的内容主要有程序启动时间性能优化的三个方面:优化 activity 的创建过程,优化 Application 对象的启动过程,正确使用启动显屏达到优化程序启动性能的目的。另外还介绍了减少安装包大小的 checklist 以及如何使用 VectorDrawable 来减少安装包的大小。


    1) Cachematters for networking

    想要使得Android系统上的网络访问操作更加的高效就必须做好网络数据的缓存,这是提高网络访问性能最基础的步骤之一。从手机的缓存中直接读取数据肯定比从网络上获取数据要更加的便捷高效,特别是对于那些会被频繁访问到的数据,需要把这些数据缓存到设备上,以便更加快速的进行访问。

    Android系统上关于网络请求的Http Response Cache是默认关闭的,这样会导致每次即使请求的数据内容是一样的也会需要重复被调用执行,效率低下。我们可以通过下面的代码示例开启HttpResponseCache

    图片描述

    开启Http Response Cache之后,Http操作相关的返回数据就会缓存到文件系统上,不仅仅是主程序自己编写的网络请求相关的数据会被缓存,另外引入的library库中的网络相关的请求数据也会被缓存到这个Cache中。

    网络请求的场景有可以是普通的http请求,也可以打开某个URL去获取数据,如下图所示:

    图片描述

    我们有两种方式来清除HttpResponseCache的缓存数据:第一种方式是缓存溢出的时候删除最旧最老的文件,第二种方式是通过Http返回Header中的Cache-Control字段来进行控制的。如下图所示:

    图片描述

    通常来说,HttpResponseCache会缓存所有的返回信息,包括实际的数据与Header的部分.一般情况下,这个Cache会自动根据协议返回Cache-Control的内容与当前缓存的数据量来决定哪些数据应该继续保留,哪些数据应该删除。但是在一些极端的情况下,例如服务器返回的数据没有设置Cache废弃的时间,或者是本地的Cache文件系统与返回的缓存数据有冲突,或者是某些特殊的网络环境导致HttpResponseCache工作异常,在这些情况下就需要我们自己来实现Http的缓存Cache。

    实现自定义的http缓存,需要解决两个问题:第一个是实现一个DiskCacheManager,另外一个是制定Cache的缓存策略。关于DiskCacheManager,我们可以扩展Android系统提供的DiskLruCache来实现。而Cache的缓存策略,相对来说复杂一些,我们可能需要把部分JSON数据设计成不能缓存的,另外一些JSON数据设计成可以缓存几天的,把缩略图设计成缓存一两天的等等,为不同的数据类型根据他们的使用特点制定不同的缓存策略。

    图片描述

    想要比较好的实现这两件事情,如果全部自己从头开始写会比较繁琐复杂,所幸的是,有不少著名的开源框架帮助我们快速的解决了那些问题。我们可以使用VollyokHTTPPicasso来实现网络缓存。

    实现好网络缓存之后,我们可以使用Android Studio里面的Network Traffic Tools来查看网络数据的请求与返回情况,另外我们还可以使用AT&T ARO工具来抓取网络数据包进行分析查看。

    2) Optimizing Network Request Frequencies

    应用程序的一个基础功能是能够保持确保界面上呈现的信息是即时最新的,例如呈现最新的新闻,天气,信息流等等信息。但是,过于频繁的促使手机客户端应用去同步最新的服务器数据会对性能产生很大的负面影响,不仅仅使得CPU不停的在工作,内存,网络流量,电量等等都会持续的被消耗,所以在进行网络请求操作的时候一定要避免多度同步操作。

    退到后台的应用为了能够在切换回前台的时候呈现最新的数据,会偷偷在后台不停的做同步的操作。这种行为会带来很严重的问题,首先因为网络请求的行为异常的耗电,其次不停的进行网络同步会耗费很多带宽流量。

    为了能够尽量的减少不必要的同步操作,我们需要遵守下面的一些规则:

    • 首先我们要对网络行为进行分类,区分需要立即更新数据的行为和其他可以进行延迟的更新行为,为不同的场景进行差异化处理。
    • 其次要避免客户端对服务器的轮询操作,这样会浪费很多的电量与带宽流量。解决这个问题,我们可以使用Google Cloud Message来对更新的数据进行推送。
    • 然后在某些必须做同步的场景下,需要避免使用固定的间隔频率来进行更新操作,我们应该在返回的数据无更新的时候,使用双倍的间隔时间来进行下一次同步。
    • 最后更进一步,我们还可以通过判断当前设备的状态来决定同步的频率,例如判断设备处于休眠,运动等不同的状态设计各自不同时间间隔的同步频率。

    图片描述

    另外,我们还可以通过判断设备是否连接上Wi-Fi,是否正在充电来决定更新的频率。为了能够方便的实现这个功能,Android为我们提供了GCMNetworkManager来判断设备当下的状态,从而设计更加高效的网络同步操作,如下图所示:

    图片描述

    3) Effective Prefetching

    关于提升网络操作的性能,除了避免频繁的网络同步操作之外,还可以使用捆绑批量访问的方式来减少访问的频率,为了达到这个目的,我们就需要了解Prefetching。

    举个例子,在某个场景下,一开始发出了网络请求得到了某张图片,隔了10s之后,发出第二次请求想要拿到另外一张图片,再隔了6s发出第三张图片的网络请求。这会导致设备的无线蜂窝一直处于高消耗的状态。Prefetching就是预先判定那些可能马上就会使用到的网络资源,捆绑一起集中进行网络请求。这样能够极大的减少电量的消耗,提升设备的续航时间。

    图片描述

    使用Prefetching的难点在于如何判断事先获取的数据量到底是多少,如果预取的数据量偏少,那么就起不到什么效果,但是如果预取过多,又可能导致访问的时间过长。

    图片描述

    那么问题来了,到底预取多少才比较合适呢?一个比较普适的规则是,在3G网络下可以预取1-5MB的数据量,或者是按照提前预期后续1-2分钟的数据作为基线标准。在实际的操作当中,我们还需要考虑当前的网络速度来决定预取的数据量,例如在同样的时间下,4G网络可以获取到12张图片的数据,而2G网络则只能拿到3张图片的数据。所以,我们还需要把当前的网络环境情况添加到设计预取数据量的策略当中去。判断当前设备的状态与网络情况,可以使用前面提到过的GCMNetworkManager

    4) Adapting to Latency

    网络延迟通常来说很容易被用户察觉到,严重的网络延迟会对用户体验造成很大的影响,用户很容易抱怨应用程序写的不好。

    一个典型的网络操作行为,通常包含以下几个步骤:首先手机端发起网络请求,到达网络服务运营商的基站,再转移到服务提供者的服务器上,经过解码之后,接着访问本地的存储数据库,获取到数据之后,进行编码,最后按照原来传递的路径逐层返回。如下图所示:

    图片描述

    在上面的网络请求链路当中的任何一个环节都有可能导致严重的延迟,成为性能瓶颈,但是这些环节可能出现的问题,客户端应用是无法进行调节控制的,应用能够做的就只是根据当前的网络环境选择当下最佳的策略来降低出现网络延迟的概率。主要的实施步骤有两步:第1步检测收集当前的网络环境信息,第2步根据当前收集到的信息进行网络请求行为的调整。

    关于第1步检测当前的网络环境,我们可以使用系统提供的API来获取到相关的信息,如下图所示:

    图片描述

    通过上面的示例,我们可以获取到移动网络的详细子类型,例如4G(LTE)、3G等等,详细分类见下图,获取到详细的移动网络类型之后,我们可以根据当前网络的速率来调整网络请求的行为:

    图片描述

    关于第2步根据收集到的信息进行策略的调整,通常来说,我们可以把网络请求延迟划分为三档:例如把网络延迟小于60ms的划分为GOOD,大于220ms的划分为BAD,介于两者之间的划分为OK(这里的60ms、220ms会需要根据不同的场景提前进行预算推测)。如果网络延迟属于GOOD的范畴,我们就可以做更多比较激进的预取数据的操作,如果网络延迟属于BAD的范畴,我们就应该考虑把当下的网络请求操作Hold住等待网络状况恢复到GOOD的状态再进行处理。

    图片描述

    前面提到说60ms、220ms是需要提前自己预测的,可是预测的工作相当复杂。首先针对不同的机器与网络环境,网络延迟的三档阈值都不太一样,出现的概率也不尽相同,我们会需要针对这些不同的用户与设备选择不同的阈值进行差异化处理:

    图片描述

    Android官方为了帮助我们设计自己的网络请求策略,提供了模拟器的网络流量控制功能来对实际环境进行模拟测量,或者还可以使用AT&T提供的AT&T Network Attenuator来帮助预估网络延迟。

    5) Minimizing Asset Payload

    为了能够减小网络传输的数据量,我们需要对传输的数据做压缩的处理,这样能够提高网络操作的性能。首先不同的网络环境,下载速度以及网络延迟是存在差异的,如下图所示:

    图片描述

    如果我们选择在网速更低的网络环境下进行数据传输,这就意味着需要执行更长的时间,而更长的网络操作行为,会导致电量消耗更加严重。另外传输的数据如果不做压缩处理,也同样会增加网络传输的时间,消耗更多的电量。不仅如此,未经过压缩的数据,也会消耗更多的流量,使得用户需要付出更多的流量费。

    通常来说,网络传输数据量的大小主要由两部分组成:图片与序列化的数据,那么我们需要做的就是减少这两部分的数据传输大小,分下面两个方面来讨论。

    • A)首先需要做的是减少图片的大小,选择合适的图片保存格式是第一步。下图展示了PNG,JPEG,WEBP三种主流格式在占用空间与图片质量之间的对比:

    图片描述

    对于JPEG与WEBP格式的图片,不同的清晰度对占用空间的大小也会产生很大的影响,适当的减少JPG Quality,可以大大地缩小图片占用的空间大小。

    另外,我们需要为不同的使用场景提供当前场景下最合适的图片大小,例如针对全屏显示的情况我们会需要一张清晰度比较高的图片,而如果只是显示为缩略图的形式,就只需要服务器提供一个相对清晰度低很多的图片即可。服务器应该支持到为不同的使用场景分别准备多套清晰度不一样的图片,以便在对应的场景下能够获取到最适合自己的图片。这虽然会增加服务端的工作量,可是这个付出却十分值得!

    • B)其次需要做的是减少序列化数据的大小。JSON与XML为了提高可读性,在文件中加入了大量的符号,空格等等字符,而这些字符对于程序来说是没有任何意义的。我们应该使用Protocal Buffers,Nano-Proto-Buffers,FlatBuffer来减小序列化的数据的大小。

    Android系统为我们提供了工具来查看网络传输的数据情况,打开Android Studio的Monitor,里面有网络访问的模块。或者是打开AT&T提供的ARO工具来查看网络请求状态。

    6) Service Performance Patterns

    Service是Android程序里面最常用的基础组件之一,但是使用Service很容易引起电量的过度消耗以及系统资源的未及时释放。学会在何时启用Service以及使用何种方式杀掉Service就显得十分有必要了。

    简要过一下Service的特性:Service和UI没有关联,Service的创建,执行,销毁Service都是需要占用系统时间和内存的。另外Service是默认运行在UI线程的,这意味着Service可能会影响到系统的流畅度。

    使用Service应该遵循下面的一些规则:

    • 避免错误的使用Service,例如我们不应该使用Service来监听某些事件的变化,不应该搞一个Service在后台对服务器不断的进行轮询(应该使用Google Cloud Messaging)
    • 如果已经事先知道Service里面的任务应该执行在后台线程(非默认的主线程)的时候,我们应该使用IntentService或者结合HanderThread,AsycnTask Loader实现的Service。

    Android系统为我们提供了以下的一些异步相关的工具类

    • GCM
    • BroadcastReciever
    • LocalBroadcastReciever
    • WakefulBroadcastReciver
    • HandlerThreads
    • AsyncTaskLoaders
    • IntentService

    如果使用上面的诸多方案还是无法替代普通的Service,那么需要注意的就是如何正确的关闭Service。

    • 普通的Started Service,需要通过stopSelf()来停止Service;

    图片描述

    • 另外一种Bound Service,会在其他组件都unBind之后自动关闭自己。

    图片描述

    把上面两种Service进行合并之后,我们可以得到如下图所示的Service(相关知识,还可以参考http://hukai.me/android-notes-services/, http://hukai.me/android-notes-bound-services/

    图片描述

    7) Removing unused code

    使用第三方库(library)可以在不用自己编写大量代码的前提下帮助我们解决一些难题,节约大量的时间,但是这些引入的第三方库很可能会导致主程序代码臃肿冗余。

    如果我们处在人力、财力都相对匮乏的情况下,通常会倾向大量使用第三方库来帮助编写应用程序。这其实是无可厚非的,那些著名的第三方库的可行性早就被很多应用所采用并实践证明过。但是这里面存在的问题是,如果我们因为只需要某个library的一小部分功能而把整个library都导入自己的项目,这就会引起代码臃肿。一旦发生代码臃肿,用户就会下载到安装包偏大的应用程序,另外因为代码臃肿,还很有可能会超过单个编译文件只能有65536个方法的上限。解决这个问题的办法是使用MultiDex的方案,可是这实在是无奈之举,原则上,我们还是应该尽量避免出现这种情况。

    Android为我们提供了Proguard的工具来帮助应用程序对代码进行瘦身,优化,混淆的处理。它会帮助移除那些没有使用到的代码,还可以对类名,方法名进行混淆处理以避免程序被反编译。举个例子,Google I/O 2015这个应用使用了大量的library,没有经过Proguard处理之前编译出来的包是8.4Mb大小,经过处理之后的包仅仅是4.1Mb大小。

    使用Proguard相当的简单,只需要在build.gradle文件中配置minifEnable为true即可,如下图所示:

    图片描述

    但是Proguard还是不足够聪明到能够判断哪些类,哪些方法是不能够被混淆的,针对这些情况,我们需要手动的把这些需要保留的类名与方法名添加到Proguard的配置文件中,如下图所示:

    图片描述

    在使用library的时候,需要特别注意这些library在proguard配置上的说明文档,我们需要把这些配置信息添加到自己的主项目中。关于Proguard的详细说明,请看官方文档http://developer.android.com/tools/help/proguard.html

    8) Removing unused resources

    减少APK安装包的大小也是Android程序优化中很重要的一个方面,我们不应该给用户下载到一个臃肿的安装包。假设这样一个场景,我们引入了Google Play Service的library,是想要使用里面的Maps的功能,但是里面的登入等等其他功能是不需要的,可是这些功能相关的代码与图片资源,布局资源如果也被引入我们的项目,这样就会导致我们的程序安装包臃肿。

    所幸的是,我们可以使用Gradle来帮助我们分析代码,分析引用的资源,对于那些没有被引用到的资源,会在编译阶段被排除在APK安装包之外,要实现这个功能,对我们来说仅仅只需要在build.gradle文件中配置shrinkResource为true就好了,如下图所示:

    图片描述

    为了辅助gradle对资源进行瘦身,或者是某些时候的特殊需要,我们可以通过tools:keep或者是tools:discard标签来实现对特定资源的保留与废弃,如下图所示:

    图片描述

    Gradle目前无法对values,drawable等根据运行时来决定使用的资源进行优化,对于这些资源,需要我们自己来确保资源不会有冗余。

    9) Perf Theory: Caching

    当我们讨论性能优化的时候,缓存是最常见最有效的策略之一。无论是为了提高CPU的计算速度还是提高数据的访问速度,在绝大多数的场景下,我们都会使用到缓存。关于缓存是如何提高效率的,这里就不赘述了。

    那么在什么地方,在何时应该利用好缓存来提高效率呢?请看下面的例子,很明显的演示了在某些细节上是如何利用缓存的原理来提高代码的执行效率的:

    图片描述

    图片描述

    类似上面的例子采用缓存原理的地方还有很多,例如缓存到内存里面的图片资源,网络请求返回数据的缓存等等。总之,使用缓存就是为了减少不必要的操作,尽量复用已有的对象来提高效率。

    10) Perf Theory: Approximation(近似法)

    很多时候,我们都需要学会在性能更优与体验更好之间做一定的权衡取舍。为了获取更好的表现性能,我们可能会需要牺牲一些用户体验,例如把某些细节做删除或者是降级处理以便有更好的性能。例如,导航类的应用,如果在导航期间是不停的执行定位的操作,这样能够很及时的获取到最新的位置信息以及当下位置相关的其他提示信息,但是这样会导致网络流量以及手机电量的过度消耗。所以我们可以做一定的降级处理,每隔固定的一段时间才去获取一次位置信息,损失一点及时性来换取更长的续航时间。

    还有很多地方都会用到近似法则来优化程序的性能,例如使用一张比较接近实际大小的图片来替代原图,换取更快的加载速度。所以对于那些对计算结果要求不需要十分精确的场景,我们可以使用近似法则来提高程序的性能。

    11) Perf Theory: Culling(遴选,挑选)

    在以前的性能优化课程里面,我们知道可以通过减少Overdraw来提高程序的渲染性能(主要手段有移除非必须的background,减少重叠的布局,使用clipRect来提高自定义View的绘制性能),今天在这里要介绍的另外一个提高性能的方法是逐步对数据进行过滤筛选,减小搜索的数据集,以此提高程序的执行性能。例如我们需要搜索到居住在某个地方,年龄是多少,符合某些特定条件的候选人,就可以通过逐层过滤筛选的方式来提高后续搜索的执行效率。

    12) Perf Theory: Threading

    使用多线程并发处理任务,从某种程度上可以快速提高程序的执行性能。对于Android程序来说,主线程通常也成为UI线程,需要处理UI的渲染,响应用户的操作等等。对于那些可能影响到UI线程的任务都需要特别留意是否有必要放到其他的线程来进行处理。如果处理不当,很有可能引起程序ANR。关于多线程的使用建议,可以参考官方的培训课程http://developer.android.com/training/best-background.html

    13) Perf Theory: Batching

    关于Batching,在前几季的性能优化课程里面也不止一次提到,下面使用一张图演示下Batching的原理:

    图片描述

    网络请求的批量执行是另外一个比较适合说明batching使用场景的例子,因为每次发起网络请求都相对来说比较耗时耗电,如果能够做到批量一起执行,可以大大的减少电量的消耗。

    图片描述

    14) Serialization performance

    数据的序列化是程序代码里面必不可少的组成部分,当我们讨论到数据序列化的性能的时候,需要了解有哪些候选的方案,他们各自的优缺点是什么。首先什么是序列化?用下面的图来解释一下:

    图片描述

    数据序列化的行为可能发生在数据传递过程中的任何阶段,例如网络传输,不同进程间数据传递,不同类之间的参数传递,把数据存储到磁盘上等等。通常情况下,我们会把那些需要序列化的类实现Serializable接口(如下图所示),但是这种传统的做法效率不高,实施的过程会消耗更多的内存。

    图片描述

    但是我们如果使用GSON库来处理这个序列化的问题,不仅仅执行速度更快,内存的使用效率也更高。Android的XML布局文件会在编译的阶段被转换成更加复杂的格式,具备更加高效的执行性能与更高的内存使用效率。

    图片描述

    下面介绍三个数据序列化的候选方案:

    • Protocal Buffers:强大,灵活,但是对内存的消耗会比较大,并不是移动终端上的最佳选择。
    • Nano-Proto-Buffers:基于Protocal,为移动终端做了特殊的优化,代码执行效率更高,内存使用效率更佳。
    • FlatBuffers:这个开源库最开始是由Google研发的,专注于提供更优秀的性能。

    上面这些方案在性能方面的数据对比如下图所示:

    图片描述

    图片描述

    为了避免序列化带来的性能问题,我们其实可以考虑使用SharedPreference或者SQLite来存储那些数据,避免需要先把那些复杂的数据进行序列化的操作。

    15) Smaller Serialized Data

    数据呈现的顺序以及结构会对序列化之后的空间产生不小的影响。通常来说,一般的数据序列化的过程如下图所示:

    图片描述

    上面的过程,存在两个弊端,第一个是重复的属性名称:

    图片描述

    另外一个是GZIP没有办法对上面的数据进行更加有效的压缩,假如相似数据间隔了32k的数据量,这样GZIP就无法进行更加有效的压缩:

    图片描述

    但是我们稍微改变下数据的记录方式,就可以得到占用空间更小的数据,如下图所示:

    图片描述

    通过优化,至少有三方面的性能提升,如下图所示:

    1)减少了重复的属性名:

    图片描述

    2)使得GZIP的压缩效率更高:

    图片描述

    3)同样的数据类型可以批量优化:

    图片描述

    16) Caching UI data

    如今绝大多数的应用界面上呈现的数据都依赖于网络请求返回的结果,如何做到在网络数据返回之前避免呈现一个空白的等待页面呢(当然这里说的是非首次冷启动的情况)?这就会涉及到如何缓存UI界面上的数据。

    缓存UI界面上的数据,可以采用方案有存储到文件系统,Preference,SQLite等等,做了缓存之后,这样就可以在请求数据返回结果之前,呈现给用户旧的数据,而不是使用正在加载的方式让用户什么数据都看不到,当然在请求网络最新数据的过程中,需要有正在刷新的提示。至于到底选择哪个方案来对数据进行缓存,就需要根据具体情况来做选择了。

    17) CPU Frequency Scaling

    调节CPU的频率会执行的性能产生较大的影响,为了最大化的延长设备的续航时间,系统会动态调整CPU的频率,频率越高执行代码的速度自然就越快。

    图片描述

    Android系统会在电量消耗与表现性能之间不断的做权衡,当有需要的时候会迅速调整CPU的频率到一个比较高负荷的状态,当程序不需要高性能的时候就会降低频率来确保更长的续航时间。

    图片描述

    Android系统检测到需要调整CPU的频率到CPU频率真的达到对应频率会需要花费大概20ms的时间,在此期间很有可能会因为CPU频率不够而导致代码执行偏慢。

    图片描述

    我们可以使用Systrace工具来导出CPU的执行情况,以便帮助定位性能问题。

    系列阅读:
    Android性能优化典范(一)
    Android性能优化典范(二)
    Android性能优化典范(三)

    责任编辑:唐小引(@唐门教主),邮箱:tangxy@csdn.net。

    本文为CSDN特约文章,未经允许不得转载,如需转载请联系mobile#csdn.net(#换成@)

    展开全文
  • Android性能优化典范 - 第2季

    千次阅读 2015-10-31 14:45:48
    Google前几天刚发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义...

    Google前几天刚发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义View的性能,提升设置alpha之后View的渲染性能,以及Lint,StictMode等等工具的使用技巧。 下面是对这些课程的总结摘要,认知有限,理解偏差的地方请多多指教!

    1)Battery Drain and Networking

    对于手机程序,网络操作相对来说是比较耗电的行为。优化网络操作能够显著节约电量的消耗。在性能优化第1季里面有提到过,手机硬件的各个模块的耗电量是不一样的,其中移动蜂窝模块对电量消耗是比较大的,另外蜂窝模块在不同工作强度下,对电量的消耗也是有差异的。当程序想要执行某个网络请求之前,需要先唤醒设备,然后发送数据请求,之后等待返回数据,最后才慢慢进入休眠状态。这个流程如下图所示:

    android_perf_2_network_request_mode

    在上面那个流程中,蜂窝模块的电量消耗差异如下图所示:

    android_perf_2_battery_drain_mode

    从图示中可以看到,激活瞬间,发送数据的瞬间,接收数据的瞬间都有很大的电量消耗,所以,我们应该从如何传递网络数据以及何时发起网络请求这两个方面来着手优化。

    1.1)何时发起网络请求

    首先我们需要区分哪些网络请求是需要及时返回结果的,哪些是可以延迟执行的。例如,用户主动下拉刷新列表,这种行为需要立即触发网络请求,并等待数据返回。但是对于上传用户操作的数据,同步程序设置等等行为则属于可以延迟的行为。我们可以通过Battery Historian这个工具来查看关于移动蜂窝模块的电量消耗(关于这部分的细节,请点击Android性能优化之电量篇)。在Mobile Radio那一行会显示蜂窝模块的电量消耗情况,红色的部分代表模块正在工作,中间的间隔部分代表模块正在休眠状态,如果看到有一段区间,红色与间隔频繁的出现,那就说明这里有可以优化的行为。如下图所示:

    android_perf_2_battery_mobile_radio

    对于上面可以优化的部分,我们可以有针对性的把请求行为捆绑起来,延迟到某个时刻统一发起请求。如下图所示:

    android_perf_2_battery_batch_delay

    经过上面的优化之后,我们再回头使用Battery Historian导出电量消耗图,可以看到唤醒状态与休眠状态是连续大块间隔的,这样的话,总体电量的消耗就会变得更少。

    android_perf_2_battery_mobile_radio_2

    当然,我们甚至可以把请求的任务延迟到手机网络切换到WiFi,手机处于充电状态下再执行。在前面的描述过程中,我们会遇到的一个难题是如何把网络请求延迟,并批量进行执行。还好,Android提供了JobScheduler来帮助我们达成这个目标。

    1.2)如何传递网络数据

    关于这部分主要会涉及到Prefetch(预取)与Compressed(压缩)这两个技术。对于Prefetch的使用,我们需要预先判断用户在此次操作之后,后续零散的请求是否很有可能会马上被触发,可以把后面5分钟有可能会使用到的零散请求都一次集中执行完毕。对于Compressed的使用,在上传与下载数据之前,使用CPU对数据进行压缩与解压,可以很大程度上减少网络传输的时间。

    想要知道我们的应用程序中网络请求发生的时间,每次请求的数据量等等信息,可以通过Android Studio中的Networking Traffic Tool来查看详细的数据,如下图所示:

    android_perf_2_battery_network_tracking

    2)Wear & Sensors

    在Android Wear上会大量的使用Sensors来实现某些特殊功能,如何在尽量节约电量的前提下利用好Sensor会是我们需要特别注意的问题。下面会介绍一些在Android Wear上的最佳实践典范。

    尽量减少刷新请求,例如我们可以在不需要某些数据的时候尽快注销监听,减小刷新频率,对Sensor的数据做批量处理等等。那么如何做到这些优化呢?

    • 首先我们需要尽量使用Android平台提供的既有运动数据,而不是自己去实现监听采集数据,因为大多数Android Watch自身记录Sensor数据的行为是有经过做电量优化的。
    • 其次在Activity不需要监听某些Sensor数据的时候需要尽快释放监听注册。
    • 还有我们需要尽量控制更新的频率,仅仅在需要刷新显示数据的时候才触发获取最新数据的操作。
    • 另外我们可以针对Sensor的数据做批量处理,待数据累积一定次数或者某个程度的时候才更新到UI上。
    • 最后当Watch与Phone连接起来的时候,可以把某些复杂操作的事情交给Phone来执行,Watch只需要等待返回的结果。

    更对关于Sensors的知识,可以点击这里

    3)Smooth Android Wear Animation

    Android Material Design风格的应用采用了大量的动画来进行UI切换,优化动画的性能不仅能够提升用户体验还可以减少电量的消耗,下面会介绍一些简单易行的方法。

    在Android里面一个相对操作比较繁重的事情是对Bitmap进行旋转,缩放,裁剪等等。例如在一个圆形的钟表图上,我们把时钟的指针抠出来当做单独的图片进行旋转会比旋转一张完整的圆形图的所形成的帧率要高56%。

    android_perf_2_waer_animation

    另外尽量减少每次重绘的元素可以极大的提升性能,假如某个钟表界面上有很多需要显示的复杂组件,我们可以把这些组件做拆分处理,例如把背景图片单独拎出来设置为一个独立的View,通过setLayerType()方法使得这个View强制用Hardware来进行渲染。至于界面上哪些元素需要做拆分,他们各自的更新频率是多少,需要有针对性的单独讨论。

    如何使用Systrace等工具来查看某些View的渲染性能,在前面的章节里面有提到过,感兴趣的可以点击这里

    对于大多数应用中的动画,我们会使用PropertyAnimation或者ViewAnimation来操作实现,Android系统会自动对这些Animation做一定的优化处理,在Android上面学习到的大多数性能优化的知识同样也适用于Android Wear。

    想要获取更多关于Android Wear中动画效果的优化,请点击WatchFace这个范例。

    4)Android Wear Data Batching

    在Android Training里面有关于Wear上面如何利用Wearable API与Phone进行沟通协作的课程(详情请点击这里)。因为Phone的CPU与电量都比Wear要强大,另外Phone还可以直接接入网络,而Wear要接入网络则相对更加困难,所以我们在开发Wear应用的时候需要尽量做到把复杂的操作交给Phone来执行。例如我们可以让Phone来获取天气信息,然后把数据返回Wear进行显示。更进一步,在之前的性能优化课程里面我们有学习过如何使用JobScheduler来延迟批量处理任务,假设Phone收到来自Wear的其中一个任务是每隔5分钟检查一次天气情况,那么Phone使用JobScheduler执行检查天气任务之后,先判断这次返回的结果和之前是否有差异,仅仅当天气发生变化的时候,才有必要把结果通知到Wear,或者仅仅把变化的某一项数据通知给Wear,这样可以更大程度上减少Wear的电量消耗。

    下面我们总结一下如何优化Wear的性能与电量:

    • 仅仅在真正需要刷新界面的时候才发出请求
    • 尽量把计算复杂操作的任务交给Phone来处理
    • Phone仅仅在数据发生变化的时候才通知到Wear
    • 把零碎的数据请求捆绑一起再进行操作

    5)Object Pools

    在程序里面经常会遇到的一个问题是短时间内创建大量的对象,导致内存紧张,从而触发GC导致性能问题。对于这个问题,我们可以使用对象池技术来解决它。通常对象池中的对象可能是bitmaps,views,paints等等。关于对象池的操作原理,不展开述说了,请看下面的图示:

    android_perf_2_object_pool

    使用对象池技术有很多好处,它可以避免内存抖动,提升性能,但是在使用的时候有一些内容是需要特别注意的。通常情况下,初始化的对象池里面都是空白的,当使用某个对象的时候先去对象池查询是否存在,如果不存在则创建这个对象然后加入对象池,但是我们也可以在程序刚启动的时候就事先为对象池填充一些即将要使用到的数据,这样可以在需要使用到这些对象的时候提供更快的首次加载速度,这种行为就叫做预分配。使用对象池也有不好的一面,程序员需要手动管理这些对象的分配与释放,所以我们需要慎重地使用这项技术,避免发生对象的内存泄漏。为了确保所有的对象能够正确被释放,我们需要保证加入对象池的对象和其他外部对象没有互相引用的关系。

    6)To Index or Iterate?

    遍历容器是编程里面一个经常遇到的场景。在Java语言中,使用Iterate是一个比较常见的方法。可是在Android开发团队中,大家却尽量避免使用Iterator来执行遍历操作。下面我们看下在Android上可能用到的三种不同的遍历方法:

    android_perf_2_iterate_1

    android_perf_2_iterate_for_loop

    android_perf_2_iterate_simple_loop

    使用上面三种方式在同一台手机上,使用相同的数据集做测试,他们的表现性能如下所示:

    android_perf_2_iterate_result

    从上面可以看到for index的方式有更好的效率,但是因为不同平台编译器优化各有差异,我们最好还是针对实际的方法做一下简单的测量比较好,拿到数据之后,再选择效率最高的那个方式。

    7)The Magic of LRU Cache

    这小节我们要讨论的是缓存算法,在Android上面最常用的一个缓存算法是LRU(Least Recently Use),关于LRU算法,不展开述说,用下面一张图演示下含义:

    android_perf_2_lru_mode

    LRU Cache的基础构建用法如下:

    android_perf_2_lru_key_value

    为了给LRU Cache设置一个比较合理的缓存大小值,我们通常是用下面的方法来做界定的:

    android_perf_2_lru_size

    使用LRU Cache时为了能够让Cache知道每个加入的Item的具体大小,我们需要Override下面的方法:

    android_perf_2_lru_sizeof

    使用LRU Cache能够显著提升应用的性能,可是也需要注意LRU Cache中被淘汰对象的回收,否者会引起严重的内存泄露。

    8)Using LINT for Performance Tips

    Lint是Android提供的一个静态扫描应用源码并找出其中的潜在问题的一个强大的工具。

    android_perf_2_lint_overview

    例如,如果我们在onDraw方法里面执行了new对象的操作,Lint就会提示我们这里有性能问题,并提出对应的建议方案。Lint已经集成到Android Studio中了,我们可以手动去触发这个工具,点击工具栏的Analysis -> Inspect Code,触发之后,Lint会开始工作,并把结果输出到底部的工具栏,我们可以逐个查看原因并根据指示做相应的优化修改。

    Lint的功能非常强大,他能够扫描各种问题。当然我们可以通过Android Studio设置找到Lint,对Lint做一些定制化扫描的设置,可以选择忽略掉那些不想Lint去扫描的选项,我们还可以针对部分扫描内容修改它的提示优先级。

    建议把与内存有关的选项中的严重程度标记为红色的Error,对于Layout的性能问题标记为黄色Warning。

    9)Hidden Cost of Transparency

    这小节会介绍如何减少透明区域对性能的影响。通常来说,对于不透明的View,显示它只需要渲染一次即可,可是如果这个View设置了alpha值,会至少需要渲染两次。原因是包含alpha的view需要事先知道混合View的下一层元素是什么,然后再结合上层的View进行Blend混色处理。

    在某些情况下,一个包含alpha的View有可能会触发改View在HierarchyView上的父View都被额外重绘一次。下面我们看一个例子,下图演示的ListView中的图片与二级标题都有设置透明度。

    android_perf_2_trans_listview

    大多数情况下,屏幕上的元素都是由后向前进行渲染的。在上面的图示中,会先渲染背景图(蓝,绿,红),然后渲染人物头像图。如果后渲染的元素有设置alpha值,那么这个元素就会和屏幕上已经渲染好的元素做blend处理。很多时候,我们会给整个View设置alpha的来达到fading的动画效果,如果我们图示中的ListView做alpha逐渐减小的处理,我们可以看到ListView上的TextView等等组件会逐渐融合到背景色上。但是在这个过程中,我们无法观察到它其实已经触发了额外的绘制任务,我们的目标是让整个View逐渐透明,可是期间ListView在不停的做Blending的操作,这样会导致不少性能问题。

    如何渲染才能够得到我们想要的效果呢?我们可以先按照通常的方式把View上的元素按照从后到前的方式绘制出来,但是不直接显示到屏幕上,而是使用GPU预处理之后,再又GPU渲染到屏幕上,GPU可以对界面上的原始数据直接做旋转,设置透明度等等操作。使用GPU进行渲染,虽然第一次操作相比起直接绘制到屏幕上更加耗时,可是一旦原始纹理数据生成之后,接下去的操作就比较省时省力。

    android_perf_2_trans_hw_layer

    如何才能够让GPU来渲染某个View呢?我们可以通过setLayerType的方法来指定View应该如何进行渲染,从SDK 16开始,我们还可以使用ViewPropertyAnimator.alpha().withLayer()来指定。如下图所示:

    android_perf_2_trans_setlayertype

    另外一个例子是包含阴影区域的View,这种类型的View并不会出现我们前面提到的问题,因为他们并不存在层叠的关系。

    android_perf_2_trans_overlap

    为了能够让渲染器知道这种情况,避免为这种View占用额外的GPU内存空间,我们可以做下面的设置。

    android_perf_2_trans_override_lap

    通过上面的设置以后,性能可以得到显著的提升,如下图所示:

    android_perf_2_trans_overlap_compare

    10)Avoiding Allocations in onDraw()

    我们都知道应该避免在onDraw()方法里面执行导致内存分配的操作,下面讲解下为何需要这样做。

    首先onDraw()方法是执行在UI线程的,在UI线程尽量避免做任何可能影响到性能的操作。虽然分配内存的操作并不需要花费太多系统资源,但是这并不意味着是免费无代价的。设备有一定的刷新频率,导致View的onDraw方法会被频繁的调用,如果onDraw方法效率低下,在频繁刷新累积的效应下,效率低的问题会被扩大,然后会对性能有严重的影响。

    android_perf_2_ondraw_gc

    如果在onDraw里面执行内存分配的操作,会容易导致内存抖动,GC频繁被触发,虽然GC后来被改进为执行在另外一个后台线程(GC操作在2.3以前是同步的,之后是并发),可是频繁的GC的操作还是会影响到CPU,影响到电量的消耗。

    那么简单解决频繁分配内存的方法就是把分配操作移动到onDraw()方法外面,通常情况下,我们会把onDraw()里面new Paint的操作移动到外面,如下面所示:

    android_perf_2_ondraw_paint

    11)Tool: Strict Mode

    UI线程被阻塞超过5秒,就会出现ANR,这太糟糕了。防止程序出现ANR是很重要的事情,那么如何找出程序里面潜在的坑,预防ANR呢?很多大部分情况下执行很快的方法,但是他们有可能存在巨大的隐患,这些隐患的爆发就很容易导致ANR。

    Android提供了一个叫做Strict Mode的工具,我们可以通过手机设置里面的开发者选项,打开Strict Mode选项,如果程序存在潜在的隐患,屏幕就会闪现红色。我们也可以通过StrictMode API在代码层面做细化的跟踪,可以设置StrictMode监听那些潜在问题,出现问题时如何提醒开发者,可以对屏幕闪红色,也可以输出错误日志。下面是官方的代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public void onCreate() {
         if (DEVELOPER_MODE) {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectDiskReads()
                     .detectDiskWrites()
                     .detectNetwork()   // or .detectAll() for all detectable problems
                     .penaltyLog()
                     .build());
             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                     .detectLeakedSqlLiteObjects()
                     .detectLeakedClosableObjects()
                     .penaltyLog()
                     .penaltyDeath()
                     .build());
         }
         super.onCreate();
    }
    

    12)Custom Views and Performance

    Android系统有提供超过70多种标准的View,例如TextView,ImageView,Button等等。在某些时候,这些标准的View无法满足我们的需要,那么就需要我们自己来实现一个View,这节会介绍如何优化自定义View的性能。

    通常来说,针对自定义View,我们可能犯下面三个错误:

    • Useless calls to onDraw():我们知道调用View.invalidate()会触发View的重绘,有两个原则需要遵守,第1个是仅仅在View的内容发生改变的时候才去触发invalidate方法,第2个是尽量使用ClipRect等方法来提高绘制的性能。
    • Useless pixels:减少绘制时不必要的绘制元素,对于那些不可见的元素,我们需要尽量避免重绘。
    • Wasted CPU cycles:对于不在屏幕上的元素,可以使用Canvas.quickReject把他们给剔除,避免浪费CPU资源。另外尽量使用GPU来进行UI的渲染,这样能够极大的提高程序的整体表现性能。

    最后请时刻牢记,尽量提高View的绘制性能,这样才能保证界面的刷新帧率尽量的高。更多关于这部分的内容,可以看这里

    13)Batching Background Work Until Later

    优化性能时大多数时候讨论的都是如何减少不必要的操作,但是选择何时去执行某些操作同样也很重要。在第1季以及上一期的性能优化之电量篇里面,我们有提到过移动蜂窝模块的电量消耗模型。为了避免我们的应用程序过多的频繁消耗电量,我们需要学习如何把后台任务打包批量,并选择一个合适的时机进行触发执行。下图是每个应用程序各自执行后台任务导致的电量消耗示意图:

    android_perf_2_batching_bg_1

    因为像上面那样做会导致浪费很多电量,我们需要做的是把部分应用的任务延迟处理,等到一定时机,这些任务一并进行处理。结果如下面的示意图:

    android_perf_2_batching_bg_2

    执行延迟任务,通常有下面三种方式:

    1)AlarmManager

    使用AlarmManager设置定时任务,可以选择精确的间隔时间,也可以选择非精确时间作为参数。除非程序有很强烈的需要使用精确的定时唤醒,否者一定要避免使用他,我们应该尽量使用非精确的方式。

    2)SyncAdapter

    我们可以使用SyncAdapter为应用添加设置账户,这样在手机设置的账户列表里面可以找到我们的应用。这种方式功能更多,但是实现起来比较复杂。我们可以从这里看到官方的培训课程:http://developer.android.com/training/sync-adapters/index.html

    3)JobSchedulor

    这是最简单高效的方法,我们可以设置任务延迟的间隔,执行条件,还可以增加重试机制。

    14)Smaller Pixel Formats

    常见的png,jpeg,webp等格式的图片在设置到UI上之前需要经过解码的过程,而解压时可以选择不同的解码率,不同的解码率对内存的占用是有很大差别的。在不影响到画质的前提下尽量减少内存的占用,这能够显著提升应用程序的性能。

    Android的Heap空间是不会自动做兼容压缩的,意思就是如果Heap空间中的图片被收回之后,这块区域并不会和其他已经回收过的区域做重新排序合并处理,那么当一个更大的图片需要放到heap之前,很可能找不到那么大的连续空闲区域,那么就会触发GC,使得heap腾出一块足以放下这张图片的空闲区域,如果无法腾出,就会发生OOM。如下图所示:

    android_perf_2_pixel_heap_free

    所以为了避免加载一张超大的图片,需要尽量减少这张图片所占用的内存大小,Android为图片提供了4种解码格式,他们分别占用的内存大小如下图所示:

    android_perf_2_pixel_format

    随着解码占用内存大小的降低,清晰度也会有损失。我们需要针对不同的应用场景做不同的处理,大图和小图可以采用不同的解码率。在Android里面可以通过下面的代码来设置解码率:

    android_perf_2_pixel_decode

    15)Smaller PNG Files

    尽量减少PNG图片的大小是Android里面很重要的一条规范。相比起JPEG,PNG能够提供更加清晰无损的图片,但是PNG格式的图片会更大,占用更多的磁盘空间。到底是使用PNG还是JPEG,需要设计师仔细衡量,对于那些使用JPEG就可以达到视觉效果的,可以考虑采用JPEG即可。我们可以通过Google搜索到很多关于PNG压缩的工具,如下图所示:

    android_perf_2_png_tools

    这里要介绍一种新的图片格式:Webp,它是由Google推出的一种既保留png格式的优点,又能够减少图片大小的一种新型图片格式。关于Webp的更多细节,请点击这里

    16)Pre-scaling Bitmaps

    对bitmap做缩放,这也是Android里面最遇到的问题。对bitmap做缩放的意义很明显,提示显示性能,避免分配不必要的内存。Android提供了现成的bitmap缩放的API,叫做createScaledBitmap(),使用这个方法可以获取到一张经过缩放的图片。

    android_perf_2_sacle_bitmap_created

    上面的方法能够快速的得到一张经过缩放的图片,可是这个方法能够执行的前提是,原图片需要事先加载到内存中,如果原图片过大,很可能导致OOM。下面介绍其他几种缩放图片的方式。

    inSampleSize能够等比的缩放显示图片,同时还避免了需要先把原图加载进内存的缺点。我们会使用类似像下面一样的方法来缩放bitmap:

    android_perf_2_sacle_bitmap_code

    android_perf_2_sacle_bitmap_insamplesize

    另外,我们还可以使用inScaled,inDensity,inTargetDensity的属性来对解码图片做处理,源码如下图所示:

    android_perf_2_sacle_bitmap_inscale

    还有一个经常使用到的技巧是inJustDecodeBounds,使用这个属性去尝试解码图片,可以事先获取到图片的大小而不至于占用什么内存。如下图所示:

    android_perf_2_sacle_bitmap_injust

    17)Re-using Bitmaps

    我们知道bitmap会占用大量的内存空间,这节会讲解什么是inBitmap属性,如何利用这个属性来提升bitmap的循环效率。前面我们介绍过使用对象池的技术来解决对象频繁创建再回收的效率问题,使用这种方法,bitmap占用的内存空间会差不多是恒定的数值,每次新创建出来的bitmap都会需要占用一块单独的内存区域,如下图所示:

    android_perf_2_inbitmap_old

    为了解决上图所示的效率问题,Android在解码图片的时候引进了inBitmap属性,使用这个属性可以得到下图所示的效果:

    android_perf_2_inbitmap_new

    使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。下面是如何使用inBitmap的代码示例:

    android_perf_2_inbitmap_code

    使用inBitmap需要注意几个限制条件:

    • 在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。
    • 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。

    我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。如下图所示:

    android_perf_2_inbitmap_pool

    Google介绍了一个开源的加载bitmap的库:Glide,这里面包含了各种对bitmap的优化技巧。

    18)The Performance Lifecycle

    大多数开发者在没有发现严重性能问题之前是不会特别花精力去关注性能优化的,通常大家关注的都是功能是否实现。当性能问题真的出现的时候,请不要慌乱。我们通常采用下面三个步骤来解决性能问题。

    Gather:收集数据

    我们可以通过Android SDK里面提供的诸多工具来收集CPU,GPU,内存,电量等等性能数据,

    Insight:分析数据

    通过上面的步骤,我们获取到了大量的数据,下一步就是分析这些数据。工具帮我们生成了很多可读性强的表格,我们需要事先了解如何查看表格的数据,每一项代表的含义,这样才能够快速定位问题。如果分析数据之后还是没有找到问题,那么就只能不停的重新收集数据,再进行分析,如此循环。

    Action:解决问题

    定位到问题之后,我们需要采取行动来解决问题。解决问题之前一定要先有个计划,评估这个解决方案是否可行,是否能够及时的解决问题。

    19)Tools not Rules

    虽然前面介绍了很多调试的方法,处理技巧,规范建议等等,可是这并不意味着所有的情况都适用,我们还是需要根据当时的情景做特定灵活的处理。

    20)Memory Profiling 101

    围绕Android生态系统,不仅仅有Phone,还有Wear,TV,Auto等等。对这些不同形态的程序进行性能优化,都离不开内存调试这个步骤。这节中介绍的内容大部分和Android性能优化典范Android性能优化之内存篇重合,不展开了。

    展开全文
  • 典范分析-Canoco软件

    2010-01-22 15:56:33
    CANOCO 生物统计软件介绍Canoco 试用版本其相关资料 Canoco 产品报价 生物多样的动态和机制的研究,尤其是物种多样和生态系统多样
  • 值得一提的是,上面图中红色线较高的一种可能是因为重新提交了视图而导致的.这些视图并不是失效的视图,但是有些时候发生了某些事,例如视图旋转,我们需要重新清理这个区域的视图,这样可能会影响这个视图下面的视图,...

    转自:http://www.jianshu.com/p/061bb80025c7

    GPU Profile工具

    渲染性能问题往往是偷取你宝贵帧数的罪魁祸首,这种问题很容易产生,很容易出现,而且在一个非常方便的工具的帮助下,也非常容易去追踪. 使用Peofile GPU Rendering tool,你可以在手机上就可以看到究竟是什么导致你的应用程序出现卡顿,变慢的情况.

    这个工具在设置-开发者选项-Profile GPU rendering选项,打开后选择on screen as bars:

    Profile GPU rendering
    Profile GPU rendering

    然后手机屏幕上就会出现三个颜色组成的小柱状图,以及一条绿线:

    gpu工具
    gpu工具

    这个工具会在屏幕上显示经过分析后的图形数据,最底部的图显示的是Navigation的相关信息,最上面显示的是Notification的相关信息,中间的图显示的是当前应用程序的图.

    使用GPU Profile工具

    当你的应用程序在运行时,你会看到一排柱状图在屏幕上,从左到右动态地显示,每一个垂直的柱状图代表一帧的渲染,越长的垂直柱状图表示这一帧需要渲染的时间越长.随着需要渲染的帧数越来越多,他们会堆积在一起,这样你就可以观察到这段时间帧率的变化.

    绿线

    下图中的绿线代表16ms,要确保一秒内打到60fps,你需要确保这些帧的每一条线都在绿色的16ms标记线之下.任何时候你看到一个竖线超过了绿色的标记现,你就会看到你的动画有卡顿现象产生.

    绿线
    绿线

    柱状图

    每一条柱状图都由三种颜色组成: 蓝-红-黄. 这些线直接和Android的渲染流水线和他实际运行帧数的时间关联:

    柱状图
    柱状图
    • 蓝色代表测量绘制的时间,或者说它代表需要多长时间去创建和更新你的DisplayList.在Android中,一个视图在可以实际的进行渲染之前,它必须被转换成GPU所熟悉的格式,简单来说就是几条绘图命令,复杂点的可能是你的自定义的View嵌入了自定义的Path. 一旦完成,结果会作为一个DisplayList对象被系统送入缓存,蓝色就是记录了需要花费多长时间在屏幕上更新视图(说白了就是执行每一个View的onDraw方法,创建或者更新每一个View的Display List对象).

      Draw Phase
      Draw Phase

      当你看到蓝色的线很高的时候,有可能是因为你的一堆视图突然变得无效了(即需要重新绘制),或者你的几个自定义视图的onDraw函数过于复杂.

      自定义视图
      自定义视图
    • 红色代表执行的时间,这部分是Android进行2D渲染 Display List的时间,为了绘制到屏幕上,Android需要使用OpenGl ES的API接口来绘制Display List.这些API有效地将数据发送到GPU,最总在屏幕上显示出来.

      红色
      红色

      记住绘制下图这样自定义的比较复杂的视图时,需要用到的OpenGl的绘制命令也会更复杂

      自定义的复杂View
      自定义的复杂View

      当你看到红色的线非常高的时候,这些复杂的自定义View就是罪魁祸首:

      Paste_Image.png
      Paste_Image.png

      值得一提的是,上面图中红色线较高的一种可能性是因为重新提交了视图而导致的.这些视图并不是失效的视图,但是有些时候发生了某些事,例如视图旋转,我们需要重新清理这个区域的视图,这样可能会影响这个视图下面的视图,因为这些视图都需要进行重新的绘制操作.

    • 橙色部分表示的是处理时间,或者说是CPU告诉GPU渲染一帧的地方,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复,如果柱状图很高,那就意味着你给GPU太多的工作,太多的负责视图需要OpenGL命令去绘制和处理.

    保持动画流畅的关键就在于让这些垂直的柱状条尽可能地保持在绿线下面,任何时候超过绿线,你就有可能丢失一帧的内容.

    总结

    GPU Profile工具能够很好地帮助你找到渲染相关的问题,但是要修复这些问题就不是那么简单了. 你需要结合代码来具体分析,找到性能的瓶颈,并进行优化.

    有时候你可以以这个为工具,让负责设计这个产品的人修改他的设计,以获得良好的用户体验.

    Perf Matters

    keep calm, profile your code, and always remember, Perf Matters


    展开全文
  • 企业云计算,典范一马当先

    千次阅读 2009-08-18 05:32:00
    <!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } --> 在本文中,云计算(CloudComputing)在企业中的应用称之为“企业云”(EnterpriseCloud,简记为EC)。典范即Ubuntu的主要赞助商
  • 作者简介: 胡凯(@胡凯me),腾讯 Android 工程师,热爱开源与分享,维护 Android 官方培训课程协作项目,关注 Android 应用性能优化的总结与分享,...前言这里是Android性能优化典范第 6 季的课程学习笔记,从...
  • C++的程序设计典范

    千次阅读 2003-06-30 09:33:00
     计算机语言程序设计自从50年代中期至今,历经了将近半个世纪,其中经历了无数的挫折,更可喜的是语言的发展取得了重大的进步,其中发展了4中程序设计典范,在计算机的发展史上留下了光辉的一页。一、过程式程序...
  • 公理系统的典范:几何基础

    千次阅读 2018-03-05 08:09:26
     这五组的公理也满足了公理体系的三个基本要求,即相容、独立和完备。如果把这五组的公理稍作增减,便得出其他不同的几何空间,例如把平行公理中的欧几里得平行公理换为罗巴切夫斯基平行公理,那便把「...
  • 微软欲变身敏捷开发典范

    千次阅读 2008-03-22 11:45:00
    他曾经作为一个开发者在微软服务器和工具部门工作了十年之久,他依然不能相信微软现在是敏捷开发的典范。 “很明显,采用CTP版和其它改变带来了一定好处,”他表示。“用户可以比较早的对产品有一定了解,微软就可以...
  • 不过随着JAVA的不断升级和优化,Swing的速度一直在提高,美观也在改善,基于Swing的成功应用也越来越多了。关于Swing是否消亡或被SWT代替或是否能作桌面应用的争论逐渐少了。不过喜欢并精通Swing技术的开发者,...
  • 优信二手车正是这种善于把握时代步伐,与时俱进创新发展的典范。短短几年的时间,优信二手车就进入我国二手车行业发展前列,在于其对企业产品和服务的不断创新和把控,还在于其前瞻的经营思维。 尽管优信二手车...
  • 自定义占位符和控制语句起始符号,这有利于减小模板语法对模板的倾入,比如在html模板中,如果定义控制语句符号是<!--:和 -->,那么,大部分模板文件都能通过浏览器打开。有的使用者仅仅采用了单个符号@ (或者单个...
  • 2018的典范雇主普遍认同人工智能与大数据已经对企业自身与所处行业均发生了深远的影响,并认识到深度学习与赋能型管理的重要,近八成典范雇主通过自建数字化平台介绍前沿或高端知识趋势,以此提高雇员专项技能与...
  • Swing是MVC设计的典范

    千次阅读 2006-06-12 17:07:00
    Swing的设计是MVC的典范。虽然MVC的概念有点泛滥,可是真正能够理解并熟练掌握、在设计和开发里面自然流露的并不多见。记得用VC++开发程序时候,MFC向导也是生成Document和View两个类,当时一直奇怪为什么这么绕...
  • Google BigTable: 数据库实现的典范

    千次阅读 2011-12-16 16:08:14
    但今天看了BigTable的论文后发现BigTable实在算得上是数据库实现的典范,BigTable中采用的所有技术都来自于数据库领域的已有研究成果,但BigTable最成功的是很多技术的选择、取舍都恰到好处。 BigTable中最重要...
  • Google官方Android性能优化典范第2季

    千次阅读 2016-09-08 15:43:13
    Google前几天刚发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义...
  • 本文按照典范相关分析的原理,研究了多个农艺性状与多个品质性状间的相关关系,讨论了由多个农艺性状的综合选择间接改善各品质性状的可能,同时提出了综合选择指数的构造方法。在实例中,采用20个油菜品系的观察测定...
  • 我们不得不承认,Jupyternotebook确实是一款非常棒的工具,它不仅加入了探索数据分析功能,也开辟了整个软件工艺界的典范。 现代软件行业非常了解如何保持代码基础整洁。为了进行结构化,代码基础将首先被分为...
  • Android性能优化典范 - 第2季 @胡凯

    千次阅读 2015-05-07 16:27:58
    Google前几天刚发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,408
精华内容 8,963
关键字:

典范性