第一:内存抖动
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);
}
}