Android开源:FileDownloader-文件下载引擎

ElizbethHid 8年前
   <h2>FileDownloader</h2>    <p>Android 文件下载引擎,稳定、高效、灵活、简单易用</p>    <h3>特点</h3>    <ul>     <li>简单易用</li>     <li>高并发</li>     <li>灵活</li>     <li>可选择性支持: 独立/非独立进程</li>     <li>自动断点续传</li>    </ul>    <p>需要注意</p>    <ul>     <li>当下载的文件大小可能大于1.99GB(2^31-1 =2_147_483_647 = 1.99GB )的时候, 请使用 FileDownloadLargeFileListener 而不是 FileDownloadListener (同理使用 getLargeFileSofarBytes() 与 getLargeFileTotalBytes() )</li>     <li>暂停: paused, 恢复: 直接调用start,默认就是断点续传</li>     <li>引擎默认会打开避免掉帧的处理(使得在有些情况下回调(FileDownloadListener)不至于太频繁导致ui线程被ddos), 如果你希望关闭这个功能(关闭以后,所有回调会与0.1.9之前的版本一样,所有的回调会立马抛一个消息ui线程(Handler))</li>     <li>如果没有特殊需要,直接通过配置 filedownloader.properties 将 process.non-separate 置为 true ,可以有效减少每次回调IPC带来的I/O。</li>    </ul>    <h2>欢迎提交 Pull requests</h2>    <ul>     <li>尽量多的英文注解。</li>     <li>每个提交尽量的细而精准。</li>     <li>Commit message 遵循: <a href="/misc/goto?guid=4959655417394256205" rel="nofollow,noindex">AngularJS's commit message convention</a> 。</li>     <li>尽可能的遵循IDE的代码检查建议(如 Android Studio 的 'Inspect Code')。</li>    </ul>    <h2>I. 效果</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/10d93285aea08c9f611e3c6a444f7744.gif"> <img src="https://simg.open-open.com/show/77500027c155f9fd37f4caff8ac42060.gif"> <img src="https://simg.open-open.com/show/ef29ac164d604b62d2e7fb6fa3a68464.gif"> <img src="https://simg.open-open.com/show/a326227bfb62a7c481a652195326d8cc.gif"> <img src="https://simg.open-open.com/show/3fc82266a8471a180e9c1d573bdbbf53.gif"> <img src="https://simg.open-open.com/show/0a96bbed1c9c62bcf9e6d3715925461e.gif"></p>    <h2>II. 使用</h2>    <p>在项目中引用:</p>    <pre>  <code class="language-java">compile 'com.liulishuo.filedownloader:library:1.4.2'</code></pre>    <p>全局初始化在 Application.onCreate 中</p>    <pre>  <code class="language-java">public XXApplication extends Application{        ...      @Override      public void onCreate() {          /**           * 仅仅是缓存Application的Context,不耗时           */          FileDownloader.init(getApplicationContext);      }        ...  }</code></pre>    <p>启动单任务下载</p>    <pre>  <code class="language-java">FileDownloader.getImpl().create(url)          .setPath(path)          .setListener(new FileDownloadListener() {              @Override              protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {              }                @Override              protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {              }                @Override              protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {              }                @Override              protected void blockComplete(BaseDownloadTask task) {              }                @Override              protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {              }                @Override              protected void completed(BaseDownloadTask task) {              }                @Override              protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {              }                @Override              protected void error(BaseDownloadTask task, Throwable e) {              }                @Override              protected void warn(BaseDownloadTask task) {              }          }).start();</code></pre>    <p>启动多任务下载</p>    <pre>  <code class="language-java">final FileDownloadListener queueTarget = new FileDownloadListener() {      @Override      protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {      }        @Override      protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {      }        @Override      protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {      }        @Override      protected void blockComplete(BaseDownloadTask task) {      }        @Override      protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {      }        @Override      protected void completed(BaseDownloadTask task) {      }        @Override      protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {      }        @Override      protected void error(BaseDownloadTask task, Throwable e) {      }        @Override      protected void warn(BaseDownloadTask task) {      }  };    // 第一种方式 :    //for (String url : URLS) {  //    FileDownloader.getImpl().create(url)  //            .setCallbackProgressTimes(0) // 由于是队列任务, 这里是我们假设了现在不需要每个任务都回调`FileDownloadListener#progress`, 我们只关系每个任务是否完成, 所以这里这样设置可以很有效的减少ipc.  //            .setListener(queueTarget)  //            .asInQueueTask()  //            .enqueue();  //}    //if(serial){      // 串行执行该队列  //    FileDownloader.getImpl().start(queueTarget, true);  // }    // if(parallel){      // 并行执行该队列  //    FileDownloader.getImpl().start(queueTarget, false);  //}    // 第二种方式:    final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(downloadListener);    final List<BaseDownloadTask> tasks = new ArrayList<>();  for (int i = 0; i < count; i++) {       tasks.add(FileDownloader.getImpl().create(Constant.URLS[i]).setTag(i + 1));  }    queueSet.disableCallbackProgressTimes(); // 由于是队列任务, 这里是我们假设了现在不需要每个任务都回调`FileDownloadListener#progress`, 我们只关系每个任务是否完成, 所以这里这样设置可以很有效的减少ipc.    // 所有任务在下载失败的时候都自动重试一次  queueSet.setAutoRetryTimes(1);    if (serial) {    // 串行执行该任务队列       queueSet.downloadSequentially(tasks);       // 如果你的任务不是一个List,可以考虑使用下面的方式,可读性更强  //      queueSet.downloadSequentially(  //              FileDownloader.getImpl().create(url).setPath(...),  //              FileDownloader.getImpl().create(url).addHeader(...,...),  //              FileDownloader.getImpl().create(url).setPath(...)  //      );  }    if (parallel) {    // 并行执行该任务队列     queueSet.downloadTogether(tasks);     // 如果你的任务不是一个List,可以考虑使用下面的方式,可读性更强  //    queueSet.downloadTogether(  //            FileDownloader.getImpl().create(url).setPath(...),  //            FileDownloader.getImpl().create(url).setPath(...),  //            FileDownloader.getImpl().create(url).setSyncCallback(true)  //    );  }    // 串行任务动态管理也可以使用FileDownloadSerialQueue。</code></pre>    <p>全局接口说明( FileDownloader )</p>    <p>所有的暂停,就是停止,会释放所有资源并且停到所有相关线程,下次启动的时候默认会断点续传</p>    <table>     <thead>      <tr>       <th>方法名</th>       <th>备注</th>      </tr>     </thead>     <tbody>      <tr>       <td>init(Context)</td>       <td>缓存Context,不会启动下载进程</td>      </tr>      <tr>       <td>init(Context, InitCustomMaker)</td>       <td>缓存Context,不会启动下载进程;在下载进程启动的时候,会传入定制化组件</td>      </tr>      <tr>       <td>create(url:String)</td>       <td>创建一个下载任务</td>      </tr>      <tr>       <td>start(listener:FileDownloadListener, isSerial:boolean)</td>       <td>启动是相同监听器的任务,串行/并行启动</td>      </tr>      <tr>       <td>pause(listener:FileDownloadListener)</td>       <td>暂停启动相同监听器的任务</td>      </tr>      <tr>       <td>pauseAll(void)</td>       <td>暂停所有任务</td>      </tr>      <tr>       <td>pause(downloadId)</td>       <td>暂停downloadId的任务</td>      </tr>      <tr>       <td>clear(downloadId, targetFilePath)</td>       <td>强制清理ID为downloadId的任务在filedownloader中的数据</td>      </tr>      <tr>       <td>getSoFar(downloadId)</td>       <td>获得下载Id为downloadId的soFarBytes</td>      </tr>      <tr>       <td>getTotal(downloadId)</td>       <td>获得下载Id为downloadId的totalBytes</td>      </tr>      <tr>       <td>bindService(void)</td>       <td>主动启动下载进程(可事先调用该方法(可以不调用),保证第一次下载的时候没有启动进程的速度消耗)</td>      </tr>      <tr>       <td>unBindService(void)</td>       <td>主动关停下载进程</td>      </tr>      <tr>       <td>unBindServiceIfIdle(void)</td>       <td>如果目前下载进程没有任务正在执行,则关停下载进程</td>      </tr>      <tr>       <td>isServiceConnected(void)</td>       <td>是否已经启动并且连接上下载进程(可参考任务管理demo中的使用)</td>      </tr>      <tr>       <td>getStatusIgnoreCompleted(downloadId)</td>       <td>获取不包含已完成状态的下载状态(如果任务已经下载完成,将收到 INVALID )</td>      </tr>      <tr>       <td>getStatus(id:int, path:String)</td>       <td>获取下载状态</td>      </tr>      <tr>       <td>getStatus(url:String, path:String)</td>       <td>获取下载状态</td>      </tr>      <tr>       <td>setGlobalPost2UIInterval(intervalMillisecond:int)</td>       <td>为了避免掉帧,这里是设置了最多每interval毫秒抛一个消息到ui线程(使用Handler),防止由于回调的过于频繁导致ui线程被ddos导致掉帧。 默认值: 10ms. 如果设置小于0,将会失效,也就是说每个回调都直接抛一个消息到ui线程</td>      </tr>      <tr>       <td>setGlobalHandleSubPackageSize(packageSize:int)</td>       <td>为了避免掉帧, 如果上面的方法设置的间隔是一个小于0的数,这个packageSize将不会生效。packageSize这个值是为了避免在ui线程中一次处理过多回调,结合上面的间隔,就是每个interval毫秒间隔抛一个消息到ui线程,而每个消息在ui线程中处理packageSize个回调。默认值: 5</td>      </tr>      <tr>       <td>enableAvoidDropFrame(void)</td>       <td>开启 避免掉帧处理。就是将抛消息到ui线程的间隔设为默认值10ms, 很明显会影响的是回调不会立马通知到监听器(FileDownloadListener)中,默认值是: 最多10ms处理5个回调到监听器中</td>      </tr>      <tr>       <td>disableAvoidDropFrame(void)</td>       <td>关闭 避免掉帧处理。就是将抛消息到ui线程的间隔设置-1(无效值),这个就是让每个回调都会抛一个消息ui线程中,可能引起掉帧</td>      </tr>      <tr>       <td>isEnabledAvoidDropFrame(void)</td>       <td>是否开启了 避免掉帧处理。默认是开启的</td>      </tr>      <tr>       <td>startForeground(id:int, notification:Notification)</td>       <td>设置FileDownloadService为前台模式,保证用户从最近应用列表移除应用以后下载服务不会被杀</td>      </tr>      <tr>       <td>stopForeground(removeNotification:boolean)</td>       <td>取消FileDownloadService的前台模式</td>      </tr>      <tr>       <td>setTaskCompleted(url:String, path:String, totalBytes:long)</td>       <td>用于告诉FileDownloader引擎,以指定Url与Path的任务已经通过其他方式(非FileDownloader)下载完成</td>      </tr>      <tr>       <td>setTaskCompleted(taskAtomList:List)</td>       <td>用于告诉FileDownloader引擎,指定的一系列的任务都已经通过其他方式(非FileDownloader)下载完成</td>      </tr>      <tr>       <td>setMaxNetworkThreadCount(int)</td>       <td>设置最大并行下载的数目(网络下载线程数), [1,12]</td>      </tr>      <tr>       <td>clearAllTaskData()</td>       <td>清空 filedownloader 数据库中的所有数据</td>      </tr>     </tbody>    </table>    <p>定制化组件接口说明( InitCustomMaker )</p>    <table>     <thead>      <tr>       <th>方法名</th>       <th>需实现接口</th>       <th>已有组件</th>       <th>默认组件</th>       <th>说明</th>      </tr>     </thead>     <tbody>      <tr>       <td>database</td>       <td>FileDownloadDatabase</td>       <td>DefaultDatabaseImpl</td>       <td>DefaultDatabaseImpl</td>       <td>传入定制化数据库组件,用于存储用于断点续传的数据</td>      </tr>      <tr>       <td>connection</td>       <td>FileDownloadConnection</td>       <td>FileDownloadUrlConnection</td>       <td>FileDownloadUrlConnection</td>       <td>传入定制化的网络连接组件,用于下载时建立网络连接</td>      </tr>      <tr>       <td>outputStreamCreator</td>       <td>FileDownloadOutputStream</td>       <td>FileDownloadRandomAccessFile、FileDownloadBufferedOutputStream、FileDownloadOkio</td>       <td>FileDownloadRandomAccessFile</td>       <td>传入输出流组件,用于下载时写文件使用</td>      </tr>      <tr>       <td>maxNetworkThreadCount</td>       <td>-</td>       <td>-</td>       <td>3</td>       <td>传入创建下载引擎时,指定可用的下载线程个数</td>      </tr>     </tbody>    </table>    <p>Task接口说明</p>    <table>     <thead>      <tr>       <th>方法名</th>       <th>备注</th>      </tr>     </thead>     <tbody>      <tr>       <td>setPath(path:String)</td>       <td>下载文件的存储绝对路径</td>      </tr>      <tr>       <td>setPath(path:String, pathAsDirectory:boolean)</td>       <td>如果 pathAsDirectory 是 true , path 就是存储下载文件的文件目录(而不是路径),此时默认情况下文件名 filename 将会默认从 response#header 中的 contentDisposition 中获得</td>      </tr>      <tr>       <td>setListener(listener:FileDownloadListener)</td>       <td>设置监听,可以以相同监听组成队列</td>      </tr>      <tr>       <td>setCallbackProgressTimes(times:int)</td>       <td>设置整个下载过程中 FileDownloadListener#progress 最大回调次数</td>      </tr>      <tr>       <td>setCallbackProgressIgnored()</td>       <td>忽略所有的 FileDownloadListener#progress 的回调</td>      </tr>      <tr>       <td>setCallbackProgressMinInterval(minIntervalMillis:int)</td>       <td>设置每个 FileDownloadListener#progress 之间回调间隔(ms)</td>      </tr>      <tr>       <td>setTag(tag:Object)</td>       <td>内部不会使用,在回调的时候用户自己使用</td>      </tr>      <tr>       <td>setTag(key:int, tag:Object)</td>       <td>用于存储任意的变量方便回调中使用,以key作为索引</td>      </tr>      <tr>       <td>setForceReDownload(isForceReDownload:boolean)</td>       <td>强制重新下载,将会忽略检测文件是否健在</td>      </tr>      <tr>       <td>setFinishListener(listener:FinishListener)</td>       <td>结束监听,仅包含结束(over(void))的监听</td>      </tr>      <tr>       <td>setAutoRetryTimes(autoRetryTimes:int)</td>       <td>当请求或下载或写文件过程中存在错误时,自动重试次数,默认为0次</td>      </tr>      <tr>       <td>setSyncCallback(syncCallback:boolean)</td>       <td>如果设为true, 所有FileDownloadListener中的回调都会直接在下载线程中回调而不抛到ui线程, 默认为false</td>      </tr>      <tr>       <td>addHeader(name:String, value:String)</td>       <td>添加自定义的请求头参数,需要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上( If-Match 与 Range 参数),请勿重复添加导致400或其他错误</td>      </tr>      <tr>       <td>addHeader(line:String)</td>       <td>添加自定义的请求头参数,需要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上( If-Match 与 Range 参数),请勿重复添加导致400或其他错误</td>      </tr>      <tr>       <td>setMinIntervalUpdateSpeed(minIntervalUpdateSpeedMs:int)</td>       <td>设置下载中刷新下载速度的最小间隔</td>      </tr>      <tr>       <td>removeAllHeaders(name:String)</td>       <td>删除由自定义添加上去请求参数为 {name} 的所有键对</td>      </tr>      <tr>       <td>setWifiRequired(isWifiRequired:boolean)</td>       <td>设置任务是否只允许在Wifi网络环境下进行下载。 默认值 false</td>      </tr>      <tr>       <td>asInQueueTask(void):InQueueTask</td>       <td>申明该任务将会是队列任务中的一个任务,并且转化为 InQueueTask ,之后可以调用 InQueueTask#enqueue 将该任务入队以便于接下来启动队列任务时,可以将该任务收编到队列中</td>      </tr>      <tr>       <td>start(void)</td>       <td>启动孤立的下载任务</td>      </tr>      <tr>       <td>pause(void)</td>       <td>暂停下载任务(也可以理解为停止下载,但是在start的时候默认会断点续传)</td>      </tr>      <tr>       <td>getId(void):int</td>       <td>获取唯一Id(内部通过url与path生成)</td>      </tr>      <tr>       <td>getUrl(void):String</td>       <td>获取下载连接</td>      </tr>      <tr>       <td>getCallbackProgressTimes(void):int</td>       <td>获得progress最大回调次数</td>      </tr>      <tr>       <td>getCallbackProgressMinInterval(void):int</td>       <td>获得每个progress之间的回调间隔(ms)</td>      </tr>      <tr>       <td>getPath(void):String</td>       <td>获取文件路径 或 文件目录</td>      </tr>      <tr>       <td>isPathAsDirectory</td>       <td>判断 getPath() 返回的路径是文件存储目录( directory ),还是文件存储路径( directory/filename )</td>      </tr>      <tr>       <td>getTargetFilePath</td>       <td>获取目标文件的存储路径</td>      </tr>      <tr>       <td>getListener(void):FileDownloadListener</td>       <td>获取监听器</td>      </tr>      <tr>       <td>getSoFarBytes(void):int</td>       <td>获取已经下载的字节数</td>      </tr>      <tr>       <td>getTotalBytes(void):int</td>       <td>获取下载文件总大小</td>      </tr>      <tr>       <td>getStatus(void):int</td>       <td>获取当前的状态</td>      </tr>      <tr>       <td>isForceReDownload(void):boolean</td>       <td>是否强制重新下载</td>      </tr>      <tr>       <td>getEx(void):Throwable</td>       <td>获取下载过程抛出的Throwable</td>      </tr>      <tr>       <td>isReusedOldFile(void):boolean</td>       <td>判断是否是直接使用了旧文件(检测是有效文件),没有启动下载</td>      </tr>      <tr>       <td>getTag(void):Object</td>       <td>获取用户setTag进来的Object</td>      </tr>      <tr>       <td>getTag(key:int):Object</td>       <td>根据key获取存储在task中的变量</td>      </tr>      <tr>       <td>isContinue(void):boolean</td>       <td>是否成功断点续传</td>      </tr>      <tr>       <td>getEtag(void):String</td>       <td>获取当前下载获取到的ETag</td>      </tr>      <tr>       <td>getAutoRetryTimes(void):int</td>       <td>自动重试次数</td>      </tr>      <tr>       <td>getRetryingTimes(void):int</td>       <td>当前重试次数。将要开始重试的时候,会将接下来是第几次</td>      </tr>      <tr>       <td>isSyncCallback(void):boolean</td>       <td>是否是设置了所有FileDownloadListener中的回调都直接在下载线程直接回调而不抛到ui线程</td>      </tr>      <tr>       <td>getSpeed():int</td>       <td>获取任务的下载速度, 下载过程中为实时速度,下载结束状态为平均速度</td>      </tr>      <tr>       <td>isUsing():boolean</td>       <td>判断当前的Task对象是否在引擎中启动过</td>      </tr>      <tr>       <td>isWifiRequired():boolean</td>       <td>获取当前任务是否被设置过只允许在Wifi网络环境下下载</td>      </tr>     </tbody>    </table>    <p>监听器( FileDownloadListener )说明</p>    <p>一般的下载回调流程:</p>    <pre>  <code class="language-java">pending -> started -> connected -> (progress <->progress) -> blockComplete -> completed</code></pre>    <p>可能会遇到以下回调而直接终止整个下载过程:</p>    <pre>  <code class="language-java">paused / completed / error / warn</code></pre>    <p>如果检测存在已经下载完成的文件(可以通过 isReusedOldFile 进行决策是否是该情况)(也可以通过 setForceReDownload(true) 来避免该情况):</p>    <pre>  <code class="language-java">blockComplete -> completed</code></pre>    <p>方法说明</p>    <table>     <thead>      <tr>       <th>回调方法</th>       <th>备注</th>       <th>带回数据</th>      </tr>     </thead>     <tbody>      <tr>       <td>pending</td>       <td>等待,已经进入下载队列</td>       <td>数据库中的soFarBytes与totalBytes</td>      </tr>      <tr>       <td>started</td>       <td>结束了pending,并且开始当前任务的Runnable</td>       <td>-</td>      </tr>      <tr>       <td>connected</td>       <td>已经连接上</td>       <td>ETag, 是否断点续传, soFarBytes, totalBytes</td>      </tr>      <tr>       <td>progress</td>       <td>下载进度回调</td>       <td>soFarBytes</td>      </tr>      <tr>       <td>blockComplete</td>       <td>在完成前同步调用该方法,此时已经下载完成</td>       <td>-</td>      </tr>      <tr>       <td>retry</td>       <td>重试之前把将要重试是第几次回调回来</td>       <td>之所以重试遇到Throwable, 将要重试是第几次, soFarBytes</td>      </tr>      <tr>       <td>completed</td>       <td>完成整个下载过程</td>       <td>-</td>      </tr>      <tr>       <td>paused</td>       <td>暂停下载</td>       <td>soFarBytes</td>      </tr>      <tr>       <td>error</td>       <td>下载出现错误</td>       <td>抛出的Throwable</td>      </tr>      <tr>       <td>warn</td>       <td>在下载队列中(正在等待/正在下载)已经存在相同下载连接与相同存储路径的任务</td>       <td>-</td>      </tr>     </tbody>    </table>    <p><img src="https://simg.open-open.com/show/744170a56e70255aa4f0f930e354a1b4.png"></p>    <p>由于 FileDownloadListener 中的方法回调过快,导致掉帧?</p>    <p>你有两种方法可以解决这个问题</p>    <ol>     <li>FileDownloader#enableAvoidDropFrame , 默认 就是开启的</li>     <li>BaseDownloadTask#setSyncCallback , 默认是false, 如果设置为true,所有的回调都会在下载线程直接同步调用而不会抛到ui线程。</li>    </ol>    <p>FileDownloadMonitor</p>    <p>你可以添加一个全局监听器来进行打点或者是调试</p>    <table>     <thead>      <tr>       <th>方法名</th>       <th>备注</th>      </tr>     </thead>     <tbody>      <tr>       <td>setGlobalMonitor(monitor:IMonitor)</td>       <td>设置与替换一个全局监听器到下载引擎中</td>      </tr>      <tr>       <td>releaseGlobalMonitor(void)</td>       <td>释放已经设置到下载引擎中的全局监听器</td>      </tr>      <tr>       <td>getMonitor(void)</td>       <td>获取已经设置到下载引擎中的全局监听器</td>      </tr>     </tbody>    </table>    <p>FileDownloadMonitor.IMonitor</p>    <p>监听器接口类</p>    <table>     <thead>      <tr>       <th>接口</th>       <th>备注</th>      </tr>     </thead>     <tbody>      <tr>       <td>onRequestStart(count:int, serial:boolean, lis:FileDownloadListener)</td>       <td>将会在启动队列任务是回调这个方法</td>      </tr>      <tr>       <td>onRequestStart(task:BaseDownloadTask)</td>       <td>将会在启动单一任务时回调这个方法</td>      </tr>      <tr>       <td>onTaskBegin(task:BaseDownloadTask)</td>       <td>将会在内部接收并开始task的时候回调这个方法(会在 pending 回调之前)</td>      </tr>      <tr>       <td>onTaskStarted(task:BaseDownloadTask)</td>       <td>将会在task结束pending开始task的runnable的时候回调该方法</td>      </tr>      <tr>       <td>onTaskOver(task:BaseDownloadTask)</td>       <td>将会在task走完所有生命周期是回调这个方法</td>      </tr>     </tbody>    </table>    <p>FileDownloadUtils</p>    <table>     <thead>      <tr>       <th>方法名</th>       <th>备注</th>      </tr>     </thead>     <tbody>      <tr>       <td>setDefaultSaveRootPath(path:String)</td>       <td>在整个引擎中没有设置路径时 BaseDownloadTask#setPath 这个路径将会作为它的Root path</td>      </tr>      <tr>       <td>getTempPath</td>       <td>获取用于存储还未下载完成文件的临时存储路径: filename.temp</td>      </tr>      <tr>       <td>isFilenameConverted(context:Context)</td>       <td>判断是否所有数据库中下载中的任务的文件名都已经从 filename (在旧架构中)转为 filename.temp</td>      </tr>     </tbody>    </table>    <p>FileDownloadNotificationHelper</p>    <p>如何快速集成Notification呢? 建议参考 <a href="/misc/goto?guid=4959746786723431513" rel="nofollow,noindex">NotificationMinSetActivity</a> 、 <a href="/misc/goto?guid=4959746786818986315" rel="nofollow,noindex">NotificationSampleActivity</a> 。</p>    <p>filedownloader.properties</p>    <p>如果你需要定制化FileDownloader,可以在你的项目模块的 assets 目录下添加 'filedownloader.properties' 文件(如 /demo/src/main/assets/filedownloader.properties ),然后添加以下可选相关配置。</p>    <p>格式: keyword=value</p>    <table>     <thead>      <tr>       <th>关键字</th>       <th>描述</th>       <th>默认值</th>      </tr>     </thead>     <tbody>      <tr>       <td>http.lenient</td>       <td>如果你遇到了: 'can't know the size of the download file, and its Transfer-Encoding is not Chunked either', 但是你想要忽略类似的返回头不规范的错误,直接将该关键字参数设置为 true 即可,我们将会将其作为 chunck 进行处理</td>       <td>false</td>      </tr>      <tr>       <td>process.non-separate</td>       <td>FileDownloadService 默认是运行在独立进程':filedownloader'上的, 如果你想要FileDownloadService共享并运行在主进程上, 将该关键字参数设置为 true ,可以有效减少IPC产生的I/O</td>       <td>false</td>      </tr>      <tr>       <td>download.min-progress-step</td>       <td>最小缓冲大小,用于判定是否是时候将缓冲区中进度同步到数据库,以及是否是时候要确保下缓存区的数据都已经写文件。值越小,更新会越频繁,下载速度会越慢,但是应对进程被无法预料的情况杀死时会更加安全</td>       <td>65536</td>      </tr>      <tr>       <td>download.min-progress-time</td>       <td>最小缓冲时间,用于判定是否是时候将缓冲区中进度同步到数据库,以及是否是时候要确保下缓存区的数据都已经写文件。值越小,更新会越频繁,下载速度会越慢,但是应对进程被无法预料的情况杀死时会更加安全</td>       <td>2000</td>      </tr>      <tr>       <td>download.max-network-thread-count</td>       <td>用于同时下载的最大网络线程数, 区间[1, 12]</td>       <td>3</td>      </tr>      <tr>       <td>file.non-pre-allocation</td>       <td>是否不需要在开始下载的时候,预申请整个文件的大小( content-length )</td>       <td>false</td>      </tr>     </tbody>    </table>    <p>III. 异常处理</p>    <p>所有的异常,都将在 FileDownloadListener#error(BaseDownloadTask, Throwable) 中获知。</p>    <table>     <thead>      <tr>       <th>Exception</th>       <th>原因</th>      </tr>     </thead>     <tbody>      <tr>       <td>FileDownloadHttpException</td>       <td>在发出请求以后,response-code不是200(HTTP_OK),也不是206(HTTP_PARTIAL)的情况下会抛出该异常; 在这个异常对象会带上 response-code、response-header、request-header。</td>      </tr>      <tr>       <td>FileDownloadGiveUpRetryException</td>       <td>在请求返回的 response-header 中没有带有文件大小(content-length),并且不是流媒体(transfer-encoding)的情况下会抛出该异常;出现这个异常,将会忽略所有重试的机会( BaseDownloadTask#setAutoRetryTimes ). 你可以通过在 filedownloader.properties 中添加 http.lenient=true 来忽略这个异常,并且在该情况下,直接作为流媒体进行下载。</td>      </tr>      <tr>       <td>FileDownloadOutOfSpaceException</td>       <td>当将要下载的文件大小大于剩余磁盘大小时,会抛出这个异常。</td>      </tr>      <tr>       <td>其他</td>       <td>程序错误。</td>      </tr>      <tr>       <td>FileDownloadNetworkPolicyException</td>       <td>设置了 BaseDownloadTask#setWifiRequired(true) ,在下载过程中,一旦发现网络情况转为非Wifi环境,便会抛回这个异常</td>      </tr>      <tr>       <td>PathConflictException</td>       <td>当有一个正在下载的任务,它的存储路径与当前任务的存储路径完全一致,为了避免多个任务对同一个文件进行写入,当前任务便会抛回这个异常</td>      </tr>     </tbody>    </table>    <h2>III. 低内存情况</h2>    <h3>非下载进程(一般是UI进程):</h3>    <p>这边的数据并不多,只是一些队列数据,用不了多少内存。</p>    <p>前台进程 数据被回收:</p>    <p>如果在前台的时候这个数据都被回收了, 你的应用应该也挂了。极低概率事件。</p>    <p>后台进程 数据被回收:</p>    <p>一般事件, 如果是你的下载是UI进程启动的,如果你的UI进程处于 后台进程 (可以理解为应用被退到后台)状态,在内存不足的情况下会被回收(回收优先级高于 服务进程 ),此时分两种情况:</p>    <ol>     <li> <p>是串行队列任务,在回收掉UI进程内存以后,下载进程会继续下载完已经pending到下载进程的那个任务,而还未pending到下载进程的任务会中断下载(由于任务驱动线性执行的是在UI进程); 有损体验: 下次进入应用重启启动整个队列,会继续上次的下载。</p> </li>     <li> <p>是并行队列任务,在回收掉UI进程内存以后,下载进程会继续下载所有任务(所有已经pending到下载进程的任务,由于这里的pending速度是很快的,因此几乎是点击并行下载,所有任务在很短的时间内都已经pending到下载进程了),而UI进程由于被回收,将不会收到所有的监听; 有损体验: 下次进入应用重新启动整个队列,就会和正常的下载启动一致,收到所有情况的监听。</p> </li>    </ol>    <h3>下载进程:</h3>    <p>对内存有一定的占用,但是并不多,每次启动进程会根据数据的有效性进行清理冗余数据,被回收是低概率事件</p>    <p>由于下载不断有不同的buffer占用内存,但是由于在下载时,是活跃的 服务进程 ,因此被回收是低概率事件(会先回收完所有 空进程 、 后台进程 (后台应用)以后,如果内存还不够,才会回收该进程)。</p>    <p>即使被回收,也不会有任何问题。由于我们使用的是 START_STICKY (如果不希望被重启可主动调用 FileDownloader#unBindService / FileDownloader#unBindServiceIfIdle ),因此在内存足够的时候,下载进程会尝试重启(系统调度),非下载进程(一般是UI进程) 接收到下载进程的连接,会继续下载与继续接收回调,下载进程也会断点续传没有下载完的所有任务(无论并行与串行),不会影响体验。</p>    <h2>IV. LICENSE</h2>    <pre>  <code class="language-java">Copyright (c) 2015 LingoChamp Inc.    Licensed under the Apache License, Version 2.0 (the "License");  you may not use this file except in compliance with the License.  You may obtain a copy of the License at       http://www.apache.org/licenses/LICENSE-2.0    Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS,  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and  limitations under the License.</code></pre>    <p> </p>    <p>    <!--StartFragment --></p>    <p>项目主页:<a href="http://www.open-open.com/lib/view/home/1491393141779"><img src="file:///C:\Users\wzw\AppData\Roaming\Tencent\QQ\Temp\%W@GJ$ACOF(TYDYECOKVDYB.png">http://www.open-open.com/lib/view/home/1491393141779</a></p>    <p> </p>