安卓内存管理机制
Android是一个基于Linux实现的操作系统,并对Linux的内存管理机制进行了优化。从操作系统的角度来说,内存就是一块数据存储区域,可以从两方面来理解内存管理机制-分配和回收!
分配机制 :Android为了整个系统的内存控制需要,Android系统为每一个应用程序都设置一个硬性的Dalvik Heap Size最大限制阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果你的应用占用内存空间已经接近这个阈值,此时再尝试分配内存的话,很容易引发OutOfMemoryError错误。
回收机制:Android系统会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发GC操作,从而腾出更多空闲的内存空间,但是Android也不是随便杀死一个进程。Android会有限清理那些已经不再使用的进程,以保证最小的副作用。
Android进程管理机制 Low Memory Killer
linux内核分配给每个进程一个值oom_adj:进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
内存泄漏:是程序不再使用的内存释放失败。一般是由于程序逻辑错误而导致的。
内存泄漏的危害:内存泄露不仅仅会造成应用内存占用过大,会导致应用卡顿,造成不好的用户体验;内存得不到释放,慢慢的会造成app内存溢出OOM,app崩溃。
导致内存泄漏的原因 (常见)
1、资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor
2、构造Adapter时,没有使用 convertView 重用(这个不用多说大家都知道的)
3、注册没取消造成的内存泄漏。
4、集合中对象没清理造成的内存泄漏。
5 、Bitmap对象不在使用时调用recycle()释放内存 (放在OOM细说)
6 、对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
示例:
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
分析:Context泄漏 :上述的代码片段,意味着这个静态的Drawable拥有一个TextView的引用, 而TextView又拥有Activity(Context类型)的引用,换句话说,Drawable拥有了更多的对象引用。即使Activity被 销毁,内存仍然不会被释放。 另外,对Context的引用超过它本身的生命周期,也会导致该Context无法回收,从而导致内存泄漏。所以尽量使用Application这种Context类型
public class TestActivity extends AppCompatActivity
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,60*1000);
}
}
分析:Handler允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有Handler,而又因为 Java内部类会持有外部类,使得 Activity 会被 Handler 持有是强引用,这样最终就导致 Activity 泄露。如何改正呢?
public class TestActivity extends AppCompatActivity {
private MyHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new MyHandler (this);
Message message = Message.obtain();
mHandler.sendMessageDelayed(message,10*60*1000);
}
private static class MyHandler extends Handler{
private WeakReference<Activity> mActivity;
public MyHandler(Activity activity){
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
总结:
强引用:如果一个对象具有强引用,垃圾回收器始终会回收这个对象;
软应用:如果一个对象只具有软引用,只有内存空间不足时,垃圾回收器才会回收它。
弱引用:垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现肉引用的对象,会立马回收。
我们可以把 Handler 对 Activity 弱引用,这样 GC 就能把 Activity 及时的回收,从而杜绝了内存泄露的问题。
静态变量对象 整个应用程序的公共变量,只要静态变量没有被销毁也没有置null,其对象一直被保持引用,也即引用计数不可能是0,因此不会被垃圾回收 ,尽可能少的用static对象;对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。
OOM内存溢出:其实就是申请的内存超过了Dalvik Heap的最大值
导致OOM的常见错误和解决方法:
1, 内部类请使用static
因为非静态内部类默认持有外部类的引用,比如在Activity里面直接放一个自定义的Adapter
2, 静态类(比如Application,单例类,其他static类)请不要持有Activity引用
因为静态类生命周期比Activity长。解决办法:在需要的地方用BaseApplication.getTopActivity。或者Activity作为弱引用传入
3, 注意Handler会默认持有当前Activity,用的时候最好不要直接new Handler().post(new Runnable...),除非你确定这个runnable会在Activity销毁前执行完
4, 内存泄漏也是造成OOM的主要原因。
5,为应用分配更多的内存 。(上面已介绍)
可以在manifest.xml加 android:largeHeap="true"。此时heapsize会增大2-3倍,缓解OOM的发生
6, 避免在Android里面使用Enum
ENUM中的每一个值都是一个Object,它的每个声明都会占用运行时的部分内存以便能够引用到
这个Object。因此ENUM的值会比对应的Integer和String所占用的内存多
7, StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
8,类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
9,图片的加载;
首先我们来认识下这个Bitmap
通过native源码方法,可得到:一张ARGB_8888的Bitmap占用内存计算公式:mBitmapWidth * mBitmapHeight * 4byte。可知,不是直接使用图片分辨率进行计算,而是根据Bitmap的宽高进行计算。
在不同的Android版本中,Bitmap或多或少都存在差异,尤其是在其内存分配上2.3之前的像素存储需要的内存是在native上分配的,并且生命周期不太可控,可能需要用户自己回收2.3-7.1之间,Bitmap的像素存储在Dalvik的Java堆上,(4.4系统Fresco采用匿名共享内存,在native层申请内存来代替部分java层的内存ashmem-匿名共享内存,它的管理由Linux内核驱动管理这样Bitmap对象的创建、释放将永远不会触发GC,使用了三级缓存:Bitmap缓存+未解码图片缓存+硬盘缓存)8.0之后的像素内存又重新回到native上去分配,不需要用户主动回收,而且资源的管理更加优秀,极大降低了OOM。
bitmap所占内存计算:
mBitmapWidth = mOriginalWidth *(opts.inTargetDensity / opts.inDensity)* 1 / inSampleSize ,
mBitmapHeight = mOriginalHeight *(opts.inTargetDensity / opts.inDensity)* 1 / inSampleSize
Int BitmapSize = mBitmapWidth * mBitmapHeight * 4byte
mOriginalWidth , mOriginalHeight 原始图片的宽高
inTargetDensity 当前设备显示的密度
inDensity 原始资源文件密度(这个密度是什么呢)
inSampleSize 压缩比率 默认是1;
将一张720x1080图片放在drawable-xhdpi目录下(inDensity = 320)(drawable-xxhdpi inDensity = 320*1.5) 在720x1080手机上加载(inTargetDensity = 320),图片不会被压缩;在480x800手机上加载(inTargetDensity = 240),图片会被压缩9/16;在1080x1920手机上加载(inTargetDensity = 480),图片会被放大2.25;
1、本地图片处理:分辨率不变,图片大小减小;分辨率改变,图片减小。(用PS都很容易的)
2、加载网络图片:当我们从网络上下载图片的时候无法知道网络图片的准确大小,所以为了节约内存,一般会在服务器上缓存一个缩略图,提升下载速度。除此之外,我们还可以在本地显示图片前将图片进行压缩,使其完全符合imageview的大小,这样就不会浪费内存了
1,BitmapFactory.Options
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
inJustDecoedBounds设置为true的话,解码bitmap时可以只返回其高、宽和Mime类型,而不必为其申请内存,从而节省了内存空间。根据返回的图片的长度和宽度,即opts.width和opts.height。
2,计算压缩比率
BitmapFactory.Options中有个inSampleSize属性,可以理解为压缩比率。设定好压缩比率后,调用上面的decodexxxx()就能得到一个缩略图了。比如inSampleSize=4,载入的缩略图是原图大小的1/4,所占内存也会减少。
3,采用低内存占用量的编码方式
bitmapConfig(Bitmap.Config.RGB_565) RGB_565,相比ARGB_8888将节省一半的内存
默认:Bitmap.Config ARGB_8888:那么一个像素点占32位 即4个字节
Bitmap.Config RGB_565 :R为5位,G为6位,B为5位共16位,一个像素点占16位 即2个字节
Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数。3个参数,任意减少一个的值,就达到了压缩的效果
网友评论