安卓核心技术高级——Bitmaps与优化

有效地处理较大的位图

图像有各种不同的形状和大小,在许多情况下,他们往往比一个典型应用程序的用户界面(UI)所需要的资源更大。

现在的图像尺寸都是已知的,他们可以被用来决定是否应该加载完整的图片到内存或者是否用一个缩小的版本去代替加载。
以下是一些值得考虑的因素:

  • 估计加载完整图像所需要的内存;
  • 你承诺加载这个图片所需空间带给你的程序的其他内存需求;
  • 准备加载图像的目标ImageView或UI组件尺寸;
  • 当前设备的屏幕尺寸和密度;
  • 当前设备的屏幕尺寸和密度;

例如,如果1024768像素的图像最终被缩略地显示在一个12696像素的ImageView中,就不值得加载到内存中去。

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private ImageView iv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView)findViewById(R.id.imageView);
    }

    public void showClick(View view){
        Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),R.mipmap.a,102,68);
        iv.setImageBitmap(bitmap);
    }

    //位图重新采样
    public Bitmap decodeSampledBitmapFromResource(Resources res,int resid,int reqwidth,int reqHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res,resid);
        options.inSampleSize = calculateInSampleSize(options,reqwidth,reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res,resid);
    }

    //计算位图的采样比例
    public int calculateInSampleSize(BitmapFactory.Options options,int reqwidth,int reqHeight){
        //获取宽高
        int w = options.outWidth;
        int h = options.outHeight;
        int inSampleSize = 1;
        if (w>reqwidth || h>reqHeight){
            if (w>h){
                inSampleSize = Math.round((float)h/(float)reqHeight);
            }else {
                inSampleSize = Math.round((float)w/(float)reqwidth);
            }
        }
        System.out.println("inSampleSize:"+inSampleSize);
        return inSampleSize;
    }
}

在这里插入图片描述

缓存位图

(1)内存缓冲

参考博客:https://jykenan.iteye.com/blog/1828086

内存缓冲提供了可以快速访问位图LruCache类用于缓存位图的任务,最近被引用的对象保存在一个强引用LinkedHashMap中,以及在缓存超过了其指定的大小之前释放最近很少使用的对象的内存.
注意:在过去,一个常用的内存缓存实现是一个SoftReference或WeakReference的位图缓存,然而从android2.3(API 级别9)开始,垃圾回收器更加注重于回收软/弱引用,这使得使用以上引用很大程度上无效.此外,之前的android3.0(API级别11),位图的备份数据存储在本地那些在一种可预测的情况下没有被释放的内存中,很有可能会导致应用程序内存溢出和崩溃.
注:LRU是Least Recently Used近期最少使用算法


import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private ImageView iv;
    private LruCache<String,Bitmap> LruCache;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView)findViewById(R.id.imageView);

        //获取当前Activity内存大小
        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        int memoryClass = am.getMemoryClass();
        final int cacheSize = memoryClass/8 * 1024 * 1024;//字节 1/8的内存作为缓存大小

        LruCache = new LruCache<>(cacheSize);

    }

    //添加缓存
    public void addBitmapToCache(String key,Bitmap bitmap){
        if (getBitmapFromCache(key)==null){
            LruCache.put(key,bitmap);
        }
    }

    //从缓存中读取对象
    public Bitmap getBitmapFromCache(String key){
        return LruCache.get(key);
    }

    public void showClick(View view){
        String key = String.valueOf(R.mipmap.a);
        Bitmap bitmap = getBitmapFromCache(key);
        if (bitmap==null){
            bitmap = decodeSampledBitmapFromResource(getResources(),R.mipmap.a,511,341);
            addBitmapToCache(key,bitmap);
        }else {
            System.out.println("lruCache中有位图");
        }
        iv.setImageBitmap(bitmap);
    }

    //位图重新采样
    public Bitmap decodeSampledBitmapFromResource(Resources res,int resid,int reqwidth,int reqHeight){
        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 int calculateInSampleSize(BitmapFactory.Options options,int reqwidth,int reqHeight){
        //获取宽高
        int w = options.outWidth;
        int h = options.outHeight;
        int inSampleSize = 1;
        if (w>reqwidth || h>reqHeight){
            if (w>h){
                inSampleSize = Math.round((float)h/(float)reqHeight);
            }else {
                inSampleSize = Math.round((float)w/(float)reqwidth);
            }
        }
        System.out.println("inSampleSize:"+inSampleSize);
        return inSampleSize;
    }
}

在这里插入图片描述

(2)磁盘缓存

参考博客:https://blog.csdn.net/xuewater/article/details/41870839

使用磁盘缓存来持续处理位图,并且有助于在图片在内存缓存中不再可用时缩短加载时间.当然,从磁盘获取图片比从内存加载更慢并且应当在后台线程中处理,因为磁盘读取的时间是不可预知的。
注意:如果它们被更频繁地访问,那么一个ContentProvider可能是一个更合适的地方来存储缓存中的图像,例如在一个图片库应用程序里。

导入disklrucache.jar
http://www.java2s.com/Code/Jar/d/Downloaddisklrucache202jar.htm

双缓存案例实现

添加权限

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

创建LruCacheUtils工具类

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.LruCache;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


public class LruCacheUtils {
    private static LruCacheUtils lruCacheUtils;

    private DiskLruCache diskLruCache;
    private LruCache<String, Bitmap> lruCache;
    private Context context;

    private LruCacheUtils(){}

    public static LruCacheUtils getInstance(){
        if (lruCacheUtils==null){
            lruCacheUtils = new LruCacheUtils();
        }
        return lruCacheUtils;
    }

    //打开磁盘缓存
    public void open(Context context,String disk_cache_subdir,int disk_cache_size){
        try {
            this.context = context;

            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            int memoryClass = am.getMemoryClass();
            lruCache = new LruCache<>(memoryClass/8 * 1024 * 1024);

            /**
             *open()方法四个参数:
             * 第一个参数:缓存文件文件的位置。
             * 第二个参数:应用程序的版本号(在manifest中设置android:versionCode="1")。
             * 第三个参数:表示同一个key可以对应多少个缓存文件,一般情况下我们都是传1,这样key和缓存文件一一对应,查找和移除都会比较方便。
             * 第四个参数:最大可以缓存多少字节的数据。
             */
            diskLruCache = DiskLruCache.open(getCacheDir(disk_cache_subdir),getAppVersion(),1,disk_cache_size);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public InputStream getDiskCache(String url){
        String key = hashKeyForDisk(url);
        System.out.println("getDiskCache="+key);
        try {
            DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
            System.out.println(snapshot);
            if (snapshot!=null){
                return snapshot.getInputStream(0);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public void putCache(final String url, final CallBack callBack){
        new AsyncTask<String,Void,Bitmap>(){

            @Override
            protected Bitmap doInBackground(String... strings) {
                String key = hashKeyForDisk(strings[0]);
                System.out.println("key="+key);
                DiskLruCache.Editor editor = null;
                Bitmap bitmap = null;
                try {
                    URL url = new URL(strings[0]);
                    HttpURLConnection conn = (HttpURLConnection)url.openConnection();
                    conn.setReadTimeout(1000 * 30);
                    conn.setConnectTimeout(1000 * 30);
                    ByteArrayOutputStream baos = null;
                    if (conn.getResponseCode()==HttpURLConnection.HTTP_OK){
                        BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
                        baos = new ByteArrayOutputStream();
                        byte[] bytes = new byte[1024];
                        int len = -1;
                        while ((len=bis.read(bytes))!=-1){
                            baos.write(bytes,0,len);
                        }
                        bis.close();
                        baos.close();
                        conn.disconnect();
                    }
                    if (baos!=null){
                        bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),512,341);
                        addBitmapToCache(strings[0],bitmap);
                        editor = diskLruCache.edit(key);
                        System.out.println(url.getFile());

                        bitmap.compress(Bitmap.CompressFormat.JPEG,100,editor.newOutputStream(0));
                        editor.commit();
                    }
                } catch (IOException e) {
                    try {
                        editor.abort();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                    e.printStackTrace();
                }
                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                callBack.response(bitmap);
            }
        }.execute(url);
    }

    //关闭磁盘缓存
    public void close(){
        if (diskLruCache!=null && !diskLruCache.isClosed()){
            try {
                diskLruCache.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //刷新磁盘缓存
    public void flush(){
        if (diskLruCache!=null){
            try {
                diskLruCache.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //回调接口,把下载完成的图片传进来
    public interface CallBack<T>{
        public void response(T entity);
    }

    //添加缓存
    public void addBitmapToCache(String url,Bitmap bitmap){
        String key = hashKeyForDisk(url);
        if (getBitmapFromCache(key)==null){
            System.out.println("key===="+key);
            System.out.println("bitmap===="+bitmap);
            lruCache.put(key,bitmap);
        }
    }

    //从缓存中读取对象
    public Bitmap getBitmapFromCache(String url){
        String key =  hashKeyForDisk(url);
        return lruCache.get(key);
    }

    //位图重新采样
    public Bitmap decodeSampledBitmapFromStream(byte[] bytes, int reqwidth, int reqHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(bytes,0,bytes.length,options);
        options.inSampleSize = calculateInSampleSize(options,reqwidth,reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(bytes,0,bytes.length,options);
    }

    //计算位图的采样比例
    public int calculateInSampleSize(BitmapFactory.Options options, int reqwidth, int reqHeight){
        //获取宽高
        int w = options.outWidth;
        int h = options.outHeight;
        int inSampleSize = 1;
        if (w>reqwidth || h>reqHeight){
            if (w>h){
                inSampleSize = Math.round((float)h/(float)reqHeight);
            }else {
                inSampleSize = Math.round((float)w/(float)reqwidth);
            }
        }
        System.out.println("inSampleSize:"+inSampleSize);
        return inSampleSize;
    }

    //字符串计算摘要
    public String hashKeyForDisk(String key){
        String cacheKey = null;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return cacheKey;
    }

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

    private int getAppVersion(){
        try {
            return context.getPackageManager().getPackageInfo(context.getPackageName(),0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    private File getCacheDir(String name){
        String cachePath =
                Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || //判断是不是SD卡
                        !Environment.isExternalStorageRemovable()?//是否有外部存储
                        context.getExternalCacheDir().getPath():context.getCacheDir().getPath();
        return new File(cachePath + File.separator + name);

    }
}

运行

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import java.io.InputStream;

public class Main2Activity extends AppCompatActivity {

    private ImageView iv;
    private LruCacheUtils lruCacheUtils;
    private static final String DISK_CACHE_SUBDIR = "temp";
    private static final int DISK_CACHE_SIZE = 1024*1024*10;//10MB

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        iv = (ImageView)findViewById(R.id.imageView);
    }

    public void showClick(View view){
        String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555663463088&di=4b6117d1cea1a7d39fd03b77b765dd48&imgtype=0&src=http%3A%2F%2Fpic.qiantucdn.com%2F58pic%2F25%2F56%2F29%2F58396c9c1a3a4_1024.jpg";
        loadBitmap(url,iv);
    }

    @Override
    protected void onResume() {
        super.onResume();
        lruCacheUtils = LruCacheUtils.getInstance();
        lruCacheUtils.open(this,DISK_CACHE_SUBDIR,DISK_CACHE_SIZE);
    }

    @Override
    protected void onPause() {
        super.onPause();
        lruCacheUtils.flush();
    }

    @Override
    protected void onStop() {
        super.onStop();
        lruCacheUtils.close();
    }

    //加载图片
    public void loadBitmap(String url,final ImageView imageView){
        //从内存缓存中取得图片
        Bitmap bitmap = lruCacheUtils.getBitmapFromCache(url);
        if (bitmap == null){
            //再从磁盘缓存中取图片
            InputStream in = lruCacheUtils.getDiskCache(url);
            if (in == null){
                lruCacheUtils.putCache(url, new LruCacheUtils.CallBack<Bitmap>() {
                    @Override
                    public void response(Bitmap entity) {
                        System.out.println("http load");
                        imageView.setImageBitmap(entity);
                    }
                });
            }else {
                System.out.println("disk cache");
                bitmap = BitmapFactory.decodeStream(in);
                lruCacheUtils.addBitmapToCache(url,bitmap);
                imageView.setImageBitmap(bitmap);
            }
        }else {
            System.out.println("memory cache");
            imageView.setImageBitmap(bitmap);
        }
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值