关于asynctask
大纲
Callable与Future
BlockingQueue接口以及LinkedBlockingQueue源码
ThreadPoolExecutor和SerialExecutor
单独使用Runnable时:
无法获得返回值
单独使用Callable时:
无法在新线程中(new Thread(Runnable r))使用,只能使用ExecutorService
Thread类只支持Runnable
FutureTask:
实现了Runnable和Future,所以兼顾两者优点
既可以使用ExecutorService,也可以使用Thread
1.Callable与Future
/**
* Callable与Runnable类似,这两个接口的实现类都能由一个thread去执行,
* 不同的是Callable接口可以返回结果或者抛出异常
* Executors类里有将其他类型转换为Callable类型的工具方法
*/
public interface Callable {
/**
* Computes a result, or throws an exception if unable to do so.
*/
V call() throws Exception;
}
public interface Future {
/**
* Attempts to cancel execution of this task. This attempt will fail if the
* task has already completed, has already been cancelled, or could not be
* cancelled for some other reason. If successful, and this task has not
* started when {@code cancel} is called, this task should never run. If the
* task has already started, then the {@code mayInterruptIfRunning}
* parameter determines whether the thread executing this task should be
* interrupted in an attempt to stop the task.
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* @return {@code true} if this task was cancelled before it completed
*/
boolean isCancelled();
/**
* Returns {@code true} if this task completed.
* @return {@code true} if this task completed
*/
boolean isDone();
/**
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
*/
V get() throws InterruptedException, ExecutionException;
/**
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException;
}
2.FutureTask
FutureTask是一个RunnableFuture
public interface RunnableFuture extends Runnable, Future {
void run();
}
public class FutureTask implements RunnableFuture {
}
可以包装Runnable和Callable
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
//Runnable注入会被Executors.callable()函数转换为Callable类型,
//即FutureTask最终都是执行Callable类型的任务
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
Runnable转换为Callable
public static Callable callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}
static final class RunnableAdapter implements Callable {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
FutureTask实例
public class RunnableFutureTask {
/**
* ExecutorService
*/
static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
/**
*
* @param args
*/
public static void main(String[] args) {
runnableDemo();
futureDemo();
}
/**
* runnable, 无返回值
*/
static void runnableDemo() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable demo : " + fibc(20));
}
}).start();
}
/**
* 其中Runnable实现的是void run()方法,无返回值;Callable实现的是 V
* call()方法,并且可以返回执行结果。其中Runnable可以提交给Thread来包装下
* ,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
*/
static void futureDemo() {
try {
/**
* 提交runnable则没有返回值, future没有数据
*/
Future> result = mExecutor.submit(new Runnable() {
@Override
public void run() {
fibc(20);
}
});
System.out.println("future result from runnable : " + result.get());
/**
* 提交Callable, 有返回值, future中能够获取返回值
*/
Future result2 = mExecutor.submit(new Callable() {
@Override
public Integer call() throws Exception {
return fibc(20);
}
});
System.out
.println("future result from callable : " + result2.get());
/**
* FutureTask则是一个RunnableFuture,即实现了Runnbale又实现了Futrue这两个接口,
* 另外它还可以包装Runnable(实际上会转换为Callable)和Callable
* ,所以一般来讲是一个符合体了,它可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行
* ,并且还可以通过v get()返回执行结果,在线程体没有执行完成的时候,主线程一直阻塞等待,执行完则直接返回结果。
*/
FutureTask futureTask = new FutureTask(
new Callable() {
@Override
public Integer call() throws Exception {
return fibc(20);
}
});
// 提交futureTask
mExecutor.submit(futureTask);
System.out.println("future result from futureTask : "
+ futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/**
* 效率底下的斐波那契数列, 耗时的操作
*/
static int fibc(int num) {
if (num == 0) {
return 0;
}
if (num == 1) {
return 1;
}
return fibc(num - 1) + fibc(num - 2);
}
}
实例二
public class FutureTaskTest {
public static void main(String[] args) {
Callable task = new Callable() {
public String call() {
System.out.println("Sleep start.");
try {
Thread.sleep(1000 * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sleep end.");
return "time=" + System.currentTimeMillis();
}
};
// 1.直接使用Thread的方式执行
FutureTask ft = new FutureTask(task);
Thread t = new Thread(ft);
t.start();
try {
System.out.println("waiting execute result");
System.out.println("result = " + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 2.使用Executors来执行
System.out.println("=========");
FutureTask ft2 = new FutureTask(task);
Executors.newSingleThreadExecutor().submit(ft2);
try {
System.out.println("waiting execute result");
System.out.println("result = " + ft2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
3.BlockingQueue
3.1BlockingQueue的核心方法
3.1.1放入数据:
offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续.
3.1.2获取数据:
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到有新的数据被加入;
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
3.2BlockingQueue的两个常用实现类
3.2.1ArrayBlockingQueue
内部维护了一个定长数组和两个整形变量,分别指示队头和队尾的位置;
ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。
ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
3.2.2ListBlockingQueue
基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。
而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了
3.2.2.1LinkedBlockingQueue的源码分析
3.2.2.2LinkedBlockingQueue的使用案例
3.2.3 SynchronousQueue
用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。比如,我先添加一个元素,接下来如果继续想尝试添加则会阻塞,直到另一个线程取走一个元素,反之亦然
3.3生产者消费者实例
4.ThreadPoolExecutor
4.1构造方法
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, //最大线程池大小
long keepAliveTime, //
TimeUnit unit,
BlockingQueue workQueue, //任务队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler)//
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
4.2Executors提供的几种构造方法
4.2.1
构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,不会由RejectedExecutionHandler处理
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
4.2.2
构造一个缓冲功能的线程池,配置corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,keepAliveTime=60s,以及一个无容量的阻塞队列 SynchronousQueue,因此任务提交之后,将会创建新的线程执行;线程空闲超过60s将会销毁
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
4.2.3
构造一个只支持一个线程的线程池,配置corePoolSize=maximumPoolSize=1,无界阻塞队列LinkedBlockingQueue;保证任务由一个线程串行执行
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
4.2.4
========================================
public class DownloadException extends Exception {
private static final long serialVersionUID = 1L;
private String mExtra;
public DownloadException(String message) {
super(message);
}
public DownloadException(String message, String extra) {
super(message);
mExtra = extra;
}
public String getExtra() {
return mExtra;
}
}
public class FileAlreadyExistException extends DownloadException {
/**
*
*/
private static final long serialVersionUID = 1L;
public FileAlreadyExistException(String message) {
super(message);
}
}
public class NoMemoryException extends DownloadException {
/**
*
*/
private static final long serialVersionUID = 1L;
public NoMemoryException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
import android.text.format.Time;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Helper for parsing an HTTP date.
*/
public final class HttpDateTime {
/*
* Regular expression for parsing HTTP-date.
*
* Wdy, DD Mon YYYY HH:MM:SS GMT RFC 822, updated by RFC 1123
*
* Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, obsoleted by RFC 1036
*
* Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format
*
* with following variations
*
* Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon YYYY HH:MM:SS GMT Wdy,DD Mon
* YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS GMT Wdy, DD Mon YYYY HH:MM:SS
* -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon (SP)D HH:MM:SS YYYY Wdy Mon DD
* HH:MM:SS YYYY GMT
*
* HH can be H if the first digit is zero.
*
* Mon can be the full name of the month.
*/
private static final String HTTP_DATE_RFC_REGEXP = "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP = "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/**
* The compiled version of the HTTP-date regular expressions.
*/
private static final Pattern HTTP_DATE_RFC_PATTERN = Pattern
.compile(HTTP_DATE_RFC_REGEXP);
private static final Pattern HTTP_DATE_ANSIC_PATTERN = Pattern
.compile(HTTP_DATE_ANSIC_REGEXP);
private static class TimeOfDay {
TimeOfDay(int h, int m, int s) {
this.hour = h;
this.minute = m;
this.second = s;
}
int hour;
int minute;
int second;
}
public static long parse(String timeString) throws IllegalArgumentException {
int date = 1;
int month = Calendar.JANUARY;
int year = 1970;
TimeOfDay timeOfDay;
Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
if (rfcMatcher.find()) {
date = getDate(rfcMatcher.group(1));
month = getMonth(rfcMatcher.group(2));
year = getYear(rfcMatcher.group(3));
timeOfDay = getTime(rfcMatcher.group(4));
} else {
Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
if (ansicMatcher.find()) {
month = getMonth(ansicMatcher.group(1));
date = getDate(ansicMatcher.group(2));
timeOfDay = getTime(ansicMatcher.group(3));
year = getYear(ansicMatcher.group(4));
} else {
throw new IllegalArgumentException();
}
}
// FIXME: Y2038 BUG!
if (year >= 2038) {
year = 2038;
month = Calendar.JANUARY;
date = 1;
}
Time time = new Time(Time.TIMEZONE_UTC);
time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
month, year);
return time.toMillis(false /* use isDst */);
}
private static int getDate(String dateString) {
if (dateString.length() == 2) {
return (dateString.charAt(0) - '0') * 10
+ (dateString.charAt(1) - '0');
} else {
return (dateString.charAt(0) - '0');
}
}
/*
* jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0
* + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20
* + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19
* = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9
*/
private static int getMonth(String monthString) {
int hash = Character.toLowerCase(monthString.charAt(0))
+ Character.toLowerCase(monthString.charAt(1))
+ Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
switch (hash) {
case 22:
return Calendar.JANUARY;
case 10:
return Calendar.FEBRUARY;
case 29:
return Calendar.MARCH;
case 32:
return Calendar.APRIL;
case 36:
return Calendar.MAY;
case 42:
return Calendar.JUNE;
case 40:
return Calendar.JULY;
case 26:
return Calendar.AUGUST;
case 37:
return Calendar.SEPTEMBER;
case 35:
return Calendar.OCTOBER;
case 48:
return Calendar.NOVEMBER;
case 9:
return Calendar.DECEMBER;
default:
throw new IllegalArgumentException();
}
}
private static int getYear(String yearString) {
if (yearString.length() == 2) {
int year = (yearString.charAt(0) - '0') * 10
+ (yearString.charAt(1) - '0');
if (year >= 70) {
return year + 1900;
} else {
return year + 2000;
}
} else if (yearString.length() == 3) {
// According to RFC 2822, three digit years should be added to 1900.
int year = (yearString.charAt(0) - '0') * 100
+ (yearString.charAt(1) - '0') * 10
+ (yearString.charAt(2) - '0');
return year + 1900;
} else if (yearString.length() == 4) {
return (yearString.charAt(0) - '0') * 1000
+ (yearString.charAt(1) - '0') * 100
+ (yearString.charAt(2) - '0') * 10
+ (yearString.charAt(3) - '0');
} else {
return 1970;
}
}
private static TimeOfDay getTime(String timeString) {
// HH might be H
int i = 0;
int hour = timeString.charAt(i++) - '0';
if (timeString.charAt(i) != ':')
hour = hour * 10 + (timeString.charAt(i++) - '0');
// Skip ':'
i++;
int minute = (timeString.charAt(i++) - '0') * 10
+ (timeString.charAt(i++) - '0');
// Skip ':'
i++;
int second = (timeString.charAt(i++) - '0') * 10
+ (timeString.charAt(i++) - '0');
return new TimeOfDay(hour, minute, second);
}
}
public interface DownloadTaskListener {
public void updateProcess(DownloadTask task);
public void finishDownload(DownloadTask task);
public void preDownload(DownloadTask task);
public void errorDownload(DownloadTask task, Throwable error);
}
public class DownloadTask extends AsyncTask {
public final static int TIME_OUT = 60 * 1000;
private final static int BUFFER_SIZE = 1024 * 8;
private static final String TAG = "DownloadTask";
private static final boolean DEBUG = true;
private static final String TEMP_SUFFIX = ".download";
private URL URL;
private File file;
private File tempFile;
private String url;
private RandomAccessFile outputStream;
private DownloadTaskListener listener;
private Context context;
private long downloadSize;
private long previousFileSize;
private long totalSize;
private long downloadPercent;
private long networkSpeed;
private long previousTime;
private long totalTime;
private Throwable error = null;
private boolean interrupt = false;
private final class ProgressReportingRandomAccessFile extends RandomAccessFile {
private int progress = 0;
private long beginTime;
public ProgressReportingRandomAccessFile(File file, String mode) throws FileNotFoundException {
super(file, mode);
beginTime = System.currentTimeMillis();
}
@Override
public void write(byte[] buffer, int offset, int count) throws IOException {
super.write(buffer, offset, count);
progress += count;
long endTime = System.currentTimeMillis();
if (endTime - beginTime >= 500) {
beginTime = endTime;
publishProgress(progress);
}
}
}
public DownloadTask(Context context, String url, String path, String fileName) throws MalformedURLException {
this(context, url, path, fileName, null);
}
public DownloadTask(Context context, String url, String path, String fileName, DownloadTaskListener listener) throws MalformedURLException {
super();
this.url = url;
this.URL = new URL(url);
this.listener = listener;
this.file = new File(path, fileName);
this.tempFile = new File(path, fileName + TEMP_SUFFIX);
this.context = context;
}
public DownloadTask(Context context, CubeModule module) throws MalformedURLException {
this(context, module, null);
}
public DownloadTask(Context context, CubeModule module, DownloadTaskListener listener) throws MalformedURLException {
super();
this.url = com.midea.common.config.URL.getDownloadUrl(module.getBundle());
this.URL = new URL(url);
this.listener = listener;
String zipPath = com.midea.common.config.URL.getModuleZipPath(module.getIdentifier(), module.getVersion());
this.file = new File(zipPath);
this.tempFile = new File(zipPath + TEMP_SUFFIX);
this.context = context;
}
public String getUrl() {
return url;
}
public boolean isInterrupt() {
return interrupt;
}
public long getDownloadPercent() {
return downloadPercent;
}
public long getDownloadSize() {
return downloadSize + previousFileSize;
}
public long getTotalSize() {
return totalSize;
}
public long getDownloadSpeed() {
return this.networkSpeed;
}
public long getTotalTime() {
return this.totalTime;
}
/**
* 已下载地址
*
* @return
*/
public String getDownloadFilePath() {
return file.getPath();
}
public DownloadTaskListener getListener() {
return this.listener;
}
@Override
protected void onPreExecute() {
previousTime = System.currentTimeMillis();
if (listener != null)
listener.preDownload(this);
}
@Override
protected Long doInBackground(Void... params) {
long result = -1;
try {
result = download();
} catch (NetworkErrorException e) {
error = e;
} catch (FileAlreadyExistException e) {
error = e;
} catch (NoMemoryException e) {
error = e;
} catch (IOException e) {
error = e;
} finally {
if (client != null) {
client.close();
}
}
return result;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (progress.length > 1) {
totalSize = progress[1];
if (totalSize == -1) {
if (listener != null)
listener.errorDownload(this, error);
} else {
}
} else {
totalTime = System.currentTimeMillis() - previousTime;
downloadSize = progress[0];
if (totalSize > 0)
downloadPercent = (downloadSize + previousFileSize) * 100 / totalSize;
networkSpeed = downloadSize / totalTime;
if (listener != null)
listener.updateProcess(this);
}
}
@Override
protected void onPostExecute(Long result) {
if (result == -1 || interrupt || error != null) {
if (DEBUG && error != null) {
Log.v(TAG, "Download failed." + error.getMessage());
}
if (listener != null) {
listener.errorDownload(this, error);
}
return;
}
// finish download 重命名临时文件名
tempFile.renameTo(file);
if (listener != null)
listener.finishDownload(this);
}
@Override
public void onCancelled() {
super.onCancelled();
interrupt = true;
}
private AndroidHttpClient client;
private HttpGet httpGet;
private HttpResponse response;
private long download() throws NetworkErrorException, IOException, FileAlreadyExistException, NoMemoryException {
FxLog.i(getClass(), "url: " + url);
FxLog.i(getClass(), "totalSize: " + totalSize);
/*
* check net work
*/
if (!SystemUtil.isNetworkConected(context)) {
throw new NetworkErrorException("Network blocked.");
}
/*
* check file length
*/
client = AndroidHttpClient.newInstance("DownloadTask");
httpGet = new HttpGet(url);
response = client.execute(httpGet);
totalSize = response.getEntity().getContentLength();
if (totalSize <= 0) {
throw new NetworkErrorException("totalSize is 0");
}
if (file.exists() && totalSize == file.length()) {
Log.v(null, "Output file already exists. Skipping download.");
publishProgress(0, (int) totalSize);
return totalSize;
// throw new
// FileAlreadyExistException("Output file already exists. Skipping download.");
} else if (tempFile.exists()) {
client.close();
client = null;
httpGet.addHeader("Range", "bytes=" + tempFile.length() + "-");
previousFileSize = tempFile.length();
client = AndroidHttpClient.newInstance("DownloadTask");
response = client.execute(httpGet);
if (DEBUG) {
Log.v(TAG, "File is not complete, download now.");
Log.v(TAG, "File length:" + tempFile.length() + " totalSize:" + totalSize);
}
}
/*
* check memory
*/
long storage = StorageUtils.getAvailableStorage();
if (DEBUG) {
Log.i(null, "storage:" + storage + " totalSize:" + totalSize);
}
if (totalSize - tempFile.length() > storage) {
throw new NoMemoryException("SD card no memory.");
}
/*
* start download
*/
outputStream = new ProgressReportingRandomAccessFile(tempFile, "rw");
publishProgress(0, (int) totalSize);
InputStream input = response.getEntity().getContent();
int bytesCopied = copy(input, outputStream);
if ((previousFileSize + bytesCopied) != totalSize && totalSize != -1 && !interrupt) {
throw new IOException("Download incomplete: " + bytesCopied + " != " + totalSize);
}
if (DEBUG) {
Log.v(TAG, "Download completed successfully.");
}
return bytesCopied;
}
public int copy(InputStream input, RandomAccessFile out) throws IOException, NetworkErrorException {
if (input == null || out == null) {
return -1;
}
byte[] buffer = new byte[BUFFER_SIZE];
BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
if (DEBUG) {
Log.v(TAG, "length" + out.length());
}
int count = 0, n = 0;
long errorBlockTimePreviousTime = -1, expireTime = 0;
try {
out.seek(out.length());
while (!interrupt) {
n = in.read(buffer, 0, BUFFER_SIZE);
if (n == -1) {
break;
}
out.write(buffer, 0, n);
count += n;
/*
* check network
*/
if (!SystemUtil.isNetworkConected(context)) {
throw new NetworkErrorException("Network blocked.");
}
if (networkSpeed == 0) {
if (errorBlockTimePreviousTime > 0) {
expireTime = System.currentTimeMillis() - errorBlockTimePreviousTime;
if (expireTime > TIME_OUT) {
throw new ConnectTimeoutException("connection time out.");
}
} else {
errorBlockTimePreviousTime = System.currentTimeMillis();
}
} else {
expireTime = 0;
errorBlockTimePreviousTime = -1;
}
}
} finally {
client.close(); // must close client first
client = null;
out.close();
in.close();
input.close();
}
return count;
}
}