主目录见:Android高级进阶知识(这是总目录索引)
今天要讲这个代理设计模式,真的是印象深刻,在好多年前在学校接触java的时候就对这个模式了解过,当初还想着Spring里面的AOP机制是不是就是用代理实现的呀。到现在插件化的hook大量用到代理设计模式。包括等会要讲的retrofit也是用到了代理设计模式。而且Framework的AIDL里面也是用到了代理模式。这些个例子足以证明了你学这个设计模式的理由,不需要我去调动大家,我们先来看看代理模式的定义:
代理模式:为其他对象提供一种代理以控制这个对象的访问。
这个定义简单明显呀,就是说不直接访问对象,而是建一个代理或者说是中间人去访问他,而且这个代理可以选择多带礼物,说祝福等等增强的行为和服务。接着我们看下UML类图:
角色介绍:
- Subject:抽象主题,声明代理主题和真实主题的接口;
- Proxy:代理主题,因为Proxy引用了RealSubject,并且实现了跟RealSubject一样的接口,所以ProxySubject可以操作RealSubject,还可以提供一些附加操作,例如before & after;
- RealSubject:真实主题,真实的对象,需要被代理主题引用。
一.目标
今天讲这个设计模式也是为了后面插件化和热更新做个准备,同时我们还能在必要时候派上用场,特别是retrofit的源码和使用,这个待会就会讲了。所以今天的目标是:
1.学会几种代理设计模式怎么使用;
2.学会在不同的场景中应用代理模式。
二.模式讲解
今天我们也来假设一个场景,比如你现在要去租房,但是呢,又不是自己去一个地方一个地方找,所以你就拜托了中介帮你找,告诉了中介你的要求,然后中介就将你的要求发布在网上,联系了房东,最后你去看了房子。首先我们看下抽象主题:
public abstract class Subject {
public abstract String houseHunting();
}
我们看到抽象主题里面有个找房子的动作。这个是给后面代理主题和真实主题使用的,我们现在来看下真实主题想干嘛:
public class Hunter extends Subject{
@Override
public String houseHunting() {
String info = "我要找一间房子,房子的价格在2000-3000之间三室一厅";
return info;
}
}
我们看到我们的的真实主题是想要找一间房子,然后说了自己的价格。接着我们代理就出现了:
public class Proxy extends Subject{
private Subject subject;
public void setHunter(Subject subject){
this.subject = subject;
}
@Override
public String houseHunting() {
String proxyFare = "我要额外收中介费500块";
String infoFromHunter = subject.houseHunting();
return infoFromHunter +", "+ proxyFare;
}
}
我们看到这里的代理也就是这个中介除了帮你找房子,还要额外再收中介费用。所以代理可以拦截原来的方法然后再增加额外的功能。我们看下这个是怎么使用:
Subject hunter = new Hunter();
Proxy houseAgency = new Proxy();
houseAgency.setHunter(hunter);
String info = houseAgency.houseHunting();
System.out.println(info);
除了这个静态代理,我们java还提供了动态代理机制,我们现在来看看用动态代理是怎么实现代理的。
1.动态代理
同静态代理不同的是,动态代理是通过反射在运行时候生成代理对象的,要实现动态代理,首先我们要注意这里的动态代理有要求就是必须被代理的对象必须是实现一个接口。而且代理对象必须实现InvocationHandler接口。我们先来看看代理对象:
public class DynamicProxy implements InvocationHandler{
private Subject subject;
public DynamicProxy(Subject subject) {
this.subject = subject;
}
@Override
public String invoke(Object arg0, Method method, Object[] arg2)
throws Throwable {
String proxyFare = "我要额外收中介费500块";
String result = (String) method.invoke(subject,arg2);
return result +", "+ proxyFare;
}
}
我们看到这里实现了InvocationHandler,然后必须实现invoke方法。invoke方法这里包括方法和方法参数。然后我们可以往在原本方法增加附加功能了,比如这里的中介费。调用变成了这样:
Subject hunter = new Hunter();
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class<?>[]{Subject.class}, new DynamicProxy(hunter));
System.out.println(subject.houseHunting());
当然这里的Subject必须是一个接口,不然程序会出错。当然了前面的都是要实现一个接口的,但是有没有不需要被代理对象实现一个接口呢?答案是有的大家可以使用这个库Cglib代理
。具体不会介绍这个库,大家有兴趣可以看看。
2.retrofit中的代理设计模式
如果想要了解retrofit的具体源码可以查看[Retrofit2源码解析],我们知道我们使用retrofit的时候如下:
PhoneService service=retrofit.create(PhoneService.class);
Call<PhoneResult>call=service.getResult(etPhone.getText().toString());
我们会获取到要调用的服务的对象PhoneService,然后调用里面的方法getResult,因为我们说到了动态代理必须要实现一个接口,这里的PhoneService 就是一个接口,然后调用create方法具体源码如下:
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
我们看到这里应用了动态代理,首先service就是接口的class对象。然后最后new InvocationHandler就是实现了代理对象。具体做了什么我们的源码分析有说明这里就不重复说明。
3.retrofit使用过程中使用到的代理设计模式
我们知道,我们在访问网络的时候,会出现当前网络环境较差,当前没有网络,服务器出错等等情况。所以在create返回访问接口之后我们可以再加一个代理来处理这些情况。我们先来看看我们的代理对象:
public class ResponseErrorProxy implements InvocationHandler {
public static final String TAG = ResponseErrorProxy.class.getSimpleName();
private Object mProxyObject;
public ResponseErrorProxy(Object proxyObject) {
mProxyObject = proxyObject;
}
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) {
return Observable.just(null)
.flatMap(new Func1<Object, Observable<?>>() {
@Override
public Observable<?> call(Object o) {
try {
return (Observable<?>) method.invoke(mProxyObject, args);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
})
.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
ResponseError error = null;
if (throwable instanceof ConnectTimeoutException
|| throwable instanceof SocketTimeoutException
|| throwable instanceof UnknownHostException
|| throwable instanceof ConnectException) {
error = new ResponseError(HTTP_NETWORK_ERROR,
StringFetcher.getString(R.string.toast_error_network));
} else if (throwable instanceof HttpException) {
HttpException exception = (HttpException) throwable;
try {
error = GsonHelper.builderGson().fromJson(
exception.response().errorBody().string(), ResponseError.class);
} catch (Exception e) {
if (e instanceof JsonParseException) {
error = new ResponseError(HTTP_SERVER_ERROR,
StringFetcher.getString(R.string.toast_error_server));
} else {
error = new ResponseError(HTTP_UNKNOWN_ERROR,
StringFetcher.getString(R.string.toast_error_unknown));
}
}
} else if (throwable instanceof JsonParseException) {
error = new ResponseError(HTTP_SERVER_ERROR,
StringFetcher.getString(R.string.toast_error_server));
} else {
error = new ResponseError(HTTP_UNKNOWN_ERROR,
StringFetcher.getString(R.string.toast_error_unknown));
}
if (error.getStatus() == HTTP_UNAUTHORIZED) {
return refreshTokenWhenTokenInvalid();
} else {
return Observable.error(error);
}
}
});
}
});
}
private Observable<?> refreshTokenWhenTokenInvalid() {
synchronized (ResponseErrorProxy.class) {
//TODO
return null;
}
}
}
这里用了rxjava的知识,如果看不清楚只要看到动态代理结构就行。然后我们这个在获取接口的时候返回代理对象:
@Provides
@Singleton
ApiService provideApiService(Retrofit retrofit){
return getByProxy(ApiService.class,retrofit);
}
ApiService getByProxy(Class<? extends ApiService> apiService, Retrofit retrofit){
ApiService api = retrofit.create(apiService);
return (ApiService) Proxy.newProxyInstance(apiService.getClassLoader(),new Class<?>[] { apiService },new ResponseErrorProxy(api));
}
我们看到在create返回ApiService 接口对象的时候我们又加了一层的代理,最终provideApiService()方法是返回我们的代理对象。这个方法是不是非常巧妙地处理了这些情况。想要看这段代码的话也可以看github上面:[HospitalGroup项目]。可以看到如果我们一个类实现了一个接口,然后我们又想增加这个类的功能就可以考虑用动态代理。
总结:静态代理还是动态代理,在使用的过程中都有其适用的场景,静态代理在之后的插件化里面用的还是比较多的,以后会见到。动态代理我们这里举了两个例子,这两个都是拦截原有的方法然后来增加额外功能或者解析原来请求方法来做一个动作。这里也希望大家遇到这种情况能想到这个模式。