Repository: lingochamp/FileDownloader Branch: master Commit: 6237a8cac174 Files: 189 Total size: 1.1 MB Directory structure: gitextract_oil8rr72/ ├── .github/ │ └── issue_template.md ├── .gitignore ├── .idea/ │ ├── codeStyleSettings.xml │ ├── codeStyles/ │ │ ├── Project.xml │ │ └── codeStyleConfig.xml │ ├── compiler.xml │ ├── copyright/ │ │ ├── apache2.xml │ │ └── profiles_settings.xml │ ├── dictionaries/ │ │ └── Jacksgong.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── inspectionProfiles/ │ │ ├── Project_Default.xml │ │ └── profiles_settings.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── .travis.yml ├── CHANGELOG-ZH.md ├── CHANGELOG.md ├── LICENSE.txt ├── README-zh.md ├── README.md ├── build.gradle ├── checkstyle.xml ├── demo/ │ ├── .gitignore │ ├── build.gradle │ ├── filedownloaderdemo.jks │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ ├── filedownloader.properties │ │ └── performance_test_data │ ├── java/ │ │ └── com/ │ │ └── liulishuo/ │ │ └── filedownloader/ │ │ └── demo/ │ │ ├── Constant.java │ │ ├── DemoApplication.java │ │ ├── GlobalMonitor.java │ │ ├── HybridTestActivity.java │ │ ├── MainActivity.java │ │ ├── MultitaskTestActivity.java │ │ ├── NotificationSampleActivity.java │ │ ├── SingleTaskTestActivity.java │ │ ├── TasksManagerDemoActivity.java │ │ ├── Utils.java │ │ └── performance/ │ │ ├── IntParcel.java │ │ ├── LongParcel.java │ │ └── PerformanceTestActivity.java │ └── res/ │ ├── drawable/ │ │ └── bg_item_task_manager.xml │ ├── layout/ │ │ ├── activity_hybrid_test.xml │ │ ├── activity_main.xml │ │ ├── activity_mutitask_test.xml │ │ ├── activity_notification_minset.xml │ │ ├── activity_notification_sample.xml │ │ ├── activity_performance.xml │ │ ├── activity_single.xml │ │ ├── activity_tasks_manager_demo.xml │ │ └── item_tasks_manager.xml │ ├── menu/ │ │ └── menu_main.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── donottranslate.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-zh/ │ │ └── strings.xml │ └── xml/ │ └── network_security_config.xml ├── gradle/ │ ├── bintray.gradle │ ├── mvn-local.gradle │ ├── mvn-push.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── install.sh ├── library/ │ ├── .gitignore │ ├── LICENSE │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ └── com/ │ │ │ └── liulishuo/ │ │ │ └── filedownloader/ │ │ │ ├── i/ │ │ │ │ ├── IFileDownloadIPCCallback.aidl │ │ │ │ └── IFileDownloadIPCService.aidl │ │ │ ├── message/ │ │ │ │ └── MessageSnapshot.aidl │ │ │ └── model/ │ │ │ ├── FileDownloadHeader.aidl │ │ │ └── FileDownloadTaskAtom.aidl │ │ ├── java/ │ │ │ └── com/ │ │ │ └── liulishuo/ │ │ │ └── filedownloader/ │ │ │ ├── BaseDownloadTask.java │ │ │ ├── DownloadSpeedMonitor.java │ │ │ ├── DownloadTask.java │ │ │ ├── DownloadTaskHunter.java │ │ │ ├── FileDownloadConnectListener.java │ │ │ ├── FileDownloadEventPool.java │ │ │ ├── FileDownloadLargeFileListener.java │ │ │ ├── FileDownloadLine.java │ │ │ ├── FileDownloadLineAsync.java │ │ │ ├── FileDownloadList.java │ │ │ ├── FileDownloadListener.java │ │ │ ├── FileDownloadMessageStation.java │ │ │ ├── FileDownloadMessenger.java │ │ │ ├── FileDownloadMonitor.java │ │ │ ├── FileDownloadQueueSet.java │ │ │ ├── FileDownloadSampleListener.java │ │ │ ├── FileDownloadServiceProxy.java │ │ │ ├── FileDownloadServiceSharedTransmit.java │ │ │ ├── FileDownloadServiceUIGuard.java │ │ │ ├── FileDownloadTaskLauncher.java │ │ │ ├── FileDownloader.java │ │ │ ├── IDownloadSpeed.java │ │ │ ├── IFileDownloadMessenger.java │ │ │ ├── IFileDownloadServiceProxy.java │ │ │ ├── ILostServiceConnectedHandler.java │ │ │ ├── IQueuesHandler.java │ │ │ ├── ITaskHunter.java │ │ │ ├── IThreadPoolMonitor.java │ │ │ ├── LostServiceConnectedHandler.java │ │ │ ├── MessageSnapshotGate.java │ │ │ ├── PauseAllMarker.java │ │ │ ├── QueuesHandler.java │ │ │ ├── connection/ │ │ │ │ ├── DefaultConnectionCountAdapter.java │ │ │ │ ├── FileDownloadConnection.java │ │ │ │ ├── FileDownloadUrlConnection.java │ │ │ │ └── RedirectHandler.java │ │ │ ├── database/ │ │ │ │ ├── FileDownloadDatabase.java │ │ │ │ ├── NoDatabaseImpl.java │ │ │ │ ├── RemitDatabase.java │ │ │ │ ├── SqliteDatabaseImpl.java │ │ │ │ └── SqliteDatabaseOpenHelper.java │ │ │ ├── download/ │ │ │ │ ├── ConnectTask.java │ │ │ │ ├── ConnectionProfile.java │ │ │ │ ├── CustomComponentHolder.java │ │ │ │ ├── DownloadLaunchRunnable.java │ │ │ │ ├── DownloadRunnable.java │ │ │ │ ├── DownloadStatusCallback.java │ │ │ │ ├── FetchDataTask.java │ │ │ │ └── ProcessCallback.java │ │ │ ├── event/ │ │ │ │ ├── DownloadEventPoolImpl.java │ │ │ │ ├── DownloadEventSampleListener.java │ │ │ │ ├── DownloadServiceConnectChangedEvent.java │ │ │ │ ├── IDownloadEvent.java │ │ │ │ ├── IDownloadEventPool.java │ │ │ │ └── IDownloadListener.java │ │ │ ├── exception/ │ │ │ │ ├── FileDownloadGiveUpRetryException.java │ │ │ │ ├── FileDownloadHttpException.java │ │ │ │ ├── FileDownloadNetworkPolicyException.java │ │ │ │ ├── FileDownloadOutOfSpaceException.java │ │ │ │ ├── FileDownloadSecurityException.java │ │ │ │ └── PathConflictException.java │ │ │ ├── message/ │ │ │ │ ├── BlockCompleteMessage.java │ │ │ │ ├── IFlowDirectly.java │ │ │ │ ├── IMessageSnapshot.java │ │ │ │ ├── LargeMessageSnapshot.java │ │ │ │ ├── MessageSnapshot.java │ │ │ │ ├── MessageSnapshotFlow.java │ │ │ │ ├── MessageSnapshotTaker.java │ │ │ │ ├── MessageSnapshotThreadPool.java │ │ │ │ └── SmallMessageSnapshot.java │ │ │ ├── model/ │ │ │ │ ├── ConnectionModel.java │ │ │ │ ├── FileDownloadHeader.java │ │ │ │ ├── FileDownloadModel.java │ │ │ │ ├── FileDownloadStatus.java │ │ │ │ └── FileDownloadTaskAtom.java │ │ │ ├── notification/ │ │ │ │ ├── BaseNotificationItem.java │ │ │ │ ├── FileDownloadNotificationHelper.java │ │ │ │ └── FileDownloadNotificationListener.java │ │ │ ├── services/ │ │ │ │ ├── BaseFileServiceUIGuard.java │ │ │ │ ├── DefaultIdGenerator.java │ │ │ │ ├── DownloadMgrInitialParams.java │ │ │ │ ├── FDServiceSeparateHandler.java │ │ │ │ ├── FDServiceSharedHandler.java │ │ │ │ ├── FileDownloadBroadcastHandler.java │ │ │ │ ├── FileDownloadManager.java │ │ │ │ ├── FileDownloadService.java │ │ │ │ ├── FileDownloadThreadPool.java │ │ │ │ ├── ForegroundServiceConfig.java │ │ │ │ └── IFileDownloadServiceHandler.java │ │ │ ├── stream/ │ │ │ │ ├── FileDownloadOutputStream.java │ │ │ │ └── FileDownloadRandomAccessFile.java │ │ │ └── util/ │ │ │ ├── DownloadServiceNotConnectedHelper.java │ │ │ ├── ExtraKeys.java │ │ │ ├── FileDownloadExecutors.java │ │ │ ├── FileDownloadHelper.java │ │ │ ├── FileDownloadLog.java │ │ │ ├── FileDownloadProperties.java │ │ │ ├── FileDownloadSerialQueue.java │ │ │ └── FileDownloadUtils.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── liulishuo/ │ │ └── filedownloader/ │ │ ├── FileDownloaderTest.java │ │ ├── connection/ │ │ │ └── FileDownloadUrlConnectionTest.java │ │ ├── download/ │ │ │ ├── DownloadLaunchRunnableTest.java │ │ │ └── DownloadRunnableTest.java │ │ └── util/ │ │ └── FileDownloadUtilsTest.java │ └── resources/ │ └── mockito-extensions/ │ └── org.mockito.plugins.MockMaker ├── okcat.yml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/issue_template.md ================================================ #### Before Issue 1. Please search on the [Issues](https://github.com/lingochamp/FileDownloader/issues) 2. Please search on the [wiki](https://github.com/lingochamp/FileDownloader/wiki) 3. Please set `FileDownloadLog.NEED_LOG=true` and review the Logcat output from main process and `:filedownloader` process ( pay attention to Warn and Error level logcat) #### Issue 1. What problem do you get? 2. Which version of FileDownloader are you using when you produce such problem? 3. How to reproduce such problem? 4. Do you set `FileDownloadLog.NEED_LOG=true`? 5. Could you please reproduce this problem and provide all main process and `:filedownloader` process logcat 6. Can you fix it by yourself and request PR, if not, what's problem do you get when you try to fix it >P.S. If you don't know how to get `:filedownloader` process, it's recommended to using `pidcat` to just filter all your application logcat, or define `process.non-separate=true` on [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties) --- 请在Issue前认真的跟进上面提到的建议,这样将可以极大的加快你遇到问题的处理。 ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries /.idea/misc.xml /.idea/checkstyle-idea.xml /.idea/caches .DS_Store /build /captures .classpath .project .settings .vscode ================================================ FILE: .idea/codeStyleSettings.xml ================================================ ================================================ FILE: .idea/codeStyles/Project.xml ================================================ ================================================ FILE: .idea/codeStyles/codeStyleConfig.xml ================================================ ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/apache2.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/dictionaries/Jacksgong.xml ================================================ buildship chunked classpathentry dreamtobe etag filedownloader gradleclasspathcontainer gradleprojectbuilder gradleprojectnature jacksgong javabuilder javanature liulishuo okhttp robolectric sofar ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/Project_Default.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/profiles_settings.xml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: .travis.yml ================================================ language: android jdk: # build-tools 24.0.2 need jdk8 or above. - oraclejdk8 android: components: # Ref https://github.com/travis-ci/travis-ci/issues/6260. - tools - platform-tools - build-tools-28.0.3 - android-28 - extra script: - ./gradlew clean check after_script: - cat ./demo/build/outputs/lint-results.xml - cat ./library/build/outputs/lint-results.xml sudo: false before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - $HOME/.android/build-cache ================================================ FILE: CHANGELOG-ZH.md ================================================ # Change log > [ Change log in english](https://github.com/lingochamp/FileDownloader/blob/master/CHANGELOG.md) ## Version 1.7.7 - 修复: FileDownloadThreadPool 可能会抛出 ArrayIndexOutOfBoundsException 和 ClassCastException。 closes #1258 - 修复: 从 1.6.x 升级到 1.7.x 后恢复之前的下载任务时出现 416 错误。 - 修复: Demo 中下载通知示例无法显示通知。closes #1224 - 修复: blockComplete 可能会在主线程中回调。closes #1069 - 修复: NoDatabaseImpl 中 SparseArray 非线程安全问题。closes #1225 ## Version 1.7.6 _2019-02-20_ #### 修复 - 修复: 在Android O以及更高版本手机上,在所有任务结束后自动将前台服务关闭. closes #1096 - 修复: 修复'Context.startForegroundService() did not then call Service.startForeground()'的问题. closes #1104 - 修复: 确保在调用停止任务后,运行中的通知被关闭. closes #1136 - 修复: 修复在重试时小概率NPE. closes #1100 ## Version 1.7.5 _2018-08-03_ #### 修复 - 修复: 修复在Android O的系统上,当应用不在前台,并且不在白名单的时候,由于下载服务无法通过`JobScheduler`来执行下载事务,只能通过`startService`,引起 "Not allowed to start service Intent..." 的问题。 closes #1078 #### Enhance - 提升实用性: 支持`Content-Disposition`中的非UTF-8编码。 closes #1057 - 提高实用性: 处理了阿里云服务错误反馈`416`的情况`。 closes #1050 ## Version 1.7.4 _2018-05-19_ #### 修复 - 修复: 修复在Android 8或更高版本上,当应用在后台时,并且此时正在下载,但是下载服务的链接断开,此时尝试重新绑定的时候发生'IllegalStateException'的问题。closes #1017 - 修复: 修复响应头带回来的文件名可能存在安全隐患的问题. closes #1028 ## Version 1.7.3 _2018-04-20_ #### 修复 修复: 修复由于在下载结束时`fd`没有被主动释放,导致当有大量的任务被不断的发起执行时有可能引发的OOM问题。 ## Version 1.7.2 _2018-03-14_ #### 修复 - 修复: 将原本所需要下载的文件大小为`0`的时候,回调错误,修改为直接回调完成。closes #789 - 修复: 修复当存在另外一个正在下载的相同临时文件路径的任务时,数据库中存在数据未被删除的问题。closes #953 - 修复: 修复在重试后重试之前下载的进度丢失的问题。closes #949 - 修复: 修复当试探连接没有提供`Content-Range`字段,但是提供`Content-Length`字段时,计算出的总长度始终是`1`的问题。 #### 性能与提高 - 提高实用性: 当在响应头中不存在`Content-Length`字段时,使用隐藏在`Content-Range`中的内容大小数据。 closes #967 ## Version 1.7.1 _2018-02-05_ #### 修复 - 修复: 修复当后端不支持`HEAD`方法的时候,返回`405`响应状态导致下载失败的问题。 close #942 ## Version 1.7.0 _2018-02-01_ #### 修复 - 修复: 通过同步处理暂停操作与状态的更新来修复状态不是一个正确向前的流的问题。 close #889 - 修复: 修复在`pending`状态回调时带回来已经被弃用的`sofar-bytes`。 close #884 - 修复: 修复当`filename`没有用`"`包裹时,无法通过`content-dispostion`获取文件名的问题。 close #908 - 修复: 修正`setCallbackProgressTimes`设置的次数不能正常生效的问题。 close #901 - 修复: 修复由于试探连接采用`0-infinite`的`Range`导致下载了无用内容到tcp-window的问题。close #933 - 修复: 在连接`ending`的时候再次主动关闭输入流,防止输入流泄漏特别是对于试探连接来说。 #### 性能与提高 - 提高实用性: 当临时文件重命名为目标文件成功时,不再做一次移除临时文件的操作,防止一些文件系统报错的问题。close #912 - 提高实用性: 当确定本地提供的`Range`是正确的,但是后端却返回`416`时,将完全弃用`Range`请求头。close #921 - 提高性能: 为试探连接使用`HEAD`的请求替代`GET`方法,提高试探通信效率。 ref #933 #### 其他 如果你正在使用`filedownloader-okhttp3-connection`,请将其更新到`1.1.0`版本来适配`1.7.0`版本。 ## Version 1.6.9 _2017-12-16_ #### 修复 - 修复(serial-queue): 修复在`FileDownloadSerialQueue`遇到的死锁。 closes #858 - 修复: 不再在非单元测试环境使用`j-unit`,避免在一些小米手机上发生`no-static-method-found`的问题。 closes #867 - 修复: 修复每次重试减少两次重试机会的问题。 closes #838 - 修复: 修复在`pending`的时候暂停任务,而后获取到该任务都是`pending`的状态的问题。 closes #855 #### 性能与提高 - 提高实用性: 开放`SqliteDatabaseImpl`、`RemitDatabase`、`NoDatabaseImpl`,便于上层覆盖他们。 - 提高实用性: 支持从更高的版本降级到该版本。 - 提高实用性: 当上层没有主动添加`User-Agent`的时候,内部添加默认的`User-Agent`。 closes #848 - 提高性能: 修改所有的线程池中线程的存活时间(从5s修改为15s),避免在高频下载中,各池子频繁的释放与创建线程 - 提高性能: 使用`RemitDatabase`作为默认的数据库,在很多小任务很快的结束下载(2s内),其数据库操作将会变得十分冗余,而这部分的数据库操作将被取消 #### RemitDatabase FileDownloader中大多数数据库长尾问题,是由于有很多很小的任务同时执行: - 由于很小的任务每次启动、等待、连接、下载进度、结束都会促发入库 - 一旦任务很小网速很快的时候,一个小任务实际下载耗时可能在1-2s完成 - 因此整个引擎不得不为该1-2s完成的任务完成一连串的数据库入库、更新到从数据库删除的操作 - 也就是说单个类似的任务在1-2s内促发了至少5次数据库操作,期间包含入库与最后的删除 - 一个任务还好,当这样的任务数上升到几百个的层面,这样高频持续的数据库操作,就很容易暴露各种数据库问题(包含文件系统问题) - 而现有体系在上层推任务大量任务到下载服务的时候, 会高频持续的3个并行对这些任务做入库处理,在这个点上数据库问题也容易发生(包含文件系统问题) --- 而相比之下写入数据库是为了断点续传,这个短期的频繁数据库操作,实质的作用甚微,早期的提供外接接口来控制下载进度间的入库频率显然无法覆盖该问题。 --- 因此,还是为FileDownloader推出新的`RemitDatabase`用于解决该问题,除去期间的多线程安全问题的处理,核心思想如下: ![][RemitDatabase-png] - 如果某一个任务的整体数据更新与结束在2s(该值可定制)内,则不再有数据库操作,全程只存内存 - 如果某一个任务的数据更新与结束操过2s,则分为两部分,2s前只存内存,2s开始同时存内存与数据库 - 如果某一个任务最终的结束是暂停或错误,则在最后的状态更新中,同时存内存与数据库 ## Version 1.6.8 _2017-10-13_ #### 修复 - 修复: 修复断点续传失败, 由于Network线程中的`isAlive`不可靠导致的问题。 this closes #793 - 修复: 修复断点续传失败,由于多个线程频繁的更新`status`并且`DownloadStatusCallback`的`sendMessage`无法保证有序性,导致下一次启动时最终状态是`process`无法断点续传(具体原因参看[这里](https://github.com/lingochamp/FileDownloader/issues/793#issuecomment-336370126))。 this refs #793, #764, #721, #769, #763, #761, #716 - 修复: 不再由于任务已经结束依然存在需要派发的信息而让用户程序奔溃,因为这个对用户并不会照成影响。 this closes #562 - 修复: 修复当用户频繁调用`pause`时,有可能出现`it can't take a snapshot for the task xxx`错误的问题。 - 修复: 修复由于内部存储的任务对象大小存在问题,导致这样的对象任务每一次启动都必然会`416`的问题。 ## Version 1.6.7 _2017-10-12_ #### 修复 - 修复: 避免`error`与`pause`的状态被运行中的状态覆盖导致下次断点续传失败。 closes #769, closes #764, closes #761, closes #763, closes #721, closes #716 - 修复: 感谢 @hongbiangoal 对问题的定位,修复了当某一个分块的断点进度大于1.99G时,请求的范围出现负数的情况。 closes #791 ## Version 1.6.6 _2017-09-29_ #### 修复 - 修复(文件完整性): 只有在确保缓存已经完全固化到本地文件了才更新数据库的进度,防止在该次暂停时固化失败,然后数据库更新了进度,导致下一次断点续传的时候(使用预分配策略的情况下),本地已经下载的文件的进度与数据库记录的进度实际不一致,导致最后下载完成了文件不完整的问题。 Closes #745 - 修复(清理): 修复调用`FileDownloader#clearAllTaskData`并没有清理连接表的问题。 Closes #754 #### 性能与提高 - 提高性能: 使用`BufferedOutputStream`来优化默认输出流,现在虚拟机内的缓存大小为8192字节。 ## Version 1.6.5 _2017-09-11_ #### 修复 - 修复(crash): 修复因为使用`%d`格式化`AtomicLong`导致`IllegalFormatConversionException`的问题。 Closes #743 ## Version 1.6.4 _2017-08-21_ #### 新接口 - 新增 `NoDatabaseImpl`: 为了有些用户需要一个没有数据库的FileDownloader的用户。 Refs #727 #### 性能与提高 - 提高性能: 使用`AtomicLong`代替锁的方式,使得下载进度递增性能更好。 #### 修复 - 修复(分块下载): 修复在断点续传时之前已经下载了分块下载的最后一块,可是在继续下载时重新请求了最后一块给了错误的Range导致416的错误。 Closes #737 - 修复(npe): 修复极小概率当事件监听已经被其他线程移除时还在分发事件导致NPE的问题。 Closes #723 ## Version 1.6.3 _2017-07-29_ #### 修复 - 修复: 修复当正在处理回调结束任务的事务时,调用了`pause`极小概率出现NPE的问题。 Closes #680 - 修复: 修复当暂停或恢复`FileDownloaderSerialQueue`的时候,其已经完成了该操作,出现`MissingFormatArgumentException`的问题。 Closes #699 ## Version 1.6.2 _2017-07-16_ #### 修复 - 修复: 修复当FileDownloader下载文件有一个分块从大于1.99G的地方开始下载,就会发生'offset < 0'异常的问题。 Closes #669 ## Version 1.6.1 _2017-07-13_ #### 性能与提高 - 提高实用性: 当返回的`content-length`不等于通过Range计算出来的`content-length`时直接抛回`GiveUpException`而不继续下载。 Closes #636 #### 修复 - 修复: 修复下载出现错误或暂停下载时强制多`sync`了一次的问题。 - 修复: 修复当从断点中恢复chuncked任务后下载文件被损坏的问题。 ## Version 1.6.0 _2017-07-07_ #### 修复 - 修复(no-response): 修复当多线程分块下载同时完成时,有可能会由于线程安全问题导致completed无法得到回调的问题,具体情况参看[这里](https://github.com/lingochamp/FileDownloader/issues/631#issuecomment-313387299)。 Closes #631 ## Version 1.5.9 _2017-07-04_ #### 修复 - 修复(duplicate-permission): 修复在Android 5.0或更高版本系统的手机中已经有一个应用引用了1.5.7或更新版本的FileDownloader后,再安装引用1.5.7或更新版本的FileDownloader的应用会报`INSTALL_FAILED_DUPLICATE_PERMISSION`的问题,这个问题是因为在1.5.7版本中我们申明了一个接收结束广播的权限导致,现在我们移除了这个权限申明来修复这个问题。Closes #641 ## Version 1.5.8 _2017-06-28_ #### 修复 - 修复(无响应): 修复非常快速的对相同任务启动、暂停来回切换会使得任务到后面没有响应的问题。Closes #625 ## Version 1.5.7 _2017-06-25_ #### 新接口 - 支持在`filedownloader.properties`中配置`broadcast.completed`: 决定是否需要在任务下载完成后发送一个完成的广播。 Closes #605 - 支持接收201的http返回状态码。 Closes #545 - 为`FileDownloadSerialQueue`支持暂停与继续功能. Closes #547 - 在FileDownloader内部处理了各类重定向的情况(300、301、302、303、307、308)。 Closes #611 - 弃用了`FileDownloader#init`取而代之的是`FileDownloader#setup`,现在如果你不需要定制组件,就只需要在使用FileDownloader之前的任意使用调用这个方法就行。 Closes #500 > - 如果你使用`broadcast.completed`并且接收任务完成的广播,你需要在AndroidManifest中注册Action为`filedownloader.intent.action.completed`的广播并且使用`FileDownloadBroadcastHandler`来处理接收到的`Intent`。 > - 现在, 不再使用`FileDownloader#init`, 取而代之的,如果你需要注册你的定制组件,你需要在`Application#onCreate`中调用`FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker`, 否则你只需要在使用FileDownloader之前的任意时候调用`FileDownloader.setup(Context)`即可。 #### 修复 - 修复: 修复来`FileDownloadQueueSet`无法处理使wifi-required失效的操作。 感谢 @qtstc - 修复(output-stream): 修复当提供的output-stream不支持seek时,FileDownloader无法使用的问题。 Closes #622 #### 性能与提高 - 提高实用性: 覆盖使用不同的Url来复用下载进度的情况(结合`idGenerator`一起使用)。 Closes #617 ## Version 1.5.6 _2017-06-18_ #### 修复 - 修复(crash): 修复当调用`findRunningTaskIdBySameTempPath`的同时请求了暂停可能导致NPE奔溃的问题。 Closes #613 - 修复(crash): 修复返回状态是`206`并且它的ETAG发生变化时导致`IllegalArgumentException`错误奔溃的问题。 Closes #612 - 修复(crash): 修复当用户请求下载需要Wifi并当前不是Wifi环境时,出现`FileDownloadNetworkPolicyException`未处理导致奔溃的问题。 感谢 @qtstc - 修复(crash): 修复当用户直接从`v1.4.3`升级到`v1.5.2`并且在一些其他综合因素下(具体可以参见 #610 ) 初始化数据库时出现`IllegalStateException`错误奔溃的问题。Closes #610 - 修复(crash): 修复当回调流已经结束当时与此同时刚好出现错误或下载完成或暂停,小概率会出现`IllegalStateException`奔溃的问题。 - 修复(no-response): 修复在接收到`connected`回调之后,多线程下载建立连接,此时在检验连接与数据获取连接期间服务端数据发生错误或变更导致启动下载后没有响应的问题。 #### 性能与提高 - 提高实用性: 当父级目录创建失败时直接回调`error`。 Closes #542 - 提高实用性: 处理了返回状态是`416`的情况。 Refs #612 ## Version 1.5.5 _2017-06-12_ #### 修复 - 修复(max-network-thread-count): 修复当任务都是多线程下载时,`download.max-network-thread-count`参数没起作用,并同时下载任务无上限的问题。 Closes #607 ## Version 1.5.4 _2017-06-11_ #### 新接口 - 通过`IdGenerator`支持了定制下载任务id生成器。 Closes #224 #### 性能与提高 - 提高实用性: 将`FileDownloadModel`的维护从`FileDownloadDatabase`中解藕,让`FileDownloadDatabase`只关心数据库相关操作。 - 提高实用性: 将数据库初始化的维护工作从默认的数据库实现中解藕出来,让定制的数据库也能够被采用相同机制维护到。 ## Version 1.5.3 _2017-06-08_ #### 修复 - 修复(crash): 修复在计算平均速度的过程中`connected`与`completed`几乎同时回调时发生`divide by zero`异常的问题。 Refs #601 - 修复(crash): 修复触发暂停的同时,`FetchDataTask`已经被创建并请求执行,但是还没有来得及被执行,导致NPE奔溃的问题。 Closes #601 ## Version 1.5.2 _2017-06-07_ #### 修复 - 修复(crash): 修复当任务需要回调`error`或者被暂停时,刚好该任务的某个或几个链接完成下载,此时遇到NPE或者是`ConcurrentModificationException`的异常。Closes #598 - 修复(crash): 修复当任务被暂停时,任务从开始到被暂停还没来得及同步一次数据到文件系统或者数据库,此时遇到NPE的异常。Refs #598 - 修复(crash): 修复当采用多链接下载一个任务时,非首次建链失败或者是创建`FetchDataTast`失败,此时遇到NPE的异常。Refs #598 - 修复(speed-calculate): 修复忽略整个下载进度回调,并且只使用`FinishListener`时,此时下载速度始终是`0`的问题。 - 修复(finish-listener): 修复对于之前已经下载好的任务,并且只监听来`FinishListener`,此时`FinishListener`的`over`方法不会被回调到的问题。 #### 性能与提高 - 提升性能: 开启了默认数据库的WAL,使得读与写可并行操作来提高性能,因为我们的绝大多数场景读写是会在不同线程中同时执行的,开启这个以后会导致内存的增加,但是在大多数情况下极大的提高了数据库的写入速度,并且更加稳定(更少的使用`fsync()`)。 ## Version 1.5.1 _2017-06-05_ #### 修复 - 修复(crash): 修复在`FileDownloader.init`中,当没有提供`InitCustomMaker`时出现的NPE奔溃。 Closes #592 - 修复(callback): 修复当之前有多个链接服务于该任务并且正在从端点恢复时,在`pending`中没有带回其正确的`sofarBytes`的问题。 - 修复(speed-monitor): 矫正`IDownloadSpeed.Monitor`在断点续传下总平均速度不准确的问题。 #### 性能与提高 - 提高稳定性: 当触发暂停时,主动同步FetchTask中的进度确保其进度得到固化到文件系统。 - 提高稳定性: 当在`FileDownloader.init`中提供的`context`为空时,抛`IllegalArgumentException`以更早的暴露问题。 ## Version 1.5.0 _2017-06-05_ #### 新接口 - 支持对单个任务多连接(多线程)下载。 Closes #102 - 支持通过`ConnectionCountAdapter`定制对每个任务使用连接(线程)数据的定制(可以通过`FileDownloader#init`设置进去) #### 性能与提高 - 提高性能: 重构整个下载的逻辑与原始回调逻辑,并删除了1000行左右的`FileDownloadRunnable`。 对于每个任务默认的连接(线程)数目策略,你可以通过`ConnectionCountAdapter`来定制自己的策略: - 1个连接(线程): 文件大小 [0, 1MB) - 2个连接(线程): 文件大小 [1MB, 5MB) - 3个连接(线程): 文件大小 [5MB, 50MB) - 4个连接(线程): 文件大小 [50MB, 100MB) - 5个连接(线程): 文件大小 [100MB, -) ## Version 1.4.3 _2017-05-07_ #### 修复 - 修复: 移除重复的被弃用的方法: `FileDownloader#init(Application)`, 因为`Application`是 `Context`的实现。 ## Version 1.4.2 _2017-03-15_ #### 修复 - 修复(Same File Path): 避免多个问题同时对相同的文件写入,一旦存在另外一个正在运行中的任务与当前任务的文件存储路径一致,当前任务将会收到`PathConflictException`来拒绝启动。 Closes #471 #### New Interfaces - 新增 `FileDownloadSerialQueue#getWaitingTaskCount`: 获取动态串行队列中正在等待启动的任务个数。Refs #345 ## Version 1.4.1 _2017-02-03_ #### 修复 - 修复(高并发): 修复由于Messenger在已经收到结束的信息将Task对象赋值为Null以后依然收到其他消息,导致NPE的问题。 Closes #462 - 修复(`FileDownloadHttpException`): 修复由于在建立连接后无法取到请求头以至于遇到`FileDownloadHttpException`时发生`IllegalStateException`的问题。 Closes #458 ## Version 1.4.0 _2017-01-11_ #### 性能与提高 - 提高性能: 优化`FileDownloader#init`中的逻辑, 使其更加的轻量(仅仅做了赋值`context`与`maker`的操作) #### 修复 - 修复(pause): 修复高并发情况下,当在启动一个任务的时候,很短的时间间隔内去暂停一个任务,可能无法暂停下来任务的问题。 Closes #402 - 修复(init FileDownloader): 修复在很低概率下在`FileDownloadService`所在进程初始化FileDownloader时出现Crash的问题。 Closes #420 - 修复(FileDownloadHttpException): 修复在遇到`FileDownloadHttpException`类型Crash时,由于字符串的formatter无法匹配导致Crash的问题 Closes #438 ## Version 1.3.9 _2016-12-18_ ### 核心: - 这个版本开始,你可以定制自己的网络连接组件: [FileDownloadConnection][FileDownloadConnection-java-link],默认情况下我们使用[这个][FileDownloadUrlConnection-java-link]。 - 这个版本开始,我们不再默认依赖okhttp,你可以根据自己的需求进行定制。(如果你依然想要使用okhttp,可以考虑集成下这个[仓库](https://github.com/Jacksgong/filedownloader-okhttp3-connection)) > 如果你依然需要配置`timeout`、`proxy`,请不用担心,我已经对默认的网络连接组件实现了这几个接口: [DemoApplication](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/DemoApplication.java#L35),如果有需要可以看看。 #### 新接口 - 新增 `FileDownloadQueueSet#reuseAndStart`: 添加 '复用并启动'接口,主要用于在启动队列任务之前,先对任务队列中的所有任务进行尽可能的复用。 Ref #383 - 新增 `FileDownloadConnection`: 支持定制化网络连接组件,不再默认依赖okhttp。 Closes #158 ## Version 1.3.0 _2016-10-31_ #### 新接口 - 新增 `FileDownloadSerialQueue`: 便于动态管理串行执行的队列。 Closes #345. - 移除 `FileDownloadListener`类中的`callback`方法, 并且新增`FileDownloadListener#isInvalid`方法,用于告知FileDownloader该监听器是否已经无效,不再接收任何消息。 - 新增 `FileDownloader#clearAllTaskData`: 清空`filedownloader`数据库中的所有数据。 Closes #361. #### 性能与提高 - 提高实用性(`FileDownloadListener#blackCompleted`): 确保`blockCompleted`回调可以接收任何的`Exception`。 Closes #369. - 提高实用性(service-not-connected): 在service-not-connected-helper中输出提示与原因, 这样当你调用有些需要确保下载服务已经连接上的方式,但下载服务没有连接上时,不但在`Logcat`中可以收到原因,还能收到提示。 #### 修复 - 修复(reuse): 修复调用`BaseDownloadTask#pause`之后短时间内调用`BaseDownloadTask#reuse`方法,可能会抛出异常的问题。 Closes #329. ## Version 1.2.2 _2016-10-15_ #### 修复 - 修复(fatal-crash): 修复当任务没有`FileDownloadListener`时,也不能收到该任务`FileDownloadMonitor.IMonitor#onTaskOver`的回调的问题。 Closes #348. ## Version 1.2.1 _2016-10-09_ #### 修复 - 修复(fatal-crash): 修复当任务没有`FileDownloadListener`时,也不能收到该任务`FileDownloadMonitor.IMonitor#onTaskOver`的回调的问题。 Closes #348. 十分的抱歉这个问题在1.2.1版本中依然存在,最终在1.2.2中验证修复。 ## Version 1.2.0 _2016-10-04_ #### 新接口 - 新增 `FileDownloader#insureServiceBind()`: 便于阻塞当前线程,并且启动下载服务,服务启动之后再执行需要服务的请求。 Refs #324. - 新增 `FileDownloader#insureServiceBindAsync()`: 便于启动下载服务,并且在服务启动之后,执行需要下载服务的请求。 Refs #324. - 新增 `FileDownloader#bindService(runnable:Runnable)`: 便于启动下载服务,并且在服务启动之后,执行 `runnable`。 Refs #324. - 新增 `FileDownloader#init(Context,InitCustomMaker)`: 便于初始化下载引擎的时候可以传入更多的定制化组件。 Refs #157. #### Enhancement - 提高实用性(`InitCustomMaker#database`): 支持定制化数据库组件(`FileDownloadDatabase`),并且实现默认的数据库组件: `DefaultDatabaseImpl`。 Closes #157. - 提高实用性(`InitCustomMaker#outputStreamCreator`): 支持定制化输出流组件(`FileDownloadOutputStream`),并且实现默认的输出流组件: `FileDownloadRandomAccessFile`,与一些可替代的组件: `FileDownloadBufferedOutputStream`、`FileDownloadOkio`。Closes #301. ## Version 1.1.5 _2016-09-29_ #### 新接口 - 支持在`filedownloader.properties`中配置`file.non-pre-allocation`: 是否不需要在开始下载的时候,预申请整个文件的大小(`content-length`), 默认值是`false`。Closes #313 . #### 修复 - 修复(fatal-crash): 修复由于`ThreadPoolExecutor#getActiveCount()`是一个大概的值,导致在其反回的不是正确值时,thread-pool库中存在`StackOverflowError`Crash的问题。Closes #321 . - 修复(minor-crash): 修复在一些小概率情况下出现Crash Message是'No reused downloaded file in this message'的IllegalStateException的问题。 Closes #316 . - 修复(minor-crash): 修复当在下载服务还没有连接上时,同时有几个串行队列任务需要执行,在一些小概率的情况下,一些相同的任务会被启动两次导致crash的问题。 Refs #282 . #### 其他 - 依赖: 取消对thread-pool库的依赖。 Refs #321 . - MinSDKVersion: 升级`minSdkVersion` : 8->9。 Refs #321 . ## Version 1.1.0 _2016-09-13_ #### 新接口 - 新增 `BaseDownloadTask#setWifiRequired`: 设置任务是否只允许在Wifi网络环境下进行下载。 默认值 `false`。 Closes #281 . #### 性能与提高 - 提高性能: 替换所有的线程池为exceed-wait-pool(更多详情参见: `FileDownloadExecutors`) 并且所有线程池中的线程将会在闲置五秒后自动结束。 Refs #303 . - 提高实用性: 当有异常从`FileDownloadListener#blockComplete`抛出时,将会被`catch`并且回调到`FileDownloadListener#error`中而非回调`FileDownloadListener#completed`。 Closes #305 . #### 修复 - 修复(lost-connect): 避免等待服务连接的列表中在一些小概率情况下存在重复任务的问题。 ## Version 1.0.2 _2016-09-06_ #### 修复 - 修复: 服务还没有连接上时,启动的'队列任务'被放在等待队列,当服务连接上以后,FileDownloader尝试重启这些等待队列中的任务,但是抛了`IllegalStateException`的Bug。 Closes #307 . ## Version 1.0.1 _2016-09-05_ #### 新接口 > 如果你之前有使用现在已经被申明弃用的方法`BaseDownloadTask#ready()`, 只需要简单的将它迁移为:`BaseDownloadTask#asInQueueTask():InQueueTask`并且调用`InQueueTask#enqueue()`。 - 添加`BaseDownloadTask#asInQueueTask():InQueueTask`并申明弃用`BaseDownloadTask#ready()`: 申明当前任务是队列任务,并且可以通过`InQueueTask#enqueue()`将当前任务放入全局队列以便于启动队列任务的时候,能被队列收编执行。`InQueueTask#enqueue()`中的操作与`BaseDownloadTask#ready()`相同, 我们通过这个方式封装`ready()`是为了让你更加清晰的了解: 只有当前任务是队列任务,才需要调用该方法;如果当前任务不是队列任务,而却调用了这个方法,你将会收到一个异常(具体异常的原因可以移步到`DownloadTask#start`报的异常信息进行了解)。 #### 修复 - 修复: 当有使用相同`listener`对象的多个孤立任务与队列任务在不同的线程中同时被启动时(后),有可能会遇到IllegalStateException异常的问题。 Closes #282 . ## Version 1.0.0 _2016-08-21_ #### 新接口 - 添加 `BaseDownloadTask#cancel`: 这个方法是为了说明为什么`pause`的操作也可以达到`cancel`的作用。 #### 性能与提高 - 提高性能: 持有`isDownloaderProcess`的结果,防止多次判断。 - 提高实用性: 重构代码的可见层。Closes #283 - 提高实用性: 完善Java Doc。Closes #284 - 提高实用性: 提供Java Doc 站点: http://fd.dreamtobe.cn 。Closes #284 ## Version 0.3.5 _2016-08-16_ #### 性能与提高 - 提高实用性: 为FileDownloader中的所有线程添加线程名。 - 提高性能: 调整`block-completed-thread-pool`中的核心线程数: 5->2,减少资源的浪费。 #### 修复 - 修复(SQLiteFullException): 覆盖了在整个下载过程中可能遇到`SQLiteFullException`的错误,就捕获相关错误并回调回 `FileDownloadListener#error` 。 Closes #243 - 修复(提供目录的情况): 修复若是提供的是文件夹,并且对应的任务已经下载完成,再次启动的时候,在直接回调`FileDownloadListener#completed`时,获取的`targetFilePath`可能为null的问题。 Closes #237 ## Version 0.3.4 _2016-07-31_ #### 新接口 - 添加 `FileDownloader#clear`: 用于强制根据任务ID清理其在filedownloader中的数据。Closes #218 #### 性能与提高 - 提高实用性: 为 `FileDownloader#start(FileDownloader, boolean)` 添加返回值: 是否成功启动任务下载。Closes #215 - 提高实用性: `FileDownloader#pause` 暂停任务时,不再仅仅是暂停一个任务,而是暂停掉所有ID为指定ID的运行中的任务。 #### 修复 - 修复(初始化-CRASH): 修复初始化FileDownloader时,从`ActivityManager`获取到运行中进程信息为空时发生CRASH。Closes #210 - 修复(小概率-CRASH): 修复当FileDownloadService已经`onDestroy`后,还接收到`snapshot-message`时发生CRASH的情况。 Closes #213 - 修复(消息流准确性): 在真正启动下载时删除目标文件,以此保证当有相同任务正在下载时,获取下载状态,不会获取到已经下载完成的错误的状态。Closes #220 - 修复(启动线性下载): 收集未绑定的任务进行启动而非只是根据FileDownloadListener去收集任务,修复无法启动两个相同`FileDownloadListener`的队列。Closes #233 - 修复(清理Messenger): 在回调 结束的消息 的回调之前进行清理任务的Messenger,而非在回调之后清理,以此确保在回调方法中可以调用`BaseDownloadTask#reuse`。Closes #229 #### 其他 - 所依赖的okhttp从`3.3.1`升到`3.4.1`。 ## Version 0.3.3 _2016-07-10_ #### 新接口 - 添加 `FileDownloadUtils#getTempPath`: 获取用于存储还未下载完成文件的临时存储路径: `filename.temp`。 Refs #172. - 添加 `FileDownloadUtils#isFilenameConverted(context:Context)`: 判断是否所有数据库中下载中的任务的文件名都已经从`filename`(在旧架构中)转为`filename.temp`。 - 添加 `FileDownloader#getStatusIgnoreCompleted(id:int)`: 获取不包含已完成状态的下载状态(如果任务已经下载完成,将收到`INVALID`)。 - 添加 `FileDownloader#getStatus(id:int, path:String)`: 获取下载状态。 - 添加 `FileDownloader#getStatus(url:String, path:String)`: 获取下载状态 - 添加 `FileDownloadUtils#generateId(url:String, path:String, pathAsDirectory:boolean)`: 生成可以被FileDownloader识别的`Download Id`。 - 添加 `BaseDownloadTask#setPath(path:String, pathAsDirectory:boolean)`: 如果`pathAsDirectory`是`true`,`path`就是存储下载文件的文件目录(而不是路径),此时默认情况下文件名`filename`将会默认从`response#header`中的`contentDisposition`中获得。 - 添加 `BaseDownloadTask#isPathAsDirectory`: 判断`BaseDownloadTask#getPath()`返回的路径是文件存储目录(`directory`),还是文件存储路径(`directory/filename`)。 - 添加 `BaseDownloadTask#getTargetFilePath`: 获取目标文件的存储路径。 - 添加 `FileDownloadQueueSet#setDirectory`: 设置队列中所有任务文件存储的目录。 #### 性能与提高 - 提高实用性: 支持将`path`作为目录来存储文件,在这个情况下,文件名默认将从`response#header`中的`contentDisposition`中获得。 Refs #200. - 提高实用性: 将还未下载完成的文件存储在临时文件中(`filename.temp`)。 Refs #172. - 提高性能: FileDownloader不再将已经完成下载的任务存储在数据库中,判定任务是否已经下载完成,直接通过判断目标文件是否存在。 Refs #176, #172. - 提高稳定性: 选用状态是`INVALID`或`progress`优先接收`completed`消息, 以此确保`connected`状态的任务能够留下来接收`progress`状态的消息。 Refs #123 - 提高稳定性: 扩张 __任务同步锁__ 到 __获取相同ID任务队列__ 的外面,以此修复由于有些状态在 __获取相同ID任务队列__ 与 __等待任务同步锁__ 的过程中已经被改变导致有些消息不能被消耗的问题。 #### 修复 - 修复(DB-维护): 保留状态是`pending`并且已经下载的字节数大于0的Model,因为这些Model可以用于恢复断点续传。 Closes #176. - 修复(crash-NPE): FileDownloader 可能遇到NPE当下载监听器被移除,但是对应任务还在FileDownloader中运行。 Closes #171. ## Version 0.3.2 _2016-06-12_ #### 新接口 - 添加 `BaseDownloadTask#setCallbackProgressMinInterval`: 用于设置每个'progress'方法回调的间隔。 Closes #167 . - 添加 `FileDownloader#setMaxNetworkThreadCount`: 用于设置最大同时下载的数目(最大同时运行的网络线程)。 Closes #168. - 添加 `FileDownloader#init(Context,OkHttpClientCustomMaker,int)`: 在下载服务初始化的时候接受设置最大同时下载数目(最大同时运行的网络线程)。 Closes #168. #### 性能与提高 - 提高稳定性: 确保每个'progress'回调方法之间的最小间隔是5ms,防止对于一个任务而言'progress'回调太频繁导致'防掉帧队列'极速膨胀导致各类Action响应都延时。 Closes #167. - 提高实用性: 在请求的操作需要在下载服务中完成,但是还未连接上下载服务时,输出对应的'warn'级别的日志。 - 提高性能: 使用`SparseArray`代替`HashMap`用于索引所有的`FileDownloadModel`。 #### 修复 - 修复(crash): 修复在某个下载任务开始下载时,发现任务的状态不正确的情况下,输出日志中提供了错误的参数类型导致的Crash。 - 修复(强制重新下载): 修复错误逻辑导致设置`BaseDownloadTask#setForceReDownload(true)`并且任务已经下载完成会促发'warn'的回调,却没有进行强制重新下载的Bug。 - 修复(class-type): 保持`SocketTimeOutException`的Class类型,不再关心`Throwable`的`message`是否为空。 #### 其他 - 所依赖的okhttp从`3.2.0`升到`3.3.1`。 ## Version 0.3.1 _2016-05-19_ #### 性能与提高 - 提高稳定性: 在结束下载时确保缓存中的数据都写入文件。 ## Version 0.3.0 _2016-05-13_ #### 修复 > 为什么FileDownload服务可以运行在UI进程? 参考 [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties). - 修复(下载服务共享UI进程时): 修复在下载服务不是运行在独立进程的情况下(非默认情况),附加的header没有带上请求的bug。Closes #149. ## Version 0.2.9 _2016-05-10_ #### 新接口 - 添加 `BaseDownloadTask#isUsing():boolean`: 用于判断当前的Task对象是否在引擎中启动过. Closes #137 。 #### 修复 - 修复(高并发情况下的npe): 当任务的状态是一个未预期的状态是,提供一个默认的错误快照,避免出现npe 。 - 修复(返回错误码-416): 覆盖返回错误码是416或者当出现已下载大小大于等于文件总大小的时候依然断点续传的bug。 ## Version 0.2.8 _2016-05-02_ #### 新接口 - 添加 `BaseDownloadTask#getId():int`: 弃用(没有删除该接口) `getDownloadId()`, 建议使用 `getId()` 代替 。 #### 性能与提高 - 提高稳定性: 重构任务启动器,使得启动任务更加可维护,以及标记任务过期更加可靠。 - 提高稳定性: 重构将事件派发给`FileDownloadListener`的体系,新的体系就如同,派件员与快递驿站的关系,每次都会对事件进行快照,打包为一个消息快件,派发到驿站,转包给 `FileDownloadListener`。 - 提高稳定性: 覆盖所有的有关暂停的高并发情况,删掉一些符合预期的警告性日志。 - 提高性能: 减少FileDownloader database I/O 。 - 提高性能: 减少创建对象(更少的内存分配请求,对于GC友好)对于每次回调, 对于一个下载状态的更新,只创建一个快照,整个通讯架构使用。 #### 修复 - 修复: 提供明确的locale用于格式化字符串,避免一些默认locale是非预期的情况发生。Closes #127 ## Version 0.2.7 _2016-04-22_ #### 新接口 - 添加 `FileDownloader#setTaskCompleted(taskAtomList:List)`: 用于告诉FileDownloader引擎,指定的一系列的任务都已经通过其他方式(非FileDownloader)下载完成。 #### 性能与提高 - 提高稳定性: 假如在下载进程调用 `bindService` 直接抛异常,防止用户在使用过程中,错误的在下载进程绑定服务,而没有暴露这个根本问题,引发其他一系列的异常。Closes #119。 ## Version 0.2.6 _2016-04-20_ #### 新接口 - 调整: 将原本需要在根目录创建的 `filedownloader.properties` ,改为到 模块的 `assets` 目录下, 如 `/demo/src/main/assets/filedownloader.properties`。 #### 修复 - 修复 `filedownloader.properties` 中的参数不起作用的bug。 Closes #117. ## Version 0.2.5 _2016-04-19_ #### 新接口 - 添加 `FileDownloader#setTaskCompleted`: 用于告诉FileDownloader引擎,以指定Url与Path的任务已经通过其他方式(非FileDownloader)下载完成。 - 支持 新的配置参数`download.max-network-thread-count` 在 `filedownloader.properties`: 同时下载的最大网络线程数,默认值是3。 Closes #116. ## Version 0.2.4 _2016-04-18_ #### 新接口 - 添加 `BaseDownloadTask#getSpeed` 以及 `BaseDownloadTask#setMinIntervalUpdateSpeed`: 获取任务的下载速度, 下载过程中为实时速度,下载结束状态为平均速度。 Closes #95 。 - 添加 `FileDownloader#startForeground` 以及 `FileDownloader#stopForeground` 用于支持 前台模式([Service#startForeground](http://developer.android.com/intl/zh-cn/reference/android/app/Service.html#startForeground(int, android.app.Notification))),保证用户从最近应用列表移除应用以后下载服务被杀。 Closes #110 。 - 支持 新的配置参数 `download.min-progress-step` 以及 `download.min-progress-time`: 最小缓冲大小以及最小缓冲时间,用于判定是否是时候将缓冲区中进度同步到数据库,以及是否是时候要确保下缓存区的数据都已经写文件。这两个值越小,更新会越频繁,下载速度会越慢,但是应对进程被无法预料的情况杀死时会更加安全。默认值是与 `com.android.providers.downloads.Constants`中的一致 65536(最小缓冲大小) 以及 2000(最小缓冲时间)。 - 支持 新的配置参数 `process.non-separate` 在 `filedownloader.properties` 中 : FileDownloadService 默认是运行在独立进程 `:filedownloader` 上的, 如果你想要FileDownloadService共享并运行在主进程上,以减少不必要的消耗(如IPC的I/O,维护进程的CPU的消耗等), 添加将该配置参数值设置为 `true`。 Closes #106 。 #### 性能与提高 - 提高性能: 提高了下载速度, 优化了同步缓冲区的数据到本地文件以及数据库的架构,很大程度的提高了下载速度。 Closes #112 。 #### 修复 - 修复: 无法重新启动一个已经暂停但是依然存在下载线程池中在pending中的任务。 Closes #111 。 ## Version 0.2.3 _2016-04-11_ #### 新接口 - 添加 `FileDownloadOutOfSpaceException`, 当将要下载的文件大小大于剩余磁盘大小时,会抛出这个异常。 - 在 `FileDownloadListener` 新增 `started` 回调方法: 在结束 `pending` 开始运行当前任务的线程时,回调该方法。 - 在 `FileDownloadMonitor.IMonitor` 新增 `onTaskStarted` 回调方法,用于监控在结束 `pending` 开始运行当前任务的线程时,回调该方法。这样就可以在监控中通过 `onTaskBegin`到`onTaskStarted`计算出pending的时间,在`onTaskStarted`到`onTaskOver`计算出真正消耗在下载的时间(Connection、Fetching)。 #### 性能与提高 - 提高实用性: 为 `FinishListener` 的 `over` 方法提供所指向的Task,为了有些时候我们为多个任务添加相同的 `FinishListener` 时,需要这个参数来判断当前是哪个任务的回调。 Closes #69 。 - 提高稳定性: 如果调用一个正在运行中的Task对象的 `start` 方法,直接抛异常;并且为已经结束的Task对象提供 `BaseDownloadTask#reuse` 进行复用。 Closes #91 。 - 提高性能: 在进入事件队列之前,拦截掉一些原本就没有监听器进行监听的事件。 #### 修复 - 修复: 在一些极端情况下,非结束的回调回调次数不符合预期的情况。 - 修复: `progress` 方法的回调中包含了对完成( `sofarBytes==totalBytes` )的回调,导致回调间隔不达预期的bug。 - 修复: 在 `warn` 回调带回 total-bytes,为了覆盖在 主进程被杀,下载进程存在的情况下,主进程重新重启并启动相同任务,total-bytes为0的bug。 Closes #90 。 - 修复: 如果连续出现失败,连续回调 `retry` 时,`retry` 只被回调一次,其他的次数的 `retry` 都不被回调的bug。 Refs: #91 。 - 修复: 在无网络状态下,启动下载,如果存在重试的机会,下载的进度被覆盖,导致下次无法断点续传的bug。 Closes #92 。 - 修复: 有可能在'检测是否可以复用'到'检测是否在下载队列'的这段时间内已经下载完成但是任务还在队列中的极端情况。 - 修复: 线性任务,在下载进程被杀重新启动被转为并行任务的bug。 ## Version 0.2.2 _2016-04-06_ #### 新接口 - 添加 `FileDownloadHttpException` 与 `FileDownloadGiveUpRetryException`, 优化异常回调处理机制. Closes #67 。 - 初始化 `FileDownloader` 传入参数由原来需要 `Application` 改为 需要 `Context`( `FileDownloader#init(Context)` ), 优化接口,并且便于单元测试。 Closes #54 。 #### 性能与提高 - 提高稳定性: 在开始获取数据之前,先检查是否有足够的空间用于存储下载文件,如果不够直接抛异常,如果足够将锁定对应空间用于正常存储正在下载的文件。 Closes #46 。 - 提高实用性: 断点续传支持,不再强制要求Etag存在;支持不需要Etag,只要后台支持 `Range` 头部参数就可以支持断点续传。 Close #35 , #66 。 #### 修复 - 修复: 在 `FileDownloadLog.NEED_LOG` 为 `true` 时,并且事件无效的情况下,`EventPool` 出现 `IllegalFormatConversionException` 异常的问题。 Closes #30 。 - 修复: 在 Filedownloader进程被杀以后, 在 `IFileDownloadIPCService` 出现异常。Closes #38 。 - 修复: 修复 response-body 可能存在的泄漏: 'WARNING: A connection to https://... was leaked. Did you forget to close a response body?' Closes #68 。 - 修复: 使用 `internal-string` 作为同步的对象,而非直接用 String对象。 - 修复: 在一些情况下如果存在重复任务,在高并发下进行中的回调次数可能不对的bug。 #### 其他 - 所依赖的okhttp从`3.1.2`升到`3.2.0`。 ## Version 0.2.0 _2016-02-15_ #### 新接口 - `filedownloader.properties-http.lenient`: 添加`http.lenient`用于配置下载引擎中是否需要忽略一些http规范性的错误(如: 忽略 `can't know the size of the download file, and its Transfer-Encoding is not Chunked`), 默认值`false`。 - `FileDownloadNotificationHelper`: 用于支持在通知栏中的通知对下载引擎中任务下载状态同步的快速集成。 - `FileDownloader#init(Application,OkHttpClientCustomMaker)`: 用于为下载引擎提供定制的OkHttpClient。 #### 修复 - 修复: 需要重新启动的列表(`FileDownloadTask.NEED_RESTART_LIST`)不为空并且下载服务断开时出现`Concurrent Modification Exception`的异常。 - 修复: 下载引擎连接丢失以后,重连任务的回调失效的bug。 - 修复: 在一些高并发下载情况下,对队列进行暂停,部分暂停不生效的bug。 ## Version 0.1.9 _2016-01-23_ > 引擎默认会打开 避免掉帧的处理(使得一次回调(FileDownloadListener)不至于太频繁导致手机显示掉帧), 如果你希望关闭这个功能(关闭以后,所有回调会与之前版本一样,所有的回调会立马抛一个消息ui线程(Handler)): `FileDownloader.getImpl().disableAvoidDropFrame()`. #### 新接口 - `FileDownloadMonitor`: 现在你可以通过这个来添加一个全局的监听器,方便调试或打点 - `FileDownloader#enableAvoidDropFrame(void)`: 开启 避免掉帧, 原理最多10ms抛一个消息到ui线程,每次在ui线程每次处理5个回调(FileDownloadListener), 默认: 开启。 - `FileDownloader#disableAvoidDropFrame(void)`: 关闭 避免掉帧,会和之前的版本一样,每个回调(FileDownloadListener)都抛一个消息到ui线程,如果频率非常高(如高并发的文件检测)可以导致ui线程被DDOS。 - `FileDownloader#isEnabledAvoidDropFrame(void)`: 是否是 开启了避免掉帧,目前如果没有设置默认是开启的。 - `FileDownloader#setGlobalPost2UIInterval(intervalMillisecond:int)`: 设置最多intervalMillisecond毫秒抛一个消息到ui线程,是 避免掉帧的具体设置。默认: 10ms,如果设置为小于0的数值,会 关闭 避免掉帧。 - `FileDownloader#setGlobalHandleSubPackageSize(packageSize:int)`: 设置每次在ui线程每次处理packageSize个回调,如果已经关闭了 避免掉帧,那么这个值将没有任何意义,默认: 5个。 - `BaseDownloadTask#setSyncCallback(syncCallback:boolean)`: 是否同步回调该task中的所有的回调(FileDownloadListener), 如果设为true, 该task的所有回调会直接在下载线程直接回调,不会抛到ui线程, 默认: false。 - `BaseDownloadTask#isSyncCallback(void):boolean`: 该task是否设置了所有回调(FileDownloadListener)同步调用(直接在下载线程直接调用,而非抛到ui线程)。 - `FileDownloadUtils#setDefaultSaveRootPath`: 设置全局默认的存储路径(Root Path),在task没有指定对应的存储路径的时候,会存储在该目录下。 - `FileDownloadQueueSet`: 用于更方便的指定几个task为一个队列,进行并行/串行下载,并且可以很方便的对整个队列中的所有任务进行统一设置。 #### 性能与提高 - 提高可调试性: 提供了一个全局监听器(`FileDownloadMonitor`),更方便与调试或打点。 - 提高性能: 优化内部EventPool的锁机制,不再处理listener的priority。 - 提高性能: 所有`FileDownloadListener`中的回调将会直接调用,而不再过一层EventPool。 #### 修复 - 修复: `EventPool`中的listener存储器无限制的bug. ## Version 0.1.5 _2016-01-17_ #### 新接口 - `BaseDownloadTask#setTag(key:int, tag:Object)`: 用于存储任意的变量方便回调中使用,以key作为索引。 - `BaseDownloadTask#getTag(key:int)`: 根据key获取存储在task中的变量。 - `BaseDownloadTask#addHeader(name:String, value:String)`: 添加自定义的请求头参数,需要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数),请勿重复添加导致400或其他错误。 - `BaseDownloadTask#addHeader(line:String)`: 添加自定义的请求头参数,需要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数),请勿重复添加导致400或其他错误。 - `BaseDownloadTask#removeAllHeaders(name:String)`: 删除由自定义添加上去请求参数为`{name}`的所有键对。 #### 性能与提高 - 提高性能: 在未打开Log的情况下,屏蔽了所有Log生成的代码。 - 提高可调试性: 重新过滤所有的日志级别,减少高级别日志输出,并且默认将会打出`Warn`、`Error`、`Assert`级别的log以便于用户在未打开日志的情况下也可以定位到基本的组件异常。 #### 修复 - 修复: 在一些高并发的情况下,有可能内部队列存在残留任务的bug,此bug可能可能引发回调被旧的任务吞掉的问题。 - 修复: 出现网络错误,或者其他错误,重新下载无法自动断点续传的bug。 #### 其他 - 所依赖的okhttp从`2.7.1`升到`3.0.1`。 ## Version 0.1.4 _2016-01-13_ #### 新接口 - `FileDownloader#unBindServiceIfIdle(void)`: 如果目前下载进程没有任务正在执行,则关停下载进程 - `FileDownloader#getStatus(downloadId)`: 获取下载Id为downloadId的状态(可参考[任务管理demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java)) - `FileDownloader#isServiceConnected(void)`: 是否已经启动并且连接上下载进程(可参考[任务管理demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java)) #### 性能与提高 - 支持[Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) 数据下载(建议看一眼[Single Task Test](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/SingleTaskTestActivity.java)). - 提高性能: 减少 IPC。 - 提高性能: 减少线程锁。 - 提高性能: 在`:filedownloader`进程启动时,对数据库中的数据进行第一级别维护。 - 提高性能: 忽略数据库中的`callbackProgressTimes`字段。 #### 修复 - 修复: 在低内存情况下,ui进程处于后台进程的情况下被回收,而下载进程(服务进程)还在, 并且还存在在下载中的任务,此时重新启动ui进程`FileDownloader#pauseAll`无法暂停已经在下载进程启动的任务的bug。 - 修复: 主动调用`FileDownloader#unBinderService`,没有释放连接相关资源的bug。 - 修复: ui进程被干掉,下载进程健还有活跃的并行任务正在下载,ui进程启动以后启动相同的队列列表,无法收到进度只收到warn的bug。 ## Version 0.1.3 _2016-01-04_ - 不再受到1.99G限制;如果是大于1.99G的文件,请使用`FileDownloadLargeFileListener`作为监听器,使用对应的`getLargeFileSoFarBytes()`与`getLargeFileTotalBytes()`接口 - 性能优化: 部分接口跨进程通信不受binder thread 阻塞。 - 依赖okhttp,从`2.7.0`升到`2.7.1` ## Version 0.1.2 _2015-12-27_ - 优化线程消化能力 - 修复大队列任务暂停可能部分无效的问题 - 修复大队列并行下载时一定概率下载已经完成回调囤积延后回调的问题 ## Version 0.1.1 _2015-12-25_ - event线程区分敏捷线程池与其他线程池,减少资源冗余强制、内部稳定性以及消化能力与性能, - 添加自动重试接口,新增用户指定如果失败自动重试的次数 ## Version 0.1.0 _2015-12-24_ - FileDownloadStatus 由`int`改为`byte`,该参数会频繁的在IPC时被拷贝 - 优化串行or并行任务时,筛选task在准备数据时就筛选好,减少冗余操作,更加安全 - 优化串行任务执行保证使用更可靠的方式 ## Version 0.0.9 _2015-12-23_ - 将调用start(启动任务)抛独立线程处理,其中的线程是通过共享非下载进程EventPool中的线程池(可并行8个线程) ## Version 0.0.8 _2015-12-22_ - initial release [RemitDatabase-png]: https://github.com/lingochamp/FileDownloader/raw/master/art/remit-database.png [FileDownloadConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java [FileDownloadUrlConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java ================================================ FILE: CHANGELOG.md ================================================ # Change log > [中文迭代日志](https://github.com/lingochamp/FileDownloader/blob/master/CHANGELOG-ZH.md) ## Version 1.7.7 - Fix: FileDownloadThreadPool may throw ArrayIndexOutOfBoundsException & ClassCastException. closes #1258 - Fix: Resume a task may occur 416 problem after upgrading from 1.6.x to 1.7.x. - Fix: Cannot show notification in demo. closes #1224 - Fix: The callback blockComplete may be invoked in main thread.closes #1069 - Fix: The thread unsafe problem of SparseArray in NoDatabaseImpl. closes #1225 ## Version 1.7.6 _2019-02-20_ #### Fix - Fix: stop foreground service after all tasks finished in Android O. closes #1096 - Fix: fix 'Context.startForegroundService() did not then call Service.startForeground()' issue. closes #1104 - Fix: insure all foreground service running notification is canceled when pause download. closes #1136 - Fix: fix tiny possibility npe during retry. closes #1100 ## Version 1.7.5 _2018-08-03_ #### Fix - Fix: fix raise "Not allowed to start service Intent..." issue when starting DownloadService on Android O and the application isn't on the foreground and also not on the whitelist, because we can't use `JobScheduler` to handle the download affair. closes #1078 #### Enhance - Improve Practicability: support character set and the launguage encoding for `Content-Disposition`. closes #1057 - Improve Practicability: cover the error response code 416 from aliyun repo. closes #1050 ## Version 1.7.4 _2018-05-19_ #### Fix - Fix: fix raise 'IllegalStateException' on Android 8+ when FileDownloader try to re-bind service after the connection with the service is lost on downloading state and the app is on the background. closes #1017 - Fix: fix directory traversal vulnerability security issue. closes #1028 ## Version 1.7.3 _2018-04-20_ #### Fix Fix: fix `fd` isn't released manually when download finished which may raise oom when there are a large number of tasks that are continuously initiated. ## Version 1.7.2 _2018-03-14_ #### Fix - Fix: do not download callback error when the instance length of the task is zero, callback complete directly instead. closes #789 - Fix: fix the temp duplicate data in the database isn't removed when there is another running task with the same temp file path. closes #953 - Fix: the data lost when retry. closes #949 - Fix: fix the instance-length is always 1 when the Content-Range isn't provided but the Content-Length is provided on the trial connection. #### Enhancement - Improve Practicability: using the content length value on the Content-Range field when there isn't Content-Length field found in the response header. closes #967 ## Version 1.7.1 _2018-02-05_ #### Fix - Fix: fix download failed with 405 response code when backend can't support `HEAD` method. close #942 ## Version 1.7.0 _2018-02-01_ #### Fix - Fix: fix update status can't keep flow through making updating status synchronized with pause action. close #889 - Fix: fix the sofar-bytes carry back through pending state callback has already discarded. close #884 - Fix: fix can't find filename if filename value on content-disposition without around with ". close #908 - Fix: correct `setCallbackProgressTimes` method make `setCallbackProgressTimes` work correctly. close #901 - Fix: fix download useless data on tcp-window because of the first trial connection use `0-infinite` range. close #933 - Fix: close intput stream when connection ending avoid input-stream leak especially for the trial connection. #### Enhancement - Improve Practicability: do not remove the temp-file if rename it to the target path success to prevent raise some file-system problem. close #912 - Improve Practicability: discard range totally if range is right but backend response 416. close #921 - Improve Performance: using HEAD request method instead of GET method for trial connect. ref #933 #### Other If you are using filedownloader-okhttp3-connection, please upgrade it to the `1.1.0` to adapter FileDownloader 1.7.0. ## Version 1.6.9 _2017-12-16_ #### Fix - Fix(serial-queue): fix deadlock on `FileDownloadSerialQueue`. closes #858 - Fix: do not use j-unit on library non-unit-test env to fix the `no-static-method-found` issue on some mi-phones. closes #867 - Fix: fix decrease two times of retry-chance each time of retry. closes #838 - Fix: fix get status is pending when a task has been paused on pending status. closes #855 #### Enhancement - Improve Practicability: public `SqliteDatabaseImpl`、`RemitDatabase`、`NoDatabaseImpl`, so you can overwrite them - Improve Practicability: support downgrade version from newer version - Improve Practicability: add the default `User-Agent` if upper layer does not add. closes #848 - Improve Performance: change the keepalive second(5s to 15s) for each executor, since when downloading multiple tasks thread release and recreate too frequently - Improve Performance: using `RemitDatabase` instead of `DefaultFiledownloadDatabase` to avoid some small task start and finished on the very short time but consume too much time on sync status to database what is useless ![][RemitDatabase-png] ## Version 1.6.8 _2017-10-13_ #### Fix - Fix: fix resume from breakpoint failed because of `isAlive` not guarantee on Network-thread. this closes #793 - Fix: fix resume from breakpoint failed, because of multi-thread update status very frequently and Messenger can't guarantee order. this refs #793, #764, #721, #769, #763, #761, #716 - Fix: do not crash user when a task has finished but the messenger still has messages, because it's fine for the user. this closes #562 - Fix: fix the callback error of 'it can't take a snapshot for the task xxx' when a user invokes pause very frequently. - Fix: fix the case of process on the model is wrong which raise 416 each time when restarting it. ## Version 1.6.7 _2017-10-12_ #### Fix - Fix: Avoid error/pause status is covered by other processing-status which will cause resume-failed, task-never-end. this closes #769, closes #764, closes #761, closes #763, closes #721, closes #716 - Fix: Fix request range value turn to negative when resuming a task which has a process more than 1.99G on its one block. Thanks to @hongbiangoal closes #791 ## Version 1.6.6 _2017-09-29_ #### Fix - Fix(file-integrality): update the process to database only if all buffers on output-stream or system has been flush and sync to device successfully to avoid resume on the wrong point raise complete file not integrality. Closes #745 - Fix(clear): fix `FileDownloader#clearAllTaskData` not clear connection table. Closes #754 #### Enhancement - Import Performance: optimize the default output-stream with buffered-output-stream, now the VM buffers length is 8192 bytes. ## Version 1.6.5 _2017-09-11_ #### Fix - Fix: fix `IllegalFormatConversionException` because of format `AtomicLong` with `%d` on `FileDownloadModel.toString`. Closes #743 ## Version 1.6.4 _2017-08-21_ #### New Interfaces - Add `NoDatabaseImpl` for the case of some users need a no-database FileDownloader. Refs #727 #### Enhancement - Import Performance: Using the `AtomicLong` instead of lock to make better efficiency on increase progressing. #### Fix - Fix: Fix response 416 http status because of the last connection range is wrong when resume downloading with the last connection has been downloaded. Closes #737 - Fix(npe): Fix the small probability NPE crash when publish event with it has been removed on other thread. Closes #723 ## Version 1.6.3 _2017-07-29_ #### Fix - Fix: Fix the small probability occur npe when the task is calling back over status with user invoke pause simultaneously. Closes #680 - Fix: Fix `MissingFormatArgumentException` when you pause or resume the FileDownloadserialQueue with it has already done it. Closes #699 ## Version 1.6.2 _2017-07-16_ #### Fix - Fix: Fix raise 'offset < 0' exception when FileDownloader downloading file with the one split connection range begin with larger than 1.99G. Closes #669 ## Version 1.6.1 _2017-07-13_ #### Enhancement - Import Practicability: Throw `GiveUpException` directly when the response `content-length` isn't equal to the expect `content-length` calculated from range. Closes #636 #### Fix - Fix: Fix sync twice when downloading paused/error. - Fix: fix file is destroyed when you download chunked file from breakpoint. ## Version 1.6.0 _2017-07-07_ #### Fix - Fix(no-response): Fix may occur no-respose when multiple connections complete fetch data simultaneously, more detail please move to [here](https://github.com/lingochamp/FileDownloader/issues/631#issuecomment-313451398). Closes #631 ## Version 1.5.9 _2017-07-04_ #### Fix - Fix(duplicate-permission): fix `INSTALL_FAILED_DUPLICATE_PERMISSION` when there are more than one application using FileDownloader lib 1.5.7 or more newer since Android 5.0. This problem is raised since v1.5.7, because of we declared permission for receiving completed status broadcast more secure, now we remove it to fix this problem. Closes #641 ## Version 1.5.8 _2017-06-28_ #### Fix - Fix(no-response): fix no-response when switch between pause and start for the same task very fast frequency. Closes #625 ## Version 1.5.7 _2017-06-25_ #### New Interfaces - Support the configuration `broadcast.completed` in `filedownloader.properties`: determine whether need post a broadcast when task is completed. Closes #605 - Support accepting 201 http status. Closes #545 - Support pause and resume for the `FileDownloadSerialQueue`. Closes #547 - Handle the case of redirect(300、301、302、303、307、308) on FileDownloader self. Closes #611 - Deprecated the `FileDownloader#init` and add the replacer `FileDownloader#setup` to let user invoke anytime before using `Filedownloader`. Closes #500 > - If you want to using `broadcast.completed` and receive completed broadcast, you also need to register receiver with `filedownloader.intent.action.completed` action name on `AndroidManifest.xml` and please using `FileDownloadBroadcastHandler` class to parse the received `Intent`. > - Now, rather than using `FileDownloader#init`, if you want to register your own customize components for FileDownloader please invoking `FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker` on the `Application#onCreate`, otherwise you just need invoke `FileDownloader.setup(Context)` anytime before using `FileDownloader`. #### Fix - Fix: fix `FileDownloadQueueSet` can't handle the case of disable wifi-required. Thanks @qtstc - Fix(output-stream): fix broken support for output-stream when it don't support seek. Closes #622 #### Enhancement - Improve Practicability: Cover the case of reusing the downloaded processing with the different url( using with `idGenerator` ). Closes #617 ## Version 1.5.6 _2017-06-18_ #### Fix - Fix(crash): fix raise NPE crash when require paused a task and invoking `findRunningTaskIdBySameTempPath` at the same time. Closes #613 - Fix(crash): fix raise `IllegalArgumentException` when response code is 206 and its ETAG is changed. Closes #612 - Fix(crash): fix raise `FileDownloadNetworkPolicyException` unhandled exception, when user enable wifi-required but no wifi-state. Thanks @qtstc - Fix(crash): fix raise `IllegalStateException` when user upgrades from `v1.4.3` or older version to `v1.5.2` or newer version directly and some more conditions, more detail please move to #610 - Fix(crash): fix some small probability case raise `IllegalStateException` when callback-flow has been final but occurring completed/error at the same time. - Fix(no-response): fix no-response after start download and receive connected callback because the resource state has been changed during the connection of verification and connections of fetch data. #### Enhancement - Improve Practicability: callback `error` directly when create the parent directory failed. Closes #542 - Improve Practicability: handle the case of response code is `416`. Closes #612 ## Version 1.5.5 _2017-06-12_ #### Fix - Fix(max-network-thread-count): fix the `download.max-network-thread-count` not work and there are no restrictions on the number of tasks downloaded at the same time since v1.5.0 when tasks runs on the multi-connection Closes #607 ## Version 1.5.4 _2017-06-11_ #### New Interfaces - Support customizing the download task identify generator through `IdGenerator`. Closes #224 #### Enhancement - Improve Practicability: Decoupling the filedownload-database with filedownload-model, let filedownload-database only care about database operation. - Improve Practicability: Decoupling the database initial-maintain from the filedownload-database default implementation to let the customized database can be maintained. ## Version 1.5.3 _2017-06-08_ #### Fix - Fix(crash): Fix divide by zero on calculating average speed when download completed and connected at the same time. Refs #601 - Fix(crash): Fix raise NPE crash when you require pause the task between executed the fetch-data-task and fetch-data-task has not yet started. Closes #601 ## Version 1.5.2 _2017-06-07_ #### Fix - Fix(crash): Fix raising NPE crash or ConcurrentModificationException when the Task is paused or error with the connection is completing at the same time. Closes #598 - Fix(crash): Fix raising NPE crash when pause the `FetchDataTask` and it still without any time to sync data to database or file-system. Refs #598 - Fix(crash): Fix raising NPE crash when using the multiple connections to download and connect failed or create `FetchDataTast` failed. Refs #598 - Fix(speed-calculate): Fix the speed result is `0` when ignore all processing callbacks and just using `FinishListener`. - Fix(finish-listener): Fix there isn't `over` callback for the `FinishListener` when the file has already been downloaded in the past. #### Enhancement - Improve Performance: Enable the WAL for the default sqlite to speed up sql operation because the most of our case is concurrently accessed and modified by multiple threads at the same time. ## Version 1.5.1 _2017-06-05_ #### Fix - Fix(crash): Fix the NPE crash when don't provided the `InitCustomMaker` on `FileDownloader#init`. Closes #592 - Fix(callback): Fix on the `pending` callback you can't get the right `sofarBytes` when there are several connections served for the task and the task is resuming from the breakpoint. - Fix(speed-monitor): Correct the result of the total average speed when the task resume from a breakpoint on `IDownloadSpeed.Monitor`. #### Enhancement - Improve Robust: Sync all process on fetch task manually when it is paused to make the process can be persist. - Improve Robust: Raise `IllegalArgumentException` when provide `context` is null on `FileDownloader.init` to expose the problem earlier. ## Version 1.5.0 _2017-06-05_ #### New Interfaces - Support multiple-connection(multiple threads) for one downloading task. Closes #102 - Support `ConnectionCountAdapter` to customize connection count for each task(you can set it through `FileDownloader#init`). #### Enhancement - Improve Performance: Refactor whole download logic and origin callback logic and remove 1000 line class `FileDownloadRunnable`. The default connection count strategy for each task, you can customize it through `ConnectionCountAdapter`: - one connection: file length [0, 1MB) - two connections: file length [1MB, 5MB) - three connections: file length [5MB, 50MB) - four connections: file length [50MB, 100MB) - five connections: file length [100MB, -] ## Version 1.4.3 _2017-05-07_ #### Fix - Fix: Remove redundant deprecated method: `FileDownloader#init(Application)`, because `Application` is implement of `Context`. ## Version 1.4.2 _2017-03-15_ #### Fix - Fix(Same File Path): Avoid two tasks writing to the same file simultaneously, Once there is an another running task with the same target path to the current task's, the current task will receive the `PathConflictException` to refused start downloading. Closes #471 #### New Interfaces - Add `FileDownloadSerialQueue#getWaitingTaskCount`: Get the count of tasks which is waiting on the serial-queue instance. Refs #345 ## Version 1.4.1 _2017-02-03_ #### Fix - Fix(High concurrency): Fix occurring the NPE crash because of it still receiving message-snapshot in the messenger but the host task has been assigned to null since it has been received over-status message-snapshot. Closes #462 - Fix(`FileDownloadHttpException`): Fix occurring the `IllegalStateException` because of cannot access request header fields after connection is set when occurring http-exception. Closes #458 ## Version 1.4.0 _2017-01-11_ #### Enhancement - Improve Performance: Optimize the logic in `FileDownloader#init`, let it lighter(just do some action like assign `context` and `maker`) #### Fix - Fix(pause): fix can't stop the task when occurring the high concurrency event about pausing task after starting it in very close time. Closes #402 - Fix(init FileDownloader): fix the very low frequent crash when init FileDownloader on the process the `FileDownloadService` settled on. Closes #420 - Fix(FileDownloadHttpException): fix params can't match the `formatter` when occur `FileDownloadHttpException` Closes #438 ## Version 1.3.9 _2016-12-18_ ### Important: - Since this version you can customize you own [FileDownloadConnection][FileDownloadConnection-java-link] component, we use [this one][FileDownloadUrlConnection-java-link] as default. - Since this version, FileDownloader don't dependency the okhttp as default. (If you still want to use the okhttp as your connection component, you can integrate [this repo](https://github.com/Jacksgong/filedownloader-okhttp3-connection) feel free) > If you still need configure `timeout`、`proxy` for the connection component, but you don't want to implement your own one, don't worry, I implement it for the default connection component too, just move to : [DemoApplication](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/DemoApplication.java#L35), check the code if you want. #### New Interfaces - Add `FileDownloadQueueSet#reuseAndStart`: Add reuseAndStart function to the queue-set to reuse task instances before start them. Ref #383 - Add `FileDownloadConnection`: Support customize the connection component for FileDownloader and remove the dependency of the okhttp as default. Closes #158 ## Version 1.3.0 _2016-10-31_ #### New Interfaces - Add `FileDownloadSerialQueue`: Easy to dynamically manage tasks and tasks in the queue will automatically start download one by one. Closes #345. - Remove the `callback` method in the `FileDownloadListener` class, besides adding the `FileDownloadListener#isInvalid` method to tell the FileDownloader whether the listener has already invalidated, which means it can't receive any messages. - Add `FileDownloader#clearAllTaskData`: Clear all data in the `filedownloader` database. Closes #361 #### Enhancement - Improve Practicability(`FileDownloadListener#blackCompleted`): Ensure the `blockCompleted` callback method can accept any `Exception`. Closes #369. - Improve Practicability(service-not-connected): Print the tips with the cause in service-not-connected-helper, in this way, when you invoke some methods need the FileDownload service has already connected but not yet, FileDownloader will not only print causes in the `Logcat` but also print the tips. #### Fix - Fix(reuse): fix `BaseDownloadTask#reuse` is called shortly after the call to `BaseDownloadTask#pause` may raise an exception. Closes #329. ## Version 1.2.2 _2016-10-15_ #### Fix - Fix(fatal-crash): fix when the task doesn't have `FileDownloadListener`, we can't receive the callback of `FileDownloadMonitor.IMonitor#onTaskOver` for it. Closes #348. ## Version 1.2.1 _2016-10-09_ #### Fix - Fix(fatal-crash): fix when the task doesn't have `FileDownloadListener`, we can't receive the callback of `FileDownloadMonitor.IMonitor#onTaskOver` for it. Closes #348. Sorry for my mistake, this bug is still exist in 1.2.1 and finally fixed in 1.2.2. ## Version 1.2.0 _2016-10-04_ #### New Interfaces - Add `FileDownloader#insureServiceBind()`: Easy to block the current thread, and start FileDownloader service, after the service started then executes the request which needs the service alive. Refs #324. - Add `FileDownloader#insureServiceBindAsync()`: Easy to start FileDownloader service, and after the service started then executes the request which needs the service alive. Refs #324. - Add `FileDownloader#bindService(runnable:Runnable)`: Easy to start FileDownloader service, and after the service started then executes the `runnable`. Refs #324. - Add `FileDownloader#init(Context,InitCustomMaker)`: Easy to initialize the FileDownloader engine with various kinds of customized components. #### Enhancement - Improve Practicability(`InitCustomMaker#database`): Support customize the database component with the implementation of `FileDownloadDatabase`, and implements the default database component: `DefaultDatabaseImpl`. - Improve Practicability(`InitCustomMaker#outputStreamCreator`): Support customize the output stream with the implementation of `FileDownloadOutputStream`, and implements the default output stream component `FileDownloadRandomAccessFile`, and some alternative components: `FileDownloadBufferedOutputStream`、`FileDownloadOkio`. ## Version 1.1.5 _2016-09-29_ #### New Interfaces - Support the configuration `file.non-pre-allocation` in `filedownloader.properties`: Whether doesn't need to pre-allocates the 'content-length' space when to start downloading, default is `false`. Closes #313 . #### Fix - Fix(fatal-crash): fix occur the `StackOverflowError` when thread pool getActiveCount is not right because of it just an approximate number. Closes #321 . - Fix(minor-crash): fix in some minor cases occur `IllegalStateException` which message is 'No reused downloaded file in this message'. Closes #316 . - Fix(minor-crash): fix when there are several serial-queues started in case of the FileDownloader service doesn't connect yet and in minor cases that the same task in the queue will be started twice which lead to crash. Refs #282 . #### Others - Dependency: Cancel the dependence of thread-pool library. Refs #321 . - MinSDKVersion: Upgrade `minSdkVersion` : 8->9. Refs #321 . ## Version 1.1.0 _2016-09-13_ #### New Interfaces - Add `BaseDownloadTask#setWifiRequired`: Set whether the task only allows downloading on the wifi network type. Default `false`. Closes #281 . #### Enhancement - Improve Performance: Alternate all thread pools to exceed-wait-pool(more detail: docs in `FileDownloadExecutors`) and all threads in pools will be terminate after idle 5 second. Refs #303 . - Improve Practicability: Handle any `Throwable`s thrown on `FileDownloadListener#blockComplete` method and callback to `FileDownloadListener#error` method instead of `FileDownloadListener#completed`. Closes #305 . #### Fix - Fix(lost-connect): Prevent the waiting-connect-list contains duplicate tasks in minor cases. ## Version 1.0.2 _2016-09-06_ #### Fix - Fix: When the service didn't connected and now it is connected and FileDownloader try to restart the 'queue-task's which in the waiting-service-connect list but occur an `IllegalStateException`. Closes #307 . ## Version 1.0.1 _2016-09-05_ #### New Interfaces > If you used `BaseDownloadTask#ready()` which is a deprecated method now, just migrate it to `BaseDownloadTask#asInQueueTask():InQueueTask` and `InQueueTask#enqueue()`. - Add `BaseDownloadTask#asInQueueTask():InQueueTask` and Deprecated `BaseDownloadTask#ready()`: Declare the task is a queue task, what will be assembled by a queue which makes up of the same `listener` task and there is a method `InQueueTask#enqueue()` to enqueue this task to the global queue to ready for being assembled by the queue. The operation of method `InQueueTask#enqueue()` is the same to the Deprecated method `BaseDownloadTask#ready()`, we wrap the `ready()` method in this way just want you to know clearly: Only if the task belongs to a queue, you need to invoke this method otherwise if this task is an isolated task but you invoke this method, it's wrong and you will receive an exception(More detail reason please move to the exception thrown in `DownloadTask#start`). #### Fix - Fix: Maybe occur an IllegalStateException when there are several isolated tasks and queues with the same `listener` object, and they are started in the different thread simultaneously. Closes #282 . ## Version 1.0.0 _2016-08-21_ #### New Interfaces - Add `BaseDownloadTask#cancel`: This method is used for explaining why the pause operation is the same as the cancel operation. #### Enhancement - Improve Performance: Hold the result of `isDownloaderProcess`. - Improve Practicability: Refactor the visible layer of the code. Closes #283 - Improve Practicability: Perfect the java doc. Closes #284 - Improve Practicability: Add the java doc website: http://fd.dreamtobe.cn. Closes #285 ## Version 0.3.5 _2016-08-16_ #### Enhancement - Improve Practicability: Add thread name to all threads used in FileDownloader. - Improve Performance: Change the count of core thread for block-completed-thread-pool: 5->2, reduce redundant resource waste. #### Fix - Fix(SQLiteFullException): Cover the case of SQLiteFullException during the entire downloading process, and ensure the exception can be carried back to `FileDownloadListener#error` . Closes #243 - Fix(directory-case): Fix in the case of the provided path is a directory, and the task already completed, if you start the task again you will receive `FileDownloadListener#completed` directly, but the `targetFilePath` may be null in the `FileDownloadListener#completed` callback method. Closes #237 ## Version 0.3.4 _2016-07-31_ #### New Interfaces - Add `FileDownloader#clear`: clear the data with the task id in the filedownloader database. Closes #218. #### Enhancement - Improve Practicability: Add return value to the method `FileDownloader#start(FileDownloadListener, boolean)` : Whether start tasks successfully. Closes #215. - Improve Practicability: Pause tasks with the same download-id rather than just pause one task through there are more than one task in downloading. #### Fix - Fix(init-crash): Fix the crash about the list of running-app-process-info from `ActivityManager` is null when to init FileDownloader. Closes #210. - Fix(minor-crash): Fix the NPE-crash when to execute receiving snapshot-message after FileDownloadService already onDestroy. Closes #213. - Fix(message-keep-flow): Delete the target file before start downloading, ensure can't get the `completed` status when another same task is downloading. Closes #220 - Fix(start-serial): Assemble non-attached-tasks to start rather than assemble tasks just refer to FileDownloadListener, fix no possibility to start two queues with the same `FileDownloadListener`. Closes #223. - Fix(free-messenger): Free the messenger of Task before call back 'over-message' to FileDownloadListener instead of after callback, ensure Task can be reused in FileDownloadListener callback method. Closes #229. #### Others - Upgrade dependency okhttp from `3.3.1` to `3.4.1`. ## Version 0.3.3 _2016-07-10_ #### New Interfaces - Add `FileDownloadUtils#getTempPath`: Get the temp path is used for storing the temporary file not completed downloading yet(`filename.temp`). Refs #172. - Add `FileDownloader#getStatusIgnoreCompleted(id:int)`: Get the downloading status without cover the completed status(If completed you will receive `INVALID`). - Add `FileDownloader#getStatus(id:int, path:String)`: Get the downloading status. - Add `FileDownloader#getStatus(url:String, path:String)`: Get the downloading status. - Add `FileDownloadUtils#isFilenameConverted(context:Context)`: Whether tasks from FileDownloader Database has converted all files' name from `filename`(in old architecture) to `filename.temp`, if it is not completed downloading yet. - Add `FileDownloadUtils#generateId(url:String, path:String, pathAsDirectory:boolean)`: Generate a `Download Id` which can be recognized in FileDownloader. - Add `BaseDownloadTask#setPath(path:String, pathAsDirectory:boolean)`: If `pathAsDirectory` is `true`, the `path` would be the absolute directory to store the downloading file, and the `filename` will be found in `contentDisposition` from the `response#header` as default. - Add `BaseDownloadTask#isPathAsDirectory`: Whether the result of `BaseDownloadTask#getPath()` is a `directory` path or `directory/filename` path. - Add `BaseDownloadTask#getTargetFilePath`: Get the target file path to store the downloading file. - Add `FileDownloadQueueSet#setDirectory`: Set the `directory` to store files in this queue. #### Enhancement - Improve Practicability: Support the `path` of the task as the directory to store the file, and in this case, the `filename` will be found in `contentDisposition` from the `response#header` as default. Refs #200. - Improve Practicability: Using the temp path to store the file not completed downloading yet(`filename.temp`). Refs #172. - Improve Performance: FileDownloader doesn't store completed tasks in Database anymore, and check whether the task has completed downloading with `File#exists()` directly. Refs #176, #172. - Improve Robust: Choosing the task which status is `INVALID` or `progress` to receive `completed` message preferentially, to ensure the callback of `progress` can be handled. Refs #123 - Improve Robust: Expanding task-sync-lock to the outside of getting-same-id-downloading-task, to fix some messages can't be consumed because status changed during getting-same-id-downloading-task and waiting for task-sync-lock. #### Fix - Fix(DB-maintain): Keeping models, whose status is `pending` and downloaded so far bytes is more than 0 because it can be used for resuming from the breakpoint. Closes #176. - Fix(crash-NPE): FileDownloader might occur NPE when the download-listener was removed, but the task is still running in FileDownloader. Closes #171. ## Version 0.3.2 _2016-06-12_ #### New Interfaces - Add `BaseDownloadTask#setCallbackProgressMinInterval`: Set the minimum time interval between each callback of 'progress'. Closes #167. - Add `FileDownloader#setMaxNetworkThreadCount`: Change the number of simultaneous downloads(the number of the simultaneously running network threads) at the code side. Closes #168. - Add `FileDownloader#init(Context,OkHttpClientCustomMaker,int)`: Accept initializing the number of simultaneous downloads(the number of the simultaneously running network threads) with the FileDownloadService initializes. Closes #168. #### Enhancement - Improve Robust: Ensure the minimum time interval between each callback of 'progress' is 5ms, To prevent internal callback of 'progress' too frequent happening. Closes #167. - Improve Practicability: Print the 'warn' priority log when a request does something in the FileDownloadService but it isn't connected yet. - Improve Performance: Using the `SparseArray` instead of `HashMap` for mapping all `FileDownloadModel`. #### Fix - Fix(crash): Fix provided wrong params in formatting character string when to starting download runnable occur the unexpected downloading status. - Fix(force-re-download): Fix the wrong logic: In the case of `BaseDownloadTask#setForceReDownload(true)` and the task has already downloaded will trigger 'warn' callback. Closes #169 . - Fix(class-type): Keep the class type of `SocketTimeOutException`, and no longer care about whether the message of Throwable is empty, this is very redundant. #### Others - Upgrade dependency okhttp from `3.2.0` to `3.3.1`. ## Version 0.3.1 _2016-05-19_ #### Enhancement - Improve Robust: Ensuring buffer is written out to the device when at the end of fetching data. ## Version 0.3.0 _2016-05-13_ #### Fix > Why FileDownload can run in UI process? Ref [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties). - Fix(shared-UI-process): fix the addition header does not attach to Http-request when the FileDownload service isn't running in the separate process to UI process. Closes #149. ## Version 0.2.9 _2016-05-10_ #### New Interfaces - Add `BaseDownloadTask#isUsing():boolean`: Whether this task object has already started and used in FileDownload Engine. Closes #137 . #### Fix - Fix(high-concurrency-npe): Providing the default snapshot when a task's status is unexpected, preventing the npe is occurred in this case. - Fix(response-416): Covering the response status code is 416 or still resume from breakpoint when its so far bytes more than or equal to total bytes. ## Version 0.2.8 _2016-05-02_ #### New Interfaces - Add `BaseDownloadTask#getId():int`: deprecate `getDownloadId()`, and using the `getId()` instead, for `BaseDownloadTask`. #### Enhancement - Improve Robust: Refactor the launcher for launching tasks more make sense, and expire tasks with listener or expire all waiting-tasks more stable. - Improve Robust: Refactor the architecture which is used to handle the event send to `FileDownloadListener`, the new architecture just like a messenger and message-station, each tasks would write snapshot messages to message-station. - Improve Robust: Cover all high concurrent situations about pausing a task, remove some expected warn logs about it. - Improve Performance: Reduce the FileDownloader database I/O. - Improve Performance: Reduce creating object(less allocating memory request, friendly to GC) for each call-back, Taking a message snapshot for a status updating, and through whole communication architecture just use it. #### Fix - Fix: Provide the definite locale for formatting strings, prevent unexpected-locale as Default happening. Closes #127 ## Version 0.2.7 _2016-04-22_ #### New Interfaces - Add `FileDownloader#setTaskCompleted(taskAtomList:List)`: Used to telling the FileDownloader Engine that a bulk of tasks have already downloaded by other ways. #### Enhancement - Improve Robust: Throw the Fatal-Exception directly when request to bind the FileDownloadService in the `:filedownloader` process. Closes #119 . ## Version 0.2.6 _2016-04-20_ #### New Interfaces - Adjust: Change the location of the `filedownloader.properties` ,no more in the root directory of project, instead below the `assets` of a module, for example `/demo/src/main/assets/filedownloader.properties`. #### Fix - Fix: `filedownloader.properties` not work. Closes #117. ## Version 0.2.5 _2016-04-19_ #### New Interfaces - Add `FileDownloader#setTaskCompleted`: Used to telling the FileDownloader Engine that the task with the url and the path has already completed downloading by other ways(not by FileDownloader Engine). - Support the configuration `download.max-network-thread-count` in `filedownloader.properties`: The maximum network thread count for downloading simultaneously, default is 3. Closes #116. ## Version 0.2.4 _2016-04-18_ #### New Interfaces - Add `BaseDownloadTask#getSpeed` and `BaseDownloadTask#setMinIntervalUpdateSpeed`: Get the download speed for a task. If it is in processing, the speed would be real-time speed; If finished, the speed would be average speed. Closes #95 - Add the `FileDownloader#startForeground` and `FileDownloader#stopForeground` for supporting the Foreground mode([Service#startForeground](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties)); For ensure the FileDownloadService would keep alive when user removed the App from the recent apps. Closes #110 . - Support configurations `download.min-progress-step` and `download.min-progress-time`: The min buffered so far bytes and millisecond, used for adjudging whether is time to sync the download so far bytes to database and make sure sync the downloaded buffers to the local file. More small more frequent, then download more slowly, but will safer in the scene of the process is killed unexpectedly. Default 65536(MinProgressStep) and 2000(MinProgressTime), which follow the value in `com.android.providers.downloads.Constants`. - Support the configuration `process.non-separate` in `filedownloader.properties`: The FileDownloadService runs in the separate process ':filedownloader' as default, if you want to run the FileDownloadService in the main process, set this configuration as `true`. Closes #106 . #### Enhancement - Improve Performance: Download more quickly, Optimize the strategy about sync the buffered datum to database and local file when processing. Closes #112 . #### Fix - Fix: Can't restart the task which in paused but is still settling in the download-pool. Closes #111 ## Version 0.2.3 _2016-04-11_ #### New Interfaces - Add `FileDownloadOutOfSpaceException`, Throw this exception, when the file will be downloaded is too large to store. - Add new call-back method in `FileDownloadListener`: `started` which will be invoked when finish pending, and start the download runnable. - Add new call-back method in `FileDownloadMonitor.IMonitor`: `onTaskStarted` which will be invoked when finish pending, and start the download runnable. #### Enhancement - Improve Practicability: Provide the current task to the method `over` in `FinishListener`, for recognizing target task in case of one-FinishListener for more than one task. Closes #69 . - Improve Robust: Throw the exception directly when invoke `BaseDownloadTask#start` for a running-task object, add provide 'reuse' method to reuse a used and already finished task object. Closes #91 . - Improve Performance: Intercept the enqueue operate for the otiose event which is no listener for handling it. #### Fix - Fix: In handful cases the task-call-back flow not follow the expect. - Fix: `progress` call-back included the ending frame ( `sofarBytes == totalBytes` ). - Fix: Carry back the total bytes in the status of warn, for covering the case of UI-process had killed but has restarted App with restarting the task and download-process is alive still, the total bytes is 0 in UI-process. Closes #90 . - Fix: Can't call-back 'retry' in expect, the case of the call-back method 'retry' one-by-one. Refs: #91 . - Fix: The wrong sofar bytes will cover the right one, when occur error in no-network and has chance to retry. Closes #92 . - Fix: Handle the case of the downloading is finished during the 'check-reuse' to 'check-downloading' in filedownloader-process. - Fix: The serial-queue converts to The parallel-queue in restoring from filedownloader-process has killed and restarting. ## Version 0.2.2 _2016-04-06_ #### New Interfaces - Add `FileDownloadHttpException` and `FileDownloadGiveUpRetryException`, and optimize the mechanism of exception. Closes #67 . - Init the `FileDownloader` use `Context` instead of `Application` ( `FileDownloader#init(Context)` ) , for more make sense and unit-test. Closes #54 . #### Enhancement - Improve Robust: Check whether free space is enough, and throw IOException directly when not enough; And pre-allocate need-available-space before fetching datum when the free space more than need-available-space. Closes #46 . - Improve Practicability: Support resume from breakpoint without ETag. Just need the server support the request-header param 'Range'. Close #35 , #66 . #### Fix - Fix: The `IllegalFormatConversionException` on `EventPool` when publishing the event which does not in effect and `FileDownloadLog.NEED_LOG` is `true`. Closes #30 . - Fix: The non-fatal-crash in `IFileDownloadIPCService.java` , when lost connection from filedownloader process. because the IBinder's hosting process(filedownloader process) has been killed/cancelled. Closes #38 . - Fix: The leak of response-body: 'WARNING: A connection to https://... was leaked. Did you forget to close a response body?' Closes #68 . - Fix: Using the internal-string as synchronized lock-object instead of string-original. - Fix: The number of the Ing-call-back is not correct in some cases. #### Others - Upgrade dependency okhttp from `3.1.2` to `3.2.0`. ## Version 0.2.0 _2016-02-15_ #### New Interfaces - `filedownloader.properties-http.lenient`: Add 'filedownloader.properties' for some special global configs, and add 'http.lenient' keyword to 'filedownloader.properties' to handle the case of want to ignore HTTP response header from download file server isn't legal. - `FileDownloadNotificationHelper`: Refashioning NotificationHelper, let handle notifications with FileDownloader more make sense. #25 - `FileDownloader#init(Application,OkHttpClientCustomMaker)`: Support customize OkHttpClient which will be used for downloading files. #### Fix - Fix: Occur 'Concurrent Modification Exception' when Downloader service is unbound or lost connection to service and NeedRestart list not empty. #23 - Fix: The case of re-connect from lost connection to service but all auto restart tasks' call-back do not effect. - Fix: In some cases of high concurrency, the Pause on some tasks is no effect. ## Version 0.1.9 _2016-01-23_ > FileDownloader is enable Avoid Missing Screen Frames as default, if you want to disable it, please invoke `FileDownloader.getImpl().disableAvoidDropFrame()`. #### New Interfaces > We default open Avoid Missing Screen Frames, if you want to disable it(will post to ui thread for each FileDownloadListener event achieved as pre version), please invoke: `FileDownloader.getImpl().disableAvoidDropFrame()`. - `FileDownloadMonitor`: You can add the global monitor for Statistic/Debugging now. - `FileDownloader#enableAvoidDropFrame(void)`: Avoid missing screen frames, but this leads to all callbacks of FileDownloadListener do not be invoked at once when it has already achieved. - `FileDownloader#disableAvoidDropFrame(void)`: Disable avoid missing screen frames, let all callbacks of FileDownloadListener be invoked at once when it achieve. - `FileDownloader#isEnabledAvoidDropFrame(void)`: Has already enabled Avoid Missing Screen Frames. Default: true - `FileDownloader#setGlobalPost2UIInterval(intervalMillisecond:int)`: For Avoid Missing Screen Frames. Each intervalMillisecond post 1 message to ui thread at most. if the value is less than 0, each callback will always post a message to ui thread immediately, may will cause missing screen frames and produce great pressure on the ui thread Looper. Default: 10ms. - `FileDownloader#setGlobalHandleSubPackageSize(packageSize:int)`: For Avoid Missing Screen Frames. {packageSize}: The number of FileDownloadListener's callback contained in each message. value completely dependent on the intervalMillisecond of setGlobalPost2UIInterval, describe will handle up to {packageSize} callbacks on the each message posted to ui thread. Default: 5. - `BaseDownloadTask#setSyncCallback(syncCallback:boolean)`: if true will invoke callbacks of FileDownloadListener directly on the download thread(do not post the message to the ui thread), default false. - `BaseDownloadTask#isSyncCallback(void):boolean`: Whether sync invoke callbacks of FileDownloadListener directly on the download thread. - `FileDownloadUtils#setDefaultSaveRootPath`: The path is used as Root Path in the case of task without setting path in the entire Download Engine. - `FileDownloadQueueSet`: In order to be more convenient to bind multiple tasks to a queue, and to the overall set. #### Enhancement - Improve Debugging: Provide the `FileDownloadMonitor` to monitor entire Download Engine. - Improve Performance: Optimize EventPool lock & do not handle listener priority any more(no use internal). - Improve Performance: Call `FileDownloadListener` methods do not through EventPool, instead, invoke directly. #### Fix - Fix: EventPool listener unlimited increased bug. ## Version 0.1.5 _2016-01-17_ #### New Interfaces - `BaseDownloadTask#setTag(key:int, tag:Object)`: Set a tag associated with this task. If the key already existed, the old tag will be replaced. - `BaseDownloadTask#getTag(key:int)`: Get the object stored in the task as a tag, or null if not set. - `BaseDownloadTask#addHeader(name:String, values:String)`: Add custom request header to the task. Attention: We have already handled ETag, and will add `If-Match` & `Range` value if it works. - `BaseDownloadTask#addHeader(line:String)`: Add custom request header to the task. Attention: We have already handled ETag, and will add `If-Match` & `Range` value if it works. - `BaseDownloadTask#removeAllHeaders(name:String)`: Remove all custom request header bind with the `{name}`. #### Enhancement - Improve Performance: Reduce the consumption of the generated log. - Improve Debugging: To filter all the log level, reduce the high level of log output, and by default, will output `Warn`、`Error`、`Assert` level of log in order to debugging in the case of the value of `FileDownloadLog.NEED_LOG` is false(default). #### Fix - Fix can't resume from the break point naturally in case of the download status of the task is Error. - Fix the size of the queue may not match the number of actual active tasks in case of high concurrency. This bug may would caused some callbacks to be consumed by the old tasks. #### Others - Upgrade dependency okhttp from `2.7.1` to `3.0.1`. ## Version 0.1.4 _2016-01-13_ #### New Interfaces - `FileDownloader#unBindServiceIfIdle(void)`: If there is no active task in the `:filedownloader` progress currently , then unbind & stop `:filedownloader` process - `FileDownloader#getStatus(downloadId)`: Get download status by the downloadId(ps: Please refer to [Tasks Manager demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java) - `FileDownloader#isServiceConnected(void)`: Whether started and connected to the `:filedownloader` progress(ps: Please refer to [Tasks Manager demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java)) #### Enhancement - Supported [Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) data download(Recommend to glance at demo on [Single Task Test](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/SingleTaskTestActivity.java)). - Improve Performance: Reduce IPC. - Improve Performance: Reduce lock. - Improve Performance: Delete invalid datum in db with the `:filedownloader` progress start. - Improve Performance: Ignore the `callbackProgressTimes` column in db. #### Fix - Fix `FileDownloader#pauseAll` not effect in case of low memory and ui progress is Background Progress situation and the `:filedownloader` progress(Service Progress) alive and still have running tasks in the `filedownloader` progress but ui progress has died and relived. - Fix not release connect resources when invoke `FileDownloader#unBinderService` manually. - Handle case of ui progress be killed by sys and download progress not be killed, and ui progress relives and re-executes same tasks queue. ## Version 0.1.3 _2016-01-04_ - Enhancement: no longer subject to the upper bound of 1.99G, add `FileDownloadLargeFileListener`, `getLargeFileSoFarBytes()`,`getLargeFileTotalBytes()`. - Performance optimization: some ipc transaction just need one-way call(async), not block(sync). - Upgrade dependency okhttp from `2.7.0` to `2.7.1`. ## Version 0.1.2 _2015-12-27_ - Optimize thread digestion([map](https://github.com/lingochamp/FileDownloader/raw/master/art/filedownload_sample_description.png). - Fix: may `pause()` invalid in large queue task. - Fix: large queue task parallel download, may download has been completed but the callback ## Version 0.1.1 _2015-12-25_ - Optimization of internal performance, according to the time split thread pool. - Add auto retry feature. ## Version 0.1.0 _2015-12-24_ - The `FileDownloadStatus` parameter type is changed from `int` to `byte`, which is frequently copied in IPC. - Optimization of multi task queue filtering time. - Optimizing serial task execution mechanism. ## Version 0.0.9 _2015-12-23_ - The start operation into independent thread processing, sharing thread pool in EventPool. ## Version 0.0.8 _2015-12-22_ - initial release [RemitDatabase-png]: https://github.com/lingochamp/FileDownloader/raw/master/art/remit-database.png [FileDownloadConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java [FileDownloadUrlConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 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. ================================================ FILE: README-zh.md ================================================ # FileDownloader Android 文件下载引擎,稳定、高效、灵活、简单易用 [![Gitter][gitter_svg]][gitter_url] [![Download][bintray_svg]][bintray_url] ![][file_downloader_svg] [![Build Status][build_status_svg]][build_status_link] [![][filedownloader_snapshot_svg]](https://oss.sonatype.org/content/repositories/snapshots/com/liulishuo/filedownloader/) > [README DOC](https://github.com/lingochamp/FileDownloader/blob/master/README.md) --- #### 版本迭代日志: [Change Log](https://github.com/lingochamp/FileDownloader/blob/master/CHANGELOG.md) #### 英文文档: [Wiki](https://github.com/lingochamp/FileDownloader/wiki)、[优化建议](https://github.com/lingochamp/FileDownloader/wiki/Optimize-Tutorial) --- ### FileDownloader2 现在, [FileDownloader2-OkDownload](https://github.com/lingochamp/okdownload) 已经正式发布, okdownload继承了所有FileDownloader的优点,甚至做了更多的优化以及更多的特性。 由于FileDownloader的单元测试覆盖太低,因此所有的进一步的需求以及提高都将会在okdownload上进行实现而非FileDownloader,而FileDownloader本身将只会关注于修复Bug。 --- ### 特点 - 简单易用 - 单任务多线程/多连接/分块下载(并支持通过`ConnectionCountAdapter`定制) - 高并发 - 灵活 - 可选择性支持: 独立/非独立进程 - 自动断点续传 #### 需要注意 - 当下载的文件大小可能大于1.99GB(2^31-1`=2_147_483_647 = 1.99GB`)的时候, 请使用`FileDownloadLargeFileListener`而不是`FileDownloadListener`(同理使用`getLargeFileSofarBytes()`与`getLargeFileTotalBytes()`) - 暂停: paused, 恢复: 直接调用start,默认就是断点续传 - 引擎默认会打开避免掉帧的处理(使得在有些情况下回调(FileDownloadListener)不至于太频繁导致ui线程被ddos), 如果你希望关闭这个功能(关闭以后,所有回调会与0.1.9之前的版本一样,所有的回调会立马抛一个消息ui线程(Handler)) - 如果没有特殊需要,直接通过配置`filedownloader.properties`将`process.non-separate`置为`true`,可以有效减少每次回调IPC带来的I/O。 --- ## Android 系统适配 ### 适配 Android 8.0 从 Android 8.0 开发,后台服务的限制增强了,可以参考[这里](https://developer.android.com/about/versions/oreo/background)了解更多信息。 因此,自 FileDownloader 1.7.6 版本开始, Android 8.0 及之后的系统上,如果在后台启动下载服务,这个服务将会是一个前台服务,同时你会看到一个标题为 "FileDownloader" 的通知。 你可以参考[这里](https://github.com/lingochamp/FileDownloader/wiki/Compatibility-of-Android-O-Servic)去自定义通知的内容。 ### 适配 Android 9.0 从 Android 9.0 (API level 28) 开始,明文请求默认被禁止,你可以在[这里](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted)了解详细信息。 FileDownloader 1.7.6 已经在 demo 中处理了此问题。 根据[迁移笔记](https://developer.android.com/about/versions/pie/android-9.0-migration#tya),`FOREGROUND_SERVICE` 这个权限已经在 1.7.6 版本添加到 library 的 manifest 里面了。 --- ## 欢迎提交 Pull requests - 尽量多的英文注解。 - 每个提交尽量的细而精准。 - Commit message 遵循: [AngularJS's commit message convention](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines)。 --- ## I. 效果 ![][single_demo_gif] ![][chunked_demo_gif] ![][serial_tasks_demo_gif] ![][parallel_tasks_demo_gif] ![][tasks_manager_demo_gif] ![][mix_tasks_demo_gif] ![][avoid_drop_frames_1_gif] ![][avoid_drop_frames_2_gif] ## II. 使用 在项目中引用: ```groovy implementation 'com.liulishuo.filedownloader:library:1.7.7' ``` > 如果是eclipse引入jar包参考: [这里](https://github.com/lingochamp/FileDownloader/issues/212#issuecomment-232240415) 如果需要引入snapshot版本,请添加sonatype的仓库: ```groovy repositories { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } ``` #### 全局初始化 如果你需要注册你的定制组件,你需要在`Application#onCreate`中调用`FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker`, 否则你只需要在使用FileDownloader之前的任意时候调用`FileDownloader.setup(Context)`即可。 这些初始化方法都十分的简单,不会启动下载服务,一般都是在10ms内完成。 #### 启动单任务下载 ```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(); ``` #### 启动多任务下载 ```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 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) // ); } // 最后你需要主动调用start方法来启动该Queue queueSet.start() // 串行任务动态管理也可以使用FileDownloadSerialQueue。 ``` #### 全局接口说明(`FileDownloader`) > 所有的暂停,就是停止,会释放所有资源并且停到所有相关线程,下次启动的时候默认会断点续传 | 方法名 | 备注 | --- | --- | setup(Context) | 如果不需要注册定制组件,就使用该方法在使用下载引擎前调用,该方法只会缓存Context | setupOnApplicationOnCreate(application):InitCustomMaker | 如果需要注册定制组件,就在Application#onCreate中调用该方法来注册定制组件以及初始化下载引擎,该方法不会启动下载服务 | create(url:String) | 创建一个下载任务 | start(listener:FileDownloadListener, isSerial:boolean) | 启动是相同监听器的任务,串行/并行启动 | pause(listener:FileDownloadListener) | 暂停启动相同监听器的任务 | pauseAll(void) | 暂停所有任务 | pause(downloadId) | 暂停downloadId的任务 | clear(downloadId, targetFilePath) | 强制清理ID为downloadId的任务在filedownloader中的数据 | getSoFar(downloadId) | 获得下载Id为downloadId的soFarBytes | getTotal(downloadId) | 获得下载Id为downloadId的totalBytes | bindService(void) | 主动启动下载进程(可事先调用该方法(可以不调用),保证第一次下载的时候没有启动进程的速度消耗) | unBindService(void) | 主动关停下载进程 | unBindServiceIfIdle(void) | 如果目前下载进程没有任务正在执行,则关停下载进程 | isServiceConnected(void) | 是否已经启动并且连接上下载进程(可参考任务管理demo中的使用) | getStatusIgnoreCompleted(downloadId) | 获取不包含已完成状态的下载状态(如果任务已经下载完成,将收到`INVALID`) | getStatus(id:int, path:String) | 获取下载状态 | getStatus(url:String, path:String) | 获取下载状态 | setGlobalPost2UIInterval(intervalMillisecond:int) | 为了避免掉帧,这里是设置了最多每interval毫秒抛一个消息到ui线程(使用Handler),防止由于回调的过于频繁导致ui线程被ddos导致掉帧。 默认值: 10ms. 如果设置小于0,将会失效,也就是说每个回调都直接抛一个消息到ui线程 | setGlobalHandleSubPackageSize(packageSize:int) | 为了避免掉帧, 如果上面的方法设置的间隔是一个小于0的数,这个packageSize将不会生效。packageSize这个值是为了避免在ui线程中一次处理过多回调,结合上面的间隔,就是每个interval毫秒间隔抛一个消息到ui线程,而每个消息在ui线程中处理packageSize个回调。默认值: 5 | enableAvoidDropFrame(void) | 开启 避免掉帧处理。就是将抛消息到ui线程的间隔设为默认值10ms, 很明显会影响的是回调不会立马通知到监听器(FileDownloadListener)中,默认值是: 最多10ms处理5个回调到监听器中 | disableAvoidDropFrame(void) | 关闭 避免掉帧处理。就是将抛消息到ui线程的间隔设置-1(无效值),这个就是让每个回调都会抛一个消息ui线程中,可能引起掉帧 | isEnabledAvoidDropFrame(void) | 是否开启了 避免掉帧处理。默认是开启的 | startForeground(id:int, notification:Notification) | 设置FileDownloadService为前台模式,保证用户从最近应用列表移除应用以后下载服务不会被杀 | stopForeground(removeNotification:boolean) | 取消FileDownloadService的前台模式 | setTaskCompleted(url:String, path:String, totalBytes:long) | 用于告诉FileDownloader引擎,以指定Url与Path的任务已经通过其他方式(非FileDownloader)下载完成 | setTaskCompleted(taskAtomList:List) | 用于告诉FileDownloader引擎,指定的一系列的任务都已经通过其他方式(非FileDownloader)下载完成 | setMaxNetworkThreadCount(int) | 设置最大并行下载的数目(网络下载线程数), [1,12] | clearAllTaskData() | 清空`filedownloader`数据库中的所有数据 #### 定制化组件接口说明(`InitCustomMaker`) | 方法名 | 需实现接口 | 已有组件 | 默认组件 | 说明 | --- | --- | --- | --- | --- | database | FileDownloadDatabase | RemitDatabase、SqliteDatabaseImpl、NoDatabaseImpl | RemitDatabase | 传入定制化数据库组件,用于存储用于断点续传的数据 | connection | FileDownloadConnection | FileDownloadUrlConnection | FileDownloadUrlConnection | 传入定制化的网络连接组件,用于下载时建立网络连接 | outputStreamCreator | FileDownloadOutputStream | FileDownloadRandomAccessFile | FileDownloadRandomAccessFile | 传入输出流组件,用于下载时写文件使用 | maxNetworkThreadCount | - | - | 3 | 传入创建下载引擎时,指定可用的下载线程个数 | ConnectionCountAdapter | ConnectionCountAdapter | DefaultConnectionCountAdapter | DefaultConnectionCountAdapter | 根据任务指定其线程数 | IdGenerator | IdGenerator | DefaultIdGenerator | DefaultIdGenerator | 自定义任务Id生成器 > - 如果你希望Okhttp作为你的网络连接组件,可以使用[这个库](https://github.com/Jacksgong/filedownloader-okhttp3-connection)。 > - 如果你不希望FileDownloader用到任何的数据库(是用于存储任务的断点续成信息的),只需要使用[NoDatabaseImpl.java](https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/NoDatabaseImpl.java)即可。 #### Task接口说明 | 方法名 | 备注 | --- | --- | setPath(path:String) | 下载文件的存储绝对路径 | setPath(path:String, pathAsDirectory:boolean) | 如果`pathAsDirectory`是`true`,`path`就是存储下载文件的文件目录(而不是路径),此时默认情况下文件名`filename`将会默认从`response#header`中的`contentDisposition`中获得 | setListener(listener:FileDownloadListener) | 设置监听,可以以相同监听组成队列 | setCallbackProgressTimes(times:int) | 设置整个下载过程中`FileDownloadListener#progress`最大回调次数 | setCallbackProgressIgnored() | 忽略所有的`FileDownloadListener#progress`的回调 | setCallbackProgressMinInterval(minIntervalMillis:int) | 设置每个`FileDownloadListener#progress`之间回调间隔(ms) | setTag(tag:Object) | 内部不会使用,在回调的时候用户自己使用 | setTag(key:int, tag:Object) | 用于存储任意的变量方便回调中使用,以key作为索引 | setForceReDownload(isForceReDownload:boolean) | 强制重新下载,将会忽略检测文件是否健在 | setFinishListener(listener:FinishListener) | 结束监听,仅包含结束(over(void))的监听 | setAutoRetryTimes(autoRetryTimes:int) | 当请求或下载或写文件过程中存在错误时,自动重试次数,默认为0次 | setSyncCallback(syncCallback:boolean) | 如果设为true, 所有FileDownloadListener中的回调都会直接在下载线程中回调而不抛到ui线程, 默认为false | addHeader(name:String, value:String) | 添加自定义的请求头参数,需要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数),请勿重复添加导致400或其他错误 | addHeader(line:String) | 添加自定义的请求头参数,需要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数),请勿重复添加导致400或其他错误 | setMinIntervalUpdateSpeed(minIntervalUpdateSpeedMs:int) | 设置下载中刷新下载速度的最小间隔 | removeAllHeaders(name:String) | 删除由自定义添加上去请求参数为`{name}`的所有键对 | setWifiRequired(isWifiRequired:boolean) | 设置任务是否只允许在Wifi网络环境下进行下载。 默认值 `false` | asInQueueTask(void):InQueueTask | 申明该任务将会是队列任务中的一个任务,并且转化为`InQueueTask`,之后可以调用`InQueueTask#enqueue`将该任务入队以便于接下来启动队列任务时,可以将该任务收编到队列中 | start(void) | 启动孤立的下载任务 | pause(void) | 暂停下载任务(也可以理解为停止下载,但是在start的时候默认会断点续传) | getId(void):int | 获取唯一Id(内部通过url与path生成) | getUrl(void):String | 获取下载连接 | getCallbackProgressTimes(void):int | 获得progress最大回调次数 | getCallbackProgressMinInterval(void):int | 获得每个progress之间的回调间隔(ms) | getPath(void):String | 获取文件路径 或 文件目录 | isPathAsDirectory | 判断`getPath()`返回的路径是文件存储目录(`directory`),还是文件存储路径(`directory/filename`) | getTargetFilePath | 获取目标文件的存储路径 | getListener(void):FileDownloadListener | 获取监听器 | getSoFarBytes(void):int | 获取已经下载的字节数 | getTotalBytes(void):int | 获取下载文件总大小 | getStatus(void):int | 获取当前的状态 | isForceReDownload(void):boolean | 是否强制重新下载 | getEx(void):Throwable | 获取下载过程抛出的Throwable | isReusedOldFile(void):boolean | 判断是否是直接使用了旧文件(检测是有效文件),没有启动下载 | getTag(void):Object | 获取用户setTag进来的Object | getTag(key:int):Object | 根据key获取存储在task中的变量 | isContinue(void):boolean | 是否成功断点续传 | getEtag(void):String | 获取当前下载获取到的ETag | getAutoRetryTimes(void):int | 自动重试次数 | getRetryingTimes(void):int | 当前重试次数。将要开始重试的时候,会将接下来是第几次 | isSyncCallback(void):boolean | 是否是设置了所有FileDownloadListener中的回调都直接在下载线程直接回调而不抛到ui线程 | getSpeed():int | 获取任务的下载速度, 下载过程中为实时速度,下载结束状态为平均速度 | isUsing():boolean | 判断当前的Task对象是否在引擎中启动过 | isWifiRequired():boolean | 获取当前任务是否被设置过只允许在Wifi网络环境下下载 #### 监听器(`FileDownloadListener`)说明 ##### 一般的下载回调流程: ``` pending -> started -> connected -> (progress <->progress) -> blockComplete -> completed ``` ##### 可能会遇到以下回调而直接终止整个下载过程: ``` paused / completed / error / warn ``` ##### 如果检测存在已经下载完成的文件(可以通过`isReusedOldFile`进行决策是否是该情况)(也可以通过`setForceReDownload(true)`来避免该情况): ``` blockComplete -> completed ``` ##### 方法说明 | 回调方法 | 备注 | 带回数据 | --- | --- | --- | pending | 等待,已经进入下载队列 | 数据库中的soFarBytes与totalBytes | started | 结束了pending,并且开始当前任务的Runnable | - | connected | 已经连接上 | ETag, 是否断点续传, soFarBytes, totalBytes | progress | 下载进度回调 | soFarBytes | blockComplete | 在完成前同步调用该方法,此时已经下载完成 | - | retry | 重试之前把将要重试是第几次回调回来 | 之所以重试遇到Throwable, 将要重试是第几次, soFarBytes | completed | 完成整个下载过程 | - | paused | 暂停下载 | soFarBytes | error | 下载出现错误 | 抛出的Throwable | warn | 在下载队列中(正在等待/正在下载)已经存在相同下载连接与相同存储路径的任务 | - ![][file_download_listener_callback_flow_png] ##### 由于`FileDownloadListener`中的方法回调过快,导致掉帧? > 你有两种方法可以解决这个问题 1. `FileDownloader#enableAvoidDropFrame`, 默认 就是开启的 2. `BaseDownloadTask#setSyncCallback`, 默认是false, 如果设置为true,所有的回调都会在下载线程直接同步调用而不会抛到ui线程。 #### `FileDownloadMonitor` > 你可以添加一个全局监听器来进行打点或者是调试 | 方法名 | 备注 | --- | --- | setGlobalMonitor(monitor:IMonitor) | 设置与替换一个全局监听器到下载引擎中 | releaseGlobalMonitor(void) | 释放已经设置到下载引擎中的全局监听器 | getMonitor(void) | 获取已经设置到下载引擎中的全局监听器 ##### `FileDownloadMonitor.IMonitor` > 监听器接口类 | 接口 | 备注 | --- | --- | onRequestStart(count:int, serial:boolean, lis:FileDownloadListener) | 将会在启动队列任务是回调这个方法 | onRequestStart(task:BaseDownloadTask) | 将会在启动单一任务时回调这个方法 | onTaskBegin(task:BaseDownloadTask) | 将会在内部接收并开始task的时候回调这个方法(会在`pending`回调之前) | onTaskStarted(task:BaseDownloadTask) | 将会在task结束pending开始task的runnable的时候回调该方法 | onTaskOver(task:BaseDownloadTask) | 将会在task走完所有生命周期是回调这个方法 #### `FileDownloadUtils` | 方法名 | 备注 | --- | --- | setDefaultSaveRootPath(path:String) | 在整个引擎中没有设置路径时`BaseDownloadTask#setPath`这个路径将会作为它的Root path | getTempPath | 获取用于存储还未下载完成文件的临时存储路径: `filename.temp` | isFilenameConverted(context:Context) | 判断是否所有数据库中下载中的任务的文件名都已经从`filename`(在旧架构中)转为`filename.temp` #### `FileDownloadNotificationHelper` > 如何快速集成Notification呢? 建议参考[NotificationMinSetActivity](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/NotificationMinSetActivity.java)、[NotificationSampleActivity](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/NotificationSampleActivity.java)。 #### `filedownloader.properties` > 如果你需要定制化FileDownloader,可以在你的项目模块的`assets` 目录下添加 'filedownloader.properties' 文件(如 `/demo/src/main/assets/filedownloader.properties`),然后添加以下可选相关配置。 > 格式: `keyword=value` | 关键字 | 描述 | 默认值 | --- | --- | --- | http.lenient | 如果你遇到了: 'can't know the size of the download file, and its Transfer-Encoding is not Chunked either', 但是你想要忽略类似的返回头不规范的错误,直接将该关键字参数设置为`true`即可,我们将会将其作为`chunck`进行处理 | false | process.non-separate | FileDownloadService 默认是运行在独立进程':filedownloader'上的, 如果你想要FileDownloadService共享并运行在主进程上, 将该关键字参数设置为`true`,可以有效减少IPC产生的I/O | false | download.min-progress-step | 最小缓冲大小,用于判定是否是时候将缓冲区中进度同步到数据库,以及是否是时候要确保下缓存区的数据都已经写文件。值越小,更新会越频繁,下载速度会越慢,但是应对进程被无法预料的情况杀死时会更加安全 | 65536 | download.min-progress-time | 最小缓冲时间,用于判定是否是时候将缓冲区中进度同步到数据库,以及是否是时候要确保下缓存区的数据都已经写文件。值越小,更新会越频繁,下载速度会越慢,但是应对进程被无法预料的情况杀死时会更加安全 | 2000 | download.max-network-thread-count | 用于同时下载的最大网络线程数, 区间[1, 12] | 3 | file.non-pre-allocation | 是否不需要在开始下载的时候,预申请整个文件的大小(`content-length`) | false | broadcast.completed | 是否需要在任务下载完成后发送一个完成的广播 | false > 如果你使用`broadcast.completed`并且接收任务完成的广播,你需要注册Action为`filedownloader.intent.action.completed`的广播并且使用`FileDownloadBroadcastHandler`来处理接收到的`Intent`。 III. 异常处理 > 所有的异常,都将在 `FileDownloadListener#error(BaseDownloadTask, Throwable)` 中获知。 | Exception | 原因 | --- | --- | `FileDownloadHttpException`| 在发出请求以后,response-code不是200(HTTP_OK),也不是206(HTTP_PARTIAL)的情况下会抛出该异常; 在这个异常对象会带上 response-code、response-header、request-header。 | `FileDownloadGiveUpRetryException` | 在请求返回的 response-header 中没有带有文件大小(content-length),并且不是流媒体(transfer-encoding)的情况下会抛出该异常;出现这个异常,将会忽略所有重试的机会(`BaseDownloadTask#setAutoRetryTimes`). 你可以通过在 `filedownloader.properties`中添加 `http.lenient=true` 来忽略这个异常,并且在该情况下,直接作为流媒体进行下载。 | `FileDownloadOutOfSpaceException` | 当将要下载的文件大小大于剩余磁盘大小时,会抛出这个异常。 | 其他 | 程序错误。 | `FileDownloadNetworkPolicyException` | 设置了`BaseDownloadTask#setWifiRequired(true)`,在下载过程中,一旦发现网络情况转为非Wifi环境,便会抛回这个异常 | `PathConflictException` | 当有一个正在下载的任务,它的存储路径与当前任务的存储路径完全一致,为了避免多个任务对同一个文件进行写入,当前任务便会抛回这个异常 ## III. 低内存情况 ### 非下载进程(一般是UI进程): > 这边的数据并不多,只是一些队列数据,用不了多少内存。 #### [前台进程](http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html)数据被回收: 如果在前台的时候这个数据都被回收了, 你的应用应该也挂了。极低概率事件。 #### [后台进程](http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html)数据被回收: 一般事件, 如果是你的下载是UI进程启动的,如果你的UI进程处于`后台进程`(可以理解为应用被退到后台)状态,在内存不足的情况下会被回收(回收优先级高于`服务进程`),此时分两种情况: 1. 是串行队列任务,在回收掉UI进程内存以后,下载进程会继续下载完已经pending到下载进程的那个任务,而还未pending到下载进程的任务会中断下载(由于任务驱动线性执行的是在UI进程); 有损体验: 下次进入应用重启启动整个队列,会继续上次的下载。 2. 是并行队列任务,在回收掉UI进程内存以后,下载进程会继续下载所有任务(所有已经pending到下载进程的任务,由于这里的pending速度是很快的,因此几乎是点击并行下载,所有任务在很短的时间内都已经pending到下载进程了),而UI进程由于被回收,将不会收到所有的监听; 有损体验: 下次进入应用重新启动整个队列,就会和正常的下载启动一致,收到所有情况的监听。 ### 下载进程: > 对内存有一定的占用,但是并不多,每次启动进程会根据数据的有效性进行清理冗余数据,被回收是低概率事件 由于下载不断有不同的buffer占用内存,但是由于在下载时,是活跃的`服务进程`,因此被回收是低概率事件(会先回收完所有`空进程`、`后台进程`(后台应用)以后,如果内存还不够,才会回收该进程)。 即使被回收,也不会有任何问题。由于我们使用的是`START_STICKY`(如果不希望被重启可主动调用`FileDownloader#unBindService`/`FileDownloader#unBindServiceIfIdle`),因此在内存足够的时候,下载进程会尝试重启(系统调度),非下载进程(一般是UI进程) 接收到下载进程的连接,会继续下载与继续接收回调,下载进程也会断点续传没有下载完的所有任务(无论并行与串行),不会影响体验。 ## IV. LICENSE ``` 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. ``` [gitter_url]: https://gitter.im/lingochamp/FileDownloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge [gitter_svg]: https://badges.gitter.im/lingochamp/FileDownloader.svg [license_2_svg]: https://img.shields.io/hexpm/l/plug.svg [android_platform_svg]: https://img.shields.io/badge/Platform-Android-brightgreen.svg [file_downloader_svg]: https://img.shields.io/badge/Android-FileDownloader-orange.svg [mix_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/mix_tasks_demo.gif [parallel_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/parallel_tasks_demo.gif [serial_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/serial_tasks_demo.gif [tasks_manager_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/tasks_manager_demo.gif [avoid_drop_frames_1_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames1.gif [avoid_drop_frames_2_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames2.gif [single_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/single_demo.gif [chunked_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/chunked_demo.gif [bintray_svg]: https://api.bintray.com/packages/jacksgong/maven/FileDownloader/images/download.svg [bintray_url]: https://bintray.com/jacksgong/maven/FileDownloader/_latestVersion [file_download_listener_callback_flow_png]: https://github.com/lingochamp/FileDownloader/raw/master/art/filedownloadlistener_callback_flow.png [build_status_svg]: https://travis-ci.org/lingochamp/FileDownloader.svg?branch=master [filedownloader_snapshot_svg]: https://img.shields.io/badge/SnapShot-1.7.8-yellow.svg [build_status_link]: https://travis-ci.org/lingochamp/FileDownloader ================================================ FILE: README.md ================================================ # FileDownloader Android multi-task file download engine. [![Download][bintray_svg]][bintray_url] ![][file_downloader_svg] [![Build Status][build_status_svg]][build_status_link] [![][filedownloader_snapshot_svg]](https://oss.sonatype.org/content/repositories/snapshots/com/liulishuo/filedownloader/) > [中文文档](https://github.com/lingochamp/FileDownloader/blob/master/README-zh.md) ## FileDownloader2 Now, [FileDownloader2-OkDownload](https://github.com/lingochamp/okdownload) is released, okdownload will contain all advantage on the FileDownloader and beyond. Because of FileDownloader unit-test coverage is very low, so all farther features and enhances will be achieved on the okdownload instead of FileDownloader, and FileDownloader will only focuses on bug fixes. ## DEMO ![][single_demo_gif] ![][chunked_demo_gif] ![][serial_tasks_demo_gif] ![][parallel_tasks_demo_gif] ![][tasks_manager_demo_gif] ![][hybrid_test_demo_gif] ![][avoid_drop_frames_1_gif] ![][avoid_drop_frames_2_gif] ## Installation FileDownloader is installed by adding the following dependency to your `build.gradle` file: ```groovy dependencies { implementation 'com.liulishuo.filedownloader:library:1.7.7' } ``` Snapshots of the development version are available in [Sonatype's `snapshots` repository](https://oss.sonatype.org/content/repositories/snapshots/), you can include on your gradle project through: ```groovy repositories { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } ``` ## Open customize component From now on, FileDownloader support following components to be customized by yourself: | Name | Interface | Default Impl | --- | --- | --- | Connection | [FileDownloadConnection][FileDownloadConnection-java-link] | [FileDownloadUrlConnection][FileDownloadUrlConnection-java-link] | OutputStream | [FileDownloadOutputStream][FileDownloadOutputStream-java-link] | [FileDownloadRandomAccessFile][FileDownloadRandomAccessFile-java-link] | Database | [FileDownloadDatabase][FileDownloadDatabase-java-link] | [RemitDatabase][RemitDatabase-java-link] | ConnectionCountAdapter | [ConnectionCountAdapter][ConnectionCountAdapter-java-link] | [DefaultConnectionCountAdapter][DefaultConnectionCountAdapter-java-link] | IdGenerator | [IdGenerator][IdGenerator-java-link] | [DefaultIdGenerator][DefaultIdGenerator-java-link] | ForegroundServiceConfig | [ForegroundServiceConfig][ForegroundServiceConfig-java-link] | [ForegroundServiceConfig][ForegroundServiceConfig-java-link] > - If you want to use okhttp as your connection component, the simplest way is [this repo](https://github.com/Jacksgong/filedownloader-okhttp3-connection). > - If you don't want to use any database on FileDownloader(the database on FileDownloader is used for persist tasks' breakpoint info) just using [NoDatabaseImpl.java](https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/NoDatabaseImpl.java) ### How to valid it? Just create your own `DownloadMgrInitialParams.InitCustomMaker` and put those customized component to it, finally init the FileDownloader with it: [FileDownloader#init](https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/FileDownloader.java#L62) ## Adaptation ### Adapt to Android 8.0 The restriction of background service has been tightened since Android 8.0, for more details, please refer to [here](https://developer.android.com/about/versions/oreo/background). So, after Android 8.0, the download service will be a foreground service when start downloading during app is in background and you will see a notification with a title named "FileDownloader" start from FileDownloader 1.7.6. You can refer to [here](https://github.com/lingochamp/FileDownloader/wiki/Compatibility-of-Android-O-Service) to custom the notification. ### Adapt to Android 9.0 Starting with Android 9.0 (API level 28), cleartext support is disabled by default, you can have a look at [here](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted) to know about more details. FileDownloader demo has handled this problem start with 1.7.6. According to the [migration notes](https://developer.android.com/about/versions/pie/android-9.0-migration#tya), the FOREGROUND_SERVICE permission has been added to the library manifest since FileDownloader 1.7.6. ## Welcome PR > If you can improve the unit test for this project would be great. - Comments as much as possible. - Commit message format follow: [AngularJS's commit message convention](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines) . - The change of each commit as small as possible. ![][structure-img] ![][message-system-img] ## Usage By default, the FileDownloadService runs on the separate process, if you want to run it on the main process, just configure on the [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties), and you can use `FileDownloadUtils.isDownloaderProcess(Context)` to check whether the FileDownloadService can run on the current process. For more readable, Moved to [Wiki](https://github.com/lingochamp/FileDownloader/wiki). ## LICENSE ``` 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. ``` [license_2_svg]: https://img.shields.io/hexpm/l/plug.svg [android_platform_svg]: https://img.shields.io/badge/Platform-Android-brightgreen.svg [file_downloader_svg]: https://img.shields.io/badge/Android-FileDownloader-orange.svg [structure-img]: https://github.com/lingochamp/FileDownloader/raw/master/art/structure.png [message-system-img]: https://github.com/lingochamp/FileDownloader/raw/master/art/message-system.png [hybrid_test_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/hybrid_test_demo.gif [parallel_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/parallel_tasks_demo.gif [serial_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/serial_tasks_demo.gif [tasks_manager_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/tasks_manager_demo.gif [avoid_drop_frames_1_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames1.gif [avoid_drop_frames_2_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames2.gif [single_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/single_demo.gif [chunked_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/chunked_demo.gif [bintray_svg]: https://api.bintray.com/packages/jacksgong/maven/FileDownloader/images/download.svg [bintray_url]: https://bintray.com/jacksgong/maven/FileDownloader/_latestVersion [file_download_listener_callback_flow_png]: https://github.com/lingochamp/FileDownloader/raw/master/art/filedownloadlistener_callback_flow.png [build_status_svg]: https://travis-ci.org/lingochamp/FileDownloader.svg?branch=master [filedownloader_snapshot_svg]: https://img.shields.io/badge/SnapShot-1.7.8-yellow.svg [build_status_link]: https://travis-ci.org/lingochamp/FileDownloader [FileDownloadConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java [FileDownloadUrlConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java [FileDownloadDatabase-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/database/RemitDatabase.java [RemitDatabase-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/database/RemitDatabase.java [FileDownloadOutputStream-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/stream/FileDownloadOutputStream.java [FileDownloadRandomAccessFile-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/stream/FileDownloadRandomAccessFile.java [ConnectionCountAdapter-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadHelper.java#L100 [DefaultConnectionCountAdapter-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/DefaultConnectionCountAdapter.java [IdGenerator-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadHelper.java#L55 [DefaultIdGenerator-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/DefaultIdGenerator.java [ForegroundServiceConfig-java-link]:https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/ForegroundServiceConfig.java ================================================ FILE: build.gradle ================================================ buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.3.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' } } allprojects { repositories { jcenter() google() } } subprojects { group = GROUP version = VERSION_NAME } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: checkstyle.xml ================================================ ================================================ FILE: demo/.gitignore ================================================ /build ================================================ FILE: demo/build.gradle ================================================ apply plugin: 'com.android.application' android { signingConfigs { release { keyAlias 'FileDownloaderDemoKey' keyPassword 'liulishuo' storeFile file('filedownloaderdemo.jks') storePassword 'liulishuo' } } compileSdkVersion COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 14 targetSdkVersion COMPILE_SDK_VERSION as int File file = project.rootProject.file('local.properties'); def needLog = null if (file.exists()) { Properties p = new Properties() p.load(file.newDataInputStream()) needLog = p.getProperty("needLog") } buildConfigField "boolean", "DOWNLOAD_NEED_LOG", needLog == "true" ? "true" : "false" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } debug { minifyEnabled false } } lintOptions { // This seems to be firing due to okio referencing java.nio.File // which is harmless for us. warning 'InvalidPackage' } } dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:design:28.0.0' debugImplementation 'cn.dreamtobe.threaddebugger:threaddebugger:1.3.3' releaseImplementation 'cn.dreamtobe.threaddebugger:threaddebugger-no-op:1.3.3' implementation project(':library') // for testing implementation 'com.squareup.okio:okio:1.14.0' } ================================================ FILE: demo/proguard-rules.pro ================================================ # filedownloader uses okhttp3-lib, so need add below proguard rules. -dontwarn okhttp3.* -dontwarn okio.** ================================================ FILE: demo/src/main/AndroidManifest.xml ================================================ ================================================ FILE: demo/src/main/assets/filedownloader.properties ================================================ # If you occur exception: 'can't know the size of the download file, and its Transfer-Encoding is not Chunked either', # but you want to ignore such exception, set true, will deal with it as the case of transfer encoding chunk. default false # # If true, will ignore HTTP response header does not has content-length either not chunk transfer # encoding. # # Default false. http.lenient=false # The FileDownloadService runs in the separate process ':filedownloader' as default, if you want to # run the FileDownloadService in the main process, just set true. default false. process.non-separate=false # The min buffered so far bytes. # # Used for adjudging whether is time to sync the downloaded so far bytes to database and make sure # sync the downloaded buffer to local file. # # More smaller more frequently, then download more slowly, but will more safer in scene of the # process is killed unexpectedly. # # Default 65536, which follow the value in com.android.providers.downloads.Constants. download.min-progress-step=65536 # The min buffered millisecond. # # Used for adjudging whether is time to sync the downloaded so far bytes to database and make sure # sync the downloaded buffer to local file. # # More smaller more frequently, then download more slowly, but will more safer in scene of the # process is killed unexpectedly. # # Default 2000, which follow the value in com.android.providers.downloads.Constants. download.min-progress-time=2000 # The maximum network thread count for downloading simultaneously. # # FileDownloader is designed to download 3 files simultaneously as maximum size as default, and the # rest of the task is in the FIFO(First In First Out) pending queue. # # Because the network resource is limited to one device, it means if FileDownloader start # downloading tasks unlimited simultaneously, it will be blocked by lack of the network resource, # and more useless CPU occupy. # # The relative efficiency of 3 is higher than others(As Fresco or Picasso do), But for case by case # FileDownloader is support to configure for this. # # Default 3. # max 12, min 1. If the value more than {@code max} will be replaced with {@code max}; If the value # less than {@code min} will be replaced with {@code min}. download.max-network-thread-count=3 # Whether need to pre-allocates the 'content-length' space when start downloading. # # FileDownloader is designed to create the file and pre-allocates the 'content-length' space for it # when start downloading. # # Because FileDownloader want to prevent the space is not enough to store coming data in downloading # state as default. # # Default false. # file.non-pre-allocation=false # Whether need to post an broadcast when downloading is completed. # # This option is very useful when you download something silent on the background on the filedownloader # process, and the main process is killed, but you want to do something on the main process when tasks # are completed downloading on the filedownloader process, so you can set this one to `true`, then # when a task is completed task, you will receive the broadcast, and the main process will be relaunched # to handle the broadcast. # # If you want to receive such broadcast, you also need to register receiver with # 'filedownloader.intent.action.completed' action name on 'AndroidManifest.xml'. # # You can use FileDownloadBroadcastHandler class to parse the received intent. # # Default false. # broadcast.completed=false # Whether you want the first trial connection with HEAD method to request to backend or not. # # if this value is true, the first trial connection will with HEAD method instead of GET method and # then you will reduce 1 byte cost on the response body, but if the backend can't support HEAD # method you will receive 405 response code and failed to download. # # Default false. # download.trial-connection-head-method=false ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/Constant.java ================================================ package com.liulishuo.filedownloader.demo; /** * Created by Jacksgong on 12/19/15. */ public interface Constant { String[] CHUNKED_TRANSFER_ENCODING_DATA_URLS = { "http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx?0.04400023248109086", }; String LIULISHUO_APK_URL = "http://cdn.llsapp.com/android/LLS-v4.0-595-20160908-143200.apk"; String LIULISHUO_CONTENT_DISPOSITION_FILENAME = "LLS-v4.0-595-20160908-143200.apk"; String[] BIG_FILE_URLS = { // 5m "http://mirror.internode.on.net/pub/test/5meg.test5", // 6m "http://download.chinaunix.net/down.php?id=10608&ResourceID=5267&site=1", // 8m "http://7xjww9.com1.z0.glb.clouddn.com/Hopetoun_falls.jpg", // 10m "http://dg.101.hk/1.rar", // 342m "http://180.153.105.144/dd.myapp.com/16891/E2F3DEBB12A049ED921C6257C5E9FB11.apk", // "http://mirror.internode.on.net/pub/test/5meg.test4", // "http://mirror.internode.on.net/pub/test/5meg.test3", // "http://mirror.internode.on.net/pub/test/5meg.test2", // "http://mirror.internode.on.net/pub/test/5meg.test1", // 6.8m // "http://dlsw.baidu.com/sw-search-sp/soft/7b/33461/freeime.1406862029.exe", // 10m // "http://mirror.internode.on.net/pub/test/10meg.test", // "http://mirror.internode.on.net/pub/test/10meg.test1", // "http://mirror.internode.on.net/pub/test/10meg.test2", // "http://mirror.internode.on.net/pub/test/10meg.test3", // 10m "http://mirror.internode.on.net/pub/test/10meg.test4", // "http://mirror.internode.on.net/pub/test/10meg.test5", // 20m "http://www.pc6.com/down.asp?id=72873", // 22m "http://113.207.16.84/dd.myapp.com/16891/2E53C25B6BC55D3330AB85A1B7B57485.apk?mkey=5630b43973f537cf&f=cf87&fsname=com.htshuo.htsg_3.0.1_49.apk&asr=02f1&p=.apk", // 206m "http://down.tech.sina.com.cn/download/d_load.php?d_id=49535&down_id=1&ip=42.81.45.159" }; String[] URLS = { // 随机小资源一般不超过10 "http://girlatlas.b0.upaiyun.com/35/20150106/19152b4c633b321f4479.jpg!mid", "http://cdn-l.llsapp.com/connett/25183b40-22f2-0133-6e99-029df5130f9e", "http://cdn-l.llsapp.com/connett/c3115411-3669-466d-8ef2-e6c42c690303", "http://cdn-l.llsapp.com/connett/a55b4727-e228-410f-b44a-0385dbe9ab85", "http://cdn-l.llsapp.com/connett/7b6b5485-0d19-476c-816c-ff6523fae539", "http://cdn-l.llsapp.com/connett/33fa9155-c99a-407f-8d2c-82e9d17f4c32", "http://cdn-l.llsapp.com/connett/fe50a391-d111-44a9-9c2f-33aaaeec9186", "http://cdn.llsapp.com/crm_test_1449051526097.jpg", "http://cdn.llsapp.com/crm_test_1449554617476.jpeg", "http://cdn.llsapp.com/yy/image/3b0430db-5ff4-455c-9c8d-0213eea7b6c4.jpg", "http://cdn.llsapp.com/forum/image/ba80be187e0947f2b60c763a04910948_1446722022222.jpg", "http://cdn.llsapp.com/forum/image/NTNjMWQwMDAwMDAwMGQ0Zg==_1446122845.jpg", "http://cdn.llsapp.com/user_images/FEFC55C5-1E8F-45C6-AA4E-79FC79F97B6F", "http://cdn.llsapp.com/user_images/26ebf7deb8eb1f66056cbdac31aa18209d2f7daf_1436262740.jpg", "http://cdn.llsapp.com/yy/image/a1de0e33-c3f3-4795-b2b9-4dafbcf06bee.jpg", "http://cdn.llsapp.com/yy/image/cc4bc37d-ef77-4469-a8e9-2c70105a3f94.jpg", // 重复 "http://cdn.llsapp.com/yy/image/cc4bc37d-ef77-4469-a8e9-2c70105a3f94.jpg", "http://cdn.llsapp.com/yy/image/dd72c879-b1c4-4fb9-b871-d57dfa3aa709.jpg", "http://cdn.llsapp.com/crm_test_1447220020113.jpg", "http://cdn.llsapp.com/crm_test_1447220428493.jpg", // 重复 "http://cdn.llsapp.com/yy/image/a1de0e33-c3f3-4795-b2b9-4dafbcf06bee.jpg", "http://cdn.llsapp.com/forum/image/72e344b20d48432487389f8ad0dec163_1435047695818.png", "http://cdn.llsapp.com/forum/image/36d3070792b14633ad1f596c38f892e2_1435047020634.jpg", "http://cdn.llsapp.com/yy/image/5d8bfbd4-51b8-4fe6-ba01-4a5f37c478a6.jpg", "http://cdn.llsapp.com/forum/image/M2YwMWQwMDAwMDAwMTBmYw==_1440748066.jpg", "http://cdn.llsapp.com/forum/image/22f8389542734b05986c0b0dd8fd1735_1435230013392.jpg", "http://cdn.llsapp.com/forum/image/2e6b8f9676aa47228aad74dd37709b0e_1446202991820.jpg", "http://cdn.llsapp.com/forum/image/f82192fa9f764af396579e51afeb9aaf_1435049606128.jpg", "http://cdn.llsapp.com/forum/image/f74026981afa42e0b73a6983450deca1_1441780286505.jpg", "http://cdn.llsapp.com/357070051859561_1390016094611.jpg", // 重复 "http://cdn-l.llsapp.com/connett/7b6b5485-0d19-476c-816c-ff6523fae539", "http://cdn.llsapp.com/forum/image/6f7a673ea1224019bf73bb2301f61b26_1435211914955.jpg", "http://cdn.llsapp.com/forum/image/a58b054f250e4237bd7d914c1feafc05_1435211918877.jpg", // 重复 "http://cdn.llsapp.com/forum/image/f74026981afa42e0b73a6983450deca1_1441780286505.jpg", "http://cdn.llsapp.com/forum/image/432f360f3a1b4436b569c1a58c0dffe4_1435917578613.jpg", "http://cdn.llsapp.com/forum/image/a704f63a5b904961b71ea04b8a6aa36d_1397448248398.jpg", "http://cdn.llsapp.com/yy/image/52f4abdb-5f7f-46c2-9095-cce5fc09b296.png", "http://placekitten.com/580/320", "http://cdn.llsapp.com/forum/image/MWIwMWQwMDAwMDAwMTA2Yw==_1436253885.jpg", "http://cdn.llsapp.com/forum/image/2f003721ddb74ea1a84b2a6e603d6a44_1435046970863.jpg", "http://cdn.llsapp.com/crm_test_1447219868528.jpg", "http://cdn.llsapp.com/crm_test_1438658295447.jpg", // --------------- "http://imgsrc.baidu.com/forum/w%3D580/sign=2d51fcce8ad4b31cf03c94b3b7d7276f/48084b36acaf2eddc470ae648c1001e9380193bf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=724ad1b57af40ad115e4c7eb672d1151/eb638535e5dde711e8dea57ba6efce1b9d166134.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7155b164f3d3572c66e29cd4ba126352/d91090ef76c6a7efb59f4eabfcfaaf51f2de6687.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4e454f3fd058ccbf1bbcb53229d9bcd4/c6188618367adab4b776fdce8ad4b31c8601e49f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e8431073c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513d06288a533c6d55fbb3fbd9ba.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5a046dafc9fcc3ceb4c0c93ba244d6b7/5cd1f703918fa0ec15ee8580279759ee3d6ddb17.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=743d1d8071cf3bc7e800cde4e101babd/3c097bf40ad162d9e1e7839110dfa9ec8a13cd37.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=10ee22be728da9774e2f86238050f872/32d6912397dda144f74a8d3fb3b7d0a20df486f9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7a4eaf490bd162d985ee621421dea950/bb34e5dde71190ef0e807352cf1b9d16fcfa60c1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=03f04c9f730e0cf3a0f74ef33a47f23d/e355564e9258d109207c483fd058ccbf6d814da5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=10a0579bcefc1e17fdbf8c397a91f67c/c5f3b2119313b07e9989b0850dd7912397dd8c0c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=eed1753686d6277fe9123230183a1f63/9dcd7cd98d1001e950d1f582b90e7bec55e7974b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4bd2cff1500fd9f9a0175561152cd42b/bc8aa61ea8d3fd1fe11e42b7314e251f95ca5f2d.jpg", // repeat case. "http://imgsrc.baidu.com/forum/w%3D580/sign=eed1753686d6277fe9123230183a1f63/9dcd7cd98d1001e950d1f582b90e7bec55e7974b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e7b4e3c7dbb44aed594ebeec831d876a/32f531adcbef76092a9a79122fdda3cc7dd99eaf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4d6e489206082838680ddc1c8898a964/3fe83901213fb80e11fd825a37d12f2eb9389414.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=299cf8ce8ad4b31cf03c94b3b7d7276f/48084b36acaf2eddc0bdaa648c1001e9380193f4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=29d77fca3812b31bc76ccd21b6193674/a9dca144ad345982e8ed061f0df431adcaef84dd.jpg", // repeat case. "http://imgsrc.baidu.com/forum/w%3D580/sign=eed1753686d6277fe9123230183a1f63/9dcd7cd98d1001e950d1f582b90e7bec55e7974b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=387f95ab6c224f4a5799731b39f69044/626134a85edf8db141979fe90823dd54574e7487.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=af2b5b0cca1349547e1ee86c664c92dd/b483b9014a90f60382797fca3812b31bb151ed70.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=291264639d82d158bb8259b9b00b19d5/8e50f8198618367a22af9d502f738bd4b31ce51f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c7d737439825bc312b5d01906ede8de7/d5c5b74543a9822625dec9aa8b82b9014a90eb26.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=77695d7638dbb6fd255be52e3925aba6/ae539822720e0cf3f179a7760b46f21fbf09aab6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=614a17ae48540923aa696376a25ad1dc/87004a90f603738d458ce5afb21bb051f919ec7f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=93d2e73ae850352ab16125006342fb1a/a13cf8dcd100baa16430af364610b912c8fc2e24.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=06184598bd315c6043956be7bdb3cbe6/894443a98226cffcf8f1563fb8014a90f703ea62.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6db0c87337d3d539c13d0fcb0a86e927/413f6709c93d70cf9f9d4280f9dcd100bba12bdd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9560edf3241f95caa6f592bef9167fc5/2131e924b899a9014e62b3bb1c950a7b0308f5ed.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a1cf8608c8ea15ce41eee00186023a25/9987c9177f3e67096bc9ad723ac79f3df9dc5577.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=13560b451b4c510faec4e21250582528/0dfb828ba61ea8d3690da189960a304e251f5815.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg", // repeat case. "http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9bc331ae72f082022d9291377bfafb8a/1b2cd42a2834349b35b3bb08c8ea15ce37d3be83.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=862537777acb0a4685228b315b61f63e/ef08b3de9c82d15846698c3c810a19d8bd3e4251.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9c1bda3ae850352ab16125006341fb1a/a13cf8dcd100baa16bf992364610b912c9fc2e63.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=420543d8d1160924dc25a213e405359b/32f2d7ca7bcb0a4606e7afb76a63f6246a60af7c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c544284d3b87e9504217f3642039531b/05c69f3df8dcd100c9e5dfaf738b4710b8122fc7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=33776e678c1001e94e3c1407880f7b06/ee170924ab18972b44bc2744e7cd7b899f510abe.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cea760b00df3d7ca0cf63f7ec21ebe3c/684f9258d109b3deca07c3e6cdbf6c81810a4c80.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=77aac436d53f8794d3ff4826e2190ead/189659ee3d6d55fb571451a86c224f4a21a4dd6b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef6ca3c0cb8065387beaa41ba7dca115/fdcfc3fdfc039245aaf7c7818694a4c27c1e25fa.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef0286e6d009b3deebbfe460fcbe6cd3/0713b31bb051f8193f5422c4dbb44aed2f73e7c8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fec55e532f738bd4c421b239918a876c/f5ee76094b36acaf0aacb7727dd98d1000e99cf4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=48422e39e850352ab16125006342fb1a/a13cf8dcd100baa1bfa066354610b912c9fc2eb4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2d1268678c1001e94e3c1407880c7b06/ee170924ab18972b5ad92144e7cd7b899f510a59.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6f9ee8a14034970a47731027a5cbd1c0/a02e070828381f302e69ad27a8014c086f06f0c9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ad5032c19f2f07085f052a08d925b865/b31101e93901213f92886e5255e736d12e2e9581.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=72992644838ba61edfeec827713597cc/b900a18b87d6277f5d8312b629381f30e824fca8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=433f1f6f63d9f2d3201124e799ed8a53/dbdce71190ef76c69f24dba59c16fdfaae51674e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7300c036d53f8794d3ff4826e21a0ead/189659ee3d6d55fb53be55a86c224f4a21a4ddc1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4c9c60b74a36acaf59e096f44cd88d03/6fdb81cb39dbb6fdd515c6a80824ab18962b37f6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=95b291bfa08b87d65042ab1737092860/10dca3cc7cd98d1027472fbf203fb80e7aec90a9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef679b0fca1349547e1ee86c664f92dd/b483b9014a90f603c235bfc93812b31bb151edbc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5ecdc676a686c91708035231f93c70c6/97004c086e061d95c17c15b67af40ad162d9ca03.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b9ddaf869a504fc2a25fb00dd5dfe7f0/312542a7d933c89547b0bbf5d01373f083020076.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=266158f421a446237ecaa56aa8237246/60de8db1cb1349544260caea574e9258d0094ac6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=50b490faeaf81a4c2632ecc1e72b6029/8f3433fa828ba61ef454eaa14034970a314e5982.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=31cb148d8644ebf86d716437e9f8d736/823fb13533fa828bfcb5b06dfc1f4134960a5aae.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a8bbd18371cf3bc7e800cde4e101babd/3c097bf40ad162d93d614f9210dfa9ec8b13cdb6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4e26126f63d9f2d3201124e799ee8a53/dbdce71190ef76c6923dd6a59c16fdfaae516755.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2630ab09d1a20cf44690fed746084b0c/ec1a0ef41bd5ad6ea276486480cb39dbb7fd3cb5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2ed231861e30e924cfa49c397c0a6e66/1f3eb80e7bec54e71f0b3690b8389b504ec26a5d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a4dd5daeb812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f76ba1244503d269758eec4d2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=458d67a0d31b0ef46ce89856edc551a1/8cfa43166d224f4ac1eb5c9d08f790529922d1cb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=888b7ae7242dd42a5f0901a3333a5b2f/7f35970a304e251fca6bcb76a686c9177e3e53a4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5d2fcb76a686c91708035231f93f70c6/97004c086e061d95c29e18b67af40ad163d9ca61.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f4524e5937d12f2ece05ae687fc3d5ff/45889e510fb30f24cd19c38dc995d143ac4b03b9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f291960fca1349547e1ee86c664f92dd/b483b9014a90f603dfc3b2c93812b31bb151edca.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c526d13e64380cd7e61ea2e59145ad14/fdfcfc039245d688a1679c2aa5c27d1ed31b24d3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=145760379f510fb37819779fe932c893/55610c338744ebf8e8d64ab1d8f9d72a6159a79e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7255fcaf91ef76c6d0d2fb23ad17fdf6/ef1273f082025aaf33875045faedab64024f1a83.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9a90a5bf4b90f60304b09c4f0913b370/f48165380cd7912387cfbdfaac345982b2b78015.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8b32fb409825bc312b5d01906ede8de7/d5c5b74543a98226693b05a98b82b9014b90eb43.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=42dbcb747acb0a4685228b315b62f63e/ef08b3de9c82d1588297703f810a19d8bc3e4223.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=08986a78a6efce1bea2bc8c29f50f3e8/bc035aafa40f4bfb639ab7da024f78f0f63618f2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=13a61069902397ddd679980c6983b216/ac44d688d43f8794d25c61a0d31b0ef41ad53a99.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6f03d5a97e3e6709be0045f70bc69fb8/50071d950a7b0208b371166f63d9f2d3562cc881.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4a44166f63d9f2d3201124e799ed8a53/dbdce71190ef76c6965fd2a59c16fdfaae5167ab.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=26e0d6ad48540923aa696376a259d1dc/87004a90f603738d022624acb21bb051f919ecd5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d835b08a5882b2b7a79f39cc01accb0a/9ac37d1ed21b0ef462a4b0d0dcc451da80cb3ef4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=de8abf31a1ec08fa260013af69ef3d4d/8a8e8c5494eef01f13a0034be1fe9925bd317d8c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d079799210dfa9ecfd2e561f52d1f754/48c7a7efce1b9d16df5081eff2deb48f8d5464ad.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a97a4b860dd79123e0e0947c9d365917/c2029245d688d43fe46e8a7c7c1ed21b0ff43b7d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4c307cfdfaf2b211e42e8546fa816511/4c8a4710b912c8fc9fc6ec43fd039245d6882103.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=714fb895d50735fa91f04eb1ae500f9f/cc1ebe096b63f624b137238d8644ebf81b4ca3d3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9ce120865243fbf2c52ca62b807fca1e/f310728b4710b9129241f370c2fdfc03934522b8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a708cb53aa64034f0fcdc20e9fc17980/f7eb15ce36d3d5395af30a4d3b87e950342ab077.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7a6533855ab5c9ea62f303ebe53bb622/efc9a786c9177f3e29f7f98371cf3bc79e3d5679.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a3c9e88dc995d143da76e42b43f18296/6f0ed9f9d72a6059c443e5942934349b023bbaea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ee9dd7737a899e51788e3a1c72a6d990/c8256b600c338744309f2bf2500fd9f9d62aa0e3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3c24e0e6cdbf6c81f7372ce08c3fb1d7/b819367adab44aed8ed5ba6ab21c8701a08bfba2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=37a42ca98b82b9013dadc33b438ca97e/ad12b07eca806538f48fa39d96dda144ac3482dc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=18cbc2a13b292df597c3ac1d8c305ce2/47300a55b319ebc43b6071178326cffc1e171620.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=599e78acb21bb0518f24b3200678da77/9f45ad345982b2b7204b4d4a30adcbef77099b6d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=70bdbebd9345d688a302b2ac94c37dab/36fb513d269759ee8e2d1745b3fb43166c22dfc4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8288e6b14afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ceeef49787b7003af33a87b223.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5bf14e8d8644ebf86d716437e9f8d736/823fb13533fa828b968fea6dfc1f4134960a5a94.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0c3a8cad48540923aa696376a259d1dc/87004a90f603738d28fc7eacb21bb051f919ec8f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b9ca35b00df3d7ca0cf63f7ec21dbe3c/684f9258d109b3debd6a96e6cdbf6c81810a4c63.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=52196d93d52a283443a636036bb4c92e/a90b304e251f95cae388ef38c8177f3e6709523b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3ca90e9d08f79052ef1f47363cf2d738/f51249540923dd544a43dae6d009b3de9c824808.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=05fda1ee342ac65c6705667bcbf3b21d/c6ddd100baa1cd114df10faeb812c8fcc2ce2dfd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6b303b354610b912bfc1f6f6f3fcfcb5/b412632762d0f70337aee95209fa513d2697c525.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2ec657a98b82b9013dadc33b438ca97e/ad12b07eca806538ededd89d96dda144ad34823e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bd1cdc74d0c8a786be2a4a065708c9c7/8698a9014c086e06859643c503087bf40ad1cb07.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2c704569902397ddd679980c6983b216/ac44d688d43f8794ed8a34a0d31b0ef41ad53ac3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=42e1458d8644ebf86d716437e9f8d736/823fb13533fa828b8f9fe16dfc1f4134960a5a84.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fb5b3683279759ee4a5060c382fa434e/ce1e3a292df5e0fe6a84db8f5d6034a85fdf72a5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e86ba9a59c16fdfad86cc6e6848e8cea/9a0e4bfbfbedab644ccb1f4ef636afc378311e87.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9e9ffaa8fcfaaf5184e381b7bc5594ed/75fafbedab64034f28749088aec379310b551d87.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef3cd96f2e2eb938ec6d7afae56085fe/a0500fb30f2442a762e8272bd043ad4bd013025f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f612834e83025aafd3327ec3cbecab8d/ea2b2834349b033b7cb4395414ce36d3d539bd05.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1258b9ea0823dd542173a760e108b3df/7491f603738da977e05943a5b151f8198718e3c8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b2bf6795d50735fa91f04eb1ae500f9f/cc1ebe096b63f62472c7fc8d8644ebf81b4ca3a3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=33e53c016d81800a6ee58906813433d6/087bdab44aed2e73696943a28601a18b86d6faba.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c00943a28601a18bf0eb1247ae2d0761/92ae2edda3cc7cd9c6cdf1573801213fb90e9159.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=927fba532f738bd4c421b2399189876c/f5ee76094b36acaf661653727dd98d1000e99c4f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=66690260b58f8c54e3d3c5270a282dee/3d4e78f0f736afc3b009fbebb219ebc4b745123c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e90ec8acb21bb0518f24b320067bda77/9f45ad345982b2b790dbfd4a30adcbef77099bfd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=331550faac345982c58ae59a3cf5310b/b995a4c27d1ed21baa3cea6bac6eddc450da3f4c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=56e2d9861e30e924cfa49c397c0a6e66/1f3eb80e7bec54e7673bde90b8389b504ec26a6e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cc0a533586d6277fe912323018391f63/9dcd7cd98d1001e9720ad381b90e7bec54e7970f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2e0417737a899e51788e3a1c72a5d990/c8256b600c338744f006ebf2500fd9f9d62aa07a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=504292e7242dd42a5f0901a3333a5b2f/7f35970a304e251f12a22376a686c9177e3e53ec.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a42ca911a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9ea33e0c56dd439b6003af3b32a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=39fe414542166d223877159c76220945/82305c6034a85edfe1b438ad48540923dd547501.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=807d27b934fae6cd0cb4ab693fb20f9e/80086b63f6246b601b6574faeaf81a4c500fa2d2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bfb3f0855ab5c9ea62f303ebe538b622/efc9a786c9177f3eec213a8371cf3bc79f3d562c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9835385177094b36db921be593cd7c00/e3c551da81cb39db1f65a1d8d1160924aa18309c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=07e91f461b4c510faec4e21250582528/0dfb828ba61ea8d37db2b58a960a304e241f58a9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=358f41318d5494ee87220f111df4e0e1/46f1f736afc37931cc0446a7eac4b74542a911d5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7f2e51b14afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ce13522087b7003af33b87b285.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f87b4980b03533faf5b6932698d2fdca/b5d5b31c8701a18b6675d2c19f2f07082938fea0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=95c5ccacb21bb0518f24b320067bda77/9f45ad345982b2b7ec10f94a30adcbef77099bb6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c103ff9aaa18972ba33a00c2d6cc7b9d/45ca0a46f21fbe097a76009a6a600c338744ad11.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=293cff4991529822053339cbe7cb7b3b/77550923dd54564efd4727b7b2de9c82d1584f1b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8a8c48b76a63f6241c5d390bb745eb32/f2be6c81800a19d8c4ad478b32fa828ba71e4697.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c5790d4cb3119313c743ffb855390c10/7911b912c8fcc3ce55c70abd9345d688d43f203e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9ca91d7ab64543a9f51bfac42e168a7b/f85d10385343fbf29da165adb17eca8064388fb4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=af58c225023b5bb5bed720f606d2d523/abcbd1c8a786c917f85291b7c83d70cf3ac757e8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=43ffae503c6d55fbc5c6762e5d234f40/13f4e0fe9925bc313908c3165fdf8db1ca1370ec.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=55a84b8f5d6034a829e2b889fb1249d9/7da88226cffc1e17460f4ebf4b90f603728de98a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=15d4b519d833c895a67e9873e1117397/244d510fd9f9d72a7aa9d293d52a2834359bbb74.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=464226e6cdbf6c81f7372ce08c3fb1d7/b819367adab44aedf4b37c6ab21c8701a08bfb45.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6da166ef0eb30f24359aec0bf894d192/32328744ebf81a4c47272147d62a6059252da62c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4fa1f2c503087bf47dec57e1c2d2575e/71c3d5628535e5ddb525685177c6a7efce1b6230.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=395088713ac79f3d8fe1e4388aa0cdbc/45f50ad162d9f2d3a741e961a8ec8a136227ccea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=131e281c0df431adbcd243317b37ac0f/30f51bd5ad6eddc4f073797538dbb6fd536633ad.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0793b7f421a446237ecaa56aa8237246/60de8db1cb134954639225ea574e9258d0094ab5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=079bb7f421a446237ecaa56aa8237246/60de8db1cb134954639a25ea574e9258d0094abd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=06d44b609d82d158bb8259b9b00819d5/8e50f8198618367a0d69b2532f738bd4b21ce55a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=449150c93812b31bc76ccd21b6193674/a9dca144ad34598285ab291c0df431adcbef8418.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3d611c63e61190ef01fb92d7fe1a9df7/934ad11373f08202e2fb5db14afbfbedaa641bd0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e51c7d68267f9e2f70351d002f31e962/42d88d1001e9390165a842b07aec54e737d19693.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1b7c903f810a19d8cb03840d03fb82c9/e4b54aed2e738bd464df7bbfa08b87d6267ff940.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=80c4a70bc8ea15ce41eee00186023a25/9987c9177f3e67094ac28c713ac79f3df9dc557b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3ee83e70c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513dd083a4503c6d55fbb2fbd911.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=79dd546dfc1f4134e0370576151e95c1/197e9e2f07082838c0f3159ab999a9014d08f140.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8668bbaeb812c8fcb4f3f6c5cc0192b4/5d2662d0f703918f540ff444503d269758eec460.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a1406895d50735fa91f04eb1ae500f9f/cc1ebe096b63f6246138f38d8644ebf81b4ca3dc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=65ea09bd728da9774e2f86238050f872/32d6912397dda144824ea63cb3b7d0a20df486fe.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a1e8d7956159252da3171d0c049a032c/c31e4134970a304e5d0e9575d0c8a786c9175c15.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=022c49eb0823dd542173a760e108b3df/7491f603738da977f02db3a4b151f8198718e3c4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4e717b4b0bd162d985ee621421dea950/bb34e5dde71190ef3abfa750cf1b9d16fcfa60fd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=296f01563801213fcf334ed464e636f8/9519972bd40735fa42b27b369f510fb30e2408fb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=208cca3f64380cd7e61ea2e59146ad14/fdfcfc039245d68844cd872ba5c27d1ed31b2476.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ac81b826a8014c08193b28ad3a7a025b/6ae636d12f2eb938def54f7dd4628535e4dd6fa1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4371a9538b13632715edc23ba18ea056/f01a9d16fdfaaf51a170b4308d5494eef11f7aaa.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fbb28bac622762d0803ea4b790ed0849/a317fdfaaf51f3dee6d18deb95eef01f3b2979da.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=29a9f1a03b292df597c3ac1d8c305ce2/47300a55b319ebc40a0242168326cffc1f1716c3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef1e24a094cad1c8d0bbfc2f4f3f67c4/d725b899a9014c08b1561c2a0b7b02087af4f4d5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=64a783bd5bafa40f3cc6ced59b65038c/1635349b033b5bb5debd147137d3d539b700bcd3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=840cbab17aec54e741ec1a1689399bfd/0bfbe6cd7b899e51aa800d9b43a7d933c8950d37.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c7b6a25309fa513d51aa6cd60d6c554c/b25594eef01f3a297bcce2419825bc315c607c3d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b7b2d786b7003af34dbadc68052bc619/f73c70cf3bc79f3d79bdd3bfbba1cd11738b29e5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c52aa550cf1b9d168ac79a69c3dcb4eb/64aea40f4bfbfbed188801f079f0f736aec31f68.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=95073312caef76093c0b99971edfa301/936fddc451da81cba028b4425366d01608243177.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f5ef186aac6eddc426e7b4f309dab6a2/714b20a4462309f76b499b9d730e0cf3d7cad618.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=aefb827438dbb6fd255be52e3925aba6/ae539822720e0cf328eb78740b46f21fbe09aa26.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c378aef4d01373f0f53f6f97940e4b8b/5e58252dd42a283426a000845ab5c9ea15cebf3f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7a589ea9fcfaaf5184e381b7bc5594ed/75fafbedab64034fccb3f489aec379310b551dc7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1142d2bfbba1cd1105b672288913c8b0/692d11dfa9ec8a138ab9616ff603918fa1ecc09b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7d615fdf35a85edffa8cfe2b795509d8/bc27cffc1e178a827851492ff703738da877e8d5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2583da46d62a60595210e1121835342d/96d2fd1f4134970a44c226a094cad1c8a6865d88.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=be5aeaef342ac65c6705667bcbf0b21d/c6ddd100baa1cd11f65644afb812c8fcc2ce2d59.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9bc33512caef76093c0b99971edca301/936fddc451da81cbaeecb2425366d01609243133.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=222ed0808694a4c20a23e7233ef51bac/67ef3d6d55fbb2fbb7b0699d4e4a20a44723dca3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e31989af00e9390156028d364bed54f9/3725ab18972bd407aa3ae2727a899e510eb30944.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d0d4a8102fdda3cc0be4b82831eb3905/07dab6fd5266d01692c6afa7962bd40734fa3566.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=79dc818ae4dde711e7d243fe97eecef4/ef42ad4bd11373f02ebc5e10a50f4bfbfaed04ba.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=45257e4b0bd162d985ee621421dea950/bb34e5dde71190ef31eba250cf1b9d16fdfa6029.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=431115875243fbf2c52ca62b807fca1e/f310728b4710b9124db1c671c2fdfc03934522c9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9e4d62b6c83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad183aaf76c562c11dfa8eccef0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4d261167bd3eb13544c7b7b3961fa8cb/10728bd4b31c87016ca78f69267f9e2f0708ff29.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0a2041eb0823dd542173a760e108b3df/7491f603738da977f821bba4b151f8198618e330.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cdc3469310dfa9ecfd2e561f52d1f754/48c7a7efce1b9d16c2eabeeef2deb48f8c546414.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8c9786b5314e251fe2f7e4f09787c9c2/16391f30e924b89964a25db76f061d950b7bf6a0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=194483fad788d43ff0a991fa4d1fd2aa/6f3c269759ee3d6db0bca34442166d224e4adecc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a77bfa7bb64543a9f51bfac42e168a7b/f85d10385343fbf2a67382acb17eca8064388fe6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f0f4189cdc54564ee565e43183df9cde/c802738da97739120abba1eef9198618377ae2a5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fd1452a96c224f4a5799731b39f59044/626134a85edf8db184fc58eb0823dd54574e746b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=85354810a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9ea12f9246cd439b6003af3b333.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=71e47c10a50f4bfb8cd09e5c334d788f/0a9a033b5bb5c9eae628106cd439b6003bf3b363.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=84ab2ceab219ebc4c0787691b227cf79/d751352ac65c1038aed9dd4db3119313b17e899f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=75d075fcfaf2b211e42e8546fa826511/4c8a4710b912c8fca626e542fd039245d788216c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2f963f67bd3eb13544c7b7b3961fa8cb/10728bd4b31c87010e17a169267f9e2f0608ff99.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0da3978a32fa828bd1239debcd1e41cd/8d1d8701a18b87d696e2b890060828381e30fd9a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6c568139c8177f3e1034fc0540ce3bb9/72096e061d950a7bbf965d4b0bd162d9f3d3c99b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1e295079a6efce1bea2bc8c29f50f3e8/bc035aafa40f4bfb752b8ddb024f78f0f6361842.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1932ba3dd058ccbf1bbcb53229dabcd4/c6188618367adab4e00108cc8ad4b31c8601e469.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=194aefa87e3e6709be0045f70bc59fb8/50071d950a7b0208c5382c6e63d9f2d3562cc849.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4cdad5a0bf096b6381195e583c328733/ef59ccbf6c81800a5f449b81b03533fa838b4798.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d0b7bf9d730e0cf3a0f74ef33a44f23d/e355564e9258d109f33bbb3dd058ccbf6d814d61.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1d63677dd462853592e0d229a0ee76f2/e732c895d143ad4b57205b4f83025aafa40f0637.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=67e1443e810a19d8cb03840d03fb82c9/e4b54aed2e738bd41842afbea08b87d6267ff9db.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=204218f1241f95caa6f592bef9167fc5/2131e924b899a901fb4046b91c950a7b0308f5cd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0f01eb42fd039245a1b5e107b796a4a8/9eed08fa513d2697952115d254fbb2fb4216d854.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=21da248c8644ebf86d716437e9f8d736/823fb13533fa828beca4806cfc1f4134960a5abe.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cefc37a88b82b9013dadc33b438ca97e/ad12b07eca8065380dd7b89c96dda144ad348204.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=316e0c92d52a283443a636036bb4c92e/a90b304e251f95ca80ff8e39c8177f3e67095233.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=835423eab219ebc4c0787691b227cf79/d751352ac65c1038a926d24db3119313b17e89e2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d5ac1f6e2e2eb938ec6d7afae56385fe/a0500fb30f2442a75878e12ad043ad4bd01302cf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=273ea5eb95eef01f4d1418cdd0ff99e0/c937afc379310a5520a8c27bb64543a9832610b5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1cfea33db8014a90813e46b599753971/8e7fca8065380cd793cabe62a044ad345882816d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a4bc83be4d086e066aa83f4332097b5a/08d02f2eb9389b5053e7ffdd8435e5dde7116e21.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b00b9181b03533faf5b6932698d1fdca/b5d5b31c8701a18b2e050ac09f2f07082938fe50.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=61c8bd5077c6a7efb926a82ecdf8afe9/4df182025aafa40fcd22d652aa64034f79f0195d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d252e43f64380cd7e61ea2e59145ad14/fdfcfc039245d688b613a92ba5c27d1ed21b2428.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=696f7410a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9eafea3186cd439b6003bf3b3ea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5d9e8d737dd98d1076d40c39113eb807/6c67d0160924ab18e468fab834fae6cd7a890bc7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7e7d9a308d5494ee87220f111df4e0e1/46f1f736afc3793187f69da6eac4b74542a911a7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=541e1764b7fd5266a72b3c1c9b1a9799/a623720e0cf3d7caae1e24f9f31fbe096a63a952.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0cdeec71c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513de2b576513c6d55fbb2fbd927.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6903fb952934349b74066e8df9eb1521/0e4f251f95cad1c8eba8e6a87e3e6709c93d512a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=29f189a1a9d3fd1f3609a232004f25ce/b9d7277f9e2f07088342308fe824b899a801f2ff.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4582d8bc908fa0ec7fc764051696594a/cddfb48f8c5494eed74d15962cf5e0fe98257ed6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6843433e810a19d8cb03840d03f882c9/e4b54aed2e738bd417e0a8bea08b87d6267ff979.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=29db73df35a85edffa8cfe2b795609d8/bc27cffc1e178a822ceb652ff703738da877e86f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=baae3c4b30adcbef01347e0e9cae2e0e/25d4ad6eddc451daebc70964b7fd5266d0163208.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=724142344610b912bfc1f6f6f3fcfcb5/b412632762d0f7032edf905309fa513d2797c5d5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=470c066cd439b6004dce0fbfd9513526/5908c93d70cf3bc7cdffc863d000baa1cc112a46.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fcc28826a8014c08193b28ad3a79025b/6ae636d12f2eb9388eb67f7dd4628535e4dd6f62.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0ad08c8e5d6034a829e2b889fb1149d9/7da88226cffc1e17197789be4b90f603728de972.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2a743d8c8644ebf86d716437e9f8d736/823fb13533fa828be70a996cfc1f4134970a5a10.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=80a8b07438dbb6fd255be52e3925aba6/ae539822720e0cf306b84a740b46f21fbf09aaf7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cae0a3e7d009b3deebbfe460fcbe6cd3/0713b31bb051f8191ab607c5dbb44aed2f73e7ab.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=28e2af9006082838680ddc1c8898a964/3fe83901213fb80e7471655837d12f2eb8389499.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=193e3c4b1ad5ad6eaaf964e2b1ca39a3/53234f4a20a44623c2d2a2ed9922720e0cf3d722.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8ca6933486d6277fe912323018391f63/9dcd7cd98d1001e932a61380b90e7bec55e797a3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2d0d6fdf35a85edffa8cfe2b795509d8/bc27cffc1e178a82283d792ff703738da977e839.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1b3c484b0bd162d985ee621421dea950/bb34e5dde71190ef6ff29450cf1b9d16fdfa6030.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0ca60645e7cd7b89e96c3a8b3f254291/5562f6246b600c335fe5d8471b4c510fd8f9a1a6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e4a3094c3b87e9504217f3642039531b/05c69f3df8dcd100e802feae738b4710b8122fa7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=09c6e2b6b2de9c82a665f9875c8080d2/8d1ab051f8198618ade4e90b4bed2e738ad4e69b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=de973a9cdc54564ee565e43183df9cde/c802738da977391224d883eef9198618377ae240.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c99a6a3db3b7d0a27bc90495fbee760d/431fd21b0ef41bd5c9c0ee7b50da81cb38db3daa.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0a5839c403087bf47dec57e1c2d1575e/71c3d5628535e5ddf0dca35077c6a7efcf1b6249.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0a2539c403087bf47dec57e1c2d2575e/71c3d5628535e5ddf0a1a35077c6a7efcf1b62b4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b7c51e4ae1fe9925cb0c695804aa5ee4/8d18ebc4b74543a90fcafc431f178a82b8011468.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44bc3f4cd6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82e7eaff006d81800a18d843b6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1282e0808694a4c20a23e7233ef51bac/67ef3d6d55fbb2fb871c599d4e4a20a44623dc0f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4ef8e2ac72f082022d9291377bfafb8a/1b2cd42a2834349be088680ac8ea15ce37d3beb0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=40f9e7b834fae6cd0cb4ab693fb10f9e/80086b63f6246b60dbe1b4fbeaf81a4c500fa257.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44a565f19358d109c4e3a9bae15accd0/97763912b31bb05161e8b5a7377adab44bede076.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=241cfda49c16fdfad86cc6e6848d8cea/9a0e4bfbfbedab6480bc4b4ff636afc378311e77.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=10b25666f3d3572c66e29cd4ba126352/d91090ef76c6a7efd478a9a9fcfaaf51f2de66e7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=93ac33b729381f309e198da199004c67/0700213fb80e7bec5964026e2e2eb9389a506b87.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=032a3d9baa18972ba33a00c2d6cc7b9d/45ca0a46f21fbe09b85fc29b6a600c338744ad39.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=970251870dd79123e0e0947c9d355917/c2029245d688d43fda16907d7c1ed21b0ff43b86.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1d3b36b77af40ad115e4c7eb672d1151/eb638535e5dde71187af4279a6efce1b9c1661c4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4184e0b834fae6cd0cb4ab693fb20f9e/80086b63f6246b60da9cb3fbeaf81a4c500fa2b4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2e0d6582279759ee4a5060c382f9434e/ce1e3a292df5e0febfd2888e5d6034a85fdf7273.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a5bb6b608cb1cb133e693c1bed5556da/20168a82b9014a9067104632a8773912b31bee10.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=973351870dd79123e0e0947c9d355917/c2029245d688d43fda27907d7c1ed21b0ff43bb7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1e04c845ae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f074249a7c78310a55b3191c16.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0897e5b6b2de9c82a665f9875c8380d2/8d1ab051f8198618acb5ee0b4bed2e738ad4e654.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=832de5a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f6f425edcf7246b600d33aec8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ccfdb80eca1349547e1ee86c664f92dd/b483b9014a90f603e1af9cc83812b31bb051ed27.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=99b676a96c224f4a5799731b39f69044/626134a85edf8db1e05e7ceb0823dd54574e74c9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4f071acc8ad4b31cf03c94b3b7d4276f/48084b36acaf2edda62648668c1001e93801936e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1e71848a32fa828bd1239debcd1e41cd/8d1d8701a18b87d68530ab90060828381e30fdd4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0ff5c8a04034970a47731027a5cbd1c0/a02e070828381f304e028d26a8014c086e06f023.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=45c476168326cffc692abfba89004a7d/6d42fbf2b211931342ffff3f64380cd790238d86.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=789dc76c562c11dfded1bf2b53266255/aeee76c6a7efce1b8752c845ae51f3deb58f65c0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=83e7e5a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f6f885edcf7246b600d33ae86.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8305e5a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f6f6a5edcf7246b600d33aee0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=76863c4891529822053339cbe7cb7b3b/77550923dd54564ea2fde4b6b2de9c82d0584fa1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bba379b76f061d957d4637304bf50a5d/112fb9389b504fc2c7c0b08ae4dde71191ef6da6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=632a3f6e63d9f2d3201124e799ee8a53/dbdce71190ef76c6bf31fba49c16fdfaae51675a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b36fc3bc728da9774e2f86238053f872/32d6912397dda14454cb6c3db3b7d0a20df48604.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7ac345703ac79f3d8fe1e4388aa3cdbc/45f50ad162d9f2d3e4d22460a8ec8a136227cc7b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=92eb32b729381f309e198da199004c67/0700213fb80e7bec5823036e2e2eb9389a506b40.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8ef485a38601a18bf0eb1247ae2e0761/92ae2edda3cc7cd9883037563801213fb80e9124.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f9e28d26a8014c08193b28ad3a7a025b/6ae636d12f2eb9388b967a7dd4628535e4dd6f42.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=713a8d308d5494ee87220f111df7e0e1/46f1f736afc3793188b18aa6eac4b74542a91160.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2f95b26bb21c8701d6b6b2ee177d9e6e/7537acaf2edda3cc7d3fb4af00e93901203f9262.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=91fa5a9863d0f703e6b295d438f85148/c3fbaf51f3deb48f97bdad51f11f3a292cf5786d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8db7ac75d0c8a786be2a4a065708c9c7/8698a9014c086e06b53d33c403087bf40bd1cbad.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2474a53dd058ccbf1bbcb53229d9bcd4/c6188618367adab4dd4717cc8ad4b31c8601e4af.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f3d28c425366d0167e199e20a72ad498/4c0f0cf3d7ca7bcbc04fc8a0bf096b63f624a80e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=21341b5837d12f2ece05ae687fc0d5ff/45889e510fb30f24187f968cc995d143ac4b035c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cef24af3500fd9f9a0175561152cd42b/bc8aa61ea8d3fd1f643ec7b5314e251f95ca5f0e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c575a361b58f8c54e3d3c5270a282dee/3d4e78f0f736afc313155aeab219ebc4b7451220.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1d9781e7cdbf6c81f7372ce08c3fb1d7/b819367adab44aedaf66db6bb21c8701a18bfb12.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=64c6a7bc908fa0ec7fc764051696594a/cddfb48f8c5494eef6096a962cf5e0fe99257e12.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cec44af3500fd9f9a0175561152cd42b/bc8aa61ea8d3fd1f6408c7b5314e251f95ca5f38.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b509d58ae4dde711e7d243fe97eecef4/ef42ad4bd11373f0e2690a10a50f4bfbfaed04ef.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fc947e62a2cc7cd9fa2d34d109002104/88fc5266d0160924fb23c794d50735fae6cd343f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e9d56b789e3df8dca63d8f99fd1072bf/34d062d9f2d3572c88c5f9538b13632762d0c31f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4f2fd769267f9e2f70351d002f31e962/42d88d1001e93901cf9be8b17aec54e737d196a1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0d17b9ef342ac65c6705667bcbf3b21d/c6ddd100baa1cd11451b17afb812c8fcc2ce2d94.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1cafe826a8014c08193b28ad3a7a025b/6ae636d12f2eb9386edb1f7dd4628535e4dd6f88.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=396a6345838ba61edfeec827713597cc/b900a18b87d6277f167057b729381f30e824fce4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e9666b789e3df8dca63d8f99fd1072bf/34d062d9f2d3572c8876f9538b13632763d0c3ae.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=535d594891529822053339cbe7cb7b3b/77550923dd54564e872681b6b2de9c82d0584ffa.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f839457137d3d539c13d0fcb0a85e927/413f6709c93d70cf0a14cf82f9dcd100bba12b57.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8cc7c3acb17eca80120539efa1219712/f6fdc3cec3fdfc03ac938637d53f8794a5c22652.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d20fc662a044ad342ebf878fe0a30c08/ea3e8794a4c27d1e91375f4b1ad5ad6eddc43828.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef8ad1b518d8bc3ec60806c2b28aa6c8/74ec2e738bd4b31c040af03486d6277f9e2ff808.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2fc6d7fbeaf81a4c2632ecc1e7286029/8f3433fa828ba61e8b26ada04034970a314e5971.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2424ada04034970a47731027a5c8d1c0/a02e070828381f3065d3e826a8014c086f06f07c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=191361f1241f95caa6f592bef9167fc5/2131e924b899a901c2113fb91c950a7b0208f51e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=99ade5308d5494ee87220f111df4e0e1/46f1f736afc379316026e2a6eac4b74542a911f7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=36ae6245838ba61edfeec827713597cc/b900a18b87d6277f19b456b729381f30e824fc98.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=198961f1241f95caa6f592bef9167fc5/2131e924b899a901c28b3fb91c950a7b0308f580.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c7b8da6bb21c8701d6b6b2ee177d9e6e/7537acaf2edda3cc9512dcaf00e93901203f9248.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=92adf6b04afbfbeddc59367748f1f78e/3d3a5bb5c9ea15cefed18786b7003af33a87b207.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2d41c0e7d009b3deebbfe460fcbe6cd3/0713b31bb051f819fd1764c5dbb44aed2e73e714.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=45650810a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9ead2a9646cd439b6003bf3b3ec.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7d471df521a446237ecaa56aa8237246/60de8db1cb13495419468feb574e9258d0094ae1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=628e11168326cffc692abfba89034a7d/6d42fbf2b211931365b5983f64380cd790238d48.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=54ec7f4ae1fe9925cb0c695804a95ee4/8d18ebc4b74543a9ece39d431f178a82b8011441.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=02244a67bd3eb13544c7b7b3961fa8cb/10728bd4b31c870123a5d469267f9e2f0708ff2b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=edc5d3b518d8bc3ec60806c2b28aa6c8/74ec2e738bd4b31c0645f23486d6277f9f2ff8c1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=19eb9b2ad043ad4ba62e46c8b2005a89/e7f8d72a6059252d14f27b8b359b033b5ab5b95d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=13a6fef4d01373f0f53f6f97940d4b8b/5e58252dd42a2834f67e50845ab5c9ea14cebf62.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=003b92e3113853438ccf8729a312b01f/84a0cd11728b47108c039c43c2cec3fdfc032315.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2c26deaf00e9390156028d364bee54f9/3725ab18972bd4076505b5727a899e510eb3097b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=da26e7a6eac4b7453494b71efffd1e78/0b2bc65c103853432b81e6ae9213b07ecb8088f0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=734253b729381f309e198da199004c67/0700213fb80e7becb98a626e2e2eb9389a506bea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e5cbc051f11f3a295ac8d5c6a924bce3/91c279310a55b319825be3fa42a98226cefc179b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=289f0f0ac8ea15ce41eee00186013a25/9987c9177f3e6709e29924703ac79f3df9dc55a0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b6c55eeab219ebc4c0787691b224cf79/d751352ac65c10389cb7af4db3119313b17e8971.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=613a7b91b8389b5038ffe05ab537e5f1/31b20f2442a7d9339f7e85fcac4bd11372f0016f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=019f897b50da81cb4ee683c56264d0a4/782209f790529822deff584cd6ca7bcb0b46d476.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6c895ff9f31fbe091c5ec31c5b610c30/a283d158ccbf6c8197484c67bd3eb13532fa40c6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c67f4a8fe824b899de3c79305e071d59/860f7bec54e736d1c169ec879a504fc2d46269cc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=07f5f3a1a9d3fd1f3609a232004f25ce/b9d7277f9e2f0708ad464a8fe824b899a801f2fb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b3a71718d833c895a67e9873e1127397/244d510fd9f9d72adcda7092d52a2834359bbb80.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e3a385a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f0fcc3edcf7246b600d33ae42.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=442c08608cb1cb133e693c1bed5556da/20168a82b9014a9086872532a8773912b21bee81.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b49d5ceab219ebc4c0787691b227cf79/d751352ac65c10389eefad4db3119313b17e89a9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7caebd7bb64543a9f51bfac42e168a7b/f85d10385343fbf27da6c5acb17eca8064388fbc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2a9c0d0ac8ea15ce41eee00186013a25/9987c9177f3e6709e09a26703ac79f3df9dc55a3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ffbc54b77af40ad115e4c7eb672d1151/eb638535e5dde71165282079a6efce1b9c16614c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=25e305fcfaf2b211e42e8546fa826511/4c8a4710b912c8fcf6159542fd039245d788215f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=dd2feeadc9fcc3ceb4c0c93ba244d6b7/5cd1f703918fa0ec92c50682279759ee3c6ddbc4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3dc1fcdb024f78f0800b9afb49300a83/2bcf36d3d539b600fcdf6d38e850352ac65cb729.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cdc3dc6bb21c8701d6b6b2ee177e9e6e/7537acaf2edda3cc9f69daaf00e93901203f92b5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=227eff6cfc1f4134e0370576151e95c1/197e9e2f070828389b50be9bb999a9014d08f1e5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c796e4a6eac4b7453494b71efffd1e78/0b2bc65c103853433631e5ae9213b07ecb808840.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=73846eadb21bb0518f24b3200678da77/9f45ad345982b2b70a515b4b30adcbef77099b70.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6e18e0b17aec54e741ec1a1689399bfd/0bfbe6cd7b899e514094579b43a7d933c8950d23.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=aad2f9fbac345982c58ae59a3cf5310b/b995a4c27d1ed21b33fb436aac6eddc451da3f0b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ba6411eb0823dd542173a760e108b3df/7491f603738da9774865eba4b151f8198718e3fc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a9d70ef19358d109c4e3a9bae159ccd0/97763912b31bb0518c9adea7377adab44bede080.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a340ce75d0c8a786be2a4a065708c9c7/8698a9014c086e069bca51c403087bf40bd1cbe4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0801867b50da81cb4ee683c56267d0a4/782209f790529822d761574cd6ca7bcb0b46d4f4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=505e9871c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513dbe3502513c6d55fbb3fbd9a7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1637b2ef342ac65c6705667bcbf3b21d/c6ddd100baa1cd115e3b1cafb812c8fcc2ce2db4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5b3f66d254fbb2fb342b581a7f482043/deff9925bc315c60367905608cb1cb1348547755.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2772559cdc54564ee565e43183df9cde/c802738da9773912dd3deceef9198618367ae223.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7f1ce5879a504fc2a25fb00dd5dce7f0/312542a7d933c8958171f1f4d01373f082020036.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7b5c6945e7cd7b89e96c3a8b3f254291/5562f6246b600c33281fb7471b4c510fd8f9a1d8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e17b97ac48540923aa696376a259d1dc/87004a90f603738dc5bd65adb21bb051f919ec4f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4806bc419825bc312b5d01906edd8de7/d5c5b74543a98226aa0f42a88b82b9014b90eb70.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=999386727a899e51788e3a1c72a6d990/c8256b600c33874447917af3500fd9f9d62aa0ee.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a4aaf575d0c8a786be2a4a065708c9c7/8698a9014c086e069c206ac403087bf40bd1cbb2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9f37f2acb17eca80120539efa1229712/f6fdc3cec3fdfc03bf63b737d53f8794a4c22622.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=97bf96bc908fa0ec7fc764051696594a/cddfb48f8c5494ee05705b962cf5e0fe98257ef4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=366a5012caef76093c0b99971edca301/936fddc451da81cb0345d7425366d0160824319a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=79bc4492d52a283443a636036bb7c92e/a90b304e251f95cac82dc639c8177f3e66095261.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3c66365837d12f2ece05ae687fc3d5ff/45889e510fb30f24052dbb8cc995d143ac4b038e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6f3f1b8e0b55b3199cf9827d73ab8286/0486e950352ac65cd0c431fcfaf2b21192138a79.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3a29f4b5314e251fe2f7e4f09787c9c2/16391f30e924b899d21c2fb76f061d950a7bf61a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c924cf8e5d6034a829e2b889fb1249d9/7da88226cffc1e17da83cabe4b90f603738de906.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=795ebb2ad043ad4ba62e46c8b2035a89/e7f8d72a6059252d74475b8b359b033b5ab5b9ea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8f2ec3c1cb8065387beaa41ba7dca115/fdcfc3fdfc039245cab5a7808694a4c27d1e2539.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a17e796e63d9f2d3201124e799ed8a53/dbdce71190ef76c67d65bda49c16fdfaae51678f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b8d16e2a0b7b02080cc93fe952dbf25f/a5514fc2d5628535330a94ae91ef76c6a6ef635c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d8d08e45ae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f0b2f0dc7c78310a55b2191c42.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0ef74d64b7fd5266a72b3c1c9b199799/a623720e0cf3d7caf4f77ef9f31fbe096b63a939.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8a6dc7619d82d158bb8259b9b00b19d5/8e50f8198618367a81d03e522f738bd4b21ce5e1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6242426e2e2eb938ec6d7afae56385fe/a0500fb30f2442a7ef96bc2ad043ad4bd01302a1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=807c83bc908fa0ec7fc764051696594a/cddfb48f8c5494ee12b34e962cf5e0fe98257ea9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=202c0f740b46f21fc9345e5bc6266b31/8ddf9c82d158ccbf9b67f4b518d8bc3eb0354163.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8f8db8006d81800a6ee58906813733d6/087bdab44aed2e73d501c7a38601a18b86d6fa52.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3646ba431f178a82ce3c7fa8c602737f/8c109313b07eca80d1587968902397dda04483e5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=de7c8845ae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f0b45cda7c78310a55b2191cee.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44e28e4db3119313c743ffb855390c10/7911b912c8fcc3ced45c89bc9345d688d53f20a5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=478c43c5dbb44aed594ebeec831d876a/32f531adcbef76098aa2d9102fdda3cc7dd99e91.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=15ed4cadb21bb0518f24b320067bda77/9f45ad345982b2b76c38794b30adcbef77099b9f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5728a4fcac4bd11304cdb73a6aaea488/e92b6059252dd42ab7894124023b5bb5c8eab8ba.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3a6b438cc995d143da76e42b43f28296/6f0ed9f9d72a60595de14e952934349b023bba49.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e9b556ae738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d0a4170a30a1ec08fa513dc611.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f37704eef2deb48ffb69a1d6c01e3aef/9565034f78f0f736a14ed28e0b55b319eac41389.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d16cc5b91c950a7b75354ecc3ad0625c/87399b504fc2d56218514e62e61190ef77c66ce1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=549d00a7962bd40742c7d3f54b889e9c/3447f21fbe096b63ab9dc0df0d338744eaf8acbe.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1844bc875243fbf2c52ca62b807fca1e/f310728b4710b91216e46f71c2fdfc039245221d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f75d60006d81800a6ee58906813433d6/087bdab44aed2e73add11fa38601a18b87d6fa02.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3ef6a04b30adcbef01347e0e9cae2e0e/25d4ad6eddc451da6f9f9564b7fd5266d11632d1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5883fc522f738bd4c421b239918a876c/f5ee76094b36acafacea15737dd98d1001e99c3c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7e7320730e2442a7ae0efdade142ad95/b945ebf81a4c510f39dbf8ea6159252dd42aa520.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4f4f6c698326cffc692abfba89004a7d/6d42fbf2b21193134874e54064380cd791238d08.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=01637b2737d12f2ece05ae687fc3d5ff/45889e510fb30f243828f6f3c995d143ac4b0394.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c66aa271ca1349547e1ee86c664f92dd/b483b9014a90f603eb3886b73812b31bb151edb3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=99e807f4359b033b2c88fcd225cf3620/1b1e95cad1c8a78684d550fe6609c93d71cf5047.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0521d2df4034970a47731027a5c8d1c0/a02e070828381f3044d69759a8014c086f06f070.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0e58043ae7cd7b89e96c3a8b3f254291/5562f6246b600c335d1bda381b4c510fd8f9a1e5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6c025fe24e4a20a4311e3ccfa0539847/0aa95edf8db1cb1366403be3dc54564e92584b11.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6de78b61cc11728b302d8c2af8fec3b3/2fdea9ec8a136327de37c6c3908fa0ec09fac76d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3eaebe9495eef01f4d1418cdd0ff99e0/c937afc379310a553938d904b64543a982261026.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a578aae2730e0cf3a0f74ef33a47f23d/e355564e9258d10986f4ae42d058ccbf6c814d2f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1751013a838ba61edfeec827713597cc/b900a18b87d6277f384b35c829381f30e824fce2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=073ea62ef11f3a295ac8d5c6a924bce3/91c279310a55b31960ae858542a98226cefc17ef.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=11b8673bb3fb43161a1f7a7210a64642/a724bc315c6034a8720abf71ca13495408237652.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=45090413d439b6004dce0fbfd9513526/5908c93d70cf3bc7cffaca1cd000baa1cc112a4c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6d115e41810a19d8cb03840d03fb82c9/e4b54aed2e738bd412b2b5c1a08b87d6267ff9b4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=34f8413083025aafd3327ec3cbefab8d/ea2b2834349b033bbe5efb2a14ce36d3d439bd69.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=01abfed1738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d04c09a24fa1ec08fa513dc608.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=64fb026dcaef76093c0b99971edca301/936fddc451da81cb51d4853d5366d01609243114.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=33d7e6a28435e5dd902ca5d746c7a7f5/f694d143ad4bd1130fe5b1c25bafa40f4bfb0512.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=19b69c0378310a55c424defc87444387/04f23a87e950352a28dc23f85243fbf2b3118b86.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8b8c14bf9f2f07085f052a08d925b865/b31101e93901213fb454482c55e736d12e2e95df.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=efa064ec10dfa9ecfd2e561f52d1f754/48c7a7efce1b9d16e0899c91f2deb48f8d5464f0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3bac038e241f95caa6f592bef9167fc5/2131e924b899a901e0ae5dc61c950a7b0308f5ac.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d1b37167d833c895a67e9873e1127397/244d510fd9f9d72abece16edd52a2834359bbb9d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cc92a198d009b3deebbfe460fcbd6cd3/0713b31bb051f8191cc405badbb44aed2f73e75a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f80a8c59a8014c08193b28ad3a7a025b/6ae636d12f2eb9388a7e7b02d4628535e5dd6f2b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=080ee5c9b2de9c82a665f9875c8080d2/8d1ab051f8198618ac2cee744bed2e738ad4e6dc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4116378f79f0f736d8fe4c093a54b382/08d2d539b6003af3d0f5dd90342ac65c1138b6f0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=435a7b8a21a446237ecaa56aa8237246/60de8db1cb134954275be994574e9258d0094afd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5eb87d940823dd542173a760e108b3df/7491f603738da977acb987dbb151f8198718e3a9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0b081ef81e30e924cfa49c397c096e66/1f3eb80e7bec54e73ad119eeb8389b504fc26a05.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1c7d9da4024f78f0800b9afb49300a83/2bcf36d3d539b600dd630c47e850352ac75cb796.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ccad6f42b3b7d0a27bc90495fbee760d/431fd21b0ef41bd5ccf7eb0450da81cb38db3d9e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=edf04106a6efce1bea2bc8c29f50f3e8/bc035aafa40f4bfb86f29ca4024f78f0f7361824.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d03350c9c83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad1cdd4c513562c11dfa9ecce0b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44ec940c7dd98d1076d40c39113eb807/6c67d0160924ab18fd1ae3c734fae6cd7b890b36.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=03ac6e2e3c6d55fbc5c6762e5d234f40/13f4e0fe9925bc31795b03685fdf8db1cb137038.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5e9a0113d439b6004dce0fbfd9513526/5908c93d70cf3bc7d469cf1cd000baa1cc112ad1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5a578ef89a504fc2a25fb00dd5dce7f0/312542a7d933c895a43a9a8bd01373f0830200fe.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c9c04c2c55e736d158138c00ab524ffc/d8cc7b899e510fb37eea7567d833c895d0430c4b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=15e18bf15d6034a829e2b889fb1249d9/7da88226cffc1e1706468ec14b90f603728de942.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=862f8bfeb03533faf5b6932698d1fdca/b5d5b31c8701a18b182110bf9f2f07082938fe7d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=05b9e6c9b2de9c82a665f9875c8380d2/8d1ab051f8198618a19bed744bed2e738ad4e667.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c42488becb8065387beaa41ba7dca115/fdcfc3fdfc03924581bfecff8694a4c27d1e253c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=234f89f532fa828bd1239debcd1e41cd/8d1d8701a18b87d6b80ea6ef060828381e30fdf7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=90e810f4359b033b2c88fcd225cf3620/1b1e95cad1c8a7868dd547fe6609c93d71cf5047.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=82d9994b86d6277fe9123230183a1f63/9dcd7cd98d1001e93cd919ffb90e7bec55e7975d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0989ac2ef11f3a295ac8d5c6a927bce3/91c279310a55b3196e198f8542a98226cefc175a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c8ebf1fd71cf3bc7e800cde4e102babd/3c097bf40ad162d95d316fec10dfa9ec8b13cd60.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=73c58b3b42166d223877159c76220945/82305c6034a85edfab8ff2d348540923dd54753b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44c116eeb8389b5038ffe05ab534e5f1/31b20f2442a7d933ba85e883ac4bd11373f00115.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=04f9e898cdbf6c81f7372ce08c3fb1d7/b819367adab44aedb608b214b21c8701a08bfbf9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=69eeedff8694a4c20a23e7233ef51bac/67ef3d6d55fbb2fbfc7054e24e4a20a44723dcec.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7f22c5c39345d688a302b2ac94c07dab/36fb513d269759ee81b26c3bb3fb43166c22df65.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=845a90b73812b31bc76ccd21b61a3674/a9dca144ad3459824560e9620df431adcaef845d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=20721fedd52a283443a636036bb4c92e/a90b304e251f95ca91e39d46c8177f3e67095228.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8fdb373430adcbef01347e0e9cae2e0e/25d4ad6eddc451dadeb2021bb7fd5266d11632fe.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cbe8f2fd71cf3bc7e800cde4e102babd/3c097bf40ad162d95e326cec10dfa9ec8b13cd67.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=06af9313fc1f4134e0370576151e95c1/197e9e2f07082838bf81d2e4b999a9014d08f1b7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=392a9413fc1f4134e0370576151e95c1/197e9e2f070828388004d5e4b999a9014c08f132.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=343240499f510fb37819779fe931c893/55610c338744ebf8c8b36acfd8f9d72a6159a705.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9527a3fdf9dcd100cd9cf829428947be/5cd8f2d3572c11df070cb6d3622762d0f603c266.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=846698cf4afbfbeddc59367748f1f78e/3d3a5bb5c9ea15cee81ae9f9b7003af33b87b24f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=01fa14f81e30e924cfa49c397c0a6e66/1f3eb80e7bec54e7302313eeb8389b504ec26a77.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5781ecc0bba1cd1105b672288913c8b0/692d11dfa9ec8a13cc7a5f10f603918fa1ecc0db.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=811b9291f2deb48ffb69a1d6c01d3aef/9565034f78f0f736d32244f10b55b319eac41366.jpg", // repeat case. "http://imgsrc.baidu.com/forum/w%3D580/sign=44c116eeb8389b5038ffe05ab534e5f1/31b20f2442a7d933ba85e883ac4bd11373f00115.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d383b614b21c8701d6b6b2ee177d9e6e/7537acaf2edda3cc8129b0d000e93901203f9276.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44e9071bb7fd5266a72b3c1c9b199799/a623720e0cf3d7cabee93486f31fbe096b63a920.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=abefa0910eb30f24359aec0bf894d192/32328744ebf81a4c8169e739d62a6059242da6ec.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=59d35241810a19d8cb03840d03fb82c9/e4b54aed2e738bd42670b9c1a08b87d6267ff9ea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=484d0f940823dd542173a760e108b3df/7491f603738da977ba4cf5dbb151f8198718e3e4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=54d963edd52a283443a636036bb4c92e/a90b304e251f95cae548e146c8177f3e66095285.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5cf0571fa8ec8a13141a57e8c7019157/99eece1b9d16fdfaa48db51eb58f8c5495ee7b59.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6e9d843dfd039245a1b5e107b795a4a8/9eed08fa513d2697f4bd7aad54fbb2fb4216d8d1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=197d132737d12f2ece05ae687fc3d5ff/45889e510fb30f2420369ef3c995d143ac4b0396.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6643c054a5c27d1ea5263bcc2bd4adaf/036c55fbb2fb4316df5e088a21a4462308f7d3fa.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=43ad6a35e1fe9925cb0c695804a95ee4/8d18ebc4b74543a9fba2883c1f178a82b8011481.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2f54900a7acb0a4685228b315b62f63e/ef08b3de9c82d158ef182b41810a19d8bd3e42ac.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1303803cc2cec3fd8b3ea77de689d4b6/c902918fa0ec08fafb2c6e5758ee3d6d55fbda17.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1c43e58bd01373f0f53f6f97940e4b8b/5e58252dd42a2834f99b4bfb5ab5c9ea15cebf06.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6e1db4c39345d688a302b2ac94c07dab/36fb513d269759ee908d1d3bb3fb43166c22df66.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=76d930f10b55b3199cf9827d73a88286/0486e950352ac65cc9221a83faf2b21193138a18.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2fe5e2a4024f78f0800b9afb49300a83/2bcf36d3d539b600eefb7347e850352ac65cb70e.jpg", // repeat case. "http://imgsrc.baidu.com/forum/w%3D580/sign=0b081ef81e30e924cfa49c397c096e66/1f3eb80e7bec54e73ad119eeb8389b504fc26a05.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ef54f0d2c9fcc3ceb4c0c93ba244d6b7/5cd1f703918fa0eca0be18fd279759ee3c6ddbc2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3665fe8542a98226b8c12b2fba83b97a/2e395343fbf2b2114eb2f9becb8065380dd78ea7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=82352cf80dd79123e0e0947c9d355917/c2029245d688d43fcf21ed027c1ed21b0ff43bb2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=220b01f6adaf2eddd4f149e1bd110102/bfca39dbb6fd5266841443e4aa18972bd4073607.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1328ccd8377adab43dd01b4bbbd5b36b/eea30cf431adcbef9d3801f6adaf2edda3cc9f37.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0b1748c87af40ad115e4c7eb672d1151/eb638535e5dde71191833c06a6efce1b9c1661e9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d646f4c96a63f6241c5d390bb745eb32/f2be6c81800a19d89867fbf532fa828ba71e46de.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=65a86bffb90e7bec23da03e91f2fb9fa/ea0635fae6cd7b89c2f845730e2442a7d8330eae.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5df6433791529822053339cbe7c87b3b/77550923dd54564e898d9bc9b2de9c82d0584f52.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5ddf853c1f178a82ce3c7fa8c601737f/8c109313b07eca80bac14617902397dda044837f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=840d71e92cf5e0feee1889096c6134e5/3454b319ebc4b74537bbc9e6cefc1e178a821517.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=11119ac9b2de9c82a665f9875c8080d2/8d1ab051f8198618b53391744bed2e738ad4e6cf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4198f93b42166d223877159c76220945/82305c6034a85edf99d280d348540923dc5475e0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=220b7d13d439b6004dce0fbfd9513526/5908c93d70cf3bc7a8f8b31cd000baa1cc112a42.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1ff9e76f2fdda3cc0be4b82831eb3905/07dab6fd5266d0165debe0d8962bd40734fa3554.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7a33f4ce7aec54e741ec1a1689399bfd/0bfbe6cd7b899e5154bf43e443a7d933c8950d09.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e5089fd60824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f096724a3f7246b600d33aef4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=51d122e24e4a20a4311e3ccfa0539847/0aa95edf8db1cb135b9346e3dc54564e93584b4c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1a099855d043ad4ba62e46c8b2035a89/e7f8d72a6059252d171078f4359b033b5bb5b938.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b1bafbd8962bd40742c7d3f54b889e9c/3447f21fbe096b634eba3ba00d338744eaf8aca4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=052385f9b7003af34dbadc680528c619/f73c70cf3bc79f3dcb2c81c0bba1cd11738b2975.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b7eaa6f6aec379317d688621dbc5b784/88013af33a87e950fa30979c11385343fbf2b418.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6dc5001a80cb39dbc1c0675ee01709a7/37f690529822720ebcf2860a7acb0a46f21fab07.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e3f97f1da2cc7cd9fa2d34d109002104/88fc5266d0160924e44ec6ebd50735fae7cd34d3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8a1634f80dd79123e0e0947c9d355917/c2029245d688d43fc702f5027c1ed21b0ff43b93.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=676aadc39345d688a302b2ac94c37dab/36fb513d269759ee99fa043bb3fb43166c22df9d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5ca7230f3ac79f3d8fe1e4388aa0cdbc/45f50ad162d9f2d3c2b6421fa8ec8a136227cc98.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=579e7e35e1fe9925cb0c695804a95ee4/8d18ebc4b74543a9ef919c3c1f178a82b80114bc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=75d38a39d62a60595210e1121836342d/96d2fd1f4134970a149276df94cad1c8a6865d59.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a51b028e9358d109c4e3a9bae159ccd0/97763912b31bb0518056d2d8377adab44bede0d5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0801ac1cd000baa1ba2c47b37711b9b1/ccd2572c11dfa9ec1d2f37e763d0f703918fc13a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=035a29ded31b0ef46ce89856edc551a1/8cfa43166d224f4a873c12e308f790529922d19e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ff07d50b38dbb6fd255be52e3926aba6/ae539822720e0cf379172f0b0b46f21fbf09aa5b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3229663a838ba61edfeec827713597cc/b900a18b87d6277f1d3352c829381f30e924fc1a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bb4b70df94cad1c8d0bbfc2f4f3f67c4/d725b899a9014c08e50348550b7b02087bf4f403.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e9a312d0b812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f3bc45d3a503d269759eec42e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=76f09e2f77094b36db921be593cd7c00/e3c551da81cb39dbf1a007a6d1160924aa1830da.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=76e159f38644ebf86d716437e9f8d736/823fb13533fa828bbb9ffd13fc1f4134960a5a86.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=213e0c2e3c6d55fbc5c6762e5d234f40/13f4e0fe9925bc315bc961685fdf8db1ca1370ae.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fcc657c87af40ad115e4c7eb672d1151/eb638535e5dde71166522306a6efce1b9c1661bb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=75946cd2b21bb0518f24b3200678da77/9f45ad345982b2b70c41593430adcbef77099b61.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6ebae5dc8601a18bf0eb1247ae2e0761/92ae2edda3cc7cd9687e57293801213fb90e91f4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f2707ef4359b033b2c88fcd225cf3620/1b1e95cad1c8a786ef4d29fe6609c93d71cf50df.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b4ce1467d833c895a67e9873e1117397/244d510fd9f9d72adbb373edd52a2834359bbb68.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5ee9943dfd039245a1b5e107b795a4a8/9eed08fa513d2697c4c96aad54fbb2fb4316d82d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9a4da513562c11dfded1bf2b53266255/aeee76c6a7efce1b6582aa3aae51f3deb58f6592.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7da28a94574e9258a63486e6ac83d1d1/4d8ca9773912b31bc4d0afd98718367adbb4e187.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ea97c32ef11f3a295ac8d5c6a924bce3/91c279310a55b3198d07e08542a98226cefc1740.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2eca244da8773912c4268569c81b8675/af2297dda144ad34814be577d1a20cf430ad854f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=76938cff8694a4c20a23e7233ef51bac/67ef3d6d55fbb2fbe30d35e24e4a20a44623dc19.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=703761d2b21bb0518f24b320067bda77/9f45ad345982b2b709e2543430adcbef77099bc6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=31276c685fdf8db1bc2e7c6c3922dddb/f1fd1e178a82b90127d7aec3728da9773812efcd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=63555433d6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82c003947f6d81800a18d843d8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=13e989d372f082022d9291377bfafb8a/1b2cd42a2834349bbd990375c8ea15ce37d3bea0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9b6a1d1da044ad342ebf878fe0a30c08/ea3e8794a4c27d1ed85284341ad5ad6edcc438ce.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bbbd7fdf3b292df597c3ac1d8c305ce2/47300a55b319ebc49816cc698326cffc1f1716d0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bfd45bff8694a4c20a23e7233ef51bac/67ef3d6d55fbb2fb2a4ae2e24e4a20a44723dcda.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e5a872df4034970a47731027a5cbd1c0/a02e070828381f30a45f3759a8014c086f06f0f9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0f7726c14d086e066aa83f43320a7b5a/08d02f2eb9389b50f82c5aa28435e5dde6116e74.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c21fc3a6d1160924dc25a213e405359b/32f2d7ca7bcb0a4686fd2fc96a63f6246a60af60.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=11c82191f91986184147ef8c7aef2e69/6783b2b7d0a20cf4937e5a2f77094b36adaf9951.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=078b0c910eb30f24359aec0bf894d192/32328744ebf81a4c2d0d4b39d62a6059252da600.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6e3ba7685fdf8db1bc2e7c6c3922dddb/f1fd1e178a82b90178cb65c3728da9773812efd9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=77603eaedcc451daf6f60ce386fc52a5/1ea5462309f79052f497e1ce0df3d7ca7acbd5b3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9ca0189495eef01f4d1418cdd0ff99e0/c937afc379310a559b367f04b64543a982261034.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8da01242b8014a90813e46b599763971/8e7fca8065380cd702940f1da044ad34588281bd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6856f2a3f7246b607b0eb27cdbf91a35/5280800a19d8bc3e676aaa3a838ba61ea9d345e5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d1a82d4f8d5494ee87220f111df4e0e1/46f1f736afc3793128232ad9eac4b74542a911f3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1b68afbadbb44aed594ebeec831d876a/32f531adcbef7609d646356f2fdda3cc7dd99ef6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=764614d000e9390156028d364bed54f9/3725ab18972bd4073f657f0d7a899e510eb309a4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6a85ce30f636afc30e0c3f6d831beb85/eb38b6003af33a87809a83eac75c10385243b548.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ab254f1cd000baa1ba2c47b37711b9b1/ccd2572c11dfa9ecbe0bd4e763d0f703918fc11e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b124c43083025aafd3327ec3cbecab8d/ea2b2834349b033b3b827e2a14ce36d3d539bd3d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0372a20e37d3d539c13d0fcb0a86e927/413f6709c93d70cff15f28fdf9dcd100bba12b9e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5801d8f4359b033b2c88fcd225cf3620/1b1e95cad1c8a786453c8ffe6609c93d70cf5029.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0a47a942b3b7d0a27bc90495fbee760d/431fd21b0ef41bd50a1d2d0450da81cb38db3df1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b801393cc2cec3fd8b3ea77de689d4b6/c902918fa0ec08fa502ed75758ee3d6d55fbda11.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ad491990342ac65c6705667bcbf0b21d/c6ddd100baa1cd11e545b7d0b812c8fcc2ce2d54.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=59288a340bd162d985ee621421dea950/bb34e5dde71190ef2de6562fcf1b9d16fdfa6026.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0695a78e9358d109c4e3a9bae159ccd0/97763912b31bb05123d877d8377adab44bede040.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8af396c9c83d70cf4cfaaa05c8ded1ba/347a02087bf40ad197140313562c11dfa8ecce54.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8f6b71ca18d8bc3ec60806c2b289a6c8/74ec2e738bd4b31c64eb504b86d6277f9f2ff869.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=eb622083ac4bd11304cdb73a6aada488/e92b6059252dd42a0bc3c55b023b5bb5c8eab87d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ad6a65ca314e251fe2f7e4f09787c9c2/16391f30e924b899455fbec86f061d950b7bf6e5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a3032f0450da81cb4ee683c56267d0a4/782209f7905298227c63fe33d6ca7bcb0b46d4eb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e5226ae2730e0cf3a0f74ef33a47f23d/e355564e9258d109c6ae6e42d058ccbf6d814df6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5f8e25ea6159252da3171d0c049a032c/c31e4134970a304ea368670ad0c8a786c8175cfd.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1cdfbb940823dd542173a760e10bb3df/7491f603738da977eede41dbb151f8198718e34b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4dab1be4b999a9013b355b3e2d940a58/45ed54e736d12f2eeba369904ec2d56284356899.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0e493f7f6d81800a6ee58906813433d6/087bdab44aed2e7354c540dc8601a18b87d6fa10.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5d232af3c995d143da76e42b43f18296/6f0ed9f9d72a60593aa927ea2934349b023bba82.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0ddb882c55e736d158138c00ab524ffc/d8cc7b899e510fb3baf1b167d833c895d0430c53.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9f40b92d2f738bd4c421b2399189876c/f5ee76094b36acaf6b29500c7dd98d1000e99c72.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8afdd2ffb90e7bec23da03e91f2cb9fa/ea0635fae6cd7b892dadfc730e2442a7d8330e7a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=93d365e396dda144da096cba82b6d009/e889d43f8794a4c2e21a26db0ff41bd5ad6e3902.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fbd248feb03533faf5b6932698d2fdca/b5d5b31c8701a18b65dcd3bf9f2f07082838fe09.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9a53b22d2f738bd4c421b2399189876c/f5ee76094b36acaf6e3a5b0c7dd98d1000e99c6d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=860db23bfaedab6474724dc8c737af81/65b4c9ea15ce36d3f73b4fc03bf33a87e950b100.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5355f395b219ebc4c0787691b227cf79/d751352ac65c103879270232b3119313b17e89e2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5b9a20f3c995d143da76e42b43f18296/6f0ed9f9d72a60593c102dea2934349b033bba3b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a2c8a36fa50f4bfb8cd09e5c334d788f/0a9a033b5bb5c9ea3504cf13d439b6003bf3b348.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5cdae018bd3eb13544c7b7b3961ca8cb/10728bd4b31c87017d5b7e16267f9e2f0608ff57.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f26782499f510fb37819779fe932c893/55610c338744ebf80ee6a8cfd8f9d72a6159a7a8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=962968e396dda144da096cba82b5d009/e889d43f8794a4c2e7e02bdb0ff41bd5ac6e3904.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9fecc1079e3df8dca63d8f99fd1072bf/34d062d9f2d3572cfefc532c8b13632762d0c322.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0a5c61904ec2d562f208d0e5d71090f3/7ca6d933c895d1431e2f2bd372f082025baf07e2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=15d49641810a19d8cb03840d03fb82c9/e4b54aed2e738bd46a777dc1a08b87d6267ff9ea.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=24b997e24e4a20a4311e3ccfa0539847/0aa95edf8db1cb132efbf3e3dc54564e93584bb5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f20df717902397ddd679980c6983b216/ac44d688d43f879433f786ded31b0ef41bd53a33.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=65b180e7cefc1e17fdbf8c397a91f67c/c5f3b2119313b07eec9867f90dd7912397dd8c1e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d80f61e663d0f703e6b295d438fb5148/c3fbaf51f3deb48fde48962ff11f3a292df5781a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b1e6a10d7dd98d1076d40c39113eb807/6c67d0160924ab180810d6c634fae6cd7b890b39.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=044ea385ac345982c58ae59a3cf5310b/b995a4c27d1ed21b9d671914ac6eddc450da3f91.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d9d7f933b3119313c743ffb855390c10/7911b912c8fcc3ce4969fec29345d688d53f2092.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1c4f6fe34e4a20a4311e3ccfa0539847/0aa95edf8db1cb13160d0be2dc54564e93584bdf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=420da6ce4afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ce2e71d7f8b7003af33b87b2a0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1c64b11f9d82d158bb8259b9b00b19d5/8e50f8198618367a17d9482c2f738bd4b21ce5f4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=56cfcfd0738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d01b6d934ea1ec08fa503dc6f5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=451567f90dd79123e0e0947c9d355917/c2029245d688d43f0801a6037c1ed21b0ff43b93.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9f8059fd0dd79123e0e0947c9d355917/c2029245d688d43fd29498077c1ed21b0ef43b02.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a87aa5940eb30f24359aec0bf897d192/32328744ebf81a4c82fce23cd62a6059242da67e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6947bb51a5c27d1ea5263bcc2bd4adaf/036c55fbb2fb4316d05a738f21a4462308f7d3fb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=88dc2cfd5243fbf2c52ca62b807fca1e/f310728b4710b912867cff0bc2fdfc0393452282.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b4ec4ecb0df3d7ca0cf63f7ec21dbe3c/684f9258d109b3deb04ced9dcdbf6c81810a4c50.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5da230f68644ebf86d716437e9f8d736/823fb13533fa828b90dc9416fc1f4134960a5a4c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7f214f4e4610b912bfc1f6f6f3fcfcb5/b412632762d0f70323bf9d2909fa513d2697c533.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5a50e9c234fae6cd0cb4ab693fb20f9e/80086b63f6246b60c148ba81eaf81a4c500fa286.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4e38f2fd9a504fc2a25fb00dd5dce7f0/312542a7d933c895b055e68ed01373f082020018.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7b91f8d98601a18bf0eb1247ae2e0761/92ae2edda3cc7cd97d554a2c3801213fb90e91c8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8c8671363b87e9504217f3642039531b/05c69f3df8dcd100802786d4738b4710b8122f88.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5a0944760e2442a7ae0efdade142ad95/b945ebf81a4c510f1da19cef6159252dd52aa5db.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5206ff1b9d82d158bb8259b9b00b19d5/8e50f8198618367a59bb06282f738bd4b31ce512.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=02a1b147b3b7d0a27bc90495fbee760d/431fd21b0ef41bd502fb350150da81cb38db3d98.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=34b39035f636afc30e0c3f6d831beb85/eb38b6003af33a87deacddefc75c10385243b507.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6bb54dca4afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ce07c93cfcb7003af33a87b225.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=59aa60ab54fbb2fb342b581a7f4b2043/deff9925bc315c6034ec03198cb1cb13485477cf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=18d5d6c87aec54e741ec1a16893a9bfd/0bfbe6cd7b899e51365961e243a7d933c9950d75.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=082508cfc83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad115c29d15562c11dfa9ecce27.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4cff68ce29381f309e198da199034c67/0700213fb80e7bec863759172e2eb9389a506b5c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=05fb8402b64543a9f51bfac42e158a7b/f85d10385343fbf204f3fcd5b17eca8064388f6e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=de240b320bd162d985ee621421dea950/bb34e5dde71190efaaead729cf1b9d16fdfa6030.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fa597c3230adcbef01347e0e9cad2e0e/25d4ad6eddc451daab30491db7fd5266d1163206.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=be0e4bef2cf5e0feee1889096c6134e5/3454b319ebc4b7450db8f3e0cefc1e178a82151c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0899a0d572f082022d9291377bf9fb8a/1b2cd42a2834349ba6e92a73c8ea15ce37d3be5e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=85297e35d6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82267fbe796d81800a19d8432b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=02443856f703738dde4a0c2a831ab073/5b390cd7912397dd1a01dff25882b2b7d1a287c9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=514075ce29381f309e198da199004c67/0700213fb80e7bec9b8844172e2eb9389a506bf3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=03f27d750e2442a7ae0efdade142ad95/b945ebf81a4c510f445aa5ec6159252dd52aa5af.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7f75c071d1a20cf44690fed7460b4b0c/ec1a0ef41bd5ad6efb33231c80cb39dbb7fd3c7a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=af7a0b36f636afc30e0c3f6d8318eb85/eb38b6003af33a87456546ecc75c10385343b539.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=516aae92574e9258a63486e6ac83d1d1/4d8ca9773912b31be8188bdf8718367adbb4e1d5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=634145172e2eb938ec6d7afae56385fe/a0500fb30f2442a7ee95bb53d043ad4bd01302a9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=77d25e33e1fe9925cb0c695804aa5ee4/8d18ebc4b74543a9cfddbc3a1f178a82b8011406.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b0c5e41ba044ad342ebf878fe0a00c08/ea3e8794a4c27d1ef3fd7d321ad5ad6edcc43869.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=873c7835d6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82246ab8796d81800a19d8433e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d66a7e93b219ebc4c0787691b227cf79/d751352ac65c1038fc188f34b3119313b17e89e7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b645525158ee3d6d22c687c373176d41/04282df5e0fe99255b4928a635a85edf8cb17184.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=291b9d96342ac65c6705667bcbf3b21d/c6ddd100baa1cd11611733d6b812c8fcc2ce2da7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=44498e34b3119313c743ffb855390c10/7911b912c8fcc3ced4f789c59345d688d43f2015.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=edd124fb279759ee4a5060c382fa434e/ce1e3a292df5e0fe7c0ec9f75d6034a85edf7237.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8fa6a4c6bba1cd1105b672288913c8b0/692d11dfa9ec8a13145d1716f603918fa1ecc086.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=597936d06c224f4a5799731b39f69044/626134a85edf8db120913c920823dd54574e748e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=aa410e36f636afc30e0c3f6d8318eb85/eb38b6003af33a87405e43ecc75c10385343b512.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bdbbb53bfd039245a1b5e107b795a4a8/9eed08fa513d2697279b4bab54fbb2fb4216d8f9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=78813ace6f061d957d4637304bf50a5d/112fb9389b504fc204e2f3f3e4dde71191ef6d8c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=198dcffe9a504fc2a25fb00dd5dce7f0/312542a7d933c895e7e0db8dd01373f0830200ae.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f959e6949922720e7bcee2f24bca0a3a/3722dd54564e925821a7c5189d82d158cdbf4eb2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2475aa0250da81cb4ee683c56267d0a4/782209f790529822fb157b35d6ca7bcb0a46d427.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0756346f8326cffc692abfba89004a7d/6d42fbf2b2119313006dbd4664380cd791238d1f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d830a0ec6159252da3171d0c0499032c/c31e4134970a304e24d6e20cd0c8a786c8175c54.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d30b87d93b292df597c3ac1d8c335ce2/47300a55b319ebc4f0a0346f8326cffc1f171668.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=05db57e8b8389b5038ffe05ab534e5f1/31b20f2442a7d933fb9fa985ac4bd11373f00115.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=04370ef70b55b3199cf9827d73ab8286/0486e950352ac65cbbcc2485faf2b21192138a78.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b286ca3d42166d223877159c76220945/82305c6034a85edf6accb3d548540923dc547581.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=dc231a1ff3d3572c66e29cd4ba116352/d91090ef76c6a7ef18e9e5d0fcfaaf51f2de667e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=18bb3b6f8326cffc692abfba89034a7d/6d42fbf2b21193131f80b24664380cd790238d02.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d2da1cfe0dd79123e0e0947c9d355917/c2029245d688d43f9fcedd047c1ed21b0ff43be5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fc89a80ea686c91708035231f93c70c6/97004c086e061d9563387bce7af40ad163d9cacf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b292ce498d5494ee87220f111df4e0e1/46f1f736afc379314b19c9dfeac4b74542a911d7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=361efacc18d8bc3ec60806c2b28aa6c8/74ec2e738bd4b31cdd9edb4d86d6277f9f2ff8a3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1e1d43d4b21bb0518f24b320067bda77/9f45ad345982b2b767c8763230adcbef77099bf6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=13f61dcfc83d70cf4cfaaa05c8ded1ba/347a02087bf40ad10e118815562c11dfa8ecce54.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=24b4aeec2934349b74066e8df9eb1521/0e4f251f95cad1c8a61fb3d17e3e6709c83d51a4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=257807d8d31b0ef46ce89856edc551a1/8cfa43166d224f4aa11e3ce508f790529922d146.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=19389002b64543a9f51bfac42e168a7b/f85d10385343fbf21830e8d5b17eca8065388f2d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8649b7796d81800a6ee58906813433d6/087bdab44aed2e73dcc5c8da8601a18b87d6fa1d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1e9d40019e3df8dca63d8f99fd1072bf/34d062d9f2d3572c7f8dd22a8b13632763d0c3de.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=427efdde377adab43dd01b4bbbd5b36b/eea30cf431adcbefcc6e30f0adaf2edda2cc9feb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cef2b2d17e3e6709be0045f70bc69fb8/50071d950a7b02081280711763d9f2d3562cc8f8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=dad8621ebd3eb13544c7b7b3961ca8cb/10728bd4b31c8701fb59fc10267f9e2f0608ff5e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=dc6b1efe0dd79123e0e0947c9d365917/c2029245d688d43f917fdf047c1ed21b0ff43b76.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3c134abcdbb44aed594ebeec831d876a/32f531adcbef7609f13dd0692fdda3cc7cd99e17.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=59d9a692574e9258a63486e6ac80d1d1/4d8ca9773912b31be0ab83df8718367adbb4e106.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cefb703230adcbef01347e0e9cae2e0e/25d4ad6eddc451da9f92451db7fd5266d11632e4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5b717bce29381f309e198da199004c67/0700213fb80e7bec91b94a172e2eb9389a506be2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d089ec2977c6a7efb926a82ecdfbafe9/4df182025aafa40f7c63872baa64034f79f0199b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b87c8bc5908fa0ec7fc764051696594a/cddfb48f8c5494ee2ab346ef2cf5e0fe98257eb0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4327c0f8b03533faf5b6932698d1fdca/b5d5b31c8701a18bdd295bb99f2f07082938fe03.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=df5419fe0dd79123e0e0947c9d365917/c2029245d688d43f9240d8047c1ed21b0ff43b5f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f479e0d0fcfaaf5184e381b7bc5594ed/75fafbedab64034f42928af0aec379310b551ded.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ee7562f6e824b899de3c79305e071d59/860f7bec54e736d1e963c4fe9a504fc2d46269d1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e1a1aa0c7acb0a4685228b315b62f63e/ef08b3de9c82d15821ed1147810a19d8bd3e42de.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=599c2c1c80cb39dbc1c0675ee01709a7/37f690529822720e88abaa0c7acb0a46f31fabe4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=bec447353b87e9504217f364203a531b/05c69f3df8dcd100b265b0d7738b4710b8122f4f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fad2fa82eaf81a4c2632ecc1e7286029/8f3433fa828ba61e5e3280d94034970a314e596d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4009c1f8b03533faf5b6932698d1fdca/b5d5b31c8701a18bde075ab99f2f07082938fe5d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e687ccf332fa828bd1239debcd1e41cd/8d1d8701a18b87d67dc6e3e9060828381e30fd45.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6a17d2692fdda3cc0be4b82831e83905/07dab6fd5266d0162805d5de962bd40735fa352c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=dca27493b219ebc4c0787691b227cf79/d751352ac65c1038f6d08534b3119313b17e899f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=31fe32da8601a18bf0eb1247ae2e0761/92ae2edda3cc7cd9373a802f3801213fb80e9136.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=1a56139ed009b3deebbfe460fcbe6cd3/0713b31bb051f819ca00b7bcdbb44aed2e73e724.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=459b140cd0c8a786be2a4a065708c9c7/8698a9014c086e067d118bbd03087bf40bd1cb88.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=57892e0578310a55c424defc87444387/04f23a87e950352a66e391fe5243fbf2b3118b43.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fd451e44d058ccbf1bbcb53229d9bcd4/c6188618367adab40476acb58ad4b31c8601e4a7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=97fdd4889358d109c4e3a9bae159ccd0/97763912b31bb051b2b004de377adab44bede0b5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=9d1030189d82d158bb8259b9b00b19d5/8e50f8198618367a96adc92b2f738bd4b31ce525.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ad7a753cae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f0c75a270578310a55b2191cf7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=27b48fce29381f309e198da199004c67/0700213fb80e7beced7cbe172e2eb9389a506ba7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=622e0a4da71ea8d38a22740ca70830cf/9f8a87d6277f9e2f56dca0fe1e30e924b999f358.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5d2697172e2eb938ec6d7afae56385fe/a0500fb30f2442a7d0f26953d043ad4bd013024c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8cc62652a5c27d1ea5263bcc2bd7adaf/036c55fbb2fb431635dbee8c21a4462308f7d305.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=551798353b87e9504217f3642039531b/05c69f3df8dcd10059b66fd7738b4710b9122f1b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5fbf440b7a899e51788e3a1c72a6d990/c8256b600c33874481bdb88a500fd9f9d62aa0c9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fdaadfd8d31b0ef46ce89856edc551a1/8cfa43166d224f4a79cce4e508f790529922d1f4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=46110fa8dcc451daf6f60ce386ff52a5/1ea5462309f79052c5e6d0c80df3d7ca7acbd548.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=76761ab8cb8065387beaa41ba7dca115/fdcfc3fdfc03924533ed7ef98694a4c27c1e25e9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d6cbfbc9d8f9d72a17641015e42b282a/981fa8d3fd1f41345b8d9a88241f95cad0c85e8b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ea716c9a113853438ccf8729a312b01f/84a0cd11728b47106649623ac2cec3fdfd0323e7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3ac7e056f703738dde4a0c2a8319b073/5b390cd7912397dd228207f25882b2b7d1a2874a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d930090a7dd98d1076d40c39113db807/6c67d0160924ab1860c67ec134fae6cd7a890b71.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f7e4da093ac79f3d8fe1e4388aa3cdbc/45f50ad162d9f2d369f5bb19a8ec8a136227cc65.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=2f2392d4b21bb0518f24b320067bda77/9f45ad345982b2b756f6a73230adcbef77099bd8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=816317f75d6034a829e2b889fb1249d9/7da88226cffc1e1792c412c74b90f603728de9ca.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f60102b13812b31bc76ccd21b6193674/a9dca144ad345982373b7b640df431adcaef8490.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=14517cec6159252da3171d0c049a032c/c31e4134970a304ee8b73e0cd0c8a786c8175cb4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=47319b1ba2cc7cd9fa2d34d109002104/88fc5266d0160924408622edd50735fae7cd34a2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=935fd71ff3d3572c66e29cd4ba126352/d91090ef76c6a7ef579528d0fcfaaf51f2de6692.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b4b26f724bed2e73fce98624b703a16d/0faccbef76094b362e679b1ba2cc7cd98c109d54.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=55d22449a1ec08fa260013af69ec3d4d/8a8e8c5494eef01f98f89833e1fe9925bd317d5d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a2381a692fdda3cc0be4b82831e83905/07dab6fd5266d016e02a1dde962bd40735fa3512.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4c96816e5fdf8db1bc2e7c6c3921dddb/f1fd1e178a82b9015a6643c5728da9773812ef7a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e61f709a113853438ccf8729a312b01f/84a0cd11728b47106a277e3ac2cec3fdfc032339.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fe761997f2deb48ffb69a1d6c01e3aef/9565034f78f0f736ac4fcff70b55b319eac41397.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=41d01bf25882b2b7a79f39cc01accb0a/9ac37d1ed21b0ef4fb411ba8dcc451da80cb3e98.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ec47bc1763d9f2d3201124e799ed8a53/dbdce71190ef76c6305c78dd9c16fdfaae5167bf.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=a05880172e2eb938ec6d7afae56385fe/a0500fb30f2442a72d8c7e53d043ad4bd0130243.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f90c20edd50735fa91f04eb1ae500f9f/cc1ebe096b63f6243974bbf58644ebf81a4ca318.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8de44ad94034970a47731027a5cbd1c0/a02e070828381f30cc130f5fa8014c086e06f03b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8dc07ad7738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d0c0622649a1ec08fa503dc6ed.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4be2f0d6b812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f9985bf3c503d269758eec4f5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cd498e1db7fd5266a72b3c1c9b199799/a623720e0cf3d7ca3749bd80f31fbe096a63a98e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=972a63f98694a4c20a23e7233ef51bac/67ef3d6d55fbb2fb02b4dae44e4a20a44723dcae.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=973de61c80cb39dbc1c0675ee01409a7/37f690529822720e460a600c7acb0a46f31fab05.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4023f9d6b812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f9244b63c503d269758eec4b4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d53db480f31fbe091c5ec31c5b620c30/a283d158ccbf6c812efca71ebd3eb13532fa407a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=df4afc6f8326cffc692abfba89004a7d/6d42fbf2b2119313d871754664380cd791238d13.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=05146292574e9258a63486e6ac83d1d1/4d8ca9773912b31bbc6647df8718367adab4e13b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f571dd16f603918fd7d13dc2613c264b/9150f3deb48f8c5402b84fd93b292df5e1fe7fda.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0e1403f8b03533faf5b6932698d1fdca/b5d5b31c8701a18b901a98b99f2f07082938fe50.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=463d5217f603918fd7d13dc2613c264b/9150f3deb48f8c54b1f4c0d83b292df5e0fe7f26.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4657db03b64543a9f51bfac42e168a7b/f85d10385343fbf2475fa3d4b17eca8064388fd3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e2040b343b87e9504217f3642039531b/05c69f3df8dcd100eea5fcd6738b4710b9122f08.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d2ef4d1e8c1001e94e3c1407880f7b06/ee170924ab18972ba524043de7cd7b899e510a2f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=514870f3960a304e5222a0f2e1c9a7c3/390928381f30e92414ce98c64d086e061c95f7e4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=da21e1650df431adbcd243317b37ac0f/30f51bd5ad6eddc4394cb00c38dbb6fd5366339a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4c4a1ee9b8389b5038ffe05ab534e5f1/31b20f2442a7d933b20ee084ac4bd11372f001a7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=39a3a382d788d43ff0a991fa4d1fd2aa/6f3c269759ee3d6d905b833c42166d224e4adead.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=25c8c91bd000baa1ba2c47b37712b9b1/ccd2572c11dfa9ec30e652e063d0f703908fc17a.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0d37073de7cd7b89e96c3a8b3f254291/5562f6246b600c335e74d93f1b4c510fd9f9a13d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=72d30bc6203fb80e0cd161df06d02ffb/a92ad40735fae6cd08b3ac960eb30f2443a70fc6.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3456413783025aafd3327ec3cbecab8d/ea2b2834349b033bbef0fb2d14ce36d3d439bdca.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=705e404c4610b912bfc1f6f6f3fcfcb5/b412632762d0f7032cc0922b09fa513d2797c5dc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5dee7d8d21a446237ecaa56aa8207246/60de8db1cb13495439efef93574e9258d0094a50.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f4e69428cf1b9d168ac79a69c3dfb4eb/64aea40f4bfbfbed2944308879f0f736afc31f2c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=73a20ac6203fb80e0cd161df06d02ffb/a92ad40735fae6cd09c2ad960eb30f2443a70fd7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6ca54ed9d31b0ef46ce89856edc551a1/8cfa43166d224f4ae8c375e408f790529922d1f4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0ca9bed700e9390156028d364bed54f9/3725ab18972bd407458ad50a7a899e510eb309fc.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=279f9f682fdda3cc0be4b82831e83905/07dab6fd5266d016658d98df962bd40734fa35b5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=aa14b411267f9e2f70351d002f31e962/42d88d1001e939012aa08bc97aec54e737d196a3.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=fce3322e3801213fcf334ed464e536f8/9519972bd40735fa973e484e9f510fb30e24087f.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=413be0c034fae6cd0cb4ab693fb20f9e/80086b63f6246b60da23b383eaf81a4c510fa21d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=63c349d9d31b0ef46ce89856edc551a1/8cfa43166d224f4ae7a572e408f790529822d10e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=de8d7e2a2f738bd4c421b239918a876c/f5ee76094b36acaf2ae4970b7dd98d1000e99cc5.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4272df03b64543a9f51bfac42e168a7b/f85d10385343fbf2437aa7d4b17eca8064388ff0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3a26e30d7acb0a4685228b315b61f63e/ef08b3de9c82d158fa6a5846810a19d8bd3e4259.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f8fa8c5ea8014c08193b28ad3a79025b/6ae636d12f2eb9388a8e7b05d4628535e4dd6f62.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ebdfa3cd314e251fe2f7e4f09784c9c2/16391f30e924b89903ea78cf6f061d950b7bf670.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cc7b016f5fdf8db1bc2e7c6c3922dddb/f1fd1e178a82b901da8bc3c4728da9773912ef20.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=767df609c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513d98166c293c6d55fbb3fbd98e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=f892b3df377adab43dd01b4bbbd5b36b/eea30cf431adcbef76827ef1adaf2edda2cc9f97.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e58a86deeac4b7453494b71efffe1e78/0b2bc65c10385343142d87d69213b07ecb80886c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=922d1ef3359b033b2c88fcd225cf3620/1b1e95cad1c8a7868f1049f96609c93d70cf500b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7cf99141c8177f3e1034fc0540ce3bb9/72096e061d950a7baf394d330bd162d9f2d3c93e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=867400bddbb44aed594ebeec831d876a/32f531adcbef76094b5a9a682fdda3cc7dd99ef0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=cf9551cec83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad1d272c414562c11dfa8ecceb0.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e9dfa1cd314e251fe2f7e4f09784c9c2/16391f30e924b89901ea7acf6f061d950b7bf670.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0e17cbd84034970a47731027a5c8d1c0/a02e070828381f304fe08e5ea8014c086f06f049.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=8130964c86d6277fe912323018391f63/9dcd7cd98d1001e93f3016f8b90e7bec54e7973d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e0add10a7a899e51788e3a1c72a6d990/c8256b600c3387443eaf2d8b500fd9f9d62aa0e4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4dbce6c7bba1cd1105b672288913c8b0/692d11dfa9ec8a13d6475517f603918fa1ecc0ed.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=446a940b7dd98d1076d40c39113eb807/6c67d0160924ab18fd9ce3c034fae6cd7a890bbb.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=78dd0dee2cf5e0feee1889096c6134e5/3454b319ebc4b745cb6bb5e1cefc1e178b82154e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4e103d81f31fbe091c5ec31c5b620c30/a283d158ccbf6c81b5d12e1fbd3eb13532fa4067.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6cf101c6203fb80e0cd161df06d02ffb/a92ad40735fae6cd1691a6960eb30f2442a70f24.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=441c6372c8ea15ce41eee00186013a25/9987c9177f3e67098e1a48083ac79f3df8dc552b.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=05898f8242a98226b8c12b2fba83b97a/2e395343fbf2b2117d5e88b9cb8065380dd78ed2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=4a77011cb7fd5266a72b3c1c9b199799/a623720e0cf3d7cab0773281f31fbe096a63a941.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5509f32877094b36db921be593cd7c00/e3c551da81cb39dbd2596aa1d1160924aa1830a8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0b099f28cf1b9d168ac79a69c3dcb4eb/64aea40f4bfbfbedd6ab3b8879f0f736aec31f53.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6b0854e54e4a20a4311e3ccfa0539847/0aa95edf8db1cb13614a30e4dc54564e92584b22.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=762b03ee2cf5e0feee1889096c6134e5/3454b319ebc4b745c59dbbe1cefc1e178a821538.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b3d7b3d4622762d0803ea4b790ed0849/a317fdfaaf51f3deaeb4b59395eef01f3b2979c7.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=0dce948cd01373f0f53f6f97940e4b8b/5e58252dd42a2834e8163afc5ab5c9ea14cebf92.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=dbc7f4dc9c16fdfad86cc6e6848e8cea/9a0e4bfbfbedab647f674237f636afc379311e34.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=24e60c162e2eb938ec6d7afae56385fe/a0500fb30f2442a7a932f252d043ad4bd113020d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=70c5883c42166d223877159c76220945/82305c6034a85edfa88ff1d448540923dc5475c2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=826daf0dd0c8a786be2a4a065708c9c7/8698a9014c086e06bae730bc03087bf40bd1cbff.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6dbaf53b1f178a82ce3c7fa8c602737f/8c109313b07eca808aa43610902397dda04483a1.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=b84864188cb1cb133e693c1bed5656da/20168a82b9014a907ae3494aa8773912b21bee6d.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=ee0db145b8014a90813e46b599763971/8e7fca8065380cd76139ac1aa044ad3459828127.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3bc50d162e2eb938ec6d7afae56385fe/a0500fb30f2442a7b611f352d043ad4bd113022e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=63b044f96609c93d07f20effaf3cf8bb/23940a7b02087bf4a076591ef3d3572c10dfcfb4.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3b030d162e2eb938ec6d7afae56085fe/a0500fb30f2442a7b6d7f352d043ad4bd0130268.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=e8bf7405d462853592e0d229a0ed76f2/e732c895d143ad4ba2fc483783025aafa50f0673.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=42792d18a8ec8a13141a57e8c7029157/99eece1b9d16fdfaba04cf19b58f8c5495ee7bd9.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=c3b657a70d3387449cc52f74610ed937/27d9bc3eb13533fab7199ad9a9d3fd1f40345b9e.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6a39361663d9f2d3201124e799ee8a53/dbdce71190ef76c6b622f2dc9c16fdfaae516751.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=5cf37df3960a304e5222a0f2e1caa7c3/390928381f30e924197595c64d086e061c95f771.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=3bf3bb83eaf81a4c2632ecc1e72b6029/8f3433fa828ba61e9f13c1d84034970a314e594c.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6b6a371663d9f2d3201124e799ed8a53/dbdce71190ef76c6b771f3dc9c16fdfaae5167a2.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=6e97343091529822053339cbe7cb7b3b/77550923dd54564ebaececceb2de9c82d0584fb8.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=7fab4f4c4610b912bfc1f6f6f3fcfcb5/b412632762d0f70323359d2b09fa513d2797c547.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=de0bf3dc9c16fdfad86cc6e6848d8cea/9a0e4bfbfbedab647aab4537f636afc378311e68.jpg", "http://imgsrc.baidu.com/forum/w%3D580/sign=d2aece19b58f8c54e3d3c5270a2b2dee/3d4e78f0f736afc304ce3792b219ebc4b6451203.jpg", }; } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/DemoApplication.java ================================================ package com.liulishuo.filedownloader.demo; import android.app.Application; import android.content.Context; import android.util.Log; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.connection.FileDownloadUrlConnection; import com.liulishuo.filedownloader.util.FileDownloadLog; import com.liulishuo.filedownloader.util.FileDownloadUtils; import cn.dreamtobe.threaddebugger.IThreadDebugger; import cn.dreamtobe.threaddebugger.ThreadDebugger; import cn.dreamtobe.threaddebugger.ThreadDebuggers; /** * Created by Jacksgong on 12/17/15. */ public class DemoApplication extends Application { public static Context CONTEXT; private final static String TAG = "FileDownloadApplication"; @Override public void onCreate() { super.onCreate(); // for demo. CONTEXT = this; // just for open the log in this demo project. FileDownloadLog.NEED_LOG = BuildConfig.DOWNLOAD_NEED_LOG; /** * just for cache Application's Context, and ':filedownloader' progress will NOT be launched * by below code, so please do not worry about performance. * @see FileDownloader#init(Context) */ FileDownloader.setupOnApplicationOnCreate(this) .connectionCreator(new FileDownloadUrlConnection .Creator(new FileDownloadUrlConnection.Configuration() .connectTimeout(15_000) // set connection timeout. .readTimeout(15_000) // set read timeout. )) .commit(); // below codes just for monitoring thread pools in the FileDownloader: IThreadDebugger debugger = ThreadDebugger.install( ThreadDebuggers.create() /** The ThreadDebugger with known thread Categories **/ // add Thread Category .add("OkHttp").add("okio").add("Binder") .add(FileDownloadUtils.getThreadPoolName("Network"), "Network") .add(FileDownloadUtils.getThreadPoolName("Flow"), "FlowSingle") .add(FileDownloadUtils.getThreadPoolName("EventPool"), "Event") .add(FileDownloadUtils.getThreadPoolName("LauncherTask"), "LauncherTask") .add(FileDownloadUtils.getThreadPoolName("ConnectionBlock"), "Connection") .add(FileDownloadUtils.getThreadPoolName("RemitHandoverToDB"), "RemitHandoverToDB") .add(FileDownloadUtils.getThreadPoolName("BlockCompleted"), "BlockCompleted"), 2000, /** The frequent of Updating Thread Activity information **/ new ThreadDebugger.ThreadChangedCallback() { /** * The threads changed callback **/ @Override public void onChanged(IThreadDebugger debugger) { // callback this method when the threads in this application has changed. Log.d(TAG, debugger.drawUpEachThreadInfoDiff()); Log.d(TAG, debugger.drawUpEachThreadSizeDiff()); Log.d(TAG, debugger.drawUpEachThreadSize()); } }); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/GlobalMonitor.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. */ package com.liulishuo.filedownloader.demo; import android.util.Log; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadListener; import com.liulishuo.filedownloader.FileDownloadMonitor; /** * Created by Jacksgong on 1/19/16. */ public class GlobalMonitor implements FileDownloadMonitor.IMonitor { private volatile int markStart; private volatile int markOver; private final static class HolderClass { private final static GlobalMonitor INSTANCE = new GlobalMonitor(); } public static GlobalMonitor getImpl() { return HolderClass.INSTANCE; } private final static String TAG = "GlobalMonitor"; @Override public void onRequestStart(int count, boolean serial, FileDownloadListener lis) { markStart = 0; markOver = 0; Log.d(TAG, String.format("on request start %d %B", count, serial)); } @Override public void onRequestStart(BaseDownloadTask task) { } @Override public void onTaskBegin(BaseDownloadTask task) { markStart++; } @Override public void onTaskStarted(BaseDownloadTask task) { } @Override public void onTaskOver(BaseDownloadTask task) { markOver++; } public int getMarkStart() { return markStart; } public int getMarkOver() { return markOver; } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/HybridTestActivity.java ================================================ package com.liulishuo.filedownloader.demo; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.text.Html; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadListener; import com.liulishuo.filedownloader.FileDownloadQueueSet; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; import java.util.ArrayList; import java.util.List; /** * Created by Jacksgong on 12/19/15. */ public class HybridTestActivity extends AppCompatActivity { private final String TAG = "Demo.HybridActivity"; private Handler uiHandler; private final int WHAT_NEED_AUTO_2_BOTTOM = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_hybrid_test); uiHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == WHAT_NEED_AUTO_2_BOTTOM) { needAuto2Bottom = true; } return false; } }); assignViews(); scrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { uiHandler.removeMessages(WHAT_NEED_AUTO_2_BOTTOM); needAuto2Bottom = false; } if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { uiHandler.removeMessages(WHAT_NEED_AUTO_2_BOTTOM); uiHandler.sendEmptyMessageDelayed(WHAT_NEED_AUTO_2_BOTTOM, 1000); } return false; } }); } private boolean needAuto2Bottom = true; public void onClickDel(final View view) { File file = new File(FileDownloadUtils.getDefaultSaveRootPath()); if (!file.exists()) { Log.w(TAG, String.format("check file files not exists %s", file.getAbsolutePath())); return; } if (!file.isDirectory()) { Log.w(TAG, String.format("check file files not directory %s", file.getAbsolutePath())); return; } File[] files = file.listFiles(); if (files == null) { updateDisplay(getString(R.string.del_file_error_empty)); return; } for (File file1 : files) { file1.delete(); updateDisplay(getString(R.string.hybrid_test_deleted_file, file1.getName())); } } private int totalCounts = 0; private int finalCounts = 0; // =================================================== demo area ======================================================== /** * Start single download task *

* 启动单任务下载 * * @param view */ public void onClickStartSingleDownload(final View view) { updateDisplay(getString(R.string.hybrid_test_start_single_task, Constant.BIG_FILE_URLS[2])); totalCounts++; FileDownloader.getImpl().create(Constant.BIG_FILE_URLS[2]) .setListener(createListener()) .setTag(1) .start(); } /** * Start multiple download tasks parallel *

* 启动并行多任务下载 * * @param view */ public void onClickMultiParallel(final View view) { updateDisplay(getString(R.string.hybrid_test_start_multiple_tasks_parallel, Constant.URLS.length)); // 以相同的listener作为target,将不同的下载任务绑定起来 final FileDownloadListener parallelTarget = createListener(); final List taskList = new ArrayList<>(); int i = 0; for (String url : Constant.URLS) { taskList.add(FileDownloader.getImpl().create(url) .setTag(++i)); } totalCounts += taskList.size(); new FileDownloadQueueSet(parallelTarget) .setCallbackProgressTimes(1) .downloadTogether(taskList) .start(); } /** * Start multiple download tasks serial *

* 启动串行多任务下载 * * @param view */ public void onClickMultiSerial(final View view) { updateDisplay(getString(R.string.hybrid_test_start_multiple_tasks_serial, Constant.URLS.length)); // 以相同的listener作为target,将不同的下载任务绑定起来 final List taskList = new ArrayList<>(); final FileDownloadListener serialTarget = createListener(); int i = 0; for (String url : Constant.URLS) { taskList.add(FileDownloader.getImpl().create(url) .setTag(++i)); } totalCounts += taskList.size(); new FileDownloadQueueSet(serialTarget) .setCallbackProgressTimes(1) .downloadSequentially(taskList) .start(); } private FileDownloadListener createListener() { return new FileDownloadListener() { @Override protected boolean isInvalid() { return isFinishing(); } @Override protected void pending(final BaseDownloadTask task, final int soFarBytes, final int totalBytes) { updateDisplay(String.format("[pending] id[%d] %d/%d", task.getId(), soFarBytes, totalBytes)); } @Override protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) { super.connected(task, etag, isContinue, soFarBytes, totalBytes); updateDisplay(String.format("[connected] id[%d] %s %B %d/%d", task.getId(), etag, isContinue, soFarBytes, totalBytes)); } @Override protected void progress(final BaseDownloadTask task, final int soFarBytes, final int totalBytes) { updateDisplay(String.format("[progress] id[%d] %d/%d", task.getId(), soFarBytes, totalBytes)); } @Override protected void blockComplete(final BaseDownloadTask task) { downloadMsgTv.post(new Runnable() { @Override public void run() { updateDisplay(String.format("[blockComplete] id[%d]", task.getId())); } }); } @Override protected void retry(BaseDownloadTask task, Throwable ex, int retryingTimes, int soFarBytes) { super.retry(task, ex, retryingTimes, soFarBytes); updateDisplay(String.format("[retry] id[%d] %s %d %d", task.getId(), ex, retryingTimes, soFarBytes)); } @Override protected void completed(BaseDownloadTask task) { finalCounts++; updateDisplay(String.format("[completed] id[%d] oldFile[%B]", task.getId(), task.isReusedOldFile())); updateDisplay(String.format("---------------------------------- %d", (Integer) task.getTag())); } @Override protected void paused(final BaseDownloadTask task, final int soFarBytes, final int totalBytes) { finalCounts++; updateDisplay(String.format("[paused] id[%d] %d/%d", task.getId(), soFarBytes, totalBytes)); updateDisplay(String.format("############################## %d", (Integer) task.getTag())); } @Override protected void error(BaseDownloadTask task, Throwable e) { finalCounts++; updateDisplay(Html.fromHtml(String.format("[error] id[%d] %s %s", task.getId(), e, FileDownloadUtils.getStack(e.getStackTrace(), false)))); updateDisplay(String.format("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! %d", (Integer) task.getTag())); } @Override protected void warn(BaseDownloadTask task) { finalCounts++; updateDisplay(String.format("[warn] id[%d]", task.getId())); updateDisplay(String.format("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ %d", (Integer) task.getTag())); } }; } // -------------------------------------------------------- something just for display ------------------------------------------------------ private void updateDisplay(final CharSequence msg) { if (downloadMsgTv.getLineCount() > 2500) { downloadMsgTv.setText(""); } downloadMsgTv.append(String.format("\n %s", msg)); tipMsgTv.setText(String.format("%d/%d", finalCounts, totalCounts)); if (needAuto2Bottom) { scrollView.post(scroll2Bottom); } } private Runnable scroll2Bottom = new Runnable() { @Override public void run() { if (scrollView != null) { scrollView.fullScroll(View.FOCUS_DOWN); } } }; @Override protected void onDestroy() { super.onDestroy(); FileDownloader.getImpl().pauseAll(); } private LinearLayout topGroup; private ScrollView scrollView; private TextView downloadMsgTv; private TextView tipMsgTv; private void assignViews() { topGroup = (LinearLayout) findViewById(R.id.top_group); scrollView = (ScrollView) findViewById(R.id.scrollView); downloadMsgTv = (TextView) findViewById(R.id.download_msg_tv); tipMsgTv = (TextView) findViewById(R.id.tip_msg_tv); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/MainActivity.java ================================================ package com.liulishuo.filedownloader.demo; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import com.liulishuo.filedownloader.FileDownloadMonitor; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.demo.performance.PerformanceTestActivity; /** * Created by Jacksgong on 12/17/15. */ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 这是只是为了全局监控。如果你有需求需要全局监控(比如用于打点/统计)可以使用这个方式,如果没有类似需求就不需要 // 如果你有这个需求,实现FileDownloadMonitor.IMonitor接口,也使用FileDownloadMonitor.setGlobalMonitor // 注册进去即可 // You do not have to add below code to your project only if you need monitor the global // FileDownloader Engine for statistic or others // If you have such requirement, just implement FileDownloadMonitor.IMonitor, and register it // use FileDownloadDownloader.setGlobalMonitor the same as below code. FileDownloadMonitor.setGlobalMonitor(GlobalMonitor.getImpl()); } public void onClickMultitask(final View view) { startActivity(new Intent(this, MultitaskTestActivity.class)); } public void onClickSingle(final View view) { startActivity(new Intent(this, SingleTaskTestActivity.class)); } public void onClickHybridTest(final View view) { startActivity(new Intent(this, HybridTestActivity.class)); } public void onClickTasksManager(final View view) { startActivity(new Intent(this, TasksManagerDemoActivity.class)); } public void onClickPerformance(final View view) { startActivity(new Intent(this, PerformanceTestActivity.class)); } public void onClickNotification(final View view){ startActivity(new Intent(this, NotificationSampleActivity.class)); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_github: openGitHub(); return true; default: return super.onOptionsItemSelected(item); } } private void openGitHub() { Uri uri = Uri.parse(getString(R.string.app_github_url)); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } @Override protected void onDestroy() { super.onDestroy(); // unbind and stop service manually if idle FileDownloader.getImpl().unBindServiceIfIdle(); FileDownloadMonitor.releaseGlobalMonitor(); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/MultitaskTestActivity.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. */ package com.liulishuo.filedownloader.demo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ProgressBar; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadListener; import com.liulishuo.filedownloader.FileDownloadQueueSet; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; import java.util.ArrayList; import java.util.List; /** * Created by Jacksgong on 12/25/15. */ public class MultitaskTestActivity extends AppCompatActivity { private final static String TAG = "MultitaskTestActivity"; @Override protected void onDestroy() { super.onDestroy(); pause(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mutitask_test); assignViews(); resetDisplayData(); actionBtn.setTag(true); actionBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final boolean toStart = (boolean) actionBtn.getTag(); if (toStart) { if (start()) { actionBtn.setText(R.string.pause); actionBtn.setTag(false); } } else { actionBtn.setText(R.string.start); pause(); actionBtn.setTag(true); } } }); taskCountSb.setMax(Constant.URLS.length); taskCountSb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { taskCountTv.setText(String.valueOf(progress)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); deleteAllFileBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int count = 0; File file = new File(FileDownloadUtils.getDefaultSaveRootPath()); do { if (!file.exists()) { break; } if (!file.isDirectory()) { break; } File[] files = file.listFiles(); if (files == null) { break; } for (File file1 : files) { count++; file1.delete(); } } while (false); Toast.makeText(MultitaskTestActivity.this, String.format("Complete delete %d files", count), Toast.LENGTH_LONG).show(); } }); avoidMissFrameCb.setChecked(FileDownloader.isEnabledAvoidDropFrame()); avoidMissFrameCb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { FileDownloader.enableAvoidDropFrame(); } else { FileDownloader.disableAvoidDropFrame(); } } }); } private void resetDisplayData() { pendingPb.setProgress(0); connectedPb.setProgress(0); progressPb.setProgress(0); retryPb.setProgress(0); errorPb.setProgress(0); pausedPb.setProgress(0); completedReusedPb.setProgress(0); completedDownloadingPb.setProgress(0); warnPb.setProgress(0); overTaskPb.setProgress(0); pendingTv.setText(getString(R.string.multitask_test_pending, 0)); connectedTv.setText(getString(R.string.multitask_test_connected, 0)); progressTv.setText(getString(R.string.multitask_test_progress, 0)); retryTv.setText(getString(R.string.multitask_test_retry, 0)); errorTv.setText(getString(R.string.multitask_test_error, 0)); pausedTv.setText(getString(R.string.multitask_test_paused, 0)); completedReusedTv.setText(getString(R.string.multitask_test_completed_reused, 0)); completedDownloadingTv.setText(getString(R.string.multitask_test_completed_downloading, 0)); warnTv.setText(getString(R.string.multitask_test_warn, 0)); pendingInfoTv.setText(""); connectedInfoTv.setText(""); retryInfoTv.setText(""); progressInfoTv.setText(""); errorInfoTv.setText(""); pausedInfoTv.setText(""); completedReusedInfoTv.setText(""); completedDownloadingInfoTv.setText(""); warnInfoTv.setText(""); } private long start = 0; private boolean start() { final int count = Integer.valueOf(taskCountTv.getText().toString()); if (count <= 0) { return false; } taskCountSb.setEnabled(false); pendingPb.setMax(count); connectedPb.setMax(count); progressPb.setMax(count); retryPb.setMax(count); errorPb.setMax(count); pausedPb.setMax(count); completedReusedPb.setMax(count); completedDownloadingPb.setMax(count); warnPb.setMax(count); overTaskPb.setMax(count); resetDisplayData(); // 需要时再显示 retryInfoTv.setVisibility(View.GONE); retryPb.setVisibility(View.GONE); retryTv.setVisibility(View.GONE); isStopTimer = false; timeConsumeTv.setTag(0); goTimeCount(); start = System.currentTimeMillis(); // =================== How to Download tasks: ============================= downloadListener = createLis(); // The first way-----------------------------: // for (int i = 0; i < count; i++) { // final String url = Constant.URLS[i]; // FileDownloader.getImpl().create(url) // .setListener(downloadListener) // .setAutoRetryTimes(1) // .setTag(i + 1) // .setCallbackProgressTimes(0) // .asInQueueTask() // .enqueue(); // } // FileDownloader.getImpl().start(downloadListener, serialRbtn.isChecked()); // The second way----------------------------: final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(downloadListener); final List tasks = new ArrayList<>(); for (int i = 0; i < count; i++) { tasks.add(FileDownloader.getImpl().create(Constant.URLS[i]).setTag(i + 1)); } queueSet.disableCallbackProgressTimes(); // do not want each task's download progress's callback, // we just consider which task will completed. // auto retry 1 time if download fail queueSet.setAutoRetryTimes(1); if (serialRbtn.isChecked()) { // start download in serial order queueSet.downloadSequentially(tasks); // if your tasks are not a list, invoke such following will more readable: // queueSet.downloadSequentially( // FileDownloader.getImpl().create(url).setPath(...), // FileDownloader.getImpl().create(url).addHeader(...,...), // FileDownloader.getImpl().create(url).setPath(...) // ); } else { // start parallel download queueSet.downloadTogether(tasks); // if your tasks are not a list, invoke such following will more readable: // queueSet.downloadTogether( // FileDownloader.getImpl().create(url).setPath(...), // FileDownloader.getImpl().create(url).setPath(...), // FileDownloader.getImpl().create(url).setSyncCallback(true) // ); } queueSet.start(); return true; } private FileDownloadListener downloadListener; private void pause() { FileDownloader.getImpl().pause(downloadListener); stopTimeCount(); taskCountSb.setEnabled(true); } private void stopTimeCount() { if (isFinishing()) { return; } isStopTimer = true; timeConsumeTv.getHandler().removeCallbacks(timeCountRunnable); final long consume = System.currentTimeMillis() - start; if (timeConsumeTv != null) { timeConsumeTv.setText(String.valueOf(consume / 1000f)); } } private FileDownloadListener createLis() { return new FileDownloadListener() { @Override protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { // 之所以加这句判断,是因为有些异步任务在pause以后,会持续回调pause回来,而有些任务在pause之前已经完成, // 但是通知消息还在线程池中还未回调回来,这里可以优化 // 后面所有在回调中加这句都是这个原因 if (task.getListener() != downloadListener) { return; } pendingPb.setProgress(pendingPb.getProgress() + 1); pendingTv.setText(getString(R.string.multitask_test_pending, pendingPb.getProgress())); pendingInfoTv.append((int) task.getTag() + " | "); } @Override protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) { super.connected(task, etag, isContinue, soFarBytes, totalBytes); if (task.getListener() != downloadListener) { return; } connectedPb.setProgress(connectedPb.getProgress() + 1); connectedTv.setText(getString(R.string.multitask_test_connected, connectedPb.getProgress())); connectedInfoTv.append((int) task.getTag() + " | "); } @Override protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { if (task.getListener() != downloadListener) { return; } // progressPb.setProgress(progressPb.getProgress() + 1); // progressTv.setText("progress: " + progressPb.getProgress()); // progressInfoTv.append((int)task.getTag() + " | "); } @Override protected void blockComplete(BaseDownloadTask task) { if (task.getListener() != downloadListener) { return; } } @Override protected void retry(BaseDownloadTask task, Throwable ex, int retryingTimes, int soFarBytes) { super.retry(task, ex, retryingTimes, soFarBytes); if (task.getListener() != downloadListener) { return; } retryInfoTv.setVisibility(View.VISIBLE); retryPb.setVisibility(View.VISIBLE); retryTv.setVisibility(View.VISIBLE); retryPb.setProgress(retryPb.getProgress() + 1); retryTv.setText(getString(R.string.multitask_test_retry, retryPb.getProgress())); retryInfoTv.append((int)task.getTag() + " | "); } @Override protected void completed(BaseDownloadTask task) { if (task.getListener() != downloadListener) { return; } if (task.isReusedOldFile()) { completedReusedPb.setProgress(completedReusedPb.getProgress() + 1); completedReusedTv.setText(getString(R.string.multitask_test_completed_reused, completedReusedPb.getProgress())); completedReusedInfoTv.append((int) task.getTag() + " | "); } else { completedDownloadingPb.setProgress(completedDownloadingPb.getProgress() + 1); completedDownloadingTv. setText(getString(R.string.multitask_test_completed_downloading, completedDownloadingPb.getProgress())); completedDownloadingInfoTv.append((int) task.getTag() + " | "); } overTaskPb.setProgress(overTaskPb.getProgress() + 1); checkEndAll(); } @Override protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { if (task.getListener() != downloadListener) { return; } pausedPb.setProgress(pausedPb.getProgress() + 1); pausedTv.setText(getString(R.string.multitask_test_paused, pausedPb.getProgress())); pausedInfoTv.append((int) task.getTag() + " | "); overTaskPb.setProgress(overTaskPb.getProgress() + 1); } @Override protected void error(BaseDownloadTask task, Throwable e) { if (task.getListener() != downloadListener) { return; } errorPb.setProgress(errorPb.getProgress() + 1); errorTv.setText(getString(R.string.multitask_test_error, errorPb.getProgress())); errorInfoTv.append((int) task.getTag() + " | "); overTaskPb.setProgress(overTaskPb.getProgress() + 1); checkEndAll(); } @Override protected void warn(BaseDownloadTask task) { if (task.getListener() != downloadListener) { return; } warnPb.setProgress(warnPb.getProgress() + 1); warnTv.setText(getString(R.string.multitask_test_warn, warnPb.getProgress())); warnInfoTv.append((int) task.getTag() + " | "); overTaskPb.setProgress(overTaskPb.getProgress() + 1); checkEndAll(); } }; } private void checkEndAll() { final boolean isEndAll = overTaskPb.getProgress() >= Integer.valueOf(taskCountTv.getText().toString()); if (isEndAll) { Log.d(TAG, String.format("start[%d] over[%d]", GlobalMonitor.getImpl().getMarkStart(), GlobalMonitor.getImpl().getMarkOver())); stopTimeCount(); actionBtn.setTag(true); actionBtn.setText("Start"); taskCountSb.setEnabled(true); } } private boolean isStopTimer = true; private void goTimeCount() { if (isFinishing()) { return; } final int time = (int) timeConsumeTv.getTag(); timeConsumeTv.setText(String.valueOf(time)); timeConsumeTv.getHandler().postDelayed(timeCountRunnable, 1000); } private Runnable timeCountRunnable = new Runnable() { @Override public void run() { if (isStopTimer) { return; } timeConsumeTv.setTag((int)timeConsumeTv.getTag() + 1); goTimeCount(); } }; private SeekBar taskCountSb; private TextView taskCountTv; private TextView timeConsumeTv; private RadioGroup wayRgp; private RadioButton serialRbtn; private RadioButton parallelRbtn; private CheckBox avoidMissFrameCb; private ProgressBar overTaskPb; private Button actionBtn; private TextView pendingTv; private TextView pendingInfoTv; private ProgressBar pendingPb; private TextView connectedTv; private TextView connectedInfoTv; private ProgressBar connectedPb; private TextView progressTv; private TextView progressInfoTv; private ProgressBar progressPb; private TextView retryTv; private TextView retryInfoTv; private ProgressBar retryPb; private TextView errorTv; private TextView errorInfoTv; private ProgressBar errorPb; private TextView pausedTv; private TextView pausedInfoTv; private ProgressBar pausedPb; private TextView completedReusedTv; private TextView completedReusedInfoTv; private ProgressBar completedReusedPb; private TextView completedDownloadingTv; private TextView completedDownloadingInfoTv; private ProgressBar completedDownloadingPb; private TextView warnTv; private TextView warnInfoTv; private ProgressBar warnPb; private Button deleteAllFileBtn; private void assignViews() { taskCountSb = (SeekBar) findViewById(R.id.task_count_sb); taskCountTv = (TextView) findViewById(R.id.task_count_tv); timeConsumeTv = (TextView) findViewById(R.id.time_consume_tv); wayRgp = (RadioGroup) findViewById(R.id.way_rgp); serialRbtn = (RadioButton) findViewById(R.id.serial_rbtn); parallelRbtn = (RadioButton) findViewById(R.id.parallel_rbtn); avoidMissFrameCb = (CheckBox) findViewById(R.id.avoid_miss_frame_cb); overTaskPb = (ProgressBar) findViewById(R.id.over_task_pb); actionBtn = (Button) findViewById(R.id.action_btn); pendingTv = (TextView) findViewById(R.id.pending_tv); pendingInfoTv = (TextView) findViewById(R.id.pending_info_tv); pendingPb = (ProgressBar) findViewById(R.id.pending_pb); connectedTv = (TextView) findViewById(R.id.connected_tv); connectedInfoTv = (TextView) findViewById(R.id.connected_info_tv); connectedPb = (ProgressBar) findViewById(R.id.connected_pb); progressTv = (TextView) findViewById(R.id.progress_tv); progressInfoTv = (TextView) findViewById(R.id.progress_info_tv); progressPb = (ProgressBar) findViewById(R.id.progress_pb); retryTv = (TextView) findViewById(R.id.retry_tv); retryInfoTv = (TextView) findViewById(R.id.retry_info_tv); retryPb = (ProgressBar) findViewById(R.id.retry_pb); errorTv = (TextView) findViewById(R.id.error_tv); errorInfoTv = (TextView) findViewById(R.id.error_info_tv); errorPb = (ProgressBar) findViewById(R.id.error_pb); pausedTv = (TextView) findViewById(R.id.paused_tv); pausedInfoTv = (TextView) findViewById(R.id.paused_info_tv); pausedPb = (ProgressBar) findViewById(R.id.paused_pb); completedReusedTv = (TextView) findViewById(R.id.completed_with_old_tv); completedReusedInfoTv = (TextView) findViewById(R.id.completed_with_old_info_tv); completedReusedPb = (ProgressBar) findViewById(R.id.completed_with_old_pb); completedDownloadingTv = (TextView) findViewById(R.id.completed_tv); completedDownloadingInfoTv = (TextView) findViewById(R.id.completed_info_tv); completedDownloadingPb = (ProgressBar) findViewById(R.id.completed_pb); warnTv = (TextView) findViewById(R.id.warn_tv); warnInfoTv = (TextView) findViewById(R.id.warn_info_tv); warnPb = (ProgressBar) findViewById(R.id.warn_pb); deleteAllFileBtn = (Button) findViewById(R.id.delete_all_file_btn); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/NotificationSampleActivity.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. */ package com.liulishuo.filedownloader.demo; import android.app.Notification; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.CheckBox; import android.widget.ProgressBar; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadQueueSet; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.model.FileDownloadStatus; import com.liulishuo.filedownloader.notification.BaseNotificationItem; import com.liulishuo.filedownloader.notification.FileDownloadNotificationHelper; import com.liulishuo.filedownloader.notification.FileDownloadNotificationListener; import com.liulishuo.filedownloader.util.FileDownloadHelper; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * Created by Jacksgong on 2/2/16. */ public class NotificationSampleActivity extends AppCompatActivity { private final FileDownloadNotificationHelper notificationHelper = new FileDownloadNotificationHelper<>(); private NotificationListener listener; private final String savePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + "notification"; private final String url = Constant.LIULISHUO_APK_URL; private final String channelId = "filedownloader_channel"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_notification_sample); assignViews(); listener = new NotificationListener(new WeakReference<>(this), channelId); Utils.createNotificationChannel(channelId, "Filedownloader", getApplicationContext()); } public void onClickStart(final View view) { startDownload.setEnabled(false); final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(listener); final List tasks = new ArrayList<>(); final int taskCount = 5; for (int i = 0; i < taskCount; i++) { tasks.add(FileDownloader.getImpl() .create(Constant.URLS[i]) .setTag(i + 1) .setPath(savePath, true) ); } queueSet.downloadTogether(tasks) .addTaskFinishListener(new BaseDownloadTask.FinishListener() { final AtomicInteger counter = new AtomicInteger(); @Override public void over(BaseDownloadTask task) { final int finishCount = counter.addAndGet(1); if (finishCount == taskCount) startDownload.post(new Runnable() { @Override public void run() { startDownload.setEnabled(true); progressBar.setIndeterminate(false); progressBar.setProgress(1); progressBar.setMax(1); } }); } }) .start(); progressBar.setIndeterminate(true); } public void onClickPause(final View view) { FileDownloader.getImpl().pause(listener); } @SuppressWarnings("ResultOfMethodCallIgnored") public void onClickDelete(final View view) { FileDownloader.getImpl().pause(listener); new Thread(new Runnable() { @Override public void run() { final File file = new File(savePath); if (file.isFile()) file.delete(); if (file.exists()) { final File[] files = file.listFiles(); if (files == null) return; for (File f : files) { f.delete(); } file.delete(); } } }).start(); } private static class NotificationListener extends FileDownloadNotificationListener { private final String channelId; private final WeakReference wActivity; public NotificationListener( WeakReference wActivity, String channelId ) { super(wActivity.get().notificationHelper); this.wActivity = wActivity; this.channelId = channelId; } @Override protected BaseNotificationItem create(BaseDownloadTask task) { return new NotificationItem( task.getId(), "Task-" + task.getId(), "", channelId ); } @Override public void addNotificationItem(BaseDownloadTask task) { super.addNotificationItem(task); if (wActivity.get() != null) { wActivity.get().showNotificationCb.setEnabled(false); } } @Override public void destroyNotification(BaseDownloadTask task) { super.destroyNotification(task); if (wActivity.get() != null) { wActivity.get().showNotificationCb.setEnabled(true); } } @Override protected boolean interceptCancel(BaseDownloadTask task, BaseNotificationItem n) { // in this demo, I don't want to cancel the notification, just show for the test // so return true return true; } @Override protected boolean disableNotification(BaseDownloadTask task) { if (wActivity.get() != null) { return !wActivity.get().showNotificationCb.isChecked(); } return super.disableNotification(task); } } public static class NotificationItem extends BaseNotificationItem { private final NotificationCompat.Builder builder; private NotificationItem(int id, String title, String desc, String channelId) { super(id, title, desc); final Intent[] intents = new Intent[2]; intents[0] = Intent.makeMainActivity( new ComponentName(DemoApplication.CONTEXT, MainActivity.class)); intents[1] = new Intent(DemoApplication.CONTEXT, NotificationSampleActivity.class); final PendingIntent pendingIntent = PendingIntent.getActivities( DemoApplication.CONTEXT, 0, intents, PendingIntent.FLAG_UPDATE_CURRENT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder = new NotificationCompat.Builder( FileDownloadHelper.getAppContext(), channelId); } else { //noinspection deprecation builder = new NotificationCompat.Builder(FileDownloadHelper.getAppContext()) .setDefaults(Notification.DEFAULT_LIGHTS) .setPriority(NotificationCompat.PRIORITY_MIN); } builder.setContentTitle(getTitle()) .setContentText(desc) .setContentIntent(pendingIntent) .setSmallIcon(R.mipmap.ic_launcher); } @Override public void show(boolean statusChanged, int status, boolean isShowProgress) { String desc = ""; switch (status) { case FileDownloadStatus.pending: desc += " pending"; builder.setProgress(getTotal(), getSofar(), true); break; case FileDownloadStatus.started: desc += " started"; builder.setProgress(getTotal(), getSofar(), true); break; case FileDownloadStatus.progress: desc += " progress"; builder.setProgress(getTotal(), getSofar(), getTotal() <= 0); break; case FileDownloadStatus.retry: desc += " retry"; builder.setProgress(getTotal(), getSofar(), true); break; case FileDownloadStatus.error: desc += " error"; builder.setProgress(getTotal(), getSofar(), false); break; case FileDownloadStatus.paused: desc += " paused"; builder.setProgress(getTotal(), getSofar(), false); break; case FileDownloadStatus.completed: desc += " completed"; builder.setProgress(getTotal(), getSofar(), false); break; case FileDownloadStatus.warn: desc += " warn"; builder.setProgress(0, 0, true); break; } builder.setContentTitle(getTitle()).setContentText(desc); getManager().notify(getId(), builder.build()); } } @Override protected void onDestroy() { super.onDestroy(); notificationHelper.clear(); Utils.deleteNotificationChannel(channelId, getApplicationContext()); } private CheckBox showNotificationCb; private ProgressBar progressBar; private View startDownload; private void assignViews() { showNotificationCb = findViewById(R.id.show_notification_cb); progressBar = findViewById(R.id.progressBar); startDownload = findViewById(R.id.view_start); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/SingleTaskTestActivity.java ================================================ package com.liulishuo.filedownloader.demo; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadSampleListener; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; import java.lang.ref.WeakReference; /** * Created by Jacksgong on 12/21/15. */ public class SingleTaskTestActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single); llsApkFilePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + "tmpdir1" + File.separator + Constant.LIULISHUO_CONTENT_DISPOSITION_FILENAME; llsApkDir = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + "tmpdir1"; normalTaskFilePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + "tmp2"; chunkedFilePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + "chunked_data_tmp1"; assignViews(); initFilePathEqualDirAndFileName(); initNormalDataAction(); initChunkTransferEncodingDataAction(); } // test for the file path = dir path / content-disposition-filename // task1: set {@code llsApkFilePath} // task2: set {@code llsApkDir} private void initFilePathEqualDirAndFileName() { // task 1 startBtn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downloadId1 = createDownloadTask(1).start(); } }); pauseBtn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileDownloader.getImpl().pause(downloadId1); } }); deleteBtn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new File(llsApkFilePath).delete(); new File(FileDownloadUtils.getTempPath(llsApkFilePath)).delete(); } }); // task 2 startBtn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downloadId2 = createDownloadTask(2).start(); } }); pauseBtn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileDownloader.getImpl().pause(downloadId2); } }); deleteBtn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new File(llsApkDir).delete(); } }); } // test for normal task. private void initNormalDataAction() { // task 3 startBtn3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downloadId3 = createDownloadTask(3).start(); } }); pauseBtn3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileDownloader.getImpl().pause(downloadId3); } }); deleteBtn3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new File(normalTaskFilePath).delete(); new File(FileDownloadUtils.getTempPath(normalTaskFilePath)).delete(); } }); } // test for chunked downloading. private void initChunkTransferEncodingDataAction() { // task 4 startBtn4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downloadId4 = createDownloadTask(4).start(); } }); pauseBtn4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileDownloader.getImpl().pause(downloadId4); } }); deleteBtn4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new File(chunkedFilePath).delete(); new File(FileDownloadUtils.getTempPath(chunkedFilePath)).delete(); } }); } @Override protected void onDestroy() { super.onDestroy(); FileDownloader.getImpl().pause(downloadId1); FileDownloader.getImpl().pause(downloadId2); } private BaseDownloadTask createDownloadTask(final int position) { final ViewHolder tag; final String url; boolean isDir = false; String path; switch (position) { case 1: url = Constant.LIULISHUO_APK_URL; tag = new ViewHolder(new WeakReference<>(this), progressBar1, null, speedTv1, 1); path = llsApkFilePath; tag.setFilenameTv(filenameTv1); break; case 2: url = Constant.LIULISHUO_APK_URL; tag = new ViewHolder(new WeakReference<>(this), progressBar2, null, speedTv2, 2); path = llsApkDir; isDir = true; tag.setFilenameTv(filenameTv2); break; case 3: url = Constant.BIG_FILE_URLS[2]; tag = new ViewHolder(new WeakReference<>(this), progressBar3, null, speedTv3, 3); path = normalTaskFilePath; break; default: url = Constant.CHUNKED_TRANSFER_ENCODING_DATA_URLS[0]; tag = new ViewHolder(new WeakReference<>(this), progressBar4, detailTv4, speedTv4, 4); path = chunkedFilePath; break; } return FileDownloader.getImpl().create(url) .setPath(path, isDir) .setCallbackProgressTimes(300) .setMinIntervalUpdateSpeed(400) .setTag(tag) .setListener(new FileDownloadSampleListener() { @Override protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { super.pending(task, soFarBytes, totalBytes); ((ViewHolder) task.getTag()).updatePending(task); } @Override protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { super.progress(task, soFarBytes, totalBytes); ((ViewHolder) task.getTag()).updateProgress(soFarBytes, totalBytes, task.getSpeed()); } @Override protected void error(BaseDownloadTask task, Throwable e) { super.error(task, e); ((ViewHolder) task.getTag()).updateError(e, task.getSpeed()); } @Override protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) { super.connected(task, etag, isContinue, soFarBytes, totalBytes); ((ViewHolder) task.getTag()).updateConnected(etag, task.getFilename()); } @Override protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { super.paused(task, soFarBytes, totalBytes); ((ViewHolder) task.getTag()).updatePaused(task.getSpeed()); } @Override protected void completed(BaseDownloadTask task) { super.completed(task); ((ViewHolder) task.getTag()).updateCompleted(task); } @Override protected void warn(BaseDownloadTask task) { super.warn(task); ((ViewHolder) task.getTag()).updateWarn(); } }); } private static class ViewHolder { private ProgressBar pb; private TextView detailTv; private TextView speedTv; private int position; private TextView filenameTv; private WeakReference weakReferenceContext; public ViewHolder(WeakReference weakReferenceContext, final ProgressBar pb, final TextView detailTv, final TextView speedTv, final int position) { this.weakReferenceContext = weakReferenceContext; this.pb = pb; this.detailTv = detailTv; this.position = position; this.speedTv = speedTv; } public void setFilenameTv(TextView filenameTv) { this.filenameTv = filenameTv; } private void updateSpeed(int speed) { speedTv.setText(String.format("%dKB/s", speed)); } public void updateProgress(final int sofar, final int total, final int speed) { if (total == -1) { // chunked transfer encoding data pb.setIndeterminate(true); } else { pb.setMax(total); pb.setProgress(sofar); } updateSpeed(speed); if (detailTv != null) { detailTv.setText(String.format("sofar: %d total: %d", sofar, total)); } } public void updatePending(BaseDownloadTask task) { if (filenameTv != null) { filenameTv.setText(task.getFilename()); } } public void updatePaused(final int speed) { toast(String.format("paused %d", position)); updateSpeed(speed); pb.setIndeterminate(false); } public void updateConnected(String etag, String filename) { if (filenameTv != null) { filenameTv.setText(filename); } } public void updateWarn() { toast(String.format("warn %d", position)); pb.setIndeterminate(false); } public void updateError(final Throwable ex, final int speed) { toast(String.format("error %d %s", position, ex)); updateSpeed(speed); pb.setIndeterminate(false); ex.printStackTrace(); } public void updateCompleted(final BaseDownloadTask task) { toast(String.format("completed %d %s", position, task.getTargetFilePath())); if (detailTv != null) { detailTv.setText(String.format("sofar: %d total: %d", task.getSmallFileSoFarBytes(), task.getSmallFileTotalBytes())); } updateSpeed(task.getSpeed()); pb.setIndeterminate(false); pb.setMax(task.getSmallFileTotalBytes()); pb.setProgress(task.getSmallFileSoFarBytes()); } private void toast(final String msg) { if (this.weakReferenceContext != null && this.weakReferenceContext.get() != null) { Snackbar.make(weakReferenceContext.get().startBtn1, msg, Snackbar.LENGTH_LONG).show(); } } } private int downloadId1; private int downloadId2; private int downloadId3; private int downloadId4; private String llsApkFilePath; private String llsApkDir; private String normalTaskFilePath; private String chunkedFilePath; private Button startBtn1; private Button pauseBtn1; private Button deleteBtn1; private TextView filenameTv1; private TextView speedTv1; private ProgressBar progressBar1; private Button startBtn2; private Button pauseBtn2; private Button deleteBtn2; private TextView filenameTv2; private TextView speedTv2; private ProgressBar progressBar2; private Button startBtn3; private Button pauseBtn3; private Button deleteBtn3; private TextView speedTv3; private ProgressBar progressBar3; private Button startBtn4; private Button pauseBtn4; private Button deleteBtn4; private TextView detailTv4; private TextView speedTv4; private ProgressBar progressBar4; private void assignViews() { startBtn1 = (Button) findViewById(R.id.start_btn_1); pauseBtn1 = (Button) findViewById(R.id.pause_btn_1); deleteBtn1 = (Button) findViewById(R.id.delete_btn_1); filenameTv1 = (TextView) findViewById(R.id.filename_tv_1); speedTv1 = (TextView) findViewById(R.id.speed_tv_1); progressBar1 = (ProgressBar) findViewById(R.id.progressBar_1); startBtn2 = (Button) findViewById(R.id.start_btn_2); pauseBtn2 = (Button) findViewById(R.id.pause_btn_2); deleteBtn2 = (Button) findViewById(R.id.delete_btn_2); filenameTv2 = (TextView) findViewById(R.id.filename_tv_2); speedTv2 = (TextView) findViewById(R.id.speed_tv_2); progressBar2 = (ProgressBar) findViewById(R.id.progressBar_2); startBtn3 = (Button) findViewById(R.id.start_btn_3); pauseBtn3 = (Button) findViewById(R.id.pause_btn_3); deleteBtn3 = (Button) findViewById(R.id.delete_btn_3); speedTv3 = (TextView) findViewById(R.id.speed_tv_3); progressBar3 = (ProgressBar) findViewById(R.id.progressBar_3); startBtn4 = (Button) findViewById(R.id.start_btn_4); pauseBtn4 = (Button) findViewById(R.id.pause_btn_4); deleteBtn4 = (Button) findViewById(R.id.delete_btn_4); detailTv4 = (TextView) findViewById(R.id.detail_tv_4); speedTv4 = (TextView) findViewById(R.id.speed_tv_4); progressBar4 = (ProgressBar) findViewById(R.id.progressBar_4); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.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. */ package com.liulishuo.filedownloader.demo; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadConnectListener; import com.liulishuo.filedownloader.FileDownloadListener; import com.liulishuo.filedownloader.FileDownloadSampleListener; import com.liulishuo.filedownloader.FileDownloader; import com.liulishuo.filedownloader.model.FileDownloadStatus; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * Created by Jacksgong on 1/9/16. */ public class TasksManagerDemoActivity extends AppCompatActivity { private TaskItemAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tasks_manager_demo); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(adapter = new TaskItemAdapter()); TasksManager.getImpl().onCreate(new WeakReference<>(this)); } public void postNotifyDataChanged() { if (adapter != null) { runOnUiThread(new Runnable() { @Override public void run() { if (adapter != null) { adapter.notifyDataSetChanged(); } } }); } } @Override protected void onDestroy() { TasksManager.getImpl().onDestroy(); adapter = null; FileDownloader.getImpl().pauseAll(); super.onDestroy(); } // ============================================================================ view adapter === private static class TaskItemViewHolder extends RecyclerView.ViewHolder { public TaskItemViewHolder(View itemView) { super(itemView); assignViews(); } private View findViewById(final int id) { return itemView.findViewById(id); } /** * viewHolder position */ private int position; /** * download id */ private int id; public void update(final int id, final int position) { this.id = id; this.position = position; } public void updateDownloaded() { taskPb.setMax(1); taskPb.setProgress(1); taskStatusTv.setText(R.string.tasks_manager_demo_status_completed); taskActionBtn.setText(R.string.delete); } public void updateNotDownloaded(final int status, final long sofar, final long total) { if (sofar > 0 && total > 0) { final float percent = sofar / (float) total; taskPb.setMax(100); taskPb.setProgress((int) (percent * 100)); } else { taskPb.setMax(1); taskPb.setProgress(0); } switch (status) { case FileDownloadStatus.error: taskStatusTv.setText(R.string.tasks_manager_demo_status_error); break; case FileDownloadStatus.paused: taskStatusTv.setText(R.string.tasks_manager_demo_status_paused); break; default: taskStatusTv.setText(R.string.tasks_manager_demo_status_not_downloaded); break; } taskActionBtn.setText(R.string.start); } public void updateDownloading(final int status, final long sofar, final long total) { final float percent = sofar / (float) total; taskPb.setMax(100); taskPb.setProgress((int) (percent * 100)); switch (status) { case FileDownloadStatus.pending: taskStatusTv.setText(R.string.tasks_manager_demo_status_pending); break; case FileDownloadStatus.started: taskStatusTv.setText(R.string.tasks_manager_demo_status_started); break; case FileDownloadStatus.connected: taskStatusTv.setText(R.string.tasks_manager_demo_status_connected); break; case FileDownloadStatus.progress: taskStatusTv.setText(R.string.tasks_manager_demo_status_progress); break; default: taskStatusTv.setText(DemoApplication.CONTEXT.getString( R.string.tasks_manager_demo_status_downloading, status)); break; } taskActionBtn.setText(R.string.pause); } private TextView taskNameTv; private TextView taskStatusTv; private ProgressBar taskPb; private Button taskActionBtn; private void assignViews() { taskNameTv = (TextView) findViewById(R.id.task_name_tv); taskStatusTv = (TextView) findViewById(R.id.task_status_tv); taskPb = (ProgressBar) findViewById(R.id.task_pb); taskActionBtn = (Button) findViewById(R.id.task_action_btn); } } private static class TaskItemAdapter extends RecyclerView.Adapter { private FileDownloadListener taskDownloadListener = new FileDownloadSampleListener() { private TaskItemViewHolder checkCurrentHolder(final BaseDownloadTask task) { final TaskItemViewHolder tag = (TaskItemViewHolder) task.getTag(); if (tag.id != task.getId()) { return null; } return tag; } @Override protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { super.pending(task, soFarBytes, totalBytes); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.updateDownloading(FileDownloadStatus.pending, soFarBytes , totalBytes); tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_pending); } @Override protected void started(BaseDownloadTask task) { super.started(task); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_started); } @Override protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) { super.connected(task, etag, isContinue, soFarBytes, totalBytes); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.updateDownloading(FileDownloadStatus.connected, soFarBytes , totalBytes); tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_connected); } @Override protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { super.progress(task, soFarBytes, totalBytes); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.updateDownloading(FileDownloadStatus.progress, soFarBytes , totalBytes); } @Override protected void error(BaseDownloadTask task, Throwable e) { super.error(task, e); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.updateNotDownloaded(FileDownloadStatus.error, task.getLargeFileSoFarBytes() , task.getLargeFileTotalBytes()); TasksManager.getImpl().removeTaskForViewHolder(task.getId()); } @Override protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { super.paused(task, soFarBytes, totalBytes); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.updateNotDownloaded(FileDownloadStatus.paused, soFarBytes, totalBytes); tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_paused); TasksManager.getImpl().removeTaskForViewHolder(task.getId()); } @Override protected void completed(BaseDownloadTask task) { super.completed(task); final TaskItemViewHolder tag = checkCurrentHolder(task); if (tag == null) { return; } tag.updateDownloaded(); TasksManager.getImpl().removeTaskForViewHolder(task.getId()); } }; private View.OnClickListener taskActionOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v.getTag() == null) { return; } TaskItemViewHolder holder = (TaskItemViewHolder) v.getTag(); CharSequence action = ((TextView) v).getText(); if (action.equals(v.getResources().getString(R.string.pause))) { // to pause FileDownloader.getImpl().pause(holder.id); } else if (action.equals(v.getResources().getString(R.string.start))) { // to start // to start final TasksManagerModel model = TasksManager.getImpl().get(holder.position); final BaseDownloadTask task = FileDownloader.getImpl().create(model.getUrl()) .setPath(model.getPath()) .setCallbackProgressTimes(100) .setListener(taskDownloadListener); TasksManager.getImpl() .addTaskForViewHolder(task); TasksManager.getImpl() .updateViewHolder(holder.id, holder); task.start(); } else if (action.equals(v.getResources().getString(R.string.delete))) { // to delete new File(TasksManager.getImpl().get(holder.position).getPath()).delete(); holder.taskActionBtn.setEnabled(true); holder.updateNotDownloaded(FileDownloadStatus.INVALID_STATUS, 0, 0); } } }; @Override public TaskItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { TaskItemViewHolder holder = new TaskItemViewHolder( LayoutInflater.from( parent.getContext()) .inflate(R.layout.item_tasks_manager, parent, false)); holder.taskActionBtn.setOnClickListener(taskActionOnClickListener); return holder; } @Override public void onBindViewHolder(TaskItemViewHolder holder, int position) { final TasksManagerModel model = TasksManager.getImpl().get(position); holder.update(model.getId(), position); holder.taskActionBtn.setTag(holder); holder.taskNameTv.setText(model.getName()); TasksManager.getImpl() .updateViewHolder(holder.id, holder); holder.taskActionBtn.setEnabled(true); if (TasksManager.getImpl().isReady()) { final int status = TasksManager.getImpl().getStatus(model.getId(), model.getPath()); if (status == FileDownloadStatus.pending || status == FileDownloadStatus.started || status == FileDownloadStatus.connected) { // start task, but file not created yet holder.updateDownloading(status, TasksManager.getImpl().getSoFar(model.getId()) , TasksManager.getImpl().getTotal(model.getId())); } else if (!new File(model.getPath()).exists() && !new File(FileDownloadUtils.getTempPath(model.getPath())).exists()) { // not exist file holder.updateNotDownloaded(status, 0, 0); } else if (TasksManager.getImpl().isDownloaded(status)) { // already downloaded and exist holder.updateDownloaded(); } else if (status == FileDownloadStatus.progress) { // downloading holder.updateDownloading(status, TasksManager.getImpl().getSoFar(model.getId()) , TasksManager.getImpl().getTotal(model.getId())); } else { // not start holder.updateNotDownloaded(status, TasksManager.getImpl().getSoFar(model.getId()) , TasksManager.getImpl().getTotal(model.getId())); } } else { holder.taskStatusTv.setText(R.string.tasks_manager_demo_status_loading); holder.taskActionBtn.setEnabled(false); } } @Override public int getItemCount() { return TasksManager.getImpl().getTaskCounts(); } } // ============================================================================ controller ==== public static class TasksManager { private final static class HolderClass { private final static TasksManager INSTANCE = new TasksManagerDemoActivity.TasksManager(); } public static TasksManager getImpl() { return HolderClass.INSTANCE; } private TasksManagerDBController dbController; private List modelList; private TasksManager() { dbController = new TasksManagerDBController(); modelList = dbController.getAllTasks(); initDemo(); } private void initDemo() { if (modelList.size() <= 0) { final int demoSize = Constant.BIG_FILE_URLS.length; for (int i = 0; i < demoSize; i++) { final String url = Constant.BIG_FILE_URLS[i]; addTask(url); } } } private SparseArray taskSparseArray = new SparseArray<>(); public void addTaskForViewHolder(final BaseDownloadTask task) { taskSparseArray.put(task.getId(), task); } public void removeTaskForViewHolder(final int id) { taskSparseArray.remove(id); } public void updateViewHolder(final int id, final TaskItemViewHolder holder) { final BaseDownloadTask task = taskSparseArray.get(id); if (task == null) { return; } task.setTag(holder); } public void releaseTask() { taskSparseArray.clear(); } private FileDownloadConnectListener listener; private void registerServiceConnectionListener(final WeakReference activityWeakReference) { if (listener != null) { FileDownloader.getImpl().removeServiceConnectListener(listener); } listener = new FileDownloadConnectListener() { @Override public void connected() { if (activityWeakReference == null || activityWeakReference.get() == null) { return; } activityWeakReference.get().postNotifyDataChanged(); } @Override public void disconnected() { if (activityWeakReference == null || activityWeakReference.get() == null) { return; } activityWeakReference.get().postNotifyDataChanged(); } }; FileDownloader.getImpl().addServiceConnectListener(listener); } private void unregisterServiceConnectionListener() { FileDownloader.getImpl().removeServiceConnectListener(listener); listener = null; } public void onCreate(final WeakReference activityWeakReference) { if (!FileDownloader.getImpl().isServiceConnected()) { FileDownloader.getImpl().bindService(); registerServiceConnectionListener(activityWeakReference); } } public void onDestroy() { unregisterServiceConnectionListener(); releaseTask(); } public boolean isReady() { return FileDownloader.getImpl().isServiceConnected(); } public TasksManagerModel get(final int position) { return modelList.get(position); } public TasksManagerModel getById(final int id) { for (TasksManagerModel model : modelList) { if (model.getId() == id) { return model; } } return null; } /** * @param status Download Status * @return has already downloaded * @see FileDownloadStatus */ public boolean isDownloaded(final int status) { return status == FileDownloadStatus.completed; } public int getStatus(final int id, String path) { return FileDownloader.getImpl().getStatus(id, path); } public long getTotal(final int id) { return FileDownloader.getImpl().getTotal(id); } public long getSoFar(final int id) { return FileDownloader.getImpl().getSoFar(id); } public int getTaskCounts() { return modelList.size(); } public TasksManagerModel addTask(final String url) { return addTask(url, createPath(url)); } public TasksManagerModel addTask(final String url, final String path) { if (TextUtils.isEmpty(url) || TextUtils.isEmpty(path)) { return null; } final int id = FileDownloadUtils.generateId(url, path); TasksManagerModel model = getById(id); if (model != null) { return model; } final TasksManagerModel newModel = dbController.addTask(url, path); if (newModel != null) { modelList.add(newModel); } return newModel; } public String createPath(final String url) { if (TextUtils.isEmpty(url)) { return null; } return FileDownloadUtils.getDefaultSaveFilePath(url); } } private static class TasksManagerDBController { public final static String TABLE_NAME = "tasksmanger"; private final SQLiteDatabase db; private TasksManagerDBController() { TasksManagerDBOpenHelper openHelper = new TasksManagerDBOpenHelper(DemoApplication.CONTEXT); db = openHelper.getWritableDatabase(); } public List getAllTasks() { final Cursor c = db.rawQuery("SELECT * FROM " + TABLE_NAME, null); final List list = new ArrayList<>(); try { if (!c.moveToLast()) { return list; } do { TasksManagerModel model = new TasksManagerModel(); model.setId(c.getInt(c.getColumnIndex(TasksManagerModel.ID))); model.setName(c.getString(c.getColumnIndex(TasksManagerModel.NAME))); model.setUrl(c.getString(c.getColumnIndex(TasksManagerModel.URL))); model.setPath(c.getString(c.getColumnIndex(TasksManagerModel.PATH))); list.add(model); } while (c.moveToPrevious()); } finally { if (c != null) { c.close(); } } return list; } public TasksManagerModel addTask(final String url, final String path) { if (TextUtils.isEmpty(url) || TextUtils.isEmpty(path)) { return null; } // have to use FileDownloadUtils.generateId to associate TasksManagerModel with FileDownloader final int id = FileDownloadUtils.generateId(url, path); TasksManagerModel model = new TasksManagerModel(); model.setId(id); model.setName(DemoApplication.CONTEXT.getString(R.string.tasks_manager_demo_name, id)); model.setUrl(url); model.setPath(path); final boolean succeed = db.insert(TABLE_NAME, null, model.toContentValues()) != -1; return succeed ? model : null; } } // ----------------------- model private static class TasksManagerDBOpenHelper extends SQLiteOpenHelper { public final static String DATABASE_NAME = "tasksmanager.db"; public final static int DATABASE_VERSION = 2; public TasksManagerDBOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS " + TasksManagerDBController.TABLE_NAME + String.format( "(" + "%s INTEGER PRIMARY KEY, " // id, download id + "%s VARCHAR, " // name + "%s VARCHAR, " // url + "%s VARCHAR " // path + ")" , TasksManagerModel.ID , TasksManagerModel.NAME , TasksManagerModel.URL , TasksManagerModel.PATH )); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 1 && newVersion == 2) { db.delete(TasksManagerDBController.TABLE_NAME, null, null); } } } private static class TasksManagerModel { public final static String ID = "id"; public final static String NAME = "name"; public final static String URL = "url"; public final static String PATH = "path"; private int id; private String name; private String url; private String path; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public ContentValues toContentValues() { ContentValues cv = new ContentValues(); cv.put(ID, id); cv.put(NAME, name); cv.put(URL, url); cv.put(PATH, path); return cv; } } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/Utils.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. */ package com.liulishuo.filedownloader.demo; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; import android.support.annotation.Nullable; public class Utils { public static void createNotificationChannel( String channelId, String channelName, Context context ) { NotificationManager notificationManager = getNotificationManager(context); if (notificationManager == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(channel); } } public static void deleteNotificationChannel(String channelId, Context context) { NotificationManager notificationManager = getNotificationManager(context); if (notificationManager == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { notificationManager.deleteNotificationChannel(channelId); } } @Nullable public static NotificationManager getNotificationManager(Context context) { return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); } } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/performance/IntParcel.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. */ package com.liulishuo.filedownloader.demo.performance; import android.os.Parcel; import android.os.Parcelable; /** * Created by Jacksgong on 1/4/16. */ public class IntParcel implements Parcelable { private int v1 = Integer.MAX_VALUE; private int v2 = Integer.MAX_VALUE; private int v3 = Integer.MAX_VALUE; public void operate() { v1 -= 11; v2 -= 12; v3 -= 13; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(this.v1); dest.writeInt(this.v2); dest.writeInt(this.v3); } public IntParcel() { } protected IntParcel(Parcel in) { this.v1 = in.readInt(); this.v2 = in.readInt(); this.v3 = in.readInt(); } public static final Creator CREATOR = new Creator() { public IntParcel createFromParcel(Parcel source) { return new IntParcel(source); } public IntParcel[] newArray(int size) { return new IntParcel[size]; } }; } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/performance/LongParcel.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. */ package com.liulishuo.filedownloader.demo.performance; import android.os.Parcel; import android.os.Parcelable; /** * Created by Jacksgong on 1/4/16. */ public class LongParcel implements Parcelable { private long v1 = Integer.MAX_VALUE; private long v2 = Integer.MAX_VALUE; private long v3 = Integer.MAX_VALUE; public void operate() { v1 -= 11; v2 -= 12; v3 -= 13; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(this.v1); dest.writeLong(this.v2); dest.writeLong(this.v3); } public LongParcel() { } protected LongParcel(Parcel in) { this.v1 = in.readLong(); this.v2 = in.readLong(); this.v3 = in.readLong(); } public static final Creator CREATOR = new Creator() { public LongParcel createFromParcel(Parcel source) { return new LongParcel(source); } public LongParcel[] newArray(int size) { return new LongParcel[size]; } }; } ================================================ FILE: demo/src/main/java/com/liulishuo/filedownloader/demo/performance/PerformanceTestActivity.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. */ package com.liulishuo.filedownloader.demo.performance; import android.os.Bundle; import android.os.Parcel; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.AppCompatSeekBar; import android.view.View; import android.widget.ScrollView; import android.widget.TextView; import com.liulishuo.filedownloader.demo.R; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import okio.Buffer; import okio.Okio; import okio.Sink; import okio.Source; /** * Created by Jacksgong on 1/4/16. */ public class PerformanceTestActivity extends AppCompatActivity { private final static int TIMES = 100000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_performance); assignViews(); } public void onClickLongOperate(final View view) { final long start = System.currentTimeMillis(); final LongParcel longParcel = new LongParcel(); for (int i = 0; i < TIMES; i++) { longParcel.operate(); } infoAppend("Long Operate", start); } public void onClickLongParcel(final View view) { final long start = System.currentTimeMillis(); final LongParcel longParcel = new LongParcel(); for (int i = 0; i < TIMES; i++) { Parcel p = Parcel.obtain(); longParcel.writeToParcel(p, 0); LongParcel longParcelCopy = new LongParcel(p); } infoAppend("Long Parcel and Alloc [and GC]", start); } public void onClickIntOperate(final View view) { final long start = System.currentTimeMillis(); final IntParcel intParcel = new IntParcel(); for (int i = 0; i < TIMES; i++) { intParcel.operate(); } infoAppend("Int Operate", start); } public void onClickIntParcel(final View view) { final long start = System.currentTimeMillis(); final IntParcel intParcel = new IntParcel(); for (int i = 0; i < TIMES; i++) { Parcel p = Parcel.obtain(); intParcel.writeToParcel(p, 0); IntParcel intParcelCopy = new IntParcel(p); } infoAppend("Int Parcel and Alloc [and GC]", start); } private static final int BUFFER_SIZE = 1024 * 4; private String writePerformanceTestPath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + "performance"; private static final int TENTH_MILLI_TO_NANO = 100000; public void onClickWriteTest(final View view) { FileOutputStream fos = null; InputStream inputStream = initPerformanceTest(); byte[] buff = new byte[BUFFER_SIZE]; long start = System.currentTimeMillis(); int tenthMilliSec = ioPerformanceSb.getProgress(); int sleepMilliSec = tenthMilliSec / 10; int sleepNanoSec = (tenthMilliSec - (tenthMilliSec / 10) * 10) * TENTH_MILLI_TO_NANO; infoTv.append(String.format("Output test with %.1f ms extra operate\n", tenthMilliSec / 10.0f)); // ---------------------- FileOutputStream try { fos = new FileOutputStream(writePerformanceTestPath, true); do { int byteCount = inputStream.read(buff); if (byteCount == -1) { break; } fos.write(buff, 0, byteCount); if (sleepMilliSec > 0 || sleepNanoSec > 0) { try { Thread.sleep(sleepMilliSec, sleepNanoSec); } catch (InterruptedException e) { e.printStackTrace(); } } } while (true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.getFD().sync(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } infoAppend("FileOutputStream", start); BufferedOutputStream bos = null; inputStream = initPerformanceTest(); start = System.currentTimeMillis(); // ---------------------- BufferedOutputStream try { bos = new BufferedOutputStream(new FileOutputStream(writePerformanceTestPath, true)); do { int byteCount = inputStream.read(buff); if (byteCount == -1) { break; } bos.write(buff, 0, byteCount); if (sleepMilliSec > 0 || sleepNanoSec > 0) { try { Thread.sleep(sleepMilliSec, sleepNanoSec); } catch (InterruptedException e) { e.printStackTrace(); } } } while (true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (bos != null) { try { bos.flush(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } infoAppend("BufferOutputStream", start); RandomAccessFile raf = null; inputStream = initPerformanceTest(); start = System.currentTimeMillis(); // ---------------------- RandomAccessFile try { raf = new RandomAccessFile(writePerformanceTestPath, "rw"); do { int byteCount = inputStream.read(buff); if (byteCount == -1) { break; } raf.write(buff, 0, byteCount); if (sleepMilliSec > 0 || sleepNanoSec > 0) { try { Thread.sleep(sleepMilliSec, sleepNanoSec); } catch (InterruptedException e) { e.printStackTrace(); } } } while (true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } infoAppend("RandomAccessFile", start); Sink sink = null; inputStream = initPerformanceTest(); Source source = Okio.source(inputStream); Buffer buffer = new Buffer(); start = System.currentTimeMillis(); try { sink = Okio.sink(new File(writePerformanceTestPath)); sink = Okio.buffer(sink); do { long byteCount = source.read(buffer, BUFFER_SIZE); if (byteCount == -1) { break; } sink.write(buffer, byteCount); if (sleepMilliSec > 0 || sleepNanoSec > 0) { try { Thread.sleep(sleepMilliSec, sleepNanoSec); } catch (InterruptedException e) { e.printStackTrace(); } } } while (true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (sink != null) { try { sink.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } infoAppend("okio", start); } private InputStream initPerformanceTest() { try { final File file = new File(writePerformanceTestPath); if (file.exists()) { file.delete(); } file.createNewFile(); return getResources().getAssets().open("performance_test_data"); } catch (IOException e) { e.printStackTrace(); } return null; } private void infoAppend(final String msg, final long start) { infoTv.append(String.format(" %s: %d\n", msg, System.currentTimeMillis() - start)); scrollView.post(new Runnable() { @Override public void run() { if (scrollView != null) { scrollView.fullScroll(View.FOCUS_DOWN); } } }); } private AppCompatSeekBar ioPerformanceSb; private ScrollView scrollView; private TextView infoTv; private void assignViews() { ioPerformanceSb = (AppCompatSeekBar) findViewById(R.id.io_performance_sb); scrollView = (ScrollView) findViewById(R.id.scrollView); infoTv = (TextView) findViewById(R.id.info_tv); } } ================================================ FILE: demo/src/main/res/drawable/bg_item_task_manager.xml ================================================ ================================================ FILE: demo/src/main/res/layout/activity_hybrid_test.xml ================================================