Recently I encountered a pit left by their predecessors, the following error.
java.lang.RuntimeException: Canvas: trying to draw too large(268435456bytes) bitmap.
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:229)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.RecordingCanvas.drawBitmap(RecordingCanvas.java:98)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:545)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.widget.ImageView.onDraw(ImageView.java:1360)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.View.draw(View.java:20211)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:19086)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.View.draw(View.java:19939)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4333)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4112)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.View.draw(View.java:20214)
09-20 17:06:39.298 26126 26126 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:19086)
problem causes:
ListView brush pictures are taken each time the local picture and do a series of operations such as compression, but compression is not good cause after compression to complete the picture is still very large, above Crash occurred.
Now I posted about the issue of code:
The code has the following problems:
1. Picture no cache each load to be calculated compression
2. Direct open thread, the thread pool is not used
3. The compression method to write dead, shrink-ratio calculation in question
Not the size of the picture compression 4. Fangsuo
private void loadBitmapsTask(String path,ImageView imageView){
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = getImage(path);
runOnUiThread(new Runnable() {
@Override
public void run() {
if(imageView != null && bitmap !=null){
ImageView imageViewImage = imageView;
if(imageViewImage != null){
imageViewImage.setImageBitmap(bitmap);
}
}
}
});
}
}).start();
}
private static Bitmap getImage(String srcPath){
BitmapFactory.Options newopts = new BitmapFactory.Options();
//返回bitmap尺寸
newopts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcPath,newopts);
//获取bitmap宽高
int w = newopts.outWidth;
int h = newopts.outHeight;
float resolutionH = 80f;
float resolutionW = 80f;
int be = 1;
if(w > h && w>resolutionW){
be = (int)(w/resolutionW);
}else if(w < h && h > resolutionH){
be = (int)(h/resolutionH);
}
if(be <= 0 ){
be = 1;
}
newopts.inJustDecodeBounds = false;
newopts.inSampleSize = be;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newopts);
return bitmap;
}
The face of these problems, combined with online data tool I had a class:
For the above question we need the following:
1. Cache
2. Local network image loading
3. Pictures extreme compression
4. multi-task management thread pool
The thread switch asynchronous refresh View
First, the memory cache
public class ImageCache extends LruCache<String, Object> {
private Map<String, SoftReference<Object>> cacheMap;
public ImageCache(Map<String, SoftReference<Object>> cacheMap) {
super((int) (Runtime.getRuntime().maxMemory() / 8));
this.cacheMap = cacheMap;
}
@Override
protected int sizeOf(String key, Object value) {
if(value instanceof Bitmap){
return ((Bitmap)value).getRowBytes() * ((Bitmap)value).getHeight();
}else{
return super.sizeOf( key, value);
}
}
@Override
protected void entryRemoved(boolean evicted, String key, Object oldValue, Object newValue) {
if (oldValue != null) {
SoftReference<Object> softReference = new SoftReference<>(oldValue);
cacheMap.put(key, softReference);
}
}
public Map<String, SoftReference<Object>> getCacheMap() {
return cacheMap;
}
}
Second, the picture compression tool
public class ImageCompressUtils {
//图片最后压缩大小访问小于50kb
private static final int IMAGE_COMPRESS_MAXS_IZE = 1024 * 50;
/**
*
* @param srcPath
* @param width
* @param height
* @return
*/
public static Bitmap getImageFromLocal(String srcPath, int width, int height) {
BitmapFactory.Options newopts = new BitmapFactory.Options();
//返回bitmap尺寸
newopts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcPath, newopts);
newopts.inJustDecodeBounds = false;
newopts.inSampleSize = calculateInSampleSize(newopts, width, height);
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newopts);
return compressImage(bitmap);
}
/**
* 计算图片放缩到目标大小
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
public static 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;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* 质量压缩方法
* @param image
* @return
*/
public static Bitmap compressImage(Bitmap image) {
if (image == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int options = 80;
// 循环判断如果压缩后图片是否大于100kb,大于继续压缩
while (baos.toByteArray().length > IMAGE_COMPRESS_MAXS_IZE && options >= 0) {
// 重置baos即清空baos
baos.reset();
// 这里压缩options%,把压缩后的数据存放到baos中
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 每次都减少5
options -= 5;
}
// 把压缩后的数据baos存放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
// 把ByteArrayInputStream数据生成图片
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
return bitmap;
}
}
Third, the image loading tools
public class ImageUtils {
//网络下载缓存路径
private static final String CACHE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Picture";
//图片缓存
private static ImageCache mCaches;
private static ImageUtils mInstance;
private static Handler mHandler;
//声明线程池,全局只有一个线程池,所有访问网络图片,只有这个池子去访问。
private static ExecutorService mPool;
private Map<ImageView, String> mTags = new LinkedHashMap<>();
private ImageUtils() {
Map<String, SoftReference<Object>> cacheMap = new HashMap<>();
mCaches = new ImageCache(cacheMap);
}
public static ImageUtils getInstance() {
if (mInstance == null) {
synchronized (ImageUtils.class) {
if (mInstance == null) {
mInstance = new ImageUtils();
if (mHandler == null) {
//实例化Handler
mHandler = new Handler(Looper.getMainLooper());
}
if (mPool == null) {
mPool = Executors.newCachedThreadPool();
}
}
}
}
return mInstance;
}
/**
* 给imageView加载url对应的图片
* @param iv
* @param url
*/
public void display(ImageView iv, String url) {
//1.从内存中获取
Bitmap bitmap = getBitmapFromMemory(url);
if (bitmap != null) {
//内存中有,显示图片
iv.setImageBitmap(bitmap);
return;
}
//2.内存中没有,从本地获取
bitmap = loadFromLocal(url);
if (bitmap != null) {
//本地有,显示
iv.setImageBitmap(bitmap);
return;
}
//从网络中获取
loadFromNet(iv, url);
}
/**
* 加载图片
* @param iv
* @param url
*/
public void loadFromLocal(ImageView iv, String url) {
mTags.put(iv, url);//url是ImageView最新的地址
//1.从内存中获取
Bitmap bitmap = getBitmapFromMemory(url);
if (bitmap != null) {
//内存中有,显示图片
iv.setImageBitmap(bitmap);
return;
}
//用线程池去管理
mPool.execute(new LocalImageTask(iv, url));
}
private void loadFromNet(ImageView iv, String url) {
mTags.put(iv, url);//url是ImageView最新的地址
//用线程池去管理
mPool.execute(new NetImageTask(iv, url));
}
/**
* 本地加载图片任务,包括加载防缩,压缩等过程
*/
private class LocalImageTask implements Runnable {
private ImageView iv;
private String url;
public LocalImageTask(ImageView iv, String url) {
this.iv = iv;
this.url = url;
}
@Override
public void run() {
//2.内存中没有,从本地获取
Bitmap bitmap = loadFromLocal(url);
//存储到内存
mCaches.put(url, bitmap);
//在显示UI之前,拿到最新的url地址
String recentlyUrl = mTags.get(iv);
//把这个url和最新的url地址做一个比对,如果相同,就显示ui
if (url.equals(recentlyUrl)) {
//显示到UI,当前是子线程,需要使用Handler。其中post方法是执行在主线程的
mHandler.post(new Runnable() {
@Override
public void run() {
display(iv, url);
}
});
}
}
}
/**
* 网络加载任务
*/
private class NetImageTask implements Runnable {
private ImageView iv;
private String url;
public NetImageTask(ImageView iv, String url) {
this.iv = iv;
this.url = url;
}
@Override
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
//连接服务器超时时间
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//连接服务器(可写可不写)
conn.connect();
//获取流
InputStream is = conn.getInputStream();
//将流变成bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is);
//存储到本地
save2Local(bitmap, url);
//存储到内存
mCaches.put(url, bitmap);
//在显示UI之前,拿到最新的url地址
String recentlyUrl = mTags.get(iv);
//把这个url和最新的url地址做一个比对,如果相同,就显示ui
if (url.equals(recentlyUrl)) {
//显示到UI,当前是子线程,需要使用Handler。其中post方法是执行在主线程的
mHandler.post(new Runnable() {
@Override
public void run() {
display(iv, url);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 存储到本地
*
* @param bitmap
* @param url
*/
public void save2Local(Bitmap bitmap, String url) throws FileNotFoundException {
File file = getCacheFile(url);
FileOutputStream fos = new FileOutputStream(file);
/**
* 用来压缩图片大小
* Bitmap.CompressFormat format 图像的压缩格式;
* int quality 图像压缩率,0-100。 0 压缩100%,100意味着不压缩;
* OutputStream stream 写入压缩数据的输出流;
* 返回值:如果成功地把压缩数据写入输出流,则返回true。
*/
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
}
/**
* 从本地获取图片
*
* @param url
* @return bitmap
*/
private Bitmap loadFromLocal(String url) {
//本地需要存储路径
File file = new File(url);
if (file.exists()) {
//本地有
//把文件解析成Bitmap
Bitmap bitmap = ImageCompressUtils.getImageFromLocal(url, 80, 80);
//存储到内存
setBitmapToMemory(url, bitmap);
mCaches.put(url, bitmap);
return bitmap;
}
return null;
}
/**
* 获取缓存文件路径(缓存目录)
*
* @return 缓存的文件
*/
private File getCacheFile(String url) {
String name = url;
File dir = new File(CACHE_PATH);
if (!dir.exists()) {
//文件不存在,就创建
dir.mkdirs();
}
//此处的url可能会很长,一般会使用md5加密
return new File(dir, name);
}
/**
* 从内存中读图片
* @param url
*/
public Bitmap getBitmapFromMemory(String url) {
Bitmap bitmap = (Bitmap) mCaches.get(url);
// 如果图片不存在强引用中,则去软引用(SoftReference)中查找
if (bitmap == null) {
Map<String, SoftReference<Object>> cacheMap = mCaches.getCacheMap();
SoftReference<Object> softReference = cacheMap.get(url);
if (softReference != null) {
bitmap = (Bitmap) softReference.get();
//重新放入强引用缓存中
mCaches.put(url, bitmap);
}
}
return bitmap;
}
/**
* 往内存中写图片
* @param url
* @param bitmap
*/
public void setBitmapToMemory(String url, Bitmap bitmap) {
mCaches.put(url, bitmap);
}
public void clear() {
mCaches.getCacheMap().clear();
}
}