android connectivity分析,Android DownloadProvider分析

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();

}

}

@Override

protected void onResume() {

super.onResume();

getContentResolver().registerContentObserver(CONTENT_URI, true, observer);

}

@Override

protected 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进行隔离。

074d39aba00b

关系图

074d39aba00b

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方法去插入数据。

....

android:authorities="downloads" android:exported="true">

....

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()方法。部分关键代码如下:

@Override

public 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;

.............

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值