app性能优化

一般我们写的app操作的数据多的时侯或者平时使用的时候都会经常出现卡顿、闪退、ANR停止运行等各种问题。这样会导致用户使用的体验非常差,因此在写代码的时候我们就要注意一些代码的书写方式和做好优化了。一般app的优化我们可以从启动、布局、内存、存储、耗电等进行优化。

1、启动优化:

*应用的启动分为冷启动和热启动。

冷启动:应用第一次启动的时候,系统会为应用创建一个新的进程,所以首先会创建和初始化appliction类,然后再创建和初始化activity类(包括测量、布局、绘制),最后显示在界面上。如下图


热启动:热启动会从已经有的进程中启动,所以不会再去创建和初始化application,而是直接创建和初始化activity。这也可以看出application只会初始化一次,只包含activity中的生命周期流程。

*一般我们要优化启动得先知道我们启动耗费了多长时间,要优化到什么程度。这就需要我们去准确获取应用启动时间。

应用的启动时间可以通过adb命令来获取,入下图


上面可以看出有三个时间

ThisTime:一般和totalTime时间一样,如果在应用启动时打开一个过渡的全透明的页面预处理一些事情,在显示出主页面,这样比totalTime 小。

TotalTime:应用的启动时间,包括创建进程+application初始化+activity初始化到界面显示

WaitTime:一般比  TotalTime大些,包括系统影响的耗时。

*一般我们应用功能模块越多,在需要初始化的就越多,这样就会导致应用启动越慢了。一般我们的应用优化有一下:

1、插入启动页:一般应用启动过程:点击启动应用-->application初始化--> AppstartActivity-->HomePageActivity。

因此我们一般打开一个app的时候都可以看到有启动页,也就是一些广告或者一些介绍app的宣传图片,在显示这个期间一般时间比较长,那就可以在这期间完成很多初始化工作,比如很多第三方库使用需要初始化、数据的预缓存等操作。

2、可以优化我们的代码,比如一些不必要在启动先加载的就不要放在初始化中加载,对于一些必须要在初始化中加载的,那我们可以通过线程、异步加载等方式实现。还有布局中的代码也可以做尽量的优化、主要就是减少布局的层级和避免过度的绘制。

2、布局优化

*应用启动慢,使用过程卡顿,造成这些问题主要场景是在ui的绘制、应用启动、页面跳转、事件响应。



页面绘制:主要是绘制的界面布局层次踢啊多,页面太过复杂、刷新不合理、由于这些原因的场景更多出现在ui和启动后的初始界面以及跳转到页面的绘制上。

数据处理:有时候应用在某些场景需要处理大量数据也有可能导致卡顿,一般分为三种情况,一是在主线程处理一些耗时的操作,而是数据处理占用cpu高,导致主线程拿不到事件片,三是,内存消耗太大导致GC频繁,引起卡顿或者应用崩溃。

*在ui的绘制过程中有三个核心的步骤:measure-->Layout-->Draw,mesure是用于计算视图的大小,layout确定视图的位置,draw是用于绘制视图。

在android系统中整体的绘制源码是在viewGroup类的performTraversals()方法,通过这个方法可以看出Mesure和layout都是递归来获取view的大小和位置,并且以深度作为优先级,当层级越深,元素越多,耗时就会越长,导致卡顿的几率也就越大。可以在as打开tools-->android -->android device monitor-->Hierarchy view查看自己写的布局的层级,最大最好不超过10级。,下面是页面构造框架图。


GPU和CUP原理在Android的绘制架构中,CPU主要负责了视图的测量、布局、记录、把内容计算成Polygons多边形或者Texture纹理,而GPU主要负责把Polygons或者Textture进行Rasterization栅格化,这样才能在屏幕上成像。在使用硬件加速后,GPU会分担CPU的计算任务,而CPU会专注处理逻辑,这样减轻CPU的负担,使得整个系统效率更高。

刷新率:屏幕每秒刷新的次数,是一个与硬件有关的固定值。在Android平台上,这个值一般为60HZ,即屏幕每秒刷新60次。即60fps/秒 即16ms/帧,如果绘制屏幕每帧超过16ms就会出现卡顿现象,所有要尽量保持一帧能在16ms内绘制完成。

要想知道自己写的布局是否有过渡绘制,最简单的方法是打开手机可以通过打开手机开发人员工具—>调节GPU过度绘制—>显示过度绘制区域,开启后就可以看到应用界面的标了不同颜色了。如下图


他们具体的含义是:


如果出现淡红色或者红色就要注意了,说明明显过渡绘制了,即绘制任务过重,导致绘制一帧内容耗时过长,需要优化布局代码,比如减少布局的层级、减少不必要的背景、暂时不显示的view设置为gone而不是invisible、自定义的on Draw方法设置canvas.clipRect()指定绘制区域或者通过canvas.quickreject()减少绘制区域、减少频繁的requerLayout()等。

*对于布局的优化我们可以从下面几个方面进行优化:

1、尽量使用RelativeLayout和LinearLayout.

2、在布局层级相同的情况下,使用LinearLayout

3、用LinearLayout有时会使嵌套层级变多,应该使用RelativeLayout,使布局扁平化。

4、使用merge。

5、如果很多布局相同的话可以共同布局来实现,比如应用头部titleBar就可以只写一个布局然后通过include添加。

6、当控件是固定大小的尽量就用固定大小,而减少使用wrap_content,因为这会增加布局measure时的计算时间。

7、删除控件中无用的属性。

3、内存优化

*java虚拟机拥有垃圾回收的机制,可以通过自动回收不用的垃圾,因此不需要在代码中分配和释放某一块的内存,不容易出现内存溢出和泄漏的问题。

*android 系统的的内存管理也是通过new关键字来为对象分配内存,通过垃圾回收器(GC)来回收。即当手机内存空间不足的时候就会根据不同的规则自动释放系统认为可以释放的内存。当我们不合理使用内存就很容易导致应用出现很多性能的问题,严重有可能崩溃(outOfMemoryError),而且一旦出现内存泄漏或溢出会很难排查哪里的问题。所以内存的合理应用也是非常有必要的,这样可以让我们的应用更流畅、用户体验更好。

*内存的回收机制:整个内存可以分为三块,yong Generation(年轻代)、old Generation(年老代)还有permanent Generation(持久代)。

yong Generation:年轻代又可分三个区,eden、s0、s1,程序中大部分生成的对象都会被存在eden区,但是当eden区满的时候,还存活的对象将被复制到so或者s1区中,如果连这两个都满了就会复制到年老代中。

old Generation: 年老代存放年轻代复制过来的对象,相对年轻代,年老大对象的生命周期比较长。

permanent Generation:这个区一般用于存放静态的类和方法,持久代对垃圾回收没有影响。


系统的年轻代和年老代采用不同的回收机制,每个内存区都有固定的大小,随着新对象陆续被分到此区域,当对象的大小临近这一级别内存的阈值时,就会触发GC操作,回收空间,用来存放新的对象。同时每一块的GC时间也是不一样的,年轻代的最短,持久代的最长,还有跟这个区中的对象数量也有关,对象越多,回收时间也就越长。

GC可以分三种类型

  • kGcCauseForAlloc:在分配内存时发现内存不足的情况下触发GC,这种情况下的GC会stop world, stop world 是由于并发GC时,其他线程会停止,知道 GC完成。
  • kGcCauseBackground:当内存达到一定的阈值时触发GC,这个时候是一个后台GC,不会引起stop world.
  • kGcCauseExplicit:显式调用时进行的GC,如果 ART打开了这个选项,在system.gc时会进行GC。

在android 4.4新增了一种ART(android runtime)模式,在GC时可以选择不同回收算法,而 Dalvik只有一种,每次触发时都会导致其他线程停止工作(包括ui线程),ART还增加了一个large object space这个主要是用于管理bitmap等占大内存对象的。ART还可以在后台整理内存、减少内存碎片,因此ART可以避免较多类似GC导致的卡顿问题。

*使用内存分析工具查找内存泄漏,使用as的可以在底部打开monitors查看memory、CPU、GPU等的使用情况。还可以打开heap viewer查看 GC情况,这个是在as的tools-->android-->android device monitor打开具体操作可自行查找。


代码中如何减少卡顿、oom、异常崩溃发生

  1. 使用一些资源对象(cursor、file、sqlite、bitmap等)使用完后及时关闭或释放。
  2. 使用改进型的for循环
  3. Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换。
  4. 注册的广播接收器、注册的观察者等,一定要注销,否则会导致观察者列表中维持对象的引用,阻止垃圾的回收。
  5. handler在使用的时候需要注意,在activity的Destory或者stop中要移除消息队列中的消息(mHandler.removeCallbacksAndMessage(null))避免引发内存泄漏。
  6. webview使用完必须手动销毁,否则容易造成内存泄漏。
  7. 不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放避免代码设计模式的错误造成内存泄露
  8. 对于一些常驻的后台service使用完后要及时停止。
  9. 一些数据类型的使用,如果用int型就尽量不要使用integer因为int对象只有4个字节,Integer对象是16个字节的,这样会造成额外的内存和时间的消耗。
  10. 当使用的对象数据比较小(1000以内),但是访问特别多,或者删除和插入频率不高时,相比HashMap,使用ArrayMap会更好。
  11. 尽量减少或者不使用枚举(enum)类型,因为枚举的内存开销比一般的定义常量多三倍以上。
  12. 图片格式的使用,降低图片的质量是可以减少内存的消耗的,位图的最高是ARGB_8888最低是ALPHA_8,还有RGB_565和ARGB_4444可根据实际需求使用。实际图片缓存可以使用第三方的,例如glide、freso、picasso等。
  13. 耗时的操作一定要放在子线程中。比如数据的加密、解密、编码、运算、处理大量数据等。

实际还有很多可以实现提高应用的流畅度和减少应用卡顿、崩溃的方法,可自行寻找学习,其实这也跟我们写代码的方式有关,最好养成良好的写代码习惯和风格。可以提高后期代码的维护性。

4、存储优化

*android存储方式:android系统有四种存储方式,分别是sharedPreferences、文件、SQLite和ContentProvider。

  • sharedPreferences:一个轻量级的数据存储方式,适用于保存软件配置参数。其实质是采用了xml文件存放数据,路径为:/data/data/<package name>/shared_prefs。它的优点是使用简单、速度快,缺点是只能存储boolean、int、float、long和string五种数据类型。
  • SQLiteSQLite是一个嵌入式库并且实现了零配置、无服务端和事务功能的SQL数据库引擎。它在广泛领域内被使用,而且单线程读写性能与MySQL比肩,并且保证ACID性。支持基本的sql语法,它提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的API。系统自带的一个数据库,使用简单、维护和管理简单。
  • File(文件):通用的文件存储方式,通常用于存储大量的数据,但缺点是更新数据比较麻烦。
  • ContentProvider:android系统中所有应用程序实现数据共享的一种存储方式。应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。特别是音频、视频、图片、通信录等。

sharedPreference优化

1、 SharedPreferences实际上是对一个xml文件存储key-value键值对,每一次的commit和apply的操作都是一次I/O写的操作,众所周知,I/O的操作是最慢的操作之一,在主线程中操作会导致主线程缓慢,所以对于SharedPreferences的设置操作,最好先获取一个editor,然后批量操作,然后调用apply方法,比commit方法略快。特别是在不需要返回值的情况下,使用apply方法可以极大提高性能。

2、当sharedPreference文件还没有被加载到内存时,调用getSharedPreferences方法会初始化文件并读入内存,这容易导致耗时过程。

3、避免频繁读写sharedPreferences,减少无所谓的调用,即在同一生命周期内,读一次即可。

4、避免进程读写sharedPreferences,因为这样需要用到contentProvider方案支持,对所有sp操作套上了contentProvider进行访问,会增加三倍左右的耗时。

SQLite数据库使用优化

1、数据库在启动的时候就准备好,这样可以避免进入应用后再初始化导致相关操作时间变长,即可以放到Application的onCreate方法中,在application生命周期结束时再关闭(应用结束时调用close方法关闭数据库)。

2、初始化 DatabaseHelper类需要context,这里的 Context一定要用ApplicationContext,因为这里是单例,在整个应用的生命周期不会销毁,如果使用某个Activity的Context,会导致这个activity的资源都不会被释放,出现内存泄漏。

3、数据库的操作都比较耗时,一定要放到异步线程中。

4、使用SQLiteStatement类来将数据插入数据库,可以减少插入时间,提高性能。

5、使用事务,对于插入大量数据,使用事务可以大大减少插入时间。

5、耗电优化


对于我们开发应用来说,对电量消耗优化也是很重要的,因此我们在开发过程中也要尽量减少电量的消耗。
1、网路方面:
  1. 使用wifi传输数据的时候,应该尽量增大每个包的大小,并降低发包的频率。
  2. 在蜂窝移动网路下,最好做到批量执行网路请求,尽量避免频繁的间隔网路请求。
  3. 尽量在Wi-Fi环境下使用数据传输
  4. 使用高效的数据格式和解析方法,在数据格式方面,使用JSON和Protobuf效率比xml好。
  5. 压缩数据格式,比如采用GZIP压缩,这昂可以提高下载速度,也可以提高上传数据时间,节省更多电量。

2、尽量减少浮点运算,浮点运算比整数运算更消耗CPU,会增加耗电。

3、避免wakeLock使用不当,下面是几种使用方式,一定要根据自己需求使用,完成后记得释放wakeLock。

  1. PARTIAL_WAKE_LOCK:保持CPU 运转,屏幕和键盘灯有可能是关闭的。 
  2. SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯 
  3. SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,允许关闭键盘灯 
  4. FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度 
  5. ACQUIRE_CAUSES_WAKEUP:不会唤醒设备,强制屏幕马上高亮显示,键盘灯开启。有一个例外,如果有notification弹出的话,会唤醒设备。 
  6. ON_AFTER_RELEASE:WakeLock 被释放后,维持屏幕亮度一小段时

4、使用Job Scheduler,android5.0后提供了一个jobScheduler组件,只有一系列的预置条件满足时才执行对应的操作,这样既省电,又保证了功能的完整性。在以下场景可以考虑使用:

  • 重要不紧急的任务,可以延迟执行,如定期数据库数据更新和数据上报。
  • 耗电量较大的任务,比如充电时才希望执行的数据备份操作。
  • 不紧急可以不执行的网路任务,如在wifi环境预加载数据。
  • 可以批量执行的任务。

5、耗电检测,可以使用下面命令查看

adb shell dumpsys batterystats

6、代码编写优化

1、遵循单一职责原则,一个模块有且只有一个职责,如果一个模块或者一个类提供了不同类型的功能,活着一个功能需要几个模块共同完成,这就有可能在抽象层上设计不合理。

2、开闭原则,在面向对象的语言中,对象对可扩编开放,对修改关闭,所以需要考虑添加/扩编另外的内容时是否会带了新的问题。

3、代码复用,根据“三振法”,即如果代码复用超过三次,提取公共的代码重构。

4、更合理的代码,写的时候思考实现这个功能是否有更好的方法实现。

5、潜在的缺陷,在写代码的时候,需要思考异常情况考虑是否全面,错误的传参是否会引起其他错误,循环是否是以我们期望的方式终止。

6、方法名、类名、资源名、变量名书写要规范。

最后app的性能优化除了这些还又很多,对于不同的问题,优化方法也不一定一样,只有找到问题的根本,才能达到优化的目的。优化也是为了提高用户的体验,所以我们得多站在一个用户的角度上考虑才能更好的做好一个产品。




  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值