05、代理模式--Proxy

教程大綱

版權(quán)聲明:本文為博主原創(chuàng)文章西雀,未經(jīng)博主允許不得轉(zhuǎn)載

PS:轉(zhuǎn)載請(qǐng)注明出處
作者: TigerChain
地址: http://www.reibang.com/p/1b3b6b003032
本文出自 TigerChain 簡(jiǎn)書 人人都會(huì)設(shè)計(jì)模式

教程簡(jiǎn)介

  • 1蟹但、閱讀對(duì)象
    本篇教程適合新手閱讀,老手直接略過
  • 2栈戳、教程難度
    初級(jí),本人水平有限难裆,文章內(nèi)容難免會(huì)出現(xiàn)問題子檀,如果有問題歡迎指出镊掖,謝謝

正文

一、什么是代理模式

1褂痰、生活中的代理

1亩进、微商代理

代理在生活中就太多了,比如微商缩歪,在朋友圈中很多時(shí)候都可以看到微商說城招全國(guó)代理「不需要貨源归薛,不需要啟動(dòng)資金,只需要一個(gè)電話就能做生意主籍,好吧我口才不好,沒有人家吹的好」骗污,這類代理就是替賣家出售商品

2崇猫、追女孩

遙想當(dāng)年情竇初開「初中的時(shí)候」沈条,喜歡上了一個(gè)女子需忿,可是迫于害羞,就給女孩子寫了幾封情書蜡歹,買了一束花「但是自己沒有那個(gè)賊膽送」屋厘,就讓我們班里一個(gè)和女孩認(rèn)識(shí)的朋友交給她,現(xiàn)在想來原來幫我送情書的女生就是我的代理呀「幫我完成我想要完成的事」~~嘻嘻月而。話說誰還干類似的事汗洒,就在文章末尾點(diǎn)個(gè)贊

3、代銷店等

其實(shí)就是現(xiàn)在的商店父款,以前小的時(shí)候聽家鄉(xiāng)人叫代銷店溢谤,也是一種代理模式。細(xì)細(xì)一想憨攒,跑業(yè)務(wù)的也是代理世杀,律師也是代理,明星的助理就是代理肝集,京東送貨機(jī)器人是代理瞻坝,共享"女友",那個(gè)"女友"也是代理「你懂得」,等等等等杏瞻。不敢再說了所刀,再說萬物都成代理了「不好意思,又忘了吃藥了」

2捞挥、程序中的代理

其實(shí)程序中使用的代理是非常多的浮创,我們?cè)诰帉?MVC 業(yè)務(wù)的時(shí)候就可以使用代理模式「可以讓客戶端使用代理仿問接口」,一般使用最多的是動(dòng)態(tài)代理

代理模式的定義

所謂代理就是代表某個(gè)真實(shí)對(duì)象砌函,也就是代理拿到真實(shí)對(duì)象的引用然后就可以實(shí)現(xiàn)真實(shí)對(duì)象中的功能了

代理模式的結(jié)構(gòu)

角色 類別 說明
AbstractObject 接口或抽象類 抽象出共同的屬性
RealObject 真實(shí)的類 實(shí)現(xiàn)了抽象角色
Prxoy 代理的類 實(shí)現(xiàn)了抽象角色斩披,持有真實(shí)類的引用

代理模式簡(jiǎn)單的 UML

代理模式簡(jiǎn)單的 UML

代理模式的分類

  • 遠(yuǎn)程代理:為不同地理的對(duì)象提供局域網(wǎng)代表對(duì)象
  • 虛擬代理:根據(jù)需要將資源消耗很大的對(duì)象進(jìn)行延遲,真正需要的時(shí)候再創(chuàng)建
  • 安全代理:控制用戶的訪問權(quán)限
  • 智能代理:提供對(duì)目標(biāo)對(duì)象額外的服務(wù)「使用最多的」

代理模式的實(shí)現(xiàn)方式「屬于智能代理」

  • 靜態(tài)代理方法
  • 動(dòng)態(tài)代理方法

二、代理模式舉例

1雏掠、幫忙追 MM

話說在高中期間斩祭,小明看上了我們班一位女同學(xué),可是小明是一個(gè)害羞膽小的人「有賊心沒賊膽」乡话,于是小明跑到我的跟前:Chain 哥摧玫,我看上了咱們班的小倩,你能幫我追一下嗎 .... 绑青。聽小明巴拉巴拉一大堆诬像,本著哥們義氣的我非常爽快的答應(yīng)了,就有了下面的追 MM 手段

簡(jiǎn)單的 UML

我?guī)托∶髯?MM

根據(jù) UML 擼碼--這里使用靜態(tài)代理方法

  • 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、定義一個(gè)追 MM 方法的接口 ZhuimmWay.java
/**
 * Created by TigerChain
 * 追 MM 的方法邪乍,是一個(gè)抽象角色
 */
public interface ZhuimmWay  {
    // 送花
    void giveFlowers() ;
    // 寫情書
    void writeLoveLetters() ;
    // 買衣服
    void buyClothes() ;
    // 干一些其它的事
    void doSomthing() ;
}
  • 3降狠、主人公小明上場(chǎng) 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 上場(chǎng) ProxyTigerChain.java
/**
 * Created by TigerChain
 * 代理人,我上場(chǎng)了吕晌,感覺像媒婆
 */
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蛋褥、一切準(zhǔn)備就緒,開始追吧睛驳,來個(gè)測(cè)試類 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烙心、運(yùn)行查看結(jié)果
追 MM 方法驗(yàn)證

上面的代碼完美嗎?完美個(gè)鳥鳥乏沸,試想把 Test 比做一個(gè)場(chǎng)景:比如是在 KTV 淫茵,我靠,小明不是害羞嗎屎蜓?竟然也出現(xiàn)在 KTV 中「如果小明能當(dāng)明看著你幫他追小倩痘昌,早就自己動(dòng)手了」,所以按正常邏輯小明不應(yīng)該出現(xiàn)在 KTV「Test 中」

  • 7炬转、修改代碼辆苔,我們添加一個(gè) ZhuimmFactory.java
/**
 * Created by TigerChain
 * 定義一個(gè)工廠類,這樣就屏蔽了客戶端對(duì)代理的感知
 */
public class ZhuimmFactory {

    public static ZhuimmWay getInstance(String name){
        return new ProxyTigerChain(new XiaoMing(),new MM(name)) ;
    }
}

嘻嘻扼劈,不知不覺又用到以前學(xué)到的簡(jiǎn)單工廠模式了「學(xué)以致用驻啤,不錯(cuò)不錯(cuò)」,我們把代理事情都放在工廠中去做荐吵,這樣客戶端對(duì)代理是無感知的骑冗,這也符合程序開發(fā)的正常邏輯

  • 8赊瞬、修改 Test 端調(diào)用代碼
public class Test {
    public static void main(String args[]){
        // 調(diào)用者不知道調(diào)用的是代理類還是真實(shí)類,這才是正常的邏輯呀
        ZhuimmWay zhuimmWay = ZhuimmFactory.getInstance("小倩") ;
        zhuimmWay.giveFlowers();
        zhuimmWay.writeLoveLetters();
        zhuimmWay.buyClothes();
        zhuimmWay.doSomthing();
    }
}
  • 9贼涩、運(yùn)行查看結(jié)果
追 MM 方法驗(yàn)證

想知道結(jié)局嗎巧涧?很不幸,小倩也有點(diǎn)"白癡"遥倦,我提醒好多次是小明喜歡她「其實(shí)我最多是代理小明送花等這些事情谤绳,也就是說錢花小明的,美女我來追」袒哥,可是她最終還是看上我了「有點(diǎn)自戀」缩筛,所以以后追 MM 的時(shí)候,千萬千萬不要找代理「以上故事純屬虛構(gòu)堡称,如有雷同瞎抛,那么小明以后就張點(diǎn)心吧」

2、真假美猴王

1却紧、使用靜態(tài)代理完成

六耳獼猴夢(mèng)想簡(jiǎn)單的 UML

六耳獼猴夢(mèng)想簡(jiǎn)單的 UML

根據(jù) UML 擼碼

  • 1桐臊、定義抽象接口 IToWest.java
/**
 * Created 抽象類,去西天的條件
 */
public interface IToWest {
    //保護(hù)唐僧
    void baohuTangSeng() ;
    //降妖除魔
    void xiangYaoChuMo() ;
    //上天入地
    void shangTianRuDi() ;
}

  • 2啄寡、定義孫悟空類 SunWuKong.java
/**
 * Created by Tigerchain
 * 悟空
 */
public class SunWuKong implements IToWest{
    @Override
    public void baohuTangSeng() {
        System.out.println("我孫悟空能 保護(hù)唐僧");
    }

    @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、測(cè)試 Test.java
/**
 * Created by TigerChain
 * 測(cè)試類 六耳 代理悟空
 */
public class Test {
    public static void main(String args[]){

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

        System.out.println("我孫悟空能去得了西天");
    }
}
  • 5挺物、運(yùn)行查看結(jié)果
結(jié)果

好了,上面我們看到我們使用代理類直接繼承了真實(shí)的類「這也是代理的一個(gè)變種」飘弧,但是根據(jù)多用類組合少用繼承的規(guī)則识藤,我們還是少用這種繼承形式的代理

以上是靜態(tài)代理,靜態(tài)代理有局限性次伶,想如果悟空多了項(xiàng)技能痴昧,六耳獼猴就得學(xué)此項(xiàng)技能「感覺很像我們搞技術(shù)的,技術(shù)日新月異冠王,得不斷的學(xué)習(xí)才能進(jìn)步」

靜態(tài)代理的缺點(diǎn):

  • 1赶撰、代理的方法如果很多,那么就要為每個(gè)方法都要代理柱彻,規(guī)模大的程序受不了
  • 2豪娜、如果真實(shí)類中新添加一個(gè)方法或功能,那么代理類中就一一對(duì)應(yīng)的寫出來哟楷,這樣不利于擴(kuò)展并且增加代碼維護(hù)成本
  • 3瘤载、一個(gè)代理類只能代理一個(gè)真實(shí)的對(duì)象
2、使用動(dòng)態(tài)代理完成

動(dòng)態(tài)代理就是代理類不是在代碼中定義的卖擅,而是根據(jù)我們的指示動(dòng)態(tài)生成的「通過反射機(jī)制動(dòng)態(tài)生成代理者對(duì)象」鸣奔,在編碼階段墨技,你從代碼上根本不知道誰代理誰,具體代理誰挎狸,好吧太繞了扣汪,直接看代碼

1、Proxy 類

說動(dòng)態(tài)代理之前锨匆,我們先來看看 Java 中提供的 Proxy 類

看看這個(gè)類的注釋一部分

/* {@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 提供一些靜態(tài)方法來創(chuàng)建動(dòng)態(tài)代理類和實(shí)例

Proxy 簡(jiǎn)單的 UML

Proxy 簡(jiǎn)單的 UML

Proxy 主要方法講解

Proxy 主要方法就是 newProxyInstance 這個(gè)方法

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

    ... // 省略若干代碼 
     
    // 取得代理類
    Class<?> cl = getProxyClass0(loader, intros)

    ... // 省略若干代碼 

    // 調(diào)用代理類的構(gòu)造方法
    final Constructor<?> cons = cl.getConstructor(constructorParams);
 
    ... // 省略若干代碼 
   
    final InvocationHandler ih = h;
    
    ... // 省略若干代碼 

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

其中三個(gè)參數(shù):

  • ClassLoader loader:代理類的類加載器
  • Class<?>[] interfaces:代理類要實(shí)現(xiàn)的接口列表
  • InvocationHandler h:調(diào)用處理程序

從 newProxyInstance 方法中我們知道了代理對(duì)象是如何產(chǎn)生的了「注釋很清楚了」

再看看 InvocationHandler

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

其中三個(gè)參數(shù):

  • Object proxy: 被代理的對(duì)象
  • Method method:要操作的方法
  • Object[] args:方法要傳入的參數(shù)私痹,可以沒有,也可以有多個(gè)或 null

InvocationHandler 接口中的方法就是執(zhí)行被代理對(duì)象中的方法

2统刮、使用動(dòng)態(tài)代理修改真假美猴王代碼

動(dòng)態(tài)代理悟空 簡(jiǎn)單的UML

動(dòng)態(tài)代理悟空 簡(jiǎn)單的UML

根據(jù) UML 擼碼

只需要在原有代碼的基礎(chǔ)上添加一個(gè)動(dòng)態(tài)類并且刪掉六耳獼猴類「動(dòng)態(tài)代理來了窗轩,小六你還不快撤」,然后修改 Test 即可

  • 1成洗、添加動(dòng)態(tài)代理類 ToWestProxy.java
/**
 * 動(dòng)態(tài)代理類
 */
public class ToWestProxy implements InvocationHandler {
    // 需要代理的對(duì)象即真實(shí)對(duì)象
    private Object delegate ;

    public Object getProxy(Object delegate){
        this.delegate = delegate ;
        // 動(dòng)態(tài)構(gòu)建一個(gè)代理
        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) ; // 通過反射調(diào)用真實(shí)對(duì)象對(duì)應(yīng)的方法
    }
}

我們看到上在被代理的對(duì)象是一個(gè) Object 類型印机,所以可以看出這個(gè)代理類就是一個(gè)萬能的代理,不僅僅可以代理悟空鞭衩,牛魔王也能代理「扯遠(yuǎn)了」

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

        IToWest sunWuKong = new SunWuKong() ;

        // 取得動(dòng)態(tài)代理
        IToWest proxy = (IToWest) new ToWestProxy().getProxy(sunWuKong);

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

看到了,真實(shí)對(duì)象悟空隨便你改论衍,我再添加接口瑞佩,方法,我動(dòng)態(tài)代理不用動(dòng)「如果是靜態(tài)代理六耳獼猴坯台,那就得隨著悟空的修改必須得修改自己」

而且炬丸,我們還可以得出,這個(gè)動(dòng)態(tài)代理不僅僅可以代理悟空蜒蕾,簡(jiǎn)直可以代理一切對(duì)象「不信你定義一個(gè)牛魔王試試」

  • 3稠炬、運(yùn)行查看結(jié)果
結(jié)果

簡(jiǎn)直 perfect 有木有

3、自動(dòng)售票機(jī)

隨著科技的發(fā)達(dá)咪啡,我們現(xiàn)在買車票的時(shí)候可以在自動(dòng)售票機(jī)「代理售票人員」上購(gòu)買

自動(dòng)售票機(jī)簡(jiǎn)單的 UML

自動(dòng)售票機(jī)簡(jiǎn)單的 UML

根據(jù) UML 擼碼--采用動(dòng)態(tài)代理技術(shù)

  • 1首启、先來一個(gè)抽象角色 ISellTicket.java
/**
 * Created by TigerChain
 * 定義一個(gè)抽象接口
 */
public interface ISellTicket {
    // 售票
    void sellTicket() ;
}
  • 2、要出票撤摸,當(dāng)然有買的票的人 User.java
/**
 * Created by TigerChain
 * 買票的人
 */
public class User {
    private String uname ; //姓名
    private String address ; // 地址
    private String sex ;     // 性別
    private String idNum ;   // 身份證號(hào)
    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毅桃、真實(shí)對(duì)象售票員小張 XiaoZhangSeller.java
/**
 * Created 真實(shí)的售票員小張
 */
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("買票者身份證號(hào):"+user.getIdNum());
            System.out.println("買票者住址:"+user.getUname());

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

            System.out.println("正在驗(yàn)證信息...信息無誤,請(qǐng)支付票錢");
            System.out.println("買票者支付:"+user.getPay()+" 元");
            System.out.println("請(qǐng)稍等正在出票.....");
            System.out.println("出票成功:從西安到寶雞大巴進(jìn)站去坐");
        }
    }
}
  • 4、動(dòng)態(tài)代理 DyAutoSellerProxy.java

/**
 * Created by TigerChain
 * 自動(dòng)出票機(jī),為了演示名字這樣想,其實(shí)這是一個(gè)萬能的動(dòng)態(tài)代理
 */
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准夷、測(cè)試一下 Test.java
/**
 * Created by TigerChain
 * 測(cè)試類
 */
public class Test {
    public static  void main(String args[]){
        // 定義個(gè)買票者
        User tigerChain = new User() ;
        tigerChain.setUname("TigerChain");
        tigerChain.setAddress("中國(guó)陜西");
        tigerChain.setSex("男");
        tigerChain.setIdNum("610326************");
        tigerChain.setPay("45.00");

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

        // 動(dòng)態(tài)代理
        DyAutoSellerProxy dyAutoSellerProxy = new DyAutoSellerProxy(iSellTicket) ;

        // 動(dòng)態(tài)創(chuàng)建一個(gè)出票機(jī)钥飞,把出票交給出票機(jī)去處理
        ISellTicket iSellTicket1 = (ISellTicket) Proxy.newProxyInstance(iSellTicket.getClass().getClassLoader(),iSellTicket.getClass().getInterfaces(),dyAutoSellerProxy);

        iSellTicket1.sellTicket();
    }
}
  • 6、運(yùn)行查看結(jié)果
自動(dòng)出票機(jī)結(jié)果

自么樣一個(gè)自動(dòng)售票機(jī)就完成了「完全代理了人工去賣票」

PS:這個(gè) Demo 使用動(dòng)態(tài)代理實(shí)現(xiàn)的冕象,請(qǐng)大家自行使用靜態(tài)代理實(shí)現(xiàn)本 Demo 代承,一定要?jiǎng)邮謱?shí)踐哦

4、AIDL 進(jìn)行進(jìn)程間通訊「遠(yuǎn)程代理」

AIDL「Android 接口定義語言渐扮,是一種語言论悴,其實(shí)就是 Android 中的遠(yuǎn)程 Service」掖棉,再說 AIDL 之前就不得不說 Binder「這里簡(jiǎn)潔明了的說一下 Binder 是什么,不展開深入討論膀估,如果深入展開幔亥,三天三夜也說不完」

什么是 Binder

由于兩個(gè)進(jìn)程不能直接進(jìn)行通訊「為了安全系統(tǒng)有進(jìn)程隔離機(jī)制」,所以兩個(gè)進(jìn)程之間是不能直接進(jìn)行通訊的察纯。Binder 可以說是 Android 系統(tǒng)中最重要的架構(gòu)之一帕棉。Binder 是連接 Client「進(jìn)程」 和 Server「進(jìn)程」 的一個(gè)橋梁,Binder 是進(jìn)程間通信的方式之一,在 Android 用的灰潮牵灰常的多

我們先來看看 Android 的架構(gòu)圖像

Android 的架構(gòu)圖

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

從 Android 的框架圖中我們可以看到香伴,應(yīng)用程序框架層和系統(tǒng)服務(wù)層之間就是通過 Binder IPC 進(jìn)行通訊的,說 Binder 機(jī)制前,我們先了解幾個(gè)特點(diǎn)

  • 1具则、兩個(gè)進(jìn)程之間不能直接通信
  • 2即纲、內(nèi)核可以仿問進(jìn)程中的所有數(shù)據(jù)
  • 3、兩個(gè)進(jìn)程之間不能直接進(jìn)行通信博肋,我們可以借助內(nèi)核做中轉(zhuǎn)達(dá)到間接通信的目的「Binder 就是這種機(jī)制」

Binder 下兩個(gè)進(jìn)程通信的簡(jiǎn)易流程

binder_arc

PS: 以上圖是便于理解所以抽象出來一張圖低斋,真實(shí)的 Binder 比這個(gè)過程復(fù)雜的多,這牽扯到 java 層的 Binder 匪凡,native 層的 Binder 等等「這不是我們討論的重點(diǎn)」膊畴,方便我們理解,我們可以認(rèn)為客戶端的進(jìn)程拿到服務(wù)端的引用病游,所以就可以調(diào)用服務(wù)端進(jìn)程的方法了

說了這么多唇跨,這跟代理有個(gè)毛關(guān)系呢,別急我們寫一個(gè) AIDL 的實(shí)例分析一下:

AIDL demo 簡(jiǎn)單的 UML

AIDL demo 簡(jiǎn)單的 UML

根據(jù) uml 寫代碼

我們寫一個(gè)簡(jiǎn)單的通過 Client 進(jìn)程調(diào)用 Server 進(jìn)程返回一個(gè)字符串功能礁遵,為了方便起見轻绞,我們直接在一個(gè)項(xiàng)目中創(chuàng)建「Server 開啟在另一個(gè)進(jìn)程中,開兩個(gè) APP 進(jìn)行通信大家可以自行試一下佣耐,道理一模一樣的」

  • 1、在項(xiàng)目中新建一個(gè) AIDL 文件「在 AS 中的 APP上直接右鍵 new AIDL 即可」
interface CustomAIDL {

    String getStr() ;
}

此時(shí)我們點(diǎn)擊一下

圖標(biāo)構(gòu)造一下項(xiàng)目唧龄,此時(shí)會(huì)在 app\build\generated\source\aidl\debug\包名\CustomAIDL.java 文件「把 AS 切換到 project 視圖下很容易找到」兼砖,這是 IDE 幫我們自動(dòng)生成的

  • 2、定義一個(gè)遠(yuǎn)程服務(wù) AIDLRemoteService.java
/**
 * @Description 創(chuàng)建一個(gè)遠(yuǎn)程服務(wù)
 * @Creator TigerChain(創(chuàng)建者)
 */
public class AIDLRemoteService extends Service {

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

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return aide;
    }
}

  • 3既棺、定義 AidlActivity 測(cè)試調(diào)用 「核心代碼給出讽挟,其余代碼看 Demo 即可」
public class AidlActivity extends AppCompatActivity implements View.OnClickListener{

 private CustomAIDL customAIDL ;
... 省略若干代碼

// 客戶端連接服務(wù)
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:
                // 綁定服務(wù)
                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,"請(qǐng)先綁定服務(wù)先",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 中注冊(cè)服務(wù)
 <service android:name=".Proxy.AIDL.AIDLRemoteService"
            android:process=":reomte"></service>

我這里給服務(wù)定義了一個(gè) process 丸冕,那說明這個(gè)服務(wù)是運(yùn)行在一個(gè)新進(jìn)程中的

  • 5耽梅、測(cè)試一下,運(yùn)行查看結(jié)果

我們看一下當(dāng)前項(xiàng)目進(jìn)程情況

當(dāng)前 Demo 進(jìn)程情況

的確是兩個(gè)進(jìn)程「AidlActivity 和 AIDLRemoteService 分別在兩個(gè)進(jìn)程中」胖烛,我們定義的 remote 也顯示出來了,看一下結(jié)果

AIDL demo 結(jié)果

怎么樣眼姐,兩個(gè)進(jìn)程之間完美的進(jìn)行了通信了

通個(gè)毛呢诅迷?這和 proxy 有個(gè)啥關(guān)系呀「巴拉巴拉這么久」,不要急嗎众旗?軟件開發(fā)有一條宗旨:先讓它運(yùn)行起來「我們先把 Demo 運(yùn)行起來再說嗎:咳咳又到了吃藥的時(shí)間了」罢杉,我們來分析一下上面的調(diào)用過程

過程分析

  • 1、還記得我們上面說的 AD 幫我們自動(dòng)生成的 CustomAIDL.java 文件嗎贡歧,我們來一窺它的真容「以下代碼是格式化后的」

// 這里的 IInterface 代表遠(yuǎn)程 Server 對(duì)象有什么能力
public interface CustomAIDL extends android.os.Interface {
    /**
     * Local-side IPC implementation stub class.
     */
    // 在 server 端調(diào)用
    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 對(duì)象是驅(qū)動(dòng)給們的滩租,這個(gè)就是我們綁定 service ,在 onServiceConnecttion 回調(diào)里面這個(gè)對(duì)象拿到一個(gè)遠(yuǎn)程的 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 在同一個(gè)進(jìn)程調(diào)用 后面 debug 可以驗(yàn)證
                return ((designpattern.jun.com.designpattern.CustomAIDL) win);
            }
            // cliet 和 Server 不在同一個(gè)進(jìn)程調(diào)用代理對(duì)象 后面 debug 可以驗(yàn)證
            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 {
            // 給客戶端寫數(shù)據(jù)
            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);
        }
        // 運(yùn)行在客戶端 server 進(jìn)程的遠(yuǎn)程代理,實(shí)現(xiàn)對(duì)遠(yuǎn)程對(duì)象的仿問
        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 {
                // 讀取服務(wù)端寫過來的數(shù)據(jù)
                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 了吧「是不是有點(diǎn)小激動(dòng)呢」利朵,我們來分析一下

AIDL 簡(jiǎn)單的流程圖

上面的圖就是一個(gè)簡(jiǎn)單的 AIDL 的流程圖律想,方便理解認(rèn)為 CustomAIDL.stub 就是遠(yuǎn)程進(jìn)程,它把信息注冊(cè)到 Binder 中绍弟, CustomAIDL.Stub.Proxy 就是一個(gè)代理技即,代理什么呢?代理遠(yuǎn)程的 Binder 晌柬,遠(yuǎn)程 Binder 把方法傳給 Client 就完成了兩個(gè)進(jìn)程間通信「詳細(xì)過程比這個(gè)復(fù)雜」姥份,對(duì)于 Binder 的入門介紹可以參看:Binder 學(xué)習(xí)指南 還是非常不錯(cuò)的,建議看三遍以上

PS:這里再說一點(diǎn)年碘,以上情況是針對(duì) client 和 server 在兩個(gè)進(jìn)程間的通信澈歉,如果 client 和 server 在一個(gè)進(jìn)程中,則 CustomAIDL.Stub.Proxy 就不會(huì)調(diào)用「在同一個(gè)進(jìn)程中屿衅,我自己就能調(diào)自己還代理個(gè)毛呀」埃难,不信?以結(jié)果征服你

client 和 server 同一進(jìn)程和不同進(jìn)程分析

  • 1涤久、不同進(jìn)程
 <service android:name=".Proxy.AIDL.AIDLRemoteService"
            android:process=":reomte"></service>

通過以上配置涡尘,我們可以看到 AIDLRemoteService 是運(yùn)行在單獨(dú)進(jìn)程中的,我們?cè)?CustomAIDL.java 中的 asInterface 方法中 debug 跟一下看看結(jié)果

AIDL 調(diào)用 Proxy

通過圖我們可以看出响迂,如果 client 和 server 不在同一個(gè)進(jìn)程中考抄,那么代碼就會(huì)走到

調(diào)用代理

調(diào)用代理的地方---CustomAIDL.Stub.Proxy,并傳遞遠(yuǎn)程代理的對(duì)象

  • 2、在同一進(jìn)程

去掉 service 中的 android:process=":reomte" 則 client 和 server 就在同一進(jìn)程了

 <service android:name=".Proxy.AIDL.AIDLRemoteService"/>       

同理 debug 看結(jié)果

不調(diào)用 proxy

對(duì)比上面的圖我們就知道了蔗彤,這里的 iin 不為空川梅,進(jìn)入了 if 的方法體「沒有調(diào)用代理」,至此上面的結(jié)果驗(yàn)證完畢

關(guān)于 AIDL 遠(yuǎn)程代理就說到這里了然遏,如果對(duì) Binder 想要深入了解贫途,可以自行回去研究「這不在本節(jié)的范圍內(nèi)」

WTF 一個(gè) AIDL 說了這么大半天,希望大家不要暈「我都有點(diǎn)暈了」

源碼地址: https://github.com/tigerchain/DesignPattern 看 proxy/aidl 這部分

三待侵、Android 源碼中的代理模式

其實(shí)通過上面的 AIDL 實(shí)驗(yàn)丢早,我們就可以知道 Binder 使用的就是遠(yuǎn)程代理模式,Android 中的源碼使用非常多秧倾,我就不一一分析了「說的太多人會(huì)受不鳥」怨酝,感興趣的朋友可以自行分析傀缩,我這里貼出一張圖,大家可以看

IPC

我們看看應(yīng)用程序框架層的 XXXManager 對(duì)應(yīng)田系統(tǒng)層的 XXXService 它們之間通過使用 AIDL 來進(jìn)行跨進(jìn)程通信凫碌,有興趣可以扒扒這部分的源碼看一下

四扑毡、代理模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 1、代理模式拿到的真實(shí)對(duì)象的引用盛险,把真實(shí)對(duì)象很好的保護(hù)起來安全性高
  • 2瞄摊、擴(kuò)展性好

缺點(diǎn)

  • 增加了系統(tǒng)的復(fù)雜度,增加了額外好多的代碼「設(shè)計(jì)模式好像都是這樣」

到此為止苦掘,我們把代理模式就說完了换帜,由于這篇篇幅比較大,Android 源碼也沒有給大家分析「希望大家自行去看看鹤啡,希望你有一種哦~原來是這樣的趕腳」惯驼,其它的虛擬代理,緩存代理大家有興趣也可以試試

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抖部,隨后出現(xiàn)的幾起案子说贝,更是在濱河造成了極大的恐慌,老刑警劉巖慎颗,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乡恕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡俯萎,警方通過查閱死者的電腦和手機(jī)傲宜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夫啊,“玉大人函卒,你說我怎么就攤上這事∑裁校” “怎么了谆趾?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)叛本。 經(jīng)常有香客問我,道長(zhǎng)彤钟,這世上最難降的妖魔是什么来候? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮逸雹,結(jié)果婚禮上营搅,老公的妹妹穿的比我還像新娘云挟。我一直安慰自己,他們只是感情好转质,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布园欣。 她就那樣靜靜地躺著,像睡著了一般休蟹。 火紅的嫁衣襯著肌膚如雪沸枯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天赂弓,我揣著相機(jī)與錄音绑榴,去河邊找鬼。 笑死盈魁,一個(gè)胖子當(dāng)著我的面吹牛翔怎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杨耙,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼赤套,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了珊膜?” 一聲冷哼從身側(cè)響起容握,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辅搬,沒想到半個(gè)月后唯沮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堪遂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年介蛉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溶褪。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡币旧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猿妈,到底是詐尸還是另有隱情吹菱,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布彭则,位于F島的核電站鳍刷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏俯抖。R本人自食惡果不足惜输瓜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尤揣,春花似錦搔啊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗜愈,卻和暖如春旧蛾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芝硬。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工蚜点, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拌阴。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓绍绘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親迟赃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陪拘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360