Android应用优化(3)APK资源图片优化

前面都是在说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。
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对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值