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 的返回方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 的方式:
1 2 3 4 5 6 7 8 9 10 | 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
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 | 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 ); } |
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 89 90 91 92 93 94 95 96 97 | 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)就行了(见下面代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } |
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 89 90 | /**** 使用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; } } |
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 | 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,良好的网络接口模块封装是非常重要的。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 《HelloGitHub》第 106 期
· 数据库服务器 SQL Server 版本升级公告
· 深入理解Mybatis分库分表执行原理
· 使用 Dify + LLM 构建精确任务处理应用