android 构造匿名内部类

下面将分别按照 "什么是匿名内部类", "匿名内部类如何引起内存泄漏", "替代方案 -- 静态内部类", "替代方案 -- 动态代理" 四个部分来讲述,请耐心按顺序的读完.

什么是匿名内部类

匿名内部类,简单的说,就是没有明确指定名字的类。以下面的代码举例:

public class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          ThreadPoolUtil.execRunnable( new Runnable() { // 去线程池中被执行
                   public  void run() {
                        Thread.sleep( 1000 * 60 ); //休眠 1 分钟
                        String info = "name:" + name + "; age:" + age;
                        Log.i("TestInfo", info);
                        ....
                   }
         } );
    }
}

这里的 new Runnable(){...} 就是一个匿名内部类。 它的优点就是让你写起来方便,这个方便体现在两点:
1. 你不必再去声明一个类
2. 可以访问外部类的成员变量,即使是 private 的,也可以访问

然而,之所以你写起来方便,是因为本应该你去做的事情,编译器 ( javac ) 帮你去做了,编译器在把 java 代码代码编译成 class 文件的时候帮你做的。具体做的事情如下:
1. 它会生成一个类,名字大概是 MyActivity$1,或 MyActivity$2 之类的,这个生成的类会实现 Runnable 接口, 会包含 new Runnable(){..} 的 {...} 里面部分的代码,。 
2. 它会有一个成员变量,类型是 MyActivity 类型,当这个类的实例被构造时,会会作为构造方法的参数传递进来,指向外部类的实例,外部类实例暂且记作 mOuter 吧。 它可以通过 mOuter 去访问 MyActivity 的 ( 非 private ) 成员变量、调用它的 (非 private ) 方法,比如 name 。
3. 而对于 MyActivity 的字段 age, 由于是 private 的,编译器会在 MyActivity 的字节码中插入名字大概叫 $access0 之类的方法,可以理解成 getAge() 方法 , 让内部类去调用,从而获取到 age 的值

匿名内部类如何引起内存泄漏

从上面的分析中可知,编译器在内部类中,插入了成员变量,指向着外围的实例。所以,只要内部类不被销毁,外围类就不可能被回收。

替代方案 -- 静态内部类

对于内存泄漏的解决方案,比较流行的就是 静态内部类 + 弱引用的方式了。 写法通常是这样的:

public  class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          ThreadPoolUtil.execRunnable(new  MyRunnable(this));
    }

   static class  MyRunnable implements Runnable {

         private  WeakReference<MyActivity>  mWrActivity;

         public  MyRunnable( MyActivity  activity ) {
               mWrActivity  =  new WeakReference<MyActivity>( activity );
         }

          public  void run() {
                       MyActivity  activity =  mWrActivity.get();
                       if (activity == null) {
                               return;
                        }
                        Thread.sleep( 1000 * 60 ); //休眠 1 分钟
                        String info = "name:" + activity.name + "; age:" + activity.age;
                        Log.i("TestInfo", info);
                        ....
          }
   }

}

静态内部类是不持有对外围类的引用的,且使用了 WeakReference ,确实是不会再引起内存泄漏了。
但这种方式的缺点就是,有一小部分工作,编译器不能帮你做了,你需要自己来做,主要是:
1. 你需要自己来声明类
2. 你要添加一个构造方法,一个 WeakReference 的成员变量,以及对 weakReference.get() 的判空

总体而言,虽然有一点工作量,但也是个不错的解决方案.

替代方案 -- 动态代理

上面的替代方案,当接口比较多、匿名内部类比较多、接口里方法比较多时,写起来总会烦躁的,那么有没有更好一点的版本?
有的,先介绍一个进化版本的静态内部类替代方案 ( 也可以理解成 动态代理 方案的雏形 )

升级版的静态内部类替代方案

代码还可以这样写:

public  class  WeakRunnable implements Runnable {
       private  WeakReference<Runnable> mWr;
       public  WeakRunnable(Runnable  runnable) {
              mWr = new WeakReference<Runnable>( runnable );
       }

      public void run() {
             Runnable runnable = mWr.get();
             if (runnable != null) {
                    runnable.run();
             }
      }
}

public  class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";
    List<Object>  mReferList = new ArrayList<Object>();// 很重要

     public void onCreate() {
          Runnable runnable = new Runnable() { // 去线程池中被执行
                   public  void run() {
                        Thread.sleep( 1000 * 60 ); //休眠 1 分钟
                        String info = "name:" + name + "; age:" + age;
                        Log.i("TestInfo", info);
                        ....
                   }
         };
         mReferList.add( runnable ); // 防止 runnable 被快速 GC 掉。
         ThreadPoolUtil.execRunnable( new WeakRunnable( runnable ));
    }
}

这个版本比着之前的那个版本,是要好了不少,所有的 Runnable 都可以共用一个类 WeakRunnable 了,你可以对 Runnable 接口自由的使用匿名内部类,且不会引起内存泄漏。

(注意: mReferList 的作用是把 runnable 给存起来,否则 runnable 没有被其他强引用所持有, 在下次 GC 的时候就被回收掉 )

有没有更通用性一点的解决方案呢? 有的

动态代理方案

上面的 WeakRunnable 类,从设计模式上讲,是设计模式中的代理模式。而 Java 语言对代理模式做了进一步的支持,它提供了一个类 Proxy,专门处理类似于上面的问题,不懂 Java 动态代理的读者,可以参考 IBM 大神们写的http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 。

此时,代码可以写成这样:

public  class WeakProxy implements InvocationHandler {
       private  WeakReference<Runnable> mWr;
       public  WeakRunnable(Runnable  runnable) {
              mWr = new WeakReference<Runnable>( runnable );
       }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          Object proxied = mWr.get();
          if (proxied == null) {
              return null;
          }
          return method.invoke(proxied, args);
       }

       public static <T> T wrap(T object) {
          if (object == null) {
              return null;
          }
          return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(), new WeakProxy(object));
        }
 }

public  class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";
    List<Object>  mReferList = new ArrayList<Object>();// 很重要

     public void onCreate() {
          Runnable runnable = new Runnable() { // 去线程池中被执行
                   public  void run() {
                        Thread.sleep( 1000 * 60 ); //休眠 1 分钟
                        String info = "name:" + name + "; age:" + age;
                        Log.i("TestInfo", info);
                        ....
                   }
         };
         mReferList.add(runnable); // 防止 runnable 被快速 GC 掉。
         ThreadPoolUtil.execRunnable( WeakProxy.wrap( runnable ));
    }
}

先大致讲解下代码,关于 WeakProxy.wrap( runnable ) 这行代码,共涉及了 3 个对象,一个是具体做事情的 runnable,一个是 WeakProxy 实例,记作 runnableWrapper, runnableWrapper 的字段 mWr 就弱引用着 runnable, 还有一个对象是 WeakProxy.wrap( runnable ) 的返回值,记作 runnableProxy; runnableProxy 和 runnable 拥有所有相同的接口方法,即 runnableProxy 可以当作 runnable 使用; 当调用 runnableProxy 的方法时,runnableProxy 会去调用 runnableWrapper 的 invoke() 方法,而 runnableWrapper 再去调用 runnable 的方法.

然而,还是有不足的,我们必须待使用 Runnable runnable = new Runnable(){...} 的方式去写匿名内部类,而不能直接以 doXxx( new Runnable() { ... } ) 的方式!

书写成 Runnable runnable = new Runnable(){...} 这个样子,是为了把 runnable 放置到 mReferList 中,而这部分工作可以使用接口化来做。所以,最终的方案如下:

public class WeakProxy implements InvocationHandler {
       private  WeakReference<Runnable> mWr;
       public  WeakRunnable(Runnable  runnable) {
              mWr = new WeakReference<Runnable>( runnable );
       }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          Object proxied = mWr.get();
          if (proxied == null) {
              return null;
          }
          return method.invoke(proxied, args);
       }

      public static <T> T wrap(IWeakHost host, T object) {
          if (host != null && object != null) {
             host.referObject(object);
          }
          return wrap(object);
       }

       private  static <T> T wrap(T object) {
          if (object == null) {
              return null;
          }
          return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(), new WeakProxy(object));
        }

       public interface IWeakHost {
          void referObject(Object obj);
       }
 }

public  class  MyActivity extends Activity implements WeakProxy. IWeakHost{
    private  int  age = 14;
    String name = "zhangsan";

    List<Object>  mReferList = new ArrayList<Object>();

    @Override
    public void referObject(Object obj) {
        mReferList.add(obj);
     }

     public void onCreate() {
         ThreadPoolUtil.execRunnable(WeakProxy.wrap(this, new Runnable() { // 去线程池中被执行
                   public  void run() {
                        Thread.sleep(1000 * 60 ); //休眠 1 分钟
                        String info = "name:" + name + "; age:" + age;
                        Log.i("TestInfo", info);
                        ....
                   }
         }));
    }
}

接口 IWeakHost 的实现可以固定为

    List<Object>  mReferList = new ArrayList<Object>();

    @Override
    public void referObject(Object obj) {
        mReferList.add(obj);
     }

建议在你的工程中的 BaseFragment 或 BaseActivity 类上实现 IWeakHost 。

(Note: WeakProxy 这个类是可以直接拿去用的, 不用做任何修改!)

动态代理的适用场景

动态代理的适用场景: 内部类的方法中,确实使用到了外围类的成员变量 或 方法。
举个例子,类似于下面的场景:
场景 1 :

public class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          ThreadPoolUtil.execRunnable( new Runnable() { // 去线程池中被执行
                   public  void run() {
                        Thread.sleep( 1000 * 60 ); 
                        String info = "name:" + name + "; age:" + age; // 使用了外围实例的成员变量
                        Log.i("TestInfo", info);
                        ....
                   }
         } );
    }
}

或 场景2 :

public  class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          ThreadPoolUtil.execRunnable(new  MyRunnable(this));
    }

   public void doXXX() {
       // do someThing
   }
   static class  MyRunnable implements Runnable {

         private  WeakReference<MyActivity>  mWrActivity;

         public  MyRunnable( MyActivity  activity ) {
               mWrActivity  =  new WeakReference<MyActivity>( activity );
         }

          public  void run() {
                       MyActivity  activity =  mWrActivity.get();
                       if (activity == null) {
                               return;
                        }
                        Thread.sleep( 1000 * 60 ); 
                        String info = "name:" + activity.name + "; age:" + activity.age;// 使用了外围实例的成员变量
                        activity.doXXX(); // 或 调用了外围实例的方法
                        Log.i("TestInfo", info);
                        ....
          }
   }

}

这两种场景,都可以使用 WeakProxy.wrap() 的写法代替。 对于场景 2 ,它的优点和缺点 与 WeakProxy 是一样的。 WeakProxy 是从场景 2 的写法中推演出来的,它并不比场景 2 的写法更高明,只是写法上让程序员更加舒服而已。

而对于以下的场景:
场景 3:

public class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          ThreadPoolUtil.execRunnable( new Runnable() { // 去线程池中被执行
                   public  void run() {
                        Log.i("TestInfo", "hello, world"); // 没有使用外围实例的成员变量 或 方法
                        ....
                   }
         } );
    }
}

或 场景 4 :

public  class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          ThreadPoolUtil.execRunnable(new  MyRunnable(this));
    }

   static class  MyRunnable implements Runnable {

         private  WeakReference<MyActivity>  mWrActivity;

         public  MyRunnable( MyActivity  activity ) {
               mWrActivity  =  new WeakReference<MyActivity>( activity );
         }

          public  void run() {

                        Log.i("TestInfo", "hello, world");// 没有使用外围实例的成员变量 或 方法
                        ....
          }
   }

}

对于上面两种场景,内部类的方法中没有使用外围实例的成员变量 和 方法,所以它们的最佳写法不是使用 WeakProxy,也不是使用静态内部类 + 弱引用的方式 ,而是使用静态方法,比如写成这样:

public  class  MyActivity extends Activity {
    private  int  age = 14;
    String name = "zhangsan";

     public void onCreate() {
          doSomeThing();
         printNameAndAge(age,  name);
    }

   private   static  void  doSomeThing() {
       ThreadPoolUtil.execRunnable( new Runnable() {  // 这个是静态匿名内部类,不引用外围类
                   public  void run() {
                        Log.i("TestInfo", "hello, world"); // 没有使用外围实例的成员变量 或 方法
                        ....
                   }
         } );
  }

 private   static  void  printNameAndAge(final  int  age , final  String  name ) {// 参数只要没有引用外围类就行
       ThreadPoolUtil.execRunnable( new Runnable() { // 这个是静态匿名内部类,不引用外围类
                   public  void run() {
                        Log.i("TestInfo", "name : " + name + "; age:" + age ); // 没有使用外围实例的成员变量 或 方法
                        ....
                   }
         } );
  }

最后需要说明一点: WeakProxy.wrap() 的方式,是从 静态内部类 + 弱引用 的写法中推演出来的,它的优点和缺点 都与 静态内部类 + 弱引用 一样 ! 所以它的使用场景,即是 静态内部类 + 弱引用 的使用场景 !

当你要写匿名内部类时,如果你的代码:
符合场景 1 和 场景 2: 请使用 WeakProxy.wrap(....)
符合场景 3 和场景 4: 请使用静态方法 + 静态匿名内部类的方式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值