赞
踩
文章图片来自于https://blog.csdn.net/happylishang/article/details/80404966
dpi = Math.sqrt(Math.pow(width,2)+Math.pow(height, 2))/屏幕对角线长度(英寸)
px = dp * (dpi / 160)
也就是密度 = dpi / 160
名称 | 1dp对应px(屏幕密度 density 大小) | dpi |
---|---|---|
低 - ldpi | 0.75 | 120dpi |
中 - mdpi | 1 | 160dpi |
高 - hdpi | 1.5 | 240dpi |
超高 - xhdpi | 2 | 320dpi |
超超高 - xxhdpi | 3 | 480dpi |
超超超高 - xxxhdpi | 4 | 640dpi |
分别对应项目下的 ldpi、mdpi、hdpi、xdpi、xxdpi、xxxdpi 文件夹。
源码BitmapFactory.cpp的doDecode()方法如下:
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; } } // Determine the output size. SkISize size = codec->getSampledDimensions(sampleSize); int scaledWidth = size.width(); int scaledHeight = size.height(); bool willScale = false; // 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; } // Scale is necessary due to density differences. if (scale != 1.0f) { willScale = true; scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); } const float sx = scaledWidth / float(decodingBitmap.width()); const float sy = scaledHeight / float(decodingBitmap.height()); SkCanvas canvas(outputBitmap); canvas.scale(sx, sy); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
从源码可以看出scaledWidth经过两次计算,一次是如果sampleSize不等于1的时候计算缩放宽高,等于原宽高分别除以采样倍数;另外一次是如果目标屏幕密度和当前图片所处文件夹的密度不一致的话,计算出:
scale = targetDensity / density,
如果scale不等于1,用第一次计算的
scaledWidth * scale + 0.5,scaledHeight * scale + 0.5,
不过具体在做缩放操作的时候缩放因子等于两次计算之后的宽高分别处以原始宽高。可见对于设置采样率可以节省部分内存。
最后实际的占用大小:
width = (originWidth / sampleSize) * (targetDensity / density) + 0.5
height = (originHeight / sampleSize) * (targetDensity / density) + 0.5
totalSize = width * height * 像素位
(targetDensity是手机实际密度,等于(宽平方 + 高平方)开根号,处于屏幕对角线长度,density是图片在app所处文件的密度见上述表格。)
getRowBytes()返回的是每行的像素值,乘以高度就是总的像素数,也就是占用内存的大小。
getAllocationByteCount()与getByteCount()的返回值一般情况下都是相等的。
只是在图片 复用的时候,getAllocationByteCount()返回的是复用图像所占内存的大小,getByteCount()返回的是新解码图片占用内存的大小。
BitmapFactory.Options类的inJustDecodeBounds属性为true,可以在Bitmap不被加载到内存的前提下,获取Bitmap的原始宽高
BitmapFactory.Options的inSampleSize属性可以真实的压缩Bitmap占用的内存,加载更小内存的Bitmap
BitmapFactory.Options的inMutable属性,mutable是易变的意思,主要用于从以解码的bitmap返回一个可修改的bitmap对象。(If set, decode methods will always return a mutable Bitmap instead of an immutable one.)
BitmapFactory.Options的inBitmap 属性,用于图片复用,复用bitmap的大小必须小于被复用的bitmap大小。另外在设置这个属性的时候,inMutable属性必须设置为true。
(The current implementation necessitates that the reused bitmap be mutable, and the resulting reused bitmap will continue to remain mutable even when decoding a resource which would normally result in an immutable bitmap.)
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)); }
再看看allocateJavaPixelRef:
android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,SkColorTable* ctable) { const SkImageInfo& info = bitmap->info(); if (info.colorType() == kUnknown_SkColorType) { doThrowIAE(env, "unknown bitmap configuration"); return NULL; } size_t size; if (!computeAllocationSize(*bitmap, &size)) { return NULL; } // we must respect the rowBytes value already set on the bitmap instead of // attempting to compute our own. const size_t rowBytes = bitmap->rowBytes(); jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size); if (env->ExceptionCheck() != 0) { return NULL; } SkASSERT(arrayObj); jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj); if (env->ExceptionCheck() != 0) { return NULL; } 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; }
可见,将像素的数据分配给了android::Bitmap* wrapper,对应的arrayObj就是java层的byte[] mBuffer。
流程图如下(来自csdn用户-看书的小蜗牛):
//Bitmap.cpp: static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,jint offset, jint stride, jint width, jint height,jint configHandle, jboolean isMutable, jfloatArray xyzD50, jobject transferParameters) { 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; sk_sp<SkColorSpace> colorSpace; if (colorType != kN32_SkColorType || xyzD50 == nullptr || transferParameters == nullptr) { colorSpace = GraphicsJNI::colorSpaceForType(colorType); } else { SkColorSpaceTransferFn p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); SkMatrix44 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix); } bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace)); sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap); if (!nativeBitmap) { return NULL; } if (jColors != NULL) { GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap); } return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable)); } //Bitmap.cpp: static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { void* addr = calloc(size, 1); if (!addr) { return nullptr; } return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); }
可以看出,8.0之后,Bitmap像素内存的分配是在native层直接调用calloc,所以其像素分配的是在native heap上, 这也是为什么8.0之后的Bitmap消耗内存可以无限增长,直到耗尽系统内存,也不会提示Java OOM的原因。
流程图如下(来自csdn用户-看书的小蜗牛):
NativeAllocationRegistry是Android 8.0引入的一种辅助自动回收native内存的一种机制,当Java对象因为GC被回收后,NativeAllocationRegistry可以辅助回收Java对象所申请的native内存。
经典的图片加载库fresco采用三级缓存,其中两级内存,一级磁盘,两级内存分为以编码和未编码缓存。分析缓存时主要是指LruCache和DiskLruCache,分别对应内存和磁盘缓存。
LRU是Least Recently Used的缩写,最近最久未使用算法。
核心是LinkedHashMap,双向链表,通过构造函数就可以看出:
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
在定义的时候要定义缓存大小,一般是:
int maxMemorySize = (int) (Runtime.getRuntime().totalMemory() / 1024);
int cacheMemorySize = maxMemorySize / 8;
totalMemory是当前已分配的总内存大小,可变化;
maxMemory是当前可用的最大内存,固定;
getMemoryClass(ActivityManager),build.prop中厂商定义的每个进程分配的最大内存,如下:
String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
如果取不到,取下面这个参数:
String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
第二个参数默认16M。
LinkedHashMap第二个参数表示是否按照访问顺序排列,此处设置为true,表示按照访问顺序排列,否则以插入顺序排列。假设我们从表尾访问数据,在表头删除数据,当访问的数据项在链表中存在时,则将该数据项移动到表尾,否则在表尾新建一个数据项。当链表容量超过一定阈值,则移除表头的数据。
使用示例如下:
File directory = getCacheDir();
int appVersion = 1;
int valueCount = 1;
long maxSize = 10 * 1024;
DiskLruCache diskLruCache = DiskLruCache.open(directory, appVersion, valueCount, maxSize);
DiskLruCache.Editor editor = diskLruCache.edit(String.valueOf(System.currentTimeMillis()));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(editor.newOutputStream(0));
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scenery);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bufferedOutputStream);
editor.commit();
diskLruCache.flush();
diskLruCache.close();
主要stream处理的是Editor,这个editor封装了Entry对象,是否有出错,是否已经写入缓存。
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
}
在未写入缓存之前是不可读的。
Editor在commit数据的时候,如果有错误(很多exception都是hasErrors),会删除缓存,并在journal文件中写入删除状态;否则将文件写入缓存,写入CLEAN状态,另外检查当前大小是否超出设定的最大限制,如果超出的话,就要删除部分缓存,删除逻辑与LruCache一致。
另外DiskLruCache的初始化函数是DiskLruCache.open,在这个函数里面,先创建journal.tmp文件,然后重命名成journal正式文件,这个文件里面写了什么呢?
缓存使用名称为journal的文件,一个典型的journal文件如下:
libcore.io.DiskLruCache
1
100
2
CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
文件开始的五行是文件的头。包含常量字符串"libcore.io.DiskLruCache",磁盘缓存的版本,应用的版本,存储大小,一个空行。
后续每一个子行记录了每一个缓存的状态。每一行含有以空格分割的数值:状态,key,可选指定状态的大小,并不一定在每个状态中都有数值。
意味着缓存被创建或被更新。每一个成功写入DIRTY状态行后续一定跟着一个CLEAN或REMOVE状态,代表这条缓存可读或已被删除。DIRTY状态后续没有CLEAN或REMOVE状态意味着临时文件需要被删除。
意味着一个缓存已经被成功发布并且可以从缓存里面访问了。一个发布状态的行后续跟着当前缓存的大小,以空格分割,如果一个key对应多个value,就有多个数值了。
意味着最近被访问了
意味着当前缓存被删除了
journal文件在缓存操作发生时追加内容。journal文件在去除重复行的时候可能会发生压缩。在压缩时一个名称为journal.tmp的临时文件被使用到,并且当缓存被打开时这个临时文件应该被删除。
另外每次commit数据的时候,会检查容量,如果缓存数量大于2000或缓存entry数大于指定数量都会触发删除操作:
private boolean journalRebuildRequired() {
final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
&& redundantOpCount >= lruEntries.size();
}
redundantOpCount等于journal文件中除头部分固定的五行外的文件行数量减去缓存中entry的数量。
文章将同步至微信公众号:Android部落格
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。