android的内存管理机制
内存分配机制
- 1.每个应用程序都运行在单独的进程中
- 2.应用程序的进程从Zygote进程fork出来
- 3.每个应用进程都对应自己唯一的虚拟机实例
- 4.每个虚拟机都有堆内存阈值即最大值限制
- 5.即使进程退出了,数据仍然在内存中`
进程优先级
1.前台进程,可见进程,服务进程,后台进程,空进程
2.android会将进程评定为它可能会达到的最高级别
回收效益:android总是会倾向于杀死一个能回收更多内存的进程
java的引用方式
1.强引用
- 只要某个对象有强引用与之关联,JVM必定不会回收这个对象
- 即使内存不足,JVM宁愿抛出OOM错误哦也不会回收这种对象
2.软引用
- 用来描述一些有用但不是必需的对象
- 对于软引用关联着的对象,只有在内存不足的时候JVM才会回收这个对象
- 很适合用来实现缓存:比如网页缓存,图片缓存等;
例:SoftReference aSoftRef=new SoftReference (aRef)
3.弱引用
- 弱应用是用来描述非必需的对象
- 当jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联着的对象
例:WeaiReference <data> ref=new WeaiReference <data>(new data);
4.虚引用
- 不影响对象的生命周期
- 如果一个对象与虚引用关联,则跟没有引用与之关联一样
- 在任何时候都可能被垃圾回收器回收
例:ReferenceQueue <String> queue=new ReferenceQueue <String>();
PhantomReference<String>pr=new PhantomReference<String>(new String("peakmain"),queue);
内存泄露
指由于错误或疏忽造成程序未能释放已经不再使用的内存
内存抖动
指内存频繁地分配和回收
内存抖动的后果
- 频繁的gc会导致卡顿
- 严重的时候还会导致oom
解决方案
- 尽量避免在循环体内创建对象,应该把对象创建移到循环体外
- 避免在View的onDraw方法里频繁的创建对象
- 对于能够复用的对象,可以使用对象池将他们缓存起来
内存溢出
应用申请超过最大内存空间
产生原因
- 应用存在内存泄露,长时间积累导致OOM
- 应用某些逻辑操作疯狂的消耗大量内存
解决方案
- 规避内存泄露
- 图片进行压缩显示或局部显示
图片优化
首先我们举个例子看下图片占用的内存的大小
private final String path = "/sdcard/Download/image1.jpg";
private void showSize() throws IOException {
File file = new File(path);
if (!file.exists()) {
return;
}
FileInputStream fis = new FileInputStream(file);
int size = fis.available();
Log.e("TAG", "size=" + size);
Bitmap bitmap= BitmapFactory.decodeStream(fis);
Log.e("TAG", "bitmapsize=" + bitmap.getByteCount());
}
我的图片的结果是
图片占用内存影响因素
图片的长度,图片的宽度,单位像素所占用的字节数
图片占用的内存=图片长度图片宽度单位像素所占用的字节数
关于图片压缩,Google提供中文文档:http://hukai.me/android-training-course-in-chinese/graphics/displaying-bitmaps/load-bitmap.html
public Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight) {
//加载图片信息,不加载图片
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//BitmapFactory.decodeResource(res, resId, options);
BitmapFactory.decodeFile(path,options);
// 计算压缩比
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Log.e("TAG","inSampleSize:"+ options.inSampleSize );
// 加载图片到内存
options.inJustDecodeBounds = false;
//单位像素所占用的字节数
options.inPreferredConfig= Bitmap.Config.RGB_565;
return BitmapFactory.decodeFile(path,options);
}
public int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
//如果只是宽或者高比较长,可以只对一个比例
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
压缩前后结果对比.png
图片压缩的主要思路:
- 1.获取图片的像素宽高
- 2.计算需要的压缩比例
- 3.将图片用计算出的比例压缩并再加载到内存中使用
图片的局部显示
private Bitmap decodeRegionBitmap(int offX) throws IOException {
//获取图片的宽高信息
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(path,options);
final int height = options.outHeight;
final int width = options.outWidth;
//设置图片局部显示的区域
BitmapRegionDecoder bitmapRegionDecoder=BitmapRegionDecoder.newInstance(path,false);
BitmapFactory.Options newOptions=new BitmapFactory.Options();
newOptions.inPreferredConfig= Bitmap.Config.RGB_565;
//加载局部图片
Bitmap bitmap=bitmapRegionDecoder
.decodeRegion(new Rect(0+offX,0,width/2+offX,height),newOptions);
return bitmap;
}
效果如下
效果图.gif
图片的内存缓存策略
三级缓存
- 1.建立一个图片缓存池,用于存放图片对应的bitmap对象
- 2.在显示的时候,先从缓存池中获取,如果取到了了直接显示,如果获取不到再从sd卡或者网络中获取
缓存池特性:总有大小限制,能够智能化的回收移除池内一部分图片
缓存池的二级缓存
- 两个缓存池,一个强引用,一个软应用
- 强应用保存常用的图片,软应用保存其他图片
二级缓存实现方式 - LinekdHashMap+软引用
//二级缓存
HashMap<String, SoftReference<Bitmap>> mSecondCache = new HashMap<>();
final int MAX_NUM = 10;
//一级缓存:强应用缓存
//最近访问量
HashMap<String, Bitmap> mFirstCache = new LinkedHashMap<String, Bitmap>(MAX_NUM, 0.75f, true) {
//移除最老的因素
@Override
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
// return super.removeEldestEntry(eldest);
if (size() > MAX_NUM) {
mSecondCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
}
return false;
}
};
- Lrucache+软引用
软引用在缓存的处理上是没有效率的,因为效率比较低,所以一般情况下是可以取掉
数据结构优化
String StringBuilder StringBuffer三者的区别
private void stringJoin() {
long time1 = System.currentTimeMillis();
String result = "";
for (int i = 0; i <4000; i++) {
result = result + data[i];
}
long time2 = System.currentTimeMillis();
long time = time2 - time1;
Log.e(TAG, "stringjoinTime:" + time);
}
private void stringBufferJoin() {
long time1 = System.currentTimeMillis();
StringBuffer result = new StringBuffer();
for (int i = 0; i < 4000; i++) {
result.append(data[i]);
}
long time2 = System.currentTimeMillis();
long time = time2 - time1;
Log.e(TAG, "stringBufferJoinTime:" + time);
}
private void stringBuilderJoin() {
long time1 = System.currentTimeMillis();
StringBuilder result = new StringBuilder();
for (int i = 0; i < 4000; i++) {
result.append(data[i]);
}
long time2 = System.currentTimeMillis();
long time = time2 - time1;
Log.e(TAG, "stringBuilderJoinTime:" + time);
}
结果.png
- String字符串常量
- StringBuffer字符串变量
- StringBuilder字符串变量
- StringBuffer是线程安全的
- StringBuilder,String是线程非安全
- 执行效率上:在大部分情况:StringBuilder>StringBuffer>String
HashMap
- 内部结构是个哈希表的拉链结构
- HashMap默认实现的扩容是以2倍增加
- 利用Hash算法实现增删改查,效率高
ArrayMap
- ArrayMap内部利用两个数组
- mHashes数组用来保存每一个key和value
- mArray数组大小为mHashes的2倍,依次保存key和value
- 利用二分查找实现查询
SparseArray
- 内部是两个数组来进行数据存储的(一个存放key,一个存放value)
- key为int值,不是hash值。内存空间占用少
- 利用二分查找实现查询
代码优化
常见内存泄露场景
- 单例导致的内存泄露——尽量使用Application的上下文
- 静态变量导致的内存泄露
- 非静态内部类导致的内存泄露
- 未取消注册或回调导致的内存泄露
- Timer和TimerTask导致的内存泄漏
- 集合中的对象未清理导致的内存泄露
- 资源未关闭或释放导致的内存泄露
- 属性动画没有即使停止导致的内存泄漏
- webview导致的内存泄漏
其他优化
- 1.避免在android中使用java的枚举类型
- 2.尽量减少锁个数减小锁范围
- 3.尽量使用线程池替代多线程操作
- 4.尽可能不要使用依赖注入
网友评论