前面都是在说UI的优化,今天跑个题,扒一扒内存优化的问题
为了绚丽的UI,大家的app中充斥着各种图片资源。这无可厚非,但是如果使用不当则会使得你的APK占用大量的内存,导致系统资源紧张,甚至应用outofmemory。
很多人好奇Bitmap和Drawable有什么区别,貌似图片用drawable的方式要优于使用bitmap的方式?为什么使用.9图片会减少内存的占用?怎么使用设置资源图片才能在适配分辨率的情况下不增加内存占用?下面将从源码的角度为大家一一解答。
(1)bitmap和drawable的区别
源码中大家可以看到drawable是一个虚类,主要的实现类有ColorDrawable、BitmapDrawable和ShapeDrawable等。drawable总的来说描述的就是系统中所有可供drawn的对象。可以是区域颜色,图形,也可以是位图。
而Bitmap就是位图对象,它就是一张位图在内存中数据的集合。
区别很明显了,但是为什么用Drawable貌似在内存上要优化许多呢,这就是两者数据的区别了,比如说ShapeDrawable,它描述的只是图形区域,这个图形是可以用数学公式和参数计算出来的,也就是说它不用保存所有像素点的信息。它的Bound和Draw方法可以好好研究哦,它可是控制少量数据在画布上做大事的关键。而bitmap就不一样了,它是一张图片所有像素点量化后的数据集合,是属于完全描述的。这对象大小的对比立马看出来了吧。
这段分析对内存的帮助就是一些能用简单颜色和图形表示的UI就尽量使用color和shapedrawable实现,不需要采用图片的方式,这样可以减少很多内存消耗。
(2)resource中图片资源的问题
(3).9图片问题
(4)屏幕适配的图片资源问题
以上者三个问题都跟resource资源相关,下面就可以分析Resources这个类。这里面关于加载drawable要说的有三种,一是colordrawable,二是bitmapdrawable,三是NinePatchDrawable。
代码就贴到这,看了注释应该就清楚了,说了这么多给应用开发的启示有两点:
首先,大图资源最好在res中按照分辨率分几分,避免运行时的缩放造成运行内存过高。实在不行就放到no-dpi中或者放到asserts里面主动去控制。
然后是,多使用.9图片,它本身占用的内存就小的多,运行时的缩放占用的内存问题也不是那么明显。
(5)来看看一些典型的Drawable为什么比bitmap要省内存
在android的GUI系统框架中,应用的显示元素与之直接打交道的对象称之为canvas,软件和硬件渲染都是由canvas只不过具体实现不太一样。也就是说要显示图片就是需要将其draw到canvas的一定区域上。这就是drawable的秘密。
Rect mBounds
public abstract void draw(Canvas canvas);
1、colordrawable:就是在画布上用一定颜色的画笔在绘制一定的区域,数据小小的,对象理应小小的。
2、shapedrawable:这个稍微多一步,多了一个shape对象,这个对象也小啊,比如roundrectshape,也就是一些点啊,线什么的,比起一片像素值小多了。
其他的就以此类推啦,BitmapDrawable为什么大呢,因为对象内部有一个完完全全的bitmap对象。
为了绚丽的UI,大家的app中充斥着各种图片资源。这无可厚非,但是如果使用不当则会使得你的APK占用大量的内存,导致系统资源紧张,甚至应用outofmemory。
很多人好奇Bitmap和Drawable有什么区别,貌似图片用drawable的方式要优于使用bitmap的方式?为什么使用.9图片会减少内存的占用?怎么使用设置资源图片才能在适配分辨率的情况下不增加内存占用?下面将从源码的角度为大家一一解答。
(1)bitmap和drawable的区别
源码中大家可以看到drawable是一个虚类,主要的实现类有ColorDrawable、BitmapDrawable和ShapeDrawable等。drawable总的来说描述的就是系统中所有可供drawn的对象。可以是区域颜色,图形,也可以是位图。
而Bitmap就是位图对象,它就是一张位图在内存中数据的集合。
区别很明显了,但是为什么用Drawable貌似在内存上要优化许多呢,这就是两者数据的区别了,比如说ShapeDrawable,它描述的只是图形区域,这个图形是可以用数学公式和参数计算出来的,也就是说它不用保存所有像素点的信息。它的Bound和Draw方法可以好好研究哦,它可是控制少量数据在画布上做大事的关键。而bitmap就不一样了,它是一张图片所有像素点量化后的数据集合,是属于完全描述的。这对象大小的对比立马看出来了吧。
这段分析对内存的帮助就是一些能用简单颜色和图形表示的UI就尽量使用color和shapedrawable实现,不需要采用图片的方式,这样可以减少很多内存消耗。
(2)resource中图片资源的问题
(3).9图片问题
(4)屏幕适配的图片资源问题
以上者三个问题都跟resource资源相关,下面就可以分析Resources这个类。这里面关于加载drawable要说的有三种,一是colordrawable,二是bitmapdrawable,三是NinePatchDrawable。
Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
boolean isColorDrawable = false;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
}
//这里通过判断输入的资源值是不是#和0x1f来确定是不是设置的颜色
final long key = isColorDrawable ? value.data :
(((long) value.assetCookie) << 32) | value.data;
//看看这个资源的值在缓存中的索引
Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
//根据索引在资源缓存中直接获取现成的drawable
if (dr != null) {
return dr;
}
if (isColorDrawable) {
dr = new ColorDrawable(value.data);
}
if (dr == null) {
String file = value.string.toString();
if (file.endsWith(".xml")) {
try {
XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp);
rp.close();
//从xml定义的drawable中去创建,这个以后有时间在分析,帮助理解selector
} catch (Exception e) {
NotFoundException rnf = new NotFoundException(
"File " file " from drawable resource ID #0x"
Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
} else {
try {
InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
// System.out.println("Opened file " file ": " is);
dr = Drawable.createFromResourceStream(this, value, is,
file, null);
//关键的处理都在createFromResourceStream方法中
is.close();
// System.out.println("Created stream: " dr);
} catch (Exception e) {
NotFoundException rnf = new NotFoundException(
"File " file " from drawable resource ID #0x"
Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
}
synchronized (mTmpValue) {
//Log.i(TAG, "Saving cached drawable @ #"
// Integer.toHexString(key.intValue())
// " in " this ": " cs);
if (isColorDrawable) {
mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
} else {
mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
}
//将新创建的对象放入到缓存中,这里的cache是软引用的,内存资源不足时可以自动回收,但
//是没有出现不足时会一直保存,这就是说加载过的资源都会一直存在,这里需要注意的是,
//如果有很大的图片并且有不显示的情况,最好是动态的设置并控制它的创建与销毁。不要等
//到内存紧张了由系统来帮你管理
}
}
}
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName, BitmapFactory.Options opts) {
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
}
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
//请注意这里的两个density值,一个是获取资源的也就是跟那些什么hdpi目录一致的,还有一个是当前屏幕的
return decodeStream(is, pad, opts);
}
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
Bitmap bm;
boolean finish = true;
//后面就是根据这个finish来判断图片是否还需要缩放
if (is instanceof AssetManager.AssetInputStream) {
final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
//这块可以看到android设计上的一个优化点,就是inBitmap的使用,这里是一个优化,就是说如果
//有 inScaled和inBitmap的情况下,可以在创建图片时直接进行缩放而不需要两步操作节省内存,但是
//可惜的是资源在这里处理时opts是没有用到这个东西的
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 {
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 [] 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);
if (opts.inScaled || isNinePatch) {
//inScaled其实前面并没有设置,而这个是默认就是true的
float scale = targetDensity / (float) density;
if (scale != 1.0f) {
final Bitmap oldBitmap = bm;
bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
(int) (bm.getHeight() * scale + 0.5f), true);
//看到了吧,就是在这里系统帮你做的缩放来适应屏幕分辨率的
if (bm != oldBitmap) oldBitmap.recycle();
if (isNinePatch) {
np = nativeScaleNinePatch(np, scale, outPadding);
bm.setNinePatchChunk(np);
//.9图就特殊了,它需要基本像素点数据,还需要patch的数据,它也是会根据分辨率来缩放的,
// 但是本身像素数据少也就影响不是很大
}
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;
}
代码就贴到这,看了注释应该就清楚了,说了这么多给应用开发的启示有两点:
首先,大图资源最好在res中按照分辨率分几分,避免运行时的缩放造成运行内存过高。实在不行就放到no-dpi中或者放到asserts里面主动去控制。
然后是,多使用.9图片,它本身占用的内存就小的多,运行时的缩放占用的内存问题也不是那么明显。
(5)来看看一些典型的Drawable为什么比bitmap要省内存
在android的GUI系统框架中,应用的显示元素与之直接打交道的对象称之为canvas,软件和硬件渲染都是由canvas只不过具体实现不太一样。也就是说要显示图片就是需要将其draw到canvas的一定区域上。这就是drawable的秘密。
Rect mBounds
public abstract void draw(Canvas canvas);
1、colordrawable:就是在画布上用一定颜色的画笔在绘制一定的区域,数据小小的,对象理应小小的。
public void draw(Canvas canvas) {
if ((mState.mUseColor >>> 24) != 0) {
mPaint.setColor(mState.mUseColor);
canvas.drawRect(getBounds(), mPaint);
}
}
2、shapedrawable:这个稍微多一步,多了一个shape对象,这个对象也小啊,比如roundrectshape,也就是一些点啊,线什么的,比起一片像素值小多了。
其他的就以此类推啦,BitmapDrawable为什么大呢,因为对象内部有一个完完全全的bitmap对象。