Android开源项目-Retrofit+Okhttp+Gson二次封装

Android网络请求开源项目组合:Retrofit+Okhttp+Gson,并二次封装,简化开发时的调用。

1 Retrofit+Okhttp+Gson是什么

Retrofit和Okhttp都是square公司开源的网络请求开源项目,也是当前最流行的网络请求组合。

Gson可以将json反序列化为相应的数据model,相对于从json取数据,数据model的建立对于获取字段数据和了解接口定义更为清晰。

2 为什么选择Retrofit+Okhttp+Gson

对于网络框架的选择有volley,android-async-http等很多很多,这些其实都是基于android本身httpurlconnecttion二次封装便于使用。而okhttp的网络框架网络请求效率更高,retrofit其实是一种封装方式。

网上有很多关于几个网络框架的比较,毫无疑问的okhttp+retrofit取得压倒性优势。唯一缺点可能就是需要学习成本。

出于之后项目的开发的考虑,最终选择了较为流行和效率高的okhttp+retrofit组合。

3 如何使用Retrofit+Okhttp+Gson

一个最基本的例子:

定义一个接口,专门放置接口

public interface GitHubService {
    @GET("test_retrofit.php")
    Call test();
}

发起调用请求:

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.2.135/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        GitHubService service = retrofit.create(GitHubService.class);

        Call repos = service.test();

        repos.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                Log.i("tsy", "repos=" + response.body().toString());
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                Log.i("tsy", "fail:" + t.getMessage());
            }
        });

Api还可以定义Get、Post、文件上传下载等等。具体的使用文档可以上网搜索。(很多很多,本篇更偏重说明如何二次封装以便于项目开发使用)

4 二次封装

如果使用retrofit本来的调用方式,项目开发起来肯定Api会写的代码很冗余而且和retrofit耦合太高。so、必须进行一个简单的二次开发。

首先,需要依赖的库

dependencies {
    ...

    //网络请求 retrofit+okhttp+gson
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.google.code.gson:gson:2.7'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2';
}

项目开发和服务端接口约定格式是json
如果成功则返回:

{
    ret => 1,
    data => 业务数据
}

如果失败返回:

{
    ret => 0,
    err_code => 错误码,
    err_msg => 错误信息
}

于是定义一个基础数据model,所有Gson的model都继承该model,可以自定义业务数据

/**
 * Gson返回Ret基本格式
 * 成功:ret=1 + 业务数据
 * 失败:ret=0 + err_code + err_msg
 * Created by tsy on 16/7/21.
 */
public class BaseRetData {
    public int ret;         //成功-1 失败-0
    public int err_code;    //错误code
    public String err_msg;  //错误msg
}

下面,就是对retrofit的二次封装,先上代码后面解释怎么用,BaseApi:

/**
 * BaseApi
 * Created by tsy on 16/7/21.
 */
public class BaseApi {

    private static final String mBaseUrl = "http://www.baidu.com/";

    protected Retrofit mRetrofit;

    private final String TAG = "BaseApi";

    public BaseApi() {
        mRetrofit = new Retrofit.Builder()
                .baseUrl(mBaseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

    }

    public BaseApi(String baseUrl) {
        mRetrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

    }

    //处理retrofit回调 并调用ApiCallback相应返回
    protected class RetrofitCallback implements Callback {

        private ApiCallback mCallback;

        public RetrofitCallback(ApiCallback callback) {
            mCallback = callback;
        }

        @Override
        public void onResponse(Call call, Response response) {
            if(response.isSuccessful()) {
                if(((BaseRetData)response.body()).ret == 1) {
                    mCallback.onSuccess(((T)response.body()));
                } else {
                    mCallback.onError(((BaseRetData)response.body()).err_code, ((BaseRetData)response.body()).err_msg);
                }
            } else {
                mCallback.onFailure();
            }
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            Log.e(TAG, "api failure,throw=" + t.getMessage());
            t.printStackTrace();
            mCallback.onFailure();
        }
    }

    //api调用回调
    public interface ApiCallback {
        void onSuccess(T ret);        //ret=1时返回
        void onError(int err_code, String err_msg);   //ret=0时返回
        void onFailure();   //网络请求失败
    }

    //文件下载回调
    public interface FileDownloadCallback {
        void onSuccess();   //下载成功返回
        void onProcess(long fileSizeDownloaded, long fileSize);   //下载进度
        void onFailure();   //网络请求失败
    }

    /**
     * 下载文件
     * @param fileUrl 下载url
     * @param filePath 本地保存path
     * @param callback FileDownloadCallback回调
     */
    public void downloadFile(final String fileUrl, final String filePath, final FileDownloadCallback callback) {
        final ApiStore apiStore = mRetrofit.create(ApiStore.class);

        new AsyncTask() {

            @Override
            protected Void doInBackground(Void... params) {
                Call call = apiStore.downloadFile(fileUrl);

                call.enqueue(new Callback() {
                    @Override
                    public void onResponse(Call call, final Response response) {
                        if (response.isSuccessful()) {
                            new AsyncTask() {

                                private boolean mWrittenToDisk;

                                @Override
                                protected Void doInBackground(Void... voids) {
                                    mWrittenToDisk = writeResponseBodyToDisk(response.body(), filePath, callback);
                                    return null;
                                }

                                @Override
                                protected void onPostExecute(Void aVoid) {
                                    if(mWrittenToDisk) {
                                        callback.onSuccess();
                                    } else {
                                        callback.onFailure();
                                    }
                                }
                            }.execute();


                        } else {
                            callback.onFailure();
                        }
                    }

                    @Override
                    public void onFailure(Call call, Throwable t) {
                        callback.onFailure();
                    }
                });
                return null;
            }
        }.execute();
    }

    /**
     * responsebody写入文件
     * @param body
     * @param filePath
     * @param callback
     * @return
     */
    private boolean writeResponseBodyToDisk(ResponseBody body, String filePath, FileDownloadCallback callback) {
        try {
            File file = new File(filePath);

            String dir = filePath.substring(0, filePath.lastIndexOf('/'));
            File fileDir = new File(dir);
            if(!fileDir.exists()) {
                fileDir.mkdirs();
            }

            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                byte[] fileReader = new byte[4096];

                long fileSize = body.contentLength();
                long fileSizeDownloaded = 0;

                inputStream = body.byteStream();
                outputStream = new FileOutputStream(file);

                while (true) {
                    int read = inputStream.read(fileReader);

                    if (read == -1) {
                        break;
                    }

                    outputStream.write(fileReader, 0, read);

                    fileSizeDownloaded += read;

                    callback.onProcess(fileSizeDownloaded, fileSize);
                }

                outputStream.flush();

                return true;
            } catch (IOException e) {
                file.delete();
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            return false;
        }
    }

    public interface ApiStore {
        @Streaming
        @GET
        Call downloadFile(@Url String fileUrl);
    }
}

把上面2个Base先放到项目中,假如要开发一个登陆功能(这里举例子只列举一个接口)

先定义登陆Api:

public class LoginApi extends BaseApi {

    private static final String mBaseUrl = "http://192.168.3.1/";

    private ApiStore mApiStore;

    public LoginApi() {
        super(mBaseUrl);
        mApiStore = mRetrofit.create(ApiStore.class);
    }

    public void login(String username, String password, ApiCallback callback) {
        Call call = ((ApiStore)mApiStore).login();
        call.enqueue(new RetrofitCallback(callback));
    }

    public interface ApiStore {
        @GET("test_retrofit.php")
        Call login();
    }
}

定义该接口的返回数据model

public class LoginRetData extends BaseDataRet {
    public int user_id;
}

如上所述。开发新的功能模块Api时,只要继承BaseApi,如果Domin是通用的直接可以吧Domin写在BaseApi中的BaseUrl,如果是以微服务模式开发,也可以写在独立功能模块Api的BaseUrl中。开发时只需要专注编写ApiStore中接口,然后增加一个对外调用的接口。

外部调用API层接口和回调:

        new LoginApi().login("tsy", "as", new BaseApi.ApiCallback() {
            @Override
            public void onSuccess(BaseDataRet ret) {
                Log.i("tsy", "onSuccess:");
            }

            @Override
            public void onError(int err_code, String err_msg) {
                Log.i("tsy", "onError:");
            }

            @Override
            public void onFailure() {
                Log.i("tsy", "onFailure:");
            }
        });

回调定义了3个方法,因为我和服务端约定的返回是固定的,所以onSuccess就是在成功返回并且ret=1时触发,onError是成功返回但是ret=0时触发,onFailure是网络请求失败或者结果解析Gson错误的,可以通用理解为服务器错误。

同时在BaseApi中提供有下载方法和回调。使用示例为:

mLoginApi.downloadFile(url, filePath, new BaseApi.FileDownloadCallback() {
                            @Override
                            public void onSuccess() {
                                Log.i("tsy", "download onSuccess");
                            }

                            @Override
                            public void onProcess(long fileSizeDownloaded, long fileSize) {
                                Log.i("tsy", "download onProcess:" + fileSizeDownloaded + "/" + fileSize);
                            }

                            @Override
                            public void onFailure() {
                                Log.i("tsy", "download onFailure");
                            }
                        });

5 总结

经过以上封装后,开发人员专注于编写各个功能模块的Api层,在Api层里面也只需要专注于定义接口即可。

外部调用接口时其实是不知道retrofit的存在,这样做到了解耦。万一以后需要改底层框架也不需要改动业务层。

以上代码Github地址:

https://github.com/tsy12321/BaseAndroidProject

注:该项目会做成一个基础的项目框架,包含各种封装好的工具,底层库和MVP架构,还在不断更新中,欢迎关注提Issue!

结尾

更多文章关注我的公众号


Android开源项目-Retrofit+Okhttp+Gson二次封装_第1张图片
我的公众号

你可能感兴趣的:(Android开源项目-Retrofit+Okhttp+Gson二次封装)