05/23
2013

Android下载管理DownloadManager功能扩展和bug修改

本文主要介绍如何修改Android系统下载管理,以支持更多的功能及部分bug修改和如何编译生效。目前内容包括暂停下载、继续下载、通知设置NotiExtra和NotiClass、wifi切换到3g自动暂停、Bug修改。

更多下载相关开源项目可见  Android 下载

 

PS: 很多童鞋不是自己做rom,所以就算修改了系统源码编译出来的包在其他系统上也不通用
这里推荐DownloadProvider@Github(并不是我的开源项目,我的项目为TrineaAndroidCommon@Github,包含图片缓存,下拉刷新,静默安装等,欢迎关注),系统下载管理的独立版,可整合进自己的应用,感谢@DONG童鞋提供地址。

 

下面需要修改的DownloadManager.java所在目录为frameworks/base/core/java/android/app
DownloadInfo.java, DownloadProvider.java,DownloadThread.java文件所在目录为packages/providers/DownloadProvider/src/com/android/providers/downloads

 

1、暂停、继续下载功能
(1) DownloadProvider类修改

public int update(final Uri uri, final ContentValues values, final String where, final String[] whereArgs)

函数,修改后代码如下(只增加了一行有效代码):

if (Binder.getCallingPid() != Process.myPid()) {
	filteredValues = new ContentValues();
	copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues);
	copyInteger(Downloads.Impl.COLUMN_VISIBILITY, values, filteredValues);
	Integer i = values.getAsInteger(Downloads.Impl.COLUMN_CONTROL);
	if (i != null) {
		filteredValues.put(Downloads.Impl.COLUMN_CONTROL, i);
		startService = true;
	}

	// trinea BEGIN, added by trinea@trinea.cn 2013/03/01
	copyInteger(Downloads.Impl.COLUMN_STATUS, values, filteredValues);
	// trinea END
	copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
	copyString(Downloads.Impl.COLUMN_TITLE, values, filteredValues);
	copyString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, values, filteredValues);
	copyString(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues);
	copyInteger(Downloads.Impl.COLUMN_DELETED, values, filteredValues);
} else {

其中以// trinea BEGIN开头,// trinea END结尾为修改部分,下面代码示例同样如此。因为DownloadProvider安全策略对非该进程id的修改会过滤掉COLUMN_STATUS状态,所以我们需要添加该行。

 

(2) DownloadThread类修改

private void setupDestinationFile(State state, InnerState innerState)

函数中这个注释掉一个else if,如下:

// trinea BEGIN, noted by trinea@trinea.cn 2013/03/01
//} else if (mInfo.mETag == null && !mInfo.mNoIntegrity) {
//    // This should've been caught upon failure
//    if (Constants.LOGVV) {
//        Log.d(TAG, "setupDestinationFile() unable to resume download, deleting "
//                + state.mFilename);
//    }
//    f.delete();
//    throw new StopRequestException(Downloads.Impl.STATUS_CANNOT_RESUME,
//            "Trying to resume a download that can't be resumed");
// trinea END

上面一段代码表示一个验证过程,可以去掉。

mETag为数据库中的etag字段值,代码中没有解释,感觉是一个验证值,类似hashcode。
mNoIntegrity为数据中no_integrity字段值,表示启动下载的应用程序能否验证下载的文件的完整性。不过坑爹的是对于etag和no_integrity都没有提供设置的接口

 

(3) DownloadManager类中添加对外接口

/**
 * pause download, added by trinea@trinea.cn 2013/03/01
 * 
 * @param ids the IDs of the downloads to be paused
 * @return the number of downloads actually paused
 */
public int pauseDownload(long... ids) {
	if (ids == null || ids.length == 0) {
		// called with nothing to remove!
		throw new IllegalArgumentException("input param 'ids' can't be null");
	}

	ContentValues values = new ContentValues();
	values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_PAUSED);
	values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PAUSED_BY_APP);
	if (ids.length == 1) {
		return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values,
				null, null);
	} 
	return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
			getWhereArgsForIds(ids));
}

/**
 * resume download, added by trinea@trinea.cn 2013/03/01
 * 
 * @param ids the IDs of the downloads to be resumed
 * @return the number of downloads actually resumed
 */
public int resumeDownload(long... ids) {
	if (ids == null || ids.length == 0) {
		// called with nothing to remove!
		throw new IllegalArgumentException("input param 'ids' can't be null");
	}

	ContentValues values = new ContentValues();
	values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
	values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_RUNNING);
	if (ids.length == 1) {
		return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values,
				null, null);
	} 
	return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
			getWhereArgsForIds(ids));
}

无论是暂停还是继续我们都是同时把Downloads.Impl.COLUMN_CONTROL和Downloads.Impl.COLUMN_STATUS字段进行修改,因为在DownloadInfo的private boolean isReadyToStart(long now)函数中,会对COLUMN_CONTROL字段进行判断,如果是用户手动暂停的话,是不会自动继续的,部分代码如下:

private boolean isReadyToStart(long now) {
	if (DownloadHandler.getInstance().hasDownloadInQueue(mId)) {
		// already running
		return false;
	}
	if (mControl == Downloads.Impl.CONTROL_PAUSED) {
		// the download is paused, so it's not going to start
		Xlog.i(Constants.DL_ENHANCE, "Download is paused " +
				"then no need to start");
		return false;
	}
	……
}

之后我们直接调用DownloadManager的pauseDownload和resumeDownload接口即可

 

PS:也可以试试不做第二步的修改,而将第一步DownloadProvider的update函数修改变为

// trinea BEGIN, added by trinea@trinea.cn 2013/03/01
copyInteger(Downloads.Impl.COLUMN_STATUS, values, filteredValues);
copyInteger(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);
// trinea END

第二步修改变为在public int resumeDownload(long… ids)加入

values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_RUNNING);
values.put(Downloads.Impl.COLUMN_NO_INTEGRITY, true);

没有亲自试,不过按照逻辑应该也可以。

 

2、通知栏可以设置NotiExtra和NotiClass

(1) DownloadProvider类中修改private void checkInsertPermissions(ContentValues values)函数

values.remove(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI);
values.remove(Downloads.Impl.COLUMN_MEDIA_SCANNED);
// BEGIN, added by trinea@trinea.cn 2013/03/01
values.remove(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
values.remove(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS);
// trinea END

在DownloadProvider insert之前会调用checkInsertPermissions检查不能被插入的字段插入,这里我们需要允许这两个字段存在。

 

(2) DownloadManager.Request添加对外接口

// BEGIN, added by trinea@trinea.cn 2013/03/01
private CharSequence mNotiClass;
private CharSequence mNotiExtras;
// trinea END

/**
 * Set notiClass, to be used as destination when click downloading in download manager ui
 * 
 * @return this object
 */
public Request setNotiClass(CharSequence notiClass) {
	mNotiClass = notiClass;
	return this;
}

/**
 * Set notiExtras, to be sended to notiClass when click downloading in download manager ui
 * 
 * @return this object
 */
public Request setNotiExtras(CharSequence notiExtras) {
	mNotiExtras = notiExtras;
	return this;
}

ContentValues toContentValues(String packageName)中
	putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
	putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
	putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
	// trinea BEGIN
	putIfNonNull(values, Downloads.Impl.COLUMN_NOTIFICATION_CLASS, mNotiClass);
	putIfNonNull(values, Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, mNotiExtras);
	// trinea END

在Request中添加接口以及允许字段修改。通过允许设置NotiExtra和NotiClass,我们可以给系统传递更丰富的参数,在通知栏点击相应或是DownloadUi中通过broadcast将这些参数传递出来方便应用调用。

 

3、wifi切换到3g自动暂停
(1) 修改DownloadInfo.java

private int checkIsNetworkTypeAllowed(int networkType) {
	if (mIsPublicApi) {
		final int flag = translateNetworkTypeToApiFlag(networkType);
		final boolean allowAllNetworkTypes = mAllowedNetworkTypes == ~0;
		if (!allowAllNetworkTypes && (flag & mAllowedNetworkTypes) == 0) {
			return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR;
		}
		// trinea BEGIN
		if (mStatus == Downloads.Impl.STATUS_WAITING_FOR_NETWORK 
				&& flag != DownloadManager.Request.NETWORK_WIFI) {
			return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR;
		}
		// trinea END
	}
	return checkSizeAllowedForNetwork(networkType);
}

表示等待网络时始终只等待wifi

 

(2) 修改DownloadReceiver.java

} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
	NetworkInfo info = (NetworkInfo)
			intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
	if (info != null && info.isConnected()) {
		startService(context);
	}
} else if (action.equals(Constants.ACTION_RETRY)) {

修改为:

} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
	NetworkInfo info = (NetworkInfo)
			intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
	// trinea BEGIN
	/** 
	 * modified by trinea@trinea.cn @2013/04/01, resume download only when network type is wifi
	 */
	if (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI) {
		// trinea END
		startService(context);
	}
} else if (action.equals(Constants.ACTION_RETRY)) {

表示只有连接wifi时才唤醒service去检查是否下载

 

(3) 修改DownloadThread.java

/**
 * Check if the download has been paused or canceled, stopping the request appropriately if it
 * has been.
 */
private void checkPausedOrCanceled(State state) throws StopRequestException {
	synchronized (mInfo) {
		if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
			Xlog.i(Constants.DL_ENHANCE, "DownloadThread: checkPausedOrCanceled: user pause download");
			throw new StopRequestException(
					Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
		}
		if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
			throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
		}
	}

	// if policy has been changed, trigger connectivity check
	if (mPolicyDirty) {
		// trinea BEGIN
		/** 
		 * add by trinea@trinea.cn @2013/04/01, when switched from wifi to 3g, pause all download
		 */
		NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
		if (info != null && !info.isConnected()) {
			mInfo.mStatus = Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
		}
		// trinea END
		checkConnectivity();
	}
}

表示如果网络变化并且表示网络断开时,下载状态变为等待网络。

 

4、Bug修改
(1) 当存储空间不足时,利用DownloadManager下载,只显示通知栏提示,在下载管理UI中不显示
DownloadManager的Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri)函数修改如下:

if ((mStatusFlags & STATUS_RUNNING) != 0) {
	parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING));
	// trinea BEGIN
	parts.add(statusClause("=", Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR));
	// trinea END
}

DownloadManager的CursorTranslator类的private int translateStatus(int status) 函数修改如下:

// trinea BEGIN
case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
// trinea END
case Downloads.Impl.STATUS_RUNNING:
	return STATUS_RUNNING;

 

5、编译安装

修改后是需要重新编译的,需同时编译framweork和DownloadProvider。

framework编译命令为:./makeMtk model mm frameworks/base/core/
编译后apk所在路径为out\target\product\model\system\framework\secondary_framework.jar,之后push到system/framework重启即可。编译命令中model为机型,非mtk平台命令有所不同

 

DownloadProvider编译命令为./makeMtk model mm packages/providers/DownloadProvider/
编译后apk所在路径为out\target\product\model\system\app\DownloadProvider.apk,之后push到system/app即可(可能需要先删除/system/app/目录下的DownloadProvider.odex)

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

145 thoughts on “Android下载管理DownloadManager功能扩展和bug修改

  1. 感谢博主推荐DownloadProvider@Github, 我的项目就用的是这个。不过这个项目还是有一些不完善的地方,我个人用这个库用了半年左右,发现了一些细小的bug,如不支持外置sd卡,连接需要网页登陆的wifi,会把网页下载下来,然后提示下载成功。不能支持设置最大同时下载任务数。同时,有些不应该判定为失败的,或者说可以继续下载的,也被判定了失败。非常感谢博主推荐这个开源库,省了我很多事。我们整个项目也稳定了一些。

  2. 请问博主,为什么我把那个下载管理系统的demo套入到我的项目去,点击开始下载总是提示这里的错误throw new SecurityException(“Invalid value for ” + column + “: ” + value);

  3. 这个DownloadProvider项目导入后偶的不行在2.3.3和4.X的虚拟机上都不能运行啊,真机有的可以,有的不行,怎么回事。按你的改了也是不能运行

      • 谢谢楼主热心及时的解答!setDestinationFromBase和setDestinationUri都是修改了mDestinationUri然后插入数据库,setDestinationUri可以下载到/data目录下吗?如果不行,我要调用setDestinationFromBase是不是要将该方法改为public就可以了?