Android加载大图片,解决OOM问题
拉大锯
发表于
2019-12-02 06:52
4689
加载大图片
OOM
采样率
大图
android开发
Android加载大图片,解决OOM问题
同学们还没有加载过大图片,都是使用框架加载的,框架内部已经处理过了。所以没有经过过因为加载图片而导致的OOM问题。你姑且认为这是一个存在的问题,我表演给大家看看吧。
加载大图片
public void loadBigImage(View view) {
ImageView imageView = this.findViewById(R.id.image_container);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.test_pic);
imageView.setImageBitmap(bitmap);
}
我这张图片有多大呢?
6.06M
分辨率是5400x4000
看看我们直接载入到控件里会怎么样?
崩溃了,报错信息如下:
2019-12-01 22:37:23.387 6292-6292/com.sunofbeaches.javanetdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.sunofbeaches.javanetdemo, PID: 6292
java.lang.RuntimeException: Canvas: trying to draw too large(117608400bytes) bitmap.
at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:260)
at android.graphics.Canvas.drawBitmap(Canvas.java:1415)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:545)
at android.widget.ImageView.onDraw(ImageView.java:1286)
at android.view.View.draw(View.java:17071)
at android.view.View.updateDisplayListIfDirty(View.java:16053)
at android.view.View.draw(View.java:16837)
at android.view.ViewGroup.drawChild(ViewGroup.java:3764)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3550)
at android.view.View.updateDisplayListIfDirty(View.java:16048)
at android.view.View.draw(View.java:16837)
at android.view.ViewGroup.drawChild(ViewGroup.java:3764)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3550)
at android.view.View.updateDisplayListIfDirty(View.java:16048)
at android.view.View.draw(View.java:16837)
at android.view.ViewGroup.drawChild(ViewGroup.java:3764)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3550)
at android.view.View.updateDisplayListIfDirty(View.java:16048)
at android.view.View.draw(View.java:16837)
at android.view.ViewGroup.drawChild(ViewGroup.java:3764)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3550)
at android.view.View.updateDisplayListIfDirty(View.java:16048)
at android.view.View.draw(View.java:16837)
at android.view.ViewGroup.drawChild(ViewGroup.java:3764)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3550)
at android.view.View.updateDisplayListIfDirty(View.java:16048)
at android.view.View.draw(View.java:16837)
at android.view.ViewGroup.drawChild(ViewGroup.java:3764)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3550)
at android.view.View.draw(View.java:17074)
at com.android.internal.policy.DecorView.draw(DecorView.java:751)
at android.view.View.updateDisplayListIfDirty(View.java:16053)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:656)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:662)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:770)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2796)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2604)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2211)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1246)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6301)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:871)
at android.view.Choreographer.doCallbacks(Choreographer.java:683)
at android.view.Choreographer.doFrame(Choreographer.java:619)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:857)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
--------- beginning of system
too large(117608400bytes) bitmap
这个bitmap太大了
那么如何解决这个问题呢?
这里有篇文章,大家可以稍微去看一下!
避免OOM
前面这篇文章有这么些请:
理论:第一次读取图片,不写入内存,直接获取到图片的宽高,再通过用户设置的宽高,和这個拉伸形式來计算期望的宽高,结合这四個参数來找出最适合的采样率,或者直接根据屏幕大小,控件大小來计算最佳采样率。
public void loadBigImage(View view) {
ImageView imageView = this.findViewById(R.id.image_container);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 10;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.test_pic,options);
imageView.setImageBitmap(bitmap);
}
我改成这样子,就可以加载出来啦!
options.inSampleSize = 10;
这个是什么意思呢?
看看文档说明吧:
/**
* If set to a value > 1, requests the decoder to subsample the original
* image, returning a smaller image to save memory. The sample size is
* the number of pixels in either dimension that correspond to a single
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
* decoder uses a final value based on powers of 2, any other value will
* be rounded down to the nearest power of 2.
*/
public int inSampleSize;
简单来说就是采样率,当这个值为1的时候,跟原图一样。如果为4,那么采样率为原图的4分之1,也就是大小会是原来的1/4。
但是我们不能把这个10写死呀。如果写成100,那不就成了1/100了,占的内存不更小吗?
大家看看100的效果如何
糊了吧!
那咋整呢?接下来我们就看看动态计算采样率吧。
动态计算采样率
你去看看前面的源码分析,就会发现框架的做法也是动态计算的,根据控件的大小,来动态计算。那么我们稍微修改一下代码如下:
public void loadBigImage(View view) {
ImageView imageView = this.findViewById(R.id.image_container);
BitmapFactory.Options options = new BitmapFactory.Options();
//If set to true, the decoder will return null (no bitmap)
//设置为true以后呢,不是真的载入到内存中,只是获取到图片的相关信息
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(),R.mipmap.test_pic,options);
int width = options.outWidth;
int height = options.outHeight;
int measuredWidth = imageView.getMeasuredWidth();
int measuredHeight = imageView.getMeasuredHeight();
Log.d(TAG,"width -- > " + width + " measure width -- > " + measuredWidth);
Log.d(TAG,"height -- > " + height + " measure height -- > " + measuredHeight);
int sampleSize;
if(width < measuredWidth || height < measuredHeight) {
sampleSize = 1;
} else {
int scaleX = width / measuredWidth;
int scaleY = height / measuredHeight;
sampleSize = scaleX > scaleY ? scaleX : scaleY;
}
Log.d(TAG,"sampleSize -- > " + sampleSize);
options.inSampleSize = sampleSize;
//If set to true, the decoder will return null (no bitmap)
//要变成false了,因为真的要载入到内存中了
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.test_pic,options);
imageView.setImageBitmap(bitmap);
}
感觉截图会好看点,哈哈!!!
okay啦!
跑一次看看!!
log输出
D/BigImageLoadActivity: width -- > 5400 measure width -- > 1440
D/BigImageLoadActivity: height -- > 4000 measure height -- > 1944
D/BigImageLoadActivity: sampleSize -- > 3
效果: