导读
读完本篇能学到以下知识
- 解决Activity的内存泄漏
- Bitmap加载优化
前言
内存优化是Android中优化的一个重点,内存优化不到位会引起频繁的GC,导致耗电严重.
要做内存优化首先要找到优化的对象.在Android开发中有两个内存大户,Activity和Bitmap.Activity主要是防止内存泄漏,Bitmap需要防止oom
Activity的内存泄漏
所谓内存泄漏就是这个对象至少有一条到达根节点的路径.
那有哪些是根节点呢?
Java虚拟机中的根节点如下:
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
- Thread
还有比较隐蔽的内部类,内部类会默认持有外部类的引用要注意下.
内存泄漏情景
- 静态引用持有
public class TestActivity extends Activity{
public static List list = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
list.add(this);
}
}
解决方法:及时remove会避免静态引用持有.
- Handler发送消息
public class TestActivity extends Activity{
Handler handler = new Handler(){
@Override
public void handleMessage(){
...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
...
handler.sendMessageDelay(msg, 100000);
}
}
因为handler现在是个匿名内部类,所以持有Activity的引用,一旦有未发送的Message,Activity就会内存泄漏.
解决方法
1. 在onDestroy()里让handler移除所有消息
Activity:
...
@Override
protected void onCreate(Bundle savedInstanceState) {
handler.removeMessage(...);
}
...
2. 定义一个继承Handler的static类,用WeakReference持有Activity(如果需要调用Activity的话)
class Activity{
MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
handler = new MyHandler(this);
}
private static class MyHandler extends Handler{
private WeakReference<Context> weakRef;
public Handler(Context context){
weakRef = new WeakReference<>(context);
}
@Override
public void handleMessage(){
...
}
}
}
- 执行Thread
class Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
...
new Thread(new Runnable(){
@Override
public void run(){
//耗时操作
}
}).start();
}
}
解决线程导致的内存泄漏跟Handler类似
1. 在onDestroy()里调用Thread.interrupt()结束线程
2. 把线程定义成静态内部类用WeakReference持有Activity
检测内存泄漏方法
- adb命令(最简单,我最喜欢用)
adb shell dumpsys meminfo [包名]
用这个命令开始的时候打印一次,然后打开关闭泄漏Activity一定次数,接着触发GC(Profile里Memory的垃圾桶图标),最后再打印一次这个命令,看看两次Activities的数量差是否和打开泄漏Activity次数一样.如果不一样说明部分(GC并不一定回收所有对象)Activity已经被回收了,没有内存泄露.反之则内存泄漏.
Views: 189 ViewRootImpl: 1
AppContexts: 11 Activities: 1
Assets: 2 AssetManagers: 2
Local Binders: 15 Proxy Binders: 25
Parcel memory: 4 Parcel count: 16
Death Recipients: 0 OpenSSL Sockets: 0
第一种方法前提是有怀疑泄漏的对象.如果没有怀疑对象,那可以用LeakCanary.LeakCanary接入到应用中会定期触发GC来判断是否内存泄露,一泄露就会给你提示.
MAT
用MAT检测内存泄露步骤跟1类似,就是去对比两次dump出来的内存信息,但MAT功能更强大.他可以找出泄露对象被谁持有.
泄漏代码:
Activity:
public static List list = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
list.add(this);
}
具体操作如下:
- dump出当前heap快照
-
用命令 hprof-conv 1.hprof 2.hprof转换hprof文件(AS生成的hprof需要转换才能被MAT识别)
用Leak Suspects Report打开2.hprof文件,点击Histogram,
Show objects by class
Merge Shortest Paths to GC Roots-->exclude all phantom/weak/soft etc.reference
现在就可以看到持有该Activity的是一个list
Bitmap优化
- 防止OOM
有时我们加载Bitmap,需要的只是一个很小的图,但从本地或网络加载的图片非常的大,稍不注意就会报oom.
解决方法如下:
Bitmap bm;
BitmapFactory.Options opt = new BitmapFactory.Options();
//设置只加载大小不加载像素数据
opt.inJustDecodeBounds = true;
bm = BitmapFactory.decodeFile(absolutePath, opt);
//计算出你需要的比例
opt.inSampleSize = bm.getHeight()/neededHeight;//伪代码
//inJustDecodeBounds设为true去加载像素数据
opt.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(absolutePath, opt);
就是先加载原始图的大小,接着计算出需要的比例传给inSampleSize,再加载一遍图片.
- 设备分级
对于一些低端机我们可以选择降低图片质量(比如RGB_565) - 缓存
图片缓存一般采取Lru的策略,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”.实现Lru策略可以用LinkedHashMap这个类构造的时候传入一个accessOrder为true
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
这时LinkedHashMap就会生成一个双端队列,get的时候把值插到最前面实现Lru的策略效果,实现原理可以参考这篇文章.