最近的项目是一个与音乐相关的App,其中有一个功能:收藏喜欢的歌曲,在wifi的环境下自动下载。
考虑到音乐歌曲都是3、4Mb的小文件,断点下载的功能便不需要了。因此只需要实现一个特别轻量、简单的下载管理类,进行管理即可。
最初的思路便是任务队列,单线程顺序执行,一个文件接着一个文件进行下载。
之前看过AsyncTask的部分源码,其设计与我的想法类似,于是便借鉴着AsyncTask的源码,实现了一个特别简单、轻量的下载管理类。
源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165public class MyDownloadManager {
private static final String TAG = "MyDownloadManager";
private File downloadDir; // 文件保存路径
private static MyDownloadManager instance; // 单例
// 单线程任务队列
public static Executor executor;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "MyDownloadManager #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(128);
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
private MyDownloadManager() {
// 初始化下载路径
downloadDir = new File(AndroidCacheUtils.getCacheDirFile(MiaApplication.getInstance()), "download");
if (!downloadDir.exists()) {
downloadDir.mkdirs();
}
executor = new SerialExecutor();
}
/**
* 顺序执行下载任务
*/
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
/**
* 获取单例对象
*
* @return
*/
public static MyDownloadManager getInstance() {
if (instance == null) {
instance = new MyDownloadManager();
}
return instance;
}
/**
* 添加下载任务
*
* @param path
*/
public void addDownloadTask(final String path) {
executor.execute(new Runnable() {
public void run() {
download(path);
}
});
}
/**
* 下载文件
*
* @param path
*/
private void download(String path) {
String fileName = AndroidMD5.MD5(path);
File savePath = new File(downloadDir, fileName); // 下载文件路径
File finallyPath = new File(downloadDir, fileName + ".mp3"); // 下载完成后加入.mp3后缀
if (finallyPath.exists()) { // 文件存在则已下载
Log.i(TAG, "file is existed");
return;
}
if (AndroidNetWorkUtils.isWifiDataEnable(MiaApplication.getInstance())) { // 如果是Wifi则开始下载
if (savePath.exists() && savePath.delete()) { // 如果之前存在文件,证明没有下载完成,删掉重新创建
savePath = new File(downloadDir, fileName);
}
Log.i(TAG, "download start");
try {
byte[] bs = new byte[1024];
int len;
URL url = new URL(path);
InputStream is = url.openStream();
OutputStream os = new FileOutputStream(savePath);
while ((len = is.read(bs)) != -1) {
os.write(bs, 0, len);
}
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
if (savePath.renameTo(finallyPath)) { // 下载完成后重命名为.mp3文件
Log.i(TAG, "download end");
EventBus.getDefault().post(new DownloadDoneEvent(path));
}
} else { // 不是wifi则不下载
Log.i(TAG, "not wifi net, stop download");
}
}
/**
* 添加删除任务
*
* @param path
*/
public void addDeleteTask(final String path) {
executor.execute(new Runnable() {
public void run() {
delete(path);
}
});
}
/**
* 删除本地文件
*
* @param path
*/
private void delete(String path) {
String fileName = AndroidMD5.MD5(path);
File savePath = new File(downloadDir, fileName + ".mp3");
Log.i(TAG, savePath.getPath());
if (savePath.exists()) {
if (savePath.delete()) {
Log.i(TAG, "file is deleted");
}
}
}
/**
* 返回下载路径
*
* @return
*/
public File getDownloadDir() {
return downloadDir;
}
}
我们看到public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
,这句代码便是创建一个线程池。其方法源码及参数说明:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default rejected execution handler.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime 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.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
这里我前三个参数传的都是1,既是最多只有1个线程。sPoolWorkQueue参数则是一个容量为128的任务队列,既最多能存放128个任务。
下面我们看到SerialExecutor
的代码,它有一个Runnable队列mTasks ,不断的接受Runnable对象,并通过poll操作,每次取出顶部的Runnable进行执行。结合创建的单一线程池,便实现了我需要的简易、轻量的下载器。