android 内存优化 (图片篇)一

内存简介:

RAM(random access memory)随机存取存储器。说白了就是内存。
一般Java在内存分配时会涉及到以下区域:

寄存器(Registers):速度最快的存储场所,因为寄存器位于处理器内部,我们在程序中无法控制

栈(Stack):存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中

堆(Heap):堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器(GC)来管理。

静态域(static field):  静态存储区域就是指在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量

常量池(constant pool):虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。

非RAM存储:硬盘等永久存储空间

凑着这个机会简单介绍一下

栈和堆特点的对比:

栈:当定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

堆:当堆中的new产生数组和对象超出其作用域后,它们不会被释放,只有在没有引用变量指向它们的时候才变成垃圾,不能再被使用。即使这样,所占内存也不会立即释放,而是等待被垃圾回收器收走。这也是Java比较占内存的原因。
 

:存取速度比堆要快,仅次于寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

:堆是一个运行时数据区,可以动态地分配内存大小,因此存取速度较慢。也正因为这个特点,堆的生存期不必事先告诉编译器,而且Java的垃圾收集器会自动收走这些不再使用的数据。

 

栈:栈中的数据可以共享, 它是由编译器完成的,有利于节省空间。

例如:需要定义两个变量int a = 3;int b = 3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再让a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并让a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

堆:例如上面栈中a的修改并不会影响到b, 而在堆中一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

OOM:

内存泄露可以引发很多问题:

1.程序卡顿,响应速度慢(内存占用高时,JVM虚拟机会频繁触发GC)

2.莫名消失(当你的程序所占内存越大,它在后台的存在的时间就越短。内存占用越小,在后台存在的时间就越长)

3.直接崩溃(OutOfMemoryError)

 

内存面临的问题:

1.有限的内存堆,原始只有16M

2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同

3.程序不能直接控制

4.支持后台多任务处理

5.运行在虚拟机之上

 

Bitmap:

Bitmap是内存消耗大户,绝大多数的OOM崩溃都是操作Bitmap时产生的。

图片显示:

比如列表中仅显示缩略图,点击具体条目时启动另一个页面显示整个图片。

图片大小:

直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能会导致崩溃

使用BitmapFactory.Options设置inSampleSize,这样做可以减少对系统资源的要求。

属性值inSampleSize表示缩略图大小为原始图片的几分之一,即如果为2,则提出的缩略图的宽和高就是原始图片的二分之一,图片大小为原始大小的四分之一。

 

        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占用的内存空间,不要等系统来进行释放。

        // 先判断是否已经回收
        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;
        }
 

 

重用

核心思路就是将已经存在的内存资源重新使用而避免去创建新的,最典型的使用就是缓存(Cache池(Pool)

Bitmap缓存:

Bitmap缓存分为两种:

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

内存缓存(LruCache):

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

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

硬盘缓存(DiskLruCache):

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

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

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

更多关于内存缓存和硬盘缓存的内容请看Google官方教程https://developer.android.com/develop/index.html

图片缓存的开源项目:
对于图片的缓存现在都倾向于使用开源项目,这里我列出几个我搜到的:

1. Android-Universal-Image-Loader 图片缓存

目前使用最广泛的图片缓存,支持主流图片缓存的绝大多数特性。
项目地址:https://github.com/nostra13/Android-Universal-Image-Loader

 

2. picasso square开源的图片缓存
项目地址:https://github.com/square/picasso
特点:(1)可以自动检测adapter的重用并取消之前的下载
(2)图片变换
(3)可以加载本地资源
(4)可以设置占位资源
(5)支持debug模式

 

3. ImageCache 图片缓存,包含内存和Sdcard缓存
项目地址:https://github.com/Trinea/AndroidCommon
特点:

(1)支持预取新图片,支持等待队列
(2)包含二级缓存,可自定义文件名保存规则
(3)可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法
(4)可方便的保存及初始化恢复数据
(5)支持不同类型网络处理
(6)可根据系统配置初始化缓存等


4. 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结束时同时取消所有网络请求)

Adapter适配器

在Android中Adapter使用十分广泛,特别是在list中。所以adapter是数据的 “集散地” ,所以对其进行内存优化是很有必要的。
下面算是一个标准的使用模版:
主要使用convertView和ViewHolder来进行缓存处理
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder vHolder = null;
        //如果convertView对象为空则创建新对象,不为空则复用  
        if (convertView == null) {
            convertView = inflater.inflate(..., null);
            // 创建 ViewHodler 对象  
            vHolder = new ViewHolder();
            vHolder.img= (ImageView) convertView.findViewById(...);
            vHolder.tv= (TextView) convertView.findViewById(...);
            // 将ViewHodler保存到Tag中(Tag可以接收Object类型对象,所以任何东西都可以保存在其中)
            convertView.setTag(vHolder);
        } else {
            //当convertView不为空时,通过getTag()得到View  
            vHolder = (ViewHolder) convertView.getTag();
        }
        // 给对象赋值,修改显示的值  
        vHolder.img.setImageBitmap(...);
        vHolder.tv.setText(...);
        return convertView;
    }
    //将显示的View 包装成类  
    static class ViewHolder {
        TextView tv;
        ImageView img;
    }


池(PooL)

对象池:

对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

线程池:
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。

java提供了ExecutorService和Executors类,我们可以应用它去建立线程池。

通常可以建立如下4种:

/** 每次只执行一个任务的线程池 */
ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();
 
/** 每次执行限定个数个任务的线程池 */
ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);
 
/** 所有任务都一次性开始的线程池 */
ExecutorService allTaskExecutor = Executors.newCachedThreadPool();
 
/** 创建一个可在指定时间里执行任务的线程池,亦可重复执行 */
ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);

更多关于线程池的内容我推荐这篇文章:http://www.xuanyusong.com/archives/2439

注意:
要根据情况适度使用缓存,因为内存有限。

能保存路径地址的就不要存放图片数据,不经常使用的尽量不要缓存,不用时就清空。

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值