Android程序员Android知识

Android性能优化之内存优化

2017-03-29  本文已影响140人  Jenson_

上一章讲了Android性能优化之耗电优化
,感兴趣的可以看下。这一章来说说Android内存方面如何优化,虽说是讲内存优化但是并不涉及虚拟机底层原理,力求通俗易懂。

屏幕快照 2017-03-29 下午2.58.41.png

养成好习惯先上图。内存从状态上来说只有已使用和未使用两种。本章内存优化也从这两方面下手:已使用的内存如何保证虚拟机的顺利回收、未使用的内存如何在满足需求的情况下尽量小的申请。

如何保证已使用内存顺利被回收?

以上是Java对象生命周期的简要介绍,要保证内存顺利回收,正确使用Java对象生命周期很重要,如果不能及时回收,我们就称之为“发生了内存泄露”。

在不可见阶段,程序本身不再持有对象强引用,但对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。

垃圾回收.jpeg

图中灰色的孤立无援的对象对于GC Roots来说不可达,会被回收。知道了内存泄露会影响回收,下面说下哪些方面会导致内存泄露

public class MainActivity extends AppCompatActivity {
public static People people;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        people = new People();
    }
    class  People{
        int age ;
        String name ;
    }
}

非静态内部类People持有外部类即当前Activity的引用,而该非静态内部类实例又是static修饰的,导致Activity一直被持有而不得释放,最终导致Activity所包含的view不能释放,如果view tree中包含多图片,那泄露的内存是很大的。

handler引起的内存泄露一般是临时性的,因为消息队列里的Message在延时到时间或者某一情况激活后还是会执行的,除非你是故意搞事情。创建handler时最好使用静态内部类,同时在Activity退出时执行handler.removeCallbacksAndMessages(null);清空队列消息

如何尽量小的申请内存?

上面说完了如何保证GC顺利回收,现在来讲讲要最小使用内存应该怎么做:

        Integer num=0;
        for (int i=0;i<100;i++) {
            num+=i;
        }```
Java基本数据类型是有自动装箱机制的。每次执行循环都会发生一次装箱操作创建一个Integer对象,造成内存消耗。包括其他基本数据类型都有可能造成这种情况。
- 内存复用
  - 视图复用
在ListView中使用ViewHolder复用item组件,一方面节省内存,一方面提高滑动流畅性。都用过不多介绍。
  - 使用对象池
看过Handler、Looper、Message、MessageQueue这一套消息循环源码的同志应该知道里面的Message使用了对象池模式。
>对象池类似线程池, 首先初始化一个固定大小池子,每次创建对象时候先去池子中找有没有,如果有直接取出,如果没有new出来使用后还到池子里。这样便可达到对象复用的目的。
对象池模式适用于那些频繁使用创建的对象,比如一个聊天app,里面对象最多的恐怕就是聊天信息(每条聊天信息对应一个信息对象)。都知道对象的创建是很耗费时间和内存的,没事不要new着玩。如果每条消息都创建一个对象,那可想而知该APP的性能。

    对象池的使用也很简单,少量代码即可完成:

public class People {
private static final Pools.SynchronizedPool<People> sPool = new Pools.SynchronizedPool<People>(
20);//需要维持对象的数量
int age;
String name;

    public static People obtain() {
        People instance = sPool.acquire();
        return (instance != null) ? instance : new People();
    }
    public void recycle() {
        sPool.release(this);
    }
}
>注意:对象申请(obtain)和释放(recycle)成对出现,使用一个对象后一定要释放还给对象池。

  - Bitmap复用
如果设置了options.inBitmap属性,以后再使用带有该options参数的decode方法加载图片资源时,decode会尝试重用已存在的位图内存,这样节省了加载和分配的时间,同时也节省了内存空间。
>该属性从3.0开始引进,低版本不支持inBitmap,4.4系统之前只能重用大小相同的内存区域,4.4以后可以重用任何比所需内存小的区域。具体使用可参考[官网](https://developer.android.com/topic/performance/graphics/manage-memory.html)。
  - 纯色规则形状背景用Color Res代替图片
经常遇到一些按钮背景是纯色显示,比如选中状态背景变为纯灰,但是设计已经发来了切图用还是不用?大声say NO!如果背景使用图片来显示,那背景每个像素都要绘制。

    假设一个分辨率为100x100的图片,占用4通道。那该图片内存占用就是100x100x4 =4万Byte≈40KB;但是如果使用```        android:background="@color/colorAccent"```引用color值的方式,由于是纯色,只需渲染一个像素而其他像素复用这个像素值即可。这样只需要4Byte即完成了背景设置。



  - 选择合适数据类型

   - 使用ArrayMap替换HashMap

先看一下HashMap模型和ArrayMap模型:
![hashmap模型.jpg](http:https://img.haomeiwen.com/i1796052/3e3049cb811341c1.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![arraymap模型.jpg](http:https://img.haomeiwen.com/i1796052/0d979501c5d1358f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

>HashMap是一个散列链表,稀疏阵列导致内存稍大,而ArrayMap提供了和HashMap一样的功能,但是避免了内存过度开销。在执行插入或删除操作时,从性能上看ArrayMap比HashMap稍差,但是如果对象数很小,比如1000以内不用担心性能问题。如果想深入了解这2个的原理请自行搜索,这里不过多阐述。

  - 枚举替身来了
      JDK1.5就支持了枚举类型,使用Enum关键字定义。使用枚举类型很多时候出于参数类型安全迫不得已作出的选择。

      ```
  public String  getValue(int type){
            switch (type) {
                case 1:
                    break;
                case 2:
                    break;
                case 3:
                    break;
                default:
                    throw new IllegalArgumentException("不合法参数");
            }
    return "";
    }
  试想一下如果一个函数的参数为int type,函数处理时只用到了1,2,3三种值,如果是其他值就抛出异常,这无疑增加了程序的不稳定性,按以前此时最好的解决办法就是参数改为枚举类型,增加了限定也就提高了稳定性。但是枚举类型就是一把双刃剑,增加安全同时也大大增加了内存占用,尤其是在移动设备上,资源有限更应该注意内存节省。

 谷歌或许考虑到了这些问题,在提供的注解包里添加了注解方式检查类型安全,目前支持int和String两种,看下使用方式:

```
//1、先声明需要的类型常量值
public static final int TYPE_1 = 1;
public static final int TYPE_2 = 2;
//2、创建注解接口同时把上一步声明的常量囊括到这里
@IntDef({TYPE_1,TYPE_2})
@Retention(RetentionPolicy.SOURCE)
public @interface _TYPE{
    
}
//3、在函数参数中增加 注解接口名称
public String getValue(@_TYPE int type){
    switch (type) {
        case 1:
            break;
        case 2:
            break;
    }
    return "";
}

       经过上面的步骤,再调用getValue()函数时如果传入其他int则报错编译不通过,这样通过注解就增加了安全性:

![屏幕快照 2017-03-29 下午7.13.32.png](http:https://img.haomeiwen.com/i1796052/62fa91a5486d42cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


结语:基本上APP大部分内存还是被图片占用,处理好图片尤为重要,但是关于图片三级缓存及缩放,目前都使用第三方框架如ImageLoader,所以这里一笔带过。以上就是日常内存优化需要注意的地方,自己做个总结,也希望能对各位看官有所帮助。
上一篇下一篇

猜你喜欢

热点阅读