引子
在Java中非静态内部类或者匿名内部类都是默认持有外部类的引用的,这两个内部类的存在是依赖于外部类的对象的。比如如下例子:
public class OutClass {
private int a;
private void setA() {
}
////内部类可以随意使用外部类的方法和属性,不论是否是private的
public class InnerClass {
private void callA() {
setA();
a = 2;
}
}
}
public class Client {
public static void main(String[] args) {
//要想new一个InnerClass,必须先new一个OutClass
//OutClass.InnerClass inn = new OutClass.InnerClass();//这句代码是错误的,会编译不过
OutClass outClass = new OutClass();
//这里就新建了一个依赖于out这个变量的内部类
OutClass.InnerClass innerClass = outClass.new InnerClass();
}
}
上面的out.new InnerClass()很奇怪,但是是正确的。单独new InnerClass()是编译不过的,非静态内部类或者匿名内部类是依赖于外部类的,默认是持有外部类的引用的
Handler存在内存泄露的使用写法
在写Android应用程序时,Handler应该是很常见的一个类。我们一般在使用该类时有采用如下方法:
非静态内部类的Handler系统提示
可以看到这段代码编辑器为我们标出了黄色,并且提示如下:
This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... (Ctrl+F1)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
简单翻译如下:
由于handler定义为内部类,可能会阻止GC。如果handler的Looper或MessageQueue 非主线程,那么没有问题。如果handler的Looper或MessageQueue 在主线程,那么需要按如下定义:定义handler为静态内部类,当你实例化handler的时候,传入一个外部类的弱引用,以便通过弱引用使用外部类的所有成员
public class MainActivity extends AppCompatActivity {
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv_handler);
doInBackground();
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
mTextView.setText(msg.obj + "");
break;
default:
break;
}
}
};
private void doInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//耗时操作完成,发送消息给UI线程
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "更新UI";
mHandler.sendMessage(msg);
}
}).start();
}
}
新建了一个匿名的内部类,并直接new给了mHandler变量。采用如上方式使用Handler时会有内存泄漏的风险。
原因:非静态的内部类持有了外部类的对象
①先说handler导致activity内存泄露的原因:
handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露。
②为何handler要定义为static?
因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露
③为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?
这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。
静态内部类的Handler的写法
解决内存泄露
public class MainActivity extends AppCompatActivity {
TextView mTextView;
MyHandler mMyHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv_handler);
mMyHandler = new MyHandler(MainActivity.this);
doInBackground();
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> mWeakReference;
public MyHandler(MainActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mWeakReference.get();
switch (msg.what) {
case 1:
if (mainActivity != null) {
mainActivity.mTextView.setText(msg.obj + "");
}
break;
default:
break;
}
}
}
private void doInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//耗时操作完成,发送消息给UI线程
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "更新UI";
mMyHandler.sendMessage(msg);
}
}).start();
}
}
请忽略activity的弱引用,因为关系不太,之所以弱引用activity,是为了访问activity对象的成员
Demo:Handler内存泄露及解决方案
网友评论