请选择 进入手机版 | 继续访问电脑版
设为首页收藏本站

houcx

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 865|回复: 0

小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理

[复制链接]

216

主题

218

帖子

1089

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
1089
发表于 2018-10-19 10:30:35 | 显示全部楼层 |阅读模式
前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述。前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以决心花些时间研究了一下 DroidPlugin 插件框架的原理,以便再出现问题时也能从容应对。打开源码后发现尽是大把大把的 hook、binder、classloader 等等,很难摸清头绪,幸运的是,有很多热心的大神已经对 DroidPlugin 的原理进行了透彻的剖析,文末会有本人对参考文章的致谢。
  本系列文章的代码已经上传至github,下载地址:https://github.com/lgliuwei/DroidPluginStudy 本篇文章对应的代码在 com.liuwei.proxy_hook.proxy 包内。
· 代理模式
  在 DroidPlugin 中用到了大量的动态代理,所以如果我们想理解 DroidPlugin 的原理,首先我们需要知道什么是动态代理,说到动态代理,我们难免会想起静态代理,那么代理是什么呢?
  代理模式的意图是通过提供一个代理( Proxy )或者占位符来控制对该对象的访问。类比我们生活中,代理也是随处可见,其中中介就是一个很好的例子,把代理看做生活中的中介,将更加易于理解,试想一下,如果我们想租房或者买房的话通过中间是不是就可以让我们非常省心。
一、静态代理
  为了保证与所代理的对象功能行为的一致性,代理类一般需要实现实体类所实现的同一个接口,以下即为一个最基本的代理模式的结构。
  首先先定义一个接口,供实体类和代理类实现。(如:接口 Sbuject1 )
[url=][/url]
1 /**2 * Created by liuwei on 17/3/1.3  */4 public interface Subject1 {5     void method1();6     void method2();7 }[url=][/url]

  然后创建一个 Subject1 的实现类。
[url=][/url]
1 /** 2 * 实体类 3 * Created by liuwei on 17/3/1. 4  */ 5 public class RealSubject1 implements Subject1 { 6     @Override 7     public void method1() { 8         Logger.i(RealSubject1.class, "我是RealSubject1的方法1"); 9     }10     @Override11     public void method2() {12         Logger.i(RealSubject1.class, "我是RealSubject1的方法2");13     }14 }[url=][/url]

  再为 RealSubject1 创建一个代理类。
[url=][/url]
1 /** 2 * 静态代理类 3 * Created by liuwei on 17/3/1. 4  */ 5 public class ProxySubject1 implements Subject1 { 6     private Subject1 subject1; 7     public ProxySubject1(Subject1 subject1) { 8         this.subject1 = subject1; 9     }10     @Override11     public void method1() {12         Logger.i(ProxySubject1.class, "我是代理,我会在执行实体方法1之前先做一些预处理的工作");13         subject1.method1();14     }15     @Override16     public void method2() {17         Logger.i(ProxySubject1.class, "我是代理,我会在执行实体方法2之前先做一些预处理的工作");18         subject1.method2();19     }20 } [url=][/url]

  可以发现,代理模式还是很简单的,很快我们就写好一个最基本的代理结构,接下来写个测试类跑一下看看效果。
[url=][/url]
1 /** 2 * Created by liuwei on 17/3/1. 3  */ 4 public class ProxyTest { 5     public static void main(String[] args){ 6         // static proxy 7         ProxySubject1 proxySubject1 = new ProxySubject1(new RealSubject1()); 8         proxySubject1.method1(); 9         proxySubject1.method2();10 }[url=][/url]

  输出结果非常简单,这里就不再贴出来了。我们看到,在测试类中只需要调用 ProxySubject1 的对像即可对实现对 RealSubject1 的操作。同时我们也发现在初始化 ProxySubject1 时需要传入 RealSubject1 的对象,当然,我们完全可以把获取 RealSubject1 的对象封装到代理类内部,这只是代理模式根据业务需要的不同体现而已。有很多人把这一点作为区分代理模式和适配器模式的依据,这个是不对的,由于本篇的重点是为插件化的原理做铺垫,至于代理模式和适配器模式的区别日后会专门写一篇文章介绍,这里就不细说了。
  其实,从这个简单的示例中也许并没有体现出代理模式的优势,而且还要多创建一个代理类,反而看起来好像更麻烦了。其实代理模式很明显的好处就是通过代理,可以控制对实体对象的访问,从而提高了安全性。而且可以在调用实体类的方法时做一些预处理和善后的工作,这样就保证了实体类可以抛开复杂的业务逻辑而只去实现一些最纯粹的功能,提高了代码的可读性和灵活性。
二、动态代理
  动态代理是本文的重点,也是 DroidPlugin 插件化框架的基础。动态代理乍一听起来好像也挺高大上的,但幸运的是,它并没有我们想象中那么高深莫测,所以我们大可不必对它有任何的畏惧之感。
  假设我们在上文静态代理的例子中又多了一个 RealSubject2 的类,它实现的接口是 Subject2,这时候我们如果想对 RealSubject2 进行代理需要如何做?这个简单,我们直接类比 ProxySubject1 再创建一个 ProxySubject2 即可,这样是可以的,但如果有非常多的实体类并且都实现了不同的接口,那我们岂不是需要创建很多的代理类:ProxySubject1,ProxySubject2 ... ProxySubjectN!还有没有更优雅一些的方法?答案是肯定的,动态代理即可解决这个问题。(当然,这并不是动态代理唯一的优点)
  动态代理是在实现阶段不需要关心代理谁,在运行阶段才指定代理对象。创建一个动态代理类很简单,JDK已经给我们提供好了动态代理接口  InvocationHandler 我们只需要实现它即可创建一个动态代理类,以下是一个简单的小例子:
[url=][/url]
1 /** 2 * 动态代理 3 * Created by liuwei on 17/3/1. 4 * 注:动态代理的步骤: 5 *  1、写一个InvocationHandler的实现类,并实现invoke方法,return method.invoke(...); 6 *  2、使用Proxy类的newProxyInstance方法生成一个代理对象。例如:生成Subject1的代理对象,注意第三个参数中要将一个实体对象传入 7 *          Proxy.newProxyInstance( 8                          Subject1.class.getClassLoader(), 9                          new Class[] {Subject1.class},10                          new DynamicProxyHandler(new RealSubject1()));11 12  */13 public class DynamicProxyHandler implements InvocationHandler {14     private Object object;15 16     public DynamicProxyHandler(Object object) {17         this.object = object;18     }19 20     @Override21     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {22         Logger.i(DynamicProxyHandler.class, "我正在动态代理[" + object.getClass().getSimpleName() + "]的[" + method.getName() + "]方法");23         return method.invoke(object, args);24     }25 26     /**27      * 调用Proxy.newProxyInstance即可生成一个代理对象28      * @param object29      * @return30      */31     public static Object newProxyInstance(Object object) {32         // 传入被代理对象的classloader,实现的接口,还有DynamicProxyHandler的对象即可。33         return Proxy.newProxyInstance(object.getClass().getClassLoader(),34                 object.getClass().getInterfaces(),35                 new DynamicProxyHandler(object));36     }37 } [url=][/url]

  这是一个名为 DynamicProxyHandler 的动态代理类,其中 invoke 方法完成了对代理对象方法的调用,是必须实现的。接下来使用此类代理其他的实体类也非常简单,只需使用 Proxy 的newProxyInstance() 方法并传入相应的参数即可获取一个代理对象,接下来我们在测试类里面添加一下代码,代码如下:
[url=][/url]
1 /** 2 * Created by liuwei on 17/3/1. 3  */ 4 public class ProxyTest { 5     public static void main(String[] args){ 6         // static proxy 7         ProxySubject1 proxySubject1 = new ProxySubject1(new RealSubject1()); 8         proxySubject1.method1(); 9         proxySubject1.method2();10 11         // 如果想对RealSubject2代理显然不得不重新再写一个代理类。12         ProxySubject2 proxySubject2 = new ProxySubject2(new RealSubject2());13         proxySubject2.method1();14         proxySubject2.method2();15 16         Logger.i(ProxyTest.class, "----------分割线----------\n");17 18         // 如果写一个代理类就能对上面两个都能代理就好了,动态代理就解决了这个问题19         Subject1 dynamicProxyHandler1 = (Subject1) DynamicProxyHandler.newProxyInstance(new RealSubject1());20         dynamicProxyHandler1.method1();21         dynamicProxyHandler1.method2();22 23         Subject2 dynamicProxyHandler2 = (Subject2)DynamicProxyHandler.newProxyInstance(new RealSubject2());24         dynamicProxyHandler2.method1();25         dynamicProxyHandler2.method2();26     }27 }[url=][/url]

  输出结果非常简单,这里不再给出。
三、小结
  至此,相信我们对动态代理已经有一个基本的认识,其实代理模式除了上文中提到的普通代理(静态代理的一种)、动态代理之外还有很多种方式,如远程代理、虚拟代理、智能代理等等,这里就不一一介绍了。
  其实插件化的原理简单来说是使用动态代理,通过反射等机制将系统中的一些方法hook掉,从而达到劫持系统方法的目的以实现对系统方法的篡改。例如通过 hook 掉 AMS 的 startActivity 方法来启动一个没有在清单文件中配置的 Activity 。下一篇文章将详细介绍 Hook 机制,以及反射在 Hook 中的实际体现。  
  致谢:最后我想说的是“吃水不忘挖井人”!非常感谢术哥《Android插件化原理解析——概要》系列文章,本人正是在参考了这些内容的思路之后才有能力写下本系列文章。本人在Android的插件化领域可以说算是一个小白,写下本系列文章的目的一方面是在实践中加深自己的理解,另一方面是根据本人以小白角度对插件化原理的体会用更加简单易懂的方式传达出来,从而帮助小白也能读懂插件化!


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|houcx ( 京ICP备15004793号  

GMT+8, 2019-1-24 10:22 , Processed in 0.063826 second(s), 24 queries .

Powered by houcx! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表