1.遇到不会解决的Bug啦
上篇博客中说想自己实现一下多线程下载一个文件,昨天就尝试写了一下,但遇到了一个不会解决的Bug,问了问别人,暂时也没能解决。这里记录一下,挖个坑,以后再来解决了。
2.Java中的线程池
newCachedThreadPool
可缓存的线程池。没有固定大小,如果线程池中的线程数量超过任务执行的数量,会回收60秒不执行的任务的空闲线程。当任务数量增加时,线程池自己会增加线程来执行任务。而能创建多少,就得看jvm能够创建多少newFixedThreadPool
固定线程数量大小的线程池,并发线程数量不会超过固定大小,超出的线程会在队列中等待。如果一个正在执行的线程出现异常结束,会创建一个显得线程来代替它newScheduledThreadPool
也是固定线程数量大小的线程池,可以延迟或者定时周期执行任务newSingleThreadExecutor
单例线程池。线程池中只有一个线程工作,出现异常会有有个新的线程来代替。线程池会保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
3.Android部分代码
思路:
将一个文件进行分部下载,利用RandomAccessFile来进行读写。一个线程负责一部分,计算好每个线程开始和结束下载位置。
public class HttpUtils {
/**
* 固定线程数的线程池
*/
private static ExecutorService fixedThreadPool;
private static String downUrl;
private static File targetFile;
private static ProgressCallback progressCallback;
private static long totalLength = 0;
public HttpUtils(String url) {
this.downUrl = url;
fixedThreadPool = Executors.newFixedThreadPool(5);
}
public static HttpUtils getInstance(String url) {
return new HttpUtils(url);
}
public HttpUtils into(File file) {
if (file != null) {
this.targetFile = file;
}
return this;
}
public static void downLoad(ProgressCallback progressCallback) {
if (fixedThreadPool == null) return;
HttpUtils.progressCallback = progressCallback;
new Thread(new Runnable() {
@Override
public void run() {
down();
}
}).start();
}
private static void down() {
URL url = null;
URLConnection urlConnection = null;
try {
url = new URL(downUrl);
urlConnection = url.openConnection();
totalLength = urlConnection.getContentLength();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//每部分的大小
final long part = totalLength / 5;
for (int i = 0; i < 5; i++) {
final long startPosition = i * part;
final URL finalUrl = url;
final PartFileDown partFileDown = new PartFileDown();
final int n = i;
final long finalTotalLength = totalLength;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
long endLength = n == 4 ? finalTotalLength : (startPosition + part);
partFileDown.downData(finalUrl, targetFile, startPosition, endLength, new Callback() {
@Override
public void currentLength(long currentLength) {
Message message = handler.obtainMessage();
message.what = 101;
message.obj = currentLength;
handler.sendMessage(message);
}
});
}
});
}
}
private static Handler handler = new Handler() {
long current;
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 101) {
current += (long) msg.obj;
progressCallback.progress(current, totalLength);
Log.e("current", "--" + current);
}
}
};
}
利用newFixedThreadPool开启5个线程来执行任务。
public class PartFileDown {
public void downData(URL downLoadUrl, File targetFile, long startPosition, long endPosition, Callback callback) {
RandomAccessFile raf = null;
InputStream is = null;
try {
URLConnection urlConnection = downLoadUrl.openConnection();
urlConnection.setAllowUserInteraction(true);
//设置当前线程下载的起点、终点
urlConnection.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);
// byte[] by = new byte[1024 * 8];
byte[] by = new byte[1024];
is = urlConnection.getInputStream();
raf = new RandomAccessFile(targetFile, "rw");
int len = 0;
raf.seek(startPosition);
while ((len = is.read(by)) != -1) {
raf.write(by, 0, len);
callback.currentLength(len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null )
is.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
is = null;
}
try {
if (raf != null)
raf.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
raf = null;
}
}
}
}
代码有Bug就不再解释了。: )
4.bug说明
遇到的Bug就是,除了采用newSingleThreadExecutor这个线程池外,其他3个线程池,都会遇到一个同样的Bug。文件在下载过程中,进度会在4/5左右中断,每次中断位置还基本不同。放置1,2分钟后,又会继续下载,直到完成。
推测是因为线程在下载过程中出现了异常然后结束,线程池创建新的线程来代替挂掉的线程需要时间。
5.OkHttp下载
OkHttp下载文件倒是很方便。直接上代码:
public class MainModel implements MainContract.MainBiz {
private Platform platform;
private Call call = null;
public MainModel() {
platform = Platform.get();
}
@Override
public void onStart(String url, String fileName, onStartDownListener onStartDownListener) {
downLoad(url, fileName, onStartDownListener);
}
@Override
public void onStop(String fileName, onStartDownListener onStartDownListener) {
if (call != null) {
if (!call.isCanceled()) {
call.cancel();
}
File f = FileUtils.getTargetFile(fileName);
if (f != null && f.exists()) {
f.delete();
sendLoadProgressCallback(onStartDownListener,0,100);
sendInfoCallback(onStartDownListener,"停止下载");
}
} else {
sendLoadProgressCallback(onStartDownListener,0,100);
sendInfoCallback(onStartDownListener,"call出现错误");
}
}
private void downLoad(String url, final String fileName, final onStartDownListener onStartDownListener) {
final OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
sendInfoCallback(onStartDownListener, e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
sendInfoCallback(onStartDownListener, "已经开始下载");
InputStream is = response.body().byteStream();
long total = response.body().contentLength();
int current = 0;
byte[] by = new byte[1024 * 8];
FileOutputStream fos = new FileOutputStream(FileUtils.getTargetFile(fileName));
int len;
while ((len = is.read(by)) != -1) {
fos.write(by, 0, len);
current += len;
sendLoadProgressCallback(onStartDownListener, current, total);
}
is.close();
fos.close();
sendInfoCallback(onStartDownListener, "下载完成");
}
});
}
private void sendLoadProgressCallback(final onStartDownListener onStartDownListener, final int current, final long total) {
if (onStartDownListener == null) return;
platform.execute(new Runnable() {
@Override
public void run() {
onStartDownListener.onLoading(current, (int) total);
}
});
}
private void sendInfoCallback(final onStartDownListener onStartDownListener, final String message) {
if (onStartDownListener == null) return;
platform.execute(new Runnable() {
@Override
public void run() {
onStartDownListener.onFailed(message);
}
});
}
}
Platform是在张鸿洋大神封装的OkHttpUtils库中看的一个工具类,方便拿到Handler。直接回调就可以。
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package utils;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Platform
{
private static final Platform PLATFORM = findPlatform();
public static Platform get()
{
L.e(PLATFORM.getClass().toString());
return PLATFORM;
}
private static Platform findPlatform()
{
try
{
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0)
{
return new Android();
}
} catch (ClassNotFoundException ignored)
{
}
return new Platform();
}
public Executor defaultCallbackExecutor()
{
return Executors.newCachedThreadPool();
}
public void execute(Runnable runnable)
{
defaultCallbackExecutor().execute(runnable);
}
static class Android extends Platform
{
@Override
public Executor defaultCallbackExecutor()
{
return new MainThreadExecutor();
}
static class MainThreadExecutor implements Executor
{
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r)
{
handler.post(r);
}
}
}
}
6.最后
多线程的学习暂时先告以段落。Android中,下载大文件,感觉还是OkHttp方便。OkHttp配合Handler也可以比较方便地进行更新进度