深入理解Android Bitmap

Bitmap (android.graphics.Bitmap)

Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。

基于android-6.0.1_r80源代码分析

通过下面三个章节基本可以扫清 Bitmap 盲区。文章没有覆盖到的一方面是Bitmap用法,这部分建议阅读 Glide 库源代码。一些 Color 的概念,例如 premultiplied / Dither ,需要具备一定CG物理基础,不管怎样先读下去。

Bitmap对象创建

Bitmap java 层构造函数是通过 native 层 jni call 过来的,逻辑在 Bitmap_creator 方法中。

// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
    jint offset, jint stride, jint width, jint height,
    jint configHandle, jboolean isMutable) {
 SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
 if (NULL != jColors) {
 size_t n = env->GetArrayLength(jColors);
 if (n < SkAbs32(stride) * (size_t)height) {
  doThrowAIOOBE(env);
  return NULL;
 }
 }
 // ARGB_4444 is a deprecated format, convert automatically to 8888
 if (colorType == kARGB_4444_SkColorType) {
 colorType = kN32_SkColorType;
 }
 SkBitmap bitmap;
 bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
 Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
 if (!nativeBitmap) {
 return NULL;
 }
 if (jColors != NULL) {
 GraphicsJNI::SetPixels(env, jColors, offset, stride,
  0, 0, width, height, bitmap);
 }
 return GraphicsJNI::createBitmap(env, nativeBitmap,
  getPremulBitmapCreateFlags(isMutable));
}

legacyBitmapConfigToColorType 将 Bitmap.Config.ARGB_8888 转成skia域的颜色类型 kBGRA_8888_SkColorType ,颜色类型定义在 SkImageInfo.h 中, kARGB_4444_SkColorType 会强转成 kN32_SkColorType ,它就是 kBGRA_8888_SkColorType ,不必纠结。

// /home/yuxiang/repo_aosp/android-6.0.1_r79/external/skia/include/core/SkImageInfo.h
enum SkColorType {
 kUnknown_SkColorType,
 kAlpha_8_SkColorType,
 kRGB_565_SkColorType,
 kARGB_4444_SkColorType,
 kRGBA_8888_SkColorType,
 kBGRA_8888_SkColorType,
 kIndex_8_SkColorType,
 kGray_8_SkColorType,
 kLastEnum_SkColorType = kGray_8_SkColorType,
#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
 kN32_SkColorType = kBGRA_8888_SkColorType,
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
 kN32_SkColorType = kRGBA_8888_SkColorType,
#else
 #error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order"
#endif
};

接着,根据宽、高、颜色类型等创建 SkBitmap ,注意 kPremul_SkAlphaType 描述是 alpha 采用 premultiplied 处理的方式, CG 处理 alpha 存在 premultiplied和unpremultiplied两 两种方式。

public:
 SkImageInfo()
 : fWidth(0)
 , fHeight(0)
 , fColorType(kUnknown_SkColorType)
 , fAlphaType(kUnknown_SkAlphaType)
 , fProfileType(kLinear_SkColorProfileType)
 {}
 static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at,
    SkColorProfileType pt = kLinear_SkColorProfileType) {
 return SkImageInfo(width, height, ct, at, pt);
 }

Make 创建 SkImageInfo 对象, fWidth 的赋值是一个关键点,后面Java层通过 getAllocationByteCount 获取 Bitmap 内存占用中会用到它计算一行像素占用空间。 allocateJavaPixelRef 是通过 JNI 调用 VMRuntime 实例的 newNonMovableArray 方法分配内存。

int register_android_graphics_Graphics(JNIEnv* env)
{
 jmethodID m;
 jclass c;
 ...
 gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
 gVMRuntime_newNonMovableArray = env->GetMethodID(gVMRuntime_class, "newNonMovableArray",
       "(Ljava/lang/Class;I)Ljava/lang/Object;");
 ...
}
env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size) 拿到虚拟机分配Heap对象, env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj) 拿到分配对象的地址,调用 native 层构造函数 new android::Bitmap(env, arrayObj, (void*) addr, info, rowBytes, ctable)
// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
  const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
 : mPixelStorageType(PixelStorageType::Java) {
 env->GetJavaVM(&mPixelStorage.java.jvm);
 mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);
 mPixelStorage.java.jstrongRef = nullptr;
 mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
 // Note: this will trigger a call to onStrongRefDestroyed(), but
 // we want the pixel ref to have a ref count of 0 at this point
 mPixelRef->unref();
}
void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
 assertValid();
 android::AutoMutex _lock(mLock);
 // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes()
 // would require locking the pixels first.
 outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
 outBitmap->setPixelRef(refPixelRefLocked())->unref();
 outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
}
void Bitmap::pinPixelsLocked() {
 switch (mPixelStorageType) {
 case PixelStorageType::Invalid:
 LOG_ALWAYS_FATAL("Cannot pin invalid pixels!");
 break;
 case PixelStorageType::External:
 case PixelStorageType::Ashmem:
 // Nothing to do
 break;
 case PixelStorageType::Java: {
 JNIEnv* env = jniEnv();
 if (!mPixelStorage.java.jstrongRef) {
  mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>(
   env->NewGlobalRef(mPixelStorage.java.jweakRef));
  if (!mPixelStorage.java.jstrongRef) {
  LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels");
  }
 }
 break;
 }
 }
}
// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.h
std::unique_ptr<WrappedPixelRef> mPixelRef;
PixelStorageType mPixelStorageType;
union {
 struct {
 void* address;
 void* context;
 FreeFunc freeFunc;
 } external;
 struct {
 void* address;
 int fd;
 size_t size;
 } ashmem;
 struct {
 JavaVM* jvm;
 jweak jweakRef;
 jbyteArray jstrongRef;
 } java;
} mPixelStorage;

native 层的 Bitmap 构造函数, mPixelStorage 保存前面创建 Heap 对象的弱引用, mPixelRef 指向 WrappedPixelRef 。 outBitmap 拿到 mPixelRef 强引用对象,这里理解为拿到 SkBitmap 对象。 Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef 完成 Bitmap Heap 分配,创建 native 层 Bitmap , SkBitmap 对象,最后自然是创建 Java 层 Bitmap 对象,把该包的包上。 native 层是通过 JNI 方法,在 Java 层创建一个数组对象的,这个数组是对应在 Java 层的 Bitmap 对象的 buffer 数组,所以 pixels 还是保存在 Java 堆。而在 native 层这里它是通过weak指针来引用的,在需要的时候会转换为strong指针,用完之后又去掉strong指针,这样这个数组对象还是能够被Java堆自动回收。里面jstrongRef一开始是赋值为null的,但是在bitmap的getSkBitmap方法会使用weakRef给他赋值。

// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
 int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
 int density) {
 bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
 bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
 // The caller needs to have already set the alpha type properly, so the
 // native SkBitmap stays in sync with the Java Bitmap.
 assert_premultiplied(bitmap->info(), isPremultiplied);

 jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
  reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
  bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
  ninePatchChunk, ninePatchInsets);
 hasException(env); // For the side effect of logging.
 return obj;
}

重点看下这里 env->NewObject(gBitmap_class, gBitmap_constructorMethodID,... ,参数中有一处 bitmap->javaByteArray() ,指向的是Heap对象。所以,实际的像素内存只有一份,被不同对象持有, Java 层的 Bitmap , native 层的 Btimap 。

这里顺带说一下 JNI 生命周期。 JNI Local Reference 的生命期是在 native method 的执行期(从 Java 程序切换到 native code 环境时开始创建,或者在 native method 执行时调用 JNI function 创建),在 native method 执行完毕切换回 Java 程序时,所有 JNI Local Reference 被删除,生命期结束(调用 JNI function 可以提前结束其生命期)。

JNI 编程中明显的内存泄漏

Native Code 本身的内存泄漏

JNI 编程首先是一门具体的编程语言,或者 C 语言,或者 C++,或者汇编,或者其它 native 的编程语言。每门编程语言环境都实现了自身的内存管理机制。因此,JNI 程序开发者要遵循 native 语言本身的内存管理机制,避免造成内存泄漏。以 C 语言为例,当用 malloc() 在进程堆中动态分配内存时,JNI 程序在使用完后,应当调用 free() 将内存释放。总之,所有在 native 语言编程中应当注意的内存泄漏规则,在 JNI 编程中依然适应。

Native 语言本身引入的内存泄漏会造成 native memory 的内存,严重情况下会造成 native memory 的 out of memory。

Global Reference 引入的内存泄漏

JNI 编程还要同时遵循 JNI 的规范标准,JVM 附加了 JNI 编程特有的内存管理机制。

JNI 中的 Local Reference 只在 native method 执行时存在,当 native method 执行完后自动失效。这种自动失效,使得对 Local Reference 的使用相对简单,native method 执行完后,它们所引用的 Java 对象的 reference count 会相应减 1。不会造成 Java Heap 中 Java 对象的内存泄漏。

而 Global Reference 对 Java 对象的引用一直有效,因此它们引用的 Java 对象会一直存在 Java Heap 中。程序员在使用 Global Reference 时,需要仔细维护对 Global Reference 的使用。如果一定要使用 Global Reference,务必确保在不用的时候删除。就像在 C 语言中,调用 malloc() 动态分配一块内存之后,调用 free() 释放一样。否则,Global Reference 引用的 Java 对象将永远停留在 Java Heap 中,造成 Java Heap 的内存泄漏。

更多JNI泄露,参考阅读 JNI 编程中潜在的内存泄漏——对 LocalReference 的深入理解

Bitmap对象释放

基于前文 JNI Local Reference和Global Reference 泄露,可以看到 nativeRecycle 实际调用native层Bitmap的 freePixels 方法, DeleteWeakGlobalRef 释放Bitmap native层Gloabl引用。逻辑还是很简单的。

// /home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
void Bitmap::freePixels() {
 AutoMutex _lock(mLock);
 if (mPinnedRefCount == 0) {
 doFreePixels();
 mPixelStorageType = PixelStorageType::Invalid;
 }
}
void Bitmap::doFreePixels() {
 switch (mPixelStorageType) {
 case PixelStorageType::Invalid:
 // already free'd, nothing to do
 break;
 case PixelStorageType::External:
 mPixelStorage.external.freeFunc(mPixelStorage.external.address,
  mPixelStorage.external.context);
 break;
 case PixelStorageType::Ashmem:
 munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
 close(mPixelStorage.ashmem.fd);
 break;
 case PixelStorageType::Java:
 JNIEnv* env = jniEnv();
 LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,
  "Deleting a bitmap wrapper while there are outstanding strong "
  "references! mPinnedRefCount = %d", mPinnedRefCount);
 env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
 break;
 }
 if (android::uirenderer::Caches::hasInstance()) {
 android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
  mPixelRef->getStableID());
 }
}

需要注意两点讯息,一是Java层主动call recycle()方法或者Bitmap析构函数都会调用freePixels,移除Global对象引用,这个对象是Heap上存一堆像素的空间。GC时释放掉。二是,JNI不再持有Global Reference,并native函数执行后释放掉,但Java层的Bitmap对象还在,只是它的 mBuffer 和 mNativePtr 是无效地址,没有像素Heap的Bitmap也就几乎不消耗内存了。至于Java层Bitmap对象什么时候释放,生命周期结束自然free掉了。

// /home/yuxiang/repo_aosp/android-6.0.1_r79/art/runtime/jni_internal.cc
static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
 JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm;
 Thread* self = down_cast<JNIEnvExt*>(env)->self;
 vm->DeleteWeakGlobalRef(self, obj);
}
// /home/yuxiang/repo_aosp/android-6.0.1_r79/art/runtime/java_vm_ext.cc
void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) {
 if (obj == nullptr) {
 return;
 }
 MutexLock mu(self, weak_globals_lock_);
 if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) {
 LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "
   << "failed to find entry";
 }
}

通过BitmapFactory创建Bitmap

Bitmap工厂类提供了多种decodeXXX方法创建Bitmap对象,主要是兼容不同的数据源,包括byte数组、文件、FD、Resource对象、InputStream,最终去到native层方法, 如下:

// BitmapFactory.java
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);

来看看 nativeDecodeStream 方法,该方法中先是创建了 bufferedStream 对象,接着 doDecode 返回Bitmap对象。 SkStreamRewindable 定义在 skia 库中继承 SkStream ,它声明了两个方法 rewind 和 duplicate ,写过网络库的同学一看命名便知是byte操作,前者功能是将文件内部的指针重新指向一个流的开头,后者是创建共享此缓冲区内容的新的字节缓冲区。

// BitmapFactory.cpp
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
 jobject padding, jobject options) {
 jobject bitmap = NULL;
 SkAutoTDelete<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
 if (stream.get()) {
 SkAutoTDelete<SkStreamRewindable> bufferedStream(
  SkFrontBufferedStream::Create(stream.detach(), BYTES_TO_BUFFER));
 SkASSERT(bufferedStream.get() != NULL);
 bitmap = doDecode(env, bufferedStream, padding, options);
 }
 return bitmap;
}

doDecode 先是通过JNI拿到 Java 层 Options 对象里面的属性, outWidth、outHeight、inDensity、inTargetDensity 这些。后两者用来计算Bitmap缩放比例,计算公式 scale = (float) targetDensity / density 。

// BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
 int sampleSize = 1;
 SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;
 SkColorType prefColorType = kN32_SkColorType;
 bool doDither = true;
 bool isMutable = false;
 float scale = 1.0f;
 bool preferQualityOverSpeed = false;
 bool requireUnpremultiplied = false;
 jobject javaBitmap = NULL;
 if (options != NULL) {
 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
 if (optionsJustBounds(env, options)) {
  decodeMode = SkImageDecoder::kDecodeBounds_Mode;
 }
 // initialize these, in case we fail later on
 env->SetIntField(options, gOptions_widthFieldID, -1);
 env->SetIntField(options, gOptions_heightFieldID, -1);
 env->SetObjectField(options, gOptions_mimeFieldID, 0);
 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
 prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
 isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
 doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
 preferQualityOverSpeed = env->GetBooleanField(options,
  gOptions_preferQualityOverSpeedFieldID);
 requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
 javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
 if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
  const int density = env->GetIntField(options, gOptions_densityFieldID);
  const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
  const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
  if (density != 0 && targetDensity != 0 && density != screenDensity) {
  scale = (float) targetDensity / density;
  }
 }
 ...
}

这些参数是提供给图片解码器 SkImageDecoder 。图片资源无非是压缩格式, SkImageDecoder 工厂类根据输入流同步拿到具体压缩格式并创建相应解码器。 GetFormatName 返回支持的图片格式。

SkImageDecoder 实例将 Options 参数设置下去。如此解压出来的是根据实际尺寸裁剪后的图片。

// BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
 ...
 SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
 if (decoder == NULL) {
 return nullObjectReturn("SkImageDecoder::Factory returned null");
 }
 decoder->setSampleSize(sampleSize);
 decoder->setDitherImage(doDither);
 decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
 decoder->setRequireUnpremultipliedColors(requireUnpremultiplied)
 ...
}
// SkImageDecoder_FactoryDefault.cpp
SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
 return image_decoder_from_stream(stream);
}
// SkImageDecoder_FactoryRegistrar.cpp
SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) {
 SkImageDecoder* codec = NULL;
 const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head();
 while (curr) {
 codec = curr->factory()(stream);
 // we rewind here, because we promise later when we call "decode", that
 // the stream will be at its beginning.
 bool rewindSuceeded = stream->rewind();
 // our image decoder's require that rewind is supported so we fail early
 // if we are given a stream that does not support rewinding.
 if (!rewindSuceeded) {
  SkDEBUGF(("Unable to rewind the image stream."));
  SkDELETE(codec);
  return NULL;
 }
 if (codec) {
  return codec;
 }
 curr = curr->next();
 }
 return NULL;
}
// SkImageDecoder.cpp
const char* SkImageDecoder::GetFormatName(Format format) {
 switch (format) {
 case kUnknown_Format:
  return "Unknown Format";
 case kBMP_Format:
  return "BMP";
 case kGIF_Format:
  return "GIF";
 case kICO_Format:
  return "ICO";
 case kPKM_Format:
  return "PKM";
 case kKTX_Format:
  return "KTX";
 case kASTC_Format:
  return "ASTC";
 case kJPEG_Format:
  return "JPEG";
 case kPNG_Format:
  return "PNG";
 case kWBMP_Format:
  return "WBMP";
 case kWEBP_Format:
  return "WEBP";
 default:
  SkDEBUGFAIL("Invalid format type!");
 }
 return "Unknown Format";
}

解码仅仅完成数据的读取,图片是经过渲染才能呈现在最终屏幕上,这个步骤在 canvas.drawBitmap 方法中完成。

// BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
 ...
 SkBitmap outputBitmap;
 if (willScale) {
 // This is weird so let me explain: we could use the scale parameter
 // directly, but for historical reasons this is how the corresponding
 // Dalvik code has always behaved. We simply recreate the behavior here.
 // The result is slightly different from simply using scale because of
 // the 0.5f rounding bias applied when computing the target image size
 const float sx = scaledWidth / float(decodingBitmap.width());
 const float sy = scaledHeight / float(decodingBitmap.height());
 // TODO: avoid copying when scaled size equals decodingBitmap size
 SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
 // FIXME: If the alphaType is kUnpremul and the image has alpha, the
 // colors may not be correct, since Skia does not yet support drawing
 // to/from unpremultiplied bitmaps.
 outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
  colorType, decodingBitmap.alphaType()));
 if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
  return nullObjectReturn("allocation failed for scaled bitmap");
 }
 // If outputBitmap's pixels are newly allocated by Java, there is no need
 // to erase to 0, since the pixels were initialized to 0.
 if (outputAllocator != &javaAllocator) {
  outputBitmap.eraseColor(0);
 }
 SkPaint paint;
 paint.setFilterQuality(kLow_SkFilterQuality);
 SkCanvas canvas(outputBitmap);
 canvas.scale(sx, sy);
 canvas.drawARGB(0x00, 0x00, 0x00, 0x00);
 canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
 }
 ...
 // now create the java bitmap
 return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
  bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}

最终渲染后的图片数据包在了 Bitmap 对象中,这部分逻辑重回第一章节 Bitmap对象创建 。

(0)

相关推荐

  • Android性能优化之Bitmap图片优化详解

    前言 在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化. 为什么Bitmap会导致OOM? 1.每个机型在编译ROM时都设置了一个应用堆内存VM值上限dalvik.vm.heapgrowthlimit,用来限定每个应用可用的最大内存,超出这个最大值将会报OOM.这个阀值,一般根据手机屏幕dpi大小递增,dpi越小的手

  • Android实现图片压缩(bitmap的六种压缩方式)

    Android中图片是以bitmap形式存在的,那么bitmap所占内存,直接影响到了应用所占内存大小,首先要知道bitmap所占内存大小计算方式: 图片长度 x 图片宽度 x 一个像素点占用的字节数 以下是图片的压缩格式: 其中,A代表透明度:R代表红色:G代表绿色:B代表蓝色. ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度 ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个

  • Android canvas drawBitmap方法详解及实例

     Android canvas drawBitmap方法详解及实例 之前自己在自定义view,用到canvas.drawBitmap(Bitmap, SrcRect, DesRect, Paint)的时候,对其中的第2和3个参数的含义含糊不清.看源码函数也没理解,然后看了一些其他的博客加上自己的理解,整理如下.首先,我们看一张图片,今天就要绘制这张图片. 然后将图片用红色的线条分成4个部分,如下: 我们自定义一个View,代码如下: public class PoterDuffLoadingVi

  • Android利用BitMap获得图片像素数据的方法

    本文实例讲述了Android利用BitMap获得图片像素数据的方法.分享给大家供大家参考,具体如下: 网上看到的参考是: int[] pixels = new int[bit.getWidth()*bit.getHeight()];//保存所有的像素的数组,图片宽×高 bit.getPixels(pixels,0,bit.getWidth(),0,0,bit.getWidth(),bit.getHeight()); for(int i = 0; i < pixels.length; i++){

  • Android App开发中将View或Drawable转为Bitmap的方法

    View转换为Bitmap Android中经常会遇到把View转换为Bitmap的情形,比如,对整个屏幕视图进行截屏并生成图片:Coverflow中需要把一页一页的view转换为Bitmap.以便实现复杂的图形效果(阴影.倒影效果等):再比如一些动态的实时View为便于观察和记录数据.需要临时生成静态的Bitmap. 实现方法: 1)下面是笔者经常用的一个转换方法 public static Bitmap convertViewToBitmap(View view, int bitmapWid

  • Android Bitmap详解及Bitmap的内存优化

    Android Bitmap详解及Bitmap的内存优化 一.Bitmap: Bitmap是Android系统中的图像处理的最重要类之一.用它可以获取图像文件信息,进行图像剪切.旋转.缩放等操作,并可以指定格式保存图像文件. 常用方法: public void recycle() // 回收位图占用的内存空间,把位图标记为Dead public final boolean isRecycled() //判断位图内存是否已释放 public final int getWidth() //获取位图的

  • 深入理解Android Bitmap

    Bitmap (android.graphics.Bitmap) Bitmap是Android系统中的图像处理的最重要类之一.用它可以获取图像文件信息,进行图像剪切.旋转.缩放等操作,并可以指定格式保存图像文件. 基于android-6.0.1_r80源代码分析 通过下面三个章节基本可以扫清 Bitmap 盲区.文章没有覆盖到的一方面是Bitmap用法,这部分建议阅读 Glide 库源代码.一些 Color 的概念,例如 premultiplied / Dither ,需要具备一定CG物理基础,

  • Android Bitmap和Drawable的对比

    Android Bitmap和Drawable的对比 Bitmap - 称作位图,一般位图的文件格式后缀为bmp,当然编码器也有很多如RGB565.RGB888.作为一种逐像素的显示对象执行效率高,但是缺点也很明显存储效率低.我们理解为一种存储对象比较好. Drawable - 作为Android平下通用的图形对象,它可以装载常用格式的图像,比如GIF.PNG.JPG,当然也支持BMP,当然还提供一些高级的可视化对象,比如渐变.图形等. A bitmap is a Drawable. A Dra

  • 详解Android Bitmap的常用压缩方式

    一.前言 已经好久没有更新博客,大概有半年了,主要是博主这段时间忙于找工作,Android岗位的工作真的是越来越难找,好不容易在广州找到一家,主要做海外产品,公司研发实力也不错,所以就敲定了三方协议.现在已经在公司实习了一个月多,目前主要是负责公司某个产品的内存优化,刚好就总结了一下Android Bitmap常用的优化方式. Android中的图片是以Bitmap方式存在的,绘制的时候也是Bitmap,直接影响到app运行时的内存,在Android,Bitmap所占用的内存计算公式是:图片长度

  • 深入理解Android中的建造者模式

    前言 在Android开发过程中,我发现很多安卓源代码里应用了设计模式,比较常用的有适配器模式(各种adapter),建造者模式(Alert Dialog的构建)等等.虽然我们对大多数设计模式都有所了解,但是在应用设计模式的这个方面,感觉很多人在这方面有所不足.所以这篇文章我们一起深入的理解Android中的建造者模式. 建造者模式(Builder Pattern)也叫生成器模式,其定义如下: separate the construction of a complex object from

  • Android Bitmap压缩方式分析

    Android Bitmap压缩方式分析 在网上调查了图片压缩的方法并实装后,大致上可以认为有两类压缩:质量压缩(不改变图片的尺寸)和尺寸压缩(相当于是像素上的压缩):质量压缩一般可用于上传大图前的处理,这样就可以节省一定的流量,毕竟现在的手机拍照都能达到3M左右了,尺寸压缩一般可用于生成缩略图. 在Android开发中我们都会遇到在一个100*100的ImageView上显示一张过大的图片,如果直接把这张图片显示上去对我们应用没有一点好处反而存在OOM的危险,所以我们有必要采用一种有效压缩方式

  • 学习理解Android菜单Menu操作

    今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至今为止看到的最好的一本android书,中文版出到<精通Android 2>. 理解Android的菜单 菜单是许多应用程序不可或缺的一部分,Android中更是如此,所有搭载Android系统的手机甚至都要有一个"Menu"键,由此可见菜单在Android程序中的特殊性.Andro

  • 关于Android bitmap你不知道的一些事

    本文为大家分享了Android bitmap使用细节,供大家参考,具体内容如下 1.计算机表示图形的几种方式 1)BMP :几乎不进行压缩 占用空间比较大 2)JPG : 在BMP的基础上对相邻的像素进行压缩,占用空间比BMP小 3)PNG : 在JPG的基础上进一步压缩 占用空间比较小 这是对三种格式进行一个简单的介绍,知道是怎么回事就行,在Android中一般都用png格式的图片,因为他占用空间小 2.图形的大小 图形的大小 = 图片的总像素*每个像素的大小 图片总像素 = 图片的长*图片的

  • 深入理解Android M 锁屏密码存储方式

    Android M 之前锁屏密码的存储 在 Android M 之前,锁屏密码的存储格式很简单,其使用了 64 位随机数作为 salt 值,此 salt 值被存储在 SQLite 数据库 /data/system/locksettings.db 中.密码在存储的时候,会将输入的密码加上此随机数组成新的字符串.然后对新的字符串分别进行 SHA-1 和 MD5 加密,将加密后的密文通过 MD5 + SHA-1 的方式进行字符串拼接,组成新的密文存储在 /data/system/password.ke

  • Android Bitmap的截取及状态栏的隐藏和显示功能

    初识Bitmap Bitmap是一个final类,因此不能被继承.Bitmap只有一个构造方法,且该构造方法是没有任何访问权限修饰符修饰,也就是说该构造方法是friendly,但是谷歌称Bitmap的构造方法是private(私有的),感觉有点不严谨.不管怎样,一般情况下,我们不能通过构造方法直接新建一个Bitmap对象. Bitmap是Android系统中的图像处理中最重要类之一.Bitmap可以获取图像文件信息,对图像进行剪切.旋转.缩放,压缩等操作,并可以以指定格式保存图像文件. 正文如下

随机推荐