布局优化
1.如果系统每次渲染时间都在16ms之内ui会非常流畅,这时候fps为60;如果一次绘制任务耗时20ms,那么16ms内信号无法绘制,该帧就会丢弃,等待下次信号才开始绘制,导致16*2ms内都显示同一帧画面,这就是画面卡顿的原因
2.避免OverDraw,例如系统默认绘制activity的背景,而如果再给布局绘制重叠的背景,就属于无效的过度绘制OverDraw
3.优化布局层级,使用include来重用layout ,merge减少嵌套 ,使用viewStub来延迟界面加载
merge标签可用于两种典型情况:
- 布局根结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容布局的parent view就是个FrameLayout,所以可以用merge消除只剩一个
- 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
ViewStub,它就是一个占位符,它在布局中不占用空间,自然inflate的时候它也不消耗资源。只有当调用ViewStub的inflate的时候才会用布局替代它,用于提高布局效率,不过使用范围很窄:ViewStub只能被inflate一次,之后就无效了不能再引用它。一般用在onCreate的时候,决定布局里面哪个部分显示,哪个不显示,可以使用ViewStub,并且只能用来Inflate一个布局文件,而不能Inflate某个具体的View。
内存优化
1.Bitmap优化;Bitmap是造成OOM的最大威胁,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获,出现异常得到一个默认的bitmap图;
缓存通用的Bitmap对象:
内存缓存(LruCache ,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除,在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。)另外传入 LruCache 的键值对不能是 null。
Lrucache:LRU即Least Recently Used,近期最少使用算法。也就是当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一种是插入顺序LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
硬盘缓存(DiskLruCache,通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/package/cache 这个路径。选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。)
key:key将会成为缓存文件的文件名,并且必须要和图片的URL是一一对应的。那么怎样才能让key和图片的URL能够一一对应呢?直接使用URL来作为key?不太合适,因为图片URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。其实最简单的做法就是将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。
MD5:根据公开的MD5算法对原信息进行数学变换后得到的一个128位(bit)的特征码。
不可逆性:这个特征码有如下特性,首先它不可逆,例如我有一段秘密的文字如:”My Secret Words”,经算法变换后得到MD5码(b9944e9367d2e40dd1f0c4040d4daaf7),把这个码告诉其他人,他们根据这个MD5码是没有系统的方法可以知道你原来的文字是什么的。
离散性:其次,这个码具有高度的离散性,也就是说,原信息的一点点变化就会导致MD5的巨大变化,例如”ABC” MD5(902fbdd2b1df0c4f70b4a5d23525e932)和”ABC “(多了一空格)MD5(12c774468f981a9487c30773d8093561)差别非常大,而且之间没有任何关系,也就是说产生的MD5码是不可预测的。
码位性:最后由于这个码有128位那么长,所以任意信息之间具有相同MD5码的可能性非常之低,通常被认为是不可能的。
DiskLruCache移除缓存:remove()方法中要求传入一个key,然后会删除这个key对应的缓存图片。这个方法我们并不应该经常去调用它。因为你完全不需要担心缓存的数据过多从而占用SD卡太多空间的问题,DiskLruCache会根据我们在调用open()方法时设定的缓存最大值来自动删除多余的缓存。只有你确定某个key对应的缓存内容已经过期,需要从网络获取最新数据的时候才应该调用remove()方法来移除缓存。每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取
补充:LruMemoryCache代码和v4包的LruCache一样,只是加了一个存储超期的处理
- 图片按需加载(要求不高时加载预览图)
- 图片大小进行缩放(设置BitmapFactory.Options的inJustDecodeBounds参数和采样率inSampleSize)
- 图片像素进行处理(RGB_565会比使用ARGB_8888少消耗2倍的内存)
- 图片回收:
生成Bitmap对象的BitmapFactory的四类方法最终都是在安卓底层实现的,对应着BitmapFactory类的几个native方法,最终都是通过JNI调用方式实现的。所以,加载Bitmap到内存里以后,是包含两部分内存区域的:Bitmap对象存储在Dalvik heap中,而Bitmap对象的像素数据则存储在Native Memory(本地内存),由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。最初需要调用recycle()方法来释放C部分的内存,安卓3.0以后,Bitmap的像素数据和Bitmap对象一起存储在Dalvik heap中,系统会自动释放 - BitmapFactory.Options.inBitmap的这个字段,假如这个字段被设置了,我们在解码Bitmap的时候,他会去重用inBitmap设置的Bitmap,减少内存的分配和释放,提高了应用的性能
自定义一个ImageLoader:图片的同步,异步加载;图片压缩;三级缓存
2.代码优化;任何Java类都占用大约500字节的内存空间,创建一个实例消耗大约15字节的内存
- 对常量使用static修饰符,使用静态方法,静态方法比普通方法提高15%左右的访问速度
- 减少不必要的成员变量,如果一个变量可以定义为局部变量就不会定义为成员变量
- 减少不必要的对象(StringBuilder和String)
- 能用基本类型如Int,Long,就不用Integer,Long对象,基本类型变量占用的内存资源比相应对象占用的少得多
3.节制的使用Service,最好使用IntentService(任务结束会自动停止,避免Service内存泄漏),只有当任务正在执行的时候才让Service运转起来
内存泄露
Android内存泄露抓取工具leakcanary (leakcanry在application中配置,它通过弱引用方式侦查Activity或对象的生命周期,在onDestroy()后检查弱引用是否被清除,如果没有被清除执行一次gc,如果仍没被清除说明内存泄漏了。若发现内存泄露找出计算弱引用所引用对象的最短强引用路径,即是泄露的最短路径,最后通过notification展示。)
-非静态内部类的实例执行耗时操作容易造成内存泄漏(1.将内部类变成静态内部类 2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用。3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务。)
非静态内部类的静态实例容易造成内存泄漏,静态实例生命周期非常长,它又持有外部类的引用(非静态内部类变成静态内部类;强引用变弱引用;不用的时候设为null)
-activity使用静态成员,单例模式,静态变量持有activity的context的引用(不使用的时候显示设置为null或者持有applicationcontext的引用)
-bitmap的回收
-使用handler时的内存问题(onDestroy时清除消息,mHandler.removeCallbacksAndMessages(null);handler设为静态内部类;设置为对activity的弱引用)
注: handler.removeCallbacks(runnable); 失效的问题;在Activity由前台转后台过程中,线程是一直在运行的,但是当Activity从后台转入前台时会重新定义Runnable runnable;也就是说此时从message queue移除的runnable与原先加入message queue中的runnable并非是同一个对象。如果把runnable定义为静态的则removeCallbacks不会失效,对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,
-注册某个对象后未反注册,添加监听器未移除,集合中对象没清理,资源对象没关闭(IO流关闭)
-构造Adapter时,没有使用缓存的 convertView
-兜底回收 在Activity onDestory时候从view的rootview开始,递归释放所有子view涉及的图片,背景,DrawingCache,监听器等等资源,让Activity成为一个不占资源的空壳,泄露了也不会导致图片资源被持有。
注:程序crash的时候可以使用CrashHandler(实现了UncaughtExceptionHandler接口)来捕获代码中未被catch的异常