1.Android之ListView异步加载网络图片(优化缓存机制)
网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化:
1、采用线程池
2、内存缓存+文件缓存
3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4
4、对下载的图片进行按比例缩放,以减少内存的消耗
具体的代码里面说明。先放上内存缓存类的代码MemoryCache.java:
- public class MemoryCache {
- private static final String TAG = "MemoryCache";
- // 放入缓存时是个同步操作
- // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
- // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
- private Map<String, Bitmap> cache = Collections
- .synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
- // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
- private long size = 0;// current allocated size
- // 缓存只能占用的最大堆内存
- private long limit = 1000000;// max memory in bytes
- public MemoryCache() {
- // use 25% of available heap size
- setLimit(Runtime.getRuntime().maxMemory() / 4);
- }
- public void setLimit(long new_limit) {
- limit = new_limit;
- Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
- }
- public Bitmap get(String id) {
- try {
- if (!cache.containsKey(id))
- return null;
- return cache.get(id);
- } catch (NullPointerException ex) {
- return null;
- }
- }
- public void put(String id, Bitmap bitmap) {
- try {
- if (cache.containsKey(id))
- size -= getSizeInBytes(cache.get(id));
- cache.put(id, bitmap);
- size += getSizeInBytes(bitmap);
- checkSize();
- } catch (Throwable th) {
- th.printStackTrace();
- }
- }
- /**
- * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
- *
- */
- private void checkSize() {
- Log.i(TAG, "cache size=" + size + " length=" + cache.size());
- if (size > limit) {
- // 先遍历最近最少使用的元素
- Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<String, Bitmap> entry = iter.next();
- size -= getSizeInBytes(entry.getValue());
- iter.remove();
- if (size <= limit)
- break;
- }
- Log.i(TAG, "Clean cache. New size " + cache.size());
- }
- }
- public void clear() {
- cache.clear();
- }
- /**
- * 图片占用的内存
- *
- * @param bitmap
- * @return
- */
- long getSizeInBytes(Bitmap bitmap) {
- if (bitmap == null)
- return 0;
- return bitmap.getRowBytes() * bitmap.getHeight();
- }
- }
也可以使用SoftReference,代码会简单很多,但是我推荐上面的方法。
- public class MemoryCache {
- private Map<String, SoftReference<Bitmap>> cache = Collections
- .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
- public Bitmap get(String id) {
- if (!cache.containsKey(id))
- return null;
- SoftReference<Bitmap> ref = cache.get(id);
- return ref.get();
- }
- public void put(String id, Bitmap bitmap) {
- cache.put(id, new SoftReference<Bitmap>(bitmap));
- }
- public void clear() {
- cache.clear();
- }
- }
- public class FileCache {
- private File cacheDir;
- public FileCache(Context context) {
- // 如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片
- // 没有SD卡就放在系统的缓存目录中
- if (android.os.Environment.getExternalStorageState().equals(
- android.os.Environment.MEDIA_MOUNTED))
- cacheDir = new File(
- android.os.Environment.getExternalStorageDirectory(),
- "LazyList");
- else
- cacheDir = context.getCacheDir();
- if (!cacheDir.exists())
- cacheDir.mkdirs();
- }
- public File getFile(String url) {
- // 将url的hashCode作为缓存的文件名
- String filename = String.valueOf(url.hashCode());
- // Another possible solution
- // String filename = URLEncoder.encode(url);
- File f = new File(cacheDir, filename);
- return f;
- }
- public void clear() {
- File[] files = cacheDir.listFiles();
- if (files == null)
- return;
- for (File f : files)
- f.delete();
- }
- }
- public class ImageLoader {
- MemoryCache memoryCache = new MemoryCache();
- FileCache fileCache;
- private Map<ImageView, String> imageViews = Collections
- .synchronizedMap(new WeakHashMap<ImageView, String>());
- // 线程池
- ExecutorService executorService;
- public ImageLoader(Context context) {
- fileCache = new FileCache(context);
- executorService = Executors.newFixedThreadPool(5);
- }
- // 当进入listview时默认的图片,可换成你自己的默认图片
- final int stub_id = R.drawable.stub;
- // 最主要的方法
- public void DisplayImage(String url, ImageView imageView) {
- imageViews.put(imageView, url);
- // 先从内存缓存中查找
- Bitmap bitmap = memoryCache.get(url);
- if (bitmap != null)
- imageView.setImageBitmap(bitmap);
- else {
- // 若没有的话则开启新线程加载图片
- queuePhoto(url, imageView);
- imageView.setImageResource(stub_id);
- }
- }
- private void queuePhoto(String url, ImageView imageView) {
- PhotoToLoad p = new PhotoToLoad(url, imageView);
- executorService.submit(new PhotosLoader(p));
- }
- private Bitmap getBitmap(String url) {
- File f = fileCache.getFile(url);
- // 先从文件缓存中查找是否有
- Bitmap b = decodeFile(f);
- if (b != null)
- return b;
- // 最后从指定的url中下载图片
- try {
- Bitmap bitmap = null;
- URL imageUrl = new URL(url);
- HttpURLConnection conn = (HttpURLConnection) imageUrl
- .openConnection();
- conn.setConnectTimeout(30000);
- conn.setReadTimeout(30000);
- conn.setInstanceFollowRedirects(true);
- InputStream is = conn.getInputStream();
- OutputStream os = new FileOutputStream(f);
- CopyStream(is, os);
- os.close();
- bitmap = decodeFile(f);
- return bitmap;
- } catch (Exception ex) {
- ex.printStackTrace();
- return null;
- }
- }
- // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
- private Bitmap decodeFile(File f) {
- try {
- // decode image size
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(new FileInputStream(f), null, o);
- // Find the correct scale value. It should be the power of 2.
- final int REQUIRED_SIZE = 70;
- int width_tmp = o.outWidth, height_tmp = o.outHeight;
- int scale = 1;
- while (true) {
- if (width_tmp / 2 < REQUIRED_SIZE
- || height_tmp / 2 < REQUIRED_SIZE)
- break;
- width_tmp /= 2;
- height_tmp /= 2;
- scale *= 2;
- }
- // decode with inSampleSize
- BitmapFactory.Options o2 = new BitmapFactory.Options();
- o2.inSampleSize = scale;
- return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
- } catch (FileNotFoundException e) {
- }
- return null;
- }
- // Task for the queue
- private class PhotoToLoad {
- public String url;
- public ImageView imageView;
- public PhotoToLoad(String u, ImageView i) {
- url = u;
- imageView = i;
- }
- }
- class PhotosLoader implements Runnable {
- PhotoToLoad photoToLoad;
- PhotosLoader(PhotoToLoad photoToLoad) {
- this.photoToLoad = photoToLoad;
- }
- @Override
- public void run() {
- if (imageViewReused(photoToLoad))
- return;
- Bitmap bmp = getBitmap(photoToLoad.url);
- memoryCache.put(photoToLoad.url, bmp);
- if (imageViewReused(photoToLoad))
- return;
- BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
- // 更新的操作放在UI线程中
- Activity a = (Activity) photoToLoad.imageView.getContext();
- a.runOnUiThread(bd);
- }
- }
- /**
- * 防止图片错位
- *
- * @param photoToLoad
- * @return
- */
- boolean imageViewReused(PhotoToLoad photoToLoad) {
- String tag = imageViews.get(photoToLoad.imageView);
- if (tag == null || !tag.equals(photoToLoad.url))
- return true;
- return false;
- }
- // 用于在UI线程中更新界面
- class BitmapDisplayer implements Runnable {
- Bitmap bitmap;
- PhotoToLoad photoToLoad;
- public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
- bitmap = b;
- photoToLoad = p;
- }
- public void run() {
- if (imageViewReused(photoToLoad))
- return;
- if (bitmap != null)
- photoToLoad.imageView.setImageBitmap(bitmap);
- else
- photoToLoad.imageView.setImageResource(stub_id);
- }
- }
- public void clearCache() {
- memoryCache.clear();
- fileCache.clear();
- }
- public static void CopyStream(InputStream is, OutputStream os) {
- final int buffer_size = 1024;
- try {
- byte[] bytes = new byte[buffer_size];
- for (;;) {
- int count = is.read(bytes, 0, buffer_size);
- if (count == -1)
- break;
- os.write(bytes, 0, count);
- }
- } catch (Exception ex) {
- }
- }
- }
- a.runOnUiThread(...);
在你的程序中的基本用法:
- ImageLoader imageLoader=new ImageLoader(context);
- ...
- imageLoader.DisplayImage(url, imageView);
OK,先到这。
2.android 网络加载图片,对图片资源进行优化,并且实现内存双缓存 + 磁盘缓存
经常会用到 网络文件 比如查看大图片数据 资源优化的问题,当然用开源的项目 Android-Universal-Image-Loader 或者 ignition 都是个很好的选择。
在这里把原来 写过的优化的代码直接拿出来,经过测试千张图片效果还是不错的。
工程目录:
至于 Activity 就是加载了 1个网格布局
- /**
- * 实现 异步加载 和 2级缓存
- */
- public class ImagedownActivity extends Activity {
- public static String filepath;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- filepath = this.getCacheDir().getAbsolutePath();
- GridView gv=(GridView)findViewById(R.id.gridview01);
- //设置列数
- gv.setNumColumns(3);
- //配置适配器
- gv.setAdapter(new Myadapter(this));
- }
- @Override
- protected void onDestroy() {
- // TODO Auto-generated method stub
- //activity 销毁时,清除缓存
- MyImageLoader.removeCache(filepath);
- super.onDestroy();
- }
- }
接下来 Myadapter.java(给网格每个item塞入图片 )在生成每个 item 异步请求网络获取image
- public class Myadapter extends BaseAdapter {
- private Context context;
- private String root ="http://192.168.0.100:8080/Android_list/";
- private String[] URLS;
- private final MyImageLoader myImageLoader = new MyImageLoader(context);;
- /**
- * adapter 初始化的时候早一堆数据
- * 这里我请求的是自己搭的服务器
- * @param context
- */
- public Myadapter(Context context){
- this.context =context;
- URLS = new String[999];
- for (int i = 0; i < 999; i++) {
- URLS[i] = root + (i+1)+".jpg";
- }
- }
- @Override
- public int getCount() {
- return URLS.length;
- }
- @Override
- public Object getItem(int position) {
- return URLS[position];
- }
- @Override
- public long getItemId(int position) {
- return URLS[position].hashCode();
- }
- @Override
- public View getView(int position, View view, ViewGroup parent) {
- ImageView imageView;
- if(view==null){
- imageView=new ImageView(context);
- imageView.setLayoutParams(new GridView.LayoutParams(200,190));
- imageView.setAdjustViewBounds(false);
- imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- imageView.setPadding(5, 5, 5, 5);
- }else{
- imageView=(ImageView)view;
- }
- myImageLoader.downLoad(URLS[position], (ImageView)imageView , context);
- return imageView;
- }
- }
MyImageLoader.java
- public class MyImageLoader {
- //最大内存
- final static int memClass = (int) Runtime.getRuntime().maxMemory();
- private Context context;
- // 是否缓存到硬盘
- private boolean diskcache = true;
- // 定义一级 缓存的图片数
- private static final int catch_num = 10;
- // 定义二级缓存 容器 软引用
- private static ConcurrentHashMap<String, SoftReference<Bitmap>> current_hashmap = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
- // 定义一级缓存容器 强引用 (catch_num ,0.75f,true) 默认参数 2.加载因子默认 3.排序模式 true
- private static LinkedHashMap<String, Bitmap> link_hashmap = new LinkedHashMap<String, Bitmap>(catch_num ,0.75f,true) {
- // 必须实现的方法
- protected boolean removeEldestEntry(java.util.Map.Entry<String, Bitmap> eldest) {
- /** 当一级缓存中 图片数量大于 定义的数量 放入二级缓存中
- */
- if (this.size() > catch_num) {
- // 软连接的方法 存进二级缓存中
- current_hashmap.put(eldest.getKey(), new SoftReference<Bitmap>(
- eldest.getValue()));
- //缓存到本地
- cancheToDisk(eldest.getKey(),eldest.getValue() );
- return true;
- }
- return false;
- };
- };
- public MyImageLoader(Context context) {
- }
- /**
- * 外部调用此方法 进行下载图片
- */
- public void downLoad(String key , ImageView imageView,Context context){
- // 先从缓存中找 。
- context = this.context;
- Bitmap bitmap = getBitmapFromCache(key);
- if( null!= bitmap){
- imageView.setImageBitmap(bitmap);
- cancleDownload(key, imageView); //取消下载
- return ;
- }
- // 缓存中 没有 把当前的 imageView 给他 得到 task
- if(cancleDownload(key, imageView)){ //没有任务进行。,。。开始下载
- ImageDownloadTask task = new ImageDownloadTask(imageView);
- Zhanwei_Image zhanwei_image = new Zhanwei_Image(task);
- //先把占位的图片放进去
- imageView.setImageDrawable(zhanwei_image);
- // task执行任务
- task.execute(key);
- }
- }
- /** 此方法 用于优化 : 用户直接 翻到 哪个 就先加载 哪个、
- * @param key - URL
- * @param imageView - imageView
- * core: 给当前的 imageView 得到给他下载的 task
- */
- private boolean cancleDownload(String key,ImageView imageView){
- // 给当前的 imageView 得到给他下载的 task
- ImageDownloadTask task = getImageDownloadTask(imageView);
- if(null != task){
- String down_key = task.key;
- if( null == down_key || !down_key.equals(key)){
- task.cancel(true); // imageview 和 url 的key不一样 取消下载
- }else{
- return false; //正在下载:
- }
- }
- return true; //没有正在下载
- }
- // public void getThisProcessMemeryInfo() {
- // int pid = android.os.Process.myPid();
- // android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(new int[] {pid});
- // System.out.println("本应用当前使用了" + (float)memoryInfoArray[0].getTotalPrivateDirty() / 1024 + "mb的内存");
- // }
- /**
- * 从缓存中得到 图片的方法 1.先从一级 缓存找 linkhashmap 不是线程安全的 必须要加同步
- */
- public Bitmap getBitmapFromCache(String key) {
- //1.先在一级缓存中找
- synchronized (link_hashmap) {
- Bitmap bitmap = link_hashmap.get(key);
- if (null != bitmap) {
- link_hashmap.remove(key);
- // 按照 LRU是Least Recently Used 近期最少使用算法 内存算法 就近 就 原则 放到首位
- link_hashmap.put(key, bitmap);
- System.out.println(" 在缓存1中找图片了 =" +key);
- return bitmap;
- }
- }
- // 2. 到二级 缓存找
- SoftReference<Bitmap> soft = current_hashmap.get(key);
- if (soft != null) {
- //得到 软连接 中的图片
- Bitmap soft_bitmap = soft.get();
- if (null != soft_bitmap) {
- System.out.println(" 在缓存2中找图片了 =" +key);
- return soft_bitmap;
- }
- } else {
- // 没有图片的话 把这个key删除
- current_hashmap.remove(key);
- }
- //3.都没有的话去从外部缓存文件读取
- if(diskcache){
- Bitmap bitmap = getBitmapFromFile(key);
- if(bitmap!= null){
- link_hashmap.put(key, bitmap); //将图片放到一级缓存首位
- return bitmap;
- }
- }
- return null;
- }
- /**
- * 缓存到本地文件
- * @param key
- * @param bitmap
- */
- public static void cancheToDisk(String key ,Bitmap bitmap ){
- //2.缓存bitmap至/data/data/packageName/cache/文件夹中
- try {
- String fileName = getMD5Str(key);
- String filePath = ImagedownActivity.filepath + "/" + fileName;
- System.out.println("缓存到本地===" + filePath);
- FileOutputStream fos = new FileOutputStream(filePath);
- bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
- } catch (Exception e) {
- }
- }
- /**
- * 从外部文件缓存中获取bitmap
- * @param url
- * @return
- */
- private Bitmap getBitmapFromFile(String url){
- Bitmap bitmap = null;
- String fileName = getMD5Str(url);
- if(fileName == null){
- return null;
- }
- String filePath = ImagedownActivity.filepath + "/" + fileName;
- try {
- FileInputStream fis = new FileInputStream(filePath);
- bitmap = BitmapFactory.decodeStream(fis);
- System.out.println("在本地缓存中找到图片==="+ filePath);
- } catch (FileNotFoundException e) {
- System.out.println("getBitmapFromFile==="+ e.toString());
- e.printStackTrace();
- bitmap = null;
- }
- return bitmap;
- }
- /**
- * 清理文件缓存
- * @param dirPath
- * @return
- */
- public static boolean removeCache(String dirPath) {
- File dir = new File(dirPath);
- File[] files = dir.listFiles();
- if(files == null || files.length == 0) {
- return true;
- }
- int dirSize = 0;
- //这里删除所有的缓存
- int all_ = (int) ( 1 * files.length + 1);
- //对files 进行排序
- Arrays.sort(files, new FileLastModifiedSort());
- for (int i = 0; i < all_ ; i++) {
- files[i].delete();
- }
- return true;
- }
- /**
- * 根据文件最后修改时间进行排序
- */
- private static class FileLastModifiedSort implements Comparator<File> {
- @Override
- public int compare(File lhs, File rhs) {
- if(lhs.lastModified() > rhs.lastModified()) {
- return 1;
- } else if(lhs.lastModified() == rhs.lastModified()) {
- return 0;
- } else {
- return -1;
- }
- }
- }
- /**
- * MD5 加密
- */
- private static String getMD5Str(String str) {
- MessageDigest messageDigest = null;
- try {
- messageDigest = MessageDigest.getInstance("MD5");
- messageDigest.reset();
- messageDigest.update(str.getBytes("UTF-8"));
- } catch (NoSuchAlgorithmException e) {
- System.out.println("NoSuchAlgorithmException caught!");
- return null;
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- return null;
- }
- byte[] byteArray = messageDigest.digest();
- StringBuffer md5StrBuff = new StringBuffer();
- for (int i = 0; i < byteArray.length; i++) {
- if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
- md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
- else
- md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
- }
- return md5StrBuff.toString();
- }
- // ------------------------ 异步加载----------------------------
- /**
- * 占位的 图片 或者 颜色 用来绑定 相应的图片
- */
- class Zhanwei_Image extends ColorDrawable{
- //里面存放 相应 的异步 处理时加载好的图片 ----- 相应的 task
- private final WeakReference<ImageDownloadTask> taskReference;
- public Zhanwei_Image(ImageDownloadTask task){
- super(Color.BLUE);
- taskReference = new WeakReference<MyImageLoader.ImageDownloadTask>(task);
- }
- // 返回去这个 task 用于比较
- public ImageDownloadTask getImageDownloadTask(){
- return taskReference.get();
- }
- }
- // 根据 给 的 iamgeView、 得到里面的 task 用于和当前的 task比较是不是同1个
- private ImageDownloadTask getImageDownloadTask(ImageView imageView){
- if( null != imageView){
- Drawable drawable = imageView.getDrawable();
- if( drawable instanceof Zhanwei_Image)
- return ((Zhanwei_Image)drawable).getImageDownloadTask();
- }
- return null;
- }
- /**
- * 把图片 添加到缓存中
- */
- public void addBitmap(String key, Bitmap bitmap) {
- if (null != bitmap) {
- synchronized (link_hashmap) { // 添加到一级 缓存中
- link_hashmap.put(key, bitmap);
- }
- }
- }
- /** 在后台 加载每个图片
- * 第一个参数 第2个要进度条不 第三个返回结果 bitmap
- */
- class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
- private String key;
- private WeakReference<ImageView> imgViReference;
- public ImageDownloadTask(ImageView imageView) {
- //imageView 传进来 。。要给哪个iamgeView加载图片
- imgViReference = new WeakReference<ImageView>(
- imageView);
- }
- @Override
- protected Bitmap doInBackground(String... params){
- key = params[0];
- //调用下载函数 根据 url 下载
- return downloadBitmap(key);
- }
- @Override
- protected void onPostExecute(Bitmap result) {
- if(isCancelled()){
- result = null;
- }
- System.out.println("result=="+ result.getByteCount()+"---memClassmemery="+memClass);
- if(null!= result){
- //保存到缓存中
- addBitmap(key, result);
- ImageView imageView = imgViReference.get();
- if( null != imageView){
- //向 imageView 里面放入 bitmap
- ImageDownloadTask task = getImageDownloadTask(imageView);
- /**
- * 判断 是不是 同一个 task( )
- * 如果当前这个 task == imageView 里面的那个 task 就是同1个
- */
- if( this == task ){
- imageView.setImageBitmap(result);
- }
- }
- }
- }
- }
- /**
- * 连接网络 客户端 下载图片
- */
- private Bitmap downloadBitmap(String url) {
- final HttpClient client = AndroidHttpClient.newInstance("Android");
- final HttpGet getRequest = new HttpGet(url);
- try {
- HttpResponse response = client.execute(getRequest);
- final int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
- return null;
- }
- final HttpEntity entity = response.getEntity();
- if (entity != null) {
- InputStream inputStream = null;
- try {
- inputStream = entity.getContent();
- /**
- * 1.没有压缩直接将生成的bitmap返回去
- */
- // return BitmapFactory.decodeStream(inputStream);
- /**
- * 2.得到data后在这里把图片进行压缩
- */
- byte[] data = StreamTool.read(inputStream);
- return BitmapManager.scaleBitmap(context, data, 0.3f);
- // return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
- } finally {
- if (inputStream != null) {
- inputStream.close();
- }
- entity.consumeContent();
- }
- }
- } catch (IOException e) {
- getRequest.abort();
- } catch (IllegalStateException e) {
- getRequest.abort();
- } catch (Exception e) {
- getRequest.abort();
- } finally {
- if ((client instanceof AndroidHttpClient)) {
- ((AndroidHttpClient) client).close();
- }
- }
- return null;
- }
- }
StreamTool.java
- public class StreamTool {
- public static byte[] read(InputStream in) throws Exception{
- ByteArrayOutputStream out_byte = new ByteArrayOutputStream();
- byte[] buff = new byte[1024];
- int len=0;
- while((len = in.read(buff))!= -1){
- //写到内存中 字节流
- out_byte.write( buff, 0 , len);
- }
- out_byte.close();
- // 把内存数据返回
- return out_byte.toByteArray();
- }
- }
BitmapManager.java ( 这个类里面对 网络资源的图片 进行了优化)
- public class BitmapManager {
- /**
- * 按屏幕适配Bitmap
- */
- public static Bitmap scaleBitmap(Context context, byte[] data , float percent) {
- //这里我不获取了,假设是下面这个分辨率
- int screenWidth = 540;
- int screenrHeight = 950;
- //设置 options
- BitmapFactory.Options options = new BitmapFactory.Options();
- /**
- * BitmapFactory.Options这个类,有一个字段叫做 inJustDecodeBounds.SDK中对这个成员的说明是这样的:
- * If set to true, the decoder will return null (no bitmap), but the out…
- * 也就是说,如果我们把它设为true,那么BitmapFactory.decodeFile(String path, Options opt)并不会真的返回一个Bitmap给你,
- * 它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。
- */
- options.inJustDecodeBounds = true;
- //读取
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
- int imgWidth = options.outWidth;
- int imgHeight = options.outHeight;
- //如果比你设置的宽高大 就进行缩放,
- if(imgWidth > screenWidth * percent || imgHeight > screenrHeight * percent) {
- options.inSampleSize = calculateInSampleSize(options, screenWidth, screenrHeight, percent);
- }
- /**
- * If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller
- * to query the bitmap without having to allocate the memory for its pixels.
- *
- * 如果设置成 true,这个编码将会返回1个null , 但是那个区域仍将被设置(也就是存在),允许(调用者)去查询那个没有分配 内存的像素 bitmap
- */
- options.inJustDecodeBounds = false;
- /**
- * Android的Bitmap.Config给出了bitmap的一个像素所对应的存储方式,
- * 有RGB_565,ARGB_8888,ARGB_4444,ALPHA_8四种。RGB_565表示的是红绿蓝三色分别用5,6,5个比特来存储,
- * 一个像素占用了5+6+5=16个比特。ARGB_8888表示红绿蓝和半透明分别用8,8,8,8个比特来存储,
- * 一个像素占用了8+8+8+8=32个比特。这样的话如果图片是以RGB_8888读入的,那么占用内存的大小将是RGB_565读入方式的2倍。
- * 通常我们给Imagview加载图片是通过setDrawable或者在xml文件中用android:src来设置
- * 默认的加载图片大小的方式是以RGB_8888读入的。
- *
- */
- options.inPreferredConfig = Bitmap.Config.RGB_565;
- /**
- * If this is set to true, then the resulting bitmap will allocate its pixels such that they can be purged
- * if the system needs to reclaim memory.
- *
- * 如果设置成 true, 这个结果bitmap 将会被分配像素,这样他们就能被 系统回收了,当系统需要回收内存的时候
- */
- options.inPurgeable = true;
- /**
- * This field works in conjuction with inPurgeable.
- * 这个方法是在 inPurgeable 的基础上工作的
- */
- options.inInputShareable = true;
- bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
- System.out.println("data==="+ data.length +" change == bitmap byte "+ bitmap.getByteCount());
- return bitmap;
- }
- // options reqWidth 屏幕宽 reqHeight屏幕高 你的view是屏幕的多大
- public static int calculateInSampleSize(BitmapFactory.Options options, int screenWidth, int screenHeight ,float percent) {
- // 原始图片宽高
- final int height = options.outHeight;
- final int width = options.outWidth;
- // 倍数
- int inSampleSize = 1;
- if (height > screenHeight * percent || width > screenWidth * percent) {
- // 计算目标宽高与原始宽高的比值
- final int inSampleSize_h = Math.round((float) height / (float)( screenHeight * percent));
- final int inSampleSize_w = Math.round((float) width / (float)( screenWidth * percent));
- // 选择两个比值中较小的作为inSampleSize的
- inSampleSize = inSampleSize_h < inSampleSize_w ? inSampleSize_h : inSampleSize_w;
- System.out.println("inSampleSize===="+ inSampleSize);
- //
- if(inSampleSize < 1) {
- inSampleSize = 1;
- }
- }
- //简单说这个数字就是 缩小为原来的几倍,根据你的image需要占屏幕多大动态算的(比如你用的权重设置layout)
- return inSampleSize;
- }
- }
这个是代码输出的最多给这个进程分配的内存 128M
可以看到我上面的bitmapManager 里面有个 options.inPreferredConfig 注释写的很清楚,可以上去看一下,接下来贴几种格式的效果图
rgb565 和 argb_444 所占的内存 (54000)
看一下 argb_8888 ( 108000)
当然可能仔细看的人会看到我一开始截的 鸣人的效果图 上半部分 和 下半部分的颜色会有点问题。上面的rgb_565 生成的,和原图色彩可能会有点出入。
但是内存真心少了一半,所以各种取舍就看个人了,代码注释都谢的很清楚了。
先贴出代码不同地方的代码 : 就是在强引用的地方 把 LinkedHashMap 换成了 LruCache
- // 获取单个进程可用内存的最大值
- // 方式一:使用ActivityManager服务(计量单位为M)
- /*int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();*/
- // 方式二:使用Runtime类(计量单位为Byte)
- final static int memClass = (int) Runtime.getRuntime().maxMemory();
- // 3. 定义一级缓存容器 强引用 (catch_num /2,0.75f,true) 默认参数 2.加载因子默认 3.排序模式 true
- final static int max = memClass/5;
- // LruCache 用强引用将 图片放入 LinkedHashMap
- private static LruCache<String, Bitmap> lrucache = new LruCache<String, Bitmap>(max) {
- protected int sizeOf(String key, Bitmap value) {
- if(value != null) {
- // 计算存储bitmap所占用的字节数
- return value.getRowBytes() * value.getHeight();
- } else {
- return 0;
- }
- }
- @Override
- protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
- if(oldValue != null) {
- // 当硬引用缓存容量已满时,会使用LRU算法将最近没有被使用的图片转入软引用缓存
- current_hashmap.put(key, new SoftReference<Bitmap>(oldValue));
- }
- }
- };
1. 强引用:LruCache 后面再说,其实他内的内部封装的就是1个 LinkedHashMap 。LinkedHashMap 是线程不安全的,所以上面都会用到同步。
2. 软引用:ConcurrentHashMap 是线程安全的,并且支持高并发很有效率,这个后面也会说到,为什么要用 软引用 SoftReference,这个是在系统将要oom时,就会回收
软引用的对象资源,所以才会用到他,防止程序出异常 。
3. 磁盘缓存: 这个经常会看到网易新闻等,应用有些界面你看了很多图片,往上翻很多, 其实没有再次访问网络,会将部分image缓存在sdcard里。
4. 其中1个优化: 当比如用户快速滑动到 最底部,其实是最先加载显示给用户的部分的内容的,这样就是用户看到哪加载哪,1个是快,1个是避免资源浪费。
原理: 当用户进入界面加载图片 ,首先会从1级缓存强引用中找,找不到回去2级缓存软引用中找,找不到再去sdcard中找,再找不到才会去请求网络加载资源。
当然sdcard的缓存 看个人需求是否需要。
注: android 4.0 后 对 SoftReference 的回收机制进行了改变,所以你是可以不用 2级缓存的,直接去掉就好了。
只要控制好你的 lrucache 或者 linkedhashmap就好了。