The formulation of the problem is often more essential than its solution, which may be merely a matter of mathematical or experimental skill. ― Albert Einstein
/** * Private constructor that must received an already allocated native bitmap * int (pointer). */ // called from JNI Bitmap(long nativeBitmap, int width, int height, int density, boolean isMutable, boolean requestPremultiplied, byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) { if (nativeBitmap == 0) { thrownewRuntimeException("internal error: native bitmap is 0"); }
publicstatic Bitmap decodeResource(Resources res, int id, Options opts) { validate(opts); Bitmapbm=null; InputStreamis=null; try { finalTypedValuevalue=newTypedValue(); is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */ } finally { try { if (is != null) is.close(); } catch (IOException e) { // Ignore } }
if (bm == null && opts != null && opts.inBitmap != null) { thrownewIllegalArgumentException("Problem decoding into existing bitmap"); }
return bm; }
publicstatic Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value, @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) { validate(opts); if (opts == null) { opts = newOptions(); } if (opts.inDensity == 0 && value != null) { finalintdensity= value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } elseif (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } // 获取当前手机设备的 dpi if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
// 省略部分跟踪代码 ......
privatestaticnative Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts);
static jobject doDecode(JNIEnv *env, SkStreamRewindable *stream, jobject padding, jobject options){ // This function takes ownership of the input stream. Since the SkAndroidCodec // will take ownership of the stream, we don't necessarily need to take ownership // here. This is a precaution - if we were to return before creating the codec, // we need to make sure that we delete the stream. std::unique_ptr<SkStreamRewindable> streamDeleter(stream);
// Set default values for the options parameters. int sampleSize = 1; // 是否只是获取图片的大小 bool onlyDecodeSize = false; SkColorType prefColorType = kN32_SkColorType; bool isMutable = false; float scale = 1.0f; bool requireUnpremultiplied = false; jobject javaBitmap = NULL;
// Update with options supplied by the client. // 解析 options 参数 if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); // Correct a non-positive sampleSize. sampleSize defaults to zero within the // options object, which is strange. if (sampleSize <= 0) { sampleSize = 1; }
if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) { onlyDecodeSize = true; }
// 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); // 解析 ColorType ,复用参数等等 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); // 计算缩放的比例 if (env->GetBooleanField(options, gOptions_scaledFieldID)) { // 获取图片当前 xhdpi 的 density constint density = env->GetIntField(options, gOptions_densityFieldID); // 获取当前设备的 dpi constint targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); constint screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { // scale = 当前设备的 dpi / xhdpi 的 density // scale = 420/320 = 1.3125 scale = (float) targetDensity / density; } } }
// Create the codec. NinePatchPeeker peeker; std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(), 280 & peeker)); if (!codec.get()) { returnnullObjectReturn("SkAndroidCodec::NewFromStream returned null"); }
// Do not allow ninepatch decodes to 565. In the past, decodes to 565 // would dither, and we do not want to pre-dither ninepatches, since we // know that they will be stretched. We no longer dither 565 decodes, // but we continue to prevent ninepatches from decoding to 565, in order // to maintain the old behavior. if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) { prefColorType = kN32_SkColorType; } // 获取当前图片的大小 // Determine the output size. SkISize size = codec->getSampledDimensions(sampleSize);
int scaledWidth = size.width(); int scaledHeight = size.height(); bool willScale = false; // 处理 simpleSize 压缩,我们这里没穿,上面默认是 1 // Apply a fine scaling step if necessary. if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) { willScale = true; scaledWidth = codec->getInfo().width() / sampleSize; scaledHeight = codec->getInfo().height() / sampleSize; }
// Set the options and return if the client only wants the size. if (options != NULL) { jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat()); if (env->ExceptionCheck()) { returnnullObjectReturn("OOM in encodedFormatToString()"); } // 设置 options 对象中的 outWidth 和 outHeight env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); env->SetObjectField(options, gOptions_mimeFieldID, mimeType); // 如果只是获取大小直接 return null 这里是 nullptr 而不是 NULL if (onlyDecodeSize) { returnnullptr; } }
// Scale is necessary due to density differences. if (scale != 1.0f) { willScale = true; // 计算 scaledWidth 和 scaledHeight // scaledWidth = 864 * 1.3125 + 0.5f = 1134 + 0.5f = 1134 scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); // scaledHeight = 582 * 1.3125 + 0.5f = 763.875 + 0.5f = 764 scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); } // 判断是否有复用的 Bitmap android::Bitmap *reuseBitmap = nullptr; unsignedint existingBufferSize = 0; if (javaBitmap != NULL) { reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap); if (reuseBitmap->peekAtPixelRef()->isImmutable()) { // 无法重用一个不变的位图图像解码器的目标。 ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; reuseBitmap = nullptr; } else { existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); } } JavaPixelAllocator javaAllocator(env); RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); SkBitmap::HeapAllocator heapAllocator; SkBitmap::Allocator *decodeAllocator; if (javaBitmap != nullptr && willScale) { // This will allocate pixels using a HeapAllocator, since there will be an extra // scaling step that copies these pixels into Java memory. This allocator // also checks that the recycled javaBitmap is large enough. decodeAllocator = &scaleCheckingAllocator; } elseif (javaBitmap != nullptr) { decodeAllocator = &recyclingAllocator; } elseif (willScale) { // This will allocate pixels using a HeapAllocator, since there will be an extra // scaling step that copies these pixels into Java memory. decodeAllocator = &heapAllocator; } else { decodeAllocator = &javaAllocator; }
// Set the decode colorType. This is necessary because we can't always support // the requested colorType. SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
// Construct a color table for the decode if necessary SkAutoTUnref <SkColorTable> colorTable(nullptr); SkPMColor *colorPtr = nullptr; int *colorCount = nullptr; int maxColors = 256; SkPMColor colors[256]; if (kIndex_8_SkColorType == decodeColorType) { colorTable.reset(newSkColorTable(colors, maxColors));
// SkColorTable expects us to initialize all of the colors before creating an // SkColorTable. However, we are using SkBitmap with an Allocator to allocate // memory for the decode, so we need to create the SkColorTable before decoding. // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is // not being used elsewhere. colorPtr = const_cast<SkPMColor *>(colorTable->readColors()); colorCount = &maxColors; }
// Set the alpha type for the decode. SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); // 创建 SkImageInfo 信息,宽,高,ColorType,alphaType const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType, alphaType); SkImageInfo bitmapInfo = decodeInfo; if (decodeColorType == kGray_8_SkColorType) { // The legacy implementation of BitmapFactory used kAlpha8 for // grayscale images (before kGray8 existed). While the codec // recognizes kGray8, we need to decode into a kAlpha8 bitmap // in order to avoid a behavior change. bitmapInfo = SkImageInfo::MakeA8(size.width(), size.height()); } // 解析 SkBitmap 设置 bitmapInfo,tryAllocPixels 开辟内存,具体分析在后面 SkBitmap decodingBitmap; if (!decodingBitmap.setInfo(bitmapInfo) || !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) { // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo() // should only only fail if the calculated value for rowBytes is too // large. // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the // native heap, or the recycled javaBitmap being too small to reuse. returnnullptr; }
// Use SkAndroidCodec to perform the decode. SkAndroidCodec::AndroidOptions codecOptions; codecOptions.fZeroInitialized = (decodeAllocator == &javaAllocator) ? SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized; codecOptions.fColorPtr = colorPtr; codecOptions.fColorCount = colorCount; codecOptions.fSampleSize = sampleSize; // 解析获取像素值 SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(), decodingBitmap.rowBytes(), &codecOptions); switch (result) { case SkCodec::kSuccess: case SkCodec::kIncompleteInput: break; default: returnnullObjectReturn("codec->getAndroidPixels() failed."); }
jbyteArray ninePatchChunk = NULL; if (peeker.mPatch != NULL) { if (willScale) { scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight); }
jobject ninePatchInsets = NULL; if (peeker.mHasInsets) { ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID, peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3], peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3], peeker.mOutlineRadius, peeker.mOutlineAlpha, scale); if (ninePatchInsets == NULL) { returnnullObjectReturn("nine patch insets == null"); } if (javaBitmap != NULL) { env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets); } } // 构建 SkBitmap 这个才是最终的 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 constfloat sx = scaledWidth / float(decodingBitmap.width()); constfloat sy = scaledHeight / float(decodingBitmap.height());
// Set the allocator for the outputBitmap. SkBitmap::Allocator *outputAllocator; if (javaBitmap != nullptr) { outputAllocator = &recyclingAllocator; } else { outputAllocator = &javaAllocator; }
SkColorType scaledColorType = 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. // 设置 SkImageInfo ,注意这里是 scaledWidth ,scaledHeight outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, scaledColorType, decodingBitmap.alphaType())); // 开辟当前 Bitmap 图片的内存 if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) { // This should only fail on OOM. The recyclingAllocator should have // enough memory since we check this before decoding using the // scaleCheckingAllocator. returnnullObjectReturn("allocation failed for scaled bitmap"); }
SkPaint paint; // kSrc_Mode instructs us to overwrite the unininitialized pixels in // outputBitmap. Otherwise we would blend by default, which is not // what we want. paint.setXfermodeMode(SkXfermode::kSrc_Mode); paint.setFilterQuality(kLow_SkFilterQuality); // decodingBitmap -> 画到 outputBitmap SkCanvas canvas(outputBitmap); canvas.scale(sx, sy); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); } else { outputBitmap.swap(decodingBitmap); }
// If we get here, the outputBitmap should have an installed pixelref. if (outputBitmap.pixelRef() == NULL) { returnnullObjectReturn("Got null SkPixelRef"); } if (!isMutable && javaBitmap == NULL) { // promise we will never change our pixels (great for sharing and pictures) outputBitmap.setImmutable(); } // 如果有复用返回原来的 javaBitmap bool isPremultiplied = !requireUnpremultiplied; if (javaBitmap != nullptr) { GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; }
int bitmapCreateFlags = 0x0; if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable; if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; // 没有复用的 Bitmap 创建一个新的 Bitmap // now create the java bitmap return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); }
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; }
size_t size; if (!computeAllocationSize(*bitmap, &size)) { returnNULL; }
// we must respect the rowBytes value already set on the bitmap instead of // attempting to compute our own. constsize_t rowBytes = bitmap->rowBytes();
jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size); if (env->ExceptionCheck() != 0) { returnNULL; } SkASSERT(arrayObj); jbyte *addr = (jbyte *) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj); if (env->ExceptionCheck() != 0) { returnNULL; } SkASSERT(addr); android::Bitmap *wrapper = new android::Bitmap(env, arrayObj, (void *) addr, info, rowBytes, ctable); wrapper->getSkBitmap(bitmap); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too bitmap->lockPixels();
return wrapper; }
从上面就可以看到, new android::Bitmap 见: frameworks/base/core/jni/android/graphics/Bitmap.cpp
1 2 3 4 5 6 7 8 9 10 11
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(newWrappedPixelRef(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(); }
// we must respect the rowBytes value already set on the bitmap instead of // attempting to compute our own. constsize_t rowBytes = bitmap->rowBytes(); if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) { returnnullptr; }
auto wrapper = alloc(size, info, rowBytes, ctable); if (wrapper) { wrapper->getSkBitmap(bitmap); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too bitmap->lockPixels(); } return wrapper; }
On Android 2.3.3 (API level 10) and lower, usingrecycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle()method allows an app to reclaim memory as soon as possible. Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap". The following code snippet gives an example of calling recycle(). It uses reference counting (in the variables mDisplayRefCount and mCacheRefCount) to track whether a bitmap is currently being displayed orin the cache. The code recycles the bitmap when these conditions are met: The reference count for both mDisplayRefCount and mCacheRefCount is 0. The bitmap isnotnull, and it hasn't been recycled yet.
/** * Free the native object associated with this bitmap, and clear the * reference to the pixel data. This will not free the pixel data synchronously; * it simply allows it to be garbage collected if there are no other references. * The bitmap is marked as "dead", meaning it will throw an exception if * getPixels() or setPixels() is called, and will draw nothing. This operation * cannot be reversed, so it should only be called if you are sure there are no * further uses for the bitmap. This is an advanced call, and normally need * not be called, since the normal GC process will free up this memory when * there are no more references to this bitmap. */
/* * 释放与此位图关联的本机对象,并清除对像素数据的引用。这不会立即释放像素数据; * 它只是允许在没有其他引用时进行垃圾回收。该位图被标记为"无效",这意味着如果调用 * getPixels()或setPixels(),它将抛出异常, * 并且不会绘制任何内容。此操作无法撤销,因此只有在确保不再需要位图时才应调用此方法。 * 这是一个高级调用,通常不需要调用,因为正常的垃圾回收过程将在不再引用该位图时释放此内 * 存。 */ publicvoidrecycle() { if (!mRecycled && mNativePtr != 0) { if (nativeRecycle(mNativePtr)) { // return value indicates whether native pixel object was actually recycled. // false indicates that it is still in use at the native level and these // objects should not be collected now. They will be collected later when the // Bitmap itself is collected. // 返回值指示本机像素对象是否实际已被回收。 // false 表示它仍在本机级别上使用,现在不应收集这些对象。它们将在位图本身被回收时稍后收集。 mNinePatchChunk = null; } mRecycled = true; } } privatestaticnativebooleannativeRecycle(long nativeBitmap);
// Native memory allocated for the duration of the Bitmap, // if pixel data allocated into native memory, instead of java byte[] privateint mNativeAllocationByteCount;
/** * Private constructor that must received an already allocated native bitmap * int (pointer). */ // called from JNI Bitmap(long nativeBitmap, int width, int height, int density, boolean isMutable, boolean requestPremultiplied, byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) { if (nativeBitmap == 0) { thrownewRuntimeException("internal error: native bitmap is 0"); }
public Runnable registerNativeAllocation(Object referent, long nativePtr) { if (referent == null) { thrownew IllegalArgumentException("referent is null"); } if (nativePtr == 0) { thrownew IllegalArgumentException("nativePtr is null"); }
CleanerThunk thunk; CleanerRunner result; try { thunk = new CleanerThunk(); Cleaner cleaner = Cleaner.create(referent, thunk); result = new CleanerRunner(cleaner); registerNativeAllocation(this.size); } catch (VirtualMachineError vme /* probably OutOfMemoryError */) { applyFreeFunction(freeFunction, nativePtr); throw vme; // Other exceptions are impossible. // Enable the cleaner only after we can no longer throw anything, including OOME. thunk.setNativePtr(nativePtr); return result; }
/** * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an * {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s * and then uses an LRU eviction policy to evict {@link android.graphics.Bitmap}s from the least * recently used bucket in order to keep the pool below a given maximum size limit. */ publicclassLruBitmapPoolimplementsBitmapPool { privatestaticfinalStringTAG="LruBitmapPool"; privatestaticfinal Bitmap.ConfigDEFAULT_CONFIG= Bitmap.Config.ARGB_8888;
/** * Constructor for LruBitmapPool. * * @param maxSize The initial maximum size of the pool in bytes. */ publicLruBitmapPool(long maxSize) { this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs()); }
/** * Constructor for LruBitmapPool. * * @param maxSize The initial maximum size of the pool in bytes. * @param allowedConfigs A white listed put of {@link android.graphics.Bitmap.Config} that are * allowed to be put into the pool. Configs not in the allowed put will be rejected. */ // Public API. @SuppressWarnings("unused") publicLruBitmapPool(long maxSize, Set<Bitmap.Config> allowedConfigs) { this(maxSize, getDefaultStrategy(), allowedConfigs); }
/** Returns the number of cache hits for bitmaps in the pool. */ publiclonghitCount() { return hits; }
/** Returns the number of cache misses for bitmaps in the pool. */ publiclongmissCount() { return misses; }
/** Returns the number of bitmaps that have been evicted from the pool. */ publiclongevictionCount() { return evictions; }
/** Returns the current size of the pool in bytes. */ publiclonggetCurrentSize() { return currentSize; }
if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap)); } dump();
evict(); }
privatevoidevict() { trimToSize(maxSize); }
@Override @NonNull public Bitmap get(int width, int height, Bitmap.Config config) { Bitmapresult= getDirtyOrNull(width, height, config); if (result != null) { // Bitmaps in the pool contain random data that in some cases must be cleared for an image // to be rendered correctly. we shouldn't force all consumers to independently erase the // contents individually, so we do so here. See issue #131. result.eraseColor(Color.TRANSPARENT); } else { result = createBitmap(width, height, config); }
return result; }
@NonNull @Override public Bitmap getDirty(int width, int height, Bitmap.Config config) { Bitmapresult= getDirtyOrNull(width, height, config); if (result == null) { result = createBitmap(width, height, config); } return result; }
@TargetApi(Build.VERSION_CODES.O) privatestaticvoidassertNotHardwareConfig(Bitmap.Config config) { // Avoid short circuiting on sdk int since it breaks on some versions of Android. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return; }
if (config == Bitmap.Config.HARDWARE) { thrownewIllegalArgumentException( "Cannot create a mutable Bitmap with config: " + config + ". Consider setting Downsampler#ALLOW_HARDWARE_CONFIG to false in your" + " RequestOptions and/or in GlideBuilder.setDefaultRequestOptions"); } }
@Nullable privatesynchronized Bitmap getDirtyOrNull( int width, int height, @Nullable Bitmap.Config config) { assertNotHardwareConfig(config); // Config will be null for non public config types, which can lead to transformations naively // passing in null as the requested config here. See issue #194. finalBitmapresult= strategy.get(width, height, config != null ? config : DEFAULT_CONFIG); if (result == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config)); } misses++; } else { hits++; currentSize -= strategy.getSize(result); tracker.remove(result); normalize(result); } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config)); } dump();
return result; }
// Setting these two values provides Bitmaps that are essentially equivalent to those returned // from Bitmap.createBitmap. privatestaticvoidnormalize(Bitmap bitmap) { bitmap.setHasAlpha(true); maybeSetPreMultiplied(bitmap); }
@TargetApi(Build.VERSION_CODES.O) privatestatic Set<Bitmap.Config> getDefaultAllowedConfigs() { Set<Bitmap.Config> configs = newHashSet<>(Arrays.asList(Bitmap.Config.values())); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // GIFs, among other types, end up with a native Bitmap config that doesn't map to a java // config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured // and are suitable for re-use. configs.add(null); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { configs.remove(Bitmap.Config.HARDWARE); } return Collections.unmodifiableSet(configs); }
@SuppressWarnings("unused") // Only used for debugging privatestaticclassThrowingBitmapTrackerimplementsBitmapTracker { privatefinal Set<Bitmap> bitmaps = Collections.synchronizedSet(newHashSet<Bitmap>());