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

  前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述。前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以决心花些时间研究了一下 DroidPlugin 插件框架的原理,以便再出现问题时也能从容应对。打开源码后发现尽是大把大把的 hook、binder、classloader 等等,很难摸清头绪,幸运的是,有很多热心的大神已经对 DroidPlugin 的原理进行了透彻的剖析,文末会有本人对参考文章的致谢。

  本系列文章的代码已经上传至github,下载地址:https://github.com/lgliuwei/DroidPluginStudy 本篇文章对应的代码在 com.liuwei.proxy_hook.proxy 包内。

· 代理模式

  在 DroidPlugin 中用到了大量的动态代理,所以如果我们想理解 DroidPlugin 的原理,首先我们需要知道什么是动态代理,说到动态代理,我们难免会想起静态代理,那么代理是什么呢?

  代理模式的意图是通过提供一个代理( Proxy )或者占位符来控制对该对象的访问。类比我们生活中,代理也是随处可见,其中中介就是一个很好的例子,把代理看做生活中的中介,将更加易于理解,试想一下,如果我们想租房或者买房的话通过中间是不是就可以让我们非常省心。

一、静态代理

  为了保证与所代理的对象功能行为的一致性,代理类一般需要实现实体类所实现的同一个接口,以下即为一个最基本的代理模式的结构。

  首先先定义一个接口,供实体类和代理类实现。(如:接口 Sbuject1 )

1 /**
2  * Created by liuwei on 17/3/1.
3  */
4 public interface Subject1 {
5     void method1();
6     void method2();
7 }

  然后创建一个 Subject1 的实现类。

 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     @Override
11     public void method2() {
12         Logger.i(RealSubject1.class, "我是RealSubject1的方法2");
13     }
14 }

  再为 RealSubject1 创建一个代理类。

 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     @Override
11     public void method1() {
12         Logger.i(ProxySubject1.class, "我是代理,我会在执行实体方法1之前先做一些预处理的工作");
13         subject1.method1();
14     }
15     @Override
16     public void method2() {
17         Logger.i(ProxySubject1.class, "我是代理,我会在执行实体方法2之前先做一些预处理的工作");
18         subject1.method2();
19     }
20 } 

  可以发现,代理模式还是很简单的,很快我们就写好一个最基本的代理结构,接下来写个测试类跑一下看看效果。

 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 }

  输出结果非常简单,这里就不再贴出来了。我们看到,在测试类中只需要调用 ProxySubject1 的对像即可对实现对 RealSubject1 的操作。同时我们也发现在初始化 ProxySubject1 时需要传入 RealSubject1 的对象,当然,我们完全可以把获取 RealSubject1 的对象封装到代理类内部,这只是代理模式根据业务需要的不同体现而已。有很多人把这一点作为区分代理模式和适配器模式的依据,这个是不对的,由于本篇的重点是为插件化的原理做铺垫,至于代理模式和适配器模式的区别日后会专门写一篇文章介绍,这里就不细说了。

  其实,从这个简单的示例中也许并没有体现出代理模式的优势,而且还要多创建一个代理类,反而看起来好像更麻烦了。其实代理模式很明显的好处就是通过代理,可以控制对实体对象的访问,从而提高了安全性。而且可以在调用实体类的方法时做一些预处理和善后的工作,这样就保证了实体类可以抛开复杂的业务逻辑而只去实现一些最纯粹的功能,提高了代码的可读性和灵活性。

二、动态代理

  动态代理是本文的重点,也是 DroidPlugin 插件化框架的基础。动态代理乍一听起来好像也挺高大上的,但幸运的是,它并没有我们想象中那么高深莫测,所以我们大可不必对它有任何的畏惧之感。

  假设我们在上文静态代理的例子中又多了一个 RealSubject2 的类,它实现的接口是 Subject2,这时候我们如果想对 RealSubject2 进行代理需要如何做?这个简单,我们直接类比 ProxySubject1 再创建一个 ProxySubject2 即可,这样是可以的,但如果有非常多的实体类并且都实现了不同的接口,那我们岂不是需要创建很多的代理类:ProxySubject1,ProxySubject2 ... ProxySubjectN!还有没有更优雅一些的方法?答案是肯定的,动态代理即可解决这个问题。(当然,这并不是动态代理唯一的优点)

  动态代理是在实现阶段不需要关心代理谁,在运行阶段才指定代理对象。创建一个动态代理类很简单,JDK已经给我们提供好了动态代理接口  InvocationHandler 我们只需要实现它即可创建一个动态代理类,以下是一个简单的小例子:

 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     @Override
21     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 object
29      * @return
30      */
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 } 

  这是一个名为 DynamicProxyHandler 的动态代理类,其中 invoke 方法完成了对代理对象方法的调用,是必须实现的。接下来使用此类代理其他的实体类也非常简单,只需使用 Proxy 的newProxyInstance() 方法并传入相应的参数即可获取一个代理对象,接下来我们在测试类里面添加一下代码,代码如下:

 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 }

  输出结果非常简单,这里不再给出。

三、小结

  至此,相信我们对动态代理已经有一个基本的认识,其实代理模式除了上文中提到的普通代理(静态代理的一种)、动态代理之外还有很多种方式,如远程代理、虚拟代理、智能代理等等,这里就不一一介绍了。

  其实插件化的原理简单来说是使用动态代理,通过反射等机制将系统中的一些方法hook掉,从而达到劫持系统方法的目的以实现对系统方法的篡改。例如通过 hook 掉 AMS 的 startActivity 方法来启动一个没有在清单文件中配置的 Activity 。下一篇文章将详细介绍 Hook 机制,以及反射在 Hook 中的实际体现。  

  致谢:最后我想说的是“吃水不忘挖井人”!非常感谢术哥《Android插件化原理解析——概要》系列文章,本人正是在参考了这些内容的思路之后才有能力写下本系列文章。本人在Android的插件化领域可以说算是一个小白,写下本系列文章的目的一方面是在实践中加深自己的理解,另一方面是根据本人以小白角度对插件化原理的体会用更加简单易懂的方式传达出来,从而帮助小白也能读懂插件化!

本文链接:http://www.cnblogs.com/codingblock/p/6580364.html

时间: 2024-11-05 02:18:05

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

小白也能看懂的插件化DroidPlugin原理(二)-- 反射机制和Hook入门

前言:在上一篇博文<小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理>中详细介绍了 DroidPlugin 原理中涉及到的动态代理模式,看完上篇博文后你就会发现原来动态代理真的非常简单,只不过就是实现一个 InvocationHandler 接口重写一下 invoke 方法而已.不错,其实很多看似 high level 的技术都并没有想象中的那么晦涩难懂,只要你肯下定决心去了解它,去认识它,去学习它你就会发现,原来都是可以学得懂的.本篇博文将介绍 DroidPlugin 框架

Android插件化开发之DexClassLoader动态加载dex、jar小Demo

一.温故动态加载ClassLoader机制 如果对Android的ClassLoader加载机制不熟悉,猛戳Android插件化开发动态加载基础之ClassLoader工作机制 http://blog.csdn.net/u011068702/article/details/53248960 二.介绍 我们知道在Android中可以跟java一样实现动态加载jar,但是Android使用德海Dalvik VM,不能直接加载java打包jar的byte code,需要通过dx工具来优化Dalvik

小白都能看懂的block

首先说明一下,我自己也是一个小白:这是我对block的一点认识或总结,有不对的地方,希望大家指出来 block就是一个代码块,用来执行一小段程序的, 通常我们定义一个block 可以用它的基本模型,返回值类型(^变量的名字)参数类型 例如: int (^myBlock)(int ); 这就是定义了一个block 这个变量的名字为myBlock 就像是 int i:(类比有利于理解) 当我们定义完block之后,我们应该实体化它,.就像是我们定义完int i: 我们要给int i 赋值, (其实实

小白也能看懂的 Laravel 核心概念讲解

自动依赖注入 什么是依赖注入,用大白话将通过类型提示的方式向函数传递参数. 实例 1 首先,定义一个类: /routes/web.php class Bar {} 假如我们在其他地方要使用到 Bar 提供的功能(服务),怎么办,直接传入参数即可: /routes/web.php Route::get('bar', function(Bar $bar) { dd($bar); }); 访问 /bar,显示 $bar 的实例: Bar {#272} 也就是说,我们不需要先对其进行实例!如果学过 PH

Unity发布Android新手教学 (小白都能看懂的教学 )

原文地址:http://blog.csdn.net/aries_h/article/details/51673338 最近在Unity的有些交流群里,发现好多Unity开发的爱好者们都遇到了这个问题. 而且都说在网上看到好多教程弄了好几天都弄不出来,每个人都解释一遍有觉得比较繁琐. 索性我就写一个博客永久保存.希望大家会喜欢. 本文纯属个人经验之谈,如有不足,欢迎指出. 下面进入正题 如果想要让Unity可以打包Apk,你需要先下载一个JDK7以上(包括7)的版本. 并且必须是64位. 安装时请

微信开发学习日记(八):7步看懂weiphp插件机制,核心目标是响应微信请求

又经过了几个小时的梳理.回顾,截至目前,终于对weiphp这个框架的机制搞明白了些.想要完全明白,自然还需要大把的时间.第1步:   配置微信公众号,http://weiphp.jiutianniao.com/ ... .html  从上面这个配置可以看出,微信请求呗weiphp的入口文件index.php接收了,可能会被/home/weixin/index/这个action响应.第2步:   index.php入口文件,校验了是否是微信请求.   /** * 微信接入验证 * 在入口进行验证而

Unity 打包发布Android新手教学 (小白都能看懂的教学 ) [转]

版权声明:本文为Aries原创文章,转载请标明出处.如有不足之处欢迎提出意见或建议,联系QQ531193915 扫码关注微信公众号,获取最新资源 最近在Unity的有些交流群里,发现好多Unity开发的爱好者们都遇到了这个问题. 而且都说在网上看到好多教程弄了好几天都弄不出来,每个人都解释一遍有觉得比较繁琐. 索性我就写一个博客永久保存.希望大家会喜欢. 本文纯属个人经验之谈,如有不足,欢迎指出. 下面进入正题 如果想要让Unity可以打包Apk,你需要先下载一个JDK7以上(包括7)的版本.

gitbook 入门教程之小白都能看懂的 Gitbook 插件开发全流程

什么是插件 Gitbook 插件是扩展 GitBook 功能(电子书和网站)的最佳方式. 只要是 Gitbook 默认没有提供的功能,基于插件机制都可以自行扩展,是插件让 Gitbook 变得更加强大. 本文将全面介绍插件的相关知识并重点介绍插件开发的全流程,只有熟悉插件开发流程才能做到有的放矢,心中有数,进而开发出自己的插件. 关于插件请参考 Gitbook 入门教程高级进阶系列文章,本文重点讲解开发 Gitbook 的基本流程. gitbook 入门教程之插件介绍 gitbook 入门教程之

小白都能看懂的Linux系统下安装配置Zabbix

实验环境: 操作系统:Centos 7.6 服务器ip:192.168.10.100 运行用户:root 网络环境:Internet Zabbix是一个基于web界面的提供分布式系统监控及网络功能的企业级的开源监控工具,做为一个企业运维人员来说,zabbix可以给企业和运维人员带来很大的帮助,zabbix是一个功能非常强大.跨平台.开源的企业级运维管理系统,由2部分构成:zabbix服务端和客户端(agentd),也可以通过系统自带的SNMP来采集数据. Zabbix可以部署在Windows.L