1 如何使用DownloadManager下载文件

Android开发中经常会用到文件下载的功能,系统为我们提供了DownloadManager类,这是android提供的系统服务,可以通过这个服务完成文件下载。整个下载过程全部交给系统负责。通过API文档,可以看出DownLoadManager包含两个重要的内部类:

  • DownLoadManager.Request:主要用于发起一个下载请求。
  • DownLoadManager.Query:主要用于查询下载信息。

1.1 DownloadManager$Request

DownLoadManager.Request此类封装了一个下载请求所需要的所有信息,通过构造函数可以初始化一个request对象,构造对象时需要传入下载文件的地址。

DownloadManager.Request request = new DownloadManager.Request(Uri.parse("http://gdown.baidu.com/data/wisegame/55dc62995fe9ba82/jinritoutiao_448.apk"));   //设置在什么网络情况下进行下载   request.setAllowedNetworkTypes(Request.NETWORK_WIFI);   //设置通知栏标题   request.setNotificationVisibility(Request.VISIBILITY_VISIBLE);   request.setTitle("下载");   request.setDescription("今日头条正在下载");   request.setAllowedOverRoaming(false);   //设置文件存放目录   request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "mydown");//调用系统服务下载文件DownloadManager downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);long id= downManager.enqueue(request);

构造完对象后,可以为request设置一些属性:

  • addRequestHeader(String header,String value):添加网络下载请求的http头信息
  • allowScanningByMediaScanner():用于设置是否允许本MediaScanner扫描。
  • setAllowedNetworkTypes(int flags):设置用于下载时的网络类型,默认任何网络都可以下载,提供的网络常量有:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。
  • setAllowedOverRoaming(Boolean allowed):用于设置漫游状态下是否可以下载
  • setNotificationVisibility(int visibility):用于设置下载时时候在状态栏显示通知信息
  • setTitle(CharSequence):设置Notification的title信息
  • setDescription(CharSequence):设置Notification的message信息
  • setDestinationInExternalFilesDir、setDestinationInExternalPublicDir、setDestinationUri等方法用于设置下载文件的存放路径,注意如果将下载文件存放在默认路径,那么在空间不足的情况下系统会将文件删除,所以使用上述方法设置文件存放目录是十分必要的。

1.2 DownloadManager$Query

DownManager会对所有的任务进行保存管理,那么如何获取这些信息呢?这个时候就要用到DownManager.Query对象,通过此对象,可以查询所有下载任务信息。

  • setFilterById(long... ids):根据任务编号查询下载任务信息
  • setFilterByStatus(int flags):根据下载状态查询下载任务
private void queryDownTask(DownloadManager downManager,int status) {        DownloadManager.Query query = new DownloadManager.Query();        query.setFilterByStatus(status);        Cursor cursor= downManager.query(query);                while(cursor.moveToNext()){            String downId= cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_ID));            String title = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE));            String address = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));            //String statuss = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));            String size= cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));            String sizeTotal = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));            Map map = new HashMap();            map.put("downid", downId);            map.put("title", title);            map.put("address", address);            map.put("status", sizeTotal+":"+size);            this.data.add(map);        }        cursor.close();    }

1.3 下载进度的监听及查询

DownloadManager没有提供相应的回调接口,用于返回实时的下载进度状态,但通过ContentProvider可以监听到当前下载项的进度状态变化。

DownloadManager.getUriForDownloadedFile(id);

该方法会返回一个下载项的Uri,如content://downloads/my_downloads/125,通过ContentObserver监听Uri.parse(“content://downloads/my_downloads”)观察这个Uri指向的数据库项的变化,然后进行下一步操作,如发送handler进行更新UI。

private Handler handler = new Handler(Looper.getMainLooper());private static final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");private DownloadContentObserver observer = new DownloadStatusObserver();class DownloadContentObserver extends ContentObserver {    public DownloadContentObserver() {        super(handler);    }    @Override    public void onChange(boolean selfChange) {        updateView();    }}@Overrideprotected void onResume() {    super.onResume();    getContentResolver().registerContentObserver(CONTENT_URI, true, observer);}@Overrideprotected void onDestroy() {    super.onDestroy();    getContentResolver().unregisterContentObserver(observer);}public void updateView() {    int[] bytesAndStatus = getBytesAndStatus(downloadId);    int currentSize = bytesAndStatus[0];//当前大小    int totalSize = bytesAndStatus[1];//总大小    int status = bytesAndStatus[2];//下载状态    Message.obtain(handler, 0, currentSize, totalSize, status).sendToTarget();}public int[] getBytesAndStatus(long downloadId) {    int[] bytesAndStatus = new int[] { -1, -1, 0 };    Query query = new Query().setFilterById(downloadId);    Cursor c = null;    try {        c = mDownloadManager.query(query);        if (c != null && c.moveToFirst()) {            bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));            bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));            bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));        }    } finally {        if (c != null) {            c.close();        }    }    return bytesAndStatus;}

1.4 下载成功监听

文件下载完成后经常需要做一下后操作,那么如何监听文件时候已经下载完成了呢?DownLoadManager在文件现在完成时会发送一个action为ACTION_DOWNLOAD_COMPLETE的广播,可以注册一个广播接收器即可进行处理:

 private class DownLoadCompleteReceiver extends BroadcastReceiver{        @Override        public void onReceive(Context context, Intent intent) {            if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){                long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);                Toast.makeText(MainActivity.this, "编号:"+id+"的下载任务已经完成!", Toast.LENGTH_SHORT).show();            }        }    }

2 DownloadProvider下载流程分析

Download下载的工作流程,应用层通过FrameWork层DownloadManager API调用到DownloadProvider,通过操作数据库,最后通过DownloadJobService中的线程调度完成工作。整体上都是由DownloadProvider进行过渡调用。而数据库与Service都通过DownloadProvider进行隔离。


关系图 Download下载时序图

Step1 : DownloadManager.enqueue

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信息转换为ContentValues类,然后调用ContentResolver进行插入,底层会调用ContentProvider的insert方法。从pacakges/providers/DownloadProvider的清单文件中很容易知道最终是调用了DownloadProvider的insert方法去插入数据。

 ....    ....

Step2 : DownloadProvider.insert

insert方法即是往DB_TABLE(downloads)表中插入了一条数据。Android N版本中引入了JobSchedule组件来进行异步下载任务的处理。然后调用Helpers.scheduleJob(getContext(), rowID);去安排一次下载。

    @Override    public Uri insert(final Uri uri, final ContentValues values) {        //...验证values中的值,检验下载路径        long rowID = db.insert(DB_TABLE, null, filteredValues);         insertRequestHeaders(db, rowID, values); //将request header插入request_headers表        grantAllDownloadsPermission(rowID, Binder.getCallingUid());        notifyContentChanged(uri, match);         final long token = Binder.clearCallingIdentity();        try {            Helpers.scheduleJob(getContext(), rowID);        } finally {            Binder.restoreCallingIdentity(token);        }        ......        return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);}

Step3 : Helpers.scheduleJob

调用queryDownloadInfo先将downloadId对应的信息保存在DownloadInfo中,Step2中会将ContentValues 插入到数据库中,DownloadInfo就是再从数据库中将下载uri、地址等Request信息取出来,然后调用scheduleJob(Context context, DownloadInfo info)

  public static void scheduleJob(Context context, long downloadId) {        final boolean scheduled = scheduleJob(context,                DownloadInfo.queryDownloadInfo(context, downloadId));        if (!scheduled) {            // If we didn't schedule a future job, kick off a notification            // update pass immediately            getDownloadNotifier(context).update();        }     public static DownloadInfo queryDownloadInfo(Context context, long downloadId) {       final ContentResolver resolver = context.getContentResolver();       try (Cursor cursor = resolver.query(                ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId),               null, null, null, null)) {            final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);            final DownloadInfo info = new DownloadInfo(context);            if (cursor.moveToFirst()) {                reader.updateFromDatabase(info);               reader.readRequestHeaders(info);                return info;            }       }        return null;    }

继续调用scheduleJob,用绑定的DownloadJobService进行下载任务。如果线程调度失败,会返回false。

public static boolean scheduleJob(Context context, DownloadInfo info) {//关键代码   final JobScheduler scheduler = context.getSystemService(JobScheduler.class);  // 使用Android JobScheduler/JobService 工作调度    final JobInfo.Builder builder = new JobInfo.Builder(jobId, new ComponentName(context, DownloadJobService.class));   scheduler.scheduleAsPackage(builder.build(), packageName, UserHandle.myUserId(), TAG);}

Step4 : DownloadJobService.onStartJob

DownloadService中调度的线程开始下载,在onStartJob中用rowId查出来后,直接开线程DownloadThread开始下载。DownloadService服务启动时会注册一个Observer,用来监听下载进度的变化,用来更新下载通知栏。

public class DownloadJobService extends JobService { @Override    public void onCreate() {        super.onCreate();        getContentResolver().registerContentObserver(ALL_DOWNLOADS_CONTENT_URI, true, mObserver);    } @Override    public boolean onStartJob(JobParameters params) {        final int id = params.getJobId();        // Spin up thread to handle this download        final DownloadInfo info = DownloadInfo.queryDownloadInfo(this, id);        if (info == null) {            Log.w(TAG, "Odd, no details found for download " + id);            return false;        }        final DownloadThread thread;        synchronized (mActiveThreads) {            thread = new DownloadThread(this, params, info);            mActiveThreads.put(id, thread);        }        thread.start();        return true;    }    private ContentObserver mObserver = new ContentObserver(Helpers.getAsyncHandler()) {       @Override       public void onChange(boolean selfChange) {            Helpers.getDownloadNotifier(DownloadJobService.this).update();        }    };}

Step5 : DownloadThread.run

其实在DownloadThread中,主要的下载方法就是就是线程中的excuteDownload()方法。部分关键代码如下:

@Overridepublic void run() {//关键代码   executeDownload(); //开始下载} private void executeDownload() throws StopRequestException {//关键代码   URL url;        try {            // TODO: migrate URL sanity checking into client side of API            url = new URL(mInfoDelta.mUri);        } catch (MalformedURLException e) {            throw new StopRequestException(STATUS_BAD_REQUEST, e);        }     HttpURLConnection conn = null;    checkConnectivity();       conn = (HttpURLConnection) mNetwork.openConnection(url); //使用HttpsURLConnection下载       conn.setInstanceFollowRedirects(false);       conn.setConnectTimeout(DEFAULT_TIMEOUT);       conn.setReadTimeout(DEFAULT_TIMEOUT);        // If this is going over HTTPS configure the trust to be the same as the calling package.        if (conn instanceof HttpsURLConnection) {             ((HttpsURLConnection)conn).setSSLSocketFactory(appContext.getSocketFactory());        }        addRequestHeaders(conn, resuming);    final int responseCode = conn.getResponseCode();     switch (responseCode) {           case HTTP_OK: //网络请求成功               if (resuming) {                   throw new StopRequestException(STATUS_CANNOT_RESUME, "Expected partial, but received OK");               }               parseOkHeaders(conn);               transferData(conn); //Transfer data from the given connection to the destination file               return;    ............. }

参考

https://www.jianshu.com/p/fe4935f27dc1
https://blog.csdn.net/weixin_34281537/article/details/86872919

更多相关文章

  1. 111111111
  2. android加固签名工具(源码下载)
  3. SONY 系列手机 Android(安卓)5.1 系统 Root 方法
  4. Android(安卓)FrameWork——Binder机制详解
  5. Android(安卓)Camera2 Hal3(一)初始化
  6. Mac系统下利用ADB命令连接android手机并进行文件操作
  7. android文件的写入与读取---简单的文本读写
  8. Android(安卓)Studio多渠道打包和代码混淆教程
  9. 一道面试题引发的对android中context的研究(一)-SharedPreference

随机推荐

  1. 我的Android进阶之旅------>Android中的
  2. App基于html/css/js的开发
  3. android中的多进程模式(IPC)
  4. Android内存分析工具:Memory Profiler
  5. android开发与jave ee集成开发心得[转]
  6. Android中ListView的几种常见的优化方法
  7. android不生成R文件
  8. 【移动开发】Android中异步加载数据(一)Han
  9. Intent原理
  10. Android读取excel文件小结(读取大数据量