Android:基于Rx、Retrofit、Dagger+MVP,如何筛选ApiService返回的JSON响应片段
嘿,这个场景我太熟悉了——面对体积庞大的JSON响应,只需要其中一小部分数据,确实没必要解析整个对象,既浪费内存又拖慢速度。结合你用的Rx+Retrofit技术栈,这里有几个实用的方案:
方案1:用简化的Model类(最省心)
这是日常开发中最常用的方法,只定义你需要的字段对应的模型类,Retrofit搭配Gson(默认Converter)会自动忽略JSON中未定义的字段。
比如你只需要User里的FirstName、BirthDate和GUID,那只需要创建一个简化版的User类:
public class SimplifiedUser { @SerializedName("FirstName") private String firstName; @SerializedName("BirthDate") private String birthDate; @SerializedName("GUID") private String guid; // 生成getter/setter方法 }
然后在你的ApiService里直接返回这个简化类的Observable:
public interface ApiService { @GET("your/user/api/path") Observable<SimplifiedUser> getUserInfo(); }
这样Retrofit会自动帮你把JSON中对应字段的数据映射到这个类里,完全不用管其他冗余字段。
方案2:手动解析ResponseBody(更灵活)
如果你的需求更动态,比如不同场景需要不同的字段组合,可以让接口返回ResponseBody,然后手动用JsonReader流式解析指定部分,避免把整个JSON加载到内存中。
先定义接口:
public interface ApiService { @GET("your/user/api/path") Observable<ResponseBody> getUserRawResponse(); }
然后在Rx的订阅逻辑里手动解析:
apiService.getUserRawResponse() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(responseBody -> { try (JsonReader reader = new JsonReader(responseBody.charStream())) { reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if ("User".equals(name)) { reader.beginObject(); while (reader.hasNext()) { String fieldName = reader.nextName(); switch (fieldName) { case "FirstName": String firstName = reader.nextString(); // 处理获取到的firstName break; case "BirthDate": String birthDate = reader.nextString(); // 处理获取到的birthDate break; case "GUID": String guid = reader.nextString(); // 处理获取到的guid break; default: reader.skipValue(); // 跳过不需要的字段 break; } } reader.endObject(); } else { reader.skipValue(); // 跳过User以外的顶层字段 } } reader.endObject(); } catch (IOException e) { e.printStackTrace(); } }, throwable -> { // 处理请求错误 });
这种方式内存效率最高,因为它是流式解析,不会把整个JSON转换成内存对象。
方案3:自定义Converter(进阶复用)
如果多个接口都需要类似的筛选逻辑,可以自定义Retrofit的Converter,把筛选逻辑封装起来,避免重复代码。
比如我们可以写一个自定义的TypeAdapter,专门解析User里的指定字段:
public class SimplifiedUserTypeAdapter extends TypeAdapter<SimplifiedUser> { @Override public void write(JsonWriter out, SimplifiedUser value) throws IOException { // 如果不需要序列化操作,这里可以抛出异常或留空 throw new UnsupportedOperationException("序列化操作不支持"); } @Override public SimplifiedUser read(JsonReader in) throws IOException { SimplifiedUser user = new SimplifiedUser(); in.beginObject(); while (in.hasNext()) { String name = in.nextName(); switch (name) { case "FirstName": user.setFirstName(in.nextString()); break; case "BirthDate": user.setBirthDate(in.nextString()); break; case "GUID": user.setGuid(in.nextString()); break; default: in.skipValue(); break; } } in.endObject(); return user; } }
然后在构建Retrofit的时候配置这个TypeAdapter:
Gson gson = new GsonBuilder() .registerTypeAdapter(SimplifiedUser.class, new SimplifiedUserTypeAdapter()) .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();
这样所有返回SimplifiedUser的接口都会自动用这个自定义解析逻辑。
额外建议
如果后端支持的话,最好让后端提供接口参数(比如fields=FirstName,BirthDate,GUID)来返回指定字段,这样从源头减少数据传输量,比客户端筛选更高效。
内容的提问来源于stack exchange,提问作者Cyrus the Great

