Android Performance Patterns 系列视频学习记录(持续更新中)-程序员宅基地

技术标签: android  Android  

系列文章旨在记录YouTube上谷歌发布的Android Performance Patterns系列视频,一共79个视频,每个视频也就几分钟。当然对于大部分安卓开发者来说,这些都是基础,可能你会说,看这些有什么用呢。但是魔鬼往往藏在细节之中,切忌眼高手低的问题。

本人英语水平有限,加之理解和经历尚浅,如果错误和不当之处欢迎指正。

1.关于线程那些事

        众所周知,安卓系统中,主线程(UI线程)是十分重要的,很多工作如绘制、与用户交互事件处理等工作都会在主线程执行

        由于主线程在大概16ms就会发出一次重绘UI的信号,为了保证用户体验的流畅和避免出现掉帧的情况,我们必须保证主线程没有大量的耗时操作。安卓系统为我们提供了很多将工作放到异步线程执行的方法,当然我们可以选择自己new Thread,但是自己管理线程总是比较麻烦,而且线程的创建和销毁也不是毫无成本的。安卓系统给我们提供了几个很好用的手段。

        AsyncTask适用于一些较小的异步任务、简单的网络操作等。HandlerThread本质是一个带有handler的thread(好像是个废话),他可以把任务放在线程中执行,之后再利用handler进行UI的更新。ThreadPool线程池适合解决频繁创建线程的问题,IntentService作为一个service,可以有较长的生命周期,处理一些长期的任务。每一个都有它的适用场景,这里不再细细展开,大家可以查询相关资料进行进一步的学习。

        需要注意的是,由于线程执行工作可以说基本与UI进行了分离,在页面被销毁或者旋转屏幕重建后,之前的对象可能就不复存在了,随之而来的内存泄露和对象不存在的问题也需要注意。内部类AsyncTask会持有外部activity的引用,在使用的时候最好使用静态内部类+弱引用,防止activity引用无法被系统回收。

2&3.理解UI线程&UI的更新

        一张图片大概介绍了,一个线程需要的几个基本功能。当我们把安卓提供给我们的looper、handler、message等凑在一起之后呢。

       

        一个很cool的handlerthread就诞生了,handler负责消息的排队执行,looper确保线程一直在运行,并在有新的消息到来时将其取出。  在app启动的时候,我们的ui线程就会被创建,并伴随app的整个生命周期,由于UI和异步线程的特殊性,可以将要更新的要求通过intent或者其他方式通知activity进行,确保UI的更新由持有他的activity进行,这样就不会出现因为activity销毁而导致内存泄露或者崩溃。

4&5.关于asyncTask&HandlerThread的使用

       

        关于asyncTask任务的串行执行,在很早版本的安卓手机上,曾经有过并行执行的asyncTask,但是后来因为种种原因,asyncTask默认采用的还是串行执行的方式(当然也可以设置其并行执行任务,但是这种场景下,线程池可能是更好的选择)。

        handlerThread适用于长时间的,无需与UI页面进行操作的任务执行。在初始化的时候需要记得指定一个合适的优先级,以免线程在竞争cpu资源的时候无法被调度。

6.徜徉线程池

        想象如果只有一个线程进行任务,可能几十个个小任务就会占用一个线程上百ms的时间,导致其他任务无法被执行,但是线程池中多个线程一起处理任务的话会将效率大大提升。ThreadPool会帮你处理线程的创建、调度甚至是回收,开发者只需要关注于如何划分任务进行执行。需要注意的是关于最大线程数,CPU能同时处理的任务是有限的,当然通过这种方式获取的数值也不一定准确,比如双CPU机器,为了节省电能,系统将其中一个CPU休眠了,那么你获得的硬件进程数也是1。当然线程池也不是万能的,除非你一直有很多任务一直在执行,否则使用线程池有一些过于重量级。

7.IntentService

        intentService像是service和handler的结合体,它是一种service,但是内部有handlerThread处理任务。作为service他就比我们使用普通的线程拥有更高的优先级,甚至在应用处于后台他也可以继续执行而不容易被系统杀死。但是注意,最好不要使用广播intent的方式回到主线程去更新UI,这样的代价过大,尽量使用handler或者runOnUiThread方法进行。

8.Loader&LoaderManager

        由于线程和我们的activity生命周期的不一致性,比如activity销毁了,但是下载还未结束,这会导致activity的内存泄露以及下载完成后需要更新view不再存在产生错误,还会导致这期间资源的浪费,在activity重新被创建,整个过程又要重新来一遍。

        loader和loadermanger是一套异步加载数据的标准机制,一个fragment或者activity只持有一个loaderManager,负责管理众多loader。loader则负责数据的加载。

        LoaderManager.LoaderCallbacks:LoaderManager.LoaderCallbacks是LoaderManager中的内部接口,客户端与Loader的通信完全是事件机制,即客户端需要实现LoaderCallbacks中的各种回调方法,以响应Loader & LoaderManager触发的各种事件。客户端在调用LoaderManager的initLoader()或restartLoader()方法时,就需要客户端向这两个方法中传入一个LoaderCallbacks的实例。LoaderCallbacks有三个回调方法需要实现:onCreateLoader()、onLoadFinished()以及onLoaderReset()。

        onCreateLoader:我们要在onCreateLoader()方法内返回一个Loader的实例对象。很多情况下,我们需要查询ContentProvider里面的内容,那么我们就需要在onCreateLoader中返回一个CursorLoader的实例,CursorLoader继承自Loader。当然,如果CursorLoader不能满足我们的需求,我们可以自己编写自己的Loader然后在此onCreateLoader方法中返回。

        onLoadFinished:当onCreateLoader中创建的Loader完成数据加载的时候,我们会在onLoadFinished回调函数中得到加载的数据。在此方法中,客户端可以得到数据并加以使用,在这之前,如果客户端已经保存了一份老的数据,那么我们需要释放对老数据的引用。

        onLoaderReset:当之前创建的Loader被销毁(且该Loader向客户端发送过数据)的时候,就会触发onLoaderReset()回调方法,此时表明我们之前获取的数据被重置且处于无效状态了,所以客户端不应该再使用这份“过期”的无效的老数据,应该释放对该无效数据的引用。

9.为线程指定优先级&10.GPU调试工具的使用

11.善用httpResponseCache

        网络请求往往意味着高代价和高延时,对于一些非敏感数据,如需要重复使用配置文件,开关数据等,可以通过缓存到本地来避免频繁的从网络上获取。http协议头有设定缓存时间或者无缓存相关的字段。在我们自己实现缓存时,可以使用DiskLruCache(okhttp中使用的就是这种方式)进行缓存。当然对于不同的场景有不同的缓存策略,一些敏感的用户信息肯定是不允许缓存,一些图片或者UI显示资源在没有发生更新时甚至可以一直缓存在本地。

12&13.适时地进行网络请求

        如用户已经很久没有打开过app、用户已经很久没有进入过页面进行网络请求,也可以使用逐渐放缓请求频率的策略如本次请求没有新数据,下次发起请求时间延长一段时间等。在wifi和充电情况下地请求可以稍微频繁一些等等等等。这些就看应用的场景和详细情况了,记住,在移动设备上,流量和电量都是稀缺资源,切忌频繁请求,会带来很不好的用户体验。

        同时使用预取策略,一次性将合适量的数据进行下载,以避免频繁请求带来的资源损耗。

14.处理弱网情况

        在一次请求发出到返回所用的时间是我们衡量真实网络情况的指标,如果应用监控到一个请求经历了几百毫秒才返回,那么我们就很有理由进行缓存,因为很可能用户处于一个较差的网络状态下。当一个请求快速返回的时候,我们可以进行一些较大的预取,这些请求策略多用于大量网络刷新的时候,比如很多新闻app的feed流,用户可能一直在刷新,这时候就需要我们有适时的预取和数据的分包处理。

15.选择合适大小的资源

        对于图片的选择基本原则就是不要使用PNG,尽量使用JPEG这种对于网络传输和压缩友好的类型。其次就是根据显示需要来下载图片,比如缩略图可能一个很小分辨率的图片就可以实现。第二点比较有意思,我们都习惯了使用json或者xml来进行数据的传输,但是事实上json在解析时长、内存、甚至后续java的GC中都表现不佳,谷歌官方推荐我们使用flatBuffers来代替json,我也是第一次听说这种方式。简单来说,flatbuffer提供了一种序列化和反序列化的规则,它的缺点是易读性很差,而且需要生成类加入代码中,存在代码注入问题。但是他的解析速度、内存占用非常诱人,据说facebook在Android客户端的CS通信使用flatbuffers,取得了很不错的收益,我后续也会进行学习并出一个关于flatbuffers的文章(FlatBuffers源码解析_ruozhalqy的博客-程序员宅基地),欢迎大家关注~flatbuffers官方主页

        此外,对于压缩文件,我们应该注意将类似的对象放在一起,这样可以达到较好的压缩结果。

16.service性能优化

安卓开发者对于service应该非常熟悉了

service通常用于执行一系列后台工作,但是service的创建以及和我们ui进程的通信开销是很大的,其次,service执行在ui线程上,如果需要执行后台操作,需要注意另开线程执行(或者直接使用intentservice)。

视频中对于service的建议就是。。。能不用就不用,因为往往有很多更高效的方式去实现我们的需要。比如进行事件的监听和处理,broadcastReciver就是很好的选择。

需要注意,我们用到的两种service,一种是使用startService启动的,一种是bound service进行IPC通信,一种需要stopservice去停止,一种需要unbindservice调用,当我们混用的时候,注意停止service的调用,避免资源的无故损耗。

17&18.移除没有使用的代码和资源

引用第三方库是开发中常进行的操作,当然,很多时候我们可能只需要一个功能,却不得不引用一整个库,这会使我们的代码不断膨胀。用户也需要下载更大的app。而且,安卓对于方法数有限制,一个app最多可以拥有65536个方法(可以使用multiDex进行拓展)。但是毫无疑问,代码膨胀总是一个不好的事情。

android提供了proguard工具帮助我们剔除没有使用的代码、类并可以进行代码的混淆。当然,如果有一些类只通过反射调用,我们需要将其“keep"住,以免被剔除。还有就是在module之间引用的时候,如果moudle被混淆,你在另一个moudle中就无法进行引用,此时就需要进行keep。

对于我们引用的第三方库,可能会有很多我们没有使用的功能,其相关资源也会占有大量空间。同样对于我们想要保留的资源,可以进行keep

同时gradle也有其局限性,一些文件夹并不在gradle的优化范围内,因为这些只有在运行时才会决定加载什么资源。这些就需要我们在开发的时候注意无用的资源管理。

19.缓存

缓存是现代计算机非常重要的组成部分,学过计算机组成原理的朋友一定有所了解CPU-缓存-内存-硬盘这样的多级存储设备,简单来说,缓存就是用于协调读取速度极快的CPU和速素的内存之间的矛盾,将常用的信息进行临时的存储,以免将时间花费在数据的读取上。当然这是计算机系统中的缓存系统,到了我们Android开发中,缓存的含义就可以非常广泛,可能是下载的图片进行缓存以节省网络资源,也可以将计算结果临时保存在内存里防止多次的运算。

视频中主要讲解了几种场景:1.能放在循环外的运算绝对不要放在循环内部。2.加载大型资源时,管理好显示部分和移除不需要的部分。3.预计算,不要用什么数据才算什么数据,充分利用空闲时间,尽量在用户需要数据的时候就能取到数据。

20.粗略计算

这里我们讲到了在完美结果但是资源消耗较大和可接受的资源消耗较少的结果中进行平衡。比如我们做了一个导航软件,用户从一个城市到另一个城市可能需要跨越几百公里,期间可能绝大部分都是高速公路,这是我们就不需要经常做网络请求来查询周边的情况,只需要保持GPS的精准即可。在出现了加油站、服务区等有价值的地点时再进行相关网络的请求和数据的分发即可。包括在沿途我们需要加载的资源,采用小体积低像素的图片即可,等到用户有兴趣详细查看的时候再进行大量资源的加载。

21.消除不必要的工作

我们常见的性能优化手段就是消除重绘,当我们确定一些视图最终展现出来的时候是被其他组件覆盖住时,我们就没有必要去进行绘制。关于重绘我会在工作中进行一版优化,希望大家关注后续的博文更新~

22.线程balabala 和前面内容比较重合

23.批处理

对于耗时长、工作量大的任务,我们往往关注比较多,处理的比较好。但是对于重复的小任务我们往往会忽略其消耗。比如频繁的网络请求,我们可以根据情况,将几个网络请求合并到一个进行处理。或者我绘制视图的时候,将多次的矩阵变换变成较大矩阵的一次变换。读取数据的时候尽量读取一批要使用的数据,而不是用一个查询一次。

24.更快、更小的序列化

简单来说,序列化就是将对象数据转换成可存储的文件数据以便下次进行读取。java提供的Serializable类是最常用的序列化方式,但是这种方式有占用内存较大、耗费时间长的问题,视频中称其为“bad choice”,Gson库也是一个不错的选择,更小的内存消耗很快的速度。但是由于其以json形式存储,为了实现其可读性,必然会引入大量冗余的信息。回到最后,我们还是推荐使用flatbuffers,其优点可以参照我进行的源码分析(FlatBuffers源码解析_ruozhalqy的博客-程序员宅基地)

25.更小的序列化数据

序列化是开发中十分常见的操作,它将我们需要保存的对象或者数据永久存于设备上或者通过网络进行传输,并且可以通过反序列化获取到原本的对象。

我们习惯性按照某种规则去序列化对象,例如形成json文件,那么存在的问题就是存在很多重复的命名。同时,比如gzip压缩使用的是32k大小的窗口,如果我们的对象数据很多,在32k内没有产生较多的重复,那么压缩的效果也会大打折扣。解决方案首先从数据结构上进行改变,比如上面这个对象,完全可以采用数组方式进行序列化。同时可以将元素进行分离,以便于压缩算法更好的执行。之后我们就可以使用一些序列化工具进行更小更好的序列化了。

26.缓存UI数据

现代app基本都是基于网络拉取数据来显示UI,但是当新的数据没有到来时,也可能用户处于一种弱网情况下,展示白屏或者loading态过久是一个很不好的体验。我们要做的是可以缓存某次用户拉取的数据,或许这部分数据是过时数据或者无效数据,但是当新的数据没有返回时,展示这样的数据会让用户得到无缝的浏览体验。

28.有效管理内存

我们经常使用的hashmap,虽然从语言层面,这是非常好用的。由于冲突的处理成本较高,于是才用了空间换时间的方案,hashmap为了防止冲突的出现,会预先分配一个较大的空间用于map,但是对于稀疏的存储,没有用到的空间就会浪费掉,这显然对于内存不友好。但是android本身提供了适用于移动设备的ArrayMap,不太方便的地方是,key只能是int或者long类型。当然这个处理使得空间和效率都有较好的提升,之后我会根据阅读源码进行一个分析,感受一下其巧妙的设计(占个坑)。

当然,没有绝对的应该使用什么,hashmap用空间换去了时间,arrayMap则是对于较多对象,访问消耗时间更长一些。对于几百个元素的map,访问频繁删减较少,我们可以使用arraymap以减少内存的使用。但是当我们有map嵌套map的操作,我们最好还是使用hashmap。

30.自动封装的性能问题

大家对基本数据类型以及java中对其的封装都很熟悉,比如int与Integer,boolean与Boolean。后者为基本数据类型作为范型提供了良好的支持。但是需要注意的是,我们在使用Integer value=1这种语句时,java其实自动对我们的变量进行了封装。

当这种操作被用于循环中时,劣势就变得十分明显,当然这种写法基本不会有人采用,这个只是一个例子,说明封装基本数据类型对象会比直接使用基本数据类型产生更大的内存和时间开销。

在使用类似于hashmap这种范型容器时,我们的key如果是基本的数据类型,可以采用ArrayMap等以基本数据类型作为key的工具。同时他们在较小数据下内存消耗更少。

31.枚举类的性能问题

枚举类是开发中常用的类型,但是android官方并不建议使用这个类型,主要原因实在内存的损耗上,在视频中有一个例子。我们知道,在安卓应用开始运行时,系统会将我们的dex文件加载到内存中

上述简单的三个静态变量让打包出的dex文件增长了124bytes

如果使用一个简单的枚举类呢,这个体积增长就相当的夸张。

实际上,每一个枚举值由于创建散列和缓存等,都会有较大的开销。所以安卓团队建议压根就不要使用枚举类,否则对于大项目,久而久之都不会知道问题出在了哪里。那么对于枚举类提供的编译、运行时检查限制,我们可以使用@intdef @StringDef注解实现,而其本质上,还是int或者string类型。

32.关于内存回收

我们知道,在我们的应用进入后台的时候。系统为了保持流畅的前后台切换,理论上我们所有在堆内存的对象都会被保留在内存中。但是随着应用越开越多,当设备内存不足时,系统会选择杀掉一部分应用以释放内存。我们不清楚用户使用的习惯,但是我们肯定不希望我们的应用直接被杀死,以至于下次打开应用变得很慢。为此android为我们提供了一个回调函数,onTrimMemory(int level)。这个回调函数可以在四大组件中进行实现。它主要的作用是在应用进入后台后,在回收的各个阶段进行回调。我们可以实现其以释放一定的资源,从而避免应用被整个杀死,而是在下次快速热启,再进行一些资源加载。比如我们可以回收一些位图资源等。

Android系统会根据不同等级的内存使用情况,调用这个函数,并传入对应的等级:

  • TRIM_MEMORY_UI_HIDDEN 表示应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键导致应用的UI界面不可见.这时候应该释放一些资源.
  • TRIM_MEMORY_RUNNING_MODERATE 表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了。
  • TRIM_MEMORY_RUNNING_LOW 表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能,同时这也会直接影响到我们应用程序的性能。
  • TRIM_MEMORY_RUNNING_CRITICAL 表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候我们应当尽可能地去释放任何不必要的资源,不然的话系统可能会继续杀掉所有缓存中的进程,并且开始杀掉一些本来应当保持运行的进程,比如说后台运行的服务。

当应用程序是缓存的,则会收到以下几种类型的回调:

  • TRIM_MEMORY_BACKGROUND 表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足,从而让我们的程序更长时间地保留在缓存当中,这样当用户返回我们的程序时会感觉非常顺畅,而不是经历了一次重新启动的过程。
  • TRIM_MEMORY_MODERATE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置,如果手机内存还得不到进一步释放的话,那么我们的程序就有被系统杀掉的风险了。
  • TRIM_MEMORY_COMPLETE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉我们的应用程序,在这个时候应当尽可能地把一切可以释放的东西都进行释放。

此外,在使用的时候尽量使用>=来进行判断,以免之后系统更新假如新的确定值情况。

33.注意view导致的内存泄露

通常来说,我们不会认为view是内存消耗的“大户”,这个倒没什么大问题,但是有时候,view引用的对象,可能是内存泄露的重点。view一般会持有其activity等的context,所以当view发生内存泄露,也会导致activity无法被回收,这就导致了很严重的内存泄露。开发时需要注意的问题:

1.尽量不要在view内部使用异步回调。首先,异步回调的执行时机并不确定,可能发生在view被销毁之后,也可能回调之前,activity应该被回收而没被回收,导致内存发生抢占等问题。

2.不要让view被静态对象持有,导致其生命周期不正常的长。

3.不要把view放到一些内存模式不确定的存储中,如WeakHashMap中以view作为value,由于WeakHashMap会把value作为强引用,又会产生一个难以察觉的引用阻碍GC。

34.定位服务与电池

定位服务在现代移动设备中不可或缺,但是受限于手机电量,频繁的位置请求可能会让电量消耗过快从而用户体验很差,我们往往需要在电量与功能上寻找平衡点。当然,一个固定数值的请求总是不太灵活。

 可以参考网络传输中的移动窗口,当用户长时间位置没有太大变化,我们可以延长请求位置的间隔,相反,如果用户位置变化较大,我们可以适当缩小请求间隔。

同样,请求位置信息也有两种方式。

35.关于两次layout

layout是构建安卓页面的核心,但是如果不注意,layout将成为一个十分耗费性能的部分。

对于我们常用的relativelayout,安卓页面渲染的主要流程:计算大小与位置---通过不同view相互关系来进行边界的确定,也就是当一个view发生变化的时候,往往会带动其他view甚至于整个页面发生重新布局。当然这种情况在很多布局中都会出现,但是当我们使用了多层嵌套之后,这种重新布局的代价会高速增长。在开发中要注意以下问题

1.尽量使用层级少的布局

2.只在确定必要的时候进行重新布局

36\37.网络表现与批处理

对于移动设备来说,流量与电量是两个非常重要的资源。基本的网络请求大概有三种,一种是用户主动要求刷新数据,第二种是服务器希望用户更新的数据,第三种事一些实时的数据,包括位置信息、上传数据等等。其中后两者是我们需要极力减少的网络请求。

1.首先,不要进行轮询操作,这种操作或许在pc上有一定的使用场景,但是移动设备上不要做这种浪费电量带宽的事情。

2.将需要下载或者上传的数据进行批处理,即积攒一定的数据再进行网络的传输。

3.进行预下载预处理等工作,防止用户产生频繁的网络请求。

每一次网络请求过后,硬件都会在请求后保持一段时间的活跃以防其余的请求到来,所以当网络请求频繁的时候,硬件可能保持长时间的活跃。从而加快电量的消耗。

最简单的批处理策略就是,对于一个请求不要立刻进行处理,而是先放在一个队列中,等待适当策略进行处理。

38.任务调度

我们在日常使用中,很多的任务可能不需要及时的执行,那么选择一个合适的时机、比如连接了wifi或者充电时执行等。Android提供的JobScheduler是一个非常好的工具。

JobScheduler的使用和原理 - 简书

39-41.传感器相关,暂不更新

42.对象池

内存抖动的发生往往是因为在短时间内创建了大量的临时对象,频繁的创建以及销毁,导致内存中出现过多零碎的碎片。解决这个问题一个很好的方式是使用对象池。当我们使用完一个对象的时候,可以不立即释放内存,当下一个同类型的对象需要被创建时,我们可以直接使用之前保留的对象。

 使用对象池可以有效防止内存抖动的发生,在程序运行过程中内存占用逐渐成为一个稳定状态,也可以减少gc时间的发生,节省帧时间。

1.尽量不要用一个对象再向对象池中分配一个,可以一次分配一组。

2.做好预分配

3.让使用频繁的对象进入对象池,注意对象池的管理和释放。

43.迭代器

首先我们要清除,每次使用迭代器,都会产生一定的内存占用。当然几个迭代器当然没有什么大问题。但是当我们在循环内部或者onDraw方法内部生成迭代器,可能导致较大的内存消耗。还有就是,迭代器在每个next调用之前,都会对容器的完整性进行检查。

 

实验对比了for loop、简写迭代器以及迭代器在arraylist和vector上的表现,显然速度上for loop是更快的。 

44.LRU缓存 

缓存已经是一个老生常谈的问题了,从pc机器发展初期,缓存就是一个很重要的手段。在移动端有线的资源上更是如此。LruCache是Android中非常好用的缓存方式,使用key value的形式存储,基本思想就是当前越常用的排位越靠前。

确保cache的容量不会太大,导致OOM,也不会太小,防止过多的缓存回收和重新加载。一般使用当前应用占用内存的1/8作为缓存大小。

 整体使用方式像是一个hash表,当获取不到资源时,将文件重新放入缓存即可。LRUcache会处理资源的回收和释放。

45.使用lint检查

使用lint检查可以排查代码中的一些隐患。

46.阿尔法混合的隐藏代价

当我们绘制不透明元素时,每个像素只需要绘制一次,因为前面的总能覆盖后面的。但是alpha透明度会导致至少两次的绘制。当然alpha变化还有一些隐藏的代价。

首先,alpha会影响整个绘制指令的重排,对于不透明的绘制指令,显然我们只需要绘制最上层的元素,那么指令就可以进行归并和优化。而对于alpha指令却不是这样,因为涉及到颜色的混合,我们需要串行执行绘制指令已得到正确结果。

其次,我们除了对view进行alpha动画,也时常会对viewgroup进行alpha变更。这里就会出现一个问题,如果单独对每个元素进行渲染,重叠部分的alpha渲染会发生叠加,如右图所示,字和图片的颜色显然更淡一些。对于这个问题,Android已经有了解决方案:绘制一帧未做alpha变换的数据到内存中,然后把这一帧的数据做alpha处理之后,渲染到屏幕(这部分数据会在会知道屏幕上之后会被抛弃)。这个确实可以解决显示上的问题。但是这却导致了整个viewgroup的重绘,明明页面并没有发生变化。

针对上述问题,可以进行下面两种方案解决

1.使用缓存数据,用空间换时间。

或者api>16使用

在进行alpha渲染之前和之后进行上述调用。以通知渲染器重新使用数据。
2.上述因为重叠部分的UI出现了异常的问题,如果你的确信不会出现UI重叠,或者为了性能可以接受因此产生的UI问题,可以重写View的hasOverlappingRendering方法,结果返回false, 它会带来渲染性能的提升。

 47.避免在onDraw中分配对象

1.对象分配是有消耗的操作,虽然很小,但是对于一个高频调用的函数,其消耗会被放大。

2.过多的对象分配会导致较多的内存碎片,从而增加gc的消耗。

3.大部分2d绘图底层代码均为c++实现,过多的临时变量创建和销毁也会多次调用c++中的析构函数。从而影响整体的性能。

4.适当使用静态对象可以更好的增加onDraw的性能。

48.严苛模式工具

为了防止主线程被耗时任务占用从而导致anr,我们可以在开发过程中使用严苛模式进行调试。

我们可以使用严苛模式,对需要在严苛模式下进行的线程规定相对应的警告错事,如输出log等。

 49.自定义view的性能

虽然Android提供了几十种标准view,但是开发中我们总会想实现一些标准view无法提供的能力,那么就产生了自定义view。但是自定义view性能维护全靠我们来进行。常见的问题

1.绘制函数调用过于频繁,导致视图可能没有改变就重新绘制

2.绘制了一些多余的区域,这些区域可能最终是不可见的

3.在绘制函数内部执行了一些耗时操作

view的绘制一定是通过重绘方法进行,但是要在真正需要调用的时候才进行调用

通过矩形区域来限制,哪些元素被覆盖、或者没有发生变化,这部分是不需要进行重新绘制的。

 

 利用好裁剪区域,裁剪区域会将剔除不需要的绘制的区域,在cliprect之后的draw方法都会生效,知道下一个cliprect被调用。

当然,在一些功能强大gpu上,有可能cpu在计算裁剪时候的花销比gpu取消重绘的花销还要大。这就要求我们在绘制函数保持高效运行。

1.不要绘制不可见(在屏幕外部的元素)的元素

2.不要使用硬件加速不支持的绘制方法,因为在绘制方面,gpu方法往往都会比cpu执行要高效的多。

3.前面说到的,不要在onDraw方法内进行对象的分配。

50.延时批处理

51.更小的图像格式

png、jpeg等图片格式确实在网络传输条件下节省了带宽。但是当我们把图片存入磁盘或者读取到内存时,仍会有问题出现。首先,当图片读入内存时,虚拟机会为图片分配一块连续的内存。

如果这个区域太大,可能图片即使被回收了,其他对象也不会进行内存调整。这可能导致应用可使用的连续内存越来越少,从而导致gc发生。

不管是什么格式的图片,在加载到内存时,都是一种可渲染的格式。因此为了网络传输而进行的压缩操作在这一步是无效的。

而且在Android中,bitmap会默认采用32位数据进行渲染,也就是一个像素数据占32位内存。即使没有alpha通道的图片也会采用这种方式。而且在渲染之前,这部分数据会作为纹理传递到gpu,也就是一个位图同时使用了内存和显存资源。安卓提供了牺牲视觉效果以换取空间的方式进行渲染。需要我们在实际应用中进行取舍。如ARGB_4444将每个像素占用内存越缩小一般等

一些简单的图标,也可以使用单独的alpha通道+颜色值进行混合渲染。

52.更小的png

png采用的无损压缩算法,可以很好的保留图片的质量。但是也容易造成体积的膨胀。如果对于品质要求不高或者无需alpha通道,我们可以使用jpeg有损压缩。也可以单独拆分出alpha通道,在使用的时候进行合并。或者使用谷歌开发的webp格式,它的体积比同质量的jpeg要小百分之四十,但是解码时间却长数倍(来源百度百科),实际应用中还需要进行适当的取舍。

当然,文件压缩都是在传输或者存储的时候有效,当他们被加载到内存中时,依然会被解压成可供渲染的格式。

53.预先缩小bitmap

在实际图片的大小大于需要显示的大小时,如果还是将原图进行显示,就会产生性能损耗。在我们使用bitmap时,可以优先读取图片宽高等配置,根据实际显示图片的大小,对图片进行预压缩和缩放。得到最好的性能和效果的平衡。

设置insampleSize是一个很快速的操作,通过在读取像素的时候,通过间隔读取的方式来压缩图片。当然我们有时候可能需要的不是2的次幂倍的缩小,就需要inDensity对图片进行滤波处理,当然滤波操作是一个很耗费时间的操作,所以我们可以先对像素进行压缩,再通过滤波得到我们希望的尺寸。

54.重用bitmap

大量的bitmap往往会导致内存的抖动,因为bitmap会占用内存中一块较大的连续内存,在bitmap回收后,需要进行gc才能将这样的大碎片清理掉。所以在我们日常使用中,如果需要使用大量的bitmap,可以使用对象池的方式来解决内存抖动的问题。使用inBitmap属性可以将新的像素数据加载到现有的bitmap中。需要注意的是:

1.在api19之前,新的bitmap必须和旧的保持一致大小,19之后则需要小于等于目前的bitmap

2.不要改变编码格式

55-57.一些工具的使用

58-59. 渲染性能

手机硬件会在每16ms尝试进行一次刷新,以求达到60帧的绘制帧数,如果绘制操作没有在16ms内准备完成,就会出现丢帧的现象。

出现这种现象的原因很多,常见的是:绘制了太多不需要绘制的内容,运行了大量的动画。当不同的视图有重叠时,很显然重叠部分只需要保留最上方的绘制,例如我们的页面有一个背景,同时页面上还有一个卡片,那么显然,卡片下方是不需要有背景的,我们可以利用clipRect限定背景的绘制区域。从而减少这个卡片上的重新绘制。当然如果重新绘制区域不大可能不需要进行这种处理,因为现代手机的GPU性能较高,可能重绘带来的花销并不比计算裁剪的cpu花销高,所以需要我们在开发中进行适当取舍。

60.VSYNC

了解vsync之前先了解两个概念:刷新率(xxhz),表示硬件支持的刷新频率。帧率(xxfps),表示gpu每秒钟刷新重绘的次数。当刷新率和帧率不一致的时候,就会出现问题。

例如,屏幕正在绘制上一帧的图像,绘制到一半时。GPU缓冲区发生了变化,导致两张帧出现在同一个画面中。

所以解决这个问题就需要双缓冲的介入。在gpu绘制时,数据写入到backbuffer中,写入完毕,backbuffer会进入帧缓冲供屏幕绘制。周而复始。这个过程就需要vsync协助。

理想状态下,gpu绘制的帧数大于屏幕刷新频率,屏幕每次绘制完成,总会等到一个新的gpu绘制内容。但是如果屏幕刷新慢于gpu绘制,屏幕会在多个帧内保持同一个画面,也就出现了卡顿。

61.绘制相关

在我们打开gpu监控后,我们可以看到三种颜色的绘制bar

1.蓝色部分:它表示了java将绘制元素转化为gpu可以使用的格式,并将其转化为一个绘制列表的时间。如果这部分过高,可能是大量view不可用或者onDraw中有过多的操作。

2.红色部分:这部分表示Android用于渲染绘制列表所用的时间,安卓使用opengES api来进行绘制,这部分绘制需要将数据传输到gpu再进行绘制。一般来说,越复杂的view需要越复杂的opengl指令进行绘制。这也是红色部分升高的主要原因。集中的红色部分徒增可能是产生了大量重新绘制的视图。如视图旋转等。

3.橙色部分:这部分是处理时间,就是cpu通知gpu已经完成了一帧的渲染,这部分会产生阻塞,cpu会等待gpu返回的数据。这个栏目如果过长,说明gpu有很多事件需要处理。

62.60帧&16ms

63-64.Android UI与GPU

Android使用OPENGL ES api将绘制信息在cpu转换成gpu可识别格式,之后传递到gpu,由gpu进行光栅化绘制到屏幕上。实际上,从cpu传递数据到gpu是一个很耗时的操作,opengl允许数据在显存中保存,这样可以在后续用到时不再进行数据的传递。但是实际使用中,数据可能变化的极其复杂,如复杂的页面、各式各样的文字、甚至动画每一帧都在进行改变。

视图绘制的时候,安卓会生成一个内部对象,displaylist,其中包含gpu可用资源的集合、进行绘制的opengl命令等。当我们后续需要这个按钮进行一个变化的时候,只需要修改当前list,再进行一次绘制即可,但是当按钮和之前不一样的时候,我们就需要重绘操作。需要注意的是,有时候我们修改了一个视图,可能连带父视图以及其他子视图发生变化。如一个按钮的大小发生变化,会触发meaure过程,同时会触发重新layout,并影响到其他的组件。每一个环节都是需要耗时的,所以大规模的视图无效化可能导致帧丢失。比如一大组元素的不可见与一大组元素的出现,需要在开发中尤其注意。

65.

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ruozhalqy/article/details/115620047

智能推荐

291 蒙德里安的梦想(状态压缩dp)_291. 蒙德里安的梦想-程序员宅基地

文章浏览阅读246次。1. 问题描述:求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。如下图所示:输入格式输入包含多组测试用例。每组测试用例占一行,包含两个整数 N 和 M。当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。输出格式每个测试用例输出一个结果,每个结果占一行。数据范围1 ≤ N,M ≤ 11输入样例:1 21 31 42 22 32 42 11_291. 蒙德里安的梦想

MapReduce设计模式学习笔记_mapreduce四个阶段的任务-程序员宅基地

文章浏览阅读1.9k次。MapReduce_mapreduce四个阶段的任务

Linux 常见特殊符号-程序员宅基地

文章浏览阅读78次。[size=large][b]1,> 重定向输出符号 用法:命令 >文件名 特性:覆盖(当输入文件和输出文件是同一文件,文 件内容被清空;不适合连续重定向) 典型应用:合并文件(cat a b >c);清空文件(cat /dev/null);文件打印2,>> 重定向输出符号 用法:命令 >>文..._linux不同字体下的¥

DXF格式文件导入AD,做成可加工的PCB文件_ad怎么根据dxf文件绘制pcb-程序员宅基地

文章浏览阅读2.4k次。DXF文件导入AD注意事项也可以导入到KEEPOUT或者机械层对于有问题,可以tool菜单,reset,对于导入绿色是报错,相当于线宽不满足规则,没有改变线宽,默认为10mil=0.0127mm_ad怎么根据dxf文件绘制pcb

如何通过CanApe进行变量的测量和标定_canape怎么添加标定量-程序员宅基地

文章浏览阅读2.3k次,点赞5次,收藏11次。1、展开对应Device下的A2L文件,可见下面的测量量和标定量2、点击Start->测量设置3、将 测量量 尽量平均的分配到各个XCP Event中。添加方法如下双击需要添加的测量量即可添加到对应的Xcp Event中4、回到主界面,按如下方法将测量量添加到图形化窗口中,然后start5、右键,将图形调整到合适大小如下图所示。_canape怎么添加标定量

基于角色、标记及BLP模型的多级访问控制-B/S架构OA系统应用_blp规则 权限控制-程序员宅基地

文章浏览阅读4.1k次。 福建电脑2007年第2期 基于角色、标记及BLP模型的多级访问控制—B/S架构OA系统应用(桂林电子科技大学 计算机系,广西桂林 541004 ) 摘 要: 本文基于BLP模型的安全性和角色访问模型的简便性,结合两者优点,克服其缺点。根据办公自动化(OA)系统的实际情况设计一种新的模型。BLP模型的读写分离,角色可以授权和回收,角色层次机制,标记等保证信息_blp规则 权限控制

随便推点

用matlab绘制信源熵函数曲线,信息理论编码之实验一 绘制二进制熵函数曲线.ppt...-程序员宅基地

文章浏览阅读2.2k次。信息理论编码之实验一 绘制二进制熵函数曲线一、实验目的 熟悉工作环境及工具箱 掌握绘图函数的运用 理解熵函数表达式及其性质 二、实验原理 熵 自信息量是针对信源的单个符号而言的,而符号是随机发生的,因此单个符号的不确定性不足于代表信源的不确定性性质,为此,可对所有符号的自信息量进行统计平均,从而得到平均不确定性。 熵的表示 注意的问题 熵是自信息量的统计平均,因此单位与自信息量的单位相同,与熵公..._用matlab制作信息熵函数曲线

重复测量设计计算机结果分析,样本量估算4.7 | 重复测量设计方差分析样本量估算...-程序员宅基地

文章浏览阅读1.6k次。【例1】某研究拟调查新型降压药A对收缩压的影响,比较高血压患者服用新型降压药A与标准降压药后,随着时间推移,收缩压平均值之间是否有差异。患者服用新型降压药A后每天测一次血压,连续测6天。既往研究表明服用标准降压药的受试者平均收缩压为143.0mmHg, 标准差为25mmHg, 同一个受试者相邻测量点间的自相关系数均为0.76。预试验中服用新型降压药A后平均收缩压较标准降压药降低了14.3mmHg,..._单组重复测量设计样本含量估算表

VB数组详解-程序员宅基地

文章浏览阅读750次,点赞2次,收藏4次。一、数组的概念数组是一组具有相同类型和名称的变量的集合。这些变量称为数组的元素,每个数组元素都有一个编号,这个编号叫做下标,我们可以通过下标来区别这些元素。数组元素的个数有时也称之为数组的长度。一般情况下,数组的元素类型必须相同,可以是前面讲过的各种基本数据类型。但当数组类型被指定为变体型时,它的各个元素就可以是不同的类型。数组和变量一样,也是有作用域的,按作用域的不同可以把数组分为:过..._vb数组的概念

php如何采集动态页生成的静态数据,PHP-php如何实现自动生成静态页 或提供思路...-程序员宅基地

文章浏览阅读51次。require_once(“db.php”);$title=$_POST[“title”];$content=$_POST[“content”]; //获得表单变量//以下建立一文本文档,其值自动计数$countfile="count.txt";if(!file_exists($countfile)){fopen($countfile,"w"); //如果此文件不存在,则自动建立一个}$fp=fo..._$title = $_post['title']; $content = $_post['content'];

QT——在已经安装配置好QT的情况下如何更新QT的组件_已安装的qt怎么更新安装组件。离线版qt安装-程序员宅基地

文章浏览阅读1.6k次。(一)找QT在线安装镜像地址1、打开 http://download.qt.io/static/mirrorlist/,这里面是各国的镜像站点2、我选了清华的站点,点HTTP3、按下面的路径进入qt文件夹online/qtsdkrepository/windows_x86/root/qt/ 然后就在下图这个界面复制当前地址(二)QT更新组件的设置是不能在QT里面直接找到的,需要用到MaintenanceTool.exe文件,这个文件就在你安装QT的路径下面,如下图所示..._已安装的qt怎么更新安装组件。离线版qt安装

计算机视觉论文_jain a k, duin r p w, mao j. statistical pattern r-程序员宅基地

文章浏览阅读3.6k次。找到了一个很好的博客,作者很详尽的总结了一系列有深刻影响的计算机视觉方面的论文,希望有更多的人能够看过这些经典的论文。在此转载改博客,在此向水木上表示深深的敬意,只有有更多像这样善于总结和分享的人才能给我们更好的网络学习环境。前言:最近由于工作的关系,接触到了很多篇以前都没有听说过的经典文章,在感叹这些文章伟大的同时,也顿感自己视野的狭小。 想在网上找找计算机视觉界的经典文章汇总,一_jain a k, duin r p w, mao j. statistical pattern recognition: a review[j]. i

推荐文章

热门文章

相关标签