一个基于Retrofit的单文件上传、下载框架

来源:互联网 发布:js new array 区别 编辑:程序博客网 时间:2024/05/16 07:37

从事Android开发工作也有一段时间了,一直都停留在使用框架别人的框架来满足公司的业务需求,很少深入到一个框架的内部,去研究它的实现方式和实现原理,更加没有自己去写过框架,真的是非常惭愧。
最近老大让我用Retrofit做一个单文件的上传和下载模块,几番折腾之后,花了三天时间,终于搞出来了,虽然很简单,但通过这么一个例子,让我学到了封装一个框架的基本思路,在这里做一个记录,顺便分享给大家。
代码下载地址在文章末尾。
首先,来说一下用Retrofit怎么实现文件的上传和下载。
1.需要在项目中新建一个接口,叫:UploadDownloadService,在其中可以使用注解的方式写三个方法:

  /**     * Upload a file without any url params.     *     * @param url the url linking to your file server     * @param description the file's description to upload     * @param file the file to upload     * @return the uploading file's results     */    @Multipart    @POST    Call<ResponseBody> uploadFileWithoutParams(@Url String url,        @Part("description") RequestBody description, @Part MultipartBody.Part file);    /**     * Upload a file with some params attached to the url.     *     * @param url the url linking to your file server     * @param map the params attached to the url     * @param description the file's description to upload     * @param file the file to upload     * @return the uploading file's result     */    @Multipart    @POST    Call<ResponseBody> uploadFileWithParams(@Url String url, @QueryMap Map<String, Object> map,        @Part("description") RequestBody description, @Part MultipartBody.Part file);    /**     * Download file from an url     *     * @param fileUrl the particular destination url     * @return download information     */    @Streaming @GET Call<ResponseBody> downloadFile(@Url String fileUrl);

前两个方法都是用于上传文件的,主要区别在于第一个方法的url后面不需要带任何参数,第二个方法的url后面需要携带参数,当然你也可以给第二个方法的参数传空,这里为了区别,就写的明显一点。第三个方法用于下载文件,参数就是一个url的地址。
然后还需要新建两个类,叫ManagerFactory和RetrofitFactory,主要作用就是生产UploadDownloadService的实例(工厂模式),源代码如下:
RetrofitFactory:

package com.yulin.download_upload.api;import android.support.annotation.NonNull;import okhttp3.OkHttpClient;import retrofit2.Converter;import retrofit2.Retrofit;import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;import retrofit2.converter.gson.GsonConverterFactory;public class RetrofitFactory {  private static Retrofit mRetrofit;  private static OkHttpClient mClient;  private static String baseUrl = "http://www.baidu.com";  public static void setBaseUrl(@NonNull String url) {    baseUrl = url;  }  public static void setOkhttpClient(@NonNull OkHttpClient client) {    mClient = client;  }  /**   * 获取配置好的retrofit对象来生产Manager对象   */  public static Retrofit getRetrofit() {    if (mRetrofit == null) {//      if (baseUrl == null || baseUrl.length() <= 0) {//        throw new IllegalStateException("请在调用getFactory之前先调用setBaseUrl");//      }      Retrofit.Builder builder = new Retrofit.Builder();      builder.baseUrl(baseUrl)          .addCallAdapterFactory(RxJavaCallAdapterFactory.create())          .addConverterFactory(GsonConverterFactory.create());      if (mClient != null) builder.client(mClient);      mRetrofit = builder.build();    }    return mRetrofit;  }  /**   * 获取配置好的retrofit对象来生产Manager对象   */  public static Retrofit getRetrofit(Converter.Factory factory) {    if (baseUrl == null || baseUrl.length() <= 0) {      throw new IllegalStateException("请在调用getFactory之前先调用setBaseUrl");    }    Retrofit.Builder builder = new Retrofit.Builder();    builder.baseUrl(baseUrl)        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())        .addConverterFactory(factory);    if (mClient != null) builder.client(mClient);    return builder.build();  }}

ManagerFactory:

package com.yulin.download_upload.api;import java.util.HashMap;import retrofit2.Converter;public class ManagerFactory {  private static ManagerFactory factory;  private static HashMap<String, Object> serviceMap = new HashMap<>();  public static ManagerFactory getFactory() {    if (factory == null) {      synchronized (ManagerFactory.class) {        if (factory == null) factory = new ManagerFactory();      }    }    return factory;  }  public <T> T getManager(Class<T> clz) {    Object service = serviceMap.get(clz.getName());    if (service == null) {      service = RetrofitFactory.getRetrofit().create(clz);      serviceMap.put(clz.getName(), service);    }    return (T) service;  }  public <T> T getManager(Class<T> clz, Converter.Factory factory) {    Object service = serviceMap.get(clz.getName());    if (service == null) {      service = RetrofitFactory.getRetrofit(factory).create(clz);      serviceMap.put(clz.getName(), service);    }    return (T) service;  }}

写好了这三个类之后,我们就可以正常使用了,首先我们要通过工厂来创建UploadDownloadService的实例:

UploadDownloadService service = ManagerFactory.getFactory().getManager(UploadDownloadService.class);

然后我们要对上传的信息做一些配置,比如要上传的文件,相关的参数等等

private static final String IMAGE_SAVE_DIR = Environment.    getExternalStorageDirectory().getPath()           + "/yulin/photo";private static final String IMAGE_SAVE_PATH =     IMAGE_SAVE_DIR + "/demo.jpg";    //要上传的文件File file = new File(IMAGE_SAVE_PATH);private static final String url =     "http://pic.test.com";//模拟的文件上传地址RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);MultipartBody.Part body = MultipartBody.Part.createFormData(               “file”,               file.getName(),               requestFile);RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"),"this is a test file");

配置好上传信息之后,我们就可以使用UploadDownloadService的实例来执行具体的上传文件的操作,这里假定我们上传的文件是要带参数的:

Call<ResponseBody> call = mService.uploadFileWithParams(    getUploadUrl(),         getParamsMap(), //获取参数    description, body);call.enqueue(new Callback<ResponseBody>() {   @Override   public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {    //成功回调   }   @Override   public void onFailure(Call<ResponseBody> call, Throwable t) {    //失败回调   });

使用Retrofit实现单个文件的下载与上传类似,这里就不在贴了。
如果我们想自己将上面这个上传文件的过程封装一下,该如何做呢?首先肯定是要提取一个类,这个类提供一个上传文件的方法,上传的过程中我们还要提供回调,因此还需要提取一个回调类,然后相关的配置信息也应该用一个单独的类来进行管理。于是我们就要创建三个类,分别叫RetrofitUploadManager、RetrofitUploadAdapter和RetrofitUoloadConfig,第一个类负责主要的上传业务,第二个类提供上传回调,第三个类的作用是将相关的配置信息统一管理。
我们分别来看一下这三个类的基本结构:
RetrofitUploadManager

public class RetrofitUploadManager {    private static final String TAG = RetrofitUploadManager.        class.getSimpleName();    private WeakReference<Context> mContext;    private RetrofitUploadConfig mConfig;    private RetrofitUploadAdapter mAdapter;    private RetrofitUploadService mService;    public RetrofitUploadManager(        RetrofitUploadConfig retrofitUploadConfig) {}    public void uploadFile(File file) {}    public void release() {}}

接着来看RetrofitUploadAdapter这个类:

public abstract class RetrofitUploadAdapter<T> {    private static final String TAG = RetrofitUploadAdapter.class.        getSimpleName();    private Class<T> mClazz;    public Class<T> getClazz() {        return mClazz;    }    public RetrofitUploadAdapter() {        try {            ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();            mClazz =(Class<T>) pt.getActualTypeArguments()[0];        }catch (Exception e) {            Log.e(TAG, e + "");            mClazz = null;        }    }    public void onUploadSuccess(int code, String message) {}    public void onUploadFailure(int code, String message){}    public void onUploadError(Throwable t){}}

这是一个抽象类,来提供下载结果的回调信息。可能会有人会问,为什么不用接口呢?主要有两个原因,一个是因为这里需要用到泛形,需要在类初始化的得到泛形的真实类型,其次是如果用接口的话需要实现所有的方法,而抽象类可以选择性的复写相应的方法。
最后看RetrofitUploadConfig这个类:

public class RetrofitUploadConfig {    private Context mContext;    private String mUploadUrl;    private String mFileKey;    private String mDescriptionString;    private Map<String, Object> mParamsMap;    private String mFileName;    private RetrofitUploadAdapter mRetrofitUploadAdapter;    private RetrofitUploadConfig() {}    public Context getContext() {        return mContext;    }    public String getUploadUrl() {        return mUploadUrl;    }    public String getFileKey() {        return mFileKey;    }    public String getDescriptionString() {        return mDescriptionString;    }      public Map<String, Object> getParamsMap() {        return mParamsMap;    }    public String getFileName() {        return mFileName;    }    public RetrofitUploadAdapter getRetrofitUploadAdapter() {        return mRetrofitUploadAdapter;    }    public static class Builder{        private static final String DEFAULT_FILE_KEY = "file";        private static final String DEFAULT_DESCRIPTION = "this is uploaded file description";        private Context mContext;        private String mUploadUrl;        private String mFileKey = DEFAULT_FILE_KEY;        private String mDescriptionString = DEFAULT_DESCRIPTION;        private Map<String, Object> mParamsMap;        private String mFileName;        private RetrofitUploadAdapter mRetrofitUploadAdapter;        public Builder(Context context) {            this.mContext = context;        }        public Builder setParamsMap(            Map<String, Object> paramsMap) {            this.mParamsMap = paramsMap;            return this;        }        public Builder setFileName(String fileName) {            this.mFileName = fileName;            return this;        }        public Builder setUploadUrl(String uploadUrl) {            this.mUploadUrl = uploadUrl;            return this;        }        public Builder setFileKey(String fileKey) {            this.mFileKey = fileKey;            return this;        }        public Builder setDescriptionString(            String descriptionString) {            this.mDescriptionString = descriptionString;            return this;        }        public Builder setRetrofitUploadAdapter(            RetrofitUploadAdapter retrofitUploadListener) {            this.mRetrofitUploadAdapter = retrofitUploadListener;            return this;        }        private void applyConfig(            RetrofitUploadConfig retrofitUploadConfig) {            retrofitUploadConfig.mContext = this.mContext;            retrofitUploadConfig.mUploadUrl = this.mUploadUrl;            retrofitUploadConfig.mFileKey = this.mFileKey;            retrofitUploadConfig.mDescriptionString                 = this.mDescriptionString;            retrofitUploadConfig.mParamsMap = this.mParamsMap;            retrofitUploadConfig.mFileName = this.mFileName;            retrofitUploadConfig.mRetrofitUploadAdapter                 = this.mRetrofitUploadAdapter;        }        public RetrofitUploadConfig build() {            RetrofitUploadConfig retrofitUploadConfig                 = new RetrofitUploadConfig();            applyConfig(retrofitUploadConfig);            return retrofitUploadConfig;        }    }}

这个类使用了建造者模式,来对外提供了相关变量的设置和获取方法。
最后来看一下如何使用:

private String url = "http://test.pic.com/";//你的文件服务器地址private String nameKey = "file";private void uploadFile(File file) {    Map<String, Object> paramMap = new HashMap<>();    paramMap.put("key",         "5fcfe94a91b1d2ae08867a4f3c55455c");    RetrofitUploadConfig retrofitUploadConfig         = new RetrofitUploadConfig.Builder(this)        .setUploadUrl(url)        .setParamsMap(paramMap)        .setFileKey(nameKey)        .setDescriptionString("this is uploading test file")        .setRetrofitUploadAdapter(            new RetrofitUploadAdapter<PhotoBean>() {                @Override                 public void onUploadSuccess(int code,                     PhotoBean photoBean) {                    if (photoBean != null                     && !TextUtils.isEmpty(                        photoBean.getUrl())) {                        mUploading.setText("Upload Success");                     }else {                        mUploading.setText("Upload Failure");             }    }public void onUploadFailure(int code, String message) {        mUploading.setText("Upload Failure");     }    @Override     public void onUploadError(Throwable t) {        mUploading.setText("Upload Error");    }    }).build();RetrofitUploadManager retrofitUploadManager     = new RetrofitUploadManager(retrofitUploadConfig);    retrofitUploadManager.uploadFile(file);}

使用起来还是蛮简单的,如果还有什么功能可以自己去扩展。下载基本上与上传类似,有兴趣的朋友可以去下载下来看看。另外,Demo的使用示例还用到了拍照和上传,如果看不懂的可以参考这篇博客:Android拍照及选择图片及裁剪及兼容6.0权限实现

Demo下载地址

0 0
原创粉丝点击