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,良好的网络接口模块封装是非常重要的。

 

posted @   KingsLanding  阅读(2463)  评论(0编辑  收藏  举报
编辑推荐:
· 深入理解 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 构建精确任务处理应用
点击右上角即可分享
微信分享提示