阅读Android源码BitmapFactory

2018-07-26  本文已影响0人  北边一小民

引言        

        一首《凉凉送给大家》,最近的工作犹如这个歌曲;还是开始今天的主题吧,BitmapFactory 想必不陌生了,我在项目中也用到很多,但是从来没有真的仔细的去看看这个东西,哈哈,所以今天想带大家简单的看看,顺便呢,找出一些解决因为加载bitmap导致的OOM(Out Of Memory)的问题;

OOM是什么?

        在使用C或C++语言时,我们可操作的内存空间就是整个设备的物理内存,程序员需要自己声明内存空间,也需要自己在恰当的时机释放掉内存,一旦出错就会造成内存泄漏。而Java语言为了解决这个问题,在操作系统之上创造了一个Java虚拟机(JVM),让Java语言编译后的字节码运行在此虚拟机之上。启动一个Java应用,会首先启动JVM,JVM 会向操作系统申请所需内存,然后把内存分成为栈内存和堆内存。堆内存用以存放对象实例,并可被Java回收机制回收,一旦剩余堆内存空间不够申请新对象时就会产生OutOfMemoryError异常。

Android内存管理

        Android的Dalvik虚拟机(DVM)是参考JVM做出来的,所以大同小异。最主要的两个区别是:一.DVM 基于寄存器,而JVM基于栈来进行局部变量的操作,当然在性能上DVM会更快;二.在DVM上运行的是被进一步处理的JAVA字节码,后缀为.dex,.dex 是把Java应用中所有的.class文件合并而成,缩减了包的体积。Android中的 DVM 如 JVM 一样对每个应用可使用的最大内存空间做了限制,每台设备出厂之前厂家就对单个 DVM 实例可使用的最大内存进行了限定。

        由于上面的限制和内存的管理,当我们使用Android的Bitmap的时候会出现加载图片过大导致的程序的内存泄漏;推荐大家检测内存泄漏的软件。用过这个,感觉不错;

LeakCanary 内存泄漏检测工具

造成的原因

1.大量的图片、音频、视频处理,当在内存比较低的系统上也容易造成内存溢出。

2.Bitmap对象的不正确处理(内存溢出)

3.非静态匿名内部类Handler由于持有外部类Activity的引用所造成的内存泄漏。

4.线程由于匿名内部类Runnable持有activity的引用,从而关闭activity,线程未完成造成内存泄漏。

5.BraodcastReceiver、File、Cursor等资源的使用未及时关闭。

6.static关键字修饰的变量由于生命周期过长,容易造成内存泄漏。

7.单列模式造成的内存泄漏,如Context的使用,单列中传入的是Activity的Context,在关闭Activity时,Activity的内存无法被回收,因为单列持有Activity的引用。

下面我们来看看Bitmap类的构造方法:

/**

* Private constructor that must received an already allocated native bitmap

* int (pointer).

*/

// called from JNI

Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,

       boolean isMutable, boolean requestPremultiplied,

       byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {

if (nativeBitmap ==0) {

throw new RuntimeException("internal error: native bitmap is 0");

   }

mWidth = width;

   mHeight = height;

   mIsMutable = isMutable;

   mRequestPremultiplied = requestPremultiplied;

   mBuffer = buffer;

   mNinePatchChunk = ninePatchChunk;

   mNinePatchInsets = ninePatchInsets;

   if (density >=0) {

mDensity = density;

   }

mNativePtr = nativeBitmap;

   mFinalizer =new BitmapFinalizer(nativeBitmap);

   int nativeAllocationByteCount = (buffer ==null ? getByteCount() :0);

   mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);

}

在bitmap类中,并没有静态的获得对象的方法,所以我们只能把希望寄托在工厂类了.

下面看看BitmapFactory

BitmapFactory中,有提供一些静态的方法去获得一个bitmap对象,主要方法如下: 

全部方法如上图。现在我们看看这几个主要用到的方法;

a) 从文件加载

public static Bitmap decodeFile(String pathName, Options opts)

public static Bitmap decodeFile(String pathName)

b)从资源中加载

public static Bitmap decodeResourceStream(Resources res,TypedValue value,InputStream is, Rect pad, Options opts)

public static Bitmap decodeResource(Resources res, int id,Options opts)

c)从二进制数组中加载文件

public static Bitmap decodeByteArray(byte[] data, int offset,int length, Options opts)

public static Bitmap decodeByteArray(byte[] data, int offset, int length)

d)从流中加载

public static Bitmap decodeStream(InputStream is, Rect outPadding,Options opts)

private static Bitmap decodeStreamInternal(InputStream is,Rect outPadding, Options opts)

public static Bitmap decodeStream(InputStream is)

e)从文件描述符加载

public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)

public static Bitmap decodeFileDescriptor(FileDescriptor fd)

        上述五对方法,都会有相对应一组中参数少的一个调用参数多的一个方法,最后转到调用几个native的方法:

private static native Bitmap nativeDecodeStream(InputStream is,

byte[] storage,Rect padding, Options opts);

private static native Bitmap nativeDecodeFileDescriptor(

FileDescriptor fd,Rect padding, Options opts);

private static native Bitmap nativeDecodeAsset(long nativeAsset,

Rect padding, Options opts);

private static native Bitmap nativeDecodeByteArray(byte[] data,

int offset,int length, Options opts);

private static native boolean nativeIsSeekable(FileDescriptor fd);

下面以分析;

首先是一个静态代码块。先看看(看注解哦)

public static class Options {

/**

* Create a default Options object, which if left unchanged will give

* the same result from the decoder as if null were passed.

*/

   public Options() {

inDither =false;

       inScaled =true;

       inPremultiplied =true;

   }

.....code....

/**

* 如果设置为true,解码器将返回null(没有位图),

* 但是输出…仍然会设置字段,允许调用者查询位图,

* 而不必为其像素分配内存。

*/

   public boolean inJustDecodeBounds;

/**

* 如果设置为值> 1,请解码器对原始图像进行子样本化,

* 返回较小的图像以保存内存。

* 样本大小是每个维对应解码位图中单个像素的像素个数。

* 例如,inSampleSize == 4返回一个图像,

* 它的宽度/高度是原来的1/4,像素的数量是1/16。

* 任何值<= 1都被视为1。注:解码器使用基于2次幂的最终值,

* 任何其他值都将四舍五入到2次幂。

*

*/

   public int inSampleSize;

/*

* 如果这是非空的,

* 解码器将尝试解码到这个内部配置。

* 如果它是空的,或者请求不能被满足,

* 解码器将尝试根据系统的屏幕深度选择最佳匹配配置,

* 以及原始图像的特征,例如如果它有每个像素的alpha值(需要配置它也可以)。

*

*/

   public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;.....code....

/*

* 位图的结果宽度。

*/

   public int outWidth;

/*

* 位图的结果高度。

*/

   public int outHeight;

///...code.....

}

下面我们以decodeFile为例 :

/**

* pathName:地址名称

* Options :可以为null, 控制下采样的选项,以及是否应该完全解码图像,或者返回大小。

*/

public static

Bitmap decodeFile(String pathName, Options opts) {

Bitmap bm =null;

   InputStream stream =null;

   try {

stream =new FileInputStream(pathName);

       bm = decodeStream(stream, null, opts);

   }catch (Exception e) {

/*  do nothing.

If the exception happened on open, bm will be null.

*/

       Log.e("BitmapFactory", "Unable to decode stream: " + e);

   }finally {

if (stream !=null) {

try {

stream.close();

           }catch (IOException e) {

// do nothing here

           }

}

}

return bm;

}

如上方法中调用了decodeStream,那我们就转到这个地方看看;

/*

* is: 将原始数据解码为位图的输入流。

*

* outPadding:如果不为空,则为位图返回填充矩形(如果存在),

* 否则将填充设置为[-1,-1,-1,-1]。

* 如果没有返回位图为null,那么填充是不变的。

* opts:可以为null, 控制下采样的选项,以及是否应该完全解码图像,或者返回大小。

*/

public staticBitmap 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;

   }

Bitmap bm =null;    Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");

   try {

if (isinstanceof AssetManager.AssetInputStream) {

final long asset = ((AssetManager.AssetInputStream) is)

.getNativeAsset();

           bm = nativeDecodeAsset(asset, outPadding, opts);

       }else {

bm = decodeStreamInternal(is, outPadding, opts);

       }

if (bm ==null && opts !=null && opts.inBitmap !=null) {

throw new IllegalArgumentException("Problem decoding

into existing bitmap"

);

       }

setDensityFromOptions(bm, opts);

   }finally {

Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);

   }

return bm;

}

在处理的时候涉及到nativeDecodeAsset和decodeStreamInternal方法,我们看看这是啥?

如果is 是Asset中的,择使用第一个方法;通过C层直接处理;

如果is不属于Asset;择使用第二个方法返回bm (bitmap)

private static Bitmap decodeStreamInternal(InputStream is,

Rect outPadding, Options opts) {

// ASSERT(is != null);

   byte [] tempStorage =null;

   if (opts !=null) tempStorage = opts.inTempStorage;

   if (tempStorage ==null) tempStorage =new byte[DECODE_BUFFER_SIZE];

   return nativeDecodeStream(is, tempStorage, outPadding, opts);

}

最终调用了nativeDecodeStream方法;看来都是地层来实现的;看来是否要去看看C层的东西? 搞起吧,不搞起都不知道咋回事了,干~

在android-6.0.1_r72/frameworks/base/core/jni/android/graphics/

BitmapFactory.cpp 

文件中找到如上方法:

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,

jobject is, jbyteArray storage,

       jobject padding, jobject options) {

//创建一个bitmap对象

jobject bitmap = NULL;

//创建一个输入流适配器,(SkAutoTUnref)自解引用

SkAutoTDelete stream(CreateJavaInputStreamAdaptor(env,

is, storage));    if (stream.get()) {

SkAutoTDelete bufferedStream(

SkFrontBufferedStream::Create(stream.detach(),

BYTES_TO_BUFFER));

       SkASSERT(bufferedStream.get() != NULL);

//图片解码

       bitmap = doDecode(env, bufferedStream, padding, options);

   }

//返回图片对象,加载失败的时候返回空  

return bitmap;

}

总结:

相对比较干货比较少,其实知道了方法还得去运用,本次还没写运用,我后面补充吧。

喜欢的来关注公众号哦
上一篇下一篇

猜你喜欢

热点阅读