王学岗性能优化————内存优化(二)内存抖动与优化内存的良好编码习惯

第一:内存抖动
1,内存抖动的原因:内存频繁的分配与回收,(分配速度大于回收速度时)最终会产生OOM。
(1)我们看一段内存抖动的代码,开辟一个二维数组,频繁的拼接字符串。

 public void imPrettySureSortingIsFree() {
        int dimension = 300;
        int[][] lotsOfInts = new int[dimension][dimension];
        Random randomGenerator = new Random();
        for (int i = 0; i < lotsOfInts.length; i++) {
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                lotsOfInts[i][j] = randomGenerator.nextInt();
            }
        }

        for (int i = 0; i < lotsOfInts.length; i++) {
            String rowAsStr = "";
            //排序
            int[] sorted = getSorted(lotsOfInts[i]);
            //拼接打印
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                rowAsStr += sorted[j];
                if (j < (lotsOfInts[i].length - 1)) {
                    rowAsStr += ", ";
                }
            }
            Log.i("ricky", "Row " + i + ": " + rowAsStr);
        }
    }
    

在拼接字符串的时候还进行一个克隆操作。对象克隆会不断的产生内存块。

 public int[] getSorted(int[] input){
            int[] clone = input.clone();
            Arrays.sort(clone);
            return clone;
        }

点击profile运行,这次我们主要看Allocations(某一段时间开辟内存对象的个数) 和Deallocations(某一段时间回收的内存个数)两个字段。
运行profile的时候,你会发现gc(垃圾回收图像)图标频繁出现。
解决方法:使用StringBuffer StringBuilder
(2)WebView造成的内存抖动

 WebView webView = (WebView) findViewById(R.id.webview);
        webView.getSettings().setUseWideViewPort(true);
        webView.getSettings().setLoadWithOverviewMode(true);
        webView.loadUrl("file:///android_asset/shiver_me_timbers.gif");

解决方法:开启独立进程。开启一个单独的进程运行当前的Activity。在文件清单中,找到该Activity,加入进程标识。

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

建议写了WebView的Activity单独开启一个进程。我们打开QQ微信京东等,会发现它们会有很多进程,就是因为WebView的缘故。

2,回收算法:
分带手机算法
第二:优化内存的良好编码习惯
1,数据类型:不要使用比需求更占空间的基本数据类型。比如能用int的不要用long,能用整数的就不要用小数。
2,循环尽量用foreach,少用iterator, 自动装箱尽量少用
3.数据结构与算法的解度处理;
(1)递归算法改成for循环
(2)尽量少用HashMap,数据量千级以内可以使用Sparse数组(key为整数),ArrayMap(key为对象)性能不如HashMap但节约内存。以时间换空间。
Sparse以整数作为键,不存在装箱的问题。ArrayMap的键为对象。
4枚举优化:
这是一段枚举代码

public enum SHAPE{
    RECTANGLE,
    TRIANGLE,
    SQUARE,
    CIRCLE
}

这里有四个值,就会在内存中开辟四个对象。对内存的消耗比较大
修改为

package com.example.wuyang.lsn4code;

import android.support.annotation.IntDef;
import android.support.annotation.StringDef;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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;

    //flag的真和假表示将来取的数据可以用或(|)来输入;比如这种形式RECTANGLE|TRIANGLE
	//value表示可选值,只允许我们定义的四个值
    @IntDef(flag=true,value={RECTANGLE,TRIANGLE,SQUARE,CIRCLE})
	//注解允许用在什么地方,我们这里允许写在参数,方法,成员变量
    @Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
	//注解的存活时间;编译阶段存在就行。
    @Retention(RetentionPolicy.SOURCE)
    public @interface Model{

    }
   
	//只有赋予我们规定的四个值
    private @Model int value=RECTANGLE;
	//限制用户传入的参数
    public void setShape(@Model int value){
        this.value=value;
    }
    @Model
    public int getShape(){
        return this.value;
    }
}

这样开辟一个对象就可以了。
在MainActivity中使用

 SHAPE s=new SHAPE();
 //因为flag=true,所以这里可以输入两个值
       s.setShape(SHAPE.CIRCLE|SHAPE.RECTANGLE);
       System.out.println(s.getShape());

5.static 与static final的问题。
static会由编译器调用clinit方法进行初始化
static final不需要进行初始化工作,打包在dex文件中可以直接调用,并不会在类初始化申请内存。所以基本数据类型的成员,可以全写成static final
6.字符串的连接尽量少用加号(+)
7.重复申请内存的问题
(1)同一个方法多次调用,如递归函数 ,回调函数中不要new对象,
(2)读流直接在循环中new对象等。不要再循环中new 对象,容易造成内存抖动。
(3)不要在onMeause() onLayout() onDraw() 中去刷新UI(比如requestLayout,Invalidate),这就造成了自己调用自己。
8.避免GC回收将来要重用的对象
比如ListView的优化就是这一点。
解决方法:内存设计模式对象池+LRU算法
我们这里写一个对象池,不断的复用一些东西。我们这里写一个类,以后读者若需要对象池可以重写我这个类。

	/**
	 *首先,在freePool中我们初始化2(initialCapacity)个对象,这时候来了个用户,需要用我对象池中的对象。
	 *就在freePool中申请一个对象,并且把这个申请到的对象放到lentPool中,这时候用户使用
	 *的,就是lentPool中的这个对象。第二个用户来用,就重复第一个用户的操作。接下来来第三个
	 *用户的时候,发现freePool中已经没有能用的对象,如果来的用户申请的对象不大于最大值(maxCapacity),
	 *就允许freePool在开一个对象给用户使用。如果lentPool中的对象(用户申请的在使用的对象)已经达到了
	 *最大值,那么该用户就拿不到对象,只能等其它用户释放后才能使用。联想数据库的连接池。recyclerView就是
	 *用的这个原理。
	 */
package com.example.wuyang.lsn4code;

import android.util.SparseArray;
//这里可以存放任何对象,我们用泛型
public abstract class ObjectPool<T> {
    //空闲沲,用户从这个里面拿对象
	//对象池保存的东西不多,所以使用SparseArray,不使用HashMap,为了节省内存。
	//freePool表示能用的对象池
    private SparseArray<T> freePool;
    //lentPool表示正在使用对象池,用户正在使用的对象放在这个沲记录
    private SparseArray<T> lentPool;

    //沲的最大值

    private int maxCapacity;

    public ObjectPool(int initialCapacity, int maxCapacity) {
        //初始化对象沲
        initalize(initialCapacity);
        this.maxCapacity=maxCapacity;
    }

    private void initalize(int initialCapacity) {
        lentPool=new SparseArray<>();
        freePool=new SparseArray<>();
        for(int i=0;i<initialCapacity;i++){
			//对象创建交给子类去实现
            freePool.put(i,create());
        }
    }

    /**
     * 申请对象
     * @return
     */
    public T acquire() throws Exception {

        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){
					//我们就把找到的对象放到另外一个(lentPool)池里
                    this.lentPool.put(key,t);
                    this.freePool.remove(key);
                    return t;
                }
            }
            //如果没对象可取了
            if(t==null && lentPool.size()+freeSize<maxCapacity){
                //这里可以自己处理,超过大小
                if(lentPool.size()+freeSize==maxCapacity){
                    throw new Exception();
                }
                t=create();
                lentPool.put(lentPool.size()+freeSize,t);


            }
        }
        return t;
    }

    /**
     * 回收对象
     * @return
     */
    public void release(T t){
        if(t==null){
            return;
        }
		//对象在池里面的索引,就是找该对象的键。
        int key=lentPool.indexOfValue(t);
        //释放前可以把这个对象交给用户处理
        restore(t);

        this.freePool.put(key,t);
        this.lentPool.remove(key);

    }

    protected  void restore(T t){

    };


    protected abstract T create();

    public ObjectPool(int maxCapacity) {
        this(maxCapacity/2,maxCapacity);
    }

}

我们创建自己的对象池

package com.example.wuyang.lsn4code;

public class MyObjectPool extends ObjectPool{
    public MyObjectPool(int initialCapacity, int maxCapacity) {
        super(initialCapacity, maxCapacity);
    }

    public MyObjectPool(int maxCapacity) {
        super(maxCapacity);
    }

    @Override
    protected Object create() {//LRU
        return new Object();
    }
}

看下在MainActivity中如何使用。

 MyObjectPool pool=new MyObjectPool(2,4);
        Object o1=pool.acquire();
        Object o2=pool.acquire();
        Object o3=pool.acquire();
        Object o4=pool.acquire();
        Object o5=pool.acquire();

        Log.i("jett",o1.hashCode()+"");
        Log.i("jett",o2.hashCode()+"");
        Log.i("jett",o3.hashCode()+"");
        Log.i("jett",o4.hashCode()+"");
        Log.i("jett",o5.hashCode()+"");

打印输出四个不同的hashCode值。第五个
在这里插入图片描述
9的第二点,和Activity有关联的对象不要写成static.
9的第三点,比如定义如下内部类

private class MyAsyncTask2 extends AsyncTask{
}

解决办法,使用弱引用,然后把这个类写成一个单独的文件

  private class MyAsyncTask2 extends AsyncTask{

        private WeakReference<Main2Activity> main2Activity;

        public MyAsyncTask2(Main2Activity main2Activity){
            this.main2Activity=new WeakReference<>(main2Activity);
        }

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

        private Object doSomeing() {
            //做了很多事后
            return new Object();
        }
    }

9的第四点
我们写一个包含回调函数的单例模式,代码如下

package com.example.administrator.lsn6_demo;

import android.view.View;
import android.widget.Button;

import java.lang.ref.WeakReference;

public class Singleton {
    private static Singleton singleton;
    private Callback callback;
    public static Singleton getInstance(){
        if(singleton==null){
            singleton=new Singleton();
        }
        return singleton;
    }



    public void setCallback(Callback callback){
        this.callback=callback;
    }
    public Callback getCallback(){
        return callback;
    }
    public interface Callback{
        void callback();
    }

}

MainActivity中调用

//这里使用了匿名内部类,会造成内存泄漏
  Singleton.getInstance().setCallback(new Singleton.Callback() {
            @Override
            public void callback() {
           //这里已经持有Activity的引用
           //单例是永不释放的东西,单例在 apk进程中是不会释放的,当然如果单例所属的进程没有了 
           // 单例也会被释放的

            }
        });

    }

单例模式改成如下

package com.example.administrator.lsn6_demo;

import android.view.View;
import android.widget.Button;

import java.lang.ref.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 void setCallback(Callback callback){
        this.callback=new WeakReference<>(callback);
    }
    public Callback getCallback(){
        return callback.get();
    }
    public interface Callback{
        void callback();
    }

}

9的第五点Handler的问题,有两个要注意的地方
第一个要注意的地方:
看一行Handler代码

 Handler handler=new Handler(Looper.myLooper());
 handler.sendMessage(new Message());

Loop的生命周期是与Application相同的,整个程序不退出,handler永远不释放,这也导致对应的Activity无法释放。
解决方法,弱引用+单独写一个类


class MyHandler extends Handler{
    private WeakReference m;
    public MyHandler(Main2Activity activity){
        m=new WeakReference(activity);
    }
    }

第二个要注意的地方

  m.post(new Runnable() {
           @Override
          public void run() {

           }
       }
       );

这又是一个非静态内部类泄漏的问题。解决方法,单独写一个类。
无论哪一种情况,都在在onDestroy()中这么写。

 @Override
    protected void onDestroy() {
        super.onDestroy();
        //清楚所有的回调和消息
        handler.removeCallbacksAndMessages(null);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值