目录

前言

一、介绍

二、使用事项

三、Request类注意事项

四、Query 类

五、下载完成监听

六、原理简析

七、小结


前言

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

 


本案例下载地址


安卓.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>数据库的,所以需要获得一个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));
        //下载对应id
        long 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();

    @Override
    protected void onResume() {
        super.onResume();
        getContentResolver().registerContentObserver(CONTENT_URI, true, observer);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getContentResolver().unregisterContentObserver(observer);
    }

//ContentObserver 内部类监听下载进度
    class DownloadContentObserver extends ContentObserver {
        public DownloadContentObserver() {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) {
            updateView();//更新UI
        }

    }

五、下载完成监听

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

private CompleteReceiver completeReceiver;

class CompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // get complete download id
        long 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 broadcast
    registerReceiver(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 scanning
    final Context context = getContext();
    context.startService(new Intent(context, DownloadService.class));

    return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}

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

mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);

private Handler.Callback mUpdateCallback = new Handler.Callback() {
    @Override
    public 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 ready
     final 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、会出现被用户手动禁用了下载器出现崩溃情况。需要做好版本兼容和弹框让用户手动开启。