[译] 内存泄露的八种花样,快速从入门到精通
最容易泄露 Activity 的方式莫过于定义一个类,类的内部通过静态变量的方式持有 Activity,然后在运行中,将 Activity 实例赋值给这个变量。如果这个静态变量的引用在 Activity 的生命周期结束前没有置空的话,Activity 实例就泄露了。因为被静态变量持有的对象,它将会被保持在内存中,在 App 的运行过程中一直存在。如果有一个静态变量持有了 Activity 的引用,那么这个 Activity 就无法被垃圾回收器回收。完整代码
void setStaticActivity() {activity = this;}
View saButton = findViewById(R.id.sa_button);saButton.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) {setStaticActivity();nextActivity();}});
Activity 内存泄露
2. 静态 View
另一个类似的场景:如果一个 Activity 需要经常被访问,那么我们可能会选择使用单例模式,保持一个实例在内存里,以便它可以被快速的使用到。然而,若前所述,干预 Activity 的生命周期并将它保持在内存里是一件很危险也没有必要的事情,应该尽可能的避免这么做。
但如果我们有一个 View 对象,需要花费很大的代价去创建它,而它在 Activity 的不同的生命周期里保持不变,那么我们能不能把在这个实例存在静态变量里,再讲他附加到 View 的层级结构里去?让我们来看下。完整代码当我们的 Activity 被回收的时候,大部分的内存可以被回收。
void setStaticView() {view = findViewById(R.id.sv_button);}
View svButton = findViewById(R.id.sv_button);svButton.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) {setStaticView();nextActivity();}});
静态 View 内存泄露
等
下!看到没。你知道一个 attach 了的 view 内部会持有一个指向 Context 的引用,换句话说,那就是我们的 Activity。通过吧一个 View 设为静态变量,我们创建了一个能长期持有 Activity 的引用链,导致 Activity 被泄露了。千万不要把 attach 的 view 设为静态变量,如果实在必须这么做,至少保证在 Activity 的生命周期结束前把它从 View 的层级结构里detach)掉。
3. 内部类
除了这,让我们在我们的 Activity 类里在定义一个类,也就是内部类。为了提高代码的可读性和健壮性,封装程序逻辑,我们可能会这么做。如果我们创建了一个这样的内部类的实例,并通过静态变量持有了它,会怎样呢?你应该能猜到这又是一个内存泄露的点。
void createInnerClass() {class InnerClass {}inner = new InnerClass();}
View icButton = findViewById(R.id.ic_button);icButton.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) {createInnerClass();nextActivity();}});
内部类导致的内存泄露
不幸的是,由于内部类可以直接访问到它的外部类的变量,这个特性意味着内部类会隐式的持有一个对它的外部类的引用,这间接导致了我们不小心又泄露了 Activity。
4. 匿名类
同样的,匿名类也持有一个指向它申明的地方所在的类的引用。如果你在Activity内定义和实例化一个AsyncTask匿名类,那也可能发生内存泄露
void startAsyncTask() {new AsyncTask<Void, Void, Void>() {@Override protected Void doInBackground(Void... params) {while(true);}}.execute();}
super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);View aicButton = findViewById(R.id.at_button);aicButton.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) {startAsyncTask();nextActivity();}});
AsyncTask 的内存泄露
5. Handler
同样的原则也适用于后台任务:定义一个匿名的Runnable,然后将它加入Handler的处理队列里。这个 Runnable 对象会隐含的持有一个指向它定义的时候所在的 Activity 的引用,然后它会作为一个消息对象加入到 Handler 的消息队列里去。在 Activity 生命周期结束之后,只要这个消息还没被 Activity 处理,那就有一条引用链指向我们的 Activity 对象,使得 Activity 对象无法被回收,进而泄露。
void createHandler() {new Handler() {@Override public void handleMessage(Message message) {super.handleMessage(message);}}.postDelayed(new Runnable() {@Override public void run() {while(true);}}, Long.MAX_VALUE >> 1);}
View hButton = findViewById(R.id.h_button);hButton.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) {createHandler();nextActivity();}});
Handler 导致的内存泄露
6. 线程
评论