android 源生DownloadManager 的两个Crash 坑

如果你的APP使用源生DownloadManager去更新apk,那么下面这3行代码你肯定不陌生,

1
2
3
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(你的APK下载地址));
request.setDestinationUri(你的app 本地存储URI));
long mDownloadId = mDownloadManager.enqueue(request);

IllegalArgumentException

这三行代码 最近在一些机型上爆出了java.lang.IllegalArgumentException: UnknownURLcontent://downloads/my_downloads 异常.
是崩溃在了mDownloadManager.enqueue(request)这里,
跟着代码进去以后

1
2
3
4
5
6
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;
}

继续跟进,找到了崩溃的源头, 是 mResolver.insert这句的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {
Preconditions.checkNotNull(url, "url");
IContentProvider provider = acquireProvider(url);
if (provider == null) {
// 看这里 嘿嘿嘿
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
releaseProvider(provider);
}
}

原因是系统的DownloadManager Service被关闭了, 遇见这种问题, 可以有两种解决方法
1 是通过产品角度去告知用户,让用户去系统里找到相应的开关 打开下载服务,
2 是catch 住这个IllegalArgumentException 然后不再使用downloadmanager 而是自己去手写下载, 就可以解决.

SecurityException

这三行代码在某些机器上会爆出SecurityException,原因是Context.getExternalFilesDir得到的地址 和Environment.getExternalStorageDirectory() 不一致.
具体崩溃代码可以去看源码 的713行.

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
private void checkFileUriDestination(ContentValues values) {
String fileUri = values.getAsString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
if (fileUri == null) {
throw new IllegalArgumentException(
"DESTINATION_FILE_URI must include a file URI under COLUMN_FILE_NAME_HINT");
}
Uri uri = Uri.parse(fileUri);
String scheme = uri.getScheme();
if (scheme == null || !scheme.equals("file")) {
throw new IllegalArgumentException("Not a file URI: " + uri);
}
final String path = uri.getPath();
if (path == null) {
throw new IllegalArgumentException("Invalid file URI: " + uri);
}
try {
final String canonicalPath = new File(path).getCanonicalPath();
final String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath();
if (!canonicalPath.startsWith(externalPath)) {
// 看这里,嘿嘿嘿
throw new SecurityException("Destination must be on external storage: " + uri);
}
} catch (IOException e) {
throw new SecurityException("Problem resolving path: " + uri);
}
}

出现这种bug 我们的解决方案 是自己去拼1个存储地址..
所以这三行代码的最终使用是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(你的APK下载地址));
request.setDestinationUri(你的app 本地存储URI));
try {
long mDownloadId = instance.mSystemDownloadManager.enqueue(request);
} catch (SecurityException e) {
File f = Environment.getExternalStorageDirectory();
f = new File(f, "Android");
f = new File(f, "data");
f = new File(f, mContext.getPackageName());
f = new File(f, "files");
f = new File(f, Environment.DIRECTORY_DOWNLOADS);
if (!f.exists()) {
f.mkdirs();
}
request.setDestinationUri(f));
instance.mDownloadId = instance.mSystemDownloadManager.enqueue(request);
} catch (IllegalArgumentException e) {
// 自己去写把 要么弹toast告诉用户他傻逼, 要么自己去手写下载
}

Share Comments