本文主要介绍如何修改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类修改
1 |
public int update(final Uri uri, final ContentValues values, final String where, final String[] whereArgs) |
函数,修改后代码如下(只增加了一行有效代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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类修改
1 |
private void setupDestinationFile(State state, InnerState innerState) |
函数中这个注释掉一个else if,如下:
1 2 3 4 5 6 7 8 9 10 11 |
// 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类中添加对外接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/** * 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字段进行判断,如果是用户手动暂停的话,是不会自动继续的,部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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函数修改变为
1 2 3 4 |
// 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)加入
1 2 3 |
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)函数
1 2 3 4 5 6 |
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添加对外接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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
1 2 3 4 5 6 7 |
} 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)) { |
修改为:
1 2 3 4 5 6 7 8 9 10 11 12 |
} 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/** * 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)函数修改如下:
1 2 3 4 5 6 |
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) 函数修改如下:
1 2 3 4 5 |
// 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)
楼主我想问下推荐你推荐的DownloadProvider@Github的开源下载项目,你应该研究过的,我现在碰到一个问题,在通知栏下点击下载的进度,没有任何反应
源码都是开放的,自己debug呗
你好,正在做下载这快,这个DownloadProvider@Github开源下载项目有对外申明权限,我可以当做library方式引用吗,请问你是怎么做的,我这遇到了一些问题,麻烦你帮忙解决一下好吗,万分感激!
感谢博主推荐DownloadProvider@Github, 我的项目就用的是这个。不过这个项目还是有一些不完善的地方,我个人用这个库用了半年左右,发现了一些细小的bug,如不支持外置sd卡,连接需要网页登陆的wifi,会把网页下载下来,然后提示下载成功。不能支持设置最大同时下载任务数。同时,有些不应该判定为失败的,或者说可以继续下载的,也被判定了失败。非常感谢博主推荐这个开源库,省了我很多事。我们整个项目也稳定了一些。
不客气,这个库是根据2.3移植过来的,4.0的DownloadProvider有很大的改进,你说的这些问题都解决了,你可以把4.0的独立出来分享给大家
hi,请问能提供DownloadProvider@Github修改过的代码吗?我修改过的还是一直下载失败,而且还有很多bug。谢谢了
楼主我先问问这个DownloadManager支持断点下载的不。
支持
请问下,根据你的修改,那不是下载文件只允许wifi下载?不支持3G的吗?
不是,只是默认暂停,还是可以手动继续的
我现在的问题是当我用3G下载的时候,出现下载错误的问题,wifi下载没问题,我的request代码是:DownloadManager.Request request = new Request(srcUri); request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE |DownloadManager.Request.NETWORK_WIFI);感觉不知道还有哪些出问题了
下载错误也是有错误代码的,logcat有提示
错误代码是1000,但是坑爹的是 /** * Value of COLUMN_ERROR_CODE when the download has completed with an error * that doesn’t fit under any other error code. */ public final static int ERROR_UNKNOWN = 1000;这是什么原因??
我的示例程序能运行吗
是TrineaAndroidCommon@Github吗 ,这个好像不支持下载暂停 停止 那些的,如果是DownloadProvider@Github那就是现在我出现的问题,你上面列出的修改里面,关于wifi切换到3g自动暂停我没去动它,其他都改了,代码都可以运行
就是3G下不来,其他都正常
DownloadProvider@Github不是我在维护,我的TrineaAndroidCommon@Github是依赖于系统的,通过反射查找接口,不是通用类型。另外我的示例程序能否在你的机器上正常运行?
https://github.com/Trinea/android-demo 是这个吗?好像少了一些文件啊,项目报错
好像是utils少了,我这里包里面就一个文件
说明的注意里面不是写的很清楚吗,另外有Apk直接运行不就可以。https://github.com/Trinea/android-demo 花点时间看看ReadMe!!
请问下,我用httpclient弄断点下载,我暂停他写文件,但是在inputstream.close()阻塞了,请问遇到这个问题吗
没
如果遇到这个问题,该怎么解决呢
断点续传不是这么做的,用RandAccessFile和RANGE,可参考:http://www.ibm.com/developerworks/cn/java/joy-down/index.html
还是建议把系统自带的DownloadManager.java提取出来,单独做成一个jar包,可以有更好的适应性。
只提取DownloadManager是不行的,重点是DownloadProvider,没时间弄,确实应该拿出来
你这个问题解决了么
这个开源项目不是我在维护,抱歉,暂时没时间去开源这个
请问博主,为什么我把那个下载管理系统的demo套入到我的项目去,点击开始下载总是提示这里的错误throw new SecurityException(“Invalid value for ” + column + “: ” + value);
抱歉,那个是其他朋友开源的项目,而目前我也没有太多时间去解决那个,如果可以的话,尽量用系统原生的下载管理吧
但是原生不支持暂停,还有remove他貌似没有停止下载
暂停确实没有,remove应该可以的
这个DownloadProvider项目导入后偶的不行在2.3.3和4.X的虚拟机上都不能运行啊,真机有的可以,有的不行,怎么回事。按你的改了也是不能运行
那个开源项目不是我在维护,暂时我也没时间看
请问楼主如果没有内存卡下载路径怎么设置,或者有内存卡想下载到/data怎么修改
setDestinationInExternalFilesDir这个函数可以写到你的app data目录下,如/data/data/com.trinea.demo/files你说的/data其他地方是需要写入权限的,有个私有函数setDestinationFromBase可以完成
谢谢楼主热心及时的解答!setDestinationFromBase和setDestinationUri都是修改了mDestinationUri然后插入数据库,setDestinationUri可以下载到/data目录下吗?如果不行,我要调用setDestinationFromBase是不是要将该方法改为public就可以了?
这个你修改了测试一下,绝对比我回复你块。。
setDestinationFromBase和setDestinationUri都测试过了 打印报错说路径必须是外部存储的,即内存卡的。楼主说的setDestinationInExternalFilesDir下载路径应该是在内存卡的Android/data/app/files下的吧。
是的。确实是有检查必须在外部sd卡的,checkFileUriDestination
将checkFileUriDestination中检测是否在外部sd卡的异常抛出注释掉后用setDestinationFromBase和setDestinationUri测了很多次都没成功,打印报错是illegalStateException:invalid combination of destination(无效的目的地组合???):4,path:/data/filename。是不是真的没办法直接下载到/data目录下?
这部分你完全可以自己去debug,理论上实现肯定是没有问题的verifySpace另外不建议下载到系统内部,没有清理的话影响系统空间,人为控制靠不住。你可以下载其他地方拷贝过去再