关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android之批量加载图片OOM问题解决方案

    Android之批量加载图片OOM问题解决方案

    2015-04-03 00:00:00 出处:Mr.Simple的专栏
    分享

    一、OOM问题出现的场景和原因

    一个好的app总少不了精美的图片,所以Android开发中图片的加载总是避免不了的,而在加载图片过程中,假如处理不当则会出现OOM的问题。那么如何彻底解决这个问题呢?本文将具体介绍这方面的知识。

    首先我们来总结一下,在加载图片过程中出现的OOM的场景无非就这么几种:

    1、 加载的图片过大

    2、 一次加载的图片过多

    3、 以上两种情况兼有

    那么为什么在以上场景下会出现OOM问题呢?实际上在API文档中有着明确的说明,出现OMM的主要原因有两点:

    1、移动设备会限制每个app所能够使用的内存,最小为16M,有的设备分配的会更多,如24、32M、64M等等不一,总之会有限制,不会让你无限制的使用。

    2、在andorid中图片加载到内存中是以位图的方式存储的,在android2.3之后默认情况下使用ARGB_8888,这种方式下每个像素要使用4各字节来存储。所以加载图片是会占用大量的内存。

    场景和原因我们都分析完了,下面我们来看看如何解决这些问题。

    二、解决大图加载问题

    首先先来解决大图加载的问题,一般在实际应用中展示图片时,因屏幕尺寸及布局显示的原因,我们没有必要加载原始大图,只需要按照比例采样缩放即可。这样即节省内存又能保证图片不失真,具体实施步骤如下:

    1、在不加载图片内容的基础上,去解码图片得到图片的尺寸信息

    这里需要用的BitmapFactory的decode系列方法和BitmapFactory.Options。当使用decode系列方法加载图片时,一定要将Options的inJustDecodeBounds属性设置为true。

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(path, options);

    2、根据获取的图片的尺寸和要展示在界面的尺寸计算缩放比例。

    public 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) {
                if (width > height) {
                    inSampleSize = Math.round((float) height / (float) reqHeight);
                } else {
                    inSampleSize = Math.round((float) width / (float) reqWidth);
                }
            }
            return inSampleSize;
        }

    3、根据计算的比例缩放图片。

    //计算图片的缩放比例
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
     options.inJustDecodeBounds = false;
     Bitmap bitmap= BitmapFactory.decodeFile(path, options);

    根据缩放比例,会比原始大图节省很多内存,效果图如下:

    Android之批量加载图片OOM问题解决方案

    三、批量加载大图

    下面我们看看如何批量加载大图,首先第一步还是我们上面所讲到的,要根据界面展示图片控件的大小来确定图片的缩放比例。在此我们使用gridview加载本地图片为例,具体步骤如下:

    1、通过系统提供的contentprovider加载外部存储器中的所有图片地址

         private void loadPhotoPaths(){
            Cursor cursor= getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
            while(cursor.moveToNext()){
                String path = cursor.getString(cursor.getColumnIndex(MediaColumns.DATA));
                paths.add(path);
            }
            cursor.close();
        }

    2、自定义adapter,在adapter的getview方法中加载图片

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder=null;
            if(convertView==null){
                convertView = LayoutInflater.from(this.mContext).inflate(R.layout.grid_item_layout, null);
                holder = new ViewHolder();
                holder.photo=(ImageView)convertView.findViewById(R.id.photo);
                convertView.setTag(holder);
            }else{
                holder=(ViewHolder)convertView.getTag();
            }
    
            final String path = this.paths.get(position);
            holder.photo.setImageBitmap(imageLoader.getBitmapFromCache(path));
            return convertView;
        }

    通过以上关键两个步骤后,我们发现程序运行后,用户体验特别差,半天没有反应,很明显这是因为我们在主线程中加载大量的图片,这是不合适的。在这里大家要将图片的加载工作放到子线程中进行,改造自定义的ImageLoader工具类,为其添加一个线程池对象,用来管理用于下载图片的子线程。

        private ExecutorService executor;
        private ImageLoader(Context mContxt) {
            super();
            executor = Executors.newFixedThreadPool(3);
        }
        //加载图片的异步方法,含有回调监听
        public void loadImage(final ImageView view,
                final String path,
                final int reqWidth,
                final int reqHeight,
                final onBitmapLoadedListener callback){
    
            final Handler mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                    case 1:
                        Bitmap bitmap = (Bitmap)msg.obj;
                        callback.displayImage(view, bitmap);
                        break;
    
                    default:
                        break;
                    }
                }
    
            };
    
            executor.execute(new Runnable() {
                @Override
                public void run() {
                        Bitmap bitmap = loadBitmapInBackground(path, reqWidth,
                                reqHeight);
                        putBitmapInMemey(path, bitmap);
    
                        Message msg = mHandler.obtainMessage(1);
                        msg.obj = bitmap;
                        mHandler.sendMessage(msg);
                }
            });
        }

    通过改造后用户体验明显好多了,效果图如下:

    Android之批量加载图片OOM问题解决方案

    虽然效果有所提升,但是在加载过程中还存在两个比较严重的问题:

    1、图片错位显示

    2、当我们滑动速度过快的时候,图片加载速度过慢

    经过分析原因不难找出,主要是因为我们时候holder缓存了grid的item进行重用和线程池中的加载任务过多所造成的,只需要对程序稍作修改,具体如下:

    Adapter中:

            holder.photo.setImageResource(R.drawable.ic_launcher);
            holder.photo.setTag(path);
            imageLoader.loadImage(holder.photo,
                    path, 
                    DensityUtil.dip2px(80),
                    DensityUtil.dip2px(80), 
                    new onBitmapLoadedListener() {
                @Override
                public void displayImage(ImageView view, Bitmap bitmap) {
                    String imagePath= view.getTag().toString();
                    if(imagePath.equals(path)){
                        view.setImageBitmap(bitmap);
                    }
                }
            });

    ImageLoader中:

    executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        String key = view.getTag().toString();
                        if (key.equals(path)) {
                            Bitmap bitmap = loadBitmapInBackground(path, reqWidth,
                                    reqHeight);
                            putBitmapInMemey(path, bitmap);
    
                            Message msg = mHandler.obtainMessage(1);
                            msg.obj = bitmap;
                            mHandler.sendMessage(msg);
                        }
                    }
                });

    为了获得更好的用户体验,我们还可以继续优化,即对图片进行缓存,缓存我们可以分为两个部分内存缓存磁盘缓存,本文例子加载的是本地图片所有只进行了内存缓存。对ImageLoader对象继续修改,添加LruCache对象用于缓存图片。

    private ImageLoader(Context mContxt) {
            super();
            executor = Executors.newFixedThreadPool(3);
            //将应用的八分之一作为图片缓存
            ActivityManager am=(ActivityManager)mContxt.getSystemService(Context.ACTIVITY_SERVICE);
            int maxSize = am.getMemoryClass()*1024*1024/8;
            mCache = new LruCache<String, Bitmap>(maxSize){
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes()*value.getHeight();
                }
            };
        }
    
        //存图片到缓存
        public void putBitmapInMemey(String path,Bitmap bitmap){
            if(path==null)
                return;
            if(bitmap==null)
                return;
            if(getBitmapFromCache(path)==null){
                this.mCache.put(path, bitmap);
            }
        }
    
        public Bitmap getBitmapFromCache(String path){
            return mCache.get(path);
        }

    在loadImage方法中异步加载图片前先从内存中取,具体代码请下载案例。

    四、总结

    总结一下解决加载图片出现OOM的问题主要有以下方法:

    1、 不要加载原始大图,根据显示控件进行比例缩放后加载其缩略图。

    2、 不要在主线程中加载图片,主要在listview和gridview中使用异步加载图片是要注意处理图片错位和无用线程的问题。

    3、 使用缓存,根据实际情况确定是否使用双缓存和缓存大小。

    小伙伴们看懂了嘛?想要自己测试的,可以点击“下载工程”运行测试哦!

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    微软已修复windows 7/windows 8.1媒体中心严重漏洞 用户可下载安

    卸载MySQL数据库,用rpm如何实现

    windows 7中使用网上银行或支付宝支付时总是打不开支付页面

    一致性哈希算法原理设计

    MySQL数字类型中的三种常用种类

    如何解决SQL Server中传入select语句in范围参数

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。