Android 内存泄露记录

142 阅读4分钟

Android 内存泄露记录

当一个不再使用的对象被其他长生命周期对象持有引用时,造成该对象在系统触发GC时无法回收,以致该对象在堆中所占的内存空间无法释放,造成内存空间浪费,此现象即为内存泄漏

常见的几种内存泄漏

1. Context引起内存泄漏

  • 延申:长生命周期的对象不要强引用Activity或Fragment对象

原因

  • 短生命周期对象持有长生命周期的对象,回收时,因其被持有所以无法回收

修复方案

  • 弱引用context对象 或 使用Application的context对象
class XxxActivity: AppCompatActivity() {
    private lateinit var mInstance: XxxInstance

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 方式一:此种方式当activity销毁时不存在内存泄露
        // 原因:XxxActivity与XxxInstance双相依赖,属于属于双向可达状态
        // 并且除了这里两个类外无其他三方类引用这两个类,此时若activity回收后GC时会同时回收两者
        mInstance = XxxInstance(this)

        // 方式二:单例模式,当activity销毁时会引起内存泄露
        // 原因:单例模式,单例对象生命周期等同于应用生命周期
        // XxxActivity销毁时,因单例中还在引用,所以无法被回收
        // 解决:单例中使用弱引用可规避其引起的内存泄露问题
        mInstance = XxxInstance(this)
    }
}

// 方式一
class XxxInstance(private val context: Context) {
}

// 方式二:单例模式
class XxxInstance private constructor(private val context: Context) {
    companion object {
        @SuppressLint("StaticFieldLeak")
        @Volatile private var instance: XxxInstance? = null
        fun getInstance(context: Context) =
            instance ?: synchronized(this) {
                instance ?:XxxInstance(context).also { instance = it }
            }
    }
}

2. 非静态内部类持有外部类引用

原因

  • 非静态内部类默认持有外部类引用,若在外部类中创建静态内部类对象,则在外部类销毁时,因被内部类所引用,所以无法回收,最终引起内存泄露

修复方案

  • 定义为静态内部类(若内部显示引用外部类成员需通过弱引用实现)
/**
 * 解决方案:将Test改为静态内部类(static class Text)
 */
class XActivity extends AppCompatActivity {
    private static Test test = null;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_x);
        if (test == null) {
            test = new Test();
        }

        // ...
    }

    // kotlin中使用inner即为内部类,会有外部类对象的引用
    // kotlin中不使用inner即为嵌套类,不持有外部类对象,等同于Java中的静态内部类
    class Test {
        // ...
    }
}

3. Handler使用不当引起内存泄漏

原因

    1. 非静态内部类Handelr 或 匿名内部类Handelr
    1. Activity被销毁时Handelr消息未处理完

修复方案

    1. Handler改为静态内部类,内部弱引用Activity对象
    1. Activity#onDestroy()中移除队列中消息
/**
 * 程序启动时,主线程会创建Looper对象,内部维护一个MessageQueue消息队列,按时间顺序存放Message
 *
 * 1. 若Handler通过内部类创建,内部类持有外部类引用(Activity),而队列中的消息target指向Handler,即消息持有Handler引用
 * 2. 若Activity销毁时队列中的消息还未处理完,这些未处理完的消息会持有Activity引用,导致Activity无法回收
 *
 * 解决方案:
 * 1. Handler改为静态内部类,Handler内弱引用Activity对象,Activity销毁时让内部类不再持有外部类的引用
 * 2. Activity#onDestroy()中移除队列中消息,
 */
class XxxActivity extends AppCompatActivity {
    XxxHandler mHandler = new XxxHandler(this);

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

         new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendMessage(Message.obtain());
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除对应的Runnable或者是Message
        // mHandler.removeCallbacks(runnable);
        // mHandler.removeMessages(what);
        mHandler.removeCallbacksAndMessages(null);
    }

    private static class XxxHandler extends Handler {
        private WeakReference<Activity> mActivity;

        public XxxHandler(Activity activity) {
            mActivity = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mActivity.get() == null || mActivity.isFinishing()) {
                return;
            }

            // to do something..

        }
    }
}

4. 资源未回收引起的内存泄露

  • 广播注册但未取消注册
  • Bitmap使用后未回收
  • 服务开启或绑定但未关闭或解绑
  • 数据库cursor使用后未关闭
  • 网络请求页面关闭时未取消(例:图片加载未绑定view生命周期等)
  • 文件流使用后未关闭
  • 多媒体(音视频)使用后未释放
  • Socket使用后未关闭
  • 三方库注册后未取消注册(例:EventBus)
  • 为便于Activity管理,将其添加至栈中,销毁时未移除

解决方案

  • kotlin中可使用use函数管理资源确保使用后自动关闭(例文件、流等)

其他内存泄露

1. WebView引起内存泄漏

两种优化方案

  • 为WebView开辟一个独立进程,操作结束后结束进程(System.exit(0));与其通信可采用AIDL、Messager、ContentProvider、Broadcast、Intent等方式
  • xml中定义一个容器类view(例FrameLayout(layout)),在代码中动态添加WebView(context可使用弱引用),onDestroy()销毁时直接layout.removeAllViews(),移除WebView

2. 线程使用不当造成的内存泄漏

原因

  • Activity销毁时,若任务未执行完毕,会导致Activity无法被回收
1. Asynctask
new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... params) {
        SystemClock.sleep(8000);
        return null;
    }
}.execute();

2. Thread
new Thread(new Runnable() {
    @Override
    public void run() {
        SystemClock.sleep(8000);
    }
}).start();

解决方案

  • 静态内部类,Activity销毁时取消任务
1. AsyncTask
static class XAstncTask extends AsyncTask<Void, Void, Void> {
    private WeakReference<XActivity> reference;
    
    public XAstncTask(XActivity activity) {
        reference = new WeakReference(activity);
    }
    
    @Override
    protected Void doInBackground(Void... params) {
        SystemClock.sleep(8000);
        return null;
    }
    
    @Override
    protected Void onPostExecute(Void void) {
        super.onPostExecute(void)
        
        val activity = (XActivity) reference.get();
        if (activity == null || activity.isDestroy() || activity.isFinishing()) {
            return;
        }
        
        ...
    }
}

2. Thread
static class XRunnable implements Runnable {
    @Override
    public void run() {
        SystemClock.sleep(8000);
    }
}

// 使用
XAsyncTask task = new XAsyncTask(this);
task.execute();
new Thread(new XRunnable()).start();

@Override
protected void onDestroy() {
    super.onDestroy();
    
    // 取消任务
    task.cancel();
}