Last active
March 4, 2019 03:02
-
-
Save wuyisheng/2dd6207dda94a62b6eb6b2a7cef58746 to your computer and use it in GitHub Desktop.
A HttpURLConnection download example for android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 [email protected] 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