1.遇到不會(huì)解決的Bug啦
上篇博客中說想自己實(shí)現(xiàn)一下多線程下載一個(gè)文件,昨天就嘗試寫了一下,但遇到了一個(gè)不會(huì)解決的Bug,問了問別人,暫時(shí)也沒能解決。這里記錄一下,挖個(gè)坑,以后再來解決了。
2.Java中的線程池
newCachedThreadPool
可緩存的線程池。沒有固定大小,如果線程池中的線程數(shù)量超過任務(wù)執(zhí)行的數(shù)量,會(huì)回收60秒不執(zhí)行的任務(wù)的空閑線程。當(dāng)任務(wù)數(shù)量增加時(shí),線程池自己會(huì)增加線程來執(zhí)行任務(wù)。而能創(chuàng)建多少,就得看jvm能夠創(chuàng)建多少newFixedThreadPool
固定線程數(shù)量大小的線程池,并發(fā)線程數(shù)量不會(huì)超過固定大小,超出的線程會(huì)在隊(duì)列中等待。如果一個(gè)正在執(zhí)行的線程出現(xiàn)異常結(jié)束,會(huì)創(chuàng)建一個(gè)顯得線程來代替它newScheduledThreadPool
也是固定線程數(shù)量大小的線程池,可以延遲或者定時(shí)周期執(zhí)行任務(wù)newSingleThreadExecutor
單例線程池。線程池中只有一個(gè)線程工作,出現(xiàn)異常會(huì)有有個(gè)新的線程來代替。線程池會(huì)保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。
3.Android部分代碼
思路:
將一個(gè)文件進(jìn)行分部下載,利用RandomAccessFile來進(jìn)行讀寫。一個(gè)線程負(fù)責(zé)一部分,計(jì)算好每個(gè)線程開始和結(jié)束下載位置。
public class HttpUtils {
/**
* 固定線程數(shù)的線程池
*/
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個(gè)線程來執(zhí)行任務(wù)。
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);
//設(shè)置當(dāng)前線程下載的起點(diǎn)、終點(diǎn)
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這個(gè)線程池外,其他3個(gè)線程池,都會(huì)遇到一個(gè)同樣的Bug。文件在下載過程中,進(jìn)度會(huì)在4/5左右中斷,每次中斷位置還基本不同。放置1,2分鐘后,又會(huì)繼續(xù)下載,直到完成。
推測(cè)是因?yàn)榫€程在下載過程中出現(xiàn)了異常然后結(jié)束,線程池創(chuàng)建新的線程來代替掛掉的線程需要時(shí)間。
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出現(xiàn)錯(cuò)誤");
}
}
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, "已經(jīng)開始下載");
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庫(kù)中看的一個(gè)工具類,方便拿到Handler。直接回調(diào)就可以。
/*
* 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.最后
多線程的學(xué)習(xí)暫時(shí)先告以段落。Android中,下載大文件,感覺還是OkHttp方便。OkHttp配合Handler也可以比較方便地進(jìn)行更新進(jìn)度