背景:有个数据库处理类,包含一个thread负责对数据库进行写入(耗时操作)。该thread在某个fragment中会被重新初始化并开始。
private void updateBookShelf() {
Globals.getInstance().setBookShelves(bookShelves);
if (Globals.getInstance().thread != null && Globals.getInstance().thread.isAlive()) {
Globals.getInstance().thread.interrupt();
}
Globals.getInstance().thread = new Thread(() -> {
List<BookShelf> bookShelfList = Globals.getInstance().getBookShelves();
clearAllBookShelf();
for (int i = 0; i < bookShelfList.size(); i++) {
BookShelf bookShelf = bookShelfList.get(i);
if (bookShelf.getType().compareTo(BookShelf.TYPE_FOLDER) == 0) {
for (int j = 0; j < ((BookShelf_FolderBean) bookShelf).getChildCount();
j++) {
BookShelf
.createWithOutCheck(((BookShelf_FolderBean) bookShelf).getChild(j));
}
}
BookShelf.createWithOutCheck(bookShelf);
}
});
Globals.getInstance().thread.start();
}
private void clearAllBookShelf() {
BookShelf.deleteForAll();
}
本身这里没有把thread写成静态内部类,只是new了一个Thread,然后重写了run方法,但是根据LeakCanary的报告这里出现了内存泄漏。
分析:
这是个长期存在的thread,发生内存泄漏的话应该thread持有了该fragment的对象。可是重新阅读代码发现并没有显示的持有该fragment的某个对象。经过漫长的排查....发现其中的某行代码clearAllBookShelf();是问题所在,thread中使用了类的成员方法,成员方法是需要fragment对象调用的,因此持有了fragment实例。尽管clearAllBookShelf();方法中并没有持有实例。
解决办法:
可以见到clearAllBookShelf();方法中BookShelf.deleteForAll();是一个其他类中的静态方法。将thread中对clearAllBookShelf();的调用直接替换成BookShelf.deleteForAll();。
见到这里突然对静态内部类避免内存泄漏有了深一点的理解,拿这个例子来说,虽然不使用静态内部类可以解决内存泄漏。但是我们使用静态内部类的话就可以直接避免这个问题了,静态内部类中不会允许我们调用外部类的成员方法,就也不会如此“秘密”地持有fragment的实例了,恰巧这个例子中还是一个thread生命周期比fragment更久的情况,导致fragment无法被回收,引发了内存泄漏了。