服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android实现断点多线程下载

Android实现断点多线程下载

2022-09-09 15:01热木星 Android

这篇文章主要为大家详细介绍了Android实现断点多线程下载,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

断点多线程下载的几个关键点:①:得到要下载的文件大小后,均分给几个线程。②:使用RandomAccessFile类进行读写,可以指定开始写入的位置。③:数据库保存下载信息,下一次继续下载的时候从数据库取出数据,然后从上次下载结束的地方开始。

这里我使用了FinalDb的数据库框架,同时在内存中存储了一份所有线程的下载信息,负责时时更新和查询下载进度。我测试用的是百度云网盘,文件大小50M左右。注意,线程中指定了开始结束下载位置的网络请求成功的返回码是206,并不是200。

效果图:

Android实现断点多线程下载

线程类:线程类负责具体的下载,线程的下载信息被存储到了数据库。线程开始下载时,根据线程ID查询自己的存储信息,然后开始从指定的位置下载和写入文件。完毕后根据自己的当前下载结果设置自己当前的下载状态。时时的下载进度存储只存储到了内存,只在本次下载结束才存储到数据库。

 

?
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
public class DownloadThread extends Thread {
 
  /**
   * 数据库操作工具
   */
  private FinalDb finalDb;
 
  /**
   * 下载状态:未开始
   */
  public static final int STATE_READY = 1;
 
  /**
   * 下载状态:下载中
   */
  public static final int STATE_LOADING = 2;
 
  /**
   * 下载状态:下载暂停中
   */
  public static final int STATE_PAUSING = 3;
 
  /**
   * 下载状态:下载完成
   */
  public static final int STATE_FINISH = 4;
 
  /**
   * 下载状态
   */
  public int downloadState;
 
  /**
   * 线程ID
   */
  private int threadID;
 
  /**
   * 要下载的URL路径
   */
  private String url;
 
  /**
   * 本线程要下载的文件
   */
  public RandomAccessFile file;
 
  /**
   * 构造器
   */
  public DownloadThread(Context context, int threadID, String downloadUrl, RandomAccessFile randomAccessFile) {
    this.threadID = threadID;
    this.url = downloadUrl;
    this.file = randomAccessFile;
    finalDb = DBUtil.getFinalDb(context);
  }
 
  @Override
  public void run() {
    //数据库查询本线程下载进度
    List<ThreadDownloadInfoBean> list = finalDb.findAllByWhere(ThreadDownloadInfoBean.class, "threadID='" + threadID + "'");
    //下载信息存放到内存
    if (list.get(0) != null) {
      MapUtil.map.put(threadID, list.get(0));
    }
    //取出实体类
    ThreadDownloadInfoBean bean = MapUtil.map.get(threadID);
    Utils.Print("bean:" + bean.toString());
    InputStream is;
    HttpURLConnection conn;
    try {
      Utils.Print("线程" + threadID + "开始连接");
      conn = (HttpURLConnection) new URL(url).openConnection();
      conn.setConnectTimeout(5000);
      conn.setReadTimeout(5000);
      conn.setRequestMethod("GET");
      //设置下载开始和结束的位置
      conn.setRequestProperty("Range", "bytes=" + (bean.startDownloadPosition + bean.downloadedSize) + "-" + bean.endDownloadPosition);
      conn.connect();
      if (conn.getResponseCode() == 206) {
        //更改下载状态
        downloadState = STATE_LOADING;
        bean.downloadState = STATE_LOADING;
        Utils.Print("线程" + threadID + "连接成功");
        is = conn.getInputStream();
        // 1K的数据缓冲
        byte[] bs = new byte[1024];
        // 读取到的数据长度
        int len;
        //从指定的位置开始下载
        file.seek(bean.startDownloadPosition);
        // 循环读取,当已经下载的大小达到了指定的本线程负责的大小时跳出循环,线程之间负责的文件首尾有重合的话没有影响,因为写入的内容时相同的
        while ((len = is.read(bs)) != -1) {
          //不用在这个循环里面更新数据库
          file.write(bs, 0, len);
          //时时更新内存中的已下载大小信息
          bean.downloadedSize += len;
          //如果调用者暂停下载,则跳出结束方法
          if (downloadState == STATE_PAUSING) {
            Utils.Print("线程" + threadID + "暂停下载");
            break;
          }
        }
        is.close();
        file.close();
      } else {
        Utils.Print("线程" + threadID + "连接失败");
      }
      conn.disconnect();
      //如果这个线程已经下载完了自己负责的部分就修改下载状态
      if (bean.downloadedSize >= bean.downloadTotalSize) {
        bean.downloadState = STATE_FINISH;
      } else {
        bean.downloadState = STATE_PAUSING;
      }
      //内存中信息更新至数据库
      finalDb.update(bean, "threadID='" + bean.threadID + "'");
    } catch (IOException e) {
      Utils.Print("线程" + threadID + "IO异常");
      e.printStackTrace();
    }
  }
}

线程信息的封装类:负责存储每个线程的ID,开始下载的位置,结束下载的位置,已经下载的大小,下载状态等;这个类用FinalDb数据库存储到数据库,一定要写get,set方法和空参构造器

?
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
@Table(name = "ThreadInfo")
public class ThreadDownloadInfoBean {
 
  /**
   * id
   */
  public int id;
 
  /**
   * 线程ID
   */
  public int threadID;
 
  /**
   * 本线程时时下载开始的位置
   */
  public long startDownloadPosition;
 
  /**
   * 本线程时时下载结束的位置
   */
  public long endDownloadPosition;
 
  /**
   * 本线程负责下载的文件大小
   */
  public long downloadTotalSize;
 
  /**
   * 已经下载了的文件大小
   */
  public long downloadedSize;
 
  /**
   * 本线程的下载状态
   */
  public int downloadState;
 
  public ThreadDownloadInfoBean() {
 
  }
 
  public ThreadDownloadInfoBean(int downloadState, long downloadedSize, long downloadTotalSize, long endDownloadPosition, long startDownloadPosition, int threadID) {
    this.downloadState = downloadState;
    this.downloadedSize = downloadedSize;
    this.downloadTotalSize = downloadTotalSize;
    this.endDownloadPosition = endDownloadPosition;
    this.startDownloadPosition = startDownloadPosition;
    this.threadID = threadID;
  }
 
  public int getId() {
    return id;
  }
 
  public void setId(int id) {
    this.id = id;
  }
 
  public int getThreadID() {
    return threadID;
  }
 
  public void setThreadID(int threadID) {
    this.threadID = threadID;
  }
 
  public long getStartDownloadPosition() {
    return startDownloadPosition;
  }
 
  public void setStartDownloadPosition(long startDownloadPosition) {
    this.startDownloadPosition = startDownloadPosition;
  }
 
  public long getEndDownloadPosition() {
    return endDownloadPosition;
  }
 
  public void setEndDownloadPosition(long endDownloadPosition) {
    this.endDownloadPosition = endDownloadPosition;
  }
 
  public long getDownloadTotalSize() {
    return downloadTotalSize;
  }
 
  public void setDownloadTotalSize(long downloadTotalSize) {
    this.downloadTotalSize = downloadTotalSize;
  }
 
  public long getDownloadedSize() {
    return downloadedSize;
  }
 
  public void setDownloadedSize(long downloadedSize) {
    this.downloadedSize = downloadedSize;
  }
 
  public int getDownloadState() {
    return downloadState;
  }
 
  public void setDownloadState(int downloadState) {
    this.downloadState = downloadState;
  }
 
  @Override
  public String toString() {
    return "ThreadDownloadInfoBean{" +
        "id=" + id +
        ", threadID=" + threadID +
        ", startDownloadPosition=" + startDownloadPosition +
        ", endDownloadPosition=" + endDownloadPosition +
        ", downloadTotalSize=" + downloadTotalSize +
        ", downloadedSize=" + downloadedSize +
        ", downloadState=" + downloadState +
        '}';
  }
}

下载工具类:这个类负责得到下载文件大小,分配线程下载大小,管理下载线程

 

?
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
public class DownUtil {
 
  /**
   * 数据库操作工具
   */
  public FinalDb finalDb;
 
  /**
   * 下载状态:准备好
   */
  public static final int STATE_READY = 1;
 
  /**
   * 下载状态:下载中
   */
  public static final int STATE_LOADING = 2;
 
  /**
   * 下载状态:暂停中
   */
  public static final int STATE_PAUSING = 3;
 
  /**
   * 下载状态:下载完成
   */
  public static final int STATE_FINISH = 4;
 
  /**
   * 下载状态
   */
  public int downloadState;
 
  /**
   * context
   */
  private Context context;
 
  /**
   * 要下载文件的大小
   */
  public long fileSize;
 
  /**
   * 线程集合
   */
  private ArrayList<DownloadThread> threadList = new ArrayList<>();
 
  /**
   * 构造器
   */
  public DownUtil(Context context) {
    this.context = context;
    finalDb = DBUtil.getFinalDb(context);
    judgeDownState();
  }
 
  /**
   * 初始化时判断下载状态
   */
  public void judgeDownState() {
    //取出数据库中的下载信息,存储到内存中
    List<ThreadDownloadInfoBean> list = finalDb.findAll(ThreadDownloadInfoBean.class);
    if (list != null && list.size() == DownloadActivity.threadNum) {
      for (int i = 0; i < list.size(); i++) {
        MapUtil.map.put(i, list.get(i));
      }
    }
    //查询SP中是否存储过要下载的文件大小
    Long spFileSize = SPUtil.getInstance(context).getLong(DownloadActivity.fileName, 0L);
    long downloadedSize = getFinishedSize();
    //SP中或者数据库中没有查询到说明从没有进行过下载
    if (spFileSize == 0 || downloadedSize == 0) {
      downloadState = STATE_READY;
    } else if (downloadedSize >= spFileSize) {
      downloadState = STATE_FINISH;
    } else {
      downloadState = STATE_PAUSING;
      fileSize = spFileSize;
    }
  }
 
  /**
   * 点击了开始按钮
   */
  public void clickDownloadBtn() {
    if (downloadState == STATE_READY) {
      startDownload();
    } else if (downloadState == STATE_PAUSING) {
      continueDownload();
    }
  }
 
  /**
   * 进入应用第一次开始下载
   */
  private void startDownload() {
    //开启新线程,得到要下载的文件大小
    new Thread() {
      @Override
      public void run() {
        try {
          HttpURLConnection conn;
          conn = (HttpURLConnection) new URL(DownloadActivity.url).openConnection();
          conn.setConnectTimeout(5000);
          conn.setReadTimeout(5000);
          conn.setRequestMethod("GET");
          conn.connect();
          if (conn.getResponseCode() == 200) {
            Utils.Print("DownUtil连接成功");
            //得到要下载的文件大小
            fileSize = conn.getContentLength();
            //得到文件名后缀名
            String contentDisposition = new String(conn.getHeaderField("Content-Disposition").getBytes("ISO-8859-1"), "UTF-8");
            String fileName = "下载测试" + contentDisposition.substring(contentDisposition.lastIndexOf("."), contentDisposition.lastIndexOf("\""));
            //得到存储路径
            String sdCardPath = context.getExternalFilesDir(null).getPath();
            DownloadActivity.fileName = sdCardPath + "/" + fileName;
            SPUtil.getInstance(context).saveString(DownloadActivity.FILE_NAME, DownloadActivity.fileName);
            SPUtil.getInstance(context).saveLong(DownloadActivity.fileName, fileSize);
            /*
             * 计算一下每个线程需要分担的下载文件大小 比如 总下载量为100 一共有三个线程
             * 那么 线程1负责0-32,线程2负责33-65,线程3负责66-99和100,
             * 也就是说下载总量除以线程数如果有余数,那么最后一个线程多下载一个余数部分
             */
            //每个线程均分的大小
            long threadDownSize = fileSize / DownloadActivity.threadNum;
            //线程均分完毕剩余的大小
            long leftDownSize = fileSize % DownloadActivity.threadNum;
            //创建要写入的文件
            RandomAccessFile file = new RandomAccessFile(DownloadActivity.fileName, "rw");
            //设置文件大小
            file.setLength(fileSize);
            //关闭
            file.close();
            for (int i = 0; i < DownloadActivity.threadNum; i++) {
              Utils.Print("开启线程" + i);
              //指定每个线程开始下载的位置
              long startPosition = i * threadDownSize;
              //指定每个线程负责下载的大小,当现场是集合里面最后一个线程的时候,它要增加leftDownSize的大小
              threadDownSize = i == DownloadActivity.threadNum - 1 ? threadDownSize + leftDownSize : threadDownSize;
              //存储線程信息
              ThreadDownloadInfoBean bean = new ThreadDownloadInfoBean(DownloadThread.STATE_READY, 0, threadDownSize, startPosition + threadDownSize, startPosition, i);
              finalDb.save(bean);
              RandomAccessFile threadFile = new RandomAccessFile(DownloadActivity.fileName, "rw");
              threadList.add(new DownloadThread(context, i, DownloadActivity.url, threadFile));
              threadList.get(i).start();
            }
            downloadState = STATE_LOADING;
            downloadInfoListener.connectSuccess();
          } else {
            Utils.Print("DownUtil-连接失败");
            downloadInfoListener.connectFail();
          }
          conn.disconnect();
        } catch (IOException e) {
          Utils.Print("DownUtil-IO异常");
          downloadInfoListener.IOException();
          e.printStackTrace();
        }
      }
    }.start();
  }
 
  /**
   * 继续下载
   */
  private void continueDownload() {
    List<ThreadDownloadInfoBean> list = finalDb.findAll(ThreadDownloadInfoBean.class);
    for (int i = 0; i < DownloadActivity.threadNum; i++) {
      //当前线程已经下载完了就不再开启
      if (list.get(i).downloadState != DownloadThread.STATE_FINISH) {
        Utils.Print("重新开启线程" + i);
        RandomAccessFile threadFile = null;
        try {
          threadFile = new RandomAccessFile(DownloadActivity.fileName, "rw");
        } catch (FileNotFoundException e) {
          e.printStackTrace();
        }
        DownloadThread downloadThread = new DownloadThread(context, i, DownloadActivity.url, threadFile);
        threadList.add(downloadThread);
        downloadThread.start();
      }
    }
    downloadState = STATE_LOADING;
    downloadInfoListener.connectSuccess();
  }
 
  /**
   * 点击了暂停的按钮
   */
  public void clickPauseBtn() {
    if (downloadState == STATE_LOADING) {
      stopDownload();
    }
  }
 
  /**
   * 暂停下载
   */
  private void stopDownload() {
    for (int i = 0; i < threadList.size(); i++) {
      if (threadList.get(i).downloadState == DownloadThread.STATE_LOADING) {
        threadList.get(i).downloadState = DownloadThread.STATE_PAUSING;
      }
    }
    downloadState = STATE_PAUSING;
    threadList.clear();
  }
 
  /**
   * 返回此刻所有线程完成的下载大小
   */
  public long getFinishedSize() {
    long totalSize = 0;
    for (int i = 0; i < DownloadActivity.threadNum; i++) {
      ThreadDownloadInfoBean bean = MapUtil.map.get(i);
      if (bean != null) {
        //如果该线程已经下载的部分大于分配给它的部分多余部分不予计算
        long addSize = bean.downloadedSize > bean.downloadTotalSize ? bean.downloadTotalSize : bean.downloadedSize;
        totalSize += addSize;
      }
    }
    return totalSize;
  }
 
  /**
   * 下载信息监听器
   */
  private DownloadInfoListener downloadInfoListener;
 
  /**
   * 下载信息监听器
   */
  public interface DownloadInfoListener {
 
    void connectSuccess();
 
    void connectFail();
 
    void IOException();
  }
 
  /**
   * 设置下载信息监听器
   */
  public void setDownloadInfoListener(DownloadInfoListener downloadInfoListener) {
    this.downloadInfoListener = downloadInfoListener;
  }
 
}

页面Activity:负责展示下载进度等信息,提供操作页面

?
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
public class DownloadActivity extends BaseActivity {
 
  /**
   * 下载地址输入框
   */
  private EditText et_download_url;
 
  /**
   * 确定输入地址的按钮
   */
  private Button btn_download_geturl;
 
  /**
   * 进度条
   */
  private NumberProgressView np_download;
 
  /**
   * 开始下载的按钮
   */
  private Button btn_download_start;
 
  /**
   * 暂停下载的按钮
   */
  private Button btn_download_pause;
 
  /**
   * 取消下载的按钮
   */
  private Button btn_download_cancel;
 
  /**
   * 文件信息显示
   */
  private TextView tv_download_file_info;
 
  /**
   * 下载速度显示
   */
  private TextView tv_download_speed;
 
  /**
   * 显示下载信息
   */
  private TextView tv_download_speed_info;
 
  /**
   * 每隔一段时间刷新下载进度显示
   */
  private final static int WHAT_INCREACE = 1;
 
  /**
   * 得到了文件名称
   */
  private final static int WHAT_GET_FILENAME = 2;
 
  /**
   * downUtil连接失败
   */
  private final static int WHAI_CONNECT_FAIL = 3;
 
  /**
   * downUtilIO异常
   */
  private final static int WHAT_IO_EXCEPTION = 4;
 
  /**
   * 下载工具
   */
  private DownUtil downUtil;
 
  /**
   * 需要开启的线程数量
   */
  public static final int threadNum = 5;
 
  /**
   * 存放文件路径名称的SP键名
   */
  public static final String FILE_NAME = "fileName";
 
  /**
   * 要下载的文件的url地址
   */
  public static String url = "";
  
  /**
   * 文件下载路径和文件名称
   */
  public static String fileName;
 
  /**
   * 上次统计已经完成下载的文件大小
   */
  private long lastFinishedSize;
 
  private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      switch (msg.what) {
        case WHAT_INCREACE:
          updateView();
          break;
        case WHAT_GET_FILENAME:
          tv_download_file_info.setText("下载路径:" + fileName);
          break;
        case WHAI_CONNECT_FAIL:
          tv_download_file_info.setText("连接失败");
          break;
        case WHAT_IO_EXCEPTION:
          tv_download_file_info.setText("IO异常");
          break;
      }
    }
  };
 
  /**
   * 更新视图
   */
  private void updateView() {
    //当前已经完成下载的文件大小
    long currentFinishedSize = downUtil.getFinishedSize();
    //要显示的下载信息的文字
    StringBuilder downloadInfo = new StringBuilder();
    tv_download_speed.setText("当前下载速度:" + formateSize(currentFinishedSize - lastFinishedSize) + "/s");
    //本次统计比上次统计增加了,更新视图,本次统计较上次统计没有变化,不做视图更新
    if (currentFinishedSize > lastFinishedSize) {
      lastFinishedSize = currentFinishedSize;
      downloadInfo.append("下载进度:" + formateSize(currentFinishedSize) + "/" + formateSize(downUtil.fileSize) + "\n");
      for (int i = 0; i < threadNum; i++) {
        ThreadDownloadInfoBean bean = MapUtil.map.get(i);
        if (bean.downloadTotalSize != 0) {
          downloadInfo.append("线程" + i + "下载进度:" + bean.downloadedSize * 100 / bean.downloadTotalSize + "%  " + formateSize(bean.downloadedSize) + "/" + formateSize(bean.downloadTotalSize) + "\n");
        }
      }
      np_download.setProgress((int) (currentFinishedSize * 100 / downUtil.fileSize));
      tv_download_speed_info.setText(downloadInfo);
      //下载完成后
      if (currentFinishedSize >= downUtil.fileSize) {
        tv_download_speed.setText("下载完毕");
        handler.removeMessages(WHAT_INCREACE);
        return;
      }
    }
    handler.sendEmptyMessageDelayed(WHAT_INCREACE, 1000);
  }
 
  /**
   * 返回子类要显示的布局 R.layout......
   */
  @Override
  protected int childView() {
    return R.layout.activity_download;
  }
 
  /**
   * 强制子类实现该抽象方法,初始化各自的View
   */
  @Override
  protected void findChildView() {
    et_download_url = (EditText) findViewById(R.id.et_download_url);
    btn_download_geturl = (Button) findViewById(R.id.btn_download_geturl);
    np_download = (NumberProgressView) findViewById(R.id.np_download);
    btn_download_start = (Button) findViewById(R.id.btn_download_start);
    btn_download_pause = (Button) findViewById(R.id.btn_download_pause);
    btn_download_cancel = (Button) findViewById(R.id.btn_download_cancel);
    tv_download_file_info = (TextView) findViewById(R.id.tv_download_file_info);
    tv_download_speed = (TextView) findViewById(R.id.tv_download_speed);
    tv_download_speed_info = (TextView) findViewById(R.id.tv_download_speed_info);
  }
 
  /**
   * 强制子类实现该抽象方法,初始化各自数据
   */
  @Override
  protected void initChildData() {
    downUtil = new DownUtil(this);
    lastFinishedSize = downUtil.getFinishedSize();
    StringBuilder downloadInfo = new StringBuilder();
    fileName = SPUtil.getInstance(this).getString(FILE_NAME, null);
    if (fileName != null) {
      downloadInfo.append("下载路径:" + fileName);
      if (downUtil.downloadState == DownUtil.STATE_FINISH) {
        np_download.setProgress(100);
      } else if (downUtil.downloadState == DownUtil.STATE_PAUSING) {
        np_download.setProgress((int) (downUtil.getFinishedSize() * 100 / downUtil.fileSize));
      }
      tv_download_file_info.setText(downloadInfo);
    }
  }
 
  /**
   * 强制子类实现该抽象方法,设置自己的监听器
   */
  @Override
  protected View[] setChildListener() {
    downUtil.setDownloadInfoListener(new DownUtil.DownloadInfoListener() {
 
      @Override
      public void connectSuccess() {
        //不能在此更新,需要到主线程刷新UI
        handler.sendEmptyMessage(WHAT_GET_FILENAME);
        handler.sendEmptyMessage(WHAT_INCREACE);
      }
 
      @Override
      public void connectFail() {
        handler.sendEmptyMessage(WHAI_CONNECT_FAIL);
      }
 
      @Override
      public void IOException() {
        handler.sendEmptyMessage(WHAT_IO_EXCEPTION);
      }
    });
    return new View[]{btn_download_start, btn_download_pause, btn_download_geturl, btn_download_cancel};
  }
 
  /**
   * 子类在这个方法里面实现自己View的点击监听事件的相应
   *
   * @param v 父类传递到子类的点击的View
   */
  @Override
  protected void clickChildView(View v) {
    switch (v.getId()) {
 
      case R.id.btn_download_start:
        downUtil.clickDownloadBtn();
        break;
 
      case R.id.btn_download_pause:
        downUtil.clickPauseBtn();
        handler.removeMessages(WHAT_INCREACE);
        break;
 
      case R.id.btn_download_cancel:
        //停止发送消息
        handler.removeMessages(WHAT_INCREACE);
        //暂停下载
        downUtil.clickPauseBtn();
        //重置状态
        downUtil.downloadState = DownUtil.STATE_READY;
        //统计下载的大小归零
        lastFinishedSize = 0;
        //重置文本信息
        tv_download_speed.setText("");
        tv_download_file_info.setText("");
        tv_download_speed_info.setText("");
        //进度条归零
        np_download.setProgress(0);
        //清空内存数据
        MapUtil.map.clear();
        //sp清理
        SPUtil.getInstance(this).removeAll();
        //清空数据库
        downUtil.finalDb.deleteAll(ThreadDownloadInfoBean.class);
        //删除文件
        if (fileName != null) {
          File file = new File(fileName);
          if (file.exists()) {
            boolean delete = file.delete();
            if (delete) {
              Utils.ToastS(this, "删除" + fileName + "成功");
            } else {
              Utils.ToastS(this, "删除失败");
            }
          } else {
            Utils.ToastS(this, "文件不存在");
          }
        } else {
          Utils.ToastS(this, "文件不存在");
        }
        break;
 
      case R.id.btn_download_geturl:
        String editTextUrl = et_download_url.getText().toString();
        if (editTextUrl.trim().equals("")) {
          Utils.ToastS(this, "请输入地址");
        } else {
          url = editTextUrl;
        }
        break;
    }
  }
 
  @Override
  protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    downUtil.clickPauseBtn();
    super.onDestroy();
  }
 
  /**
   * 格式化数据大小
   */
  private String formateSize(long size) {
    return Formatter.formatFileSize(this, size);
  }
 
}

布局

?
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context="com.example.testdemo.activity.DownloadActivity">
 
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
 
    <EditText
      android:id="@+id/et_download_url"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:hint="输入下载地址"/>
 
    <Button
      android:id="@+id/btn_download_geturl"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="确定"/>
 
  </LinearLayout>
 
  <com.example.testdemo.view.NumberProgressView
    android:id="@+id/np_download"
    android:layout_width="match_parent"
    android:layout_height="100dp">
  </com.example.testdemo.view.NumberProgressView>
 
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
 
    <Button
      android:id="@+id/btn_download_start"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="开始下载"/>
 
    <Button
      android:id="@+id/btn_download_pause"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="暂停下载"/>
 
    <Button
      android:id="@+id/btn_download_cancel"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="取消下载"/>
 
  </LinearLayout>
 
  <TextView
    android:id="@+id/tv_download_file_info"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
 
  <TextView
    android:id="@+id/tv_download_speed"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
 
  <TextView
    android:id="@+id/tv_download_speed_info"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
 
</LinearLayout>

下载Activity的父类

?
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
public abstract class BaseActivity extends Activity {
 
  /**
   * 点击监听器,子类也可以使用
   */
  protected BaseOnClickListener onClickListener = new BaseOnClickListener();
 
  /**
   * 在onCreate方法里面,找到视图,初始化数据,设置点击监听器
   */
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(childView());
    findView();
    initData();
    setListener();
  }
 
  /**
   * 返回子类要显示的布局 R.layout......
   */
  protected abstract int childView();
 
  /**
   * 找到需要的视图---网址:https://www.buzzingandroid.com/tools/android-layout-finder/
   */
  private void findView() {
    findChildView();
  }
 
  /**
   * 初始化数据
   */
  private void initData() {
    // 初始化子类的数据
    initChildData();
  }
 
  /**
   * 对需要设置监听 的视图设置监听
   */
  private void setListener() {
    // 子类实现setChildListene()方法,返回一个View数组,里面包含所有需要设置点击监听的View,然后进行For循环,对里面所有View设置监听器
    if (setChildListener() == null || setChildListener().length == 0) {
      return;
    }
    View[] viewArray = setChildListener();
    for (View view : viewArray) {
      view.setOnClickListener(onClickListener);
    }
  }
 
  /**
   * 自定义的点击监听类
   */
  protected class BaseOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
      clickChildView(v);
    }
  }
 
  /**
   * 强制子类实现该抽象方法,初始化各自的View
   */
  protected abstract void findChildView();
 
  /**
   * 强制子类实现该抽象方法,初始化各自数据
   */
  protected abstract void initChildData();
 
  /**
   * 强制子类实现该抽象方法,设置自己的监听器
   */
  protected abstract View[] setChildListener();
 
  /**
   * 子类在这个方法里面实现自己View的点击监听事件的相应
   *
   * @param v 父类传递到子类的点击的View
   */
 
  protected abstract void clickChildView(View v);
 
}

得到数据库的操作类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DBUtil {
 
  public static FinalDb getFinalDb(final Context context) {
 
    FinalDb finalDb = FinalDb.create(context, "REMUXING.db", false, 1, new FinalDb.DbUpdateListener() {
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVirsion, int newVirsion) {
 
      }
    });
    return finalDb;
  }
}

内存中存储下载信息的类

?
1
2
3
public class MapUtil {
  public static HashMap<Integer, ThreadDownloadInfoBean> map = new HashMap<>();
}

SP存储的工具类

?
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
public enum SPUtil {
 
  SPUTIL;
 
  public static final String NOTIFICATIONBAR_HEIGHT = "notificationbar_height";
 
  private static SharedPreferences sp;
 
  public static SPUtil getInstance(Context context) {
 
    if (sp == null) {
      sp = context.getSharedPreferences("REMUXING", Context.MODE_PRIVATE);
    }
    return SPUTIL;
  }
 
  public void saveLong(String key, Long value) {
    sp.edit().putLong(key, value).apply();
  }
 
  public Long getLong(String key, Long defValue) {
    return sp.getLong(key, defValue);
  }
 
  public void saveString(String key, String value) {
    sp.edit().putString(key, value).apply();
  }
 
  public String getString(String key, String defValue) {
    return sp.getString(key, defValue);
  }
 
  public void removeAll() {
    sp.edit().clear().apply();
  }
 
}

工具类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Utils {
 
  /**
   * 打印
   */
  public static void Print(String message) {
    Log.e("TAG", "-----   " + message + "   ------") ;
  }
 
  /**
   * 短吐司
   */
  public static void ToastS(Context context, String message) {
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
  }
}

进度条:这个可以忽略,用安卓原生的,代码看我的另一篇博客:Android自定义View实现水平带数字百分比进度条

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_27102463/article/details/51612999

延伸 · 阅读

精彩推荐