Android学习之内存优化(一)—— 图片处理

在Android应用里,最耗费内存的就是图片资源。而且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,如果超出了,就会出现OutOfMemory异常。所以,对于图片的内存优化,是Android应用开发中比较重要的内容。

Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。

对于图片,内存优化中有两个手段,一是减少图片本身所占的内存、二是缓存经常使用的图片,避免重复创建Bitmap文件,增加内存的开支。

 一、减少

下面来看看几个处理图片的方法:

  图片显示:

  我们需要根据需求去加载图片的大小。

  例如在列表中仅用于预览时加载缩略图(thumbnails )。

  只有当用户点击具体条目想看详细信息的时候,这时另启动一个fragment/activity/对话框等等,去显示整个图片

  图片大小:

  直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃。 
  使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。 
  属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。

        BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
        bitmapFactoryOptions.inJustDecodeBounds = true;
        bitmapFactoryOptions.inSampleSize = 2;
        // 这里一定要将其设置回false,因为之前我们将其设置成了true
        // 设置inJustDecodeBounds为true后,decodeFile并不分配空间,即,BitmapFactory解码出来的Bitmap为Null,但可计算出原始图片的长度和宽度
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);

  图片像素:

  Android中图片有四种属性,分别是:
  ALPHA_8:每个像素占用1byte内存 
  ARGB_4444:每个像素占用2byte内存 
  ARGB_8888:每个像素占用4byte内存 (默认)
  RGB_565:每个像素占用2byte内存

  Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用  RGB_565(565没有透明度属性),如下:



   publicstaticBitmapreadBitMap(Contextcontext, intresId) {
            BitmapFactory.Optionsopt = newBitmapFactory.Options();
            opt.inPreferredConfig = Bitmap.Config.RGB_565;
            opt.inPurgeable = true;
            opt.inInputShareable = true;
            //获取资源图片
            InputStreamis = context.getResources().openRawResource(resId);
            returnBitmapFactory.decodeStream(is, null, opt);
        }

  图片回收:

  使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。

  下面是释放Bitmap的示例代码片段。

        // 先判断是否已经回收
        if(bitmap != null && !bitmap.isRecycled()){
            // 回收并且置为null
            bitmap.recycle();
            bitmap = null;
        }
        System.gc();

  捕获异常:

  经过上面这些优化后还会存在报OOM的风险,所以下面需要一道最后的关卡——捕获OOM异常:

        Bitmap bitmap = null;
        try {
            // 实例化Bitmap
            bitmap = BitmapFactory.decodeFile(path);
        } catch (OutOfMemoryError e) {
            // 捕获OutOfMemoryError,避免直接崩溃
        }
        if (bitmap == null) {
            // 如果实例化失败 返回默认的Bitmap对象
            return defaultBitmapMap;
        }

二、缓存

  Bitmap缓存分为两种:

  一种是内存缓存,一种是硬盘缓存。

  内存缓存(LruCache):

  以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。系统提供的LruCache类是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。

  注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案无效。

  硬盘缓存(DiskLruCache):

  一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能 局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打 断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。

在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。

  注意:如果访问图片的次数非常频繁,那么ContentProvider可能更适合用来存储缓存图片,例如Image Gallery这样的应用程序。

三、一些好用的开源框架

  1、 Android-Universal-Image-Loader 图片缓存(Git地址:https://github.com/nostra13/Android-Universal-Image-Loader)

这个开源库存在的特征:

  1. 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
  2. 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
  3. 支持图片的内存缓存,文件系统缓存或者SD卡缓存
  4. 支持图片下载过程的监听
  5. 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
  6. 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
  7. 提供在较慢的网络下对图片进行加载

在使用之前,我们先来了解一下Android-Universal-Image-Loader中的三大组件:ImageLoaderConfigurationImageLoader、DisplayImageOptions

博客(http://www.cnblogs.com/kissazi2/p/3886563.html)中有对这三者的详细解读。

        ImageLoaderConfiguration是针对图片缓存的全局配置,主要有线程类、缓存大小、磁盘大小、图片下载与解析、日志方面的配置。

        ImageLoader是具体下载图片,缓存图片,显示图片的具体执行类,它有两个具体的方法displayImage(...)、loadImage(...),但是其实最终他们的实现都是displayImage(...)。

        DisplayImageOptions用于指导每一个Imageloader根据网络图片的状态(空白、下载错误、正在下载)显示对应的图片,是否将缓存加载到磁盘上,下载完后对图片进行怎么样的处理。

下面是我的项目中实际使用到的例子:

ImgConfig .java(在这个文件中对img加载属性进行了统一的配置)
/**图片配置文件

*/public class ImgConfig {
    public static void initImgConfig(Context context) {
        File cacheDir =new File(StorageUtil.getDirByType(context, StorageUtil.TYPE_IMG_CACHE_DIR));       //ImageLoaderConfiguration是针对图片缓存的全局配置,主要有线程类、缓存大小、磁盘大小、图片下载与解析、日志方面的配置。
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
                context)
                .memoryCacheExtraOptions(480, 800)
                // max width, max height,即保存的每个缓存文件的最大长宽
                .threadPoolSize(3)
                // 线程池内加载的数量
                .threadPriority(Thread.NORM_PRIORITY - 2)
                .denyCacheImageMultipleSizesInMemory()
                .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024))
                // You can pass your own memory cache
                // 将保存的时候的URI名称用MD5 加密
                .tasksProcessingOrder(QueueProcessingType.LIFO)
                // 缓存的文件数量
                .diskCache(new UnlimitedDiskCache(cacheDir))
                // 自定义缓存路径
                .defaultDisplayImageOptions(DisplayImageOptions.createSimple())
                .imageDownloader(
                        new BaseImageDownloader(context, 5 * 1000, 30 * 1000))
                .writeDebugLogs() // Remove for release app
                .build();// 开始构建
        ImageLoader.getInstance().init(config);
    }
    //人物头像的加载    //DisplayImageOptions用于指导每一个Imageloader根据网络图片的状态(空白、下载错误、正在下载)显示对应的图片,是否将缓存加载到磁盘上,下载完后对图片进行怎么样的处理。
    public static DisplayImageOptions getPortraitOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_portrait_female_little)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_portrait_female_little)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_portrait_female_little)              //加载失败时的图片
        .cacheInMemory(true)                               //启用内存缓存
        .cacheOnDisk(true)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .build();
        return options;
    }
    //人物头像的加载
    public static DisplayImageOptions getPortraitLargeOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_portrait_fmale_large)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_portrait_fmale_large)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_portrait_fmale_large)              //加载失败时的图片
        .cacheInMemory(true)                               //启用内存缓存
        .cacheOnDisk(true)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .build();
        return options;
    }
    //大图的加载
    public static DisplayImageOptions getBigImgOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_big_img)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_big_img)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_big_img)              //加载失败时的图片
        .cacheInMemory(true)                               //启用内存缓存
        .cacheOnDisk(true)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .displayer(new RoundedBitmapDisplayer(20))         //设置显示风格这里是圆角矩形
        .build();
        return options;
    }
    //相册的加载
    public static DisplayImageOptions getAlbumImgOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_album)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_album)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_album)              //加载失败时的图片
        .cacheInMemory(true)                               //启用内存缓存
        .cacheOnDisk(true)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .bitmapConfig(Config.RGB_565)                 //设置图片编码格式
        .build();
        return options;
    }
    //相册的加载
    public static DisplayImageOptions getAlbumImgDefOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_album)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_album)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_album)              //加载失败时的图片
        .cacheInMemory(false)                               //启用内存缓存
        .cacheOnDisk(false)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .bitmapConfig(Config.RGB_565)
        .build();
        return options;
    }
    //Card图片的加载
    public static DisplayImageOptions getCardImgOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_big_img)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_big_img)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_big_img)              //加载失败时的图片
        .cacheInMemory(false)                               //启用内存缓存
        .cacheOnDisk(true)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .bitmapConfig(Config.ARGB_8888)
        .build();
        return options;
    }
    //BannerCard图片的加载
    public static DisplayImageOptions getBannerImgOption(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.default_banner_img)            //加载图片时的图片
        .showImageForEmptyUri(R.drawable.default_banner_img)         //没有图片资源时的默认图片
        .showImageOnFail(R.drawable.default_banner_img)              //加载失败时的图片
        .cacheInMemory(true)                               //启用内存缓存
        .cacheOnDisk(true)                                 //启用外存缓存
        .considerExifParams(true)                          //启用EXIF和JPEG图像格式
        .build();
        return options;
    }
}

使用:

//配置application

public void initConfig(BeautyDiaryApplication application) {
        synchronized (sLock) {
            this.application = application;
            context = application.getBaseContext();
            packageName = context.getPackageName();
            versionCode = getVersionCode(context, BeautyDiaryApplication.class);
            versionName = getVersionName(context, BeautyDiaryApplication.class);
            imei = Util.getImei(getBaseContext());
            try {
                sLock.notifyAll();
            } catch (Exception e) {
            }
            ImgConfig.initImgConfig(application);
        }
    }
//加载、展示图片//第一个参数: 图片url//第二个参数: 要设置在哪个view上//第三个参数: 加载图片配置(imgconfig中的方法)ImageLoader.getInstance().displayImage(StorageUtil.getPid2Url(entity.getPortrait(), StorageUtil.PIC_TYPE_LARGE),                                             portraitIv,ImgConfig.getPortraitLargeOption());

2、 Android 网络通信框架Volley

项目地址:https://android.googlesource.com/platform/frameworks/volley

我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在2013年的Google I/O发布了Volley。Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。

特点:

(1)JSON,图像等的异步下载;

(2)网络请求的排序(scheduling)

(3)网络请求的优先级处理

(4)缓存

(5)多级别取消请求

(6)和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)

 
时间: 2024-12-18 10:53:42

Android学习之内存优化(一)—— 图片处理的相关文章

Android学习之内存优化

本文部分内容来自http://blog.csdn.net/a396901990/article/details/37914465,感谢博主的分享,知识很系统很全面.   这两天在看Android的内存优化方面的知识,Android开发中最容易出现的就是内存泄露问题,因为Android内存是有限的,而且并不是很大,这就要求开发人员在开发客户端时要格外注意内存的优化,避免出现内存泄露. 那么内存泄露会引发哪些问题呢? 1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC) 2.程序莫名消

Android学习笔记进阶之在图片上涂鸦(能清屏)

Android学习笔记进阶之在图片上涂鸦(能清屏) 2013-11-19 10:52 117人阅读 评论(0) 收藏 举报 HandWritingActivity.java [java] view plaincopy package xiaosi.handWriting; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import andro

Android APP内存优化之图片优化

网上有很多大拿分享的关于Android性能优化的文章,主要是通过各种工具分析,使用合理的技巧优化APP的体验,提升APP的流畅度,但关于内存优化的文章很少有看到.在Android设备内存动不动就上G的情况下,的确没有必要去太在意APP对Android系统内存的消耗,但在实际工作中我做的是教育类的小学APP,APP中的按钮.背景.动画变换基本上全是图片,在2K屏上(分辨率2048*1536)一张背景图片就会占用内存12M,来回切换几次内存占用就会增涨到上百兆,为了在不影响APP的视觉效果的前提下,

android中的内存优化

内存泄露可以引发很多的问题: 1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC) 2.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉.反之内存占用越小,在后台存在的时间就越长) 3.直接崩溃(OutOfMemoryError) ANDROID内存面临的问题: 1.有限的堆内存,原始只有16M 2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同 3.程序不能直接控制 4.支持后台多任务处理(multitasking) 5.运行在虚拟机之上 我主要通过以下

关于android性能,内存优化(转载)

原文地址:http://www.cnblogs.com/zyw-205520/archive/2013/02/17/2914190.html   转自(http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost&gid=74&tid=20713&pg=1) 随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都

关于android性能,内存优化

随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要 求远远高于PC的桌面应用程序.以上理由,足以需要开发人员更加专心去实现和优化你的代码了.选择合适的算法和数据结构永远是开发人员最先应该考虑的事 情.同时,我们应该时刻牢记,写出高效代码的两条基本的原则:(1)不要做不必要的事:(2)不要分配不必要的内存. 我从去年开始接触Android开发,以下结合自己的一点项目经验,同时参考了Google的优化文档和网

android内存优化之图片优化

对图片本身进行操作.尽量不要使用setImageBitmap.setImageResource.BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存.因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的source,decodeStream最大的秘密在于其直接调用JNI>>nativeDeco

android内存优化之图片压缩和缓存

由于手机内存的限制和网络流量的费用现在,我们在加载图片的时候,必须要做好图片的压缩和缓存. 图片缓存机制一般有2种,软引用和内存缓存技术. 1.压缩图片:压缩图片要既不能模糊,也不能拉伸图片. 图片操作的时候,最常用的是BitmapFactory,现在看看如何压缩图片. Bitmapfactory.Options options= new BitmapFactory.Options(); options.inJustDecodeBounds = true;// 如果设置这个参数为ture,就不会

Redis学习(内存优化)

一.特殊编码: 自从Redis 2.2之后,很多数据类型都可以通过特殊编码的方式来进行存储空间的优化.其中,Hash.List和由Integer组成的Sets都可以通过该方式来优化存储结构,以便占用更少的空间,在有些情况下,可以省去9/10的空间. 这些特殊编码对于Redis的使用而言是完全透明的,事实上,它只是CPU和内存之间的一个交易而言.如果内存使用率方面高一些,那么在操作数据时消耗的CPU自然要多一些,反之亦然.在Redis中提供了一组配置参数用于设置与特殊编码相关的各种阈值,如:#如果