代理模式介紹
代理模式(Proxy Pattern)也稱為委托模式,是結構型設計模式的一種郑诺。在現(xiàn)實生活中用到代理的場景有很多,如:加盟商杉武,去代售點買票,代理上網(wǎng)等辙售。
代理模式定義
為其他對象提供一種代理以控制這個對象的訪問轻抱。
代理模式使用場景
當無法或不想直接引用某個對象或訪問某個對象存在困難時,可以通過代理類對象來間接訪問旦部。
代理模式 UML 類圖
角色介紹:
- Subject:抽象主題類祈搜,聲明真實主題與代理的共同接口方法。
- RealSubject:真實主題類士八,該類定義了代理所表示的真實對象容燕,是負責執(zhí)行系統(tǒng)真正的邏輯業(yè)務對象。
- Proxy:代理類婚度,其內(nèi)部持有 RealSubject 的引用蘸秘,因此具備對 RealSubject 的代理權。
代理模式簡單實現(xiàn)
這里以郵遞物品為例,郵遞公司看做代理(其不負責郵遞),郵遞員為實際郵遞者醋虏。
抽象主題類(Subject)
抽象主題類具有真實主題類和代理的共同接口方法寻咒,需要郵遞物品,則就是郵遞
public interface IPost {
void post(String goods);
}
真實主題類(RealSubject)
真正負責郵遞物品的是快遞員 Postman
public class Postman implements IPost {
@Override
public void post(String goods) {
System.out.println("郵遞員:郵寄物品為" + goods);
}
}
代理類(ProxySubject)
起代理作用的是郵遞公司颈嚼,郵遞公司并不進行實際郵遞工作毛秘,其會讓郵遞員進行郵遞,為了避免物品給郵遞員帶來危險阻课,其負責對物品進行安全檢查叫挟。
public class PostProxy implements IPost {
private Postman mPostman;
public PostProxy(Postman postman) {
mPostman = postman;
}
@Override
public void post(String goods) {
System.out.println("貨物安檢");
if (goods.contains("handgun")) {
System.out.println("貨物存在違禁品,無法郵遞");
return;
}
System.out.println("貨物正常限煞,開始郵遞");
mPostman.post(goods);
}
}
客戶端
public class Client {
public static void main(String[] args) {
Postman postman = new Postman();
PostProxy postProxy = new PostProxy(postman);
// 郵遞手機
postProxy.post("handset");
// 郵遞手槍
postProxy.post("handgun");
}
}
輸出結果如下:
貨物安檢
貨物正常抹恳,開始郵遞
郵遞員:郵寄貨物為handset
貨物安檢
貨物存在違禁品,無法郵遞
代理 PostProxy 完成了物品的安檢和郵遞晰骑。這里先說下我的疑問:已經(jīng)有Postman 對象了适秩,為什么又去調(diào)用 PostProxy?
我們的代理類可以不只為一個真實主題類做代理,我們可以為多個具有郵遞能力的機構做代理硕舆,那么我們就可以將 PostProxy 持有 Postman 對象改為 IPost 接口秽荞。
代理模式的擴展
設計模式中有普通代理和強制代理的概念。
- 普通代理:我們需要知道代理的存在抚官,而不能去訪問真實主題角色扬跋。
- 強制代理:調(diào)用者直接調(diào)用真實角色,而不用關心代理是否存在凌节,其代理的產(chǎn)生是由真實角色決定钦听。
普通代理示例
基于上述示例稍作修改:
public class Postman implements IPost {
public Postman(IPost post) throws Exception {
// 通過是否傳入代理類來驗證創(chuàng)建方
if (post == null) {
throw new Exception("不能創(chuàng)建真實主題角色");
}
}
@Override
public void post(String goods) {
System.out.println("郵遞員:郵寄貨物為" + goods);
}
}
public class PostProxy implements IPost {
private Postman mPostman;
public PostProxy() {
try {
// 有權創(chuàng)建 Postman
mPostman = new Postman(this);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void post(String goods) {
System.out.println("貨物安檢");
if (goods.contains("handgun")) {
System.out.println("貨物存在違禁品,無法郵遞");
return;
}
System.out.println("貨物正常倍奢,開始郵遞");
mPostman.post(goods);
}
}
public class Client {
public static void main(String[] args) {
IPost post = new PostProxy();
post.post("handset");
}
}
運行結果完全相同朴上。在該模式下,調(diào)用者只知代理而不用知道真實的角色是誰卒煞,屏蔽了真實角色的變更對高層模塊的影響痪宰。當然,在實際的項目中畔裕,一般都是通過約定來禁止new一個真實的角色衣撬,這也是一個非常好的方案。
強制代理示例
該種代理要求必須通過真實角色查找到代理角色扮饶,否則你無法訪問具练。無論你是通過代理類還是通過直接new一個主題角色類,都無法訪問甜无,只有通過真實角色指定的代理類才可以訪問扛点,也就是說由真實角色管理代理角色哥遮。以明星和他的經(jīng)紀人為例,明星相當于真實角色占键,經(jīng)紀人相當于代理昔善,而你和該明星關系非常好,找他辦事肯定直接給他打電話畔乙,明星最后就找他指定的經(jīng)紀人完成你求的事君仆。
public interface IPost {
void post(String goods);
// 都可以有自己指定的代理
IPost getProxy();
}
public class Postman implements IPost {
private IPost mProxy;
@Override
public void post(String goods) {
// 是我的代理郵遞,不是則提示
if (isProxy()) {
System.out.println("郵遞員:郵寄貨物為" + goods);
} else {
System.out.println("請找我的代理進行郵寄");
}
}
// 返回我指定的代理
@Override
public IPost getProxy() {
mProxy = new PostProxy(this);
return mProxy;
}
// 判斷是不是我的代理
private boolean isProxy() {
return mProxy != null;
}
}
public class PostProxy implements IPost {
// 被代理的對象
private IPost mPoster;
public PostProxy(IPost post) {
mPoster = post;
}
@Override
public void post(String goods) {
mPoster.post(goods);
}
// 自己也可以被代理牲距,我為自己代理返咱。
@Override
public IPost getProxy() {
return this;
}
}
客戶端代碼
public class Client {
public static void main(String[] args) {
// 1. 直接通過真實對象郵遞,郵遞失敗
IPost post = new Postman();
post.post("handset");
// 2. 自己尋找郵遞代理牍鞠,郵遞失敗
IPost proxy = new PostProxy(post);
proxy.post("handset");
// 3. 訪問郵遞員指定的代理咖摹,郵遞成功
post.getProxy().post("handset");
}
}
輸出結果:
請找我的代理進行郵寄
請找我的代理進行郵寄
郵遞員:郵寄貨物為handset
強制代理的概念就是要從真實角色查找到代理角色,不允許直接訪問真實角色难述。高層模塊只要調(diào)用getProxy就可以訪問真實角色的所有方法萤晴,它根本就不需要自己創(chuàng)建一個代理出來,代理的管理已經(jīng)由真實角色自己完成胁后。
代理類不僅僅可以實現(xiàn)主題接口店读,也可以實現(xiàn)其他接口完成不同的任務,而且代理的目的是在目標對象方法的基礎上作增強攀芯,這種增強的本質(zhì)通常就是對目標對象的方法進行攔截和過濾屯断。
動態(tài)代理模式簡單實現(xiàn)
從編碼的角度來說,代理模式分為靜態(tài)代理和動態(tài)代理侣诺,上面的例子是靜態(tài)代理殖演,在代碼運行前就已經(jīng)存在了代理類的class編譯文件,而動態(tài)代理則是在代碼運行時通過反射來動態(tài)的生成代理類的對象年鸳,并確定到底來代理誰颁督。也就是我們在編碼階段不需要知道代理誰圈暗,代理誰我們將會在代碼運行時決定滤淳。Java 給我們提供了一個便捷的動態(tài)代理接口 InvocationHandler赠摇,實現(xiàn)該接口需要重寫invoke()方法并巍。下面我們在上面靜態(tài)代理的例子上做修改:
創(chuàng)建動態(tài)代理類:
public class DynamicProxy implements InvocationHandler {
// 被代理的類引用
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}
當調(diào)用代理類的接口方法時忌穿,會調(diào)用到 invoke 方法连躏,該方法中負責反射調(diào)用被代理者的真實邏輯實現(xiàn)的方法石景。
修改客戶端代碼
public class Client {
public static void main(String[] args) {
IPost postman = new Postman();
// 創(chuàng)建動態(tài)代理
DynamicProxy dynamicProxy = new DynamicProxy(postman);
// 創(chuàng)建被代理者的 ClassLoader
ClassLoader loader = postman.getClass().getClassLoader();
// 創(chuàng)建代理者
IPost poster = (IPost) Proxy.newProxyInstance(loader, new Class[]{IPost.class}, dynamicProxy);
poster.post("handgun");
}
}
輸出結果:
郵遞員:郵寄貨物為handgun
動態(tài)代理通過一個代理類來代理 N 多個被代理者更舞,其實質(zhì)是對代理者和被代理者解耦畦幢,使兩者沒有直接的耦合關系。 而靜態(tài)代理則只對給定的接口下的實現(xiàn)類做代理缆蝉,如果接口不同就需要重新定義代理類宇葱。
代理模式類型
從編碼角度區(qū)分
- 靜態(tài)代理
- 動態(tài)代理
從適用范圍區(qū)分
- 遠程代理(Remote Proxy):為某個對象在不同的內(nèi)存地址空間提供局部代理瘦真。是系統(tǒng)可以將 Server 部分的實現(xiàn)隱藏,以便 Client 可以不必考慮 Server 的存在黍瞧。Android 中 aidl 生成的 Java 代碼 中存在個 Proxy诸尽,該代碼運行在客戶端,而實際邏輯運行在服務端印颤,該Proxy 就是遠程代理您机。
- 虛擬代理(Virtual Proxy):使用一個代理對象表示一個十分耗資源的對象并在真正需要時才創(chuàng)建。
- 保護代理(Protection Proxy):使用代理控制對真實對象的訪問年局。該類型的代理常被用于對原始對象有不同的訪問權限的情況际看。上面的靜態(tài)代理示例就屬于該種。
- 智能引用(Smart Reference):在訪問原始對象時執(zhí)行一些自己的附加操作并對指向原始對象的引用計數(shù)矢否。
靜態(tài)代理和動態(tài)代理都可以應用于上述 4 中情形仲闽。
總結
代理模式優(yōu)點:
1.職責清晰:真實的角色就是實現(xiàn)實際的業(yè)務邏輯,不用關心其他非本職責的事務
2.高擴展性:代理類可以完全控制真實主題僵朗,可以控制訪問赖欣,擴展功能,而不用修改原真實主題验庙。
代理模式缺點:
1.由于在客戶端和真實主題之間增加了代理對象顶吮,因此有些類型的代理模式可能會造成請求的處理速度變慢。
2.實現(xiàn)代理模式需要額外的工作壶谒,有些代理模式的實現(xiàn)非常復雜云矫。
Android 源碼中代理模式實現(xiàn)
Android 源碼中有很多代理模式的實現(xiàn),比如 AIDL 生成的Java文件中 Proxy類汗菜,這里直接以 Android 7.0 上 ActivityManagerProxy 為例介紹让禀,ActivityManagerProxy 就相當于 AIDL 生成的 Proxy 類,只不過 Framework 中直接以 Java 代碼的形式書寫陨界,在Android 8.0 上已經(jīng)變化為 AIDL 形式巡揍,請知悉。
為了方便菌瘪,下面對ActivityManagerProxy腮敌,ActivityManagerNative 和 ActivityManagerService 會以簡寫方式書寫,分別為: AMP俏扩,AMN 和 AMS糜工。
AMP 具體代理的是 AMN 的子類 AMS, AMP與 AMN 在同一文件中。
class ActivityManagerProxy implements IActivityManager {
...
}
IActivityManager 為接口類就是代理模式中的抽象主題录淡,其中定義了一些 Activity 相關的接口方法
public interface IActivityManager extends IInterface {
...
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter,
String requiredPermission, int userId) throws RemoteException;
public int checkPermission(String permission, int pid, int uid)
throws RemoteException;
...
}
對于真正的主題角色是誰呢捌木?就是繼承 AMN 的 AMS,這幾個類大致關系如下圖:
通過 UML 圖可以清晰的看出 AMP 和 AMN 都實現(xiàn)了 IActivityManager 接口嫉戚,嚴格地說刨裆,AMP 就是代理部分澈圈,而 AMN 就是真實主題角色,但 AMN 是抽象類帆啃,并不處理過多邏輯瞬女,大部分是由 AMS 完成。
AMS 是系統(tǒng)級Service努潘,運行在system_server進程中诽偷,而 AMP 一般運行在客戶端進程也就是 app 進程,他們之間的通信屬于 Binder 跨進程通信慈俯。對于 AMP 并不是在 app 進程直接使用的渤刃,而是通過 ActivityManager 類,該類負責管理和維護 Activity 相關信息贴膘,但實際大多數(shù)邏輯是通過 AMP 承擔卖子。這里以 getMemoryInfo 為例介紹是如何工作的。
ActivityManager.java
public void getMemoryInfo(MemoryInfo outInfo) {
try {
ActivityManagerNative.getDefault().getMemoryInfo(outInfo);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
ActivityManagerNative.java
ActivityManagerNative.getDefault()
該方法實現(xiàn)如下
static public IActivityManager getDefault() {
return gDefault.get();
}
gDefault 又是什么刑峡?
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
// 1. 與ServiceManger 進行 IPC 通信洋闽,獲取系統(tǒng)級 Service,該Service 實質(zhì)上是 AMS
IBinder b = ServiceManager.getService("activity");
// 2. 返回 AMP
IActivityManager am = asInterface(b);
return am;
}
};
// 跨進程通信突梦,則返回的是 AMP
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
gDefault.get()
就是單例的 AMP 對象
ActivityManagerProxy.java
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
// 1.
mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0);
reply.readException();
outInfo.readFromParcel(reply);
data.recycle();
reply.recycle();
}
該段就是 Binder 通信的核心代碼诫舅,在注釋1處,調(diào)用客戶端Binder的 transact() 方法宫患,服務端 Binder 的 onTransact() 就會回調(diào)刊懈,這個是由 Binder 驅(qū)動進行完成⊥尴校客戶端 transact 方法中傳入方法標識為GET_MEMORY_INFO_TRANSACTION虚汛,data 用于向服務端寫入數(shù)據(jù),reply 用于接收服務端的返回數(shù)據(jù)皇帮,當服務端對應方法執(zhí)行完后, transact 就停止阻塞卷哩,繼續(xù)走到outInfo.readFromParcel(reply);
,將返回結果寫入outInfo 對象中。接下來我們看服務端是 AMS 是如何執(zhí)行的属拾?
onTransact 方法在 抽象類 AMN 中
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
...
case GET_MEMORY_INFO_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
getMemoryInfo(mi);
reply.writeNoException();
mi.writeToParcel(reply, 0);
return true;
}
...
}
實際是通過 getMemoryInfo 方法完成
ActivityManagerService.java
@Override
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ);
final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ);
outInfo.availMem = Process.getFreeMemory();
outInfo.totalMem = Process.getTotalMemory();
outInfo.threshold = homeAppMem;
outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
outInfo.hiddenAppThreshold = cachedAppMem;
outInfo.secondaryServerThreshold = mProcessList.getMemLevel(
ProcessList.SERVICE_ADJ);
outInfo.visibleAppThreshold = mProcessList.getMemLevel(
ProcessList.VISIBLE_APP_ADJ);
outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
ProcessList.FOREGROUND_APP_ADJ);
}
AMS 中完成獲取 Memory 信息的獲取将谊,最后返回到客戶端中調(diào)用時傳入的 MemoryInfo 對象中。
客戶端和具體主題角色 AMS 屬于不同進程渐白,所以這里的 AMP 就是遠程代理尊浓,使系統(tǒng)可以將服務端的實現(xiàn)隱藏,客戶端使用時無需關心 AMS纯衍。對于訪問 AMS 都交由 AMP眠砾,由于牽扯到跨進程,調(diào)用 AMS 代碼會比較復雜,這些都有 AMP 完成了處理褒颈,簡化了客戶端的訪問。