Android Broswer下载路径设置
- Tab.java //有一个下载监听,调用mWebViewController.onDownloadStart
- mDownloadListener = new BrowserDownloadListener() {
- public void onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, String referer,
- long contentLength) {
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,
- mimetype, referer, contentLength);
- }
- };
中间经过一系列传递,最终调用到DownloadHandler.java中的onDownloadStart方法
onDownloadStart ---> onDownloadStartNoStream
- static void onDownloadStartNoStream(Activity activity,
- String url, String userAgent, String contentDisposition,
- String mimetype, String referer, boolean privateBrowsing) {
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- String filename = URLUtil.guessFileName(url,
- contentDisposition, mimetype);
- // Check to see if we have an SDCard
- String status = Environment.getExternalStorageState();
- if (!status.equals(Environment.MEDIA_MOUNTED)) {
- int title;
- String msg;
- // Check to see if the SDCard is busy, same as the music app
- if (status.equals(Environment.MEDIA_SHARED)) {
- msg = activity.getString(R.string.download_sdcard_busy_dlg_msg);
- title = R.string.download_sdcard_busy_dlg_title;
- } else {
- msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename);
- title = R.string.download_no_sdcard_dlg_title;
- }
- new AlertDialog.Builder(activity)
- .setTitle(title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setMessage(msg)
- .setPositiveButton(R.string.ok, null)
- .show();
- return;
- }
- // java.net.URI is a lot stricter than KURL so we have to encode some
- // extra characters. Fix for b 2538060 and b 1634719
- WebAddress webAddress;
- try {
- webAddress = new WebAddress(url);
- webAddress.setPath(encodePath(webAddress.getPath()));
- } catch (Exception e) {
- // This only happens for very bad urls, we want to chatch the
- // exception here
- Log.e(LOGTAG, "Exception trying to parse url:" + url);
- return;
- }
- String addressString = webAddress.toString();
- Uri uri = Uri.parse(addressString);
- final DownloadManager.Request request;
- try {
- request = new DownloadManager.Request(uri);
- } catch (IllegalArgumentException e) {
- Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
- return;
- }
- request.setMimeType(mimetype);
- // set downloaded file destination to /sdcard/Download.
- // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype?
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); //设置下载路径
- // let this downloaded file be scanned by MediaScanner - so that it can
- // show up in Gallery app, for example.
- request.allowScanningByMediaScanner();
- request.setDescription(webAddress.getHost());
- // XXX: Have to use the old url since the cookies were stored using the
- // old percent-encoded url.
- String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);
- request.addRequestHeader("cookie", cookies);
- request.addRequestHeader("User-Agent", userAgent);
- request.addRequestHeader("Referer", referer);
- request.setNotificationVisibility(
- DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- if (mimetype == null) {
- if (TextUtils.isEmpty(addressString)) {
- return;
- }
- // We must have long pressed on a link or image to download it. We
- // are not sure of the mimetype in this case, so do a head request
- new FetchUrlMimeType(activity, request, addressString, cookies,
- userAgent).start();
- } else {
- final DownloadManager manager
- = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
- new Thread("Browser download") {
- public void run() {
- manager.enqueue(request);
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- }
- }.start();
- }
- Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT)
- .show();
- }
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); 跟踪代码是DownloadManager.java调用Environment.java里面的方法设置默认下载路径,设置完之后保存在request里面,然后获取系统的DownloadManager,创建线程,调用DownloadManager里面的manager.enqueue(request),并且将设置好的request传递过去。
下面来看enqueue,DownloadManager.java
- /**
- * Enqueue a new download. The download will start automatically once the download manager is
- * ready to execute it and connectivity is available.
- *
- * @param request the parameters specifying this download
- * @return an ID for the download, unique across the system. This ID is used to make future
- * calls related to this download.
- */
- 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;
- }
做了2个事情,1:根据request得到ContentValues;2:将改ContentValues插入到mResolver;
第一个事情没什么好说的,看第二个
mResolver的类型是ContentResolver,在DownloadManager构造函数中初始化;DownloadManager这个服务的创建在ContextImpl.java
- registerService(DOWNLOAD_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new DownloadManager(ctx.getContentResolver(), ctx.getPackageName());
- }});
这个插入动作insert 经过一系列跟踪(过程不表),最终调用的是DownloadProvider.java中的insert,代码很长,大致意思为把数据存入sqlite里面,通知ContentChanged(这个会被DownloadService.java中的DownloadManagerContentObserver检测到),启动DownloadService。
- public Uri insert(final Uri uri, final ContentValues values) {
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- Context mcontext = getContext();
- Intent intent=new Intent(mcontext,com.android.providers.downloads.MyFileManager.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mcontext.startActivity(intent);
- String str="";
- SharedPreferences sharedPreferences = getContext().getSharedPreferences("mark_sp", Context.MODE_WORLD_READABLE);
- str=sharedPreferences.getString("mark_download_file_route","");
- Log.d(TAG,"file_route="+str);
- checkInsertPermissions(values);
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- // note we disallow inserting into ALL_DOWNLOADS
- int match = sURIMatcher.match(uri);
- if (match != MY_DOWNLOADS) {
- Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
- throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
- }
- // copy some of the input values as it
- ContentValues filteredValues = new ContentValues();
- copyString(Downloads.Impl.COLUMN_URI, values, filteredValues);
- copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues);
- copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);
- copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues);
- copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues);
- copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);
- boolean isPublicApi =
- values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;
- // validate the destination column
- Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);
- if (dest != null) {
- if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
- != PackageManager.PERMISSION_GRANTED
- && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION
- || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
- || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) {
- throw new SecurityException("setting destination to : " + dest +
- " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted");
- }
- // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically
- // switch to non-purgeable download
- boolean hasNonPurgeablePermission =
- getContext().checkCallingPermission(
- Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE)
- == PackageManager.PERMISSION_GRANTED;
- if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE
- && hasNonPurgeablePermission) {
- dest = Downloads.Impl.DESTINATION_CACHE_PARTITION;
- }
- if (dest == Downloads.Impl.DESTINATION_FILE_URI) {
- getContext().enforcePermission(
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Binder.getCallingPid(), Binder.getCallingUid(),
- "need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI");
- checkFileUriDestination(values);
- } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) {
- getContext().enforcePermission(
- android.Manifest.permission.ACCESS_CACHE_FILESYSTEM,
- Binder.getCallingPid(), Binder.getCallingUid(),
- "need ACCESS_CACHE_FILESYSTEM permission to use system cache");
- }
- filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest);
- }
- // validate the visibility column
- Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY);
- if (vis == null) {
- if (dest == Downloads.Impl.DESTINATION_EXTERNAL) {
- filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
- Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- } else {
- filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
- Downloads.Impl.VISIBILITY_HIDDEN);
- }
- } else {
- filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis);
- }
- // copy the control column as is
- copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
- /*
- * requests coming from
- * DownloadManager.addCompletedDownload(String, String, String,
- * boolean, String, String, long) need special treatment
- */
- if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
- Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
- // these requests always are marked as 'completed'
- filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
- filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES,
- values.getAsLong(Downloads.Impl.COLUMN_TOTAL_BYTES));
- filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
- copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues);
- copyString(Downloads.Impl._DATA, values, filteredValues);
- copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);
- } else {
- filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
- filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
- filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
- }
- // set lastupdate to current time
- long lastMod = mSystemFacade.currentTimeMillis();
- filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod);
- // use packagename of the caller to set the notification columns
- String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
- String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
- if (pckg != null && (clazz != null || isPublicApi)) {
- int uid = Binder.getCallingUid();
- try {
- if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) {
- filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg);
- if (clazz != null) {
- filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz);
- }
- }
- } catch (PackageManager.NameNotFoundException ex) {
- /* ignored for now */
- }
- }
- // copy some more columns as is
- copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues);
- copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues);
- copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues);
- copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues);
- // UID, PID columns
- if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
- == PackageManager.PERMISSION_GRANTED) {
- copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues);
- }
- filteredValues.put(Constants.UID, Binder.getCallingUid());
- if (Binder.getCallingUid() == 0) {
- copyInteger(Constants.UID, values, filteredValues);
- }
- // copy some more columns as is
- copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, "");
- copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, "");
- // is_visible_in_downloads_ui column
- if (values.containsKey(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI)) {
- copyBoolean(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, values, filteredValues);
- } else {
- // by default, make external downloads visible in the UI
- boolean isExternal = (dest == null || dest == Downloads.Impl.DESTINATION_EXTERNAL);
- filteredValues.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, isExternal);
- }
- // public api requests and networktypes/roaming columns
- if (isPublicApi) {
- copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues);
- copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues);
- copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues);
- }
- if (Constants.LOGVV) {
- Log.v(Constants.TAG, "initiating download with UID "
- + filteredValues.getAsInteger(Constants.UID));
- if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) {
- Log.v(Constants.TAG, "other UID " +
- filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID));
- }
- }
- long rowID = db.insert(DB_TABLE, null, filteredValues);
- if (rowID == -1) {
- Log.d(Constants.TAG, "couldn't insert into downloads database");
- return null;
- }
- insertRequestHeaders(db, rowID, values);
- notifyContentChanged(uri, match);
- // Always start service to handle notifications and/or scanning
- final Context context = getContext();
- Log.d(TAG,"mark before startService, "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- context.startService(new Intent(context, DownloadService.class));
- return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
- }
下面来看DownloadService.java
- public void onCreate() {
- super.onCreate();
- if (Constants.LOGVV) {
- Log.v(Constants.TAG, "Service onCreate");
- }
- if (mSystemFacade == null) {
- mSystemFacade = new RealSystemFacade(this);
- }
- mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- mStorageManager = new StorageManager(this);
- mUpdateThread = new HandlerThread(TAG + "-UpdateThread");
- mUpdateThread.start();
- mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);
- mScanner = new DownloadScanner(this);
- mNotifier = new DownloadNotifier(this);
- mNotifier.cancelAll();
- mObserver = new DownloadManagerContentObserver();
- getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
- true, mObserver);
- }
内容改变最终会调用下面的这个消息处理(过程自己追一下不提了),其中调用了一个方法updateLocked();
- private Handler.Callback mUpdateCallback = new Handler.Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- final int startId = msg.arg1;
- if (DEBUG_LIFECYCLE) Log.v(TAG, "Updating for startId " + startId);
- // Since database is current source of truth, our "active" status
- // depends on database state. We always get one final update pass
- // once the real actions have finished and persisted their state.
- // TODO: switch to asking real tasks to derive active state
- // TODO: handle media scanner timeouts
- final boolean isActive;
- synchronized (mDownloads) {
- isActive = updateLocked();
- }
- if (msg.what == MSG_FINAL_UPDATE) {
- // Dump thread stacks belonging to pool
- for (Map.Entry<Thread, StackTraceElement[]> entry :
- Thread.getAllStackTraces().entrySet()) {
- if (entry.getKey().getName().startsWith("pool")) {
- Log.d(TAG, entry.getKey() + ": " + Arrays.toString(entry.getValue()));
- }
- }
- // Dump speed and update details
- mNotifier.dumpSpeeds();
- Log.wtf(TAG, "Final update pass triggered, isActive=" + isActive
- + "; someone didn't update correctly.");
- }
- if (isActive) {
- // Still doing useful work, keep service alive. These active
- // tasks will trigger another update pass when they're finished.
- // Enqueue delayed update pass to catch finished operations that
- // didn't trigger an update pass; these are bugs.
- enqueueFinalUpdate();
- } else {
- // No active tasks, and any pending update messages can be
- // ignored, since any updates important enough to initiate tasks
- // will always be delivered with a new startId.
- if (stopSelfResult(startId)) {
- if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");
- getContentResolver().unregisterContentObserver(mObserver);
- mScanner.shutdown();
- mUpdateThread.quit();
- }
- }
- return true;
- }
- };
- private boolean updateLocked() {
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- final long now = mSystemFacade.currentTimeMillis();
- boolean isActive = false;
- long nextActionMillis = Long.MAX_VALUE;
- final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());
- final ContentResolver resolver = getContentResolver();
- final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
- null, null, null, null);
- try {
- final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
- final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);
- while (cursor.moveToNext()) {
- final long id = cursor.getLong(idColumn);
- staleIds.remove(id);
- DownloadInfo info = mDownloads.get(id);
- if (info != null) {
- updateDownload(reader, info, now);
- } else {
- info = insertDownloadLocked(reader, now);
- }
- if (info.mDeleted) {
- // Delete download if requested, but only after cleaning up
- if (!TextUtils.isEmpty(info.mMediaProviderUri)) {
- resolver.delete(Uri.parse(info.mMediaProviderUri), null, null);
- }
- deleteFileIfExists(info.mFileName);
- resolver.delete(info.getAllDownloadsUri(), null, null);
- } else {
- // Kick off download task if ready
- Log.d(TAG,"in updateLocked before info.startDownloadIfReady");
- final boolean activeDownload = info.startDownloadIfReady(mExecutor);
- // Kick off media scan if completed
- final boolean activeScan = info.startScanIfReady(mScanner);
- if (DEBUG_LIFECYCLE && (activeDownload || activeScan)) {
- Log.v(TAG, "Download " + info.mId + ": activeDownload=" + activeDownload
- + ", activeScan=" + activeScan);
- }
- isActive |= activeDownload;
- isActive |= activeScan;
- }
- // Keep track of nearest next action
- nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);
- }
- } finally {
- cursor.close();
- }
- // Clean up stale downloads that disappeared
- for (Long id : staleIds) {
- deleteDownloadLocked(id);
- }
- // Update notifications visible to user
- mNotifier.updateWith(mDownloads.values());
- // Set alarm when next action is in future. It's okay if the service
- // continues to run in meantime, since it will kick off an update pass.
- if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) {
- if (Constants.LOGV) {
- Log.v(TAG, "scheduling start in " + nextActionMillis + "ms");
- }
- final Intent intent = new Intent(Constants.ACTION_RETRY);
- intent.setClass(this, DownloadReceiver.class);
- mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + nextActionMillis,
- PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT));
- }
- return isActive;
- }
在DownloadInfo.java中的方法startDownloadIfReady,会检测是否准备好了下载,准备好了就创建DownloadThread,并且启动该线程
- public boolean startDownloadIfReady(ExecutorService executor) {
- synchronized (this) {
- final boolean isReady = isReadyToDownload();
- final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
- if (isReady && !isActive) {
- if (mStatus != Impl.STATUS_RUNNING) {
- mStatus = Impl.STATUS_RUNNING;
- ContentValues values = new ContentValues();
- values.put(Impl.COLUMN_STATUS, mStatus);
- mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
- }
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- mTask = new DownloadThread(
- mContext, mSystemFacade, this, mStorageManager, mNotifier);
- mSubmittedTask = executor.submit(mTask);
- }
- return isReady;
- }
- }
- public void run() {
- Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- try {
- runInternal();
- } finally {
- mNotifier.notifyDownloadSpeed(mInfo.mId, 0);
- }
- }
- private void runInternal() {
- // Skip when download already marked as finished; this download was
- // probably started again while racing with UpdateThread.
- if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mInfo.mId)
- == Downloads.Impl.STATUS_SUCCESS) {
- Log.d(TAG, "Download " + mInfo.mId + " already finished; skipping");
- return;
- }
- State state = new State(mInfo);
- PowerManager.WakeLock wakeLock = null;
- int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
- int numFailed = mInfo.mNumFailed;
- String errorMsg = null;
- final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext);
- final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- try {
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
- wakeLock.setWorkSource(new WorkSource(mInfo.mUid));
- wakeLock.acquire();
- // while performing download, register for rules updates
- netPolicy.registerListener(mPolicyListener);
- Log.i(Constants.TAG, "Download " + mInfo.mId + " starting");
- // Remember which network this download started on; used to
- // determine if errors were due to network changes.
- final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
- if (info != null) {
- state.mNetworkType = info.getType();
- }
- // Network traffic on this thread should be counted against the
- // requesting UID, and is tagged with well-known value.
- TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD);
- TrafficStats.setThreadStatsUid(mInfo.mUid);
- try {
- // TODO: migrate URL sanity checking into client side of API
- state.mUrl = new URL(state.mRequestUri);
- } catch (MalformedURLException e) {
- throw new StopRequestException(STATUS_BAD_REQUEST, e);
- }
- executeDownload(state);
- finalizeDestinationFile(state);
- finalStatus = Downloads.Impl.STATUS_SUCCESS;
- } catch (StopRequestException error) {
- // remove the cause before printing, in case it contains PII
- errorMsg = error.getMessage();
- String msg = "Aborting request for download " + mInfo.mId + ": " + errorMsg;
- Log.w(Constants.TAG, msg);
- if (Constants.LOGV) {
- Log.w(Constants.TAG, msg, error);
- }
- finalStatus = error.getFinalStatus();
- // Nobody below our level should request retries, since we handle
- // failure counts at this level.
- if (finalStatus == STATUS_WAITING_TO_RETRY) {
- throw new IllegalStateException("Execution should always throw final error codes");
- }
- // Some errors should be retryable, unless we fail too many times.
- if (isStatusRetryable(finalStatus)) {
- if (state.mGotData) {
- numFailed = 1;
- } else {
- numFailed += 1;
- }
- if (numFailed < Constants.MAX_RETRIES) {
- final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
- if (info != null && info.getType() == state.mNetworkType
- && info.isConnected()) {
- // Underlying network is still intact, use normal backoff
- finalStatus = STATUS_WAITING_TO_RETRY;
- } else {
- // Network changed, retry on any next available
- finalStatus = STATUS_WAITING_FOR_NETWORK;
- }
- }
- }
- // fall through to finally block
- } catch (Throwable ex) {
- errorMsg = ex.getMessage();
- String msg = "Exception for id " + mInfo.mId + ": " + errorMsg;
- Log.w(Constants.TAG, msg, ex);
- finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
- // falls through to the code that reports an error
- } finally {
- if (finalStatus == STATUS_SUCCESS) {
- TrafficStats.incrementOperationCount(1);
- }
- TrafficStats.clearThreadStatsTag();
- TrafficStats.clearThreadStatsUid();
- cleanupDestination(state, finalStatus);
- notifyDownloadCompleted(state, finalStatus, errorMsg, numFailed);
- Log.i(Constants.TAG, "Download " + mInfo.mId + " finished with status "
- + Downloads.Impl.statusToString(finalStatus));
- netPolicy.unregisterListener(mPolicyListener);
- if (wakeLock != null) {
- wakeLock.release();
- wakeLock = null;
- }
- }
- mStorageManager.incrementNumDownloadsSoFar();
- private void executeDownload(State state) throws StopRequestException {
- state.resetBeforeExecute();
- setupDestinationFile(state);
- // skip when already finished; remove after fixing race in 5217390
- if (state.mCurrentBytes == state.mTotalBytes) {
- Log.i(Constants.TAG, "Skipping initiating request for download " +
- mInfo.mId + "; already completed");
- return;
- }
- while (state.mRedirectionCount++ < Constants.MAX_REDIRECTS) {
- // Open connection and follow any redirects until we have a useful
- // response with body.
- HttpURLConnection conn = null;
- try {
- checkConnectivity();
- conn = (HttpURLConnection) state.mUrl.openConnection();
- conn.setInstanceFollowRedirects(false);
- conn.setConnectTimeout(DEFAULT_TIMEOUT);
- conn.setReadTimeout(DEFAULT_TIMEOUT);
- addRequestHeaders(state, conn);
- final int responseCode = conn.getResponseCode();
- switch (responseCode) {
- case HTTP_OK:
- if (state.mContinuingDownload) {
- throw new StopRequestException(
- STATUS_CANNOT_RESUME, "Expected partial, but received OK");
- }
- processResponseHeaders(state, conn);
- transferData(state, conn);
- return;
- case HTTP_PARTIAL:
- if (!state.mContinuingDownload) {
- throw new StopRequestException(
- STATUS_CANNOT_RESUME, "Expected OK, but received partial");
- }
- transferData(state, conn);
- return;
- case HTTP_MOVED_PERM:
- case HTTP_MOVED_TEMP:
- case HTTP_SEE_OTHER:
- case HTTP_TEMP_REDIRECT:
- final String location = conn.getHeaderField("Location");
- state.mUrl = new URL(state.mUrl, location);
- if (responseCode == HTTP_MOVED_PERM) {
- // Push updated URL back to database
- state.mRequestUri = state.mUrl.toString();
- }
- continue;
- case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
- throw new StopRequestException(
- STATUS_CANNOT_RESUME, "Requested range not satisfiable");
- case HTTP_UNAVAILABLE:
- parseRetryAfterHeaders(state, conn);
- throw new StopRequestException(
- HTTP_UNAVAILABLE, conn.getResponseMessage());
- case HTTP_INTERNAL_ERROR:
- throw new StopRequestException(
- HTTP_INTERNAL_ERROR, conn.getResponseMessage());
- default:
- StopRequestException.throwUnhandledHttpError(
- responseCode, conn.getResponseMessage());
- }
- } catch (IOException e) {
- // Trouble with low-level sockets
- throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);
- } finally {
- if (conn != null) conn.disconnect();
- }
- }
- throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");
- }
1. 在Broswer的设置里面添加路径设置选项,设置好路径后保存在SharePreference中,在下载的时候onDownloadStartNoStream ---> setDestinationInExternalPublicDir ,把设置的路径作为参数传递给DownloadManager,在DownloadManager的setDestinationInExternalPublicDir 方法中根据路径,直接new File,然后保存到相应的ContentResolver中,最后该ContentResolver会被写入sqlite,并且转换成为DownloadInfo。
该方法遇到的问题是一点击下载,浏览器就重启,查看设置的路径是被创建好了的,跟踪代码,添加打印log,找到原因为:
1. DownloadProvider.java中的insert方法,调用checkFileUriDestination(values);这个方法有个检测路径是什么开始的,如果不是在它规定的范围,就抛出异常,canonicalPath.startsWith(externalPath)
- 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);
- }
- }
---> storageManager.verifySpace 判断,如果不是Manifest.xml中设置的路径,就会抛出异常。
- static String generateSaveFile(
- Context context,
- String url,
- String hint,
- String contentDisposition,
- String contentLocation,
- String mimeType,
- int destination,
- long contentLength,
- StorageManager storageManager) throws StopRequestException {
- if (contentLength < 0) {
- contentLength = 0;
- }
- String path;
- File base = null;
- if (destination == Downloads.Impl.DESTINATION_FILE_URI) {
- path = Uri.parse(hint).getPath();
- File destdir = new File(path).getParentFile();
- destdir.mkdirs();
- } else {
- base = storageManager.locateDestinationDirectory(mimeType, destination,
- contentLength);
- path = chooseFilename(url, hint, contentDisposition, contentLocation,
- destination);
- }
- storageManager.verifySpace(destination, path, contentLength);
- if (DownloadDrmHelper.isDrmConvertNeeded(mimeType)) {
- path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
- }
- path = getFullPath(path, mimeType, destination, base);
- return path;
- }
- void verifySpace(int destination, String path, long length) throws StopRequestException {
- resetBytesDownloadedSinceLastCheckOnSpace();
- File dir = null;
- if (Constants.LOGV) {
- Log.i(Constants.TAG, "in verifySpace, destination: " + destination +
- ", path: " + path + ", length: " + length);
- }
- if (path == null) {
- throw new IllegalArgumentException("path can't be null");
- }
- switch (destination) {
- case Downloads.Impl.DESTINATION_CACHE_PARTITION:
- case Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING:
- case Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE:
- dir = mDownloadDataDir;
- break;
- case Downloads.Impl.DESTINATION_EXTERNAL:
- dir = mExternalStorageDir;
- break;
- case Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION:
- dir = mSystemCacheDir;
- break;
- case Downloads.Impl.DESTINATION_FILE_URI:
- if (path.startsWith(mExternalStorageDir.getPath())) {
- dir = mExternalStorageDir;
- } else if (path.startsWith(mDownloadDataDir.getPath())) {
- dir = mDownloadDataDir;
- } else if (path.startsWith(mSystemCacheDir.getPath())) {
- dir = mSystemCacheDir;
- }
- break;
- }
- if (dir == null) {
- throw new IllegalStateException("invalid combination of destination: " + destination +
- ", path: " + path);
- }
- findSpace(dir, length, destination);
- }
解决办法:注释掉相关的异常抛出,或者在Manifest.xml中添加相关路径前缀
- <provider android:name=".DownloadProvider"
- android:authorities="downloads" android:exported="true">
- <!-- Anyone can access /my_downloads, the provider internally restricts access by UID for
- these URIs -->
- <path-permission android:pathPrefix="/my_downloads"
- android:permission="android.permission.INTERNET"/>
- <!-- to access /all_downloads, ACCESS_ALL_DOWNLOADS permission is required -->
- <path-permission android:pathPrefix="/all_downloads"
- android:permission="android.permission.ACCESS_ALL_DOWNLOADS"/>
- <!-- Temporary, for backwards compatibility -->
- <path-permission android:pathPrefix="/download"
- android:permission="android.permission.INTERNET"/>
- <!-- Apps with access to /all_downloads/... can grant permissions, allowing them to share
- downloaded files with other viewers -->
- <grant-uri-permission android:pathPrefix="/all_downloads/"/>
- <!-- Apps with access to /my_downloads/... can grant permissions, allowing them to share
- downloaded files with other viewers -->
- <grant-uri-permission android:pathPrefix="/my_downloads/"/>
- </provider>
有试过在DownloadProvider中添加如下功能:在点击下载的时候,弹出对话框,选择下载路径;因为未存档,所以相关代码没贴出来,也没有时间重新写了,如果你对整个过程都了解了,相信实现起来应该也会很快的