【达内课程】图库应用(2)

内存

【内存的种类】
1、ROM->Read Only Memory
2、RAM->Random Access Memory
3、Cache->高速缓存

【RAM–随机存取存储器】
RAM 是可读可写的存储器,通常表现为计算机的“内存条”,或手机的“运行内存”
RAM 用于存储正在执行的程序和数据
RAM 是 CPU 与其他所有外部设备之间交换数据的桥梁
RAM 一旦断电,所有数据都会消失
RAM 容量越大,则整个硬件系统运行过程中,表现的越流畅

【JVM中的内存】
JVM 在运行 JAVA 程序时,会把内存区分为栈内存(stack)和堆内存(heap)
栈内存用于存储级别数据类型的变量与值、引用数据类型的引用、参数的副本
堆内存用于存储引用数据类型的数据

除了基本数据类型外,其他都是引用类型,例如 InputStream 和自己写的类全是引用类型

【关于参数的副本】

public class Main {
    public static void main(String[] args) {
        int i = 8;
        add(i);//i->8

        int[] arr = {9, 5, 2, 7};
        add(arr);//arr[0]->10
    }

    public static void add(int x) {
        x++;
    }

    public static void add(int[] array) {
        array[0]++;
    }
}

i 在栈内存,调用add()方法时,会在栈内存中增加一个i的副本,执行方法时,用参数的副本完成运算,完成后,副本消失,i还是原来的 8。

而数组arr存在与堆内存中,它的内存地址是存在栈内存的,根据内存地址找到数组,在执行add()方法时,会产生一个副本,也就是在栈内存中再复制它的一个内存地址,根据这个地址它找到的还是数组arr,所以数据变了,虽然副本消失了。

内存溢出问题

正在执行的程序是在内存里的,也就是说,我们现在展示的图片也在内存中,那么一张图片到底会占多少内存呢?

可以用以下公式计算

也就是说这张图片占的二进制位是 1280 x 1024 x 24 = 31457280

8 个二进制位占一个字节,所以 31457280 / 8 = 3932160

3932160是这张图加载到内存中占的字节的数量,换算成 kb 即3932160 / 1024 = 3840

换算成 M 即 3840 / 1024 = 3.75

然后我们可以看看一屏显示几张图片再做乘法运算,就可以粗略计算出一屏图片占用的内存。

所以这就涉及到内存溢出的问题了。内存溢出(OutOfMemoryError)简称 OOM,导致的原因是使用的内存超出了限制,如果内存占用太多就会出现 OutOfMemory 的 error 。例如我们上边显示相册的程序。当一次性显示多张高清图片时,很容易导致 Android APP 崩溃,可行的解决方案通常有:
1、压缩图片质量
2、缩小图片,即图片的封边率会更小
3、裁切,只显示图片的某个部分,例如滑动时再加载必要的部分的数据

我们选择第二种方法,来缩小图片,修改 ImageAdapter

public class ImageAdapter extends BaseAdapter<Image> {
    public ImageAdapter(Context context, List data) {
        super(context, data);
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            viewHolder = new ViewHolder();
            view = getInflater().inflate(R.layout.item_image, null);
            viewHolder.imageView = view.findViewById(R.id.img_item);
            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }
        Image image = getData().get(i);
        Log.d("Image", "id:" + image.getId() + ";data:" + image.getData() + ";size:" + image.getSize() +
                ";displayname:" + image.getDisplayName() + ";dataTaken:" + image.getDateTaken() + ";尺寸:" + image.getWidth() +
                "x" + image.getHeight());
        //判断并计算图片的缩放比
        //200单位是px
        int rate = 1;
        if (image.getWidth() > IMAGE_MAX_SIZE && image.getHeight() > IMAGE_MAX_SIZE) {
            if (image.getWidth() < image.getHeight()) {
                rate = image.getWidth() / IMAGE_MAX_SIZE;
            } else {
                rate = image.getHeight() / IMAGE_MAX_SIZE;
            }
        }
        //创建解码图片选项参数
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inSampleSize = rate;
        //解码图片得到图片的bitmap对象
        Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
        Log.d("Image", "解码完成,bitmap尺寸为" + bitmap.getWidth() + "x" + bitmap.getHeight() + "bitmap占用内存:" + bitmap.getByteCount());
        viewHolder.imageView.setImageBitmap(bitmap);
        return view;
    }

    private static final int IMAGE_MAX_SIZE = 200;

    class ViewHolder {
        ImageView imageView;
    }
}

如果 rate = 4,那么图片将被缩小为原来的 1/4,如果传入了一个比1小的值,程序处理时按 1 处理。

运行程序,查看 Log

>id:9370;data:/storage/emulated/0/DCIM/Camera/IMG_20201212_091204.jpg;size:3741069;displayname:IMG_20201212_091204.jpg;dataTaken:1607735525530;尺寸:3472x4624
解码完成,bitmap尺寸为204x272bitmap占用内存:221952

卡的问题

我们现在运行程序会发现图片上下滑动会很卡:
在这里插入图片描述
卡的原因只有一个就是主线程太卡,因此我们需要开线程,我们可以利用 AsyncTask 解决卡的问题。如果对线程不了解可以学习线程系列文章:---->传送门<----

修改 ImageAdapter

public class ImageAdapter extends BaseAdapter<Image> {
    public ImageAdapter(Context context, List data) {
        super(context, data);
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            viewHolder = new ViewHolder();
            view = getInflater().inflate(R.layout.item_image, null);
            viewHolder.imageView = view.findViewById(R.id.img_item);
            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }

        Image image = getData().get(i);
        Log.d("Image", "id:" + image.getId() + ";data:" + image.getData() + ";size:" + image.getSize() +
                ";displayname:" + image.getDisplayName() + ";dataTaken:" + image.getDateTaken() + ";尺寸:" + image.getWidth() +
                "x" + image.getHeight());

        //创建任务
        LoadImageTask task = new LoadImageTask(image, viewHolder.imageView);
        task.execute();
        //viewHolder.imageView.setImageBitmap(bitmap);

        return view;
    }

    private static final int IMAGE_MAX_SIZE = 200;

    class ViewHolder {
        ImageView imageView;
    }

    //参数;是否更新进度;结果
    private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
        private Image image;
        private ImageView imageView;

        public LoadImageTask(Image image, ImageView imageView) {
            super();
            this.image = image;
            this.imageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(Void... voids) {
            //判断并计算图片的缩放比
            //200单位是px
            int rate = 1;
            if (image.getWidth() > IMAGE_MAX_SIZE && image.getHeight() > IMAGE_MAX_SIZE) {
                if (image.getWidth() < image.getHeight()) {
                    rate = image.getWidth() / IMAGE_MAX_SIZE;
                } else {
                    rate = image.getHeight() / IMAGE_MAX_SIZE;
                }
            }

            //创建解码图片选项参数
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inSampleSize = rate;
            //解码图片得到图片的bitmap对象
            Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
            Log.d("Image", "解码完成,bitmap尺寸为" + bitmap.getWidth() + "x" + bitmap.getHeight() + "bitmap占用内存:" + bitmap.getByteCount());
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            imageView.setImageBitmap(bitmap);
        }
    }
}

运行程序:
在这里插入图片描述
可以看到现在下拉图片不卡了,但是出现了一个新的问题,图片闪。

图片闪烁

图片乱跳的原因是每次getView导致新的列表项重新进入屏幕,就会重新创建任务,执行任务

修改后的ImageAdapter

public class ImageAdapter extends BaseAdapter<Image> {
    public ImageAdapter(Context context, List data) {
        super(context, data);
    }

    private Map<View, LoadImageTask> tasks = new HashMap<View, LoadImageTask>();

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            ......
        } else {
            ......
        }
		viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
        Image image = getData().get(i);

        //尝试获取当前控件对应的任务
        LoadImageTask task;
        task = tasks.get(view);
        //判断当前是否存在任务
        if (task != null) {
            //当前存在任务,则取消现有任务
            task.cancel(true);
        }
        //创建新任务
        task = new LoadImageTask(image, viewHolder.imageView);
        //将新的任务添加到集合中
        tasks.put(view, task);
        //执行任务
        task.execute();
        return view;
    }
   	......
}

再次运行程序:
在这里插入图片描述
图片不会来回闪烁了,但是我们发现已经加载过的图片还是会重新加载,继续优化。

图片重复加载问题

Image 类中增加

	private Bitmap bitmap;

    public Bitmap getBitmap() {
        return bitmap;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

修改ImageAdapter

public class ImageAdapter extends BaseAdapter<Image> {
    ......

    //参数;是否更新进度;结果
    private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
        ......

        @Override
        protected Bitmap doInBackground(Void... voids) {
            //判断是否需要解码图片
            if (image.getBitmap() == null) {
                ......
                Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
                //将解码得到的Bitmap封装到图片数据中
                image.setBitmap(bitmap);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            imageView.setImageBitmap(image.getBitmap());
        }
    }
}

完整的 ImageAdapter 代码如下:

public class ImageAdapter extends BaseAdapter<Image> {
    public ImageAdapter(Context context, List data) {
        super(context, data);
    }

    private Map<View, LoadImageTask> tasks = new HashMap<View, LoadImageTask>();

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            viewHolder = new ViewHolder();
            view = getInflater().inflate(R.layout.item_image, null);
            viewHolder.imageView = view.findViewById(R.id.img_item);
            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }

        viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
        Image image = getData().get(i);

        //尝试获取当前控件对应的任务
        LoadImageTask task;
        task = tasks.get(view);
        //判断当前是否存在任务
        if (task != null) {
            //当前存在任务,则取消现有任务
            task.cancel(true);
        }
        //创建新任务
        task = new LoadImageTask(image, viewHolder.imageView);
        //将新的任务添加到集合中
        tasks.put(view, task);
        //执行任务
        task.execute();
        return view;
    }

    private static final int IMAGE_MAX_SIZE = 200;

    class ViewHolder {
        ImageView imageView;
    }

    //参数;是否更新进度;结果
    private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
        private Image image;
        private ImageView imageView;

        public LoadImageTask(Image image, ImageView imageView) {
            super();
            this.image = image;
            this.imageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(Void... voids) {
            //判断是否需要解码图片
            if (image.getBitmap() == null) {
                //判断并计算图片的缩放比
                //200单位是px
                int rate = 1;
                if (image.getWidth() > IMAGE_MAX_SIZE && image.getHeight() > IMAGE_MAX_SIZE) {
                    if (image.getWidth() < image.getHeight()) {
                        rate = image.getWidth() / IMAGE_MAX_SIZE;
                    } else {
                        rate = image.getHeight() / IMAGE_MAX_SIZE;
                    }
                }

                //创建解码图片选项参数
                BitmapFactory.Options opts = new BitmapFactory.Options();
                opts.inSampleSize = rate;
                //解码图片得到图片的bitmap对象
                Bitmap bitmap = BitmapFactory.decodeFile(image.getData(), opts);
                //Log.d("Image", "解码完成,bitmap尺寸为" + bitmap.getWidth() + "x" + bitmap.getHeight() + "bitmap占用内存:" + bitmap.getByteCount());
                //将解码得到的Bitmap封装到图片数据中
                image.setBitmap(bitmap);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            imageView.setImageBitmap(image.getBitmap());
        }
    }
}

运行程序:
在这里插入图片描述
效果已经好了很多。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值