oom异常:大图片导致
图片的三级缓存:内存、磁盘、网络
下面通过一张图来了解下三级缓存原理:
代码:
public class Davince {
//使用固定线程池优化
private static ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
private static final String TAG = "Davince";
private static Davince sInstance = new Davince();
public static Context sContext;
public static Davince with(Context context) {
//线程池多少个线程:有当前的设备的cpu核数决定
sContext = context;
return sInstance;
}
public Requestor load(String url) {
return new Requestor(url);
}
//给 LruCache分配最大的使用内存
//使用lru缓存必须重写sizeOf方法
public static LruCache<String, Bitmap> mCaches = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 3)) {
@Override
protected int sizeOf(String key, Bitmap value) {
//缓存当前要存放的Bitmap的大小
return value.getByteCount();
}
};
//图片的内存缓存 以前的缓存方式,现在不用
//private Map<String, SoftReference<Bitmap>> mCaches = new HashMap<String, SoftReference<Bitmap>>();
public static final Handler sHandler = new Handler(Looper.getMainLooper());
//加载图片的类
public class Requestor {
private String mUrl;
public Requestor(String mUrl) {
this.mUrl = mUrl;
}
private int mErrorResId;
public Requestor error(int errorResId) {
this.mErrorResId = errorResId;
return this;
}
//加载图片:三级缓存
public void into(ImageView target) {
//从内存加载图片:设计内存缓存
//Bitmap bitmap = mCaches.get(mUrl);
//从Lru获取
Bitmap bitmap = mCaches.get(mUrl);
//从软引用获取 舍弃不用
// SoftReference<Bitmap> reference = mCaches.get(mUrl);
// if (reference!=null) {
//
// bitmap = reference.get();
// }
//内存有图片
if (bitmap != null) {
Log.i(TAG, "从内存获取");
target.setImageBitmap(bitmap);
return;
}
//内存没有图片,从disk获取
bitmap = getBitmapFromDisk(mUrl);
//磁盘有图片:
if (bitmap != null) {
Log.i(TAG, "从磁盘获取");
//存放到内存
//存放到Lru缓存
mCaches.put(mUrl, bitmap);
//mCaches.put(mUrl, bitmap);
// //存放到软引用 舍弃不用
// mCaches.put(mUrl, new SoftReference<Bitmap>(bitmap));
//显示图片
target.setImageBitmap(bitmap);
return;
}
//磁盘没有图片,从网络获取
//每次new Thread性能消耗会很大
//new Thread(new LoadBitmapTask(mUrl, target, mErrorResId)).start();
//从线程池中 开启线程
threadPool.execute(new LoadBitmapTask(mUrl, target, mErrorResId));
}
}
/*
加载的图片的任务
*/
public class LoadBitmapTask implements Runnable {
private String mUrl;
private ImageView mImageView;
private int mErrorResId;
private Bitmap bitmap;
public LoadBitmapTask(String mUrl, ImageView mImageView, int mErrorResId) {
this.mUrl = mUrl;
this.mImageView = mImageView;
this.mErrorResId = mErrorResId;
}
@Override
public void run() {
//从网络加载图片
try {
URLConnection urlConnection = new URL(mUrl).openConnection();
urlConnection.setReadTimeout(5000);
urlConnection.setConnectTimeout(5000);
urlConnection.connect();
//获取到的图片
InputStream inputStream = urlConnection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
//从网络加载成功
//存放到磁盘
saveBitmapToDisk(mUrl, bitmap);
Log.i(TAG, "从网络获取");
//保存的内存
//存放到Lru缓存
mCaches.put(mUrl, bitmap);
//mCaches.put(mUrl, bitmap);
// //存放到软引用 舍弃不用
// mCaches.put(mUrl, new SoftReference<Bitmap>(bitmap));
//显示图片 跳转主线程显示
sHandler.post(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
} catch (Exception e) {
e.printStackTrace();
//出错了,加载错误的图片
sHandler.post(new Runnable() {
@Override
public void run() {
mImageView.setImageResource(mErrorResId);
}
});
}
}
}
private void saveBitmapToDisk(String mUrl, Bitmap bitmap) {
try {
//文件路径加MD5加密文件名
File file = new File(getMyCacheDir(), MD5Utils.encode(mUrl));
FileOutputStream fileOutputStream = new FileOutputStream(file);
//包Bitmap保存到文件
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private File getMyCacheDir() {
//获取apk: data/data/包民/cache
File dir = new File(sContext.getCacheDir(), "davince-cache");
if (!dir.exists()) {
dir.mkdirs();
}
return dir;
}
//从磁盘获取图片
private Bitmap getBitmapFromDisk(String mUrl) {
File file = new File(getMyCacheDir(), MD5Utils.encode(mUrl));
return BitmapFactory.decodeFile(file.getAbsolutePath());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
曾经,在各大缓存图片的框架没流行的时候。有一种很常用的内存缓存技术:SoftReference 和 WeakReference(软引用和弱引用)。
但是走到了 Android 2.3(Level 9)时代,垃圾回收机制更倾向于回收 SoftReference 或 WeakReference 的对象。后来,
又来到了 Android3.0,图片缓存在内容中,因为不知道要在是什么时候释放内存,没有策略,没用一种可以预见的场合去将其释放。这就造成了内存溢出。
优化点:
1.用线程池去管理线程,不用每次都去new 一个线程
2.Bitmap的缓存优化:Bitmap占用内存很大,如果不能被即时的回收就会oom
强引用:
软引用:SoftReference, 用软引用包裹Bitmap对象,当内存不足的时候就算Bitmap有引用也会回收Bitmap
弱引用:WeakReference 只要垃圾回收器以启动就会回收弱引用的对象
虚引用:PhantomReference: 如果对象用虚引用包裹,对象只要一使用完就被回收
安卓:3.0之后Google不推荐使用了:art : android runtime
3、改成使用LRU缓存:
1.设定最大的使用内存:
2.重写sizeof方法:因为存放对象的时候LRU会计算内存的使用大小进行累加,当超出设定的最大使用内存的时候会把使用最少的对象移除
LRU 是 Least Recently Used 最近最少使用算法。
接下来我们看下LruCache源码:
首先我们开下他的英文注释,这是我们了解一个类的作用的最快最有效的方式,以下是LruCache类的官方注释:
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is
added to a full cache, the value at the end of that queue is evicted and may
become eligible for garbage collection.
这段话什么意思尼?其实就是设个类是个缓存作用的类,里面存储的数据是强引用,当每次访问里面的一个值的时候,那么就将该数值放到队列的头部,另一方面,当添加的数据充满整个队列的时候,就将队列的末尾数据移除,该移除的数据才有可能被系统进行垃圾回收。
其实简单一句话描述LruCache的功能就是采用队列方式存储,采用最近最少使用算法,维护一个缓存。
首先我们看下源码部分的构造函数:
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;//设置Lrucache的最大容量
//初始容量为零,0.75是加载因子,表示容量达到最大容量的75%的时候会把内存增加一半。最后这个参数至关重要。
//表示访问元素的排序方式,true表示按照访问顺序排序,false表示按照插入的顺序排序。(实现Lru算法的关键)
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//存放数据的集合
}
1
2
3
4
5
6
7
8
9
下面再看下 put方法
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
首先把size增加,然后判断是否以前已经有元素,如果有,就更新当前的size,并且调用entryRemoved方法,entryRemoved是一个空实现,
如果我们使用LruCache的时候需要掌握元素移除的信息,可以重写这个方法。最后就会调用trimToSize,来调整集合中的内容。
接下来我们再看下 trimToSize方法:
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();//获得最老数据
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
该方法的目的就是删除最老的条目,直到剩余条目的总数达到或低于要求的大小。具体就是:这个方法是一个无限循环,跳出循环的条件是,size < maxSize或者 map 为空。主要的功能是判断当前容量时候已经超出最大的容量,如果超出了maxSize的话,就会循环移除map中的第一个元素,直到达到跳出循环的条件。由上面的分析知道,map中的第一个元素就是最近最少使用的那个元素。
我们再看下 get方法:
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* 尝试创建一个值。 这可能需要很长时间,并且create()返回时map集合可能会有所不同。
* 如果在create()正在工作时将冲突值添加到地图,则我们将该值保留在地图中并释放创建的值。
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
这个方法就先通过key来获取value,如果能获取到就就直接返回,获取不到的话,就调用create()方法创建一个,事实上,
如果我们不重写这个create方法的话是return null的,所以整个流程就是获取得到就直接返回,获取不到就返回null。
remove方法:
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
就是使用remove方法移除一个元素。
---------------------
原文:https://blog.youkuaiyun.com/chaoshenzhaoxichao/article/details/79853595