抓住人生中的一分一秒,胜过虚度中的一月一年!
小做个动图开篇引题
懒洋洋.gif
前言
目前较火的网络框架有MVP+Retrofit2+okhttp3+Rxjava2,于是也加入了使用行列,本框架为Retrofit基本写法及特殊情况处理衍生,为大家学习使用提供帮助,本次优化对使用过程中所遇到问题进行总结,基本满足实际开发需求,有不足地方我将继续完善
相关业务需求及解决方案 |
一、 MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建及使用 |
二、 对BaseActivity、BaseFragment封装协调框架更好使用 |
三、 Android部分手机4G网第一次请求很慢(wifi正常)解决方案 |
四、 Retrofit运行时动态改变BaseUrl解决方案 |
五、 Retrofit文件上传(本片文章介绍中包含进度条) |
六、 Retrofit文件下载(含进度条) |
七、 Retrofit,Gson解析,请求返回的类型不统一,假如double返回的是null |
八、 Retrofit实现cookie自动化管理 |
九、 路由判断第二种解决方案(文章为旧版,提供思路) |
十、 Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等) |
十一、 后记 |
十二、 本文譩在一篇文章搞定所有,上述描述文章都有讲解 |
一、MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建
1、 相关依赖引用
implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.squareup.retrofit2:retrofit:2.4.0' //ConverterFactory的Gson依赖包 implementation 'com.squareup.retrofit2:converter-gson:2.4.0' //CallAdapterFactory的Rx依赖包 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' //cookie管理 implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' //日志 implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
2、 创建接口类ApiServer,定义接口方法
public interface ApiServer { @FormUrlEncoded @POST("/api/table_list/") Observable<BaseModel<Object>> getCeShi(@FieldMap HashMap<String, String> params); }
3、 创建Retrofit
public class ApiRetrofit { private static ApiRetrofit mApiRetrofit; private Retrofit retrofit; private ApiServer apiServer; private static final int DEFAULT_TIMEOUT = 15; public static String mBaseUrl = BaseContent.baseUrl; public ApiRetrofit() { OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); httpClientBuilder .cookieJar(new CookieManger(App.getContext())) .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); retrofit = new Retrofit.Builder() .baseUrl(mBaseUrl) .addConverterFactory(GsonConverterFactory.create()) //支持RxJava2 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(httpClientBuilder.build()) .build(); apiServer = retrofit.create(ApiServer.class); } public static ApiRetrofit getInstance() { if (mApiRetrofit == null) { synchronized (Object.class) { if (mApiRetrofit == null) { mApiRetrofit = new ApiRetrofit(); } } } return mApiRetrofit; } public ApiServer getApiService() { return apiServer; } }
4、 定义常用的接口,如网络请求开始,结束,进度条加载,错误码等
public interface BaseView { //显示dialog void showLoading(); //隐藏 dialog void hideLoading(); //显示错误信息 void showError(String msg); //错误码 void onErrorCode(BaseModel model); //进度条显示 void showProgress(); //进度条隐藏 void hideProgress(); //文件下载进度监听 void onProgress(int progress); }
5、 BaseModel封装
封装理由:一个项目一般情况下json返回格式外层都是统一的
public class BaseModel<T> implements Serializable { private String reason; private int error_code; private T result; public BaseModel(String reason, int error_code) { this.reason = reason; this.error_code = error_code; } public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } public int getError_code() { return error_code; } public void setError_code(int error_code) { this.error_code = error_code; } public T getResult() { return result; } public void setResult(T result) { this.result = result; } }
6、 BasePresenter封装,协调m层v层的中间信使通用代码封装
public class BasePresenter<V extends BaseView> { private CompositeDisposable compositeDisposable; public V baseView; protected ApiServer apiServer = ApiRetrofit.getInstance().getApiService(); public BasePresenter(V baseView) { this.baseView = baseView; } /** * 解除绑定 */ public void detachView() { baseView = null; removeDisposable(); } /** * 返回 view * * @return */ public V getBaseView() { return baseView; } public void addDisposable(Observable<?> observable, BaseObserver observer) { if (compositeDisposable == null) { compositeDisposable = new CompositeDisposable(); } compositeDisposable.add(observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(observer)); } public void addFileDisposable(Observable<?> observable, FileObserver observer) { if (compositeDisposable == null) { compositeDisposable = new CompositeDisposable(); } compositeDisposable.add(observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(observer)); } public void removeDisposable() { if (compositeDisposable != null) { compositeDisposable.dispose(); } } }
6、 BaseObserver封装,数据等异常处理路由
封装理由:处理业务逻辑路由,与错误信息处理
注:在正常开发中,前后台会约定相关字段如code的值代表各情况,在此路由通道,另一种路由方案是重写Gson解析类,文章不做体现,demo中有相关代码
public abstract class BaseObserver<T> extends DisposableObserver<BaseModel<T>> { protected BaseView view; /** * 网络连接失败 无网 */ public static final int NETWORK_ERROR = 100000; /** * 解析数据失败 */ public static final int PARSE_ERROR = 1008; /** * 网络问题 */ public static final int BAD_NETWORK = 1007; /** * 连接错误 */ public static final int CONNECT_ERROR = 1006; /** * 连接超时 */ public static final int CONNECT_TIMEOUT = 1005; /** * 其他所有情况 */ public static final int NOT_TRUE_OVER = 1004; public BaseObserver(BaseView view) { this.view = view; } public BaseObserver() { } @Override protected void onStart() { if (view != null) { view.showLoading(); } } @Override public void onNext(BaseModel<T> o) { // T t = o.getData(); try { if (view != null) { view.hideLoading(); } if (o.getError_code() == BaseContent.basecode) { onSuccess(o); } else { if (view != null) { view.onErrorCode(o); } //非 true的所有情况 onException(PARSE_ERROR, o.getReason()); } } catch (Exception e) { e.printStackTrace(); onError(e.toString()); } } @Override public void onError(Throwable e) { if (view != null) { view.hideLoading(); } if (e instanceof HttpException) { // HTTP错误 onException(BAD_NETWORK, ""); } else if (e instanceof ConnectException || e instanceof UnknownHostException) { // 连接错误 onException(CONNECT_ERROR, ""); } else if (e instanceof InterruptedIOException) { // 连接超时 onException(CONNECT_TIMEOUT, ""); } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) { // 解析错误 onException(PARSE_ERROR, ""); e.printStackTrace(); } else { if (e != null) { onError(e.toString()); } else { onError("未知错误"); } } } private void onException(int unknownError, String message) { switch (unknownError) { case CONNECT_ERROR: onError("连接错误"); break; case CONNECT_TIMEOUT: onError("连接超时"); break; case BAD_NETWORK: onError("网络超时"); break; case PARSE_ERROR: onError("数据解析失败"); break; //非true的所有情况 case NOT_TRUE_OVER: onError(message); break; default: break; } } //消失写到这 有一定的延迟 对dialog显示有影响 @Override public void onComplete() { /* if (view != null) { view.hideLoading(); }*/ } public abstract void onSuccess(BaseModel<T> o); public abstract void onError(String msg); }
如上,相关框架已封装完毕,下面看下如何使用
8、 定义MainView,并继承BaseView
public interface MainView extends BaseView { void onTextSuccess(BaseModel<TextBean> o); }
9、 定义MainPresenter,并继承BasePresenter
public class MainPresenter extends BasePresenter<MainView> { public MainPresenter(MainView baseView) { super(baseView); } /** * 写法好多种 怎么顺手怎么来 */ public void getTextApi() { HashMap<String, String> params = new HashMap<>(); params.put("type", "junshi"); params.put("key", "2c1cb93f8c7430a754bc3ad62e0fac06"); addDisposable(apiServer.getText(params), new BaseObserver(baseView) { @Override public void onSuccess(BaseModel o) { baseView.onTextSuccess((BaseModel<TextBean>) o); } @Override public void onError(String msg) { if (baseView != null) { baseView.showError(msg); } } }); } }
10、 在Activity中进行网络请求,如下
public class MainActivity extends AppCompatActivity implements MainView { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MainPresenter presenter = new MainPresenter(this); //网络请求 presenter.getTextApi(); } @Override public void onTextSuccess(BaseModel<TextBean> o) { //我是网络请求成功后的结果 } @Override public void showLoading() { //网络开始请求时我会执行 } @Override public void hideLoading() { //网络请求完毕时我会执行 } @Override public void showError(String msg) { //异常情况下我会提示内容 } @Override public void onErrorCode(BaseModel model) { //在异常时候我会回调 } @Override public void showProgress() { //需要显示进度条时候我是开始标识 } @Override public void hideProgress() { //需要隐藏进度条时候我是结束标识 } @Override public void onProgress(int progress) { //进度条最主要的是我 } }
第一章结束(MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建及使用)
activity内容太多了,阅读性差,引发了强烈的需求对Activity封装与Fragment封装,请往下看
二、对BaseActivity、BaseFragment封装协调框架更好使用
有兴趣的撸友们可以转战我其他俩篇文章,本文意在一篇掌握网络请求,如下继续介绍如何封装
1、 BaseActivity相关内容进行封装,BaseFragment可到demo中查看
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView { protected final String TAG = this.getClass().getSimpleName(); public Context mContext; protected P mPresenter; protected abstract P createPresenter(); private LoadingDialog loadingDialog; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = this; setContentView(getLayoutId()); mPresenter = createPresenter(); setStatusBar(); this.initData(); } /** * 获取布局ID * * @return */ protected abstract int getLayoutId(); /** * 数据初始化操作 */ protected abstract void initData(); /** * 此处设置沉浸式地方 */ protected void setStatusBar() { StatusBarUtil.setTranslucentForImageViewInFragment(this, 0, null); } /** * 封装toast方法(自行定制实现) * * @param str */ public void showToast(String str) { ToastUtils.show(str); } public void showLongToast(String str) { ToastUtils.show(str); } @Override public void showError(String msg) { showToast(msg); } /** * 返回所有状态 除去指定的值 可设置所有(根据需求) * * @param model */ @Override public void onErrorCode(BaseModel model) { if (model.getError_code() == 10000000) { //处理些后续逻辑 如果某个页面不想实现 子类重写这个方法 将super去掉 自定义方法 // App.put(); // startActivity(LoginActivity.class); } } @Override public void showLoading() { showLoadingDialog(); } @Override public void hideLoading() { dissMissDialog(); } public void showLoadingDialog() { showLoadingDialog("加载中..."); } /** * 加载 黑框... */ public void showLoadingDialog(String msg) { if (loadingDialog == null) { loadingDialog = new LoadingDialog(this); } loadingDialog.setMessage(msg); if (!loadingDialog.isShowing()) { loadingDialog.show(); } } /** * 消失 黑框... */ public void dissMissDialog() { if (loadingDialog != null) { loadingDialog.dismiss(); } } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.detachView(); } if (loadingDialog != null) { loadingDialog.dismiss(); } if (progressDialog != null) { progressDialog.dismiss(); } if (mPresenter != null) { mPresenter.detachView(); } } /** * 进度条显示 */ @Override public void showProgress() { if (progressDialog == null) { progressDialog = new ProgressDialog(this); } progressDialog.getProgressBar().performAnimation(); if (!progressDialog.isShowing()) { progressDialog.show(); } } /** * 进度条隐藏 */ @Override public void hideProgress() { if (progressDialog != null) { progressDialog.getProgressBar().releaseAnimation(); progressDialog.dismiss(); } } /** * 进度条 回调 * @param progress */ @Override public void onProgress(int progress) { if (progressDialog != null) { progressDialog.updateProgress(progress); } } }
2、 定义MainView,并继承BaseView(同上)
3、 定义MainPresenter,并继承BasePresenter(同上)
4、 在Activity中进行网络请求,如下
public class MainActivity extends BaseActivity<MainPresenter> implements MainView { @Override protected MainPresenter createPresenter() { return new MainPresenter(this); } @Override protected int getLayoutId() { return R.layout.activity_main; } @Override protected void initData() { //网络请求 mPresenter.getTextApi(); } @Override public void onTextSuccess(BaseModel<TextBean> o) { //我是网络请求成功后的结果 } }
三、Android部分手机4G网第一次请求很慢(wifi正常)解决方案
Android部分手机4G网第一次请求很慢(wifi正常)解决方案
1、出现此类问题场景
经测试,一般手机都没有发现网络请求慢现象,只有部分手机会出现,如(小米手机)
2、出现此类问题现象
手机4G网网络请求特别慢,第一次进入app加载网络会出现30s+延迟现象,只有第一次慢,第二次网络访问回归正常,但重新进入又会出现网络延迟30s+
3、出现此类问题排查
通过网上查阅资料,都趋向于ipv4、ipv6地址问题,经对应手机测试发现,DNS 解析的 IP 地址①.连接到wifi,只解析到 ipv4 地址
,②.连接到4G网,解析到了ipv4、ipv6俩个地址
,但是ipv6默认为集合中的第一个,是否我们可以尝试修改集合第一个为ipv4呢?
4、出现此类问题解决方案
解决方案:集合中ipv4,ipv6调换位置,将ipv4当到集合首位
调换集合中ipv4 ipv6位置,将ipv4当到集合首位
import okhttp3.Dns; public class ApiDns implements Dns { @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { if (hostname == null) { throw new UnknownHostException("hostname == null"); } else { try { List<InetAddress> mInetAddressesList = new ArrayList<>(); InetAddress[] mInetAddresses = InetAddress.getAllByName(hostname); for (InetAddress address : mInetAddresses) { if (address instanceof Inet4Address) { mInetAddressesList.add(0, address); } else { mInetAddressesList.add(address); } } return mInetAddressesList; } catch (NullPointerException var4) { UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour"); unknownHostException.initCause(var4); throw unknownHostException; } } } }
第二步,将自定义方法插入到okhttp中
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(AppUMS.mContent)); httpClientBuilder .cookieJar(cookieJar) .addInterceptor(interceptor) .addInterceptor(new HeadUrlInterceptor()) //设置请求超时时长 .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .dns(new ApiDns());//添加如下方法
四、Retrofit运行时动态改变BaseUrl解决方案
1、出现此类问题场景
Android
正式项目中可能会涉及到多个BaseUrl
,使用Retrofit开发者可能会遇到多BaseUrl
不是很好处理情况
2、第一种解决方案
简单粗暴解决方案,利用Retrofit
请求优先级,因为Retrofit
支持全路径,比如
@GET("http://www.baidu.com") Observable<Object> getApi(@Path("param") String param);
3、第二种解决方案
Retrofit
默认只能设置一个BaseUrl
,没有提供其Api
去修改,所以我们只能通过其他方案去实现,网上也有很多介绍的,但尝试用了下感觉很不理想,于是自己稍加封装了下,思路其实简单。
思路:一个Retrofit
只能设置一个BaseUrl
,这样我们可以创建多个Retrofit
不就可以了吗?但如果一个请求创建一个Retrofit
必然是不理想的,所以我们可以有几个BaseUrl
创建几个,有人会说这样不会造成内存的开销吗?答案是不会的,一个项目中也不会出现N
多个BaseUrl
,所以这点开销不用过于纠结
代码实现:在代码设计时可以尽可能去优化,所以当我们用到此BaseUrl
时,再去创建,用不到不创建,这样便会出现个问题,怎样知道我应该使用哪个Retrofit
和Retrofit
怎么去保存等问题,本人思路是创建成功便添加到集合缓存下载,使用的时候去比对集合中BaseUrl
和当前是否匹配,如果一致从集合中获取,如果不一致去创建新的,如果使用没有传入BaseUrl
便用默认的,最基本的判断,实现代码如下
4、正常创建Retrofit
public class ApiRetrofit { private static ApiRetrofit mApiRetrofit; private Retrofit retrofit; private ApiServer apiServer; public static String mBaseUrl = BaseContent.baseUrl; public ApiRetrofit() { OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); httpClientBuilder .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .retryOnConnectionFailure(true);//错误重联 retrofit = new Retrofit.Builder() .baseUrl(mBaseUrl ) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(httpClientBuilder.build()) .build(); apiServer = retrofit.create(ApiServer.class); } public static ApiRetrofit getInstance() { if (mApiRetrofit == null) { synchronized (Object.class) { if (mApiRetrofit == null) { mApiRetrofit = new ApiRetrofit(); } } } return mApiRetrofit; } }
5、对创建Retrofit稍加封装,已适应我们的需求
新建保存对象的集合
private static List<Retrofit> mRetrofitList = new ArrayList<>(); private static List<ApiRetrofit> mApiRetrofitList = new ArrayList<>();
修改创建时候的逻辑,如果请求接口时传入BaseUrl
,检测BaseUrl
是否为空,如果为空使用默认接口,如果不为空,再从缓存的Retrofit
中查找是否已经才创建过了,如果创建了用缓存的,如果没有创建则创建
注:这块可以用正则检查下传入的url
是否为正规的域名,再做下判断
//创建Retrofit代码中加入 apiServer = retrofit.create(ApiServer.class); mRetrofitList.add(retrofit); public static ApiRetrofit getInstance() { mBaseUrl = BaseContent.baseUrl; int mIndex = -1; for (int i = 0; i < mRetrofitList.size(); i++) { if (BaseContent.baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) { mIndex = i; break; } } //新的baseUrl if (mIndex == -1) { synchronized (Object.class) { mApiRetrofit = new ApiRetrofit(); mApiRetrofitList.add(mApiRetrofit); return mApiRetrofit; } } else { //以前已经创建过的baseUrl return mApiRetrofitList.get(mIndex); } } public static ApiRetrofit getInstance(String baseUrl) { if (!TextUtils.isEmpty(baseUrl)) { mBaseUrl = baseUrl; } else { mBaseUrl = BaseContent.baseUrl; } int mIndex = -1; for (int i = 0; i < mRetrofitList.size(); i++) { if (baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) { mIndex = i; break; } } //新的baseUrl if (mIndex == -1) { synchronized (Object.class) { mApiRetrofit = new ApiRetrofit(); mApiRetrofitList.add(mApiRetrofit); return mApiRetrofit; } } else { //以前已经创建过的baseUrl return mApiRetrofitList.get(mIndex); } }
6、使用时写法
地址可以写成常量,不要我这样写,写成常量判断准确
ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)
五、Retrofit文件上传(含进度条)
文件上传已封装到框架中,目的是让写法更简便
1、 FileObserver封装,文件上传下载时所用
上述文章有BaseObserver,现封装FileObserver单独用来文件上传下载时候所用,内容大同小异
public abstract class FileObserver<T> extends DisposableObserver<T> { protected BaseView view; /** * 网络连接失败 无网 */ public static final int NETWORK_ERROR = 100000; /** * 解析数据失败 */ public static final int PARSE_ERROR = 1008; /** * 网络问题 */ public static final int BAD_NETWORK = 1007; /** * 连接错误 */ public static final int CONNECT_ERROR = 1006; /** * 连接超时 */ public static final int CONNECT_TIMEOUT = 1005; /** * 其他所有情况 */ public static final int NOT_TRUE_OVER = 1004; public FileObserver(BaseView view) { this.view = view; } @Override protected void onStart() { if (view != null) { view.showProgress(); } } @Override public void onNext(T t) { onSuccess(t); } @Override public void onError(Throwable e) { if (view != null) { view.hideProgress(); } if (view != null) { view.hideLoading(); } if (e instanceof HttpException) { // HTTP错误 onException(BAD_NETWORK, ""); } else if (e instanceof ConnectException || e instanceof UnknownHostException) { // 连接错误 onException(CONNECT_ERROR, ""); } else if (e instanceof InterruptedIOException) { // 连接超时 onException(CONNECT_TIMEOUT, ""); } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) { // 解析错误 onException(PARSE_ERROR, ""); e.printStackTrace(); } else { if (e != null) { onError(e.toString()); } else { onError("未知错误"); } } } private void onException(int unknownError, String message) { switch (unknownError) { case CONNECT_ERROR: onError("连接错误"); break; case CONNECT_TIMEOUT: onError("连接超时"); break; case BAD_NETWORK: onError("网络超时"); break; case PARSE_ERROR: onError("数据解析失败"); break; //非true的所有情况 case NOT_TRUE_OVER: onError(message); break; default: break; } } @Override public void onComplete() { if (view != null) { view.hideProgress(); } } public abstract void onSuccess(T o); public abstract void onError(String msg); }
2、定义接口
public interface ApiServer { @Multipart @POST("/wxapp/public/upload") Observable<BaseModel<Object>> getUpload(@PartMap Map<String, RequestBody> map, @Part MultipartBody.Part parts ); }
3、 定义MainView,并继承BaseView
public interface MainView extends BaseView { void onUpLoadImgSuccess(BaseModel<Object> o); }
4、 定义ProgressRequestBody,监听上传进度
public class ProgressRequestBody extends RequestBody { private File mFile; private String mPath; private String mMediaType; private BaseView mListener; private int mEachBufferSize = 1024; public ProgressRequestBody(final File file, String mediaType, BaseView baseView) { mFile = file; mMediaType = mediaType; mListener = baseView; } public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, BaseView baseView) { mFile = file; mMediaType = mediaType; mEachBufferSize = eachBufferSize; mListener = baseView; } @Override public MediaType contentType() { // i want to upload only images return MediaType.parse(mMediaType); } @Override public void writeTo(BufferedSink sink) throws IOException { long fileLength = mFile.length(); byte[] buffer = new byte[mEachBufferSize]; FileInputStream in = new FileInputStream(mFile); long uploaded = 0; try { int read; Handler handler = new Handler(Looper.getMainLooper()); while ((read = in.read(buffer)) != -1) { // update progress on UI thread handler.post(new ProgressUpdater(uploaded, fileLength)); uploaded += read; sink.write(buffer, 0, read); } } finally { in.close(); } } private class ProgressUpdater implements Runnable { private long mUploaded; private long mTotal; public ProgressUpdater(long uploaded, long total) { mUploaded = uploaded; mTotal = total; } @Override public void run() { mListener.onProgress((int) (100 * mUploaded / mTotal)); } } }