需求:
最近有个需求,要求把网页中的图片地址和音频地址抓出来,然后下载保存到本地。这里一个网页中有可能有一个url地址,也有可能有多个个url地址。当然,下载这种操作,另外下载超过1个,想到的就是用线程池。
实现:
第一步:先写一个ThreadPoolProxyFactory ,里面提供下载的线程池。代码如下:
/**
* @author weijunqiang
* @date 2018/7/20
* 一个ThreadPoolProxyFactory ,里面提供下载的线程池
*/
public class ThreadPoolProxyFactory {
static ThreadPoolProxy mDownLoadThreadPoolProxy;
/**
* 得到下载线程池代理对象mDownLoadThreadPoolProxy
*/
public static ThreadPoolProxy getDownLoadThreadPoolProxy() {
if (mDownLoadThreadPoolProxy == null) {
synchronized (ThreadPoolProxyFactory.class) {
if (mDownLoadThreadPoolProxy == null) {
mDownLoadThreadPoolProxy = new ThreadPoolProxy(3, 20);
}
}
}
return mDownLoadThreadPoolProxy;
}
}
第二步:写一个线程池代理,替线程池完成一些操作。提供了三种方法:执行任务,提交任务,移除任务。
/**
* @author weijunqiang
* @date 2018/7/20
* 线程池代理,替线程池完成一些操作。提供了三种方法:执行任务,提交任务,移除任务。
*/
public class ThreadPoolProxy {
ThreadPoolExecutor mExecutor;
private int mCorePoolSize;
private int mMaximumPoolSize;
/**
* @param corePoolSize 核心池的大小
* @param maximumPoolSize 最大线程数
*/
public ThreadPoolProxy(int corePoolSize, int maximumPoolSize) {
mCorePoolSize = corePoolSize;
mMaximumPoolSize = maximumPoolSize;
}
/**
* 初始化ThreadPoolExecutor
* 双重检查加锁,只有在第一次实例化的时候才启用同步机制,提高了性能
*/
private void initThreadPoolExecutor() {
if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {
synchronized (ThreadPoolProxy.class) {
if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {
long keepAliveTime = 3000;
TimeUnit unit = TimeUnit.MILLISECONDS;
BlockingQueue workQueue = new LinkedBlockingDeque<>();
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
mExecutor = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, handler);
}
}
}
}
/**
执行任务和提交任务的区别?
1.有无返回值
execute->没有返回值
submit-->有返回值
2.Future的具体作用?
1.有方法可以接收一个任务执行完成之后的结果,其实就是get方法,get方法是一个阻塞方法
2.get方法的签名抛出了异常===>可以处理任务执行过程中可能遇到的异常
*/
/**
* 执行任务
*/
public void execute(Runnable task) {
initThreadPoolExecutor();
mExecutor.execute(task);
}
/**
* 提交任务
* Runnable
*/
public Future submit(Runnable task) {
initThreadPoolExecutor();
return mExecutor.submit(task);
}
/**
* 提交任务
* Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
* Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
*/
public Future<ImgCacheBean> submit(Callable<ImgCacheBean> callable) {
initThreadPoolExecutor();
return mExecutor.submit(callable);
}
/**
* 移除任务
*/
public void remove(Runnable task) {
initThreadPoolExecutor();
mExecutor.remove(task);
}
}
第三步:线程池的使用
private void startDownloads(final List<ImgCacheBean> urls) {
try {
final List<Future<ImgCacheBean>> result = new ArrayList<>();
for (int i = 0; i < urls.size(); i++) {
String cacheUrl = "";
String fileName = "";
//TODO 将url中的汉字进行转码,空格换成"%20"
String gradeChineseStr = RegularUtils.getChinese(urls.get(i).getHttpUrl());
cacheUrl = decodeSrc.replace(" ", "%20");
URL url = new URL(cacheUrl);
Future<ImgCacheBean> future = ThreadPoolProxyFactory.getDownLoadThreadPoolProxy().submit(new DoCallable(url, urls.get(i), fileName));
result.add(future);
}
//TODO 这里面使用又起一个线程,是因为Future的get()方法是一个阻塞方法,不能在主线程直接使用
new Thread() {
@Override
public void run() {
super.run();
try {
for (int k = 0; k < result.size(); k++) {
//TODO get(6, TimeUnit.SECONDS)单个文件最长等6秒
ImgCacheBean imgCacheBean = result.get(k).get(6, TimeUnit.SECONDS);
if (imgCacheBean != null) {
downloads.add(imgCacheBean);
}
}
if (downloads.size() >= urls.size()) {
Message msg = mHandle.obtainMessage();
msg.what = 1;
mHandle.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
Message msg = mHandle.obtainMessage();
msg.what = 2;
mHandle.sendMessage(msg);
}
}
}.start();
} catch (Exception e) {
e.printStackTrace();
initWebview();
}
}
总结:
上面第三个使用方法,只是是部分代码,在使用的时候需要注意,在线程池调用了submit(Callable<ImgCacheBean> callable)
之后,该方法会立刻返回一个Future<ImgCacheBean> future
对象。但它并不代表Callable代表的任务已经执行完了。Callable代表的任务执行没执行完是不知道的。具体体现在Future的get()方法是一个阻塞方法。当调用Future的get()方法后,直到Callable的call方法执行完后或者抛出异常才返回结果(这个过程可能很长时间,所以使用了get(6, TimeUnit.SECONDS)方法,表示最多等6秒)。所以上述在获取future对象返回的结果时,将它另起线程处理,等有了结果再通知主线程结果。