一般我们写的app操作的数据多的时侯或者平时使用的时候都会经常出现卡顿、闪退、ANR停止运行等各种问题。这样会导致用户使用的体验非常差,因此在写代码的时候我们就要注意一些代码的书写方式和做好优化了。一般app的优化我们可以从启动、布局、内存、存储、耗电等进行优化。
*应用的启动分为冷启动和热启动。
冷启动:应用第一次启动的时候,系统会为应用创建一个新的进程,所以首先会创建和初始化appliction类,然后再创建和初始化activity类(包括测量、布局、绘制),最后显示在界面上。如下图
热启动:热启动会从已经有的进程中启动,所以不会再去创建和初始化application,而是直接创建和初始化activity。这也可以看出application只会初始化一次,只包含activity中的生命周期流程。
*一般我们要优化启动得先知道我们启动耗费了多长时间,要优化到什么程度。这就需要我们去准确获取应用启动时间。
应用的启动时间可以通过adb命令来获取,入下图
上面可以看出有三个时间
ThisTime:一般和totalTime时间一样,如果在应用启动时打开一个过渡的全透明的页面预处理一些事情,在显示出主页面,这样比totalTime 小。
TotalTime:应用的启动时间,包括创建进程+application初始化+activity初始化到界面显示
WaitTime:一般比 TotalTime大些,包括系统影响的耗时。
*一般我们应用功能模块越多,在需要初始化的就越多,这样就会导致应用启动越慢了。一般我们的应用优化有一下:
1、插入启动页:一般应用启动过程:点击启动应用-->application初始化--> AppstartActivity-->HomePageActivity。
因此我们一般打开一个app的时候都可以看到有启动页,也就是一些广告或者一些介绍app的宣传图片,在显示这个期间一般时间比较长,那就可以在这期间完成很多初始化工作,比如很多第三方库使用需要初始化、数据的预缓存等操作。
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、删除控件中无用的属性。
*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可以分三种类型:
在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、异常崩溃发生:
实际还有很多可以实现提高应用的流畅度和减少应用卡顿、崩溃的方法,可自行寻找学习,其实这也跟我们写代码的方式有关,最好养成良好的写代码习惯和风格。可以提高后期代码的维护性。
*android存储方式:android系统有四种存储方式,分别是sharedPreferences、文件、SQLite和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、使用事务,对于插入大量数据,使用事务可以大大减少插入时间。
2、尽量减少浮点运算,浮点运算比整数运算更消耗CPU,会增加耗电。
3、避免wakeLock使用不当,下面是几种使用方式,一定要根据自己需求使用,完成后记得释放wakeLock。
4、使用Job Scheduler,android5.0后提供了一个jobScheduler组件,只有一系列的预置条件满足时才执行对应的操作,这样既省电,又保证了功能的完整性。在以下场景可以考虑使用:
5、耗电检测,可以使用下面命令查看
adb shell dumpsys batterystats
1、遵循单一职责原则,一个模块有且只有一个职责,如果一个模块或者一个类提供了不同类型的功能,活着一个功能需要几个模块共同完成,这就有可能在抽象层上设计不合理。
2、开闭原则,在面向对象的语言中,对象对可扩编开放,对修改关闭,所以需要考虑添加/扩编另外的内容时是否会带了新的问题。
3、代码复用,根据“三振法”,即如果代码复用超过三次,提取公共的代码重构。
4、更合理的代码,写的时候思考实现这个功能是否有更好的方法实现。
5、潜在的缺陷,在写代码的时候,需要思考异常情况考虑是否全面,错误的传参是否会引起其他错误,循环是否是以我们期望的方式终止。
6、方法名、类名、资源名、变量名书写要规范。
最后app的性能优化除了这些还又很多,对于不同的问题,优化方法也不一定一样,只有找到问题的根本,才能达到优化的目的。优化也是为了提高用户的体验,所以我们得多站在一个用户的角度上考虑才能更好的做好一个产品。