我们先一起简单回顾下它的基本原理。
http://blog.csdn.net/shimiso/article/details/6763664 android 多线程断点续传下载 一
http://blog.csdn.net/shimiso/article/details/6763986 android 多线程断点续传下载 二
http://blog.csdn.net/shimiso/article/details/8448544 android 多线程断点续传下载 三
在下面介绍实现下载原理的时候,我想尝试倒着来说,这样是否好理解一点?
我们都知道,下载助手,比如360, 百度的 手机助手,下载APP 的时候 ,都可以同时下载多个,所以,下载肯定是多线程的,所以我们就需要一个线程工具类 来管理我们的线程,这个工具类的核心,就是 线程池。
线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用
01.
/**
02.
* Parameters:
03.
corePoolSize
04.
the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
05.
maximumPoolSize
06.
the maximum number of threads to allow in the pool
07.
keepAliveTime
08.
when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
09.
unit
10.
the time unit for the keepAliveTime argument
11.
workQueue
12.
the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
13.
handler
14.
the handler to use when execution is blocked because the thread bounds and queue capacities are reached
15.
Throws:
16.
IllegalArgumentException - if one of the following holds:
17.
corePoolSize < 0
18.
keepAliveTime < 0
19.
maximumPoolSize <= 0
20.
maximumPoolSize < corePoolSize
21.
NullPointerException - if workQueue or handler is null
22.
*/
23.
ThreadPoolExecutor threadpool=
new
ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, hand
上面是 ThreadPoolExecutor的参数介绍,
第一个参数 corePoolSize : 空闲时 存在的线程数目、
第二个参数 maximumPoolSize :允许同时存在的最大线程数、
第三个参数 keepAliveTime: 这个参数是 允许空闲线程存活的时间、
第四个参数 unit : 是 时间的单位 、
第五个参数 workQueue :这个是一个容器,它里面存放的是、 threadpool.execute(new Runnable()) 执行的线程.new Runnable()、
第六个参数 handler:当执行被阻塞时,该处理程序将被阻塞,因为线程的边界和队列容量达到了 。
介绍完了 线程池参数,那我们就先创建一个线程管理的工具类 ThreadManager
001.
public
class
ThreadManager {
002.
public
static
final
String DEFAULT_SINGLE_POOL_NAME =
"DEFAULT_SINGLE_POOL_NAME"
;
003.
004.
private
static
ThreadPoolProxy mLongPool =
null
;
005.
private
static
Object mLongLock =
new
Object();
006.
007.
private
static
ThreadPoolProxy mShortPool =
null
;
008.
private
static
Object mShortLock =
new
Object();
009.
010.
private
static
ThreadPoolProxy mDownloadPool =
null
;
011.
private
static
Object mDownloadLock =
new
Object();
012.
013.
private
static
Map<String, ThreadPoolProxy> mMap =
new
HashMap<String, ThreadPoolProxy>();
014.
private
static
Object mSingleLock =
new
Object();
015.
016.
/** 获取下载线程 */
017.
public
static
ThreadPoolProxy getDownloadPool() {
018.
synchronized
(mDownloadLock) {
019.
if
(mDownloadPool ==
null
) {
020.
mDownloadPool =
new
ThreadPoolProxy(
3
,
3
, 5L);
021.
}
022.
return
mDownloadPool;
023.
}
024.
}
025.
026.
/** 获取一个用于执行长耗时任务的线程池,避免和短耗时任务处在同一个队列而阻塞了重要的短耗时任务,通常用来联网操作 */
027.
public
static
ThreadPoolProxy getLongPool() {
028.
synchronized
(mLongLock) {
029.
if
(mLongPool ==
null
) {
030.
mLongPool =
new
ThreadPoolProxy(
5
,
5
, 5L);
031.
}
032.
return
mLongPool;
033.
}
034.
}
035.
036.
/** 获取一个用于执行短耗时任务的线程池,避免因为和耗时长的任务处在同一个队列而长时间得不到执行,通常用来执行本地的IO/SQL */
037.
public
static
ThreadPoolProxy getShortPool() {
038.
synchronized
(mShortLock) {
039.
if
(mShortPool ==
null
) {
040.
mShortPool =
new
ThreadPoolProxy(
2
,
2
, 5L);
041.
}
042.
return
mShortPool;
043.
}
044.
}
045.
046.
/** 获取一个单线程池,所有任务将会被按照加入的顺序执行,免除了同步开销的问题 */
047.
public
static
ThreadPoolProxy getSinglePool() {
048.
return
getSinglePool(DEFAULT_SINGLE_POOL_NAME);
049.
}
050.
051.
/** 获取一个单线程池,所有任务将会被按照加入的顺序执行,免除了同步开销的问题 */
052.
public
static
ThreadPoolProxy getSinglePool(String name) {
053.
synchronized
(mSingleLock) {
054.
ThreadPoolProxy singlePool = mMap.get(name);
055.
if
(singlePool ==
null
) {
056.
singlePool =
new
ThreadPoolProxy(
1
,
1
, 5L);
057.
mMap.put(name, singlePool);
058.
}
059.
return
singlePool;
060.
}
061.
}
062.
063.
public
static
class
ThreadPoolProxy {
064.
private
ThreadPoolExecutor mPool;
065.
private
int
mCorePoolSize;
066.
private
int
mMaximumPoolSize;
067.
private
long
mKeepAliveTime;
068.
069.
private
ThreadPoolProxy(
int
corePoolSize,
int
maximumPoolSize,
long
keepAliveTime) {
070.
mCorePoolSize = corePoolSize;
071.
mMaximumPoolSize = maximumPoolSize;
072.
mKeepAliveTime = keepAliveTime;
073.
}
074.
075.
/** 执行任务,当线程池处于关闭,将会重新创建新的线程池 */
076.
public
synchronized
void
execute(Runnable run) {
077.
if
(run ==
null
) {
078.
return
;
079.
}
080.
if
(mPool ==
null
|| mPool.isShutdown()) {
081.
mPool =
new
ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS,
new
LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(),
new
AbortPolicy());
082.
}
083.
mPool.execute(run);
084.
}
085.
086.
/** 取消线程池中某个还未执行的任务 */
087.
public
synchronized
void
cancel(Runnable run) {
088.
if
(mPool !=
null
&& (!mPool.isShutdown() || mPool.isTerminating())) {
089.
mPool.getQueue().remove(run);
090.
}
091.
}
092.
093.
/** 取消线程池中某个还未执行的任务 */
094.
public
synchronized
boolean
contains(Runnable run) {
095.
if
(mPool !=
null
&& (!mPool.isShutdown() || mPool.isTerminating())) {
096.
return
mPool.getQueue().contains(run);
097.
}
else
{
098.
return
false
;
099.
}
100.
}
101.
102.
/** 立刻关闭线程池,并且正在执行的任务也将会被中断 */
103.
public
void
stop() {
104.
if
(mPool !=
null
&& (!mPool.isShutdown() || mPool.isTerminating())) {
105.
mPool.shutdownNow();
106.
}
107.
}
108.
109.
/** 平缓关闭单任务线程池,但是会确保所有已经加入的任务都将会被执行完毕才关闭 */
110.
public
synchronized
void
shutdown() {
111.
if
(mPool !=
null
&& (!mPool.isShutdown() || mPool.isTerminating())) {
112.
mPool.shutdownNow();
113.
}
114.
}
115.
116.
}
117.
}
这个线程池工具类 主要就是 生成一个线程池, 以及 取消线程池中的任务,查询线程池中是否包含某一任务。
我们的现在线程 DownloadTask 就 通过 ThreadManager .getDownloadPool().execute() 方法 交给线程池去管理。
有了线程池管理我们的线程, 那我们下一步 就是 DownloadTask 这个类去下载了。
01.
/** 下载任务 */
02.
public
class
DownloadTask
implements
Runnable {
03.
private
DownloadInfo info;
04.
05.
public
DownloadTask(DownloadInfo info) {
06.
this
.info = info;
07.
}
08.
09.
@Override
10.
public
void
run() {
11.
info.setDownloadState(STATE_DOWNLOADING);
// 先改变下载状态
12.
notifyDownloadStateChanged(info);
13.
File file =
new
File(info.getPath());
// 获取下载文件
14.
HttpResult httpResult =
null
;
15.
InputStream stream =
null
;
16.
if
(info.getCurrentSize() ==
0
|| !file.exists()
17.
|| file.length() != info.getCurrentSize()) {
18.
// 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
19.
20.
info.setCurrentSize(
0
);
21.
file.delete();
22.
}
23.
httpResult = HttpHelper.download(info.getUrl());
24.
// else {
25.
// // //文件存在且长度和进度相等,采用断点下载
26.
// httpResult = HttpHelper.download(info.getUrl() + "&range=" +
27.
// info.getCurrentSize());
28.
// }
29.
if
(httpResult ==
null
30.
|| (stream = httpResult.getInputStream()) ==
null
) {
31.
info.setDownloadState(STATE_ERROR);
// 没有下载内容返回,修改为错误状态
32.
notifyDownloadStateChanged(info);
33.
}
else
{
34.
try
{
35.
skipBytesFromStream(stream, info.getCurrentSize());
36.
}
catch
(Exception e1) {
37.
e1.printStackTrace();
38.
}
39.
40.
FileOutputStream fos =
null
;
41.
try
{
42.
fos =
new
FileOutputStream(file,
true
);
43.
int
count = -
1
;
44.
byte
[] buffer =
new
byte
[
1024
];
45.
while
(((count = stream.read(buffer)) != -
1
)
46.
&& info.getDownloadState() == STATE_DOWNLOADING) {
47.
// 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
48.
fos.write(buffer,
0
, count);
49.
fos.flush();
50.
info.setCurrentSize(info.getCurrentSize() + count);
51.
notifyDownloadProgressed(info);
// 刷新进度
52.
}
53.
}
catch
(Exception e) {
54.
info.setDownloadState(STATE_ERROR);
55.
notifyDownloadStateChanged(info);
56.
info.setCurrentSize(
0
);
57.
file.delete();
58.
}
finally
{
59.
IOUtils.close(fos);
60.
if
(httpResult !=
null
) {
61.
httpResult.close();
62.
}
63.
}
64.
65.
// 判断进度是否和app总长度相等
66.
if
(info.getCurrentSize() == info.getAppSize()) {
67.
info.setDownloadState(STATE_DOWNLOADED);
68.
notifyDownloadStateChanged(info);
69.
}
else
if
(info.getDownloadState() == STATE_PAUSED) {
// 判断状态
70.
notifyDownloadStateChanged(info);
71.
}
else
{
72.
info.setDownloadState(STATE_ERROR);
73.
notifyDownloadStateChanged(info);
74.
info.setCurrentSize(
0
);
// 错误状态需要删除文件
75.
file.delete();
76.
}
77.
}
78.
mTaskMap.remove(info.getId());
79.
}
80.
}
下载的原理 很简单,就是 通过 目标的URL 拿到流,然后写到本地。
因为下载在 run() 里面执行,这个DownloadTask 类 我们就看run() 方法的实现,所以 关键代码 就是下面一点点
01.
fos =
new
FileOutputStream(file,
true
);
02.
int
count = -
1
;
03.
byte
[] buffer =
new
byte
[
1024
];
04.
while
(((count = stream.read(buffer)) != -
1
)
05.
&& info.getDownloadState() == STATE_DOWNLOADING) {
06.
// 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
07.
fos.write(buffer,
0
, count);
08.
fos.flush();
09.
info.setCurrentSize(info.getCurrentSize() + count);
10.
notifyDownloadProgressed(info);
// 刷新进度
11.
}
这个在我们刚接触Java 的时候 肯定都写过了。 这就是往本地写数据的代码。所以run()方法中的 前面 就是拿到 stream 输入流, 以及 把file 创建出来。
关于控制 button中text 显示 暂停 ,下载,还是进度,就靠 notifyDownloadProgressed(info);和 notifyDownloadStateChanged(info)两个方法, 这两个方法 实际上调用的是两个接口,只要我们在我们需要改变界面的类里 实现这两个接口,就可以接收到 包含最新信息的info对象。而我们在哪个类里改变button 上面 显示的文字呢? 当然是在 我们的adapter 里面了,大家都知道 是在 adapter 的getView() 方法里面 加载的每一条数据的布局。
那就一起看下是不是这样子呢?
001.
public
class
RecommendAdapter
extends
BaseAdapter
implements
002.
DownloadManager.DownloadObserver {
003.
004.
ArrayList<AppInfo> list;
005.
private
List<ViewHolder> mDisplayedHolders;
006.
private
FinalBitmap finalBitmap;
007.
private
Context context;
008.
009.
public
RecommendAdapter(ArrayList<AppInfo> list, FinalBitmap finalBitmap,
010.
Context context) {
011.
this
.list = list;
012.
this
.context = context;
013.
this
.finalBitmap = finalBitmap;
014.
mDisplayedHolders =
new
ArrayList<ViewHolder>();
015.
}
016.
017.
018.
019.
public
void
startObserver() {
020.
DownloadManager.getInstance().registerObserver(
this
);
021.
}
022.
023.
public
void
stopObserver() {
024.
DownloadManager.getInstance().unRegisterObserver(
this
);
025.
}
026.
027.
@Override
028.
public
int
getCount() {
029.
return
list.size();
030.
}
031.
032.
@Override
033.
public
Object getItem(
int
position) {
034.
return
list.get(position);
035.
}
036.
037.
@Override
038.
public
long
getItemId(
int
position) {
039.
return
position;
040.
}
041.
042.
@Override
043.
public
View getView(
int
position, View convertView, ViewGroup parent) {
044.
final
AppInfo appInfo = list.get(position);
045.
final
ViewHolder holder;
046.
047.
if
(convertView ==
null
) {
048.
holder =
new
ViewHolder(context);
049.
}
else
{
050.
holder = (ViewHolder) convertView.getTag();
051.
}
052.
holder.setData(appInfo);
053.
mDisplayedHolders.add(holder);
054.
return
holder.getRootView();
055.
}
056.
057.
@Override
058.
public
void
onDownloadStateChanged(DownloadInfo info) {
059.
refreshHolder(info);
060.
}
061.
062.
@Override
063.
public
void
onDownloadProgressed(DownloadInfo info) {
064.
refreshHolder(info);
065.
066.
}
067.
068.
public
List<ViewHolder> getDisplayedHolders() {
069.
synchronized
(mDisplayedHolders) {
070.
return
new
ArrayList<ViewHolder>(mDisplayedHolders);
071.
}
072.
}
073.
074.
public
void
clearAllItem() {
075.
if
(list !=
null
){
076.
list.clear();
077.
}
078.
if
(mDisplayedHolders !=
null
) {
079.
mDisplayedHolders.clear();
080.
}
081.
}
082.
083.
public
void
addItems(ArrayList<AppInfo> infos) {
084.
list.addAll(infos);
085.
}
086.
087.
private
void
refreshHolder(
final
DownloadInfo info) {
088.
List<ViewHolder> displayedHolders = getDisplayedHolders();
089.
for
(
int
i =
0
; i < displayedHolders.size(); i++) {
090.
final
ViewHolder holder = displayedHolders.get(i);
091.
AppInfo appInfo = holder.getData();
092.
if
(appInfo.getId() == info.getId()) {
093.
AppUtil.post(
new
Runnable() {
094.
@Override
095.
public
void
run() {
096.
holder.refreshState(info.getDownloadState(),
097.
info.getProgress());
098.
}
099.
});
100.
}
101.
}
102.
103.
}
104.
105.
public
class
ViewHolder {
106.
public
TextView textView01;
107.
public
TextView textView02;
108.
public
TextView textView03;
109.
public
TextView textView04;
110.
public
ImageView imageView_icon;
111.
public
Button button;
112.
public
LinearLayout linearLayout;
113.
114.
public
AppInfo mData;
115.
private
DownloadManager mDownloadManager;
116.
private
int
mState;
117.
private
float
mProgress;
118.
protected
View mRootView;
119.
private
Context context;
120.
private
boolean
hasAttached;
121.
122.
public
ViewHolder(Context context) {
123.
mRootView = initView();
124.
mRootView.setTag(
this
);
125.
this
.context = context;
126.
127.
128.
}
129.
130.
public
View getRootView() {
131.
return
mRootView;
132.
}
133.
134.
public
View initView() {
135.
View view = AppUtil.inflate(R.layout.item_recommend_award);
136.
137.
imageView_icon = (ImageView) view
138.
.findViewById(R.id.imageview_task_app_cion);
139.
140.
textView01 = (TextView) view
141.
.findViewById(R.id.textview_task_app_name);
142.
textView02 = (TextView) view
143.
.findViewById(R.id.textview_task_app_size);
144.
textView03 = (TextView) view
145.
.findViewById(R.id.textview_task_app_desc);
146.
textView04 = (TextView) view
147.
.findViewById(R.id.textview_task_app_love);
148.
button = (Button) view.findViewById(R.id.button_task_download);
149.
linearLayout = (LinearLayout) view
150.
.findViewById(R.id.linearlayout_task);
151.
152.
button.setOnClickListener(
new
OnClickListener() {
153.
@Override
154.
public
void
onClick(View v) {
155.
System.out.println(
"mState:173 "
+mState);
156.
if
(mState == DownloadManager.STATE_NONE
157.
|| mState == DownloadManager.STATE_PAUSED
158.
|| mState == DownloadManager.STATE_ERROR) {
159.
160.
mDownloadManager.download(mData);
161.
}
else
if
(mState == DownloadManager.STATE_WAITING
162.
|| mState == DownloadManager.STATE_DOWNLOADING) {
163.
mDownloadManager.pause(mData);
164.
}
else
if
(mState == DownloadManager.STATE_DOWNLOADED) {
165.
// tell2Server();
166.
mDownloadManager.install(mData);
167.
}
168.
}
169.
});
170.
return
view;
171.
}
172.
173.
174.
public
void
setData(AppInfo data) {
175.
176.
if
(mDownloadManager ==
null
) {
177.
mDownloadManager = DownloadManager.getInstance();
178.
179.
}
180.
String filepath= FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + data.getName() +
".apk"
;
181.
182.
boolean
existsFile = FileUtil.isExistsFile(filepath);
183.
if
(existsFile){
184.
int
fileSize = FileUtil.getFileSize(filepath);
185.
186.
if
(data.getSize()==fileSize){
187.
DownloadInfo downloadInfo = DownloadInfo.clone(data);
188.
downloadInfo.setCurrentSize(data.getSize());
189.
downloadInfo.setHasFinished(
true
);
190.
mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
191.
}
192.
// else if(fileSize>0){
193.
// DownloadInfo downloadInfo = DownloadInfo.clone(data);
194.
// downloadInfo.setCurrentSize(data.getSize());
195.
// downloadInfo.setHasFinished(false);
196.
// mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
197.
// }
198.
199.
}
200.
201.
DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(data
202.
.getId());
203.
if
(downloadInfo !=
null
) {
204.
205.
mState = downloadInfo.getDownloadState();
206.
mProgress = downloadInfo.getProgress();
207.
}
else
{
208.
209.
mState = DownloadManager.STATE_NONE;
210.
mProgress =
0
;
211.
}
212.
this
.mData = data;
213.
refreshView();
214.
}
215.
216.
public
AppInfo getData() {
217.
return
mData;
218.
}
219.
220.
public
void
refreshView() {
221.
linearLayout.removeAllViews();
222.
AppInfo info = getData();
223.
textView01.setText(info.getName());
224.
textView02.setText(FileUtil.FormetFileSize(info.getSize()));
225.
textView03.setText(info.getDes());
226.
textView04.setText(info.getDownloadNum() + "下载量);
227.
finalBitmap.display(imageView_icon, info.getIconUrl());
228.
229.
230.
if
(info.getType().equals(
"0"
)) {
231.
// mState = DownloadManager.STATE_READ;
232.
textView02.setVisibility(View.GONE);
233.
}
else
{
234.
String path=FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + info.getName() +
".apk"
;
235.
hasAttached = FileUtil.isValidAttach(path,
false
);
236.
237.
DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(info
238.
.getId());
239.
if
(downloadInfo !=
null
&& hasAttached) {
240.
if
(downloadInfo.isHasFinished()){
241.
242.
mState = DownloadManager.STATE_DOWNLOADED;
243.
}
else
{
244.
mState = DownloadManager.STATE_PAUSED;
245.
246.
}
247.
248.
}
else
{
249.
mState = DownloadManager.STATE_NONE;
250.
if
(downloadInfo !=
null
){
251.
downloadInfo.setDownloadState(mState);
252.
}
253.
}
254.
}
255.
256.
refreshState(mState, mProgress);
257.
}
258.
259.
public
void
refreshState(
int
state,
float
progress) {
260.
mState = state;
261.
mProgress = progress;
262.
switch
(mState) {
263.
case
DownloadManager.STATE_NONE:
264.
button.setText(R.string.app_state_download);
265.
break
;
266.
case
DownloadManager.STATE_PAUSED:
267.
button.setText(R.string.app_state_paused);
268.
break
;
269.
case
DownloadManager.STATE_ERROR:
270.
button.setText(R.string.app_state_error);
271.
break
;
272.
case
DownloadManager.STATE_WAITING:
273.
button.setText(R.string.app_state_waiting);
274.
break
;
275.
case
DownloadManager.STATE_DOWNLOADING:
276.
button.setText((
int
) (mProgress *
100
) +
"%"
);
277.
break
;
278.
case
DownloadManager.STATE_DOWNLOADED:
279.
button.setText(R.string.app_state_downloaded);
280.
break
;
281.
// case DownloadManager.STATE_READ:
282.
// button.setText(R.string.app_state_read);
283.
// break;
284.
default
:
285.
break
;
286.
}
287.
}
288.
}
289.
}
里面代码有点多,那就看startObserver()方法做了什么。
1.
public
void
startObserver() {
2.
DownloadManager.getInstance().registerObserver(
this
);
3.
}
这里 是 注册了observer, Observer 是什么东西?在DownloadManager 中我们定义了
1.
public