android性能优化的方方面面

Posted 潇潇凤儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android性能优化的方方面面相关的知识,希望对你有一定的参考价值。

 

1、电量优化

Battery Historian工具查看关于移动蜂窝模块的电量消耗
有针对性的把请求行为捆绑延迟到某个时刻统一发起请求
把请求任务延迟到手机网络切换到WIFI、充电状态下再执行,用JobScheduler实现,给任务设置延迟间隔,执行条件,增加重试机制
对数据Prefetch(预取)、压缩(compress),预取需要预先判断用户在此次操作后,后续零散请求可能会很快马上触发,把后面5分钟有可能会使用的零散请求都一次集中执行完毕
在上传和下载数据前,使用CPU对数据进行压缩和解压
控制更新频率,仅在需要刷新显示数据时才触发获取最新数据显示
增量更新操作,仅在数据变量时才推送

 

2、网络优化

图片方面:
1、对于同一图片服务器准备多套不同分辨率大小图:手机可以根据设备分辨率大小去请求不同的分辨率的图片;
2、对于同一图片服务器准备多套不同压缩比的图:手机可以根据当前网速去请求不同压缩质量的图片,比如4G\\wifi下请求100%压缩比图片,即原图;3G网络请求压缩比为75%的图片;2G网络下请求压缩比为50%质量的图片;
3、手机端根据手机当前网络选择不同加载模式,极速模式:对于2G\\3G网速下,无图模式预览,4G、wifi下有图模式预览
数据使用gzip压缩,对请求的数据和返回的数据进行压缩后传输
多个请求合并
失败后重传机制
TCP长连接,HTTP是无状态连接,可以让TCP keep-alive
服务器和手机端传输的数据若是json,可用ProtoBuffer,它是二进制协议,在表示大数据时,空间比JSON小很多
设置取消网络请求

3、应用启动速度优化(冷启动)

方法1、TraceView:查看各线程各方法执行时间
1)代码中添加,在觉得耗时的地方加上以下语句
Debug.startMethodTracing("GithubApp");// 在自己想要开始调试的地方start
Debug.stopMethodTracing();// 在合适的地方stop
从手机导出trace文件,用android Studio打开,查看方法执行时间,点击Top Down,会按耗时从长到短进行方法排序,耗时超过500ms都要引起注意
2)利用Eclipse的DDMS查看要trace的App进程
2、SysTrace:查看UI的绘制问题,跟踪CPU执行情况等
在方法里面添加trace.beginSection()与trace.endSection()方法来声明需要跟踪的起止位置,系统会帮忙统计中间经历过的函数调用耗时,并输出报表
3、AlloCation Tracker:内存分配跟踪
4、Trace OpenGL:录制每一帧的绘制过程,
5、Heap和Memory Mointor:查看内存分配和变化情况,还可查看内存抖动和GC情况。
优化方法1、防止应用进入黑屏或是白屏,给主界面主题的windowBackground设置成闪屏图片
1)设置图片
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/white" />   <!-- 底层白色 -->
    <item>
        <bitmap
            android:gravity="center"
            android:src="@drawable/ic_github" /><!-- 顶层Logo居中 -->
    </item>
</layer-list>
2)设置主题
<style name="SplashTheme" parent="AppTheme">
    <item name="android:windowBackground">@drawable/logo_splash</item>
</style>
在AndroidMainfest.xml的启动页设计主题,在启动页的onCreate方法中在super.onCreate()前把主题设置回来
2、对于Application.onCreate()中第三方SDK初始化单开一个线程中初始化(设置线程优化级为后台线程)或是延时初始化
3、开发者模式中Show GPU Overdraw,查看是否存在过渡绘制,去掉不必要的背景
4、View Hierarchy,查看界面的布局、View和层级嵌套情况,可以用ViewStub\\merge\\include进行懒加载、布局复用,用ReleativeLayout代替LinearLayout、用ConstraintLayout进行扁平化布局
5、用户信息本地缓存,之前用户每次进入主页前都要进行自动登录操作,现在将用户信息序列化保存在本地并设置token有效期,进入首页前先从缓存中读取用户信息若未过期直接先用缓存信息,再单开线程进行登录、获取用户信息,更新本地缓存,如果发现登录不上,可能用户密码修改过,再提示登录过期进入登录页。
6、首页由多个fragment组成,fragment懒加载,刚进入只加载首页的fragment,其他页面只在用户点击tab时才创建且只在用的时候加载,且只用加载一次
7、应用是多进程的,只在主进程中执行Application.onCreate()方法

4、自定义view优化

1、如果View设置了alpha值,至少需要渲染两次,因为alpha的view需事先知道混合View的下一层元素是什么,再结合上层的View进行 Blend混色处理
可以先把View上元素按从后到前的方式绘制出来,但不直接显示到屏幕上,使用GPU预处理后,再由GPU渲染到屏幕上,GPU可以对界面上的原始数据直接旋转,设置透明度。可通过setLayerType指定View应该如何渲染。
setLayerType(View.LAYER_TYPE_HARDWARE, null)
2、对于包含阴影区域的view,它不存在层叠关系,为了让渲染器知道这种情况,避免为View占和额外的GPU内存空间,可以做如下设置
@Override
public boolean hasOverlappingRendering()
    return false;

3、避免在onDraw()方法里执行内存分配操作,因为onDraw()是执行在UI线程,设备有一定的刷新频率,会导致view的onDraw()方法被频繁调用,如果在onDraw()执行内存分配操作,会容易触发GC,导致内存抖动,频繁触发GC操作会影响到CPU从而影响到电量消耗。
4、避免调用onDraw()操作,用View.invalidate()会触发view重绘,仅在View内容发生改变时才触发invalidate方法,尽量使用clipRect()方法提高绘制性能。或是调用带四种参数不同类型的invalidate(),而不是调用无参的方法。无参变量需要刷新整个view,而带参数的方法只需刷新指定部分的view。在onDraw()方法中减少冗余代码。
5、减少不必要的绘制元素,对于不可见的元素,尽量避免绘制;
6、对于不在屏幕上的元素,可以使用Canvas.quickReject把它们剔除掉,避免浪费CPU资源。尽量使用GPU来进行UI渲染,这样可极大提高程序的整体表现性能。

7、使用硬件加速,GPU硬件加速可以带来性能增加。

8、状态保存与恢复,如果因内存不足,Activity置于后台被杀重启时,View应尽可能保存自己属性,可以重写onSaveInstanceState和onRestoreInstanceState方法,状态保存。

5、Bitmap优化

LRUCache1、为LRU Cache设置一个合理的缓存大小值
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int availMemInBytes = am.getMemeoryClass()*1024*1024;
LruCache bitmapCache = new LruCache<String, Bitmap>(availMemInBytes/8);
2、重写sizeOf方法
@Override
protected int sizeOf(String key, Bitmap value)
    return value.getByteCount();
重用1、设置BitmapFactory.Options().inBitmap=true重用Bitmap,每次新创建出的bitmap都会需要占用一块单独的内存区域,设置inBitmap后,会告知Bitmap解码器尝试使用heap中bitmap占据的pixel data内存区域,而不是重新申请一块区域来存放bitmap。
inBitmap使用条件限制,SDK11~18,bitmap大小必须一致才能被重用,SDK19开始,新申请的bitmap大小必须<=已赋值过的bitmap大小,且新申请的bitmap须和旧bitmap有相同的解码格式
2、创建bitmap对象池,让后续的bitmap创建都从对象池中找到复用对象,如Glide中的
PNG压缩1、对于ARGB_8888占用4字节,RGB_565和ARGB_4444占用2字节,ALPHA_8占用1字节,如果图片不包含透明度,可用RGB_565解码率,BitmapFactory.Options()设置inPreferredConfig=BitmapConfig.RGB_565;
2、pgn格式图片会占用更多磁盘空间,可考虑使用JPEG,还可用webP格式图片
3、BitmapFactory.Options().inSampleSize设置图片缩放比,还可以用inScaled\\inDensity\\inTargetDensity属性对解码图片做处理;
3、设置Options().inJustDecodeBounds=false,事先获取图片大小但不占用内存

6、StrictMode模式

StrictMode1、在手机设置中打开Strict Mode选项,若程序存在潜在隐患,屏幕会闪现红色; 
2、可以在代码中加入StrictMode监听
if(DEVELOP_MODE)
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
        .detectLeakedSqlLiteObjects().detectLeakedClosableObjects()
        .penaltyLog().penaltyDeath().build());

 

7、Android系统提供的特殊容器避免自动装箱

(ArrayMap除外,它不是Android系统提供,且也没有避免自动装箱)

ArrayMap1)数据结构
两个数组:数组1存放Key值的hashCode(hashes of keys),按顺序存放
数组2存放key-value值,根据数组1中的位置存放key-value,位置是第一个数组的2*i\\2*i+1处
相对于HashMap来说,存放相同元素,占用内存更少,紧密存放的。
2)取数据,先计算key转换后的hash值,对hash数组使用二分查找法寻找对应的index,再通过index找数组2中要访问的键值对,若数组2中对应的key和查询的key不一致,认为发生hash冲突,以当前key为中心点,分别向上向下展开搜索,逐个对比查询,直到找到匹配的值。
3)从中间删除和插入数据,会需要移动后面的数组元素,耗时会更长。
SparseArray<int, object>
数据结构:两个数组,第一个数组是整型数组,第二个数组是Value,避免了对key的自动装箱
int[] mKeys;
Object[] mValues;
SparseIntArray<int, int>   key和value都需为int型
private int[] mKeys;
private int[] mValues;
SparseBooleanArray<int, boolean> key为int型,value为boolean型
private int[] mKeys;
private boolean[] mValues;
SparseIntMap<int, obj>
SparseLongArray<int, long> key为int型,value为long型
private int[] mKeys;
private long[] mValues;
LongSparseArray<long, Object> key为long型,value为Object型
private long[] mKeys;
private Object[] mValues;

 

8、内存优化

内存泄漏不再使用的对象无法及时释放,不仅占用了宝贵的内存空间,且很容易导致后续需要分配内存时空闲空间GC频繁被触发,出现内存抖动,最终很可能内存不足而出现OOM
内存溢出Android系统为每个应用程序都设置了一个硬性的Dalvik heap size最大限制阈值,这个阈值会因不同设备上RAM大小不同而各有差异,如果应用占用内存空间已接近这个阈值,再尝试分配内存,很容易引发OOM
避免方法1、减小对象的内存占用
1)使用更加轻量的数据结构,如用ArrayMap/SparseArray代替HashMap,因为HashMap需要一个额外的对象来记录Mapping操作,而SparseArray避免了对key和value的自动装箱,也避免了装箱后的解箱。
2)避免使用Enum,可用静态常量;
3)减小Bitmap对象的内存占用,inSampleSize设置缩放比,decodeFromat设置解码格式
4)使用更小的图片,对图片进行压缩
2、内存对象重复利用
1)编码时显示的在程序中创建对象池;
a) 在ListView/GridView中对convertView复用;b) 利用LRU机制处理好Bitmap对象复用;c) 利用inBitmap特性让Bitmap解码器尝试使用已经存在的pixel data内存区域,而不是重新在内存中申请一块区域来存放bitmap。d)避免在onDraw方法里执行对象的创建;e)字符串拼接使用StringBuilder
2)利用系统框架既有特性达到减少对象的重复创建,如利用Android系统内置的字符串/颜色/图片/动画/样式等
3、避免对象的内存泄漏
1)注意Activity的泄漏
a) 如内部类引用导致Activity的泄漏(引用链Looper->MessageQueue->Message->Handler->Activity),解决办法,在UI退出前,执行remove Handler消息队列中消息与runnable对象或使用static+weakReference方法达到新开的Handler与Activity之间存在引用关系。
b) Activity Context被传到其他实例中;
2)考虑使用Application Context代替Activity Context;
3)注意临时Bitmap对象及时回收,如临时创建某个相对比较大的bitmap对象后在经过变换得到新的bitmap对象后,应尽快回收原始bitmap。但要注意Bitmap.createBitmap()返回的bitmap可能和source bitmap是同一个,故在回收时,需要检查两个bitmap引用是否相同,只有在不等的情况下,才能执行source bitmap的recycle方法;
4)监听器的注销,如add Listener需要remove Listener, register需要unregister
5)缓存容器的对象
6)WebView可能导致的泄漏,可以为WebView开启另外一个进程,通过AIDL与主进程通信,WebView所在进程根据业务的需要选择合适的时机进行销毁。
7)Cursor、Stream等对象及时关闭。
4、内存使用策略优化
1)谨慎使用Large Heap,使用额外的内存空间会影响系统整体的用户体验,并且会使得每次gc运行时间更长;在任务切换时,系统的性能会大打折扣。且large heap并不一定能够获取到更大的heap.
2)综合考虑设备内存阈值与其他因素设计合适的缓存大小,要考虑应用剩下了多少可用内存空间;有多少图片会被一次呈现到屏幕上,有多少图片需事先缓存好以便快捷滑动能够立即显示到屏幕上;设备的屏幕大小与密度是多少;不同页面针对Bitmap设计的尺寸与配置是什么;页面中图片被访问频率等;
3)onLowMemory:系统提供了一些回调来通知当前应用的内存使用情况,当所有background应用都被kill掉时,forground应用会收到onLowMemory的回调,这种情况下,需要尽快释放当前应用的非必须内存资源,从而确保系统能够继续稳定运行。
onTrimMemory:系统内存达到某些条件时,所有正在运行的应用都会收到此回调,系统开始清除LRU缓存中的进程
4)资源文件需要选择合适的文件夹进行存放;
5)try catch某些大内存分配操作,事先评估可能导致OOM的代码,加入catch机制,考虑在catch里尝试一次降级的内存分配操作。
6)谨慎使用static对象,留意单例对象不合理的持有;
7)优化布局层次,减少内存消耗,越是扁平化的布局,占用内存越少;可考虑自定义view来达到目的。
8)使用nano protobufs序列化数据
9)谨慎使用依赖注入框架,谨慎使用多进程,使用proguard来剔除不需要的代码,谨慎使用第三方库,考虑不同的实现方式来优化内存占用;

以上是关于android性能优化的方方面面的主要内容,如果未能解决你的问题,请参考以下文章

Android 性能优化的方面方面都在这儿

Linux性能优化从入门到实战:01 Linux性能优化学习路线

Android性能优化--关于内存溢出

Unity3D性能优化--- 收集整理的一堆

Android 性能优化--卡顿优化

Android性能优化之如何避免Overdraw