人人都會設計模式---代理模式--Proxy

TigerChain發表於2017-11-22

教程大綱
教程大綱

版權宣告:本文為博主原創文章,未經博主允許不得轉載

PS:轉載請註明出處
作者: TigerChain
地址: www.jianshu.com/p/1b3b6b003…
本文出自 TigerChain 簡書 人人都會設計模式

教程簡介

  • 1、閱讀物件
    本篇教程適合新手閱讀,老手直接略過
  • 2、教程難度
    初級,本人水平有限,文章內容難免會出現問題,如果有問題歡迎指出,謝謝

正文

一、什麼是代理模式

1、生活中的代理

1、微商代理

代理在生活中就太多了,比如微商,在朋友圈中很多時候都可以看到微商說城招全國代理「不需要貨源,不需要啟動資金,只需要一個電話就能做生意,好吧我口才不好,沒有人家吹的好」,這類代理就是替賣家出售商品

2、追女孩

遙想當年情竇初開「初中的時候」,喜歡上了一個女子,可是迫於害羞,就給女孩子寫了幾封情書,買了一束花「但是自己沒有那個賊膽送」,就讓我們班裡一個和女孩認識的朋友交給她,現在想來原來幫我送情書的女生就是我的代理呀「幫我完成我想要完成的事」~~嘻嘻。話說誰還幹類似的事,就在文章末尾點個贊

3、代銷店等

其實就是現在的商店,以前小的時候聽家鄉人叫代銷店,也是一種代理模式。細細一想,跑業務的也是代理,律師也是代理,明星的助理就是代理,京東送貨機器人是代理,共享"女友",那個"女友"也是代理「你懂得」,等等等等。不敢再說了,再說萬物都成代理了「不好意思,又忘了吃藥了」

2、程式中的代理

其實程式中使用的代理是非常多的,我們在編寫 MVC 業務的時候就可以使用代理模式「可以讓客戶端使用代理仿問介面」,一般使用最多的是動態代理

代理模式的定義

所謂代理就是代表某個真實物件,也就是代理拿到真實物件的引用然後就可以實現真實物件中的功能了

代理模式的結構

角色 類別 說明
AbstractObject 介面或抽象類 抽象出共同的屬性
RealObject 真實的類 實現了抽象角色
Prxoy 代理的類 實現了抽象角色,持有真實類的引用

代理模式簡單的 UML

代理模式簡單的 UML
代理模式簡單的 UML

代理模式的分類

  • 遠端代理:為不同地理的物件提供區域網代表物件
  • 虛擬代理:根據需要將資源消耗很大的物件進行延遲,真正需要的時候再建立
  • 安全代理:控制使用者的訪問許可權
  • 智慧代理:提供對目標物件額外的服務「使用最多的」

代理模式的實現方式「屬於智慧代理」

  • 靜態代理方法
  • 動態代理方法

二、代理模式舉例

1、幫忙追 MM

話說在高中期間,小明看上了我們班一位女同學,可是小明是一個害羞膽小的人「有賊心沒賊膽」,於是小明跑到我的跟前:Chain 哥,我看上了我們們班的小倩,你能幫我追一下嗎 .... 。聽小明巴拉巴拉一大堆,本著哥們義氣的我非常爽快的答應了,就有了下面的追 MM 手段

簡單的 UML

我幫小明追 MM
我幫小明追 MM

根據 UML 擼碼--這裡使用靜態代理方法

  • 1、要追 MM 首先肯定有 MM ,定義 MM.java
public class MM {
    private String name ; // 姓名 
    private int age ;//年齡 
    private String address ; // 住址

    public MM(String name){
        this.name = name ;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}複製程式碼
  • 2、定義一個追 MM 方法的介面 ZhuimmWay.java
/**
 * Created by TigerChain
 * 追 MM 的方法,是一個抽象角色
 */
public interface ZhuimmWay  {
    // 送花
    void giveFlowers() ;
    // 寫情書
    void writeLoveLetters() ;
    // 買衣服
    void buyClothes() ;
    // 幹一些其它的事
    void doSomthing() ;
}複製程式碼
  • 3、主人公小明上場 XiaoMing.java
/**
 * Created by TigerChain
 * 主人公小明,真正的角色
 */
public class XiaoMing implements ZhuimmWay {

    // 要追的 MM
    private MM mm ;

    public void like(MM mm){
        this.mm = mm ;
    }

    @Override
    public void giveFlowers() {
        System.out.println(mm.getName()+" 送給你一朵花");
    }

    @Override
    public void writeLoveLetters() {
        System.out.println(mm.getName()+" 給你八封情書");
    }

    @Override
    public void buyClothes() {
        System.out.println(mm.getName()+" 這是給你買的衣服");
    }

    @Override
    public void doSomthing() {
        System.out.println("給 "+mm.getName()+"說好聽的話");
        System.out.println("給 "+mm.getName()+"洗衣服,買單等等一系列手段");
    }
}複製程式碼
  • 4、代理人 TigerChain 上場 ProxyTigerChain.java
/**
 * Created by TigerChain
 * 代理人,我上場了,感覺像媒婆
 */
public class ProxyTigerChain implements ZhuimmWay {

    private XiaoMing xiaoMing ;

    public ProxyTigerChain(XiaoMing xiaoMing, MM mm){
        this.xiaoMing = xiaoMing ;
        xiaoMing.like(mm);
    }

    @Override
    public void giveFlowers() {
        xiaoMing.giveFlowers();
    }

    @Override
    public void writeLoveLetters() {
        xiaoMing.writeLoveLetters();
    }

    @Override
    public void buyClothes() {
        xiaoMing.buyClothes();
    }

    @Override
    public void doSomthing() {
        xiaoMing.doSomthing();
    }
}複製程式碼
  • 5、一切準備就緒,開始追吧,來個測試類 Test.java
public class Test {
    public static void main(String args[]){
        // 主人公小明
        XiaoMing xiaoMing = new XiaoMing();
        // 要追的人小倩
        MM xiaoqian = new MM("小倩") ;

        // 小明委託我去幫他追小倩
        ProxyTigerChain proxyChain = new ProxyTigerChain(xiaoMing,xiaoqian) ;
        proxyChain.giveFlowers();
        proxyChain.writeLoveLetters();
        proxyChain.buyClothes();
        proxyChain.doSomthing();
    }
}複製程式碼
  • 6、執行檢視結果

追 MM 方法驗證
追 MM 方法驗證

上面的程式碼完美嗎?完美個鳥鳥,試想把 Test 比做一個場景:比如是在 KTV ,我靠,小明不是害羞嗎?竟然也出現在 KTV 中「如果小明能當明看著你幫他追小倩,早就自己動手了」,所以按正常邏輯小明不應該出現在 KTV「Test 中」

  • 7、修改程式碼,我們新增一個 ZhuimmFactory.java
/**
 * Created by TigerChain
 * 定義一個工廠類,這樣就遮蔽了客戶端對代理的感知
 */
public class ZhuimmFactory {

    public static ZhuimmWay getInstance(String name){
        return new ProxyTigerChain(new XiaoMing(),new MM(name)) ;
    }
}複製程式碼

嘻嘻,不知不覺又用到以前學到的簡單工廠模式了「學以致用,不錯不錯」,我們把代理事情都放在工廠中去做,這樣客戶端對代理是無感知的,這也符合程式開發的正常邏輯

  • 8、修改 Test 端呼叫程式碼
public class Test {
    public static void main(String args[]){
        // 呼叫者不知道呼叫的是代理類還是真實類,這才是正常的邏輯呀
        ZhuimmWay zhuimmWay = ZhuimmFactory.getInstance("小倩") ;
        zhuimmWay.giveFlowers();
        zhuimmWay.writeLoveLetters();
        zhuimmWay.buyClothes();
        zhuimmWay.doSomthing();
    }
}複製程式碼
  • 9、執行檢視結果

追 MM 方法驗證
追 MM 方法驗證

想知道結局嗎?很不幸,小倩也有點"白痴",我提醒好多次是小明喜歡她「其實我最多是代理小明送花等這些事情,也就是說錢花小明的,美女我來追」,可是她最終還是看上我了「有點自戀」,所以以後追 MM 的時候,千萬千萬不要找代理「以上故事純屬虛構,如有雷同,那麼小明以後就張點心吧」

2、真假美猴王

1、使用靜態代理完成

六耳獼猴夢想簡單的 UML

六耳獼猴夢想簡單的 UML
六耳獼猴夢想簡單的 UML

根據 UML 擼碼

  • 1、定義抽象介面 IToWest.java
/**
 * Created 抽象類,去西天的條件
 */
public interface IToWest {
    //保護唐僧
    void baohuTangSeng() ;
    //降妖除魔
    void xiangYaoChuMo() ;
    //上天入地
    void shangTianRuDi() ;
}複製程式碼
  • 2、定義孫悟空類 SunWuKong.java
/**
 * Created by Tigerchain
 * 悟空
 */
public class SunWuKong implements IToWest{
    @Override
    public void baohuTangSeng() {
        System.out.println("我孫悟空能 保護唐僧");
    }

    @Override
    public void xiangYaoChuMo() {
        System.out.println("我孫悟空能 降妖除魔");
    }

    @Override
    public void shangTianRuDi() {
        System.out.println("我孫悟空能 能上天入地");
    }
}複製程式碼
  • 3、定義六耳獼猴類「代理角色」 LiuErMiHou.java
package prxoy.monkeyking;

/**
 * Created by Tigerchain
 * 悟空的代理六耳獼猴
 */
public class LiuErMiHou extends SunWuKong implements IToWest {

    @Override
    public void baohuTangSeng() {
        super.baohuTangSeng();
    }

    @Override
    public void xiangYaoChuMo() {
        super.xiangYaoChuMo();
    }

    @Override
    public void shangTianRuDi() {
        super.shangTianRuDi();
    }
}複製程式碼
  • 4、測試 Test.java
/**
 * Created by TigerChain
 * 測試類 六耳 代理悟空
 */
public class Test {
    public static void main(String args[]){

        IToWest liuErMiHou = new LiuErMiHou() ;
        liuErMiHou.baohuTangSeng();
        liuErMiHou.xiangYaoChuMo();
        liuErMiHou.shangTianRuDi();

        System.out.println("我孫悟空能去得了西天");
    }
}複製程式碼
  • 5、執行檢視結果

結果
結果

好了,上面我們看到我們使用代理類直接繼承了真實的類「這也是代理的一個變種」,但是根據多用類組合少用繼承的規則,我們還是少用這種繼承形式的代理

以上是靜態代理,靜態代理有侷限性,想如果悟空多了項技能,六耳獼猴就得學此項技能「感覺很像我們搞技術的,技術日新月異,得不斷的學習才能進步」

靜態代理的缺點:

  • 1、代理的方法如果很多,那麼就要為每個方法都要代理,規模大的程式受不了
  • 2、如果真實類中新新增一個方法或功能,那麼代理類中就一一對應的寫出來,這樣不利於擴充套件並且增加程式碼維護成本
  • 3、一個代理類只能代理一個真實的物件
2、使用動態代理完成

動態代理就是代理類不是在程式碼中定義的,而是根據我們的指示動態生成的「通過反射機制動態生成代理者物件」,在編碼階段,你從程式碼上根本不知道誰代理誰,具體代理誰,好吧太繞了,直接看程式碼

1、Proxy 類

說動態代理之前,我們先來看看 Java 中提供的 Proxy 類

看看這個類的註釋一部分

/* {@code Proxy} provides static methods for creating dynamic proxy
 * classes and instances, and it is also the superclass of all
 * dynamic proxy classes created by those methods.
 * .....
 *
/
 public class Proxy implements java.o.Serializable {
     .... 省略程式碼 
 }複製程式碼

從註釋可以看出 Proxy 提供一些靜態方法來建立動態代理類和例項

Proxy 簡單的 UML

Proxy 簡單的 UML
Proxy 簡單的 UML

Proxy 主要方法講解

Proxy 主要方法就是 newProxyInstance 這個方法

   public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){

    ... // 省略若干程式碼 

    // 取得代理類
    Class<?> cl = getProxyClass0(loader, intros)

    ... // 省略若干程式碼 

    // 呼叫代理類的構造方法
    final Constructor<?> cons = cl.getConstructor(constructorParams);

    ... // 省略若干程式碼 

    final InvocationHandler ih = h;

    ... // 省略若干程式碼 

    // 通過代理類的構造方法生成代理類的例項
    return cons.newInstance(new Object[]{h});

   }複製程式碼

其中三個引數:

  • ClassLoader loader:代理類的類載入器
  • Class<?>[] interfaces:代理類要實現的介面列表
  • InvocationHandler h:呼叫處理程式

從 newProxyInstance 方法中我們知道了代理物件是如何產生的了「註釋很清楚了」

再看看 InvocationHandler

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}複製程式碼

其中三個引數:

  • Object proxy: 被代理的物件
  • Method method:要操作的方法
  • Object[] args:方法要傳入的引數,可以沒有,也可以有多個或 null

InvocationHandler 介面中的方法就是執行被代理物件中的方法

2、使用動態代理修改真假美猴王程式碼

動態代理悟空 簡單的UML

動態代理悟空 簡單的UML
動態代理悟空 簡單的UML

根據 UML 擼碼

只需要在原有程式碼的基礎上新增一個動態類並且刪掉六耳獼猴類「動態代理來了,小六你還不快撤」,然後修改 Test 即可

  • 1、新增動態代理類 ToWestProxy.java
/**
 * 動態代理類
 */
public class ToWestProxy implements InvocationHandler {
    // 需要代理的物件即真實物件
    private Object delegate ;

    public Object getProxy(Object delegate){
        this.delegate = delegate ;
        // 動態構建一個代理
        return  Proxy.newProxyInstance(delegate.getClass().getClassLoader(),delegate.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(delegate,args) ; // 通過反射呼叫真實物件對應的方法
    }
}複製程式碼

我們看到上在被代理的物件是一個 Object 型別,所以可以看出這個代理類就是一個萬能的代理,不僅僅可以代理悟空,牛魔王也能代理「扯遠了」

  • 2、修改 Test.java
/**
 * Created by TigerChain
 * 測試類
 */
public class Test {
    public static void main(String args[]){

        IToWest sunWuKong = new SunWuKong() ;

        // 取得動態代理
        IToWest proxy = (IToWest) new ToWestProxy().getProxy(sunWuKong);

        proxy.baohuTangSeng();
        proxy.xiangYaoChuMo();
        proxy.shangTianRuDi();
        System.out.println("我孫悟空能去得了西天");
    }
}複製程式碼

看到了,真實物件悟空隨便你改,我再新增介面,方法,我動態代理不用動「如果是靜態代理六耳獼猴,那就得隨著悟空的修改必須得修改自己」

而且,我們還可以得出,這個動態代理不僅僅可以代理悟空,簡直可以代理一切物件「不信你定義一個牛魔王試試」

  • 3、執行檢視結果

結果
結果

簡直 perfect 有木有

3、自動售票機

隨著科技的發達,我們現在買車票的時候可以在自動售票機「代理售票人員」上購買

自動售票機簡單的 UML

自動售票機簡單的 UML
自動售票機簡單的 UML

根據 UML 擼碼--採用動態代理技術

  • 1、先來一個抽象角色 ISellTicket.java
/**
 * Created by TigerChain
 * 定義一個抽象介面
 */
public interface ISellTicket {
    // 售票
    void sellTicket() ;
}複製程式碼
  • 2、要出票,當然有買的票的人 User.java
/**
 * Created by TigerChain
 * 買票的人
 */
public class User {
    private String uname ; //姓名
    private String address ; // 地址
    private String sex ;     // 性別
    private String idNum ;   // 身份證號
    private String pay ;     // 掏票錢

    public String getUname() {
        return name;;
    }

    public void setUname(String uname) {
        this.uname = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getIdNum() {
        return idNum;
    }

    public void setIdNum(String idNum) {
        this.idNum = idNum;
    }

    public String getPay() {
        return pay;
    }

    public void setPay(String pay) {
        this.pay = pay;
    }
}複製程式碼
  • 3、真實物件售票員小張 XiaoZhangSeller.java
/**
 * Created 真實的售票員小張
 */
public class XiaoZhangSeller implements ISellTicket {

    private User user ;

    public XiaoZhangSeller(User user){
        this.user = user ;
    }

    @Override
    public void sellTicket() {
        if(null !=user){
            System.out.println("買票者的資訊===============");

            System.out.println("買票者姓名:"+user.getUname());
            System.out.println("買票性別:"+user.getSex());
            System.out.println("買票者身份證號:"+user.getIdNum());
            System.out.println("買票者住址:"+user.getUname());

            System.out.println("==============================") ;

            System.out.println("正在驗證資訊...資訊無誤,請支付票錢");
            System.out.println("買票者支付:"+user.getPay()+" 元");
            System.out.println("請稍等正在出票.....");
            System.out.println("出票成功:從西安到寶雞大巴進站去坐");
        }
    }
}複製程式碼
  • 4、動態代理 DyAutoSellerProxy.java

/**
 * Created by TigerChain
 * 自動出票機,為了演示名字這樣想,其實這是一個萬能的動態代理
 */
public class DyAutoSellerProxy implements InvocationHandler {

    private Object object ;

    public DyAutoSellerProxy(Object object){
        this.object = object ;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(object,args) ;
    }
}複製程式碼
  • 5、測試一下 Test.java
/**
 * Created by TigerChain
 * 測試類
 */
public class Test {
    public static  void main(String args[]){
        // 定義個買票者
        User tigerChain = new User() ;
        tigerChain.setUname("TigerChain");
        tigerChain.setAddress("中國陝西");
        tigerChain.setSex("男");
        tigerChain.setIdNum("610326************");
        tigerChain.setPay("45.00");

        // 真實的買票員小張
        ISellTicket iSellTicket = new XiaoZhangSeller(tigerChain) ;

        // 動態代理
        DyAutoSellerProxy dyAutoSellerProxy = new DyAutoSellerProxy(iSellTicket) ;

        // 動態建立一個出票機,把出票交給出票機去處理
        ISellTicket iSellTicket1 = (ISellTicket) Proxy.newProxyInstance(iSellTicket.getClass().getClassLoader(),iSellTicket.getClass().getInterfaces(),dyAutoSellerProxy);

        iSellTicket1.sellTicket();
    }
}複製程式碼
  • 6、執行檢視結果

自動出票機結果
自動出票機結果

自麼樣一個自動售票機就完成了「完全代理了人工去賣票」

PS:這個 Demo 使用動態代理實現的,請大家自行使用靜態代理實現本 Demo ,一定要動手實踐哦

4、AIDL 進行程式間通訊「遠端代理」

AIDL「Android 介面定義語言,是一種語言,其實就是 Android 中的遠端 Service」,再說 AIDL 之前就不得不說 Binder「這裡簡潔明瞭的說一下 Binder 是什麼,不展開深入討論,如果深入展開,三天三夜也說不完」

什麼是 Binder

由於兩個程式不能直接進行通訊「為了安全系統有程式隔離機制」,所以兩個程式之間是不能直接進行通訊的。Binder 可以說是 Android 系統中最重要的架構之一。Binder 是連線 Client「程式」 和 Server「程式」 的一個橋樑,Binder 是程式間通訊的方式之一,在 Android 用的灰常灰常的多

我們先來看看 Android 的架構影象

Android 的架構圖
Android 的架構圖

圖片來自 Android 的原始碼官站:source.android.com/devices/

從 Android 的框架圖中我們可以看到,應用程式框架層和系統服務層之間就是通過 Binder IPC 進行通訊的,說 Binder 機制前,我們先了解幾個特點

  • 1、兩個程式之間不能直接通訊
  • 2、核心可以仿問程式中的所有資料
  • 3、兩個程式之間不能直接進行通訊,我們可以藉助核心做中轉達到間接通訊的目的「Binder 就是這種機制」

Binder 下兩個程式通訊的簡易流程

binder_arc
binder_arc

PS: 以上圖是便於理解所以抽象出來一張圖,真實的 Binder 比這個過程複雜的多,這牽扯到 java 層的 Binder ,native 層的 Binder 等等「這不是我們討論的重點」,方便我們理解,我們可以認為客戶端的程式拿到服務端的引用,所以就可以呼叫服務端程式的方法了

說了這麼多,這跟代理有個毛關係呢,別急我們寫一個 AIDL 的例項分析一下:

AIDL demo 簡單的 UML

AIDL demo 簡單的 UML
AIDL demo 簡單的 UML

根據 uml 寫程式碼

我們寫一個簡單的通過 Client 程式呼叫 Server 程式返回一個字串功能,為了方便起見,我們直接在一個專案中建立「Server 開啟在另一個程式中,開兩個 APP 進行通訊大家可以自行試一下,道理一模一樣的」

  • 1、在專案中新建一個 AIDL 檔案「在 AS 中的 APP上直接右鍵 new AIDL 即可」
interface CustomAIDL {

    String getStr() ;
}複製程式碼

此時我們點選一下

圖示構造一下專案,此時會在 app\build\generated\source\aidl\debug\包名\CustomAIDL.java 檔案「把 AS 切換到 project 檢視下很容易找到」,這是 IDE 幫我們自動生成的

  • 2、定義一個遠端服務 AIDLRemoteService.java
/**
 * @Description 建立一個遠端服務
 * @Creator TigerChain(建立者)
 */
public class AIDLRemoteService extends Service {

    private final CustomAIDL.Stub aidl = new CustomAIDL.Stub() {
        @Override
        public String getStr() throws RemoteException {
            return " 我是遠端服務返回的 HELLO ";
        }
    } ;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return aide;
    }
}複製程式碼
  • 3、定義 AidlActivity 測試呼叫 「核心程式碼給出,其餘程式碼看 Demo 即可」
public class AidlActivity extends AppCompatActivity implements View.OnClickListener{

 private CustomAIDL customAIDL ;
... 省略若干程式碼

// 客戶端連線服務
private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            customAIDL = CustomAIDL.Stub.asInterface(service) ;
            Log.e("service:","onServiceConnected") ;
            isServerStarted = true ;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            customAIDL = null ;
            Log.e("service:","onServiceDisconnected") ;
            isServerStarted = false ;

        }
    } ;

 @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_bind_service:
                // 繫結服務
                bindService(new Intent(AidlActivity.this,AIDLRemoteService.class),serviceConnection, Context.BIND_AUTO_CREATE) ;
                break ;
            case R.id.btn_test_method:
                if(!isServerStarted){
                    Toast.makeText(AidlActivity.this,"請先繫結服務先",Toast.LENGTH_SHORT).show();
                    return ;
                }
                try {
                    String str = customAIDL.getStr();
                    Toast.makeText(AidlActivity.this,str,Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();

                }
                break ;
            default:
                break ;
        }
    }

... 省略若干程式碼

}複製程式碼
  • 4、在 mainifests 中註冊服務
 <service android:name=".Proxy.AIDL.AIDLRemoteService"
            android:process=":reomte"></service>複製程式碼

我這裡給服務定義了一個 process ,那說明這個服務是執行在一個新程式中的

  • 5、測試一下,執行檢視結果

我們看一下當前專案程式情況

當前 Demo 程式情況
當前 Demo 程式情況

的確是兩個程式「AidlActivity 和 AIDLRemoteService 分別在兩個程式中」,我們定義的 remote 也顯示出來了,看一下結果

AIDL demo 結果
AIDL demo 結果

怎麼樣,兩個程式之間完美的進行了通訊了

通個毛呢?這和 proxy 有個啥關係呀「巴拉巴拉這麼久」,不要急嗎?軟體開發有一條宗旨:先讓它執行起來「我們先把 Demo 執行起來再說嗎:咳咳又到了吃藥的時間了」,我們來分析一下上面的呼叫過程

過程分析

  • 1、還記得我們上面說的 AD 幫我們自動生成的 CustomAIDL.java 檔案嗎,我們來一窺它的真容「以下程式碼是格式化後的」

// 這裡的 IInterface 代表遠端 Server 物件有什麼能力
public interface CustomAIDL extends android.os.Interface {
    /**
     * Local-side IPC implementation stub class.
     */
    // 在 server 端呼叫
    public static abstract class Stub extends android.os.Binder implements designpattern.jun.com.designpattern.CustomAIDL {
        private static final java.lang.String DESCRIPTOR = "designpattern.jun.com.designpattern.CustomAIDL";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an designpattern.jun.com.designpattern.CustomAIDL interface,
         * generating a proxy if needed.
         * 其中的 android.os.IBinder obj 物件是驅動給們的,這個就是我們繫結 service ,在 onServiceConnecttion 回撥裡面這個物件拿到一個遠端的 Service 
         */
        public static designpattern.jun.com.designpattern.CustomAIDL asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof designpattern.jun.com.designpattern.CustomAIDL))) {
                // client 和 Server 在同一個程式呼叫 後面 debug 可以驗證
                return ((designpattern.jun.com.designpattern.CustomAIDL) win);
            }
            // cliet 和 Server 不在同一個程式呼叫代理物件 後面 debug 可以驗證
            return new designpattern.jun.com.designpattern.CustomAIDL.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            // 給客戶端寫資料
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getStr: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getStr();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        // 執行在客戶端 server 程式的遠端代理,實現對遠端物件的仿問
        private static class Proxy implements designpattern.jun.com.designpattern.CustomAIDL {
            private android.os.Binder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.lang.String getStr() throws android.os.RemoteException {
                // 讀取服務端寫過來的資料
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getStr, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getStr = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public java.lang.String getStr() throws android.os.RemoteException;
}複製程式碼

這下看到 Proxy 了吧「是不是有點小激動呢」,我們來分析一下

AIDL 簡單的流程圖
AIDL 簡單的流程圖

上面的圖就是一個簡單的 AIDL 的流程圖,方便理解認為 CustomAIDL.stub 就是遠端程式,它把資訊註冊到 Binder 中, CustomAIDL.Stub.Proxy 就是一個代理,代理什麼呢?代理遠端的 Binder ,遠端 Binder 把方法傳給 Client 就完成了兩個程式間通訊「詳細過程比這個複雜」,對於 Binder 的入門介紹可以參看:Binder 學習指南 還是非常不錯的,建議看三遍以上

PS:這裡再說一點,以上情況是針對 client 和 server 在兩個程式間的通訊,如果 client 和 server 在一個程式中,則 CustomAIDL.Stub.Proxy 就不會呼叫「在同一個程式中,我自己就能調自己還代理個毛呀」,不信?以結果征服你

client 和 server 同一程式和不同程式分析

  • 1、不同程式
 <service android:name=".Proxy.AIDL.AIDLRemoteService"
            android:process=":reomte"></service>複製程式碼

通過以上配置,我們可以看到 AIDLRemoteService 是執行在單獨程式中的,我們在 CustomAIDL.java 中的 asInterface 方法中 debug 跟一下看看結果

AIDL 呼叫 Proxy
AIDL 呼叫 Proxy

通過圖我們可以看出,如果 client 和 server 不在同一個程式中,那麼程式碼就會走到

呼叫代理
呼叫代理

呼叫代理的地方---CustomAIDL.Stub.Proxy,並傳遞遠端代理的物件

  • 2、在同一程式

去掉 service 中的 android:process=":reomte" 則 client 和 server 就在同一程式了

 <service android:name=".Proxy.AIDL.AIDLRemoteService"/>複製程式碼

同理 debug 看結果

不呼叫 proxy
不呼叫 proxy

對比上面的圖我們就知道了,這裡的 iin 不為空,進入了 if 的方法體「沒有呼叫代理」,至此上面的結果驗證完畢

關於 AIDL 遠端代理就說到這裡了,如果對 Binder 想要深入瞭解,可以自行回去研究「這不在本節的範圍內」

WTF 一個 AIDL 說了這麼大半天,希望大家不要暈「我都有點暈了」

原始碼地址: github.com/githubchen0… 看 proxy/aidl 這部分

三、Android 原始碼中的代理模式

其實通過上面的 AIDL 實驗,我們就可以知道 Binder 使用的就是遠端代理模式,Android 中的原始碼使用非常多,我就不一一分析了「說的太多人會受不鳥」,感興趣的朋友可以自行分析,我這裡貼出一張圖,大家可以看

IPC
IPC

我們看看應用程式框架層的 XXXManager 對應田系統層的 XXXService 它們之間通過使用 AIDL 來進行跨程式通訊,有興趣可以扒扒這部分的原始碼看一下

四、代理模式的優缺點

優點

  • 1、代理模式拿到的真實物件的引用,把真實物件很好的保護起來安全性高
  • 2、擴充套件性好

缺點

  • 增加了系統的複雜度,增加了額外好多的程式碼「設計模式好像都是這樣」

到此為止,我們把代理模式就說完了,由於這篇篇幅比較大,Android 原始碼也沒有給大家分析「希望大家自行去看看,希望你有一種哦~原來是這樣的趕腳」,其它的虛擬代理,快取代理大家有興趣也可以試試

點贊是一種態度,是一種鼓勵「雖然你點不點我都會繼續」

參考資料

近期發現自己的文章被別人轉載不加出處「很是傷心,文章都是晚上加班趕出來的,其中文章的圖更是耗費了大量的時間,希望大家能理解,以後文章全部首發公號中」

以後文章會第一時間發在公號,請大家新增博文的公號,掃描新增即可關注
公眾號:TigerChain

TigerChain
TigerChain

相關文章