匿名内部类(Android\Handler)
学习handler的过程中有关内存泄漏原因,让我又重新复习了匿名内部类,结合多人的经验进行了总结,如有侵权部分请联系删除!
一、匿名内部类是什么?
匿名内部类,就是没有名字的嵌套类。它是局部内部类一种特殊形式,也就是没有变量名指向这个类的实例,而且具体的类实现会写在这个类里面。
内部类:可以把一个类定义到另外一个类的内部,在类里面的这个类就叫内部类,外面的类就叫外部类。 在这种情况下,这个内部类可以看做外部类的一个成员。
二、适用场景
分析:在实际开发中,我们常常遇到这样的情况:一个接口/类的方法的某个实现方式在程序中只会执行一次,但为了使用它,我们需要创建它的实现类/子类去实现/重写。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余。
总结:
- 只用到类的一个实例;
- 类在定义后马上用到;
- 类非常小(SUN建议是4行代码以下);
- 给类命名并不会导致代码更容易被理解。
三、特点
- 匿名内部类不能定义任何静态成员、方法和类;
- 匿名内部类中的方法不能是抽象的;
- 匿名内部类必须实现接口或抽象类中的所有方法;
- 匿名内部类不能定义构造器;
- 匿名内部类访问的外部类成员变量或成员方法必须用final修饰。
- 匿名内部类不能是public,protected,private,static。
- 只能创建匿名内部类的一个实例。
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
**注意:**匿名内部类必须继承或实现一个接口。
四、基本格式
new 接口/类名(参数1, 参数2...){
实现方法1(){
}
实现方法2(){
}
......
};
- 发现:形式上有点像new一个接口或类的感觉,但是我们必须记住接口是不可以new出来的(实例化),我们要在内部重写需要的接口的方法,这样就new出了一个实现接口方法的对象,但是这个对象的类名不可见,所以叫做匿名类。
五、应用实例代码
1.基础应用
代码如下(示例):
package cn.tedu.innerclass;
/*本类用于测试匿名内部类
* 匿名内部类没有名字,通常与匿名对象结合在一起使用*/
public class TestInner5 {
public static void main(String[] args) {
//传统方式:创建接口的实现类+实现类实现接口中的抽象方法+创建实现类对象+通过对象调用方法
//3.创建接口一对应的匿名对象与匿名内部类,并调用实现了的方法save()
new Inter1(){
@Override
public void save() {
System.out.println("save()...");
}
@Override
public void get() { }
}.save();
//5.创建抽象类对应的匿名对象与匿名内部类
new Inter2(){
@Override
public void drink() {
System.out.println("一人饮酒醉");
}
}.drink();
//7.调用普通类的功能怎么调用?创建匿名对象直接调用
new Inter3().powerUp();
new Inter3().powerUp();//new了2次,所以是两个匿名对象
/*如果想要多次使用实现后的功能,还是要创建普通的对象
* 匿名对象只能使用一次,一次只能调用一个功能
* 匿名内部类其实就充当了实现类的角色,去实现未实现的抽象方法,只是没有名字而已*/
Inter3 in = new Inter3();
in.study();
in.study();
in.study();
in.study();
in.study();
in.study();
}
}
//1.创建接口
interface Inter1{
//2.定义接口中的抽象方法
void save();
void get();
}
//4.创建抽象类
abstract class Inter2{
public void play(){
System.out.println("Inter2...play()");
}
abstract public void drink();
}
//6.创建普通类
class Inter3{
public void study(){
System.out.println("什么都阻挡不了我想学习赚钱的决心");
}
public void powerUp(){
System.out.println("我们会越来越强的!");
}
}
2.Thread中的应用
代码如下(示例):
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
tvHandler3.setText("update");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
通常我们习惯用这样的方式创建线程。
3.Handler中的应用
代码如下(示例):
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
此处由于内部类持有外部类的引用(Message持有Handler,handler持有activity)可能会导致内存泄漏。
解决方案:
- 声明handler为static
- 使用activity的弱引用来访问activity对象内的成员
代码如下(示例):
/**
* 避免handler内存泄露的写法
* handler为何会导致内存泄露:
* 如果handler为内部类(非静态内部类),那么会持有外部类的实例,
* 若在handler.sendMessage的时候,activity finish掉了,那么此时activity将无法得到释放
* <p>
* 如果申明handler为静态内部类,则不会含有外部类的引用,
* 但是需要在handler中更新UI(注意此时handler为static),则需要引入一个activity引用,
* 显然必须是弱引用,否则会导致和上面一样的结果
* <p>
* 使用activity弱引用
* 之所以使用弱引用,是因为handler为static,使用activity的弱引用来访问activity对象内的成员
*/
private static class MyHandler extends Handler {
private WeakReference<HandlerOOMActivity> weakReference;
public MyHandler(HandlerOOMActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage: ");
HandlerOOMActivity activity = weakReference.get();
switch (msg.what) {
case 0:
if (activity != null) {
Person person = (Person)msg.obj;
activity.tvText.setText("我是避免OOM更改Handler后的文字:"+person.getName());
}
break;
case 1:
activity.tvText.setText("我是更改后的文字");
break;
}
}
}
总结
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用
以上就是今天要讲的内容,本文仅仅简单介绍了匿名内部类的使用和在Android中一些的体现,而Android中用到的地方还有很多。