内存,是Android应用的生命线,一旦在内存上出现问题,轻者内存泄漏造成App卡顿,重者直接crash,因此一个应用保持健壮,要做好内存的使用和优化。网上有很多讲JAVA内存虚拟机的好文章,我就不赘述了。今天主要总结下内存优化。
作为一个开发者,需要在平时的代码中就要多注意,如有不合理的地方,则需要进行优化。
一、内存泄漏(memory leak):
内存泄漏就是在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小,通俗点讲,就是无法回收无用对象。
二、内存溢出(out of memory,也叫OOM):
系统在申请内存空间时,没有总够的内存空间供其使用。简单来说就是系统不能再分配你所需要的内存空间,比如你申请需要100M的内存空间,但是系统仅剩90M了,这时候就会内存溢出。(溢出会导致应用Crash崩溃)
内存泄漏的本质原因:
本该被回收的对象没有被回,继续停留着内存空间中,导致内存被占用。其实是持有引用者的生命周期 > 被持有引用者的生命周期。过多内存泄漏会把内存空间占用完,最终会导致内存溢出。
三、内存泄漏的常见情况
1.非静态内部类创建静态实例
非静态内部类创建静态实例会导致内存泄漏,通常项目中,通常在Activity启动的时候创建单例数据,避免重复创建相同的数据,会在Activity内部创建一个非静态内部类的静态实例。
错误的例子:非静态内部类默认持有外部引用
public class NotStaticActivityextends AppCompatActivity {
//非静态内部类实例引用,设置为静态
private static InnerClass sInnerClass;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//保证非静态内部类实例只有一个
if (sInnerClass == null) {
sInnerClass = new InnerClass();
}
}
//非静态内部类
private class InnerClass {
//……
}
}
非静态外部类(InnerClass)的生命周期 = 应用的生命周期,而且持有外部类Activity的引用,在Activity在销毁的时候无法被GC回收,从而导致内存泄漏。
优化后的代码1:将非静态内部类设置为静态内部类(静态内部类默认不持有外部类的引用)
//静态内部类
private static class InnerClass2 {
//……
}
优化后的代码2:将外部类抽取出来封装成一个单例
public class InnerClass extends AppCompatActivity {
private static final String TAG = InnerClass3.class.getSimpleName();
private static InnerClass sInnerClass;
//单例
public InnerClass getInstance() {
if (sInnerClass == null) {
sInnerClass = new InnerClass();
}
return sInnerClass;
}
}
2.注册对象未注销或资源对象未关闭
注册了像BraodcastReceiver,EventBus这种,没有在页面销毁时注销的话,会引发泄露问题,所以应该在Activity销毁时及时注销。
3.多线程造成内存泄漏
我们知道线程类属性非静态(匿名)内部类,多线程的使用主要是AsyncTask、实现Runnable接口,继承Thread类,在使用线程类时需要注意内存泄漏。
1).AsyncTask
我们常常使用AsyncTask来执行一些异步的耗时操作。
我们需要在界面销毁的时候调用AsyncTask.cancel(true)方法。
@Override
protected void onDestroy() {
//强制退出AsyncTask
mAsyncTask.cancel(true);
super.onDestroy();
}
2).Thread和Handler的使用
Thread:线程类Thread属于非静态内部类,它的生命周期比ThreadActivity的生命周期长,运行时默认持有外部类的引用,如果ThreadActivity需要销毁时,线程类Thread持有ThreadActivity的引用,导致ThreadActivity无法回收利用,造成内存泄漏。
我们经常这样使用:
//方式一:新建内部类
new MyThread().start();
//方式二:匿名Thread内部类
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
//自定义Thread
private class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
优化方法1;把 private class MyThread extends Thread改成静态内部类 private static class MyThread extends Thread;
优化方法2:在界面的ondestroy方法中,把线程结束掉.
Handler:也是一个内部类,我们在使用的时候也会提示他如果是非静态的,就会造成内存泄漏。
解决方法1:
如果 Handler
不需要外部 Activity
的数据,直接使用使用静态内部类或外部类即可,避免使用成员内部类。如果需要,还可以使用外部类+弱引用的方法。
private static class MyHandler extends Handler {
private WeakReference<MainActivity> ref;
public MyHandler(Activity activity) {
this.ref = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
// TODO:ref.get()...
super.handleMessage(msg);
}
}
tips:使用成员内部类 + 弱引用的方法是不行的,内部类默认持有外部类引用的。
解决方法2:当外部类结束生命周期时,清空Handler内消息队列;
@Override
protected void onDestroy() {
//当外部类结束生命周期时,清空Handler内消息队列;
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
4.static关键字修饰的成员变量
我们知道被static修饰的成员变量的生命周期等于app应用程序的生命周期。
所以,是否有些不需要的是否可以去掉,静态的改成非静态的。
如果静态的对象引用了activity,是否可以替换成App的上下文就足够了。
5.引用的对象是否回收,置空
(1)动画相关资源使用后,记得及时回收;
(2)IO流相关类:在使用IO流,File文件类或者Sqlite、Cursor等资源时要及时关闭,这些资源在读写操作时一般都行了缓冲,如果不及时关闭,这些缓冲对象就会一直被占用得不到释放,以致内存泄漏,所以在在它们不需要使用时,及时关闭,缓冲释放资源,避免内存泄漏。
stream.close();
cursor.close();
bitmap.recycle();bitmap = null;
arrayList.clear();arrayList = null;
6、内存抖动
当大量的对象创建又回收时,就会内存抖动,例如常见的在onDraw方法中创建对象,应把对象放在类中或者初始化方法中。