您的位置:首页 > 理论基础 > 计算机网络

Android 之 OkHttp + EventBus 进行后台下载网络文件

2018-03-25 15:27 375 查看

前言:

        本篇文章纯粹是个人学习日记。如有错误或不正确的地方,请指出,谢谢!

        我在学习郭霖的《第一行代码》,就想找找有没有比较简便的方法替代第十章的最佳实践网络下载去下载网络文件。然后就想用EventBus代替其中的 AsyncTask 和 Interface回调接口。
1.EventBus可以非常方便的发送和订阅事件。
2.EventBus自带线程池,利用自身的线程调度机制,随意转换子线程和主线程。

效果图:



上图是下载中的一些操作和回显



上图是完成时的一些回显
自己画了一张大概的"大局流程图",方便大家更容易的理解。



有专家研究过,图像记忆是最好的记忆方法!

导入:

我用的是Android Studio3.0 在app的build.gradle里添加implementation 'org.greenrobot:eventbus:3.1.1'                 //EventBus导包
implementation 'com.squareup.okhttp3:okhttp:3.10.0'            //OkHttp导包
implementation 'com.daimajia.numberprogressbar:library:1.4@aar'//好看的ProgressBar

EventBus部分代码:

1.自定义MessageEvent实体类
public class MessageEvent {
private int tag;            //标志位,方便EventBus辨识
private String url;         //下载地址,我也利用这个传入一些操作信息,所以名字不标准
private int progress;       //反馈过程的进度

public MessageEvent(int tag,String url,int progress) {
super();
this.tag = tag;
this.url = url;
this.progress = progress;
}

public int getProgress() {
return progress;
}

public int getTag() {
return tag;
}

public String getUrl() {
return url;
}
}

2.子线程的下载流程(敲黑板)放在Service类里面的static final int START_DOWNLOAD = 1; //标志位:开始下载
static final int FAILED__DOWNLOAD = 2; //标志位:下载失败
static final int SUCCESS_DOWNLOAD = 3; //标志位:下载完成
static final int CANCEL_DOWNLOAD = 4; //标志位:取消下载
static final int PAUSE_DOWNLOAD = 5; //标志位:暂停下载
static final int UPDATE_PROGRESS = 6; //标志位:更新进度

private boolean isStarted = false; //操作子线程的标志状态:是否运行
private boolean isCanceled = false; //操作子线程的标志状态:是否取消
private boolean isPaused = false; //操作子线程的标志状态:是否暂停

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onSubThread(MessageEvent event) {
switch (event.getTag()) {
case START_DOWNLOAD:
//最开始的启动前台服务消息
startForeground(1,getNotification("正在获取资源...",-1));

InputStream is = null;
RandomAccessFile saveFile = null;
File file = null;
try {
//已经下载的长度
long downloadLength = 0;

//获得传入的下载地址
String downloadUrl = event.getUrl();

//根据地址截取文件名
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));

//文件名中不能包含特殊字符
fileName = fileName.replace("?", "");
fileName = fileName.replace("\\", "");
fileName = fileName.replace(":", "");
fileName = fileName.replace("*", "");

            //获取SD卡下载目录的路径
String directory = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS).getPath();

//根据文件名和下载目录生成文件
file = new File(directory + fileName);

//如果文件存在,获取已下载的长度,等等实现断点下载
if (file.exists()) {
downloadLength = file.length();
}

//获取文件总长度
long contentLength = getContentLength(downloadUrl);

if (contentLength == 0) {
//获取不了将要下载的文件的总长度
//发出信息,下载失败
EventBus.getDefault().post(new MessageEvent(FAILED__DOWNLOAD,
"下载地址有误!!", -1));
isStarted = false;
return;
} else if (contentLength == downloadLength) {
//总长度等于已下载的长度
//说明已经完整下载好了
//发出信息,无需下载,已经下载好了
EventBus.getDefault().post(new MessageEvent(SUCCESS_DOWNLOAD,
"已经下载完成!!", -1));
return;
}

//开始断点下载
OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.sslSocketFactory(SSLSocketClient.getSSLSocketFactory())//配置
.hostnameVerifier(SSLSocketClient.getHostnameVerifier())//配置 !!看文章底部!!
.build();

Request request = new Request.Builder()
//这个就是跳到断点的地方
.addHeader("RANGE", "bytes=" + downloadLength + "-")
.url(downloadUrl)
.build();

Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();
saveFile = new RandomAccessFile(file, "rw");
//跳到断点的地方
saveFile.seek(downloadLength);
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1) {

total += len;
saveFile.write(b, 0, len);

//计算已下载的百分比
int progress = (int) ((total + downloadLength) * 100 / contentLength);

//发出消息,更新UI界面进度
EventBus.getDefault().post(new MessageEvent(UPDATE_PROGRESS, "", progress));
getManager().notify(1, getNotification("正在下载...", progress));

if (isCanceled) {
//发出消息,取消下载
EventBus.getDefault().post(new MessageEvent(CANCEL_DOWNLOAD,
"取消下载!!", -1));
return;
}
if (isPaused) {
//发出消息,暂停下载
EventBus.getDefault().post(new MessageEvent(PAUSE_DOWNLOAD,
"暂停下载!!", progress));
return;
}
}
response.close();
//发出消息,下载成功
EventBus.getDefault().post(new MessageEvent(SUCCESS_DOWNLOAD, "", -1));
}
} catch (Exception e) {
EventBus.getDefault().post(new MessageEvent(FAILE
4000
D__DOWNLOAD,
e.getMessage(), -1));
e.printStackTrace();
}finally {
try {
if (is != null) {
is.close();
}
if (saveFile != null) {
saveFile.close();
}
if (isCanceled && file != null) {
isCanceled = false;
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
default:

    }
}其中有个获取下载文件大小的总长度方法private long getContentLength(String downloadUrl) throws Exception {
OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.sslSocketFactory(SSLSocketClient.getSSLSocketFactory())//配置
.hostnameVerifier(SSLSocketClient.getHostnameVerifier())//配置   !!看文章底部!!
.build();

Request request = new Request.Builder()
.url(downloadUrl)
.build();

Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
还有获取NotificationManager 和 发送前台服务的方法private NotificationManager getManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}

private Notification getNotification(String content,int progress){
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification.Builder builder = new Notification.Builder(this);

builder.setContentTitle("Day3_22");                    //这里我改成自己的app名字
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
builder.setContentIntent(pi);
builder.setContentText(content);
if (progress >= 0) {
builder.setContentText(progress + " %");
builder.setProgress(100, progress, false);
}

return builder.build();
}

3.主线程的回显操作
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMainThread(MessageEvent event) {
switch (event.getTag()) {
case FAILED__DOWNLOAD:
isStarted = false;
Toast.makeText(this,
event.getUrl(),Toast.LENGTH_SHORT).show();
stopForeground(true);
getManager().notify(1, getNotification("下载失败," + event.getUrl(), -1));
break;

case SUCCESS_DOWNLOAD:
isStarted = false;
Toast.makeText(this,
"下载完成!!",Toast.LENGTH_SHORT).show();
stopForeground(true);
getManager().notify(1, getNotification("下载完成!!", -1));
break;

case CANCEL_DOWNLOAD:
isStarted = false;
Toast.makeText(this,
"取消下载并删除残留文件!!",Toast.LENGTH_SHORT).show();
stopForeground(true);
getManager().notify(1, getNotification("取消下载并删除残留文件!!", -1));
break;

case PAUSE_DOWNLOAD:
isStarted = false;
isPaused = false;
Toast.makeText(this,
"暂停下载!!",Toast.LENGTH_SHORT).show();
getManager().notify(1, getNotification("暂停下载!!", -1));
break;
    }
}


Service部分代码:

因为要保证子线程在后台持续运行,当然要后台会维持。
1.因为要用到EventBus,所以生命周期里要注册和注销
public class DownloadService extends Service {
    @Override
public void onCreate() {
super.onCreate();
EventBus.getDefault().register(this);
}

@Override
public void onDestroy() {
super.onDestroy();
mBinder.cancelDownload();                //强制关闭APP,防止子线程还在下载
EventBus.getDefault().unregister(this);
}
}

2.自定义的Binder
public class DownloadService extends Service {
    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder{
void startDownload(String downloadUrl){
isStarted = true;
EventBus.getDefault().post(new MessageEvent(START_DOWNLOAD, downloadUrl, 0));
}

void pauseDownload(){
isStarted = false;
isPaused = true;
}

void cancelDownload(){
isStarted = false;
isCanceled = true;
}

boolean isStarted(){
return isStarted;
}
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}


MainActivity的部分代码:

1.因为也需要EventBus订阅事件改变progressBar    所以生命周期方法里要注册和注销,至于Service绑定或onDestory的解绑我就不写了public class Main2Activity extends AppCompatActivity {
    private NumberProgressBar npb;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);

npb = findViewById(R.id.numberProgressBar);
npb.setProgressTextSize(33f);
npb.setReachedBarHeight(5f);
npb.setUnreachedBarHeight(5f);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMainThread(MessageEvent event) {
switch (event.getTag()) {
case UPDATE_PROGRESS:
npb.setProgress(event.getProgress());
break;

case CANCEL_DOWNLOAD:
npb.setProgress(0);
break;

default:

}
}

@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}

@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}

}

2.各按钮的对应方法至于Service绑定或onDestory的解绑我就不写了
public void startDownload(View view) {
if (mBinder.isStarted()) {
Toast.makeText(this, "正在下载,请勿重复点击。",
Toast.LENGTH_SHORT).show();
} else {
//空地址测试链接
//            String url = "";

//哔哩哔哩客户端
//            String url = "https://dl.hdslb.com/mobile/latest/" +
//                    "iBiliPlayer-bili.apk?spm_id_from=333.47.download-link.1";

//taptap客户端
String url = "https://c.tapimg.com/pub2/201803/" +
"82fef2820826b8b0738ef95f463f1403.apk?_upd=com.taptap_1.9.11.apk";

mBinder.startDownload(url);
}
}

public void pauseDownload(View view) {
if (mBinder.isStarted()) {
mBinder.pauseDownload();
} else {
Toast.makeText(this, "当前没有下载任务!!",
Toast.LENGTH_SHORT).show();
}
}

public void cancelDownload(View view) {
if (mBinder.isStarted()) {
mBinder.cancelDownload();
} else {
Toast.makeText(this, "当前没有下载任务!!",
Toast.LENGTH_SHORT).show();
}
}

实现过程中遇到的坑:

1.从代码中看到  !!看文章底部!!,来这里集合    为什么要加 //配置 那两条代码,因为下载时会出现 没法认证https证书问题!!

解决办法:okhttp3.0忽略https证书
2.创建文件时,文件名是截取下载地址"/"后面的一部分    但是就是这一部分都有些特殊符号例如:? 就是不能作为文件名一部分

解决办法:我在代码中去掉截取文件名的特殊符号。
    但是这不是最好的办法!我很好奇浏览器怎么获取到正确规范的文件名。求有心人指点!先谢!

3.存储空间满了!下载报错!解决办法:这个只能弹出消息提示 下载错误:存储空间已满!

Thanks:

    以上部分代码来自
        郭霖大神的《第一行代码》

        Anonymous-OS的 okhttp3.0忽略https证书
        代码家daimajia的 精美ProgressBar
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android OkHttp EventBus