一般我们写的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、异常崩溃发生
使用一些资源对象cursor、file、sqlite、bitmap等使用完后及时关闭或释放。
使用改进型的for循环
Context使用不当造成内存泄露不要对一个Activity Context保持长生命周期的引用。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换。
注册的广播接收器、注册的观察者等一定要注销否则会导致观察者列表中维持对象的引用阻止垃圾的回收。
handler在使用的时候需要注意在activity的Destory或者stop中要移除消息队列中的消息mHandler.removeCallbacksAndMessage(null)避免引发内存泄漏。
webview使用完必须手动销毁否则容易造成内存泄漏。
不要在执行频率很高的方法或者循环中创建对象可以使用HashTable等创建一组对象容器从容器中取那些对象而不用每次new与释放避免代码设计模式的错误造成内存泄露
对于一些常驻的后台service使用完后要及时停止。
一些数据类型的使用如果用int型就尽量不要使用integer因为int对象只有4个字节Integer对象是16个字节的这样会造成额外的内存和时间的消耗。
当使用的对象数据比较小1000以内但是访问特别多或者删除和插入频率不高时相比HashMap,使用ArrayMap会更好。
尽量减少或者不使用枚举enum类型因为枚举的内存开销比一般的定义常量多三倍以上。
图片格式的使用降低图片的质量是可以减少内存的消耗的位图的最高是ARGB_8888最低是ALPHA_8,还有RGB_565和ARGB_4444可根据实际需求使用。实际图片缓存可以使用第三方的例如glide、freso、picasso等。
耗时的操作一定要放在子线程中。比如数据的加密、解密、编码、运算、处理大量数据等。
实际还有很多可以实现提高应用的流畅度和减少应用卡顿、崩溃的方法可自行寻找学习其实这也跟我们写代码的方式有关最好养成良好的写代码习惯和风格。可以提高后期代码的维护性。
4、存储优化
android存储方式android系统有四种存储方式分别是sharedPreferences、文件、SQLite和ContentProvider。
sharedPreferences:一个轻量级的数据存储方式适用于保存软件配置参数。其实质是采用了xml文件存放数据,路径为:/data/data/<package name>/shared_prefs。它的优点是使用简单、速度快缺点是只能存储boolean、int、float、long和string五种数据类型。
SQLite: SQLite是一个嵌入式库并且实现了零配置、无服务端和事务功能的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、网路方面
使用wifi传输数据的时候应该尽量增大每个包的大小并降低发包的频率。
在蜂窝移动网路下最好做到批量执行网路请求尽量避免频繁的间隔网路请求。
尽量在Wi-Fi环境下使用数据传输
使用高效的数据格式和解析方法在数据格式方面使用JSON和Protobuf效率比xml好。
压缩数据格式比如采用GZIP压缩这昂可以提高下载速度也可以提高上传数据时间节省更多电量。
2、尽量减少浮点运算浮点运算比整数运算更消耗CPU会增加耗电。
3、避免wakeLock使用不当下面是几种使用方式一定要根据自己需求使用完成后记得释放wakeLock。
PARTIAL_WAKE_LOCK:保持CPU
运转屏幕和键盘灯有可能是关闭的。
SCREEN_DIM_WAKE_LOCK保持CPU
运转允许保持屏幕显示但有可能是灰的允许关闭键盘灯
SCREEN_BRIGHT_WAKE_LOCK保持CPU
运转保持屏幕高亮显示允许关闭键盘灯
FULL_WAKE_LOCK保持CPU
运转保持屏幕高亮显示键盘灯也保持亮度
ACQUIRE_CAUSES_WAKEUP不会唤醒设备强制屏幕马上高亮显示键盘灯开启。有一个例外如果有notification弹出的话会唤醒设备。
ON_AFTER_RELEASEWakeLock
被释放后维持屏幕亮度一小段时间
4、使用Job Scheduler,android5.0后提供了一个jobScheduler组件只有一系列的预置条件满足时才执行对应的操作这样既省电又保证了功能的完整性。在以下场景可以考虑使用
重要不紧急的任务可以延迟执行如定期数据库数据更新和数据上报。
耗电量较大的任务比如充电时才希望执行的数据备份操作。
不紧急可以不执行的网路任务如在wifi环境预加载数据。
可以批量执行的任务。
5、耗电检测,可以使用下面命令查看
adb shell dumpsys batterystats
6、代码编写优化
1、遵循单一职责原则一个模块有且只有一个职责如果一个模块或者一个类提供了不同类型的功能活着一个功能需要几个模块共同完成这就有可能在抽象层上设计不合理。
2、开闭原则在面向对象的语言中对象对可扩编开放对修改关闭所以需要考虑添加扩编另外的内容时是否会带了新的问题。
3、代码复用根据“三振法”即如果代码复用超过三次提取公共的代码重构。
4、更合理的代码写的时候思考实现这个功能是否有更好的方法实现。
5、潜在的缺陷在写代码的时候需要思考异常情况考虑是否全面错误的传参是否会引起其他错误循环是否是以我们期望的方式终止。
6、方法名、类名、资源名、变量名书写要规范。
最后app的性能优化除了这些还又很多对于不同的问题优化方法也不一定一样只有找到问题的根本才能达到优化的目的。优化也是为了提高用户的体验所以我们得多站在一个用户的角度上考虑才能更好的做好一个产品。