美文网首页Android TechAndroid知识Android
Android开源项目-Retrofit+Okhttp+Gson

Android开源项目-Retrofit+Okhttp+Gson

作者: Tsy远 | 来源:发表于2016-07-21 20:01 被阅读5360次

    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<datalist> test();
    }
    

    发起调用请求:

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://192.168.2.135/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
    
            GitHubService service = retrofit.create(GitHubService.class);
    
            Call<datalist> repos = service.test();
    
            repos.enqueue(new Callback<datalist>() {
                @Override
                public void onResponse(Call<datalist> call, Response<datalist> response) {
                    Log.i("tsy", "repos=" + response.body().toString());
                }
    
                @Override
                public void onFailure(Call<datalist> 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<T> implements Callback<T> {
    
            private ApiCallback<T> mCallback;
    
            public RetrofitCallback(ApiCallback<T> callback) {
                mCallback = callback;
            }
    
            @Override
            public void onResponse(Call<T> call, Response<T> 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<T> call, Throwable t) {
                Log.e(TAG, "api failure,throw=" + t.getMessage());
                t.printStackTrace();
                mCallback.onFailure();
            }
        }
    
        //api调用回调
        public interface ApiCallback<T> {
            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<Void, Long, Void>() {
    
                @Override
                protected Void doInBackground(Void... params) {
                    Call<ResponseBody> call = apiStore.downloadFile(fileUrl);
    
                    call.enqueue(new Callback<ResponseBody>() {
                        @Override
                        public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
                            if (response.isSuccessful()) {
                                new AsyncTask<Void, Void, Void>() {
    
                                    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<ResponseBody> 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<ResponseBody> 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<LoginRetData> call = ((ApiStore)mApiStore).login();
            call.enqueue(new RetrofitCallback<LoginRetData>(callback));
        }
    
        public interface ApiStore {
            @GET("test_retrofit.php")
            Call<LoginRetData> 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!

    结尾

    更多文章关注我的公众号


    我的公众号

    相关文章

      网友评论

      • 6667babdb6b6:哥们,你这个 V就直接跟M交互了,设计模式考虑,少了一个主持类。如果在主持类中回调,你的每一个api不只对应,主持类也会发生改变。这样的封装还有欠缺。
        Tsy远:再详细说说呢,希望得到一些建议。感谢:stuck_out_tongue:
      • yedajiang44:楼主!如果我的BaseRetData中有个String类型的data属性,该data为服务端序列化泛型T后的jaon字符串,如果用您的代码
        if(((BaseRetData)response.body()).ret == 200) {//200为请求成功
        mCallback.onSuccess(((T)response.body()));
        }
        来实现onSuccess接收data反序列化的实体T,是不是要自己重新写一个GsonConverterFactory?因为我第一次用Retrofit和Okhttp。。。。希望博主能指点一下:pray:
      • 幻月痴人:楼主,这个要加LOG 怎么加,还有加一些基础的公共参数怎么加?
        ziabo_yu:okhttp自带的有一个logintercepter 你加进去就好了 基础的公共参数 okhttp还有别的拦截器 你可以看看我写的这个 https://github.com/ziabo/EasyAppMoudle
      • 南城的人:您好!我想问您一个问题,就是安卓4.4版本后的手机支持计步功能(4.4版本后的系统中写含有计步传感器,此时如果手机硬件功能中含有传感器,那么就可以实现计步功能),另外一种情况是手机中不含有计步传感器(硬件),此时应怎样实现计步功能?
        南城的人:@Tsy远 谢谢您,我在看看吧
        Tsy远:可能要通过重力感应之类的然后配合算法吧。因为我也没做过这方面
        应该首先要硬件能过去数据,如果过去不到直接数据就要通过间接数据,比如水平仪等,然后配合算法
        仅仅个人看法
      • xue57233:楼上的你们没经历过换网络请求框架的,想死😂😂
      • 72c35595f71b:没方便多少
      • 小子考驾照:这样封装越发复杂了

      本文标题:Android开源项目-Retrofit+Okhttp+Gson

      本文链接:https://www.haomeiwen.com/subject/bmgbjttx.html