Android性能优化3----内存优化

内存优化,常用到的工具有:DDMS、MAT、LeakCanary、LeakInspector

1、JVM
在这里插入图片描述
对于JVM的知识,在《JVM》中已经介绍了许多,就不在此介绍过多。

真正想要内存优化,就是从堆内存优化。

2、强软弱虚

(1)虚引用 PhantomReference [fæntəm]

虚引用不会影响对象的生命周期,但是可以让程序员知道,该对象什么时候被回收了。

public class PhantomReference<T> extends Reference<T> {
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        throw new RuntimeException("Stub!");
    }
    public T get() {
        throw new RuntimeException("Stub!");
    }
}

PhantomReference当中,存在一个ReferenceQueue队列,被回收的对象均存在这个队列中。

		ReferenceQueue<Object> queue = new ReferenceQueue<>();
        Object object = new Object();
        PhantomReference reference = new PhantomReference(object,queue);

        System.out.println("object=="+object);
        System.out.println("reference=="+reference.get());

        System.gc();
        System.out.println("queue=="+queue.poll());

结果:

object==java.lang.Object@5b80350b
//使用虚引用,get不到信息的
reference==null
queue==null

当将对象置为空之后,进行垃圾回收,被回收的对象信息将会保存在ReferenceQueue队列中(注意不是将对象存放在队列中,而是对象的信息)。

		object = null;
        System.out.println("object=="+object);
        System.out.println("reference=="+reference.get());

        System.gc();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("queue=="+queue.poll());

结果:

object==null
reference==null
queue==java.lang.ref.PhantomReference@5b80350b

(2)弱引用

弱引用在之前MVP架构中,为了防止内存泄漏,用WeakReference来引用View对象。

		ReferenceQueue<Object> queue = new ReferenceQueue<>();
        Object object = new Object();
        WeakReference<Object> reference = new WeakReference<>(object,queue);
        //获取虚引用对象使用
        System.out.println("reference:"+reference.get());
        System.out.println("queue:"+queue.poll());

结果:

//可以get到信息
reference:java.lang.Object@5b80350b
queue:null

当启动垃圾回收器的时候。

		object = null;
        //获取虚引用对象使用
        System.out.println("reference:"+reference.get());
        System.out.println("queue:"+queue.poll());

        System.gc();
        Thread.sleep(3000);
        System.out.println("reference:"+reference.get());
        System.out.println("queue:"+queue.poll());
reference:java.lang.Object@5b80350b
queue:null
reference:null
queue:java.lang.ref.WeakReference@5d6f64b1

所以,使用弱引用就是,只要垃圾回收器监测到了,就会被拿来回收。

弱引用和虚引用的区别:

弱引用和虚引用都是需要配合Reference队列来使用,将对象放入队列中,对于虚引用来说,放在PhantomReference 中的对象是get不到的,所以它无法影响对象的生命周期,但是可以知道对象是什么时候被回收的;而弱引用在WeakReference中是可以被get到的,所以对弱引用对象的生命周期会造成影响,当gc检测到弱引用对象的时候,会被立刻回收(前提是已经执行了finalize方法)。

所以软、弱、虚引用的适用场景:

软引用:处理后台信息,软引用是内存不足才会被回收,所以在处理后台信息时,如果系统内存不足的时候,就选择清理这些后台保存信息;如果内存充足,则可以保存这些信息。

弱引用:GC一来就会被回收;
虚引用:跟踪GC。

3、内存泄漏优化

这里会使用到的工具:Profiler、MAT

(1)Profiler工具
在这里插入图片描述
点击Profiler工具,就可以看到当打开APP的时候,内存动态变化的趋势。
在这里插入图片描述
其中有一些参数需要注意:
(1)Total Count:该实例的总个数
(2)Shallow Size:该实例在内存中的字节数。

在这里插入图片描述
在分析一块内存之后,将这个分析结果导出,在内存文件中右击,选择“Export”导出。
在这里插入图片描述
将导出的文件,放到MAT工具中去分析。

(2)MAT工具

安装步骤:打开Eclipse软件,点击“Help”—“Install New SoftWare”,点击Add,将MAT依赖的地址添加进入http://download.eclipse.org/mat/1.6/update-site/
在这里插入图片描述
点击Next安装完成。

之前在Profiler中生成的快照内存文件是hprof格式的文件,需要使用下面这个工具,将其转换为MAT可以使用的文件
在这里插入图片描述
去到保存hprof文件的文件夹下,使用命令行工具:

hprof-conv -z 原文件名 mat文件名
在这里插入图片描述
导入hprof文件,就可以分析内存。
在这里插入图片描述
点击直方图,可以查看每个对象在内存 中的个数。
在这里插入图片描述
某个对象,点击右键查询与GcRoot引用的对象,就可以查看被谁引用。
在这里插入图片描述
通过直方图可以查看到,对象相互之间的引用关系。
在这里插入图片描述
android.view.inputmethod.InputMethodManager类中,存在两个对象分别为mServiceView和,NextServiceView,他会持有MainActivity的引用,那么我们可以在MainActivity调用onDestory方法之后,将GC链断掉,这样就不会持有MainActivity的引用,防止内存泄漏。

4、内存抖动

内存抖动指的是内存频繁地分配与回收,分配的速度大于回收的速度,最终导致OOM。

同样是通过Profiler工具,就可以定位到内存抖动的位置,如果出现频繁地垃圾回收,如下图那样,就是出现了内存抖动。
在这里插入图片描述
在项目开发过程中,出现内存抖动的场景,就是使用字符串的“+=”,在使用+=的时候,每次调用都会new StringBuilder,开辟一块内存,如果频繁地调用,那么就频繁地创建和销毁对象,最终可能导致内存溢出或者ANR异常。

解决方法:在循环外创建StringBuilder对象,在字符串连接的时候,采用append连接字符串。

另一种场景:在使用WebView的时候,就会出现内存泄漏和内存抖动的情况出现。
在这里插入图片描述
如果是按照上面的方式 去定位问题出现的位置,发现找不到,因为在app heap中的代码不是自己写的,都是系统的,那怎么去解决这个问题。

一般在使用WebView的时候,都是开一个进程,不在APP的主进程中做任何操作,这样就能解决内存抖动的问题。
在这里插入图片描述
在这里插入图片描述
将WebView所在的Activity单独开一个进程。

 <activity android:name=".view.activity.ArticleInfoActivity" android:process=":P" />

改进后的内存就变为比较顺滑了。
在这里插入图片描述
还有就是算法的问题,如果使用递归的方式,那么是非常消耗CPU的性能的,往往也会带来内存抖动的影响,因此考虑优化算法,减少内存抖动。

5、书写代码时注意内存优化

(1)数据类型的使用

举例intfloat

 @Test
    public void text(){
        long time = System.currentTimeMillis();
        int num = 0;
        for (int i = 0; i < 10000; i++) {
            num += i;
        }
        long now = System.currentTimeMillis();
        System.out.println("time="+(now - time));
    }

输出结果:time=5

 @Test
    public void textInt(){
        long time = System.currentTimeMillis();
        float num = 0;
        for (float i = 0; i < 1000000; i++) {
            num += i;
        }
        long now = System.currentTimeMillis();
        System.out.println("time="+(now - time));
    }

输出结果:time=8

(2)选择合适的数据结构和算法。

在使用HashMap时,不管是否存储数据,系统都会给HashMap开辟一块内存,因为默认底层数组容量大小是16,所以在数据量小于1000时,使用SparseArray或者ArrayMap代替HashMap能够减小系统内存开销。

在使用HashMap时,如果输入的key是int,那么会将其自动转换为Integer封装类,这是非常消耗性能的;在SparseArray中,保存key和value的是两个数据,分别为保存key的int数组,和保存value的Object数组,所以int的key不会转换为Integer。

ArrayMap同样也是可以代替HashMap的数据结构,在内部也是存在两个数据,一个int数组,存储item的hashcode,另一个数组Object[ ]数组用来存储key/value键值对,它的默认构造方法,默认是0,也就是不会占据内存空间。

但是SparseArray和ArrayMap在查找索引时都是通过二分查找,当数据量特别大的时候,性能就会减半,所以只有在小数据量的情况下,用来代替HashMap,提高内存效率。

(3)枚举优化

在项目开发过程中,如果存在4个状态需要选择,那么往往会采用枚举的形式。

 public enum SHAPE{
        OVAL,
        RECT,
        CIRCLE,
        SQUARE
    }

但是在使用枚举时,系统会开4个栈,这样是非常消耗系统内存,性能很低,所以通常采用自定义注解 + static final的形式来优化。

public class Shape {
    public static final int OVAL = 0;
    public static final int RECT = 1;
    public static final int CIRCLE = 2;
    public static final int SQUARE = 3;

    //该注解,意味着使用Model注解的地方只能使用这4个状态值
    @IntDef(flag = true,value = {OVAL,RECT,CIRCLE,SQUARE})
    @Target(value = {ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Model{
        
    }
    private  @Model int value = OVAL;
    
    @Model
    public int getShape() {
        return value;
    }

    public void setShape(@Model int value) {
        this.value = value;
    }
}

在使用的时候,就可以自己设置取值,只能取Shape中的4个值,其他的值就会报错。

		Shape shape = new Shape();
        shape.setShape(Shape.SQUARE);
        System.out.println(shape.getShape());
		//可以使用或,这是因为@IntDef注解的flag设置为true
        shape.setShape(Shape.SQUARE | Shape.CIRCLE);

(4)static 和 static final

static变量,会在类加载的时候完成初始化,会申请方法区内存;
static final不会在类加载的时候初始化,打包到dex文件中可以直接使用,并不会在类初始化的时候申请内存。

因此基本数据类型可以使用static final来修饰。

(5)避免垃圾回收器回收将来可能会重用的对象

这种情况通常需要设计对象池,使用对象池缓存可重复使用的对象。

对象池的思想:可以设置对象池初始容量,存在两个队列,一个是空闲的Pool,一个是正在使用的对象Pool(LintPool),当外界申请某个对象的时候,如果空闲Pool中有对象可用,那么就拿到对象去LintPool中;如果空闲Pool没有可用对象,且没有达到maxCapacity,那么可以生成一个对象,拿到LintPool;反之,就等待有空闲对象即可。

public abstract class ObjectPool<T> {
    //存在两个队列
    private SparseArray<T> freePool;
    private SparseArray<T> lintPool;
    //对象池最大个数
    private int maxCapacity;


    public ObjectPool(int minCapacity,int maxCapacity){
        //首先初始化空闲池中的可用对象
        init(minCapacity);
        this.maxCapacity = maxCapacity;
    }

    //构造方法初始化
    public ObjectPool(int maxCapacity){
        this(maxCapacity/2,maxCapacity);
    }
    private void init(int minCapacity) {
        //实例化队列
        freePool = new SparseArray<>();
        lintPool = new SparseArray<>();
        //初始化可用对象
        for (int i = 0; i < minCapacity; i++) {
            freePool.put(i,create());
        }
    }

    protected abstract T create();

    //从freePool取出数据
    public T getObject(){
        T t = null;
        synchronized (freePool){
            int freeSize = freePool.size();
            for (int i = 0; i < freeSize; i++) {
                //取出可用的对象
                int key = freePool.keyAt(i);
                t = freePool.get(key);
                if(t != null){
                    //如果当前对象池中对象可用,就放到使用池
                    this.lintPool.put(key,t);
                    this.freePool.remove(key);
                    return t;
                }
            }
            //如果空闲池对象可用,空间大小没有超出maxCapacity
            if(t == null && freePool.size() + lintPool.size() < maxCapacity){
                t = create();
                this.lintPool.put(freePool.size() + lintPool.size(),t);
            }
        }
        return t;
    }

    //使用完毕从lingPool移除
    public void remove(T t){
        if(t == null){
            return;
        }
        int key = lintPool.indexOfValue(t);
        this.lintPool.remove(key);
        this.freePool.put(key,t);
    }
}

使用:

public class MyObject extends ObjectPool<Object> {
    public MyObject(int minCapacity, int maxCapacity) {
        super(minCapacity, maxCapacity);
    }
	//创建对象
    @Override
    protected Object create() {
        return new Object();
    }
}
 		MyObject object = new MyObject(2,4);
        Object object1 = object.getObject();
        Object object2 = object.getObject();
        Log.e("TAG","o1==="+object1.hashCode());
        Log.e("TAG","o2==="+object2.hashCode());
2020-03-26 09:25:33.805 481-481/com.example.myapplication E/TAG: o1===229109964
2020-03-26 09:25:33.805 481-481/com.example.myapplication E/TAG: o2===94887701

在构造方法中初始化两个对象,打印出两个对象的hashcode,如果获取4个对象,那么就会输出最大空间的4个对象。

2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o1===229109964
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o2===94887701
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o3===229675562
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o4===120797211

(6)避免重复申请内存

在循环中创建对象,或者在频繁调用的方法中创建对象,例如onDraw方法。

(7)不要使用非静态内部类和匿名内部类

这种情况会持有Activity的引用,导致内存泄漏,简单说一下匿名内部类。

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        //这个地方会标黄,意味着会发生内存泄漏
        new AysncTask().execute();
    }
    
    private class AysncTask extends AsyncTask{

        @Override
        protected Object doInBackground(Object[] objects) {
            return doSomething();
        }

        private Object doSomething() {
            return new Object();
        }
    }
}

AysncTask 是一个匿名内部类,这个类必定会持有Main2Activity 的引用,导致内存泄漏,这种情况如何去优化?

  new AysncTask(this).execute();
    }

    private class AysncTask extends AsyncTask{

        private WeakReference<Main2Activity> weakReference;

        public AysncTask(Main2Activity main2Activity){
            weakReference = new WeakReference<>(main2Activity);
        }

        @Override
        protected Object doInBackground(Object[] objects) {
            return doSomething();
        }

        private Object doSomething() {
            return new Object();
        }
    }

采用弱引用的方式去修饰当前Activity。

(8)单例持有MainActivity的引用

public class Singleton {
    private static Singleton singleton;
    private Callback callback;
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
    
    public Callback getCallback(){
        return callback;
    }
    
    public void setCallback(Callback callback){
        this.callback = callback;
    }
    public interface Callback{
        void callback();
    }
}
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        new AysncTask(this).execute();

        Singleton.getInstance().setCallback(new Singleton.Callback() {
            @Override
            public void callback() {
                //此处会持有Activity的引用
            }
        });
    }

在Activity中使用setCallback的时候,就会持有Callback的引用,而且Callback不会被释放。

所以在使用Callback的时候,就需要使用WeakReference

public class Singleton {
    private static Singleton singleton;
//    private Callback callback;
    private WeakReference<Callback> callback;
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
    public WeakReference<Callback> getCallback(){
        return callback;
    }
    public void setCallback(Callback callback){
        this.callback = new WeakReference<>(callback);
    }
    public interface Callback{
        void callback();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Awesome_lay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值