• Displaying Bitmaps Efficiently 显示图片相关


    http://developer.android.com/training/displaying-bitmaps/index.html

    .手机内存资源有限

    .Bitmap占用的内存大

    .App有时需要同时加载多张bitmap到内存

    一张 2592x1936 的照片,在默认 ARGB_8888 的情况下,占用的内存: 19MB (2592*1936*4 bytes)

    1.图片内存占用的计算

     android 3.1之前 (level 12):

    int bytes = bmp.getRowBytes() * bmp.getHeight()

    android 3.1 开始增加了方法,实现和上述是一样的:(android4.4开始,这个方法返回的数值可能不准)

    bmp.getByteCount();
    
    /**
     * Returns the number of bytes used to store this bitmap's pixels.
     */
    public final int getByteCount() {
        // int result permits bitmaps up to 46,340 x 46,340
        return getRowBytes() * getHeight();
    }

    从android 4.4开始要用新增的方法:

    bmp.getAllocationByteCount();

    This can be larger than the result of getByteCount() if a bitmap is reused to decode other bitmaps of smaller size, or by manual reconfiguration. See reconfigure(int, int, Config),setWidth(int), setHeight(int), setConfig(Bitmap.Config), and BitmapFactory.Options.inBitmap. If a bitmap is not modified in this way, this value will be the same as that returned bygetByteCount().

    2. Loading Large Bitmaps Efficiently

    如果图片的实际大小比界面上显示的要大,会消耗更多的内存和需要额外的缩放计算。

    BitmapFactory 提供了一系列从各种资源创建Bitmap的方法,如果直接创建很可能会因为图片过大而OOM。 BitmapFactory.Options 中 inJustDecodeBounds = true 时,只解析出图片的大小等信息,不会创建bitmap:

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
    int imageHeight = options.outHeight;
    int imageWidth = options.outWidth;
    String imageType = options.outMimeType;

    加载图片时,设置inSampleSize可以减小加载到内存图片的尺寸,比如inSampleSize=2,则图片的长和高都变为原来的1/2.

    //计算inSampleSize:
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
    
        if (height > reqHeight || width > reqWidth) {
    
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
    
            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
    
        return inSampleSize;
    }
    
    //根据需要的大小加载图片
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
    
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
    
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    
        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }


    3.Processing Bitmaps Off the UI Thread

    图片的加载通常耗时较长,不应放在UI线程中。

    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        //WeakReference 防止因为AsyncTask保留有ImageView 的引用而导致其不能正常释放,
        //所以不能保证执行onPostExecute时,imageView还是有效的,因此要检查null.
        private final WeakReference<ImageView> imageViewReference;
    
        private int data = 0;
    
        public BitmapWorkerTask(ImageView imageView) {
            // Use a WeakReference to ensure the ImageView can be garbage collected
            imageViewReference = new WeakReference<ImageView>(imageView);
        }
    
        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
        }
    
        // Once complete, see if ImageView is still around and set bitmap.
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }
    
    public void loadBitmap(int resId, ImageView imageView) {
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        task.execute(resId);
    }
    

    如何在复用View的同时,正确的利用多线程加载图片的一个解决方案。

    static class AsyncDrawable extends BitmapDrawable {
        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
    
        public AsyncDrawable(Resources res, Bitmap bitmap,
                BitmapWorkerTask bitmapWorkerTask) {
            super(res, bitmap);
            bitmapWorkerTaskReference =
                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
        }
    
        public BitmapWorkerTask getBitmapWorkerTask() {
            return bitmapWorkerTaskReference.get();
        }
    }
    
    public void loadBitmap(int resId, ImageView imageView) {
        if (cancelPotentialWork(resId, imageView)) {
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }
    
    public static boolean cancelPotentialWork(int data, ImageView imageView) {
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
    
        if (bitmapWorkerTask != null) {
            final int bitmapData = bitmapWorkerTask.data;
            // If bitmapData is not yet set or it differs from the new data
            if (bitmapData == 0 || bitmapData != data) {
                // Cancel previous task
                bitmapWorkerTask.cancel(true);
            } else {
                // The same work is already in progress
                return false;
            }
        }
        // No task associated with the ImageView, or an existing task was cancelled
        return true;
    }
    
    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
       if (imageView != null) {
           final Drawable drawable = imageView.getDrawable();
           if (drawable instanceof AsyncDrawable) {
               final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
               return asyncDrawable.getBitmapWorkerTask();
           }
        }
        return null;
    }
    
    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        ...
    
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (isCancelled()) {
                bitmap = null;
            }
    
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                final BitmapWorkerTask bitmapWorkerTask =
                        getBitmapWorkerTask(imageView);
                if (this == bitmapWorkerTask && imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }
    View Code


    4.Caching Bitmaps 图片缓存

    为了保证效率和界面的流畅性,需要缓存图片。一般分为mem缓存和disk缓存。

    MEM:
    Mem缓存时常用到 LruCache类(Added in API level 12,support-lib4中也有)。

    其内部实现,是将对象用强引用保存在一个LinkedHashMap中,当达到最大的缓存数量或者缓存内存大小时,会将最近最少使用的对象移除(每次添加一个新对象或者get使用一个对象时,都将其放到链表的头部,尾部的对象就是最少使用,要移除的

    以前常见缓存方式是用softReference和weakReference,但是现在不推荐使用了。 从 2.3开始,GC对软/弱引用的回收更加具有aggressive,也就是说它们很快就会被回收,起不到缓存的作用,也使其效率很低。 其次 3.0之前,bitmap 的内存数据是存储在 native 内存中的。

    没有一个固定的缓存大小能适合所有的app,缓存太小影响效率,太大可能导致OOM。

    设置缓存的大小需要考虑以下因素:

    .最大可用内存多少。

    .一次有多少图片在屏幕上,有多少即将要显示。

    .屏幕的尺寸和密度。

    .bitmap的参数影响其占用内存的多少 。

    .图片加载的频率,根据频率高低可以考虑用多个lrucache来缓存不同类别的图片。

    .平衡图片的质量和数量,有时需要大量低质量的图片,有时又需要高质量的图片。

    Disk:

    Mem缓存可能很快被占满。当应用切换到后台,可能被kill掉,导致mem缓存丢失,要重新加载处理图片,所以需要disk缓存。 从disk读数据比mem读慢,而且时间不确定,所以要放在工作线程中。

    DiskLruCache

    5.Managing Bitmap Memory

    .android 2.2(API level 8)和以下的版本中,GC工作时,会阻塞app线程,导致性能降低。 从2.3开始,GC是并行工作的,bitmap没有引用指向时会很快被回收掉。

    .Android 2.3.3 (API level 10)和以下的版本中,bitmap的像素数据是存储在 native内存中的,而bitmap对象是分配在虚拟机的堆上,二者是分开的。native内存中像素数据是以一种不可预测的方式释放,很可能导致OOM。

    .从Android 3.0 (API level 11)开始,bitmap的像素数据和bitmap对象本身都是存储在dalvik虚拟机的堆上的。所以最好在3.0以上的机器上调试图片内存占用的问题。

    所以3.0以下时,推荐使用 recycle()来显示的释放bitmap的内存。 但要注意的是,调用该方法时要确认该bitmap对象后续不会再使用到,否则会抛异常: "Canvas: trying to use a recycled bitmap". bitmapfun示例中使用了一种基于引用计数的解决方案。

    Options.inBitmap

    从android 3.0 开始,引入了 BitmapFactory.Options.inBitmap 字段,可以传入一个不再使用的bitmap对象,来加载一个新的bitmap,新的对象会复用旧的bitmap的内存,这样可以减少内存的分配和释放,提高了效率。

    但是使用inBitmap字段有一定的限制条件:

    .首先,要求旧的bitmap必须是mutable的才能复用(options.inMutable = true)。

    .Android 4.4 (API level 19)之前,要求新/旧对象的尺寸必须是相等的(the dimensions must match exactly and the inSampleSize must be 1)。

    .从4.4开始,新对象占用总字节数<=旧bitmap对象占用的总字节数就可以复用(the byte size of the new bitmap is smaller than the reusable bitmap candidate allocation byte count.)。

    在bitmapFun 示例中,从LruCache内存缓存中移除的bitmap,放入了一个 Set<SoftReference<Bitmap>> mReusableBitmaps 中,后续decode新bitmap对象的时候,到这里面去找看是否有能符合条件的,可以复用的bitmap,有的话就复用其内存。

    static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // From Android 4.4 (KitKat) onward we can re-use if the byte size of
            // the new bitmap is smaller than the reusable bitmap candidate
            // allocation byte count.
            int width = targetOptions.outWidth / targetOptions.inSampleSize;
            int height = targetOptions.outHeight / targetOptions.inSampleSize;
            int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
            return byteCount <= candidate.getAllocationByteCount();
        }
    
        // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
        return candidate.getWidth() == targetOptions.outWidth
                && candidate.getHeight() == targetOptions.outHeight
                && targetOptions.inSampleSize == 1;
    }
    
    static int getBytesPerPixel(Config config) {
        if (config == Config.ARGB_8888) {
            return 4;
        } else if (config == Config.RGB_565) {
            return 2;
        } else if (config == Config.ARGB_4444) {
            return 2;
        } else if (config == Config.ALPHA_8) {
            return 1;
        }
        return 1;
    }

    6. Displaying Bitmaps in Your UI

  • 相关阅读:
    设计模式总结
    字符编码小结
    搞定java.io
    将代码托管到GitHub上
    linuxlinux 路由表设置 之 route 指令详解 路由表设置 之 route 指令详解
    linux子系统的初始化_subsys_initcall()
    Linux系统目录结构介绍
    EtherType :以太网类型字段及值
    socket编程原理
    linux下的网络接口和网络桥接
  • 原文地址:https://www.cnblogs.com/zijianlu/p/3628701.html
Copyright © 2020-2023  润新知