目录

前言

一、介绍

二、使用事项

三、Request类注意事项

四、Query 类

五、下载完成监听

六、原理简析

七、小结


前言

在日常APP的开发中,通常情况下无可避免的要与调用网络后台数据接口。关于Android 网络请求接口的方式可以点击此此处进行学习。当我们实现一个从网络下载文件的功能时候,一般设计思路是这样的:使用Http发起请求,在IntentService的线程进行中下载,再配合Handler更新UI显示保持和用户交互。那么看了本篇就不需要那么麻烦了,因为Google因为帮我们封装好了一个方便下载的API叫做Downloadmanager。下面会先详细介绍实际使用方式,最后将这个API的实现原理。


本案例下载地址

https://download.csdn.net/download/csdn_aiyang/10906816


一、介绍

DownloadManager是android2.3(API 9)以后系统提供下载的方法,是处理长期运行的HTTP下载的系统服务。客户端可以请求的URI被下载到一个特定的目标文件。客户端将会在后台与http交互进行下载,或者在下载失败,或者连接改变,重新启动系统后重新下载。还可以进入系统的下载管理界面查看进度。DownloadManger有两个内部类:Request 和Query。Request类可设置下载的一些属性;Query类可查询当前下载的进度等信息。三个公共方法:enqueue、query和remove。enqueue在队列中插入一个新的下载。当连接正常,并且DownloadManager准备执行这个请求时,开始自动下载。返回结果是系统提供的唯一下载ID,这个ID可以用于与这个下载相关的回调。query公共方法,用于查询下载信息。remove公共方法,用于删除下载,如果下载中则取消下载。同时会删除下载文件和记录。

二、使用事项

首先,填写好所需权限:

<uses-permission android:name="android.permission.INTERNET" />;<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>;

创建对象,设置下载地址:

DownloadManager downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
long id = downloadManager.enqueue(request);//每下载的一个文件对应一个id,通过此id可以查询数据

取消下载:

该方法返回成功取消的下载的个数。如果一个下载被取消了,所有相关联的、部分下载的和完全下载的文件都会被删除。

downloadManager.remove(id);
int cancers = downloadManager.remove(id_1, id_2, id_3);  

小案例实现代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {private TextView down;private TextView progress;private TextView file_name;private ProgressBar pb_update;private DownloadManager downloadManager;private DownloadManager.Request request;public static String downloadUrl = "http://ucdl.25pp.com/fs08/2017/01/20/2/2_87a290b5f041a8b512f0bc51595f839a.apk";Timer timer;long id;TimerTask task;Handler handler =new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Bundle bundle = msg.getData();int pro = bundle.getInt("pro");String name  = bundle.getString("name");pb_update.setProgress(pro);progress.setText(String.valueOf(pro)+"%");file_name.setText(name);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);down = (TextView) findViewById(R.id.down);progress = (TextView) findViewById(R.id.progress);file_name = (TextView) findViewById(R.id.file_name);pb_update = (ProgressBar) findViewById(R.id.pb_update);down.setOnClickListener(this);//创建对象downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);request = new DownloadManager.Request(Uri.parse(downloadUrl));request.setTitle("大象投教");request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);request.setAllowedOverRoaming(false);request.setMimeType("application/vnd.android.package-archive");request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);//创建目录Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ;//设置文件存放路径request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS  , "app-release.apk" ) ;//手写更新UIpb_update.setMax(100);final  DownloadManager.Query query = new DownloadManager.Query();timer = new Timer();task = new TimerTask() {@Overridepublic void run() {Cursor cursor = downloadManager.query(query.setFilterById(id));if (cursor != null && cursor.moveToFirst()) {if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {pb_update.setProgress(100);install(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/app-release.apk" );task.cancel();}String title = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE));String address = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));int bytes_downloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));int pro =  (bytes_downloaded * 100) / bytes_total;Message msg =Message.obtain();Bundle bundle = new Bundle();bundle.putInt("pro",pro);bundle.putString("name",title);msg.setData(bundle);handler.sendMessage(msg);}cursor.close();}};timer.schedule(task, 0,1000);//        downloadManager.remove(id);}@Overridepublic void onClick(View v) {id = downloadManager.enqueue(request);task.run();down.setClickable(false);}private void install(String path) {Intent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive");intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//4.0以上系统弹出安装成功打开界面startActivity(intent);}
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:gravity="center"android:layout_height="match_parent"><TextViewandroid:id="@+id/file_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="20dp"android:text="dxtj.apk"/><ProgressBarandroid:id="@+id/pb_update"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="10dp"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:layout_gravity="center_horizontal"android:layout_marginTop="10dp"android:max="100"android:progress="0"android:progressDrawable="@drawable/progressbar_color"android:layout_marginBottom="20dp"/><TextViewandroid:id="@+id/progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:layout_marginBottom="20dp"android:text="0%"/><TextViewandroid:id="@+id/down"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:paddingTop="10dp"android:paddingBottom="10dp"android:paddingLeft="30dp"android:paddingRight="30dp"android:background="@color/colorAccent"android:text="立即下载"/>
</LinearLayout>

Progress_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" ><item android:id="@android:id/background"><shape><corners android:radius="10dip" /><gradientandroid:angle="0"android:centerColor="#e4e4e4"android:centerY="0.75"android:endColor="#e4e4e4"android:startColor="#e4e4e4" /></shape></item><item android:id="@android:id/secondaryProgress"><clip><shape><corners android:radius="10dip" /><gradientandroid:angle="0"android:centerColor="#e4e4e4"android:centerY="0.75"android:endColor="#e4e4e4"android:startColor="#e4e4e4" /></shape></clip></item><item android:id="@android:id/progress"><clip><shape><corners android:radius="10dip" /><gradientandroid:angle="0"android:endColor="@color/colorAccent"android:startColor="@color/colorAccent" /></shape></clip></item></layer-list>

三、Request类注意事项

 public static class Request {public static final int NETWORK_MOBILE = 1;public static final int NETWORK_WIFI = 2;public static final int VISIBILITY_HIDDEN = 2;public static final int VISIBILITY_VISIBLE = 0;public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3;
}

1、指定下载位置及文件名称:

        /*** 方法1: * 目录: Android -> data -> com.app -> files -> Download -> dxtj.apk* 这个文件是你的应用所专用的,软件卸载后,下载的文件将随着卸载全部被删除*/request.setDestinationInExternalFilesDir( this , Environment.DIRECTORY_DOWNLOADS ,  "dxtj.apk" );  /*** 方法2:* 下载的文件存放地址  SD卡 download文件夹,dxtj.apk* 软件卸载后,下载的文件会保留*///在SD卡上创建一个文件夹request.setDestinationInExternalPublicDir(  "/epmyg/"  , "dxtj.apk" ) ;  /*** 方法3:* 如果下载的文件希望被其他的应用共享* 特别是那些你下载下来希望被Media Scanner扫描到的文件(比如音乐文件)*/request.setDestinationInExternalPublicDir( Environment.DIRECTORY_MUSIC,  "告白气球.mp3" );  /*** 方法4* 文件将存放在外部存储的确实download文件内,如果无此文件夹,创建之,如果有,下面将返回false。* 系统有个下载文件夹,比如小米手机系统下载文件夹  SD卡--> Download文件夹*///创建目录Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ; //设置文件存放路径request.setDestinationInExternalPublicDir(  Environment.DIRECTORY_DOWNLOADS  , "dxtj.apk" ) ;

2、指定下载的网络类型:

//指定在WIFI状态下,执行下载操作。
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
//指定在MOBILE状态下,执行下载操作
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE);
//是否允许漫游状态下,执行下载操作
request.setAllowedOverRoaming(boolean);
//是否允许“计量式的网络连接”执行下载操作
request.setAllowedOverMetered(boolean); //默认是允许的。

3、定制Notification通知样式:

//设置Notification的标题和描述
request.setTitle("标题");
request.setDescription("描述");
//设置Notification的显示,和隐藏。
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);

VISIBILITY_VISIBLE:通知显示,只是在下载任务执行的过程中显示,下载完成自动消失。(默认值) 
 VISIBILTY_HIDDEN: 通知将不会显示,如果设置该属性的话,必须要添加权限

Android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 

VISIBILITY_VISIBLE_NOTIFY_COMPLETED : 通知显示,下载进行时,和完成之后都会显示。 
 VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION :下载完成时显示通知。

4、设置下载文件类型:

request.setMimeType("application/vnd.android.package-archive");

这是安卓.apk文件的类型。有些机型必须设置此方法,才能在下载完成后,点击通知栏的Notification时,才能正确的打开安装界面。不然会弹出一个Toast(can not open file)。其他文件类型的MimeType ,根据需求上网查一下吧 。如果设置了mimeType为application/cn.trinea.download.file,我们可以同时设置某个Activity的intent-filter为application/cn.trinea.download.file,用于响应点击的打开文件。

   <activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /><data android:mimeType="application/cn.trinea.download.file" /></intent-filter></activity>

5、添加请求下载的网络链接的http头,比如User-Agent,gzip压缩等:

request.addRequestHeader(String header, String value);

四、Query 类

Request类中封装了Notification ,简单设置属性就显示进度信息了。有时候需要在App中获取实时下载进度。而Query类就是提供查询的一些方法。 由于downloadManager将数据保存在数据库的,所以需要获得一个Cursor 结果集,通过结果集获得我们想要的数据。

DownloadManager.Query query = new DownloadManager.Query();Cursor cursor = downloadManager.query(query.setFilterById(id));if (cursor != null && cursor.moveToFirst()) {//下载的文件到本地的目录String address = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));//已经下载的字节数int bytes_downloaded =cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//总需下载的字节数int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));//Notification 标题String title =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE));//描述String description =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION));//下载对应idlong id =cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));//下载文件名称String filename =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));//下载文件的URL链接String url =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI));}

这只能获取一次,数据库中的信息。我们可以使用Timer类,每隔一段时间去查询数据库即可。也可以使用ContentProvider去访问:

    private static final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");private DownloadContentObserver observer = new DownloadContentObserver();@Overrideprotected void onResume() {super.onResume();getContentResolver().registerContentObserver(CONTENT_URI, true, observer);}@Overrideprotected void onDestroy() {super.onDestroy();getContentResolver().unregisterContentObserver(observer);}//ContentObserver 内部类监听下载进度class DownloadContentObserver extends ContentObserver {public DownloadContentObserver() {super(handler);}@Overridepublic void onChange(boolean selfChange) {updateView();//更新UI}}

五、下载完成监听

下载完成后,下载管理服务会发出DownloadManager.ACTION_DOWNLOAD_COMPLETE这个广播,并传递downloadId作为参数。通过接受广播我们可以打开对下载完成的内容进行操作。

private CompleteReceiver completeReceiver;class CompleteReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// get complete download idlong completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);// to do here}
};@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//...completeReceiver = new CompleteReceiver();//register download success broadcastregisterReceiver(completeReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}@Override
protected void onDestroy() {super.onDestroy();unregisterReceiver(completeReceiver);
}

六、原理简析

DownloadManager开始下载的入口enqueue方法,这个方法的源码如下:

public long enqueue(Request request) {ContentValues values = request.toContentValues(mPackageName);Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);long id = Long.parseLong(downloadUri.getLastPathSegment());return id;
}

使用的ContentProvider方式,“request.toContentValues()”,将Request信息转换为ContentValues类。

然后调用ContentResolver进行插入“mResolver.insert()”,调用对应的ContentProvider的insert方法。传入的参数,URI是Downloads.Impl.CONTENT_URI,即"content://downloads/my_downloads",找到对应系统提供的DownloadProvider

DownloadProvider类在系统源码的src/com/android/providers/downloads的路径下,找都其insert方法的实现,可以发现最后部分的代码:

public Uri insert(final Uri uri, final ContentValues values) {...// Always start service to handle notifications and/or scanningfinal Context context = getContext();context.startService(new Intent(context, DownloadService.class));return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}

即插入信息后,会启动DownloadService开始进行下载。(源码学习|Android N DownloadManager源码分析)

DownloadService的入口是onStartCommand方法,其中用mUpdateHandler发送消息MSG_UPDATE,mUpdateHandler处理消息的方式如下:

mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);private Handler.Callback mUpdateCallback = new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);...final boolean isActive;synchronized (mDownloads) {isActive = updateLocked();}...}
};private boolean updateLocked() {...// Kick off download task if readyfinal boolean activeDownload = info.startDownloadIfReady(mExecutor);...
}public boolean startDownloadIfReady(ExecutorService executor) {synchronized (this) {final boolean isReady = isReadyToDownload();final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();if (isReady && !isActive) {if (mStatus != Impl.STATUS_RUNNING) {mStatus = Impl.STATUS_RUNNING;ContentValues values = new ContentValues();values.put(Impl.COLUMN_STATUS, mStatus);mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);}//启动DownloadThread开始下载任务mTask = new DownloadThread(mContext, mSystemFacade, mNotifier, this);mSubmittedTask = executor.submit(mTask);}return isReady;}
}

从上面源码可以看,DownloadService的onStartCommand方法,最终启动DownloadThread,开始下载的任务(网络请求接口使用的是HttpURLConnection)。DownloadThread在下载过程中,会更新DownloadProvider。

综上所述,DownloadManager的enqueue方法的流程是:

DownloadProvider插入信息 >> 启动DownloadService >> 开始DownloadThread进行下载

七、小结

1、我发现,在下载的时候,发送Notification时 是没有声音的。也没有设置声音的方法。不过这影响不大。主要的功能实现就好。 
2、因为这是系统的类,每个系统的Notification界面是不一样的。这就是每个rom厂家的自定义了。小米和魅族的就大不一样。魅族Notification上有一个下载暂停的按钮,而小米没有。所以导致Notification是不能统一的。其实,暂停的话用户可以点击notification,进入到下载管理界面,就有暂停按钮了。

3、会出现被用户手动禁用了下载器出现崩溃情况。需要做好版本兼容和弹框让用户手动开启。


参考链接:https://www.jianshu.com/p/e0496200769c DownloadManager的使用和解析-简书

Android DownloadManager下载管理,app更新相关推荐

  1. [Android] DownloadManager下载管理类2.3新增API介绍

    从Android 2.3开始新增了一个下载管理类,在SDK的文档中我们查找android.app.DownloadManager可以看到.下载管理类可以长期处理多个HTTP下载任务,客户端只需要给出请 ...

  2. [Android]之一:Android系统下载管理DownloadManager

    嗷,这个android系统下载管理DownloadManager功能还是蛮强大的.虽然老大只是让我做一个下载工具类给他们使用,但是想加深一下印象,接下来是摘抄笔记,以后也要自己再看看不要又忘了. 一. ...

  3. Android系统下载管理DownloadManager

    转载: http://www.trinea.cn/android/android-downloadmanager/ http://www.trinea.cn/android/android-downl ...

  4. Android DownloadManager下载完成事件监听(系列4)

     Android DownloadManager下载完成事件监听(系列4) 我在之前写了写一些关于Android DownloadManager 的文章: (系列1)<Android大数据. ...

  5. Android社团活动设计界面图,基于Android的社团管理APP设计

    一.基本信息 标题:基于Android的社团管理APP设计 作者:张晏齐,田菲,杨洁,杨树媛 时间:2018 来源:中国知网 二.研究背景 随着互联网技术的飞速发展和智能手机的广泛应用,APP作为基于 ...

  6. Android DownloadManager下载状态查询(2)

     Android DownloadManager下载状态查询(2) 在我写的前一篇文章中, <Android大数据.断点续传.耗时下载之DownloadManager开发简介(1)> ...

  7. 基于Android的班级管理APP的设计与实现

    选题背景 在当代大学生的日常学习和生活中,手机已经成为必不可少的工具,这导致大学学习生活中的各种日常生活及学习事务需要通过手机作为媒介进行通知及处理.而目前市场上存在的各种社交软件(如QQ.微信等)并 ...

  8. 基于android的健身管理APP(ssm+uinapp+Mysql)

    基于android的健身管理APP(ssm+uinapp+Mysql) 管理员:管理员使用本系统涉到的功能主要有首页.个人中心.用户管理.健身课程管理.课程类型管理.健身圈子管理.系统管理等功能 用户 ...

  9. Android系统下载管理DownloadManager功能介绍及使用示例

    一.DownloadManager简单介绍 DownloadManager是系统开放给第三方应用使用的类,包含两个静态内部类DownloadManager.Query和DownloadManager. ...

最新文章

  1. 学习 TTreeView [15] - 连接数据库 (作为给 丁永其 和 DELPHI万岁 两位朋友的回复)...
  2. python hook技术,python hook监听事件详解
  3. php 调用redfish,RedfishWhitePaperRedfish白皮书.pdf
  4. 美丽又实用的欧拉螺线,数学界当之无愧的画家!
  5. 官方野生蝇群流口水分数
  6. Java并发编程(08):Executor线程池框架
  7. ggeditor推动_谁推动了特殊招聘的文化?
  8. 初识大数据(四. 大数据与人工智能的关系)
  9. Android支付实践(三)之银联支付功能(客户端+服务端)
  10. vue手机端回退_vue移动端弹框组件 - osc_eiolhkks的个人空间 - OSCHINA - 中文开源技术交流社区...
  11. 计算机旅游网站毕业论文,旅游网站任务书
  12. 项目变更管理:变更流程
  13. 【毕业设计】基于单片机的智能饮水控制系统 - 物联网 嵌入式 stm32 c51
  14. 从Spring源码探究IOC初始化流程
  15. VSTO之PPT幻灯片放映时间记录
  16. 毕业设计怎么快速搞定——工具开篇,无往不利!
  17. win10彻底删除Ubuntu EFI分区及启动项
  18. MediaPlayer简单使用
  19. 国外十大高校人工智能实验室及其代表性人物一览
  20. 不懂问题的总结与学习 在郑州遇到的 (mysql 问题,数组,链表。。。。等问题)

热门文章

  1. 统计建模:数据分析基础
  2. Windows10让资源管理器始终显示文件的详细信息
  3. 玩一玩Greasemonkey--开发上手篇
  4. 存储服务器系统本身密码忘了,云主机系统密码忘记
  5. Exp7 网络欺诈防范 Week10 - 20165201
  6. 强制删除桌面多余或恶意IE图标方法 :强制删除桌面多余或恶意IE图标方法:
  7. source insight macro
  8. C语言实现人机五子棋
  9. Java 中tag配置文件中的标签属性意思
  10. STM32F429 笔记(一)- Keil 创建HAL工程