java 多线程 从无到有_多线程断点续传(简单demo)——从无到有

复杂功能总是由许多小功能组合在一起完成的,一步一步完成多线程断点续传,可以从以下几个方面来考虑。

第一,实现简单的下载;

第二,打断下载线程,实现暂停功能;

第三,从已经下载点进行续传;

第四,引入多线程。

截图:

878415244b33

demo截图.png

简单的下载

下载代码

InputStream is = null;

OutputStream os = null;

try {

HttpURLConnection urlConnection = createConnection();

is = urlConnection.getInputStream();

// 获取输出流,注意检查文件夹和文件是否存在

os = new FileOutputStream(

createFile(FileUtil.getExternalCacheDir(),fileName));

// 获取文件大小,用于百分比的计算

int contentSize = urlConnection.getContentLength();

byte[] buffer = new byte[BUFFER_SIZE];

int length;

while ((length = is.read(buffer)) != -1){

os.write(buffer,0,length);

currentLength += length;

os.flush();

Message message = Message.obtain();

// 这个百分比的计算方式有问题,待会儿讲。

message.arg1 = currentLength * 100 / contentSize;

handler.sendMessage(message);

}

} catch (IOException e) {

e.printStackTrace();

} finally {

IOCloseUtil.inputClose(is);

IOCloseUtil.outputClose(os);

}

注意点

相信以上的代码大家早已烂熟于心了。不过还是有几个注意点:

第一点:创建下载文件夹时

private File createFile(String fileDir, String fileName){

File dir = new File(fileDir);

// 注意要先检查文件夹是否存在并且创建

if (!dir.exists())

dir.mkdirs();

// 然后检查文件是否存在

File file = new File(dir,fileName);

if (!file.exists()){

try {

file.createNewFile();

} catch (IOException e) {

Log.e("zp_test","文件创建失败!");

}

}

return file;

}

第二点:

message.arg1 = currentLength * 100 / contentSize;

currentLength为当前下载的大小,如果文件较大,比如超过int的最大值个字节,也就是超过20.48M。那么这种计算方式就会导致错误。具体做法待会儿下面会讲到。

线程打断

打断代码

while (!Thread.interrupted()){

byte[] buffer = new byte[BUFFER_SIZE];

int length;

if ((length = mStream.read(buffer)) != -1){

mAccessFile.write(buffer,0,length);

currentLength += length;

Message message = Message.obtain();

message.arg1 = (int) (currentLength / totalLength * 100);

message.obj = progressBar;

handler.sendMessage(message);

Log.d("zp_test","rate: " + message.arg1);

if (message.arg1 == 100) {

DatabaseManager.getInstance().updateStart(url,currentLength);

break;

}

}

}

我所使用的打断代码,没错,就是!Thread.interrupted(),这个用线程的打断方法就可以打断,而我用的是线程池返回的future对象的cancel()方法进行打断。注意,打断标记在Thread.interrupted()后会迅速置回原值。这样写还有一个好处,就是不会cancel()方法不会被read()方法导致的IO阻塞给截住,而导致不会退出while循环。

另外,message.arg1 = (int) (currentLength / totalLength * 100);把totalLength 变量变为float类型,这样相除后就变成了带小数点的float类型,就不会出现上面int型溢出的问题。

断点续传

断点续传代码

HttpURLConnection urlConnection = createConnection();

File file = new File(FileUtil.getExternalCacheDir(),fileName);

// 判断下载文件是否存在

if (file != null && file.length() > 0) {

// 判断url是否存在本地数据库中

DownloadInfo info = DatabaseManager.getInstance().isExistUrl(url);

if (info != null) {

totalLength = info.getContentSize();

Log.d("zp_test","start: " + info.getStart() + " end: " + info.getContentSize());

urlConnection.setRequestProperty("Range","bytes=" +

info.getStart() + "-" + info.getContentSize());

// 设置range后,content length的值会发生变化,变成没有下载的内容长度

// setRequestProperty这个方法必须在连接发生前进行调用

// if (info.getContentSize() == urlConnection.getContentLength()){

mAccessFile = new RandomAccessFile(file,"rwd");

// 下载文件类移动到指定的指针位置。

mAccessFile.seek(info.getStart());

currentLength = info.getStart();

// 文件已经下载完毕,不需要重新下载

if (info.getContentSize() == currentLength)

return;

} else {

Log.w("zp_test",LOG_TAG + "info is null......");

}

}

注意点

在这里有个问题困恼我了一会儿,最开始我在注掉的代码if (info.getContentSize() == urlConnection.getContentLength())这句后,进行的urlConnection.setRequestProperty操作,结果,代码运行到这句set操作后,直接卡死在这里,也没有报出任何错误。

最后想起,像urlConnection.getContentLength() urlConnection.getInputStream();等等这类操作,会导致流通道建立连接,开始进行数据的交互。这以后是不能进行进行urlConnection.setRequestProperty这类型操作的。

所以重点注意:** setRequestProperty这个方法必须在连接发生前进行调用 **

引入多线程

public class PegasusExecutors extends ThreadPoolExecutor {

private static final int DEFAULT_THREAD_COUNT = 4;

// 线程池中4条线程,考虑到有可能的复用,每条线程在下载后,还会

// 保留10s钟

public PegasusExecutors() {

super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT,

10, TimeUnit.SECONDS, new PriorityBlockingQueue());

}

@Override

public Future> submit(Runnable task) {

PegasusFutureTask futureTask = new PegasusFutureTask((DownloadTaskRunnable) task);

execute(futureTask);

return futureTask;

}

}

其实这里还可以参考picasso的源码进行线程池的编写。不同的网络环境不同的线程的条数。

最后一个问题

当引入listview的时候,最开始想到的更新listview中进度条progressbar的方式是线程中进行本地数据库的更新,然后再在handler处理消息方法中获取本地数据库数据,赋值给listview,刷新适配器。但是这样做有个问题:

刷新适配器在下载中是不断进行,这样会导致停止按钮不断刷新而不能点击。

最后想到的解决方案是给每个任务在构造时传入一个progressbar对象,然后在handler中进行处理更新进度条。

executors.submit(new DownloadTaskRunnable(url, new MyHandler(),viewHolder.pb)));(若各位有其他的方式方法欢迎一起讨论)数据库的操作也在demo中,如果需要可以下载demo。由于只是一个简单的例子,错误也在所难免,主要是为了多体会从零到一的感觉,让习惯了复制粘贴的我们多发现一些实现细节问题。

最后,由于本人水平有限,如有错误,欢迎指出。谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值