简介 该框架是一个网络框架,一般是作为okhttp的上层框架;请求先经过该框架的处理之后再交给okhttp;它的功能实际上是将java interface提取成request请求接口。之后交给 okhttp将发送request。
下面以美团9.8.0作为例子来分析。
分析Retrofit框架 Retrofit类 Retrofit类是框架的核心类,管理所有的interface;只要获得该对象,就可以调用任意一个已经创建好的interface接口;
Retrofit$Builder Retrofit类有一个Builder内部类,用来创建Retrofit类的对象。
通过hook Retrofit$Builder.build
可以获取到Retrofit的对象,也可以通过调用栈继续追踪调用源头(建议)。美团的Retrofit类采用单例设计模式;不同的app Retrofit对象的数量可能不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public final Retrofit build () { if (this .baseUrl != null ) { Factory v2 = this .callFactory; if (v2 != null ) { Executor v0 = this .httpExecutor; if (v0 == null ) { v0 = Retrofit.defaultHttpExecutor; if (v0 == null ) { v0 = this .platform.defaultHttpExecutor(); } } Executor v7 = v0; Executor v0_1 = this .callbackExecutor; if (v0_1 == null ) { v0_1 = this .platform.defaultCallbackExecutor(); } ArrayList v5 = new ArrayList(this .adapterFactories); v5.add(this .platform.defaultCallAdapterFactory(v0_1)); return new Retrofit(v2, this .baseUrl, new ArrayList(this .converterFactories), v5, this .interceptors, v7, v0_1, this .validateEagerly, this .cache); } throw new IllegalStateException("RawCall.Factory required." ); } throw new IllegalStateException("Base URL required." ); }
Retrofit对象的源头 通过Retrofit$Builder.build
调用栈追踪,定位到com.sankuai.waimai.platform.capacity.network.retrofit.c.a(java.lang.Class): com.sankuai.meituan.retrofit2.Retrofit
方法
该方法传入一个interface后返回Retrofit对象。从代码上来看它是全局保存的
1 2 3 4 5 6 7 8 9 10 11 12 public static Retrofit a (Class arg11) { q v11 = (q)c.c.get(arg11); if (v11 == null ) { v11 = c.b; } return (Retrofit)v11.c(); }
Retrofit.create Retrofit类的create方法传入一个interface,然后为这个interface实现代理类;也就是说Retrofit已经为这个接口创建好了request,等待外部调用即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public final Object create (Class arg13) { Utils.validateServiceInterface(arg13); if (this .validateEagerly) { this .eagerlyValidateMethods(arg13); } return Proxy.newProxyInstance(arg13.getClassLoader(), new Class[]{arg13}, new InvocationHandler() { public static ChangeQuickRedirect changeQuickRedirect; public final Platform platform; public final Retrofit this $0 ; public final Class val$service; { Class arg2 = arg13; this .platform = Platform.get(); } @Override public Object invoke (Object arg12, Method arg13, Object[] arg14) throws Throwable { if (arg13.getDeclaringClass() == Object.class) { return arg13.invoke(this , arg14); } if (this .platform.isDefaultMethod(arg13)) { return this .platform.invokeDefaultMethod(arg13, arg13, arg12, arg14); } ServiceMethod v12 = Retrofit.this .loadServiceMethod(arg13); return v12.callAdapter.adapt(new ClientCall(v12, arg14, Retrofit.this .interceptors, Retrofit.defInterceptors, Retrofit.this .httpExecutor, Retrofit.this .cache)); } }); }
HomePageApi interface HomePageApi interface 是美团定义的,需要被Retrofit.create注册为动态代理类的interface,它的内部定义了一些method,当Retrofit注册完成后,就可以被外部所调用以完成request。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public interface HomePageApi { @FormUrlEncoded @POST("v6/home/dynamic/tabs") d getDynamicTabInfo (@Field("last_time_actual_latitude") String arg1, @Field("last_time_actual_longitude") String arg2) ; @FormUrlEncoded @POST("v7/product/list") d getFoodReunionPoilist (@Field("page_index") String arg1, @Field("session_id") String arg2, @Field("rank_trace_id") String arg3, @Field("rank_list_id") String arg4, @Field("filter_tab_list") String arg5, @Field("behavioral_characteristics") String arg6, @Field("navigate_type") long arg7) ; @FormUrlEncoded @POST("v7/poi/homepage") d getHomePagePoiList ( // getHomePagePoiList 的参数列表,当调用该参数时需要传入这些参数。 @Field("seq_num") int arg1, @Field("offset") int arg2, @Field("dynamic_page") boolean arg3, @Field("page_index") long arg4, @Field("page_size") long arg5, @Field("sort_type") long arg6, @Field("activity_filter_codes") String arg7, @Field("slider_select_data") String arg8, @Field("load_type") int arg9, @Field("rank_trace_id") String arg10, @Field("session_id") String arg11, @Field("union_id") String arg12, @Field("rank_list_id") String arg13, @Field("category_type") int arg14, @Field("second_category_type") int arg15, @Field("behavioral_characteristics") String arg16 ) ; @FormUrlEncoded @POST("v7/product/recommend/entrance") d getRecommendEntrance (@FieldMap Map arg1) ; @FormUrlEncoded @POST("v6/poi/dynamicrecommend") d getRecommendPoiCard (@FieldMap Map arg1) ; @FormUrlEncoded @POST("v8/home/gettopbanner") d getTopBanner (@Field("topbanner_refresh_poi_ids") String arg1, @Field("topbanner_refresh_activity_ids") String arg2) ; @FormUrlEncoded @POST("v6/product/tag") d optimizationFeedbackReport (@Field("poi_id") long arg1, @Field("tag_type") int arg2, @Field("entry_id") int arg3, @Field("reason_type") int arg4, @Field("extend") String arg5) ; @FormUrlEncoded @POST("v6/product/tag") d tagProduct (@Field("poi_id") long arg1, @Field("spu_id") long arg2, @Field("dpc_id") long arg3, @Field("tag_type") int arg4, @Field("entry_id") int arg5, @Field("reason_type") int arg6, @Field("extend") String arg7) ; }
ServiceMethod 类 ServiceMethod 类用来关联 interface method, 每个ServiceMethod对象关联一个interface method。
通过ServiceMethod$Builder.build来创建ServiceMethod 对象。ServiceMethod 记录了url,请求类型,参数个数,参数类型等等request信息;最终由ClientCall使用ServiceMethod 对象生成和发送request。
可以理解为一个interface method对应一个ServiceMethod;一个ServiceMethod对应一个request;
生成逻辑:interface method生成ServiceMethod,ServiceMethod生成request。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public final ServiceMethod build () { Type v4; int v2; this .callAdapter = this .createCallAdapter(); this .responseType = this .callAdapter.responseType(); if (this .responseType != Response.class && this .responseType != RawResponse.class) { this .responseConverter = this .createResponseConverter(); Annotation[] v1 = this .methodAnnotations; int v3; for (v3 = 0 ; v3 < v1.length; ++v3) { this .parseMethodAnnotation(v1[v3]); } if (this .httpMethod != null ) { if (!this .hasBody) { if (this .isMultipart) { throw this .methodError("Multipart can only be specified on HTTP methods with request body (e.g., @POST)." , new Object[0 ]); } if (this .isFormEncoded) { throw this .methodError("FormUrlEncoded can only be specified on HTTP methods with request body (e.g., @POST)." , new Object[0 ]); throw this .methodError("Multipart can only be specified on HTTP methods with request body (e.g., @POST)." , new Object[0 ]); } } this .parameterHandlers = new ParameterHandler[this .parameterAnnotationsArray.length]; v2 = 0 ; goto label_58; } throw this .methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)." , new Object[0 ]); while (true ) { label_58: if (v2 >= this .parameterAnnotationsArray.length) { goto label_81; } v4 = this .parameterTypes[v2]; if (Utils.hasUnresolvableType(v4)) { throw this .parameterError(v2, "Parameter type must not include a type variable or wildcard: %s" , new Object[]{v4}); } Annotation[] v3_1 = this .parameterAnnotationsArray[v2]; if (v3_1 == null ) { throw this .parameterError(v2, "No Retrofit annotation found." , new Object[0 ]); } this .parameterHandlers[v2] = this .parseParameter(v2, v4, v3_1); ++v2; } throw this .parameterError(v2, "No Retrofit annotation found." , new Object[0 ]); throw this .parameterError(v2, "Parameter type must not include a type variable or wildcard: %s" , new Object[]{v4}); label_81: if (this .relativeUrl == null && !this .gotUrl) { throw this .methodError("Missing either @%s URL or @Url parameter." , new Object[]{this .httpMethod}); } if (!this .isFormEncoded && !this .isMultipart && !this .hasBody && (this .gotBody)) { throw this .methodError("Non-body HTTP method cannot contain @Body." , new Object[0 ]); } if ((this .isFormEncoded) && !this .gotField) { throw this .methodError("Form-encoded method must contain at least one @Field." , new Object[0 ]); } if ((this .isMultipart) && !this .gotPart) { throw this .methodError("Multipart method must contain at least one @Part." , new Object[0 ]); } return new ServiceMethod(this ); } StringBuilder v1_1 = new StringBuilder("\'" ); v1_1.append(Utils.getRawType(this .responseType).getName()); v1_1.append("\' is not a valid response body type. Did you mean ResponseBody?" ); throw this .methodError(v1_1.toString(), new Object[0 ]); }
ClientCall 类 ClientCall对象由Retrofit生成的动态代理类创建,ClientCall用来生成okhttp.Request对象,并发送request。
如果在Retrofit + okhttp的app中,ClientCall表示为okhttp3.RealCall
;但是美团app的核心请求没有使用okhttp框架,而是自己实现的一套ClientCall
代码;所以美团ClientCall
的功能对标okhttp3.RealCall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public ClientCall (ServiceMethod arg19, Object[] arg20, List arg21, List arg22, Executor arg23, Cache arg24) { this .convertElapse = -1L ; this .serviceMethod = arg19; this .args = arg20; this .clientInterceptors = arg21; this .defInterceptors = arg22; this .httpExecutor = arg23; this .cache = arg24; } public final Response execute () throws IOException { Request v2; long v0 = System.currentTimeMillis(); __monitor_enter(this ); try { if (this .executed) { throw new IllegalStateException("Already executed." ); } this .executed = true ; if (this .creationFailure != null ) { if ((this .creationFailure instanceof IOException)) { throw (IOException)this .creationFailure; } throw (RuntimeException)this .creationFailure; } v2 = this .originalRequest; if (v2 == null ) { try { v2 = this .serviceMethod.toRequest(this .args); this .originalRequest = v2; goto label_42; } catch (IOException | RuntimeException v0_2) { } this .creationFailure = v0_2; throw v0_2; } label_42: __monitor_exit(this ); } catch (Throwable v0_1) { goto label_67; } try { Response v0_4 = this .parseResponse(this .getResponseWithInterceptorChain(v2.newBuilder().addHeader("retrofit_exec_time" , String.valueOf(v0)).build())); Retrofit.RequestCallbackDispatcher.onSuccess(this , this .realRequest, v0_4, this .convertElapse); return v0_4; } catch (Throwable v0_3) { } Retrofit.RequestCallbackDispatcher.onError(this , this .realRequest, v0_3); throw v0_3; try { throw new IllegalStateException("Already executed." ); label_67: __monitor_exit(this ); } catch (Throwable v0_1) { goto label_67; } throw v0_1; }
总结与hook 我们现在需要调用v7/poi/homepage
这个request,并拦截response。
首先确定这个url 属于 HomePageApi.getHomePagePoiList interface method:
1 2 3 4 5 6 7 8 9 public interface HomePageApi { @FormUrlEncoded @POST("v7/poi/homepage") d getHomePagePoiList (@Field("seq_num") int arg1, @Field("offset") int arg2, @Field("dynamic_page") boolean arg3, @Field("page_index") long arg4, @Field("page_size") long arg5, @Field("sort_type") long arg6, @Field("activity_filter_codes") String arg7, @Field("slider_select_data") String arg8, @Field("load_type") int arg9, @Field("rank_trace_id") String arg10, @Field("session_id") String arg11, @Field("union_id") String arg12, @Field("rank_list_id") String arg13, @Field("category_type") int arg14, @Field("second_category_type") int arg15, @Field("behavioral_characteristics") String arg16) ; }
hook com.sankuai.waimai.platform.capacity.network.retrofit.c.a(java.lang.Class)
方法,传入需要的interface后获取到全局唯一的Retrofit对象;使Retrofit框架为HomePageApi interface注册动态代理;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 let rObject = Java.use('java.lang.Object' );let rThread = Java.use('java.lang.Thread' );let rString = Java.use('java.lang.String' );let rInteger = Java.use('java.lang.Integer' );let rBoolean = Java.use('java.lang.Boolean' );let rLong = Java.use('java.lang.Long' );let rArray = Java.use('java.lang.reflect.Array' );let rRetrofit = Java.use('com.sankuai.meituan.retrofit2.Retrofit' );let rClientCall = Java.use('com.sankuai.meituan.retrofit2.ClientCall' );let rRetrofit$Builder = Java.use('com.sankuai.meituan.retrofit2.Retrofit$Builder' );let rRetrofit$Creator = Java.use('com.sankuai.waimai.platform.capacity.network.retrofit.c' );let rHomePageApi = Java.use('com.sankuai.waimai.business.page.home.net.request.HomePageApi' );let retrofit = rRetrofit$Creator.a.overload('java.lang.Class' ).call( rRetrofit$Creator, rHomePageApi.class ); console .log(`retrofit: ${retrofit} ` );let methods = rHomePageApi.class.getDeclaredMethods();let method;for (let i = 0 ; i < methods.length; i++) { if (methods[i].getName().indexOf('getHomePagePoiList' ) !== -1 ) { method = methods[i]; console .log(`method: ${method} ` ); break ; } } let serviceMethod = retrofit.loadServiceMethod(method);console .log(`serviceMethod: ${serviceMethod} ` );let params = Java.array('java.lang.Object' , [ rInteger.$new(0 ), rInteger.$new(0 ), rBoolean.$new(true ), rInteger.$new(0 ), rInteger.$new(20 ), rInteger.$new(0 ), "" , "" , rInteger.$new(1 ), "" , '1884e2f6-0fd6-4254-9555-9e701faf1a771604177571387562' , '77c5243a18ca439c9e3eb05ee894707ea159474625289990347' , '9a44b234332e43a69a6bb06d6de1863a' , rInteger.$new(0 ), rInteger.$new(0 ), '' ]); console .log(`ary: ${params} , ary.length: ${params.length} ` );let call = rClientCall.$new(serviceMethod, params, retrofit.interceptors.value, retrofit.defInterceptors.value, retrofit.httpExecutor.value, retrofit.cache.value);console .log(`call: ${call} ` );let response = call.execute();console .log(`response: ${response} ` );