本文主要介绍如何修改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)
楼主..能不能把你修改编译完成的android.jar(2.3.3)发我一个呀..我邮箱179840045@qq.com
里面不少公司的东西,不太合适。你如果对修改方面有疑问可以直接回复。下载管理除了DownloadProvider文件外,就只涉及到android/app下面的DownloadManager和android/provider下面Downloads文件了,其他都是android统一的
前辈..那个我按你的方法修改了源码..但不知道怎么重新编译成android.jar包
android不用重编,只要重编framework就行了,博客的第五部分介绍了这个已经
前辈..用什么重编framework??没搞过..第一次改源码..菜鸟.什么都不懂..
请仔细看第五部分,其他不会的google
多谢楼主,这下省了不少事了。不过没找到博主关于下载管理底层设计介绍的文章呢
那篇文章一直没时间整理啊
楼主你好:我想把修改了framework层重新打包一个android.jar ,但是我在源码目录下输入make PRODUCT-sd-sdk ,然后报错:build/core/base_rules.mk:171: *** sdk/emulator/opengl/system/egl: MODULE.TARGET.
ETC.egl.cfg already defined by frameworks/native/opengl/libs。,楼主能帮我解决一下吗?万分感谢。
这个也没碰到过,貌似是模块冲突了,你看看未修改前是不是编译也有这个错
未修改之前也报出了这个错误。我是先source build/envsetup.sh 然后输入make PRODUCT-sd-sdk 就报出这个错误了。build/core/base_rules.mk:171: *** sdk/emulator/opengl/system/egl: MODULE.TARGET.
ETC.egl.cfg already defined by frameworks/native/opengl/libs
感谢博主,终于可以暂停继续了
etag的判断是有用处的,具体含义可以参见http协议的响应头。
非常感谢你的提醒,我看了下eTag可以非常有效的标志资源的有效性,后面会把文章更新一下。下载管理里面跟http协议关联很紧密啊,状态的设置也是http response code
楼主..能不能把你修改编译完成的android.jar(2.3.3)发我一个呀..我邮箱179840045@qq.com
前辈多多给予指导哇!!谢谢。
把你调用的代码发来看看,我看你Request怎么设置的
把DownloadThread类再修改一下应该就可以了,文章已修改,看下文章中的介绍。解决了
Aborting request for download 1: http error 200, mContinuingDownload: true
Aborting request for download 1: Trying to resume a download that can’t be resumed
这两个问题
请问楼主的resumedownload()是断点续传吗?我按照楼主的样子做。直接挂掉了。
嗯,继续下载,默认是支持断点续传的。你可以把错误日志贴出来我看看
没有出错日志。而是系统直接关闭了我的下载进程了。
是不是暂停的状态不是pause?????
手动pause状态是Downloads.Impl.STATUS_PAUSED_BY_APP不会错的,logcat看下error日志,怎么会没有日志呢,而且你确保DownloadProvider和DownloadManager都改了。这些代码我都是经过市场的验证的
Download的logcat by Application Name的filter是android.process.media
Aborting request for download 1: http error 200, mContinuingDownload: true 望前辈多多给予指导!!。谢谢。。
应该是你的下载源地址不支持断点下载,你可以试试其他下载地址,比如美丽说的apk
Aborting request for download 1: Trying to resume a download that can’t be resumed 这是美丽的传说的apk下载地址报出的错误
前辈:谢谢你热情的帮助,加我QQ :1134981459,以后多多沟通。
在你继续下载的时候,你的下载地址http response code应该返回206,却直接返回了200,说已经下载成功了
什么意思?可以说详细点吗?
几个问题
1、你的android版本是多少
2、暂停是否生效
3、你是否先调用Request的enqueue接口去下载,再调用resumeDownload接口的
Android 版本:4.1.1:
暂停也生效了:
调用equeue下载的。
之后调用resumedownload()
Pingback: Android系统下载管理DownloadManager功能介绍及使用示例 - Android - 开发者问答
正在熟悉DownloadProvider这块,期待博主关注这块设计的博客
多谢关注,最近有点忙,但我会尽可能下周前完成的
原来需要把downprovider中的status字段加上,怪不得我老是暂停失败