zoukankan      html  css  js  c++  java
  • 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结束时同时取消所有网络请求)
     
  • 相关阅读:
    《JavaWeb从入门到改行》JSP+EL+JSTL大杂烩汤
    Linux下进程线程,Nignx与php-fpm的进程线程方式
    solr全文检索实现原理
    LSM树以及在hbase中的应用
    MySQL的MyISAM与InnoDB的索引方式
    MySQL的innoDB存储引擎的运作方式,数据结构等
    Redis作缓存
    Redis的几点积累
    Redis数据库各种数据结构的内部实现。
    正则表达式!!!
  • 原文地址:https://www.cnblogs.com/mafangfang/p/5434668.html
Copyright © 2011-2022 走看看