BiBi - Android -12- Bitmap

From:Android开发艺术探索

  • BitmapFactory加载图片的方法
    decodeFile【文件】
    decodeResource【资源】
    decodeStream【输入流】
    decodeByteArray【字节数组】
    其中decodeFile和decodeResource间接调用decodeStream。

  • 高效加载Bitmap
    核心思想:使用BitmapFactory.Options来加载所需尺寸的图片。
    因为使用ImageView显示的图片并没有图片原始尺寸那么大,若把这个图片全部加载进来后再给ImageView显然没有必要。所以可以按照一定的【采样率】来加载缩小后的图片,将缩小后的图片在ImageView中显示。
    options.inSampleSize【采样率】,其值大于等于1,并且会向下取整为最接近2的指数。
    当inSampleSize为2时,400 x 400的图像变为200 x 200。
    options.inJustDecodeBounds = true,只会解析图片的高/宽信息,不会真正的加载图片,是个轻量级的操作,节省内存开销。

注意:BitmapFactory获取图片的高/宽和图片的位置【不同drawable目录】、设备的屏幕密度有关。

  • 获取采样率
public class GenerateBitmapUtils {
  public static Bitmap decodeSampleBitmapFromResource(Resources res, int resId, int reqWidth,
      int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId);
    options.inSampleSize = calulateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
  }

  private static int calulateInSampleSize(BitmapFactory.Options options, int reqWidth,
      int reqHeight) {
    final int height = options.outHeight;
    final int wight = options.outWidth;
    int inSampleSize = 1;
    while ((height / inSampleSize) >= reqHeight && (wight / inSampleSize) >= reqWidth) {
      inSampleSize *= 2;
    }
    return inSampleSize / 2 > 1 ? inSampleSize / 2 : 1;
  }
}
  • 缓存LruCache【属于Android SDK】
    LRU【Least Recently Used,近期最少使用】
    LruCache内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象。
    代码展示:
public class LruCacheTest {
  //缓存的总容量为:当前进程可用内存的1/8
  int cacheSize = (int) Runtime.getRuntime().maxMemory() / 1024 / 8;
  LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    public void resize(int maxSize) {
      super.resize(maxSize);
    }

    @Override
    public void trimToSize(int maxSize) {
      super.trimToSize(maxSize);
    }

    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
      super.entryRemoved(evicted, key, oldValue, newValue);
      //移除旧缓存时,可以在此方法中完成一些资源回收工作
    }

    @Override
    protected Bitmap create(String key) {
      return super.create(key);
    }

    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
      //计算缓存对象的大小,单位和总容量的单位一致
      return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }
  };

  public Bitmap get(String key) {
    return lruCache.get(key);
  }

  public void put(String key, Bitmap bitmap) {
    lruCache.put(key, bitmap);
  }

  public void delete(String key) {
    lruCache.remove(key);
  }
}
  • 缓存DiskLruCache【不属于Android SDK】
  /**
   * Opens the cache in {@code directory}, creating a cache if none exists
   * there.
   *
   * @param directory a writable directory
   * 存储路径。根据卸载APP后是否要删除该目录,可以指定不同的文件路径
   * @param valueCount the number of values per cache entry. Must be positive
   * 应用的版本号,一般设为1,当版本号发生改变时会清空之前所有的缓存文件
   * @param maxSize the maximum number of bytes this cache should use to store
   * 单个节点所对应的数据个数,一般设为1
   * @throws IOException if reading or writing the cache directory fails
   * 缓存的总大小
   */
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

DiskLruCached的缓存添加操作通过Editor完成,如果这个缓存图片正在被编辑,那么editor()返回null,即DiskLruCache不允许同时编辑一个缓存对象。

图片的url可能会有特殊字符,一般将图片的url的md5值作为key。

代码展示:

  private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) {
    if (null == diskLruCache) return null;
    Bitmap bitmap = null;
    String key = UrlMd5Utils.hashKeyFromUrl(url);
    try {
      DiskLruCache.Value value = diskLruCache.get(key);
      if (null != value) {
        FileInputStream fileInputStream = new FileInputStream(value.getFile(DISK_CACHE_INDEX));
        FileDescriptor fileDescriptor = fileInputStream.getFD();
        bitmap =
            imageResizer.decodeSampleBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
        if (null != bitmap) {
          addBitmapToMemoryCache(key, bitmap);
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return bitmap;
  }

通过DiskLruCache的get得到一个Snapshot对象,通过Snapshot对象可得到缓存的文件输入流。【注意:Snapshot现在改为了Value对象】

注意:FileInputStream采用BitmapFactory.Options方法缩放图像存在问题,因为:FileInputStream是一种有序的文件,而两次decodeStream调用会影响文件流的位置属性,导致第二次decodeSteam为null。可以通过文件流得到它所对应的文件描述符,再通过BitmapFactory.decodeFileDescriptor方法来加载一张缩放后的图片。

  • 列表错位问题
    由于View复用会导致列表错位,可以在给imageView设置图片之前检查它所对应的url是否发生变化。
String url = (String) imageView.getTag(TAG_KEY_RUI);
if (null != url && url.equals(result.url)) {
  imageView.setImageBitmap(result.bitmap);
}
  • 优化列表卡顿
    1)仅仅在加载图片时采用异步处理是不够的,当用户频繁滑动时,就会瞬间产生上百个异步任务,这些异步任务给线程池造成拥堵,并且会有大量UI更新操作,造成卡顿。
    解决:在列表滑动时停止加载图片。
public void onScrollStateChanged(AbsListView view, int scrollState) {
  if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
    isGridViewIdle = true;
    imageAdapter.notifyDataSetChanged();
  } else {
    isGridViewIdle = false;
  }
}
if(isGridViewIdle){
  imageView.setTag(url);
  bindBitmap(uri, imageView, width, height);
}

2)开启硬件加速
android:hardwareAcclerated="true"

  • ImageLoader
    图片加载库应具备的功能:
    网络拉取、磁盘缓存、内存缓存、图片压缩、同步加载、异步加载。
    流程图:


    6116263-f3e9a2344f6984df.png
    imageLoader.png
  • 完整代码

package com.ljg;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.support.annotation.NonNull;
import android.util.LruCache;
import android.widget.ImageView;
import com.bumptech.glide.disklrucache.DiskLruCache;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ImageLoader {
  private static final int MESSAGE_POST_RESULT = 1;
  //线程池参数
  private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
  private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
  private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
  private static final long KEEP_ALIVE = 10L;

  private static final int TAG_KEY_RUI = 777;
  private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
  private static final int IO_BUFFER_SIZE = 8 * 1024;
  private static final int DISK_CACHE_INDEX = 0;
  private boolean isDiskLruCacheCreated = false;

  private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger count = new AtomicInteger(1);

    @Override
    public Thread newThread(@NonNull Runnable r) {
      return new Thread(r, "ImageLoader#" + count.getAndIncrement());
    }
  };
  public static final Executor THREAD_POOL_EXECUTOR =
      new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE,
          TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), sThreadFactory);

  private static class LoaderResult {
    public ImageView imageView;
    public String url;
    public Bitmap bitmap;

    public LoaderResult(ImageView imageView, String url, Bitmap bitmap) {
      this.imageView = imageView;
      this.url = url;
      this.bitmap = bitmap;
    }
  }

  private Handler mainHandler = new Handler(Looper.myLooper()) {
    @Override
    public void handleMessage(Message msg) {
      LoaderResult result = (LoaderResult) msg.obj;
      ImageView imageView = result.imageView;
      String url = (String) imageView.getTag(TAG_KEY_RUI);
      if (null != url && url.equals(result.url)) {
        imageView.setImageBitmap(result.bitmap);
      }
    }
  };

  private Context context;
  private ImageResizer imageResizer = new ImageResizer();
  private LruCache<String, Bitmap> memoryCache;
  private DiskLruCache diskLruCache;

  public static ImageLoader build(Context context) {
    return new ImageLoader(context);
  }

  private ImageLoader(Context context) {
    this.context = context.getApplicationContext();
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    int cacheSize = maxMemory / 8;
    memoryCache = new LruCache<String, Bitmap>(cacheSize) {
      @Override
      protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
      }
    };
    File diskCacheDir = getDiskCacheDir(context, "bitmap");
    if (!diskCacheDir.exists()) {
      diskCacheDir.mkdirs();
    }
    if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
      try {
        diskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
        isDiskLruCacheCreated = true;
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  private long getUsableSpace(File diskCacheDir) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
      return diskCacheDir.getUsableSpace();
    }
    final StatFs statFs = new StatFs(diskCacheDir.getPath());
    return (long) statFs.getBlockSize() * statFs.getAvailableBlocksLong();
  }

  private File getDiskCacheDir(Context context, String name) {
    //存储状态(如果介质存在并在其安装点处安装,具有读/写访问权限)
    boolean externalStorageAvailable =
        Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    final String cachePath;
    if (externalStorageAvailable) {
      //扩展SDK的路径
      cachePath = context.getExternalCacheDir().getPath();
    } else {
      //手机硬盘的路径
      cachePath = context.getCacheDir().getPath();
    }
    return new File(cachePath + File.pathSeparator + name);
  }

  private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (null == getBitmapFromMemoryCache(key)) {
      memoryCache.put(key, bitmap);
    }
  }

  private Bitmap getBitmapFromMemoryCache(String key) {
    return memoryCache.get(key);
  }

  public void bindBitmap(final String url, final ImageView imageView) {
    bindBitmap(url, imageView, 0, 0);
  }

  public void bindBitmap(final String url, final ImageView imageView, final int reqWidth,
      final int reqHeight) {
    imageView.setTag(TAG_KEY_RUI, url);
    Bitmap bitmap = loadBitmapFromMemoryCache(url);
    if (null != bitmap) {
      imageView.setImageBitmap(bitmap);
      return;
    }
    Runnable loadBitmapTask = new Runnable() {
      @Override
      public void run() {
        Bitmap bmp = loadBitmap(url, reqWidth, reqHeight);
        if (null != bmp) {
          LoaderResult result = new LoaderResult(imageView, url, bmp);
          mainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
        }
      }
    };
    THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
  }

  private Bitmap loadBitmap(String url, int reqWidth, int reqHeight) {
    Bitmap bitmap = loadBitmapFromMemoryCache(url);
    if (null != bitmap) {
      return bitmap;
    }
    bitmap = loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    if (null != bitmap) {
      return bitmap;
    }
    if (isDiskLruCacheCreated) {
      bitmap = loadAndSaveDiskBitmapFromHttp(url, reqWidth, reqHeight);
    } else {
      bitmap = onlyLoadBitmapFromHttp(url);
    }
    return bitmap;
  }

  /**
   * 网络请求数据
   */
  private Bitmap onlyLoadBitmapFromHttp(String urlString) {
    Bitmap bitmap = null;
    HttpURLConnection urlConnection = null;
    BufferedInputStream in = null;
    try {
      final URL url = new URL(urlString);
      urlConnection = (HttpURLConnection) url.openConnection();
      in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
      bitmap = BitmapFactory.decodeStream(in);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (null != urlConnection) {
        urlConnection.disconnect();
      }
      if (null != in) {
        try {
          in.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    return bitmap;
  }

  private Bitmap loadAndSaveDiskBitmapFromHttp(String url, int reqWidth, int reqHeight) {
    if (null == diskLruCache) {
      return null;
    }
    String key = UrlMd5Utils.hashKeyFromUrl(url);
    try {
      DiskLruCache.Editor editor = diskLruCache.edit(key);
      if (null != editor) {
        OutputStream outputStream = new FileOutputStream(editor.getFile(DISK_CACHE_INDEX));
         //网络请求数据
        if (downloadUrlToStream(url, outputStream)) {
          editor.commit();
        } else {
          editor.abort();
        }
        diskLruCache.flush();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
  }

  /**
   * 网络请求数据
   */
  private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
    HttpURLConnection urlConnection = null;
    BufferedOutputStream out = null;
    BufferedInputStream in = null;
    try {
      final URL url = new URL(urlString);
      urlConnection = (HttpURLConnection) url.openConnection();
      in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
      out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
      int b;
      while ((b = in.read()) != -1) {
        out.write(b);
      }
      return true;
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (null != urlConnection) {
        urlConnection.disconnect();
      }
      if (null != in) {
        try {
          in.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
        if (null != out) {
          try {
            out.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
    return false;
  }

  private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) {
    if (null == diskLruCache) return null;
    Bitmap bitmap = null;
    String key = UrlMd5Utils.hashKeyFromUrl(url);
    try {
      DiskLruCache.Value value = diskLruCache.get(key);
      if (null != value) {
        FileInputStream fileInputStream = new FileInputStream(value.getFile(DISK_CACHE_INDEX));
        FileDescriptor fileDescriptor = fileInputStream.getFD();
        bitmap =
            imageResizer.decodeSampleBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
        if (null != bitmap) {
          addBitmapToMemoryCache(key, bitmap);
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return bitmap;
  }

  private Bitmap loadBitmapFromMemoryCache(String url) {
    final String key = UrlMd5Utils.hashKeyFromUrl(url);
    return getBitmapFromMemoryCache(key);
  }
}
  • 工具类【ImageResizer】
package com.ljg;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.FileDescriptor;

public class ImageResizer {
  public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth,
      int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
  }

  public Bitmap decodeSampleBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth,
      int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFileDescriptor(fd, null, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFileDescriptor(fd, null, options);
  }

  private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    if (0 == reqWidth || 0 == reqHeight) {
      return 1;
    }
    final int height = options.outHeight;
    final int wight = options.outWidth;
    int inSampleSize = 1;
    while ((height / inSampleSize) >= reqHeight && (wight / inSampleSize) >= reqWidth) {
      inSampleSize *= 2;
    }
    return inSampleSize / 2 > 1 ? inSampleSize / 2 : 1;
  }
}
  • 工具类【UrlMd5Utils】
package com.ljg;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class UrlMd5Utils {

  public static String hashKeyFromUrl(String url) {
    String cacheKey;
    try {
      final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
      messageDigest.update(url.getBytes());
      cacheKey = bytesToHexString(messageDigest.digest());
    } catch (NoSuchAlgorithmException e) {
      cacheKey = String.valueOf(url.hashCode());
    }
    return cacheKey;
  }

  private static String bytesToHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    String hex;
    for (byte aByte : bytes) {
      hex = Integer.toHexString(0xFF & aByte);
      if (hex.length() == 1) {
        sb.append('0');
      }
      sb.append(hex);
    }
    return sb.toString();
  }
}

猜你喜欢

转载自blog.csdn.net/weixin_33785972/article/details/87309378