Rock with Android


图片处理

现在的移动设备DPI越来越高,应用的图片的分辨率也是越来越高,在具体实现时,就需要了解安卓的图片处理机制,才能让你的应用在处理大量高清图片时,不崩不卡。

其实做到这一点并不容易,在安卓上的应用,复杂的时间图片流做到流畅的应用并不多见。Google Plus算是一个最好的例子,他的图片规格多样,但是十分流畅。Google的团队做了大量优化的工作,就图片处理这块,我们普通开发者能做的工作有那些呢?

Bitmap

安卓框架本身是用Bitmap这个封装对象,如果是自己手动生成Bitmap的话, 可以仔细看下Bitmap.Config

一般我们都是从网络下载图片到本地后,再加载显示。最大的问题是内存,所以在列表或者多图场景下,最好是缩成所需要规格的Bitmap来使用,如不是直接使用高分辨率的大图。

在用BitmapFactory做decode图片时,安卓本身根据机器和系统版本,会有一个解析图片的内存上限,如果图片比较大,很容易造成OOM错误,所以要使用下面的策略,尽量使生成的图片最小。

        Bitmap pic = null;
        int width = 640; //典型的取屏幕宽度或者ImageView的宽度
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            //只是得到具体宽高,并不真正decode图片
            options.inJustDecodeBounds = true;
            pic = BitmapFactory.decodeFile(path, options);
            //真正进行decode
            options.inJustDecodeBounds = false;
            //int be = options.outWidth / width;
            //不使用2的幂数缩放
            options.inSampleSize = 1;
            if (options.outWidth > width){
                //使用比例缩放
                options.inScaled = true;
                options.inDensity = options.outWidth;
                options.inTargetDensity = width;
            }
            pic = BitmapFactory.decodeFile(path, options);
        }catch (OutOfMemoryError e){
            //还是要防止OOM 
        }
        

Bitmap类还可以做一些图片的简单处理,比如圆角,灰度等效果,比较容易找到实现代码。这里给出一个取Exif的方向的函数:

    public static int getExifOrientation(String filepath) {
        int degree = 0;
        try{
            Class cls = Class.forName("android.media.ExifInterface");
            Class[] types = new Class[]{ String.class };
            Constructor cons = cls.getConstructor(types);

            types = new Class[] { String.class, Integer.TYPE };
            Method method = cls.getMethod("getAttributeInt", types);

            Object[] args = new Object[] { filepath };
            Object exif = cons.newInstance(args);

            if (exif != null) {
                args = new Object[] {"Orientation", -1};
                int orientation = (Integer) method.invoke(exif, args);
                if (orientation != -1) {
                    switch(orientation) {
                        //case android.media.ExifInterface.ORIENTATION_ROTATE_90:
                        case 6:
                            degree = 90;
                            break;
                        //case android.media.ExifInterface.ORIENTATION_ROTATE_180:
                        case 3:
                            degree = 180;
                            break;
                        //case android.media.ExifInterface.ORIENTATION_ROTATE_270:
                        case 8:
                            degree = 270;
                            break;
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return degree;
    }
        

另外还推荐一个比较高性能的类似高斯模糊的函数,具体见fastblur

对于滤镜本身,还没有看到比较靠谱的开源实现,因为性能原因,估计得用NDK,用c来实现。

图片加载器

真实项目中,往往是讲网络的图片加载到对于的ImageView或自定义的View里去。图片加载器就是抽象和封装这些操作的库,基本包括了图片的异步下载,本地SD卡缓存,图片文件decode成Bitmap对象,Bitmap对象的内存缓存,和具体View的绑定这些环节。而且最好能够定制使用的内存缓存大小,是否能用LRU策略,能不能方便对图片进行裁剪缩放。某熊曾经自己写过一个简单的加载器,也实际用过一段时间,但是我更推荐现在比较流行的2个图片加载器。

nostra13/Android-Universal-Image-Loader,是我厂项目里用的比较多的一个项目,支持方便的配置,效果不错。不过需要注意的一点,这个加载器的缓存策略是根据图片尺寸的,所以同一个图片文件,被用在不同尺寸的View里,会有多个Bitmap对象。

picasso,项目地址是https://github.com/square/picasso,是Square的开源项目,用起来比较简单,而且提供一个setDebug(true)的方式,可以方便的看到正在加载的图片是从网络,磁盘还是内存里加载的。这个项目相当比较新,虽然没研究过代码,但是公司出品,相信品质还是不错的。

另外提示下,养成及时解绑ImageView和Bitmap的操作,也就是某个ImageView不再显示了,就setImageBitmap(null),使得Bitmap的内存对象能及时回收,减少内存占用。

目录上一章Intent与应用互调