Android Broswer下载路径设置

来源:互联网 发布:触摸屏幕软件 编辑:程序博客网 时间:2024/05/17 22:52

Android Broswer下载路径设置

从点击下载开始看
[java] view plaincopy
  1. Tab.java //有一个下载监听,调用mWebViewController.onDownloadStart  
  2.   
  3.         mDownloadListener = new BrowserDownloadListener() {  
  4.             public void onDownloadStart(String url, String userAgent,  
  5.                     String contentDisposition, String mimetype, String referer,  
  6.                     long contentLength) {  
  7.                     Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  8.                 mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,  
  9.                         mimetype, referer, contentLength);  
  10.             }  
  11.         };  

中间经过一系列传递,最终调用到DownloadHandler.java中的onDownloadStart方法

onDownloadStart ---> onDownloadStartNoStream

[java] view plaincopy
  1. static void onDownloadStartNoStream(Activity activity,  
  2.             String url, String userAgent, String contentDisposition,  
  3.             String mimetype, String referer, boolean privateBrowsing) {  
  4.     Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  5.         String filename = URLUtil.guessFileName(url,  
  6.                 contentDisposition, mimetype);  
  7.   
  8.         // Check to see if we have an SDCard  
  9.         String status = Environment.getExternalStorageState();  
  10.         if (!status.equals(Environment.MEDIA_MOUNTED)) {  
  11.             int title;  
  12.             String msg;  
  13.   
  14.             // Check to see if the SDCard is busy, same as the music app  
  15.             if (status.equals(Environment.MEDIA_SHARED)) {  
  16.                 msg = activity.getString(R.string.download_sdcard_busy_dlg_msg);  
  17.                 title = R.string.download_sdcard_busy_dlg_title;  
  18.             } else {  
  19.                 msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename);  
  20.                 title = R.string.download_no_sdcard_dlg_title;  
  21.             }  
  22.   
  23.             new AlertDialog.Builder(activity)  
  24.                 .setTitle(title)  
  25.                 .setIconAttribute(android.R.attr.alertDialogIcon)  
  26.                 .setMessage(msg)  
  27.                 .setPositiveButton(R.string.ok, null)  
  28.                 .show();  
  29.             return;  
  30.         }  
  31.   
  32.         // java.net.URI is a lot stricter than KURL so we have to encode some  
  33.         // extra characters. Fix for b 2538060 and b 1634719  
  34.         WebAddress webAddress;  
  35.         try {  
  36.             webAddress = new WebAddress(url);  
  37.             webAddress.setPath(encodePath(webAddress.getPath()));  
  38.         } catch (Exception e) {  
  39.             // This only happens for very bad urls, we want to chatch the  
  40.             // exception here  
  41.             Log.e(LOGTAG, "Exception trying to parse url:" + url);  
  42.             return;  
  43.         }  
  44.   
  45.         String addressString = webAddress.toString();  
  46.         Uri uri = Uri.parse(addressString);  
  47.         final DownloadManager.Request request;  
  48.         try {  
  49.             request = new DownloadManager.Request(uri);  
  50.         } catch (IllegalArgumentException e) {  
  51.             Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();  
  52.             return;  
  53.         }  
  54.         request.setMimeType(mimetype);  
  55.         // set downloaded file destination to /sdcard/Download.  
  56.         // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype?  
  57.         request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);            //设置下载路径  
  58.         // let this downloaded file be scanned by MediaScanner - so that it can   
  59.         // show up in Gallery app, for example.  
  60.         request.allowScanningByMediaScanner();  
  61.         request.setDescription(webAddress.getHost());  
  62.         // XXX: Have to use the old url since the cookies were stored using the  
  63.         // old percent-encoded url.  
  64.         String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);  
  65.         request.addRequestHeader("cookie", cookies);  
  66.         request.addRequestHeader("User-Agent", userAgent);  
  67.         request.addRequestHeader("Referer", referer);  
  68.         request.setNotificationVisibility(  
  69.                 DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);  
  70.         if (mimetype == null) {  
  71.             if (TextUtils.isEmpty(addressString)) {  
  72.                 return;  
  73.             }  
  74.             // We must have long pressed on a link or image to download it. We  
  75.             // are not sure of the mimetype in this case, so do a head request  
  76.             new FetchUrlMimeType(activity, request, addressString, cookies,  
  77.                     userAgent).start();  
  78.         } else {  
  79.             final DownloadManager manager  
  80.                     = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);  
  81.             new Thread("Browser download") {  
  82.                 public void run() {  
  83.                     manager.enqueue(request);  
  84.                     Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  85.                 }  
  86.             }.start();  
  87.         }  
  88.         Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT)  
  89.                 .show();  
  90.     }  

request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); 跟踪代码是DownloadManager.java调用Environment.java里面的方法设置默认下载路径,设置完之后保存在request里面,然后获取系统的DownloadManager,创建线程,调用DownloadManager里面的manager.enqueue(request),并且将设置好的request传递过去。

下面来看enqueue,DownloadManager.java

[java] view plaincopy
  1. /** 
  2.  * Enqueue a new download.  The download will start automatically once the download manager is 
  3.  * ready to execute it and connectivity is available. 
  4.  * 
  5.  * @param request the parameters specifying this download 
  6.  * @return an ID for the download, unique across the system.  This ID is used to make future 
  7.  * calls related to this download. 
  8.  */  
  9. public long enqueue(Request request) {  
  10.   
  11.     ContentValues values = request.toContentValues(mPackageName);  
  12.     Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);  
  13.     long id = Long.parseLong(downloadUri.getLastPathSegment());  
  14.     return id;  
  15. }  

做了2个事情,1:根据request得到ContentValues;2:将改ContentValues插入到mResolver;

第一个事情没什么好说的,看第二个

mResolver的类型是ContentResolver,在DownloadManager构造函数中初始化;DownloadManager这个服务的创建在ContextImpl.java

[java] view plaincopy
  1. registerService(DOWNLOAD_SERVICE, new ServiceFetcher() {  
  2.         public Object createService(ContextImpl ctx) {  
  3.             return new DownloadManager(ctx.getContentResolver(), ctx.getPackageName());  
  4.         }});  

这个插入动作insert 经过一系列跟踪(过程不表),最终调用的是DownloadProvider.java中的insert,代码很长,大致意思为把数据存入sqlite里面,通知ContentChanged(这个会被DownloadService.java中的DownloadManagerContentObserver检测到),启动DownloadService。
[java] view plaincopy
  1.   public Uri insert(final Uri uri, final ContentValues values) {  
  2.   Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  3.   
  4. Context mcontext = getContext();  
  5.     Intent intent=new Intent(mcontext,com.android.providers.downloads.MyFileManager.class);  
  6. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  7. mcontext.startActivity(intent);  
  8.   
  9.   
  10.   
  11. String str="";  
  12.   
  13. SharedPreferences sharedPreferences = getContext().getSharedPreferences("mark_sp", Context.MODE_WORLD_READABLE);  
  14. str=sharedPreferences.getString("mark_download_file_route","");  
  15. Log.d(TAG,"file_route="+str);  
  16.   
  17.       checkInsertPermissions(values);  
  18.       SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
  19.   
  20.       // note we disallow inserting into ALL_DOWNLOADS  
  21.       int match = sURIMatcher.match(uri);  
  22.       if (match != MY_DOWNLOADS) {  
  23.           Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);  
  24.           throw new IllegalArgumentException("Unknown/Invalid URI " + uri);  
  25.       }  
  26.   
  27.       // copy some of the input values as it  
  28.       ContentValues filteredValues = new ContentValues();  
  29.       copyString(Downloads.Impl.COLUMN_URI, values, filteredValues);  
  30.       copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues);  
  31.       copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);  
  32.       copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues);  
  33.       copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues);  
  34.       copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);  
  35.   
  36.       boolean isPublicApi =  
  37.               values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;  
  38.   
  39.       // validate the destination column  
  40.       Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);  
  41.       if (dest != null) {  
  42.           if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)  
  43.                   != PackageManager.PERMISSION_GRANTED  
  44.                   && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION  
  45.                           || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING  
  46.                           || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) {  
  47.               throw new SecurityException("setting destination to : " + dest +  
  48.                       " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted");  
  49.           }  
  50.           // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically  
  51.           // switch to non-purgeable download  
  52.           boolean hasNonPurgeablePermission =  
  53.                   getContext().checkCallingPermission(  
  54.                           Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE)  
  55.                           == PackageManager.PERMISSION_GRANTED;  
  56.           if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE  
  57.                   && hasNonPurgeablePermission) {  
  58.               dest = Downloads.Impl.DESTINATION_CACHE_PARTITION;  
  59.           }  
  60.           if (dest == Downloads.Impl.DESTINATION_FILE_URI) {  
  61.               getContext().enforcePermission(  
  62.                       android.Manifest.permission.WRITE_EXTERNAL_STORAGE,  
  63.                       Binder.getCallingPid(), Binder.getCallingUid(),  
  64.                       "need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI");  
  65.               checkFileUriDestination(values);  
  66.           } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) {  
  67.               getContext().enforcePermission(  
  68.                       android.Manifest.permission.ACCESS_CACHE_FILESYSTEM,  
  69.                       Binder.getCallingPid(), Binder.getCallingUid(),  
  70.                       "need ACCESS_CACHE_FILESYSTEM permission to use system cache");  
  71.           }  
  72.           filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest);  
  73.       }  
  74.   
  75.       // validate the visibility column  
  76.       Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY);  
  77.       if (vis == null) {  
  78.           if (dest == Downloads.Impl.DESTINATION_EXTERNAL) {  
  79.               filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,  
  80.                       Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);  
  81.           } else {  
  82.               filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,  
  83.                       Downloads.Impl.VISIBILITY_HIDDEN);  
  84.           }  
  85.       } else {  
  86.           filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis);  
  87.       }  
  88.       // copy the control column as is  
  89.       copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);  
  90.   
  91.       /* 
  92.        * requests coming from 
  93.        * DownloadManager.addCompletedDownload(String, String, String, 
  94.        * boolean, String, String, long) need special treatment 
  95.        */  
  96.       if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==  
  97.               Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {  
  98.           // these requests always are marked as 'completed'  
  99.           filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);  
  100.           filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES,  
  101.                   values.getAsLong(Downloads.Impl.COLUMN_TOTAL_BYTES));  
  102.           filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);  
  103.           copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues);  
  104.           copyString(Downloads.Impl._DATA, values, filteredValues);  
  105.           copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);  
  106.       } else {  
  107.           filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);  
  108.           filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);  
  109.           filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);  
  110.       }  
  111.   
  112.       // set lastupdate to current time  
  113.       long lastMod = mSystemFacade.currentTimeMillis();  
  114.       filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod);  
  115.   
  116.       // use packagename of the caller to set the notification columns  
  117.       String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);  
  118.       String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);  
  119.       if (pckg != null && (clazz != null || isPublicApi)) {  
  120.           int uid = Binder.getCallingUid();  
  121.           try {  
  122.               if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) {  
  123.                   filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg);  
  124.                   if (clazz != null) {  
  125.                       filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz);  
  126.                   }  
  127.               }  
  128.           } catch (PackageManager.NameNotFoundException ex) {  
  129.               /* ignored for now */  
  130.           }  
  131.       }  
  132.   
  133.       // copy some more columns as is  
  134.       copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues);  
  135.       copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues);  
  136.       copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues);  
  137.       copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues);  
  138.   
  139.       // UID, PID columns  
  140.       if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)  
  141.               == PackageManager.PERMISSION_GRANTED) {  
  142.           copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues);  
  143.       }  
  144.       filteredValues.put(Constants.UID, Binder.getCallingUid());  
  145.       if (Binder.getCallingUid() == 0) {  
  146.           copyInteger(Constants.UID, values, filteredValues);  
  147.       }  
  148.   
  149.       // copy some more columns as is  
  150.       copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, "");  
  151.       copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, "");  
  152.   
  153.       // is_visible_in_downloads_ui column  
  154.       if (values.containsKey(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI)) {  
  155.           copyBoolean(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, values, filteredValues);  
  156.       } else {  
  157.           // by default, make external downloads visible in the UI  
  158.           boolean isExternal = (dest == null || dest == Downloads.Impl.DESTINATION_EXTERNAL);  
  159.           filteredValues.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, isExternal);  
  160.       }  
  161.   
  162.       // public api requests and networktypes/roaming columns  
  163.       if (isPublicApi) {  
  164.           copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues);  
  165.           copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues);  
  166.           copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues);  
  167.       }  
  168.   
  169.       if (Constants.LOGVV) {  
  170.           Log.v(Constants.TAG, "initiating download with UID "  
  171.                   + filteredValues.getAsInteger(Constants.UID));  
  172.           if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) {  
  173.               Log.v(Constants.TAG, "other UID " +  
  174.                       filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID));  
  175.           }  
  176.       }  
  177.   
  178.       long rowID = db.insert(DB_TABLE, null, filteredValues);  
  179.       if (rowID == -1) {  
  180.           Log.d(Constants.TAG, "couldn't insert into downloads database");  
  181.           return null;  
  182.       }  
  183.   
  184.       insertRequestHeaders(db, rowID, values);  
  185.       notifyContentChanged(uri, match);  
  186.   
  187.       // Always start service to handle notifications and/or scanning  
  188.       final Context context = getContext();  
  189. Log.d(TAG,"mark before startService, "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  190.       context.startService(new Intent(context, DownloadService.class));  
  191.   
  192.       return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);  
  193.   }  

下面来看DownloadService.java 
[java] view plaincopy
  1. public void onCreate() {  
  2.     super.onCreate();  
  3.     if (Constants.LOGVV) {  
  4.         Log.v(Constants.TAG, "Service onCreate");  
  5.     }  
  6.   
  7.     if (mSystemFacade == null) {  
  8.         mSystemFacade = new RealSystemFacade(this);  
  9.     }  
  10.   
  11.     mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);  
  12.     mStorageManager = new StorageManager(this);  
  13.   
  14.     mUpdateThread = new HandlerThread(TAG + "-UpdateThread");  
  15.     mUpdateThread.start();  
  16.     mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);  
  17.   
  18.     mScanner = new DownloadScanner(this);  
  19.   
  20.     mNotifier = new DownloadNotifier(this);  
  21.     mNotifier.cancelAll();  
  22.   
  23.     mObserver = new DownloadManagerContentObserver();  
  24.     getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,  
  25.             true, mObserver);  
  26. }  

内容改变最终会调用下面的这个消息处理(过程自己追一下不提了),其中调用了一个方法updateLocked();
[java] view plaincopy
  1. private Handler.Callback mUpdateCallback = new Handler.Callback() {  
  2.     @Override  
  3.     public boolean handleMessage(Message msg) {  
  4.         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  5.   
  6.         final int startId = msg.arg1;  
  7.         if (DEBUG_LIFECYCLE) Log.v(TAG, "Updating for startId " + startId);  
  8.   
  9.         // Since database is current source of truth, our "active" status  
  10.         // depends on database state. We always get one final update pass  
  11.         // once the real actions have finished and persisted their state.  
  12.   
  13.         // TODO: switch to asking real tasks to derive active state  
  14.         // TODO: handle media scanner timeouts  
  15.   
  16.         final boolean isActive;  
  17.         synchronized (mDownloads) {  
  18.             isActive = updateLocked();  
  19.         }  
  20.   
  21.         if (msg.what == MSG_FINAL_UPDATE) {  
  22.             // Dump thread stacks belonging to pool  
  23.             for (Map.Entry<Thread, StackTraceElement[]> entry :  
  24.                     Thread.getAllStackTraces().entrySet()) {  
  25.                 if (entry.getKey().getName().startsWith("pool")) {  
  26.                     Log.d(TAG, entry.getKey() + ": " + Arrays.toString(entry.getValue()));  
  27.                 }  
  28.             }  
  29.   
  30.             // Dump speed and update details  
  31.             mNotifier.dumpSpeeds();  
  32.   
  33.             Log.wtf(TAG, "Final update pass triggered, isActive=" + isActive  
  34.                     + "; someone didn't update correctly.");  
  35.         }  
  36.   
  37.         if (isActive) {  
  38.             // Still doing useful work, keep service alive. These active  
  39.             // tasks will trigger another update pass when they're finished.  
  40.   
  41.             // Enqueue delayed update pass to catch finished operations that  
  42.             // didn't trigger an update pass; these are bugs.  
  43.             enqueueFinalUpdate();  
  44.   
  45.         } else {  
  46.             // No active tasks, and any pending update messages can be  
  47.             // ignored, since any updates important enough to initiate tasks  
  48.             // will always be delivered with a new startId.  
  49.   
  50.             if (stopSelfResult(startId)) {  
  51.                 if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");  
  52.                 getContentResolver().unregisterContentObserver(mObserver);  
  53.                 mScanner.shutdown();  
  54.                 mUpdateThread.quit();  
  55.             }  
  56.         }  
  57.   
  58.         return true;  
  59.     }  
  60. };  
updateLocked();这个方法 从ContentResolver中构建DownloadInfo(insertDownloadLocked ---> reader.newDownloadInfo ---> new DownloadInfo),并且调用info.startDownloadIfReady
[java] view plaincopy
  1. private boolean updateLocked() {  
  2. Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  3.     final long now = mSystemFacade.currentTimeMillis();  
  4.   
  5.     boolean isActive = false;  
  6.     long nextActionMillis = Long.MAX_VALUE;  
  7.   
  8.     final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());  
  9.   
  10.     final ContentResolver resolver = getContentResolver();  
  11.     final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,  
  12.             nullnullnullnull);  
  13.     try {  
  14.         final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);  
  15.         final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);  
  16.         while (cursor.moveToNext()) {  
  17.             final long id = cursor.getLong(idColumn);  
  18.             staleIds.remove(id);  
  19.   
  20.             DownloadInfo info = mDownloads.get(id);  
  21.             if (info != null) {  
  22.                 updateDownload(reader, info, now);  
  23.             } else {  
  24.                 info = insertDownloadLocked(reader, now);  
  25.             }  
  26.   
  27.             if (info.mDeleted) {  
  28.                 // Delete download if requested, but only after cleaning up  
  29.                 if (!TextUtils.isEmpty(info.mMediaProviderUri)) {  
  30.                     resolver.delete(Uri.parse(info.mMediaProviderUri), nullnull);  
  31.                 }  
  32.   
  33.                 deleteFileIfExists(info.mFileName);  
  34.                 resolver.delete(info.getAllDownloadsUri(), nullnull);  
  35.   
  36.             } else {  
  37.                 // Kick off download task if ready  
  38.                 Log.d(TAG,"in updateLocked before info.startDownloadIfReady");  
  39.                 final boolean activeDownload = info.startDownloadIfReady(mExecutor);  
  40.   
  41.                 // Kick off media scan if completed  
  42.                 final boolean activeScan = info.startScanIfReady(mScanner);  
  43.   
  44.                 if (DEBUG_LIFECYCLE && (activeDownload || activeScan)) {  
  45.                     Log.v(TAG, "Download " + info.mId + ": activeDownload=" + activeDownload  
  46.                             + ", activeScan=" + activeScan);  
  47.                 }  
  48.   
  49.                 isActive |= activeDownload;  
  50.                 isActive |= activeScan;  
  51.             }  
  52.   
  53.             // Keep track of nearest next action  
  54.             nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);  
  55.         }  
  56.     } finally {  
  57.         cursor.close();  
  58.     }  
  59.   
  60.     // Clean up stale downloads that disappeared  
  61.     for (Long id : staleIds) {  
  62.         deleteDownloadLocked(id);  
  63.     }  
  64.   
  65.     // Update notifications visible to user  
  66.     mNotifier.updateWith(mDownloads.values());  
  67.   
  68.     // Set alarm when next action is in future. It's okay if the service  
  69.     // continues to run in meantime, since it will kick off an update pass.  
  70.     if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) {  
  71.         if (Constants.LOGV) {  
  72.             Log.v(TAG, "scheduling start in " + nextActionMillis + "ms");  
  73.         }  
  74.   
  75.         final Intent intent = new Intent(Constants.ACTION_RETRY);  
  76.         intent.setClass(this, DownloadReceiver.class);  
  77.         mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + nextActionMillis,  
  78.                 PendingIntent.getBroadcast(this0, intent, PendingIntent.FLAG_ONE_SHOT));  
  79.     }  
  80.   
  81.     return isActive;  
  82. }  

在DownloadInfo.java中的方法startDownloadIfReady,会检测是否准备好了下载,准备好了就创建DownloadThread,并且启动该线程

[java] view plaincopy
  1. public boolean startDownloadIfReady(ExecutorService executor) {  
  2.     synchronized (this) {  
  3.         final boolean isReady = isReadyToDownload();  
  4.         final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();  
  5.         if (isReady && !isActive) {  
  6.             if (mStatus != Impl.STATUS_RUNNING) {  
  7.                 mStatus = Impl.STATUS_RUNNING;  
  8.                 ContentValues values = new ContentValues();  
  9.                 values.put(Impl.COLUMN_STATUS, mStatus);  
  10.                 mContext.getContentResolver().update(getAllDownloadsUri(), values, nullnull);  
  11.             }  
  12. Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  13.             mTask = new DownloadThread(  
  14.                     mContext, mSystemFacade, this, mStorageManager, mNotifier);  
  15.             mSubmittedTask = executor.submit(mTask);  
  16.         }  
  17.         return isReady;  
  18.     }  
  19. }  
DownloadThread.java中的run方法,run --> runInternal ---> executeDownload
[java] view plaincopy
  1. public void run() {  
  2.     Log.d(TAG,"mark "+new Throwable().getStackTrace()[0].getClassName()+"==="+new Throwable().getStackTrace()[0].getMethodName());  
  3.     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  4.     try {  
  5.         runInternal();  
  6.     } finally {  
  7.         mNotifier.notifyDownloadSpeed(mInfo.mId, 0);  
  8.     }  
  9. }  
  10.   
  11. private void runInternal() {  
  12.     // Skip when download already marked as finished; this download was  
  13.     // probably started again while racing with UpdateThread.  
  14.     if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mInfo.mId)  
  15.             == Downloads.Impl.STATUS_SUCCESS) {  
  16.         Log.d(TAG, "Download " + mInfo.mId + " already finished; skipping");  
  17.         return;  
  18.     }  
  19.   
  20.     State state = new State(mInfo);  
  21.     PowerManager.WakeLock wakeLock = null;  
  22.     int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;  
  23.     int numFailed = mInfo.mNumFailed;  
  24.     String errorMsg = null;  
  25.   
  26.     final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext);  
  27.     final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);  
  28.   
  29.     try {  
  30.         wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);  
  31.         wakeLock.setWorkSource(new WorkSource(mInfo.mUid));  
  32.         wakeLock.acquire();  
  33.   
  34.         // while performing download, register for rules updates  
  35.         netPolicy.registerListener(mPolicyListener);  
  36.   
  37.         Log.i(Constants.TAG, "Download " + mInfo.mId + " starting");  
  38.   
  39.         // Remember which network this download started on; used to  
  40.         // determine if errors were due to network changes.  
  41.         final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);  
  42.         if (info != null) {  
  43.             state.mNetworkType = info.getType();  
  44.         }  
  45.   
  46.         // Network traffic on this thread should be counted against the  
  47.         // requesting UID, and is tagged with well-known value.  
  48.         TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD);  
  49.         TrafficStats.setThreadStatsUid(mInfo.mUid);  
  50.   
  51.         try {  
  52.             // TODO: migrate URL sanity checking into client side of API  
  53.             state.mUrl = new URL(state.mRequestUri);  
  54.         } catch (MalformedURLException e) {  
  55.             throw new StopRequestException(STATUS_BAD_REQUEST, e);  
  56.         }  
  57.   
  58.         executeDownload(state);  
  59.   
  60.         finalizeDestinationFile(state);  
  61.         finalStatus = Downloads.Impl.STATUS_SUCCESS;  
  62.     } catch (StopRequestException error) {  
  63.         // remove the cause before printing, in case it contains PII  
  64.         errorMsg = error.getMessage();  
  65.         String msg = "Aborting request for download " + mInfo.mId + ": " + errorMsg;  
  66.         Log.w(Constants.TAG, msg);  
  67.         if (Constants.LOGV) {  
  68.             Log.w(Constants.TAG, msg, error);  
  69.         }  
  70.         finalStatus = error.getFinalStatus();  
  71.   
  72.         // Nobody below our level should request retries, since we handle  
  73.         // failure counts at this level.  
  74.         if (finalStatus == STATUS_WAITING_TO_RETRY) {  
  75.             throw new IllegalStateException("Execution should always throw final error codes");  
  76.         }  
  77.   
  78.         // Some errors should be retryable, unless we fail too many times.  
  79.         if (isStatusRetryable(finalStatus)) {  
  80.             if (state.mGotData) {  
  81.                 numFailed = 1;  
  82.             } else {  
  83.                 numFailed += 1;  
  84.             }  
  85.   
  86.             if (numFailed < Constants.MAX_RETRIES) {  
  87.                 final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);  
  88.                 if (info != null && info.getType() == state.mNetworkType  
  89.                         && info.isConnected()) {  
  90.                     // Underlying network is still intact, use normal backoff  
  91.                     finalStatus = STATUS_WAITING_TO_RETRY;  
  92.                 } else {  
  93.                     // Network changed, retry on any next available  
  94.                     finalStatus = STATUS_WAITING_FOR_NETWORK;  
  95.                 }  
  96.             }  
  97.         }  
  98.   
  99.         // fall through to finally block  
  100.     } catch (Throwable ex) {  
  101.         errorMsg = ex.getMessage();  
  102.         String msg = "Exception for id " + mInfo.mId + ": " + errorMsg;  
  103.         Log.w(Constants.TAG, msg, ex);  
  104.         finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;  
  105.         // falls through to the code that reports an error  
  106.     } finally {  
  107.         if (finalStatus == STATUS_SUCCESS) {  
  108.             TrafficStats.incrementOperationCount(1);  
  109.         }  
  110.   
  111.         TrafficStats.clearThreadStatsTag();  
  112.         TrafficStats.clearThreadStatsUid();  
  113.   
  114.         cleanupDestination(state, finalStatus);  
  115.         notifyDownloadCompleted(state, finalStatus, errorMsg, numFailed);  
  116.   
  117.         Log.i(Constants.TAG, "Download " + mInfo.mId + " finished with status "  
  118.                 + Downloads.Impl.statusToString(finalStatus));  
  119.   
  120.         netPolicy.unregisterListener(mPolicyListener);  
  121.   
  122.         if (wakeLock != null) {  
  123.             wakeLock.release();  
  124.             wakeLock = null;  
  125.         }  
  126.     }  
  127.     mStorageManager.incrementNumDownloadsSoFar();  
执行下载
[java] view plaincopy
  1. private void executeDownload(State state) throws StopRequestException {  
  2.     state.resetBeforeExecute();  
  3.     setupDestinationFile(state);  
  4.   
  5.     // skip when already finished; remove after fixing race in 5217390  
  6.     if (state.mCurrentBytes == state.mTotalBytes) {  
  7.         Log.i(Constants.TAG, "Skipping initiating request for download " +  
  8.               mInfo.mId + "; already completed");  
  9.         return;  
  10.     }  
  11.   
  12.     while (state.mRedirectionCount++ < Constants.MAX_REDIRECTS) {  
  13.         // Open connection and follow any redirects until we have a useful  
  14.         // response with body.  
  15.         HttpURLConnection conn = null;  
  16.         try {  
  17.             checkConnectivity();  
  18.             conn = (HttpURLConnection) state.mUrl.openConnection();  
  19.             conn.setInstanceFollowRedirects(false);  
  20.             conn.setConnectTimeout(DEFAULT_TIMEOUT);  
  21.             conn.setReadTimeout(DEFAULT_TIMEOUT);  
  22.   
  23.             addRequestHeaders(state, conn);  
  24.   
  25.             final int responseCode = conn.getResponseCode();  
  26.             switch (responseCode) {  
  27.                 case HTTP_OK:  
  28.                     if (state.mContinuingDownload) {  
  29.                         throw new StopRequestException(  
  30.                                 STATUS_CANNOT_RESUME, "Expected partial, but received OK");  
  31.                     }  
  32.                     processResponseHeaders(state, conn);  
  33.                     transferData(state, conn);  
  34.                     return;  
  35.   
  36.                 case HTTP_PARTIAL:  
  37.                     if (!state.mContinuingDownload) {  
  38.                         throw new StopRequestException(  
  39.                                 STATUS_CANNOT_RESUME, "Expected OK, but received partial");  
  40.                     }  
  41.                     transferData(state, conn);  
  42.                     return;  
  43.   
  44.                 case HTTP_MOVED_PERM:  
  45.                 case HTTP_MOVED_TEMP:  
  46.                 case HTTP_SEE_OTHER:  
  47.                 case HTTP_TEMP_REDIRECT:  
  48.                     final String location = conn.getHeaderField("Location");  
  49.                     state.mUrl = new URL(state.mUrl, location);  
  50.                     if (responseCode == HTTP_MOVED_PERM) {  
  51.                         // Push updated URL back to database  
  52.                         state.mRequestUri = state.mUrl.toString();  
  53.                     }  
  54.                     continue;  
  55.   
  56.                 case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:  
  57.                     throw new StopRequestException(  
  58.                             STATUS_CANNOT_RESUME, "Requested range not satisfiable");  
  59.   
  60.                 case HTTP_UNAVAILABLE:  
  61.                     parseRetryAfterHeaders(state, conn);  
  62.                     throw new StopRequestException(  
  63.                             HTTP_UNAVAILABLE, conn.getResponseMessage());  
  64.   
  65.                 case HTTP_INTERNAL_ERROR:  
  66.                     throw new StopRequestException(  
  67.                             HTTP_INTERNAL_ERROR, conn.getResponseMessage());  
  68.   
  69.                 default:  
  70.                     StopRequestException.throwUnhandledHttpError(  
  71.                             responseCode, conn.getResponseMessage());  
  72.             }  
  73.         } catch (IOException e) {  
  74.             // Trouble with low-level sockets  
  75.             throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);  
  76.   
  77.         } finally {  
  78.             if (conn != null) conn.disconnect();  
  79.         }  
  80.     }  
  81.   
  82.     throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");  
  83. }  
上面就是整个过程,关于设置下载路径的,我做了2种方式

1. 在Broswer的设置里面添加路径设置选项,设置好路径后保存在SharePreference中,在下载的时候onDownloadStartNoStream --->  setDestinationInExternalPublicDir ,把设置的路径作为参数传递给DownloadManager,在DownloadManager的setDestinationInExternalPublicDir 方法中根据路径,直接new File,然后保存到相应的ContentResolver中,最后该ContentResolver会被写入sqlite,并且转换成为DownloadInfo。

该方法遇到的问题是一点击下载,浏览器就重启,查看设置的路径是被创建好了的,跟踪代码,添加打印log,找到原因为:

1. DownloadProvider.java中的insert方法,调用checkFileUriDestination(values);这个方法有个检测路径是什么开始的,如果不是在它规定的范围,就抛出异常,canonicalPath.startsWith(externalPath)

[java] view plaincopy
  1. private void checkFileUriDestination(ContentValues values) {  
  2.     String fileUri = values.getAsString(Downloads.Impl.COLUMN_FILE_NAME_HINT);  
  3.     if (fileUri == null) {  
  4.         throw new IllegalArgumentException(  
  5.                 "DESTINATION_FILE_URI must include a file URI under COLUMN_FILE_NAME_HINT");  
  6.     }  
  7.     Uri uri = Uri.parse(fileUri);  
  8.     String scheme = uri.getScheme();  
  9.     if (scheme == null || !scheme.equals("file")) {  
  10.         throw new IllegalArgumentException("Not a file URI: " + uri);  
  11.     }  
  12.     final String path = uri.getPath();  
  13.     if (path == null) {  
  14.         throw new IllegalArgumentException("Invalid file URI: " + uri);  
  15.     }  
  16.     try {  
  17.         final String canonicalPath = new File(path).getCanonicalPath();  
  18.         final String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath();  
  19.         if (!canonicalPath.startsWith(externalPath)) {  
  20.             throw new SecurityException("Destination must be on external storage: " + uri);  
  21.         }  
  22.     } catch (IOException e) {  
  23.         throw new SecurityException("Problem resolving path: " + uri);  
  24.     }  
  25. }  
2. 在执行下载的时候DownloadThread.java  executeDownload ---> processResponseHeaders(state, conn) ---> generateSaveFile --->  Helpers.java

---> storageManager.verifySpace    判断,如果不是Manifest.xml中设置的路径,就会抛出异常。

[java] view plaincopy
  1. static String generateSaveFile(  
  2.         Context context,  
  3.         String url,  
  4.         String hint,  
  5.         String contentDisposition,  
  6.         String contentLocation,  
  7.         String mimeType,  
  8.         int destination,  
  9.         long contentLength,  
  10.         StorageManager storageManager) throws StopRequestException {  
  11.     if (contentLength < 0) {  
  12.         contentLength = 0;  
  13.     }  
  14.     String path;  
  15.     File base = null;  
  16.     if (destination == Downloads.Impl.DESTINATION_FILE_URI) {  
  17.         path = Uri.parse(hint).getPath();  
  18.         File destdir = new File(path).getParentFile();  
  19.         destdir.mkdirs();  
  20.     } else {  
  21.         base = storageManager.locateDestinationDirectory(mimeType, destination,  
  22.                 contentLength);  
  23.         path = chooseFilename(url, hint, contentDisposition, contentLocation,  
  24.                                          destination);  
  25.     }  
  26.     storageManager.verifySpace(destination, path, contentLength);  
  27.     if (DownloadDrmHelper.isDrmConvertNeeded(mimeType)) {  
  28.         path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);  
  29.     }  
  30.     path = getFullPath(path, mimeType, destination, base);  
  31.     return path;  
  32. }  

[java] view plaincopy
  1. void verifySpace(int destination, String path, long length) throws StopRequestException {  
  2.     resetBytesDownloadedSinceLastCheckOnSpace();  
  3.     File dir = null;  
  4.     if (Constants.LOGV) {  
  5.         Log.i(Constants.TAG, "in verifySpace, destination: " + destination +  
  6.                 ", path: " + path + ", length: " + length);  
  7.     }  
  8.     if (path == null) {  
  9.         throw new IllegalArgumentException("path can't be null");  
  10.     }  
  11.     switch (destination) {  
  12.         case Downloads.Impl.DESTINATION_CACHE_PARTITION:  
  13.         case Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING:  
  14.         case Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE:  
  15.             dir = mDownloadDataDir;  
  16.             break;  
  17.         case Downloads.Impl.DESTINATION_EXTERNAL:  
  18.             dir = mExternalStorageDir;  
  19.             break;  
  20.         case Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION:  
  21.             dir = mSystemCacheDir;  
  22.             break;  
  23.         case Downloads.Impl.DESTINATION_FILE_URI:  
  24.             if (path.startsWith(mExternalStorageDir.getPath())) {  
  25.                 dir = mExternalStorageDir;  
  26.             } else if (path.startsWith(mDownloadDataDir.getPath())) {  
  27.                 dir = mDownloadDataDir;  
  28.             } else if (path.startsWith(mSystemCacheDir.getPath())) {  
  29.                 dir = mSystemCacheDir;  
  30.             }  
  31.             break;  
  32.      }  
  33.     if (dir == null) {  
  34.         throw new IllegalStateException("invalid combination of destination: " + destination +  
  35.                 ", path: " + path);  
  36.     }  
  37.     findSpace(dir, length, destination);  
  38. }  

解决办法:注释掉相关的异常抛出,或者在Manifest.xml中添加相关路径前缀
[html] view plaincopy
  1. <provider android:name=".DownloadProvider"  
  2.           android:authorities="downloads" android:exported="true">  
  3.   <!-- Anyone can access /my_downloads, the provider internally restricts access by UID for  
  4.        these URIs -->  
  5.   <path-permission android:pathPrefix="/my_downloads"  
  6.                    android:permission="android.permission.INTERNET"/>  
  7.   <!-- to access /all_downloads, ACCESS_ALL_DOWNLOADS permission is required -->  
  8.   <path-permission android:pathPrefix="/all_downloads"  
  9.                    android:permission="android.permission.ACCESS_ALL_DOWNLOADS"/>  
  10.   <!-- Temporary, for backwards compatibility -->  
  11.   <path-permission android:pathPrefix="/download"  
  12.                    android:permission="android.permission.INTERNET"/>  
  13.   <!-- Apps with access to /all_downloads/... can grant permissions, allowing them to share  
  14.        downloaded files with other viewers -->  
  15.   <grant-uri-permission android:pathPrefix="/all_downloads/"/>  
  16.   <!-- Apps with access to /my_downloads/... can grant permissions, allowing them to share  
  17.        downloaded files with other viewers -->  
  18.   <grant-uri-permission android:pathPrefix="/my_downloads/"/>  
  19. </provider>  


有试过在DownloadProvider中添加如下功能:在点击下载的时候,弹出对话框,选择下载路径;因为未存档,所以相关代码没贴出来,也没有时间重新写了,如果你对整个过程都了解了,相信实现起来应该也会很快的
0 0