Android中,应用的性能优化是一个比较重要的问题,应用性能直接影响到用户的体验,应用的流畅度,崩溃率,流量使用量,耗电量,以及启动的快慢,内存使用等,都会严重影响用户的使用。
而内存优化更是Android应用性能优化中的重要部分,这篇文章就来记录总结一下Android应用的内存优化
首先先来了解一下两个概念:
内存泄漏:由于某种原因,导致程序中动态分配的堆内存,无法被释放,导致系统内存的浪费。
主要表现为长生命周期对象持有短生命周期对象引用,从而导致短生命周期对象无 法被回收。
内存抖动:程序运行过程中,频繁的创建和销毁对象,导致频繁GC,
从而引起应用卡顿。
主要表现为在循环中创建对象,导致短时间内有大量的对象创建和回收,
如果严重的话,就会导致应用卡顿。
下面来总结一下Android中内存泄漏的场景以及对应的解决方案:
1.单例引起的内存泄漏
由于单例的静态特性,导致单例的生命周期和整个应用的生命周期一样长,如果对象不再被使用,但是被单例持有引用的话,那么这个对象就没有办法被系统回收,导致内存泄漏。
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context) {
mContext = context;
}
public static SingleInstance getInstance(Context context) {
if (mInstance == null) {
mInstance = new SingleInstance(context);
}
return mInstance;
}
}
上面定义的单例类,持有了一个上下文对象,如果上下文对象是Activity,
那么当Activity被关闭时,单例对象会一直持有Activity的引用,系统是无法回收Activity对象的。
解决方法:如果不是必须要以Activity作为上下文对象传递给单例持有,我们可以使用 Application上下文对象,因为Application生命周期本来就是和整个应用一样长,如果非要持有Activity类型上下文对象,我们可以持有Activity的弱引用。
2.非静态内部类(包括匿名内部类)导致内存泄漏
由于非静态内部(包括匿名内部类)持有外部类的引用,;如果非静态内部类的实例的生命周期比外部类的生命周期长,就会导致内存泄漏。
//Thread
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread myThread = new MyThread();
myThread.start();
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
//耗时操作
Thread.sleep(10000)
}
}
}
//Handler
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler myHandler = new MyHandler();
myHandler.sendEmptyMessageDelayed(0, 10000);
}
public class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//操作
}
}
}
上面也是Android种两种比较常用的场景,线程Thread和Handler的使用,都是非静态内部类,创建了实例,那么当Activity被关闭的时候,如果Thread的或者Handler的操作没有结束,那么Activity就会被持有引用,无法被回收,导致内存泄漏。
解决方法:由于非静态内部类(包括匿名内部类)会持有外部内的引用,所以我们我们避免使用非静态内部类,改为使用静态内部类,如果需要外部类
的引用,可以持有弱引用的方式。 即静态内部类+弱引用的解决方法。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler myHandler = new MyHandler(new WeakReference(this));
myHandler.sendEmptyMessageDelayed(0, 10000);
}
public void doSomething() {
}
public static class MyHandler extends Handler {
private WeakReference mActivityWeakReference;
public MyHandler(WeakReference reference) {
mActivityWeakReference = reference;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//操作
if (mActivityWeakReference.get()!=null){
((MainActivity) mActivityWeakReference.get()).doSomething();
}
}
}
对于Handler我们也可以在Activity销毁时,从消息队列中移除所有的msg
来避免内存泄漏,即在onDestory()中移除Handler的发送的所有消息
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
myHandler.removeCallbacksAndMessages(null);
}
3.静态变量引起的内存泄漏
静态变量生命周期从类加载开始到整个应用进程结束,所以如果静态变量持有了Activity的引用,那么Activity被关闭后,系统也是无法回收Activity的
对象的,导致内存泄漏。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static Activity mActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActivity=this;
}
}
解决方法:最好不要让静态变量持有Activity的强引用。也可以通过持有弱引用的方式
来避免内存泄漏。
4.注册监听器,没有取消导致的内存泄漏
比如广播,注册了之后,要在Activity的onDestory()中取消注册。
5.各种资源未释放导致内存泄漏
如Cursor,IO等使用了之后都要及时关闭。
6.Bitmap使用之后没有调用recycle导致内存泄漏
Bitmap使用完之后,需要手动调用recycle()方法回收,并置为null,否则
无法被回收。
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
7.List集合中存放对象,不用的时候需要移除
List
8.还有像Timer,TimerTask,CountDownTimer等使用之后都要及时cancel,并置
成null。
@Override
protected void onDestroy() {
super.onDestroy();
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
mTimerTask.cancel();
mTimerTask = null;
}
}
以上就是一些内存泄漏的场景以及对应的解决方法。
下面再来看一下内存抖动的一些场景以及解决方法
前面我们已经说过,引起内存抖动主要是由于短时间内频繁的创建回收对象导致的。
比如在循环中创建对象,或者在可能频繁调用的方法中创建对象,都可能导致内存抖动。
所以如果可以避免,我们尽量不要在循环中创建对象,或者在可能频繁调用的方法中
创建对象。
1.如字符串的拼接:
String str="";
List userList=new ArrayList<>();
for (int i = 0; i < userList.size(); i++) {
if (i==userList.size()-1){
str=str+userList.get(i).getId();
}else{
str=str+userList.get(i).getId()+",";
}
}
上面的代码中,由于String类型的对象是一个常量,是不能改变的,所以每次拼接都会创建一个新的String对象,所以循环中会频繁的创建对象,可能导致内存抖动。
解决方法:在单线程中我们可以使用StringBuilder来替换String进行字符串拼接,在多线程
中我们使用StringBuffer替换String进行字符串拼接。
StringBuilder stringBuilder=new StringBuilder();
List userList=new ArrayList<>();
for (int i = 0; i < userList.size(); i++) {
if (i==userList.size()-1){
stringBuilder.append(userList.get(i).getId());
}else{
stringBuilder.append(userList.get(i).getId()).append(",");
}
}
2.在自定义View中,我们会重写onDraw()方法,View的onDraw()方法每次刷新View 即invalidate()时都会被调用,所以尽量避免在onDraw()方法中创建对象。