Android 网络通信API的选择和实现实例
Android开发网络通信一开始的时候使用的是AsyncTask封装HttpClient,没有使用原生的HttpURLConnection就跳到了Volley,随着OkHttp的流行又开始迁移到OkHttp上面,随着Rxjava的流行又了解了Retrofit,随着Retrofit的发展又从1.x到了2.x......。好吧,暂时到这里。
那么的多的使用工具有时候有点眼花缭乱,今天来总结一下现在比较流行的基于OkHttp 和 Retrofit 的网络通信API设计方法。有些同学可能要想,既然都有那么好用的Volley和Okhttp了,在需要用到的地方创建一个Request然后交给RequestQueue(Volley的方式)或者 Call(Okhttp的方式)就行了吗,为什么还那么麻烦? 但是我认为这种野生的网络库的用法还是是有很多弊端(弊端就不说了,毕竟是总结新东西),在好的Android架构中都不会出现这样的代码。
网络通信都是异步完成,设计网络API我觉得首先需要考虑异步结果的返回机制。基于Okhttp或Retrofit,我们考虑如何返回异步的返回结果,有几种方式:
1. 直接返回:
OkHttp 的返回方式:
OkHttpClient : OkHttpClient client = new OkHttpClient(); Request : Request request = new Request.Builder() .url("https://api.github.com/repos/square/okhttp/issues") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build(); //第一种 Response response = client.newCall(request).execute(); // 第二种 client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, Throwable throwable) { } @Override public void onResponse(Response response) throws IOException { } }
Retrofit 的方式:
interface GitHubService { @GET("/repos/{owner}/{repo}/contributors") Call<List<Contributor>> repoContributors( @Path("owner") String owner, @Path("repo") String repo); } Call<List<Contributor>> call = gitHubService.repoContributors("square", "retrofit"); response = call.execute();
上面的方式适用于野生的返回网络请求的内容。
2. 使用事件总线(Otto,EventBus,RxBus(自己使用PublishSubject封装))
代码来源:https://github.com/saulmm/Material-Movies
public interface MovieDatabaseAPI { /************Retrofit 1.x ,使用异步的方式返回 ****************/ @GET("/movie/popular") void getPopularMovies( @Query("api_key") String apiKey, Callback<MoviesWrapper> callback); @GET("/movie/{id}") void getMovieDetail ( @Query("api_key") String apiKey, @Path("id") String id, Callback<MovieDetail> callback ); @GET("/movie/popular") void getPopularMoviesByPage( @Query("api_key") String apiKey, @Query("page") String page, Callback<MoviesWrapper> callback ); @GET("/configuration") void getConfiguration ( @Query("api_key") String apiKey, Callback<ConfigurationResponse> response ); @GET("/movie/{id}/reviews") void getReviews ( @Query("api_key") String apiKey, @Path("id") String id, Callback<ReviewsWrapper> response ); @GET("/movie/{id}/images") void getImages ( @Query("api_key") String apiKey, @Path("id") String movieId, Callback<ImagesWrapper> response ); }
public class RestMovieSource implements RestDataSource { private final MovieDatabaseAPI moviesDBApi; private final Bus bus; /***********使用了Otto**************/ public RestMovieSource(Bus bus) { RestAdapter movieAPIRest = new RestAdapter.Builder() /*** Retrofit 1.x ***/ .setEndpoint(Constants.MOVIE_DB_HOST) .setLogLevel(RestAdapter.LogLevel.HEADERS_AND_ARGS) .build(); moviesDBApi = movieAPIRest.create(MovieDatabaseAPI.class); this.bus = bus; } @Override public void getMovies() { moviesDBApi.getPopularMovies(Constants.API_KEY, retrofitCallback); } @Override public void getDetailMovie(String id) { moviesDBApi.getMovieDetail(Constants.API_KEY, id, retrofitCallback); } @Override public void getReviews(String id) { moviesDBApi.getReviews(Constants.API_KEY, id, retrofitCallback); } @Override public void getConfiguration() { moviesDBApi.getConfiguration(Constants.API_KEY, retrofitCallback); } @Override public void getImages(String movieId) { moviesDBApi.getImages(Constants.API_KEY, movieId, retrofitCallback); } public Callback retrofitCallback = new Callback() { /******************这里统一的Callback,根据不同的返回值使用事件总线进行返回**************************/ @Override public void success(Object o, Response response) { if (o instanceof MovieDetail) { MovieDetail detailResponse = (MovieDetail) o; bus.post(detailResponse); } else if (o instanceof MoviesWrapper) { MoviesWrapper moviesApiResponse = (MoviesWrapper) o; bus.post(moviesApiResponse); } else if (o instanceof ConfigurationResponse) { ConfigurationResponse configurationResponse = (ConfigurationResponse) o; bus.post(configurationResponse); } else if (o instanceof ReviewsWrapper) { ReviewsWrapper reviewsWrapper = (ReviewsWrapper) o; bus.post(reviewsWrapper); } else if (o instanceof ImagesWrapper) { ImagesWrapper imagesWrapper = (ImagesWrapper) o; bus.post(imagesWrapper); } } @Override public void failure(RetrofitError error) { System.out.printf("[DEBUG] RestMovieSource failure - " + error.getMessage()); } }; @Override public void getMoviesByPage(int page) { moviesDBApi.getPopularMoviesByPage( Constants.API_KEY, page + "", retrofitCallback ); } }
3. 返回Observable(这里也可以考虑直接返回Observable 和间接返回Observable)
直接的返回 Observable,在创建 apiService 的时候使用 Retrofit.create(MovieDatabaseAPI)就行了(见下面代码)
public interface MovieDatabaseAPI { @GET("/movie/popular") Observable<MovieWrapper> getPopularMovies( @Query("api_key") String apiKey, ); @GET("/movie/{id}") Observable<MovideDetail> getMovieDetail ( @Query("api_key") String apiKey, @Path("id") String id, ); }
间接返回Observable,这里参考了AndroidCleanArchitecture:
public interface RestApi { /************定义API接口*****************/ String API_BASE_URL = "http://www.android10.org/myapi/"; /** Api url for getting all users */ String API_URL_GET_USER_LIST = API_BASE_URL + "users.json"; /** Api url for getting a user profile: Remember to concatenate id + 'json' */ String API_URL_GET_USER_DETAILS = API_BASE_URL + "user_"; /** * Retrieves an {@link rx.Observable} which will emit a List of {@link UserEntity}. */ Observable<List<UserEntity>> userEntityList(); /** * Retrieves an {@link rx.Observable} which will emit a {@link UserEntity}. * * @param userId The user id used to get user data. */ Observable<UserEntity> userEntityById(final int userId); }
/**** 使用Rx Observable 实现 RestApi 接口,实际调用的是 ApiConnection 里面的方法 ****/ public class RestApiImpl implements RestApi { /***注意这里没有使用Retrofit,而是对上面接口的实现***/ private final Context context; private final UserEntityJsonMapper userEntityJsonMapper; /** * Constructor of the class * * @param context {@link android.content.Context}. * @param userEntityJsonMapper {@link UserEntityJsonMapper}. */ public RestApiImpl(Context context, UserEntityJsonMapper userEntityJsonMapper) { if (context == null || userEntityJsonMapper == null) { throw new IllegalArgumentException("The constructor parameters cannot be null!!!"); } this.context = context.getApplicationContext(); this.userEntityJsonMapper = userEntityJsonMapper; } @RxLogObservable(SCHEDULERS) @Override public Observable<List<UserEntity>> userEntityList() { return Observable.create(subscriber -> { if (isThereInternetConnection()) { try { String responseUserEntities = getUserEntitiesFromApi(); if (responseUserEntities != null) { subscriber.onNext(userEntityJsonMapper.transformUserEntityCollection( responseUserEntities)); subscriber.onCompleted(); } else { subscriber.onError(new NetworkConnectionException()); } } catch (Exception e) { subscriber.onError(new NetworkConnectionException(e.getCause())); } } else { subscriber.onError(new NetworkConnectionException()); } }); } @RxLogObservable(SCHEDULERS) @Override public Observable<UserEntity> userEntityById(final int userId) { return Observable.create(subscriber -> { if (isThereInternetConnection()) { try { String responseUserDetails = getUserDetailsFromApi(userId); if (responseUserDetails != null) { subscriber.onNext(userEntityJsonMapper.transformUserEntity(responseUserDetails)); subscriber.onCompleted(); } else { subscriber.onError(new NetworkConnectionException()); } } catch (Exception e) { subscriber.onError(new NetworkConnectionException(e.getCause())); } } else { subscriber.onError(new NetworkConnectionException()); } }); } private String getUserEntitiesFromApi() throws MalformedURLException { return ApiConnection.createGET(RestApi.API_URL_GET_USER_LIST).requestSyncCall(); } private String getUserDetailsFromApi(int userId) throws MalformedURLException { String apiUrl = RestApi.API_URL_GET_USER_DETAILS + userId + ".json"; return ApiConnection.createGET(apiUrl).requestSyncCall(); } /** * Checks if the device has any active internet connection. * * @return true device with internet connection, otherwise false. */ private boolean isThereInternetConnection() { boolean isConnected; ConnectivityManager connectivityManager = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting()); return isConnected; } }
public class ApiConnection implements Callable<String> { /***********************网络接口的实际实现********************************/ private static final String CONTENT_TYPE_LABEL = "Content-Type"; private static final String CONTENT_TYPE_VALUE_JSON = "application/json; charset=utf-8"; private URL url; private String response; private ApiConnection(String url) throws MalformedURLException { this.url = new URL(url); } public static ApiConnection createGET(String url) throws MalformedURLException { return new ApiConnection(url); } /** * Do a request to an api synchronously. * It should not be executed in the main thread of the application. * * @return A string response */ @Nullable public String requestSyncCall() { connectToApi(); return response; } private void connectToApi() { OkHttpClient okHttpClient = this.createClient(); /*******************使用OKhttp的实现*******************/ final Request request = new Request.Builder() .url(this.url) .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_VALUE_JSON) .get() .build(); try { this.response = okHttpClient.newCall(request).execute().body().string(); } catch (IOException e) { e.printStackTrace(); } } private OkHttpClient createClient() { final OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setReadTimeout(10000, TimeUnit.MILLISECONDS); okHttpClient.setConnectTimeout(15000, TimeUnit.MILLISECONDS); return okHttpClient; } @Override public String call() throws Exception { return requestSyncCall(); } }
这里简单总结了一下OkHttp和Retrofit该如何封装,这样的封装放在整个大的代码框架中具有很好的模块化效果。对于使用MVP架构或者类似架构的APP,良好的网络接口模块封装是非常重要的。