文章目录

  • 一、内存抖动是什么?
  • 二、出现场景
  • 1.onDraw里频繁创建对象
  • 三、java回收机制
  • 1、什么是java回收机制呢?
  • 四、怎么避免内存抖动
  • 1、提升至成员变量,在其他位置初始化
  • 2、维护一个集合,集合中有就直接拿,没有才new
  • 3、善用单例模式和全局Application类
  • 五、预防内存泄漏



一、内存抖动是什么?

示例:内存抖动是指内存频繁地分配和回收,而频繁的 GC 会导致卡顿,严重时和内存泄漏一样会导致 OOM。

二、出现场景

1.onDraw里频繁创建对象

代码如下(示例):

@Override
    protected void onDraw(Canvas canvas) {
        Paint paint = new Paint();
    }

由于onDraw在绘图时会被频繁调用,new Paint()也会跟着被创建,浪费性能,同时屏幕UI界面可能会不是很顺滑(由于垃圾回收器机制的影响)。这样Paint对象被频繁的创建达到一定程度就会触发Java gs垃圾回收机制。对象被回收,被回收后由于onDraw在绘图时会被频繁调用new Paint()也会跟着被创建内存又会达到峰值又会触发 java gs垃圾回收机制。这种起起伏伏的象限被称为内存抖动。

三、java回收机制

1、什么是java回收机制呢?

在开发中,必然会new对象,一般为了方便,很可能我们在每次要用的时候,直接就new对象拿来用,这样既方便又省事。同时,又由于内存垃圾回收器的机制,一般情况下可以让我们不必担心new对象会产生内存溢出的问题(相对于C语言每次要考虑用完后释放,省去了很多麻烦)电脑的内存大小的不变的,当我们使用对象的时候,如使用New关键字的时候,就会在内存中生产一个对象,但是我们在使用JAVA开发的时候,当一个对象使用完毕之后我们并没有手动的释放那个对象所占用的内存,就这样在使用程序的过程中,对象越来越多,当内存存放不了这么多对象的时候,电脑就会崩溃了,JAVA为了解决这个问题就推出了这个自动清除无用对象的功能,或者叫机制,这就是GC,有个好听是名字叫垃圾回收,其实就在用来帮你擦屁股的,好让你安心写代码,不用管内存释放,对象清理的事情了

四、怎么避免内存抖动

1、提升至成员变量,在其他位置初始化

针对于这种需要重复使用的对象,我们应该提高其复用性,而不是每次都new。所以,此例中你可以将Paint提升为成员变量,先在构造函数中初始化,如下:

private Paint mPaint;
 
    public MyView(Context context) {
        super(context);
        mPaint = new Paint();
    }

onLayout也是同理。同时,你还需要注意开发中的其他需要多次执行的代码块,“是不是有必要每次都new对象呢?“这块代码是否可以复用呢?”等等。


2、维护一个集合,集合中有就直接拿,没有才new

开发中有很多资源都会被频繁重复使用(例如同一张处理后的图片),我们每次都重新去处理再设置,是相当不合理的。既然我们之前已经处理过了,那么我们就应该拿一个集合(例如HashMap)将他们维护起来,需要的时候直接从集合中取出就可以了,而不是每次都去重新处理、重新new对象。

现在App开发中最常见的界面就是通过Fragment实现卡片式布局(底部有一排按钮,点击切换不同页面),而Fragment一般都是有好几个的,一个Fragment中有很多代码需要处理,对于Fragment就应该使用集合进行维护,而不是每次都new一个Fragment对象返回。

具体如何实现呢?我举出一个FragmentFactory的例子供大家参考,如下:

public class FragmentFactory {
 
//    private static HashMap<Integer, BaseFragment> mFragmentMap = new HashMap<>();
    private static SparseArray<BaseFragment> mFragmentArray = new SparseArray<>();
    //数量不大,key为int类型时,效果比HashMap更好
 
    public static BaseFragment createFragment(int pos) {
        BaseFragment fragment = mFragmentArray.get(pos);//先从集合中取
 
        if (fragment == null) {//如果集合中没有,才创建
            switch (pos) {
                case 0:     //首页
                    fragment = new HomeFragment();
                    break;
                case 1:     //应用
                    fragment = new AppFragment();
                    break;
                case 2:     //游戏
                    fragment = new GameFragment();
                    break;
                case 3:     //专题
                    fragment = new SubjectFragment();
                    break;
                case 4:     //推荐
                    fragment = new RecommendFragment();
                    break;
            }
            mFragmentArray.put(pos, fragment);//将fragment保存在集合中
        }
        return fragment;
    }
}

onLayout也是同理。同时,你还需要注意开发中的其他需要多次执行的代码块,“是不是有必要每次都new对象呢?“这块代码是否可以复用呢?”等等。
完成上述FragmentFactory类后,以后使用Fragment时,你只需要调用FragmentFactory.createFragment(position)即可。 ( 另外推荐大家查询一下SparseArray,SparseArray是android提供的一个工具类,在android.util下。它在key为int类型、低数量的条件下,比HashMap使用起来效果更佳。)
这样,如果集合中存在就取,不存在才会去新建一个,而不是每次都new对象了。另外,像对于图片集合的维护,这种方式也很常用。(图片缓存的维护还涉及到软引用、弱引用,LruCache缓存策略,在后面的内容中会讲到)


3、善用单例模式和全局Application类

五、预防内存泄漏

1、数据类型,不要使用比需求更占空间的基本数据类型

2、循环尽量用foreach,少用iterator, 自动装箱尽量少用

3、数据结构与算法的解度处理
数据量千级以内可以使用
Sparse数组(key为整数),ArrayMap(key为对象)
性能不如HashMap但节约内存

4、枚举优化

缺点:

每一个枚举值都是一个单例对象,在使用它时会增加额外的内存消耗,所以枚举相比与 Integer 和 String 会占用更多的内存

较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的IO开销,使我们的应用需要更多的空间

特别是分dex多的大型APP,枚举的初始化很容易导致ANR

枚举可以进行改进

public enum SHAPE{
RECTANGLE,
TRIANGLE,
SQUARE,
CIRCLE
}
public class SHAPE{
public static final int RECTANGLE=0;
public static final int TRIANGLE=1;
public static final int SQUARE=2;
public static final int CIRCLE=3;
}

5、static staticfinal的问题

static会由编译器调用clinit方法进行初始化
static final不需要进行初始化工作,打包在dex文件中可以直接调用,并不会在类初始化申请内存
所以基本数据类型的成员,可以全写成static final

6、字符串的连接尽量少用加号(+)

7、重复申请内存的问题

同一个方法多次调用,如递归函数 ,回调函数中new对象,读流直接在循环中new对象等
不要在onMeause() onLayout() onDraw() 中去刷新UI(requestLayout)

8、避免GC回收将来要重用的对象

内存设计模式对象沲+LRU算法

Activity组件泄漏