Skip to content

Instantly share code, notes, and snippets.

@wuyisheng
Last active March 4, 2019 03:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wuyisheng/2dd6207dda94a62b6eb6b2a7cef58746 to your computer and use it in GitHub Desktop.
Save wuyisheng/2dd6207dda94a62b6eb6b2a7cef58746 to your computer and use it in GitHub Desktop.
A HttpURLConnection download example for android
package org.yeshen.download;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/*********************************************************************
* This file is part of Download project
* Created by hello@yeshen.org on 2019/02/28.
* Copyright (c) 2019 yeshen.org . - All Rights Reserved
*********************************************************************/
@SuppressWarnings("WeakerAccess")
public class DownloadFactory {
private static final String TAG = "download";
private static final int BUFFER_SIZE = 4096;
public static Download create() {
// NOTICE:
// Each DownloadImpl holds an HandleThread
// Please call "destroy" method after download done
return new DownloadImpl();
}
public interface DownloadDelegate {
@UiThread
void onProgress(long progress, long total);
@WorkerThread
boolean onCheck(File file);
@UiThread
void onSuccess(File file);
@UiThread
void onFail(int downloadCode);
}
public interface Download {
@UiThread
void register(DownloadDelegate delegate);
@UiThread
void start(Request request);
@UiThread
void pause();
@UiThread
void resume();
@UiThread
void cancel();
@UiThread
void destroy();
}
public interface DownloadCode {
int USER_CANCEL = 1;
int NET_ERROR = 2;
int NO_SPACE = 3;
int CHECK_ERROR = 4;
int UNKNOWN_ERROR = 5;
int USER_PAUSE = 6;
}
public static final class Request {
private String mUrl;
private String mPath;
private String mName;
private volatile long mTotal;
private volatile long mPosition;
public volatile boolean mDownloading;
public volatile boolean mUserCancel;
public volatile boolean mUserPause;
public Request(String url, String path, String name) {
this.mUrl = url;
this.mPath = path;
this.mName = name;
reset();
}
public Request(Request copy) {
this.mUrl = copy.mUrl;
this.mPath = copy.mPath;
this.mName = copy.mName;
reset();
}
public void reset() {
this.mTotal = 0;
this.mPosition = 0;
this.mUserCancel = false;
this.mUserPause = false;
this.mDownloading = false;
}
}
public static final class DownloadImpl implements Download, Runnable {
@Nullable
private HandlerThread mHandlerThread;
@Nullable
private DownloadDelegate mDelegate;
@Nullable
private Handler mThreadHandler;
private Handler mMainHandler;
private Request mRequest;
public DownloadImpl() {
mMainHandler = new Handler(Looper.getMainLooper());
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper());
}
@Override
public void register(DownloadDelegate delegate) {
mDelegate = delegate;
}
@Override
public void start(Request request) {
if (request.mDownloading) {
throw new IllegalArgumentException("Invalid request,it's downloading");
}
if (this.mRequest != null && !request.equals(this.mRequest)) {
// cancel last task
this.mRequest.mUserCancel = true;
}
this.mRequest = request;
this.mRequest.reset();
if (mThreadHandler != null) mThreadHandler.post(this);
}
@Override
public void pause() {
if (this.mRequest != null) this.mRequest.mUserPause = true;
}
@Override
public void resume() {
if (this.mRequest == null) {
Log.e(TAG, "Nothing to resume,skip this request!");
return;
}
if (this.mRequest.mUserPause) {
this.mRequest.mUserPause = false;
if (mThreadHandler != null) mThreadHandler.post(this);
} else {
start(new Request(this.mRequest));
}
}
@Override
public void cancel() {
if (this.mRequest != null) this.mRequest.mUserCancel = true;
}
@Override
public void destroy() {
if (mHandlerThread != null) mHandlerThread.quit();
mThreadHandler = null;
mHandlerThread = null;
}
@WorkerThread
@Override
public void run() {
Request localRequest = this.mRequest;
localRequest.mDownloading = true;
File path = new File(localRequest.mPath);
if (!path.exists()) {
if (!path.mkdirs()) {
onFail(DownloadCode.NO_SPACE);
return;
}
}
String saveFilePath = localRequest.mPath + File.separator + localRequest.mName;
File file = new File(saveFilePath);
// TODO check space
HttpURLConnection httpConn = null;
InputStream inputStream = null;
RandomAccessFile randomAccessFile = null;
long contentLength;
int readingCounts = 0;
try {
URL url = new URL(localRequest.mUrl);
httpConn = (HttpURLConnection) url.openConnection();
if (localRequest.mPosition != 0) {
// seek to last download position,create PARTIAL download
httpConn.setRequestProperty("Range", "bytes=" + localRequest.mPosition + "-");
}
httpConn.setRequestProperty("Connection", "Keep-Alive");
int responseCode = httpConn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK
&& responseCode != HttpURLConnection.HTTP_PARTIAL) {
localRequest.mDownloading = false;
onFail(DownloadCode.NET_ERROR);
} else {
String disposition = httpConn.getHeaderField("Content-Disposition");
String contentType = httpConn.getContentType();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
contentLength = httpConn.getContentLengthLong();
} else {
contentLength = httpConn.getContentLength();
}
if (responseCode == HttpURLConnection.HTTP_OK) {
// full download
localRequest.mPosition = 0;
localRequest.mTotal = contentLength;
}
Log.d(TAG, disposition + contentType);
inputStream = httpConn.getInputStream();
int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE];
randomAccessFile = new RandomAccessFile(saveFilePath, "rw");
randomAccessFile.seek(localRequest.mPosition);
while ((bytesRead = inputStream.read(buffer)) != -1) {
readingCounts++;
randomAccessFile.write(buffer, 0, bytesRead);
localRequest.mPosition += bytesRead;
if (readingCounts % 64 == 0) {
if (localRequest.mUserCancel) {
localRequest.mDownloading = false;
onFail(DownloadCode.USER_CANCEL);
return;
} else if (localRequest.mUserPause) {
localRequest.mDownloading = false;
onFail(DownloadCode.USER_PAUSE);
return;
}
if (readingCounts % 16 == 0)
onProgress(localRequest.mPosition, localRequest.mTotal);
}
}
randomAccessFile.getFD().sync();
if (!onCheck(file)) {
localRequest.mDownloading = false;
onFail(DownloadCode.CHECK_ERROR);
} else {
localRequest.mDownloading = false;
onSuccess(file);
}
}
} catch (IOException e) {
e.printStackTrace();
localRequest.mDownloading = false;
onFail(DownloadCode.UNKNOWN_ERROR);
} finally {
if (randomAccessFile != null) try {
randomAccessFile.getFD().sync();
} catch (IOException e) {/*ignore*/}
if (randomAccessFile != null) try {
randomAccessFile.close();
} catch (IOException e) {/*ignore*/}
if (inputStream != null) try {
inputStream.close();
} catch (IOException e) {/*ignore*/}
if (httpConn != null) httpConn.disconnect();
}
}
private void onProgress(final long progress, final long total) {
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (mDelegate != null) mDelegate.onProgress(progress, total);
}
});
}
private boolean onCheck(File file) {
return mDelegate != null && mDelegate.onCheck(file);
}
private void onSuccess(final File file) {
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (mDelegate != null) mDelegate.onSuccess(file);
}
});
}
private void onFail(final int reason) {
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (mDelegate != null) mDelegate.onFail(reason);
}
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment