Android之 多线程下载、断点续传 实现
阅读原文时间:2021年04月20日阅读:2

这里使用的是github上的一个开源项目 DownloadProvider 下载地址:https://github.com/yxl/DownloadProvider,支持多线程下载和断点续传功能,自己在它上面作了一些修改以满足业务需求。

先看效果图吧:

通知栏下载进度 提示

工程目录结构如下:

清单文件

[java]  view plain copy

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"

  2. package="com.example.downloadtest"

  3. android:versionCode="1"

  4. android:versionName="1.0" >

  5. <uses-sdk

  6. android:minSdkVersion="8"

  7. android:targetSdkVersion="17" />

  8. <permission

  9. android:name="com.example.downloadtest.permission.ACCESS_DOWNLOAD_MANAGER"

  10. android:description="@string/permdesc_downloadManager"

  11. android:label="@string/permlab_downloadManager"

  12. android:protectionLevel="normal" />

  13. <permission

  14. android:name="com.example.downloadtest.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"

  15. android:description="@string/permdesc_downloadManagerAdvanced"

  16. android:label="@string/permlab_downloadManagerAdvanced"

  17. android:protectionLevel="normal" />

  18. <permission

  19. android:name="com.example.downloadtest.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"

  20. android:description="@string/permdesc_downloadCompletedIntent"

  21. android:label="@string/permlab_downloadCompletedIntent"

  22. android:protectionLevel="normal" />

  23. <application

  24. android:allowBackup="true"

  25. android:icon="@drawable/ic_launcher"

  26. android:label="@string/app_name"

  27. android:theme="@style/AppTheme" android:name=".app.MyApplication">

  28. <activity

  29. android:name="com.example.downloadtest.MainActivity"

  30. android:label="@string/app_name" >

  31. <provider

  32. android:name="com.example.downloadtest.providers.downloads.DownloadProvider"

  33. android:authorities="com.example.downloadtest.downloads" android:exported="false"/>

  34. <receiver

  35. android:name="com.example.downloadtest.providers.downloads.DownloadReceiver"

  36. android:exported="false" >

界面布局文件 activity_main.xml

[html]  view plain copy

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

  2. xmlns:tools="http://schemas.android.com/tools"

  3. android:layout_width="match_parent"

  4. android:layout_height="match_parent"

  5. tools:context=".MainActivity" >

  6. <ListView

  7. android:id="@+id/mListView"

  8. android:layout_width="match_parent"

  9. android:layout_height="match_parent"

  10. android:cacheColorHint="@android:color/transparent"/>

  11. </RelativeLayout>

MainActivity.java

[java]  view plain copy

  1. package com.example.downloadtest;

  2. import java.util.ArrayList;

  3. import java.util.List;

  4. import android.app.Activity;

  5. import android.content.Context;

  6. import android.database.ContentObserver;

  7. import android.database.Cursor;

  8. import android.net.Uri;

  9. import android.os.Bundle;

  10. import android.os.Environment;

  11. import android.os.Handler;

  12. import android.util.Log;

  13. import android.view.LayoutInflater;

  14. import android.view.View;

  15. import android.view.View.OnClickListener;

  16. import android.view.ViewGroup;

  17. import android.widget.BaseAdapter;

  18. import android.widget.Button;

  19. import android.widget.ListView;

  20. import android.widget.ProgressBar;

  21. import android.widget.TextView;

  22. import android.widget.Toast;

  23. import com.example.downloadtest.app.MyApplication;

  24. import com.example.downloadtest.entity.AppInfo;

  25. import com.example.downloadtest.entity.DownloadItem;

  26. import com.example.downloadtest.providers.DownloadManager;

  27. import com.example.downloadtest.providers.DownloadManager.Request;

  28. import com.example.downloadtest.providers.downloads.Downloads;

  29. public class MainActivity extends Activity {

  30. public static final String TAG = MainActivity.class.getSimpleName();

  31. private static final int QUERY_DOWNLOAD_PROGRESS = 10;

  32. private static final String DOWNLOAD_DIR_NAME = "test_download";

  33. private DownloadManager mDownloadManager;

  34. private ListView mListView;

  35. private MyContentObserver mContentObserver = new MyContentObserver();

  36. private List downloadList = new ArrayList();

  37. private DownloadAdapter mAdapter;

  38. private MyApplication mApp;

  39. private Handler handler = new Handler() {

  40. public void handleMessage(android.os.Message msg) {

  41. switch (msg.what) {

  42. case QUERY_DOWNLOAD_PROGRESS:

  43. if (mAdapter != null) {

  44. mAdapter.notifyDataSetChanged();

  45. }

  46. break;

  47. default:

  48. break;

  49. }

  50. };

  51. };

  52. @Override

  53. protected void onCreate(Bundle savedInstanceState) {

  54. super.onCreate(savedInstanceState);

  55. setContentView(R.layout.activity_main);

  56. mDownloadManager = new DownloadManager(getContentResolver(),

  57. getPackageName());

  58. mApp = (MyApplication) getApplication();

  59. initDownloadData();

  60. findView();

  61. handleDownloadsChanged(); // 默认初始化

  62. }

  63. @Override

  64. protected void onStart() {

  65. getContentResolver().registerContentObserver(Downloads.CONTENT_URI,

  66. true, mContentObserver);

  67. super.onStart();

  68. }

  69. @Override

  70. protected void onStop() {

  71. getContentResolver().unregisterContentObserver(mContentObserver);

  72. super.onStop();

  73. }

  74. public void handleDownloadsChanged() {

  75. DownloadManager.Query baseQuery = new DownloadManager.Query()

  76. .setOnlyIncludeVisibleInDownloadsUi(true);

  77. Cursor cursor = mDownloadManager.query(baseQuery);

  78. int mIdColumnId = cursor

  79. .getColumnIndexOrThrow(DownloadManager.COLUMN_ID);

  80. int mStatusColumnId = cursor

  81. .getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS);

  82. int mTotalBytesColumnId = cursor

  83. .getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);

  84. int mCurrentBytesColumnId = cursor

  85. .getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);

  86. while (cursor.moveToNext()) {

  87. long downloadId = cursor.getLong(mIdColumnId);

  88. long totalBytes = cursor.getLong(mTotalBytesColumnId);

  89. long currentBytes = cursor.getLong(mCurrentBytesColumnId);

  90. int status = cursor.getInt(mStatusColumnId);

  91. int progress = getProgressValue(totalBytes, currentBytes);

  92. DownloadItem di = mApp.downloadMap

  93. .get(mApp.mapping.get(downloadId));

  94. if (di != null) {

  95. di.setDownloadId(downloadId);

  96. di.setTotalBytes(totalBytes);

  97. di.setCurrentBytes(currentBytes);

  98. di.setStatus(status);

  99. di.setProgress(progress);

  100. }

  101. }

  102. cursor.close();

  103. handler.sendEmptyMessage(QUERY_DOWNLOAD_PROGRESS);

  104. }

  105. public int getProgressValue(long totalBytes, long currentBytes) {

  106. if (totalBytes == -1) {

  107. return 0;

  108. }

  109. return (int) (currentBytes * 100 / totalBytes);

  110. }

  111. private void findView() {

  112. mListView = (ListView) findViewById(R.id.mListView);

  113. mAdapter = new DownloadAdapter(getApplicationContext());

  114. mListView.setAdapter(mAdapter);

  115. }

  116. private void initDownloadData() {

  117. String[] names = { "易信", "百度地图", "天天动听", "网易新闻", "电话帮", "墨迹天气", "生活日历",

  118. "豆果美食" };

  119. String[] urls = {

  120. "http://gdown.baidu.com/data/wisegame/653346a13ab69081/yixin_146.apk",

  121. "http://gdown.baidu.com/data/wisegame/824ed743d6cdbbb6/baiduditu_431.apk",

  122. "http://gdown.baidu.com/data/wisegame/839ca8c2da93e9e8/tiantiandongting_5902.apk",

  123. "http://gdown.baidu.com/data/wisegame/8cafd2fb933d5c09/NetEaseNews_273.apk",

  124. "http://www.yulore.com/go/?id=30",

  125. "http://gdown.baidu.com/data/wisegame/2fdcb550af198691/mojitianqi_24402.apk",

  126. "http://gdown.baidu.com/data/wisegame/0a3e9a0575a77fe2/shenghuorili_19.apk",

  127. "http://gdown.baidu.com/data/wisegame/e61ab2327372efdf/DouguoRecipe_80.apk" };

  128. String[] infos = { "32万人安装 . 16.0MB", "4182万人安装 . 10.5MB",

  129. "4493万人安装 . 7.9MB", "1148万人安装 . 6.1MB", "21万人安装 . 3.1MB",

  130. "8000万人安装 . 8.1MB", "100万人安装 . 6.1MB", "500万人安装 . 8.6MB" };

  131. for (int i = 0; i < names.length; i++) {

  132. String name = names[i];

  133. String url = urls[i];

  134. AppInfo info = new AppInfo();

  135. info.setId("1000" + i);

  136. info.setName(name);

  137. info.setUrl(url);

  138. info.setInfo(infos[i]);

  139. downloadList.add(info);

  140. }

  141. }

  142. private class MyContentObserver extends ContentObserver {

  143. public MyContentObserver() {

  144. super(new Handler());

  145. }

  146. @Override

  147. public void onChange(boolean selfChange) {

  148. handleDownloadsChanged();

  149. }

  150. }

  151. private long startDownload(AppInfo ai) {

  152. //判断SD卡是否可用

  153. if(!checkSDCardIsAvailable()){

  154. Toast.makeText(getApplicationContext(), "外部存储空间不可用,请检查后再试", Toast.LENGTH_LONG).show();

  155. return -1;

  156. }

  157. try {

  158. String url = ai.getUrl();

  159. Log.e(TAG, "download url:" + url);

  160. Uri srcUri = Uri.parse(url);

  161. DownloadManager.Request request = new Request(srcUri);

  162. // 设置文件保存的路径

  163. //          request.setDestinationInExternalDir(getApplicationContext(),

  164. //                  DOWNLOAD_DIR_NAME, ai.getName() + ".apk");

  165. request.setDestinationInExternalFilesDir(getApplicationContext(), DOWNLOAD_DIR_NAME, "");

  166. request.setDescription("下载");

  167. return mDownloadManager.enqueue(request);

  168. } catch (Exception e) {

  169. e.printStackTrace();

  170. }

  171. return -1;

  172. }

  173. /**

  174. * 判断 SDCard是否可用

  175. *

  176. * @return

  177. */

  178. public boolean checkSDCardIsAvailable() {

  179. if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))

  180. return true;

  181. else

  182. return false;

  183. }

  184. private class DownloadAdapter extends BaseAdapter {

  185. private Context ctx;

  186. private LayoutInflater inflater;

  187. public DownloadAdapter(Context ctx) {

  188. this.ctx = ctx;

  189. inflater = LayoutInflater.from(ctx);

  190. }

  191. @Override

  192. public int getCount() {

  193. return downloadList.size();

  194. }

  195. @Override

  196. public Object getItem(int position) {

  197. // TODO Auto-generated method stub

  198. return downloadList.get(position);

  199. }

  200. @Override

  201. public long getItemId(int position) {

  202. // TODO Auto-generated method stub

  203. return position;

  204. }

  205. @Override

  206. public View getView(final int position, View convertView,

  207. ViewGroup parent) {

  208. View view;

  209. ViewHolder holder;

  210. if (convertView == null) {

  211. view = inflater.inflate(R.layout.download_item, null);

  212. holder = new ViewHolder();

  213. holder.tv_app_name = (TextView) view

  214. .findViewById(R.id.tv_app_name);

  215. holder.tv_app_info = (TextView) view

  216. .findViewById(R.id.tv_app_info);

  217. holder.tv_progress_percent = (TextView) view

  218. .findViewById(R.id.tv_progress_percent);

  219. holder.bt_app_download = (Button) view.findViewById(R.id.bt_app_download);

  220. holder.pb_download_progress = (ProgressBar) view

  221. .findViewById(R.id.pb_download_progress);

  222. view.setTag(holder);

  223. } else {

  224. view = convertView;

  225. holder = (ViewHolder) view.getTag();

  226. }

  227. AppInfo ai = downloadList.get(position);

  228. holder.tv_app_name.setText(ai.getName());

  229. holder.tv_app_info.setText(ai.getInfo());

  230. holder.bt_app_download.setText(R.string.download);

  231. final DownloadItem di = mApp.downloadMap.get(ai.getId());

  232. if (di != null) {

  233. int status = di.getStatus();

  234. switch (status) {

  235. case DownloadManager.STATUS_FAILED:

  236. Toast.makeText(ctx,

  237. "下载 " + ai.getName() + " 失败,请检查网络之后重试",

  238. Toast.LENGTH_LONG).show();

  239. holder.pb_download_progress.setVisibility(View.GONE);

  240. holder.tv_progress_percent.setVisibility(View.GONE);

  241. mApp.mapping.remove(di.getDownloadId());// 删除

  242. mApp.downloadMap.remove(ai.getId());// 删除

  243. // mDownloadManager.remove(downloadId);

  244. mDownloadManager.markRowDeleted(di.getDownloadId()); // 删除该下载

  245. holder.bt_app_download.setText(R.string.failed);

  246. holder.tv_app_info.setVisibility(View.VISIBLE);

  247. break;

  248. case DownloadManager.STATUS_SUCCESSFUL:

  249. Log.i(TAG, "download complete " + di.getDownloadId());

  250. holder.pb_download_progress.setVisibility(View.GONE);

  251. holder.tv_progress_percent.setVisibility(View.GONE);

  252. mApp.mapping.remove(di.getDownloadId());// 删除

  253. mApp.downloadMap.remove(ai.getId());// 删除

  254. holder.bt_app_download.setText(R.string.complete);

  255. holder.tv_app_info.setVisibility(View.VISIBLE);

  256. break;

  257. case DownloadManager.STATUS_PENDING:

  258. holder.tv_progress_percent.setVisibility(View.VISIBLE);

  259. holder.pb_download_progress.setVisibility(View.VISIBLE);

  260. holder.tv_app_info.setVisibility(View.GONE);

  261. holder.tv_progress_percent.setText(R.string.pending);

  262. holder.bt_app_download.setText(R.string.cancel);

  263. break;

  264. case DownloadManager.STATUS_RUNNING:

  265. holder.tv_progress_percent.setVisibility(View.VISIBLE);

  266. holder.pb_download_progress.setVisibility(View.VISIBLE);

  267. holder.tv_app_info.setVisibility(View.GONE);

  268. int progress = di.getProgress();

  269. holder.pb_download_progress.setProgress(progress);

  270. holder.tv_progress_percent.setText(progress + "%");

  271. break;

  272. case DownloadManager.STATUS_PAUSED:

  273. holder.tv_progress_percent.setVisibility(View.VISIBLE);

  274. holder.pb_download_progress.setVisibility(View.VISIBLE);

  275. holder.tv_app_info.setVisibility(View.GONE);

  276. holder.bt_app_download.setText(R.string.resume);

  277. break;

  278. default:

  279. break;

  280. }

  281. } else {

  282. holder.tv_progress_percent.setVisibility(View.GONE);

  283. holder.pb_download_progress.setVisibility(View.GONE);

  284. holder.tv_app_info.setVisibility(View.VISIBLE);

  285. }

  286. holder.bt_app_download.setOnClickListener(new OnClickListener() {

  287. @Override

  288. public void onClick(View v) {

  289. Button bt = (Button) v;

  290. AppInfo info = downloadList.get(position);

  291. DownloadItem di = mApp.downloadMap.get(info.getId());

  292. String str = bt.getText().toString();

  293. if ("下载".equals(str)) {

  294. long downloadId = startDownload(info);

  295. if (downloadId != -1) {

  296. DownloadItem downloadItem = new DownloadItem();

  297. downloadItem.setDownloadId(downloadId);

  298. downloadItem.setId(info.getId());

  299. mApp.downloadMap.put(info.getId(), downloadItem);

  300. mApp.mapping.put(downloadId, info.getId());

  301. }

  302. } else if ("暂停".equals(str)) {

  303. if (di != null && (di.getStatus() == DownloadManager.STATUS_RUNNING)) {

  304. mDownloadManager.pauseDownload(di.getDownloadId());

  305. }

  306. } else if ("继续".equals(str)) {

  307. if (di != null && (di.getStatus() == DownloadManager.STATUS_PAUSED)) {

  308. mDownloadManager.resumeDownload(di.getDownloadId());

  309. }

  310. } else if ("取消".equals(str)) {

  311. if (di != null && (di.getStatus() == DownloadManager.STATUS_PENDING)) {

  312. mDownloadManager.markRowDeleted(di.getDownloadId());

  313. }

  314. }

  315. notifyDataSetChanged(); // 刷新

  316. }

  317. });

  318. return view;

  319. }

  320. }

  321. static class ViewHolder {

  322. public TextView tv_app_name;

  323. public TextView tv_app_info;

  324. public TextView tv_progress_percent;

  325. public Button bt_app_download;

  326. public ProgressBar pb_download_progress;

  327. }

  328. }

MyApplication.java

[java]  view plain copy

  1. package com.example.downloadtest.app;

  2. import java.util.HashMap;

  3. import java.util.Map;

  4. import android.app.Application;

  5. import android.content.Intent;

  6. import com.example.downloadtest.entity.DownloadItem;

  7. import com.example.downloadtest.providers.downloads.DownloadService;

  8. public class MyApplication extends Application {

  9. public Map downloadMap = new HashMap(); //key AppInfo id,value 下载进度信息

  10. public Map mapping = new HashMap(); //key downloadId,value AppInfo id

  11. @Override

  12. public void onCreate() {

  13. startDownloadService();

  14. super.onCreate();

  15. }

  16. private void startDownloadService() {

  17. Intent intent = new Intent();

  18. intent.setClass(this, DownloadService.class);

  19. startService(intent);

  20. }

  21. }

工程下载地址:http://download.csdn.net/detail/fx_sky/6419647