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

从事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下载地址

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值