Android学习笔记之BitmapFactory.Options实现图片资源的加载...
PS:小项目总算是做完了...历经20多天...素材,设计,以及实现全由自己完成...心力憔悴啊...该写写博客记录一下学习到的东西了...
学习内容:
1.使用BitmapFactory.Options实现图片资源的加载...防止OOM的产生...
我们在设计和制作app的时候,有时我们需要去动态的加载图片资源数据,图片的大小将决定读取图片资源所耗费物理内存...Android对内存的要求是非常苛刻的...如果图片的资源过大,那么就会出现OOM,也就是所说的内存溢出...这样程序就不得不被终止掉...因此我们需要避免这种情况的发生...这里我们需要使用到BitmapFactory.Options...
BitmapFactory.Options其实是一个辅助类,这个类提供了创建Bitmap类的接口,真正完成图像处理的类其实是Bitmap,但是由于这个类的构造函数是私有的,因此是无法在其他的类外进行实例化对象的,因此BitmapFactory.Options充当了这个辅助类,对外暴露接口,这样,我们就可以真正的调用Bitmap中的方法了...
BitmapFactory.Options通过decodeFile()的方法去获取图片资源的位置.那么获取到了资源位置后,我们就可以对图片进行解码操作,因为我们都知道,想要获取到一个图片,首先要从图片的码流开始,对码流进行解析才会得到相应的图片...那么这个解码的过程就是使用decodeStream()方法进行解码,解码也是可以限制的...BitmapFactory.Options这个类的Options其实就是对解码进行一个限制...
那么再说一下Options,Options有几个属性,第一个inJustDecodeBounds(这是一个布尔值,true和false两个属性),inJustDecodeBounds=true的时候,那么代表对现在的这张图片进行非完全解码,其实说白了就是不给这个图片资源分配任何的内存,只是获取这个图片的基本信息(比如说:长度和宽度),不分配内存的原因想必大家都知道了,就是防止图片过大的问题,如果图片过大,那么我们获取到图片长度和宽度后,需要对图片进行一个压缩的操作,那么这个压缩就涉及第二个参数,inSampleSize(int 类型,可以是任意值)...这个值的获取取决于我们压缩操作的方式,这个压缩方式是需要我们自己去实现的,最后通过压缩的方式去获取inSampleSize的值,通过这个值指定图片缩放的大小...最后将inJustDecodeBounds=false这样就可以得到缩放后的图片了...如果图片满足正常的范围之内,那么就直接进行显示就可以了...没必要再进行压缩操作...
还是来一段代码,来方便大家的理解...
private Bitmap decodeFile(File f){ try { //解码图像大小,对图片进行缩放...防止图片过大导致内存溢出... BitmapFactory.Options o = new BitmapFactory.Options();//实例化一个对象... o.inJustDecodeBounds = true;//这个就是Options的第一个属性,设置为true的时候,不会完全的对图片进行解码操作,不会为其分配内存,只是获取图片的基本信息... BitmapFactory.decodeStream(new FileInputStream(f),null,o); //以码流的形式进行解码.... /* * 下面也就是对图片进行的一个压缩的操作...如果图片过大,最后会根据指定的数值进行缩放... * 找到正确的刻度值,它应该是2的幂. * 这里我指定了图片的长度和宽度为70个像素... * * */ final int REQUIRED_SIZE=70; int width_tmp=o.outWidth, height_tmp=o.outHeight; int scale=1; while(true){ if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE) break; width_tmp/=2; height_tmp/=2; scale*=2; } BitmapFactory.Options o2 = new BitmapFactory.Options(); //这里定义了一个新的对象...获取的还是同一张图片... o2.inSampleSize=scale; //对这张图片设置一个缩放值...inJustDecodeBounds不需要进行设置... return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); //这样通过这个方法就可以产生一个小的图片资源了... } catch (Exception e) {} return null; }
这里只是粘贴了缩放过程的一个代码,通过这段代码,我们就可以实现大图片缩放为小型图片,防止OOM的发生...这只是一个小的函数,最后我会给出一个源码提供给大家去下载,方便大家去学习...这里还需要说明一些问题...就是这个inSampleSize值的设置.这个值如果设置为2,那么图片就缩放4倍...如果为3,那么缩放的程度就为9倍...以此类推...
还有这个decodeStream()方法只是一个把数据封装成流的形式对码流进行一个数据传递...真正完成图片绘制的是其内部过程...我们还是来看一下源代码,方便大家的理解...
/* *这是源码的实现过程,说实话,我也看不懂所有的东西...因为自己也是个小菜鸟... *不过我们可以分析一下... */ public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { // we don't throw in this case, thus allowing the caller to only check // the cache, and not force the image to be decoded. if (is == null) { return null; } // we need mark/reset to work properly if (!is.markSupported()) { is = new BufferedInputStream(is, DECODE_BUFFER_SIZE); } // so we can call reset() if a given codec gives up after reading up to // this many bytes. FIXME: need to find out from the codecs what this // value should be. is.mark(1024); Bitmap bm; boolean finish = true; /* *我们来看下面这个函数... */ if (is instanceof AssetManager.AssetInputStream) {//这块是一个判断的过程,判断的东西就是,我们这个图片资源到底来自于什么地方,如果满足这个if条件,那么这个图片资源属于drawable文件下的资源... final int asset = ((AssetManager.AssetInputStream) is).getAssetInt(); /* *这里就是设置缩放的一个过程,其中包含一些参数的设置... *比如说缩放的参数,以及目标面积的大小设定... */ if (opts == null || (opts.inScaled && opts.inBitmap == null)) { float scale = 1.0f; int targetDensity = 0; if (opts != null) { final int density = opts.inDensity; targetDensity = opts.inTargetDensity; if (density != 0 && targetDensity != 0) { scale = targetDensity / (float) density; } } /* *下面这个函数的源码,我也看不到了...应该就是一个解码的操作过程... */ bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); finish = false; } else { /* *这块就是直接解码操作,这个else满足的条件是图片资源满足指定的大小,因此我们不需要传递scale参数了...也就是代表不用进行缩放... */ bm = nativeDecodeAsset(asset, outPadding, opts); } } else { //这里满足的条件想必大家知道,就是图片资源可能来自于其他地方... // pass some temp storage down to the native code. 1024 is made up, // but should be large enough to avoid too many small calls back // into is.read(...) This number is not related to the value passed // to mark(...) above. /* *通过byte流的形式对图片进行获取,这个也很好理解,如果想获取图片资源,那么必须要经过流的形式,对资源数据进行封装...然后获取... *上面之所以不用流的形式,是因为drawable中的资源有特定的获取方式... */ byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[16 * 1024]; /* *还是同理,设置参数.... */ if (opts == null || (opts.inScaled && opts.inBitmap == null)) { float scale = 1.0f; int targetDensity = 0; if (opts != null) { final int density = opts.inDensity; targetDensity = opts.inTargetDensity; if (density != 0 && targetDensity != 0) { scale = targetDensity / (float) density; } } bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale); if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); finish = false; } else { bm = nativeDecodeStream(is, tempStorage, outPadding, opts); } } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } //这个方法,将会被最终调用..也就是下面的方法... return finish ? finishDecode(bm, outPadding, opts) : bm; } /* *这个方法是当完成解码操作后需要调用的方法... */ private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { if (bm == null || opts == null) { return bm; } final int density = opts.inDensity; if (density == 0) { return bm; } bm.setDensity(density); final int targetDensity = opts.inTargetDensity; if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { return bm; } byte[] np = bm.getNinePatchChunk(); int[] lb = bm.getLayoutBounds(); final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); /* *这个函数重要部分就是下面,我们可以看到,通过对参数的一些设定..最后会调用 Bitmap.createScaledBitmap方法... */ if (opts.inScaled || isNinePatch) { float scale = targetDensity / (float) density; if (scale != 1.0f) { final Bitmap oldBitmap = bm; bm = Bitmap.createScaledBitmap(oldBitmap, Math.max(1, (int) (bm.getWidth() * scale + 0.5f)), Math.max(1, (int) (bm.getHeight() * scale + 0.5f)), true); if (bm != oldBitmap) oldBitmap.recycle(); if (isNinePatch) { np = nativeScaleNinePatch(np, scale, outPadding); bm.setNinePatchChunk(np); } if (lb != null) { int[] newLb = new int[lb.length]; for (int i=0; i<lb.length; i++) { newLb[i] = (int)((lb[i]*scale)+.5f); } bm.setLayoutBounds(newLb); } } bm.setDensity(targetDensity); } return bm; }
我们可以不完全弄懂这个源码到底是怎么回事,但是我们通过源码可以知道其中到底是以怎样的过程进行实现的,这才是看源码的目的...如果还是有更深入研究的读者...那么您可以完全弄懂...这里我们可以看到最后的调用时Bitmap.createScaledBitmap方法,接着Bitmap.createScaledBitmap方法将调用Bitmap createBitmap...这个方法才是最终实现把图片显示在画布上的一个最终函数,这个函数通过使用Canvas最后在画布上将图片描画出来...这是这个函数的源码,我就不进行解释了,其实这个源码比上面那个更简单,容易理解,其实就是根据这些参数把图片展示出来,比如说获取图片的高度和宽度,缩放的大小,图片的编码格式,最后通过调用Paint画笔,结合Canvas,这里我们能够发现,最后画出来的画布是一个矩形,那么画出来的图片必然也是一个矩形..最后把画出来的图片进行返回..最后就显示在画布之上了...
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) { checkXYSign(x, y); checkWidthHeight(width, height); if (x + width > source.getWidth()) { throw new IllegalArgumentException("x + width must be <= bitmap.width()"); } if (y + height > source.getHeight()) { throw new IllegalArgumentException("y + height must be <= bitmap.height()"); } // check if we can just return our argument unchanged if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() && height == source.getHeight() && (m == null || m.isIdentity())) { return source; } int neww = width; int newh = height; Canvas canvas = new Canvas(); Bitmap bitmap; Paint paint; Rect srcR = new Rect(x, y, x + width, y + height); RectF dstR = new RectF(0, 0, width, height); Config newConfig = Config.ARGB_8888; final Config config = source.getConfig(); // GIF files generate null configs, assume ARGB_8888 if (config != null) { switch (config) { case RGB_565: newConfig = Config.RGB_565; break; case ALPHA_8: newConfig = Config.ALPHA_8; break; //noinspection deprecation case ARGB_4444: case ARGB_8888: default: newConfig = Config.ARGB_8888; break; } } if (m == null || m.isIdentity()) { bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha()); paint = null; // not needed } else { final boolean transformed = !m.rectStaysRect(); RectF deviceR = new RectF(); m.mapRect(deviceR, dstR); neww = Math.round(deviceR.width()); newh = Math.round(deviceR.height()); bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig, transformed || source.hasAlpha()); canvas.translate(-deviceR.left, -deviceR.top); canvas.concat(m); paint = new Paint(); paint.setFilterBitmap(filter); if (transformed) { paint.setAntiAlias(true); } } // The new bitmap was created from a known bitmap source so assume that // they use the same density bitmap.mDensity = source.mDensity; canvas.setBitmap(bitmap); canvas.drawBitmap(source, srcR, dstR, paint); canvas.setBitmap(null); return bitmap; }
这样就实现了通过对图片大小的缩放,避免OOM的发生...最后放上面一个源码....提供大家下载....只不过这个源码是通过从服务器上下载图片,通过解析XML文件,然后对数据进行获取..然后对图片进行缩放,最后以ListView的形式进行显示...图片的缩放只隶属于一个小的模块....
源码位置:https://files.cnblogs.com/files/RGogoing/daimarufeng.zip
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?