美文网首页
Android 内存优化 (二)

Android 内存优化 (二)

作者: 一航jason | 来源:发表于2017-04-12 22:47 被阅读0次

    Bitmap内存优化:
    Bitmap是内存消耗大户,绝大多数的OOM崩溃都是在操作Bitmap时产生的,下面来看看如何几个处理图片的方法:

    图片显示优化:
    图片服务器配合加载对应尺寸的图片。
    根据屏幕尺寸和用户体验地最低值选择图片压缩尺寸和质量。

    图片大小优化:
    直接使用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();  
    
    

    另外也可以使用lru算法来控制bitmap对内存占用。

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

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

    Java四种引用类型:

    强引用(strong reference)
    如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解

    决内存不足问题。
    软引用(SoftReference)
    只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;
    弱引用(WeakReference)
    弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
    虚引用(PhantomReference)
    "虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收

    Android 2.3 引用回收变化:

    注意:对于SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收

    ,那么依靠软、弱引用来优化内存回收已经不可靠了。

    到底什么时候使用软引用,什么时候使用弱引用呢?

    个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
    还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。
    另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用

    ReferenceQueue实现的这种机制。

    static final修饰符使用的好处

    让我们来看看这两段在类前面的声明:
    static int intVal = 42;
    static String strVal = "Hello, world!";
    编译器会生成一个叫做clinit的初始化类的方法,当类第一次被使用的时候这个方法会被执行。方法会将42赋给intVal,然后把一个指向类中常量表 的引用赋给strVal。当以后要用到这些值

    的时候,会在成员变量表中查找到他们。 下面我们做些改进,使用“final”关键字:
    static final int intVal = 42;
    static final String strVal = "Hello, world!";
    现在,类不再需要clinit方法,因为在成员变量初始化的时候,会将常量直接保存到类文件中。用到intVal的代码被直接替换成42,而使用strVal的会指向一个字符串常量,而不是使用成员

    变量。
    将一个方法或类声明为final不会带来性能的提升,但是会帮助编译器优化代码。举例说,如果编译器知道一个getter方法不会被重载,那么编译器会对其采用内联调用。
    你也可以将本地变量声明为final,同样,这也不会带来性能的提升。使用“final”只能使本地变量看起来更清晰些(但是也有些时候这是必须的,比如在使用匿名内部类的时候)。

    静态方法代替虚拟方法

    如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。

    减少不必要的全局变量

    尽量避免static成员变量引用资源耗费过多的实例,比如Context因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。 你可以通过调

    用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。

    避免创建不必要的对象

    最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。
    对于所有所有基本类型的组合:int数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比 (int,int)对象数组性能要好很多。
    总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。

    避免内部Getters/Setters

    在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问

    避免使用浮点数

    通常的经验是,在Android设备中,浮点数会比整型慢两倍。

    使用实体类比接口好

    假设你有一个HashMap对象,你可以将它声明为HashMap或者Map:
    Map map1 = new HashMap();
    HashMap map2 = new HashMap();
    哪个更好呢?
    按照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式系统。调用一个接口的引用会

    比调用实体类的引用多花费一倍的时间。如果HashMap完全适合你的程序,那么使用Map就没有什么价值。如果有些地方你不能确定,先避免使用Map,剩下的交给IDE提供的重构功能好了。(当

    然公共API是一个例外:一个好的API常常会牺牲一些性能)

    避免使用枚举

    枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。
    使用枚举变量可以让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。但是当性能方面有所限制的时候,你就应该避免这种做法了。

    for循环已经foreach 循环

    在java1.5中引入的for-each语法。编译器会将对数组的引用和数组的长度保存到本地变量中,这对访问数组元素非常好。 但是编译器还会在每次循环中产生一个额外的对本地变量的存储操

    作(如下面例子中的变量a),这样会比普通循环多出4个字节,速度要稍微慢一些:
    使用for循环时,判断条件尽量使用局部变量和常量。

    了解并使用类库

    选择Library中的代码而非自己重写,除了通常的那些原因外,考虑到系统空闲时会用汇编代码调用来替代library方法,这可能比JIT中生成的等价的最好的Java代码还要好。
    当你在处理字串的时候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊实现的方法。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。
    System.arraycopy方法在有JIT的Nexus One上,自行编码的循环快9倍。
    android.text.format包下的Formatter类,提供了IP地址转换、文件大小转换等方法;DateFormat类,提供了各种时间转换,都是非常高效的方法。
    TextUtils类,对于字符串处理Android为我们提供了一个简单实用的TextUtils类,如果处理比较简单的内容不用去思考正则表达式不妨试试这个在android.text.TextUtils的类
    高性能MemoryFile类,很多人抱怨Android处理底层I/O性能不是很理想,如果不想使用NDK则可以通过MemoryFile类实现高性能的文件读写操作。MemoryFile适用于哪些地方呢?对于I/O需要

    频繁操作的,主要是和外部存储相关的I/O操作,MemoryFile通过将 NAND或SD卡上的文件,分段映射到内存中进行修改处理,这样就用高速的RAM代替了ROM或SD卡,性能自然提高不少,对于

    Android手机而言同时还减少了电量消耗。该类实现的功能不是很多,直接从Object上继承,通过JNI的方式直接在C底层执行。

    内存缓存(LruCache):

    以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。系统提供的LruCache类是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的

    LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。
    注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃

    圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。

    硬盘缓存(DiskLruCache):

    一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行

    其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。
    在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是

    不可预知的。

    对象池:
    对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非所有对象都适合拿来池化―

    ―因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销

    可观的对象,池化技术就是提高性能的有效策略了。

    线程池:
    线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对

    象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
    比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程

    用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。

    相关文章

      网友评论

          本文标题:Android 内存优化 (二)

          本文链接:https://www.haomeiwen.com/subject/dnsvattx.html