├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zs │ │ └── demo │ │ └── downloadfile │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zs │ │ │ └── demo │ │ │ └── downloadfile │ │ │ ├── Constant.java │ │ │ ├── LimitActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── MultipleActivity.java │ │ │ ├── SingleActivity.java │ │ │ ├── adapter │ │ │ ├── DownloadAdapter.java │ │ │ └── LimitDownloadAdapter.java │ │ │ └── download │ │ │ ├── DownloadIO.java │ │ │ ├── DownloadInfo.java │ │ │ ├── DownloadManager.java │ │ │ ├── DownloadObserver.java │ │ │ └── limit │ │ │ ├── DownloadLimitManager.java │ │ │ └── DownloadLimitObserver.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_recycler.xml │ │ ├── activity_single.xml │ │ └── item_download_layout.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zs │ └── demo │ └── downloadfile │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DownloadFile 2 | Android OkHttp 下载多个文件 断点下载 3 | ## 介绍 4 | demo的主要逻辑是,利用okhttp 和 RxJava 在子线程中下载文件,通关观察者模式监听下载的进度,再回调到主线程中,然后利用EventBus 通知页面刷新,更新进度。 5 | ## 效果图 6 | ![download.gif](https://upload-images.jianshu.io/upload_images/3183047-18efb4a2a30a86f2.gif?imageMogr2/auto-orient/strip) 7 | ### step1 导入依赖库 8 | ```Java 9 | // OKHttp RxJava 10 | implementation 'com.squareup.okhttp3:okhttp:3.6.0' 11 | implementation 'io.reactivex.rxjava2:rxjava:2.1.3' 12 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 13 | // eventbus 14 | implementation 'org.greenrobot:eventbus:3.0.0' 15 | implementation 'com.android.support:recyclerview-v7:27.1.1' 16 | ``` 17 | ### step 2 定义下载bean 18 | ```Java 19 | /** 20 | * Created by zs 21 | * Date:2018年 09月 12日 22 | * Time:13:50 23 | * ————————————————————————————————————— 24 | * About: 下载管理 25 | * ————————————————————————————————————— 26 | */ 27 | public class DownloadInfo { 28 | 29 | /** 30 | * 下载状态 31 | */ 32 | public static final String DOWNLOAD = "download"; 33 | public static final String DOWNLOAD_PAUSE = "pause"; 34 | public static final String DOWNLOAD_CANCEL = "cancel"; 35 | public static final String DOWNLOAD_OVER = "over"; 36 | public static final String DOWNLOAD_ERROR = "error"; 37 | 38 | public static final long TOTAL_ERROR = -1;//获取进度失败 39 | 40 | private String url; 41 | private String fileName; 42 | private String downloadStatus; 43 | private long total; 44 | private long progress; 45 | 46 | public DownloadInfo(String url) { 47 | this.url = url; 48 | } 49 | 50 | public String getUrl() { 51 | return url; 52 | } 53 | 54 | public String getFileName() { 55 | return fileName; 56 | } 57 | 58 | public void setFileName(String fileName) { 59 | this.fileName = fileName; 60 | } 61 | 62 | public long getTotal() { 63 | return total; 64 | } 65 | 66 | public void setTotal(long total) { 67 | this.total = total; 68 | } 69 | 70 | public long getProgress() { 71 | return progress; 72 | } 73 | 74 | public void setProgress(long progress) { 75 | this.progress = progress; 76 | } 77 | 78 | public String getDownloadStatus() { 79 | return downloadStatus; 80 | } 81 | 82 | public void setDownloadStatus(String downloadStatus) { 83 | this.downloadStatus = downloadStatus; 84 | } 85 | } 86 | 87 | ``` 88 | ### step 3 下载管理类 也是主要的内容 (DownloadManager) 89 | ```Java 90 | /** 91 | * Created by zs 92 | * Date:2018年 09月 12日 93 | * Time:13:56 94 | * ————————————————————————————————————— 95 | * About: 下载管理 96 | * ————————————————————————————————————— 97 | */ 98 | public class DownloadManager { 99 | 100 | private static final AtomicReference INSTANCE = new AtomicReference<>(); 101 | private OkHttpClient mClient; 102 | private HashMap downCalls; //用来存放各个下载的请求 103 | 104 | public static DownloadManager getInstance() { 105 | for (; ; ) { 106 | DownloadManager current = INSTANCE.get(); 107 | if (current != null) { 108 | return current; 109 | } 110 | current = new DownloadManager(); 111 | if (INSTANCE.compareAndSet(null, current)) { 112 | return current; 113 | } 114 | } 115 | } 116 | 117 | private DownloadManager() { 118 | downCalls = new HashMap<>(); 119 | mClient = new OkHttpClient.Builder().build(); 120 | } 121 | 122 | /** 123 | * 查看是否在下载任务中 124 | * @param url 125 | * @return 126 | */ 127 | public boolean getDownloadUrl(String url){ 128 | return downCalls.containsKey(url); 129 | } 130 | 131 | /** 132 | * 开始下载 133 | * 134 | * @param url 下载请求的网址 135 | * @param downLoadObserver 用来回调的接口 136 | */ 137 | public void download(String url, DownloadObserver downLoadObserver) { 138 | 139 | Observable.just(url) 140 | .filter(new Predicate() { 141 | @Override 142 | public boolean test(String s) { 143 | return !downCalls.containsKey(s); 144 | } 145 | }) // 过滤 call的map中已经有了,就证明正在下载,则这次不下载 146 | .flatMap(new Function>() { 147 | @Override 148 | public ObservableSource apply(String s) { 149 | return Observable.just(createDownInfo(s)); 150 | } 151 | }) // 生成 DownloadInfo 152 | .map(new Function() { 153 | @Override 154 | public DownloadInfo apply(Object o) { 155 | return getRealFileName((DownloadInfo)o); 156 | } 157 | }) // 如果已经下载,重新命名 158 | .flatMap(new Function>() { 159 | @Override 160 | public ObservableSource apply(DownloadInfo downloadInfo) { 161 | return Observable.create(new DownloadSubscribe(downloadInfo)); 162 | } 163 | }) // 下载 164 | .observeOn(AndroidSchedulers.mainThread()) // 在主线程中回调 165 | .subscribeOn(Schedulers.io()) // 在子线程中执行 166 | .subscribe(downLoadObserver); // 添加观察者,监听下载进度 167 | } 168 | 169 | /** 170 | * 下载取消或者暂停 171 | * @param url 172 | */ 173 | public void pauseDownload(String url) { 174 | Call call = downCalls.get(url); 175 | if (call != null) { 176 | call.cancel();//取消 177 | } 178 | downCalls.remove(url); 179 | } 180 | 181 | /** 182 | * 取消下载 删除本地文件 183 | * @param info 184 | */ 185 | public void cancelDownload(DownloadInfo info){ 186 | pauseDownload(info.getUrl()); 187 | info.setProgress(0); 188 | info.setDownloadStatus(DownloadInfo.DOWNLOAD_CANCEL); 189 | EventBus.getDefault().post(info); 190 | Constant.deleteFile(info.getFileName()); 191 | } 192 | 193 | /** 194 | * 创建DownInfo 195 | * 196 | * @param url 请求网址 197 | * @return DownInfo 198 | */ 199 | private DownloadInfo createDownInfo(String url) { 200 | DownloadInfo downloadInfo = new DownloadInfo(url); 201 | long contentLength = getContentLength(url);//获得文件大小 202 | downloadInfo.setTotal(contentLength); 203 | String fileName = url.substring(url.lastIndexOf("/")); 204 | downloadInfo.setFileName(fileName); 205 | return downloadInfo; 206 | } 207 | 208 | /** 209 | * 如果文件已下载重新命名新文件名 210 | * @param downloadInfo 211 | * @return 212 | */ 213 | private DownloadInfo getRealFileName(DownloadInfo downloadInfo) { 214 | String fileName = downloadInfo.getFileName(); 215 | long downloadLength = 0, contentLength = downloadInfo.getTotal(); 216 | File path = new File(Constant.FILE_PATH); 217 | if (!path.exists()) { 218 | path.mkdir(); 219 | } 220 | File file = new File(Constant.FILE_PATH, fileName); 221 | if (file.exists()) { 222 | //找到了文件,代表已经下载过,则获取其长度 223 | downloadLength = file.length(); 224 | } 225 | //之前下载过,需要重新来一个文件 226 | int i = 1; 227 | while (downloadLength >= contentLength) { 228 | int dotIndex = fileName.lastIndexOf("."); 229 | String fileNameOther; 230 | if (dotIndex == -1) { 231 | fileNameOther = fileName + "(" + i + ")"; 232 | } else { 233 | fileNameOther = fileName.substring(0, dotIndex) 234 | + "(" + i + ")" + fileName.substring(dotIndex); 235 | } 236 | File newFile = new File(Constant.FILE_PATH, fileNameOther); 237 | file = newFile; 238 | downloadLength = newFile.length(); 239 | i++; 240 | } 241 | //设置改变过的文件名/大小 242 | downloadInfo.setProgress(downloadLength); 243 | downloadInfo.setFileName(file.getName()); 244 | return downloadInfo; 245 | } 246 | 247 | private class DownloadSubscribe implements ObservableOnSubscribe { 248 | private DownloadInfo downloadInfo; 249 | 250 | public DownloadSubscribe(DownloadInfo downloadInfo) { 251 | this.downloadInfo = downloadInfo; 252 | } 253 | 254 | @Override 255 | public void subscribe(ObservableEmitter e) throws Exception { 256 | String url = downloadInfo.getUrl(); 257 | long downloadLength = downloadInfo.getProgress();//已经下载好的长度 258 | long contentLength = downloadInfo.getTotal();//文件的总长度 259 | //初始进度信息 260 | e.onNext(downloadInfo); 261 | Request request = new Request.Builder() 262 | //确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分 263 | .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength) 264 | .url(url) 265 | .build(); 266 | Call call = mClient.newCall(request); 267 | downCalls.put(url, call);//把这个添加到call里,方便取消 268 | Response response = call.execute(); 269 | File file = new File(Constant.FILE_PATH, downloadInfo.getFileName()); 270 | InputStream is = null; 271 | FileOutputStream fileOutputStream = null; 272 | try { 273 | is = response.body().byteStream(); 274 | fileOutputStream = new FileOutputStream(file, true); 275 | byte[] buffer = new byte[2048];//缓冲数组2kB 276 | int len; 277 | while ((len = is.read(buffer)) != -1) { 278 | fileOutputStream.write(buffer, 0, len); 279 | downloadLength += len; 280 | downloadInfo.setProgress(downloadLength); 281 | e.onNext(downloadInfo); 282 | } 283 | fileOutputStream.flush(); 284 | downCalls.remove(url); 285 | } finally { 286 | //关闭IO流 287 | DownloadIO.closeAll(is, fileOutputStream); 288 | 289 | } 290 | e.onComplete();//完成 291 | } 292 | } 293 | 294 | /** 295 | * 获取下载长度 296 | * 297 | * @param downloadUrl 298 | * @return 299 | */ 300 | private long getContentLength(String downloadUrl) { 301 | Request request = new Request.Builder() 302 | .url(downloadUrl) 303 | .build(); 304 | try { 305 | Response response = mClient.newCall(request).execute(); 306 | if (response != null && response.isSuccessful()) { 307 | long contentLength = response.body().contentLength(); 308 | response.close(); 309 | return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength; 310 | } 311 | } catch (IOException e) { 312 | e.printStackTrace(); 313 | } 314 | return DownloadInfo.TOTAL_ERROR; 315 | } 316 | 317 | } 318 | ``` 319 | 这里要多分析一下,下载管理类是单例模式这是必须的,里面定义一个HashMap用来存放所有的下载任务,里面的download()方法,是主要过程 320 | ```Java 321 | public void download(String url, DownloadObserver downLoadObserver) { 322 | 323 | Observable.just(url) 324 | .filter(new Predicate() { 325 | @Override 326 | public boolean test(String s) { 327 | return !downCalls.containsKey(s); 328 | } 329 | }) // 过滤 call的map中已经有了,就证明正在下载,则这次不下载 330 | .flatMap(new Function>() { 331 | @Override 332 | public ObservableSource apply(String s) { 333 | return Observable.just(createDownInfo(s)); 334 | } 335 | }) // 生成 DownloadInfo 336 | .map(new Function() { 337 | @Override 338 | public DownloadInfo apply(Object o) { 339 | return getRealFileName((DownloadInfo)o); 340 | } 341 | }) // 如果已经下载,重新命名 342 | .flatMap(new Function>() { 343 | @Override 344 | public ObservableSource apply(DownloadInfo downloadInfo) { 345 | return Observable.create(new DownloadSubscribe(downloadInfo)); 346 | } 347 | }) // 下载 348 | .observeOn(AndroidSchedulers.mainThread()) // 在主线程中回调 349 | .subscribeOn(Schedulers.io()) // 在子线程中执行 350 | .subscribe(downLoadObserver); // 添加观察者,监听下载进度 351 | } 352 | ``` 353 | 这里用到的是Rxjava,第一步filter,过滤下载,已经下载的url再点击下载是不会另起下载任务的;第二步flatMap,通过url生成Bean类,这个按需求来设计,也可以直接传一个Bean进来也是可以的;第三步map,如果这个文件已经下载了,再次下载重新命名文件,这也是根据需求改变,如果下载过的文件不需要下载,这就可以省了;第四步flatMap,去下载具体去看下载方法;剩下的就是切换线程和添加观察者了。 354 | ### step4 观察者 355 | ```Java 356 | /** 357 | * Created by zs 358 | * Date:2018年 09月 12日 359 | * Time:13:50 360 | * ————————————————————————————————————— 361 | * About: 观察者 362 | * ————————————————————————————————————— 363 | */ 364 | public class DownloadObserver implements Observer { 365 | 366 | public Disposable d;//可以用于取消注册的监听者 367 | public DownloadInfo downloadInfo; 368 | 369 | @Override 370 | public void onSubscribe(Disposable d) { 371 | this.d = d; 372 | } 373 | 374 | @Override 375 | public void onNext(DownloadInfo value) { 376 | this.downloadInfo = value; 377 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD); 378 | EventBus.getDefault().post(downloadInfo); 379 | } 380 | 381 | @Override 382 | public void onError(Throwable e) { 383 | Log.d("My_Log","onError"); 384 | if (DownloadManager.getInstance().getDownloadUrl(downloadInfo.getUrl())){ 385 | DownloadManager.getInstance().pauseDownload(downloadInfo.getUrl()); 386 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ERROR); 387 | EventBus.getDefault().post(downloadInfo); 388 | }else{ 389 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_PAUSE); 390 | EventBus.getDefault().post(downloadInfo); 391 | } 392 | 393 | } 394 | 395 | @Override 396 | public void onComplete() { 397 | Log.d("My_Log","onComplete"); 398 | if (downloadInfo != null){ 399 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_OVER); 400 | EventBus.getDefault().post(downloadInfo); 401 | } 402 | } 403 | } 404 | 405 | ``` 406 | 这里是用到EventBus来通知页面刷新的,当然也可以不用,把订阅者直接写在Activity,但是那样不利于代码的复用,如果多个页面需要进度更新就麻烦了。 407 | ### step5 Activity 和 Adapter 408 | 页面中是一个RecyclerView,交互逻辑不是很多,说一点,Adapter条目的更新,用的notifyItemChanged(i) 每次更新某一条的进度,而不是notifyDataSetChanged()全部刷新,因为调用全部刷新,刷新的频率很高会导致条目中控件的点击事件不好用没有响应,被拦截了,而更新某一条也会有一个问题是条目刷新时会闪动,解决方案是把RecyclerView的刷新动画去掉,这样就解决了。 409 | ```Java 410 | // 取消item刷新的动画 411 | ((SimpleItemAnimator)recycler_view.getItemAnimator()).setSupportsChangeAnimations(false); 412 | ``` 413 | ```Java 414 | /** 415 | * Created by zs 416 | * Date:2018年 09月 11日 417 | * Time:18:06 418 | * ————————————————————————————————————— 419 | * About: 420 | * ————————————————————————————————————— 421 | */ 422 | public class DownloadAdapter extends RecyclerView.Adapter { 423 | 424 | private List mdata; 425 | 426 | public DownloadAdapter(List mdata) { 427 | this.mdata = mdata; 428 | } 429 | 430 | /** 431 | * 更新下载进度 432 | * @param info 433 | */ 434 | public void updateProgress(DownloadInfo info){ 435 | for (int i = 0; i < mdata.size(); i++){ 436 | if (mdata.get(i).getUrl().equals(info.getUrl())){ 437 | mdata.set(i,info); 438 | notifyItemChanged(i); 439 | break; 440 | } 441 | } 442 | } 443 | 444 | @Override 445 | public UploadHolder onCreateViewHolder(ViewGroup parent, int viewType) { 446 | View view = View.inflate(parent.getContext(), R.layout.item_download_layout,null); 447 | return new UploadHolder(view); 448 | } 449 | 450 | @Override 451 | public void onBindViewHolder(UploadHolder holder, int position) { 452 | 453 | final DownloadInfo info = mdata.get(position); 454 | if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){ 455 | holder.main_progress.setProgress(0); 456 | }else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){ 457 | holder.main_progress.setProgress(holder.main_progress.getMax()); 458 | }else { 459 | if (info.getTotal() == 0){ 460 | holder.main_progress.setProgress(0); 461 | }else { 462 | float d = info.getProgress() * holder.main_progress.getMax() / info.getTotal(); 463 | holder.main_progress.setProgress((int) d); 464 | } 465 | } 466 | holder.main_btn_down.setOnClickListener(new View.OnClickListener() { 467 | @Override 468 | public void onClick(View view) { 469 | DownloadManager.getInstance().download(info.getUrl(), new DownloadObserver()); 470 | } 471 | }); 472 | 473 | holder.main_btn_pause.setOnClickListener(new View.OnClickListener() { 474 | @Override 475 | public void onClick(View view) { 476 | DownloadManager.getInstance().pauseDownload(info.getUrl()); 477 | } 478 | }); 479 | 480 | holder.main_btn_cancel.setOnClickListener(new View.OnClickListener() { 481 | @Override 482 | public void onClick(View view) { 483 | DownloadManager.getInstance().cancelDownload(info); 484 | } 485 | }); 486 | } 487 | 488 | @Override 489 | public int getItemCount() { 490 | return mdata.size(); 491 | } 492 | 493 | public class UploadHolder extends RecyclerView.ViewHolder{ 494 | 495 | private ProgressBar main_progress; 496 | private Button main_btn_down; 497 | private Button main_btn_pause; 498 | private Button main_btn_cancel; 499 | 500 | public UploadHolder(View itemView) { 501 | super(itemView); 502 | main_progress = itemView.findViewById(R.id.main_progress); 503 | main_btn_down = itemView.findViewById(R.id.main_btn_down); 504 | main_btn_pause = itemView.findViewById(R.id.main_btn_pause); 505 | main_btn_cancel = itemView.findViewById(R.id.main_btn_cancel); 506 | } 507 | } 508 | 509 | } 510 | 511 | ``` 512 | ### github地址 513 | https://github.com/QQzs/DownloadFile 514 | 515 | ### 简书地址 516 | https://www.jianshu.com/p/4bab2e9c577e 517 | 518 | 参考地址: 519 | https://blog.csdn.net/cfy137000/article/details/54838608 520 | 所有内容基本是参考的这篇文章,我只是少有修改,希望给大家有所帮助。 521 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.zs.demo.downloadfile" 7 | minSdkVersion 16 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | 29 | // OKHttp RxJava 30 | implementation 'com.squareup.okhttp3:okhttp:3.6.0' 31 | implementation 'io.reactivex.rxjava2:rxjava:2.1.3' 32 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 33 | implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar' 34 | 35 | // eventbus 36 | implementation 'org.greenrobot:eventbus:3.0.0' 37 | implementation 'com.android.support:recyclerview-v7:27.1.1' 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zs/demo/downloadfile/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zs.demo.downloadfile", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/Constant.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * Created by zs 9 | * Date:2018年 09月 12日 10 | * Time:13:54 11 | * ————————————————————————————————————— 12 | * About: 13 | * ————————————————————————————————————— 14 | */ 15 | public class Constant { 16 | 17 | /** 18 | * 下载路径 19 | */ 20 | public final static String FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()+"/AAA/"; 21 | 22 | /** 23 | * 若文件下载不下来,更换网址 24 | */ 25 | public final static String URL_1 = "http://files.ibaodian.com/v2/teamfile/1ca447a600580cdcb575ab9348536f38/CM10086_android_V4.8.0_20180708_A0001.apk"; 26 | public final static String URL_2 = "http://files.ibaodian.com/v2/teamfile/f063d3c2c4a32a8143fc4f36be39cfd9/jtyh.patch"; 27 | public final static String URL_3 = "http://files.ibaodian.com/v2/teamfile/482fd8d425d25f3c3fbdb83156a85af1/IMG_20160508_184212.jpg"; 28 | public final static String URL_4 = "http://files.ibaodian.com/v2/teamfile/c0ab1e924a99738a268c137f60f3a6db/IMG_20160525_115133.jpg"; 29 | public final static String URL_5 = "http://files.ibaodian.com/v2/teamfile/5fe13f1385a0112fb75fceed364088e7/IMG_20180818_132629.jpg"; 30 | public final static String URL_6 = "http://files.ibaodian.com/v2/teamfile/da43a96fde668d4c3fd6f89b8da7e20c/5b726910e7bce766b218d0ee.jpg"; 31 | public final static String URL_7 = "http://files.ibaodian.com/v2/teamfile/ac43a96d0f21e83cd3967e60e6775d1d/sf_updata.apk"; 32 | public final static String URL_8 = "http://files.ibaodian.com/v2/teamfile/长城金禧利年金保险菁华版(A计划).pdf"; 33 | public final static String URL_9 = "http://files.ibaodian.com/v2/teamfile/2b1d7f518fbcf467ec9bf748743bea80/D90B2EA927372212B33BB673318AA1A1361024EB20B8493A9B23E8178DF3D001"; 34 | 35 | /** 36 | * 删除文件 37 | * 38 | * @param fileName 39 | * @return 40 | */ 41 | public static boolean deleteFile(String fileName) { 42 | boolean status; 43 | SecurityManager checker = new SecurityManager(); 44 | File file = new File(FILE_PATH + fileName); 45 | if (file.exists()){ 46 | checker.checkDelete(file.toString()); 47 | if (file.isFile()) { 48 | try { 49 | file.delete(); 50 | status = true; 51 | } catch (SecurityException se) { 52 | se.printStackTrace(); 53 | status = false; 54 | } 55 | } else 56 | status = false; 57 | }else 58 | status = false; 59 | return status; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/LimitActivity.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.SimpleItemAnimator; 9 | import android.widget.Toast; 10 | 11 | import com.zs.demo.downloadfile.adapter.LimitDownloadAdapter; 12 | import com.zs.demo.downloadfile.download.DownloadInfo; 13 | 14 | import org.greenrobot.eventbus.EventBus; 15 | import org.greenrobot.eventbus.Subscribe; 16 | import org.greenrobot.eventbus.ThreadMode; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | 23 | /** 24 | * Created by zs 25 | * Date:2018年 09月 11日 26 | * Time:17:57 27 | * ————————————————————————————————————— 28 | * About: 限制同时下载文件的最大个数 29 | * ————————————————————————————————————— 30 | */ 31 | public class LimitActivity extends AppCompatActivity { 32 | 33 | private RecyclerView recycler_view; 34 | private ExecutorService mLimitThreadPool; 35 | private LimitDownloadAdapter mAdapter; 36 | private List mData = new ArrayList<>(); 37 | 38 | @Override 39 | protected void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_recycler); 42 | EventBus.getDefault().register(this); 43 | recycler_view = findViewById(R.id.recycler_view); 44 | 45 | mData.add(new DownloadInfo(Constant.URL_1)); 46 | mData.add(new DownloadInfo(Constant.URL_2)); 47 | mData.add(new DownloadInfo(Constant.URL_3)); 48 | mData.add(new DownloadInfo(Constant.URL_4)); 49 | mData.add(new DownloadInfo(Constant.URL_5)); 50 | mData.add(new DownloadInfo(Constant.URL_6)); 51 | mData.add(new DownloadInfo(Constant.URL_7)); 52 | mData.add(new DownloadInfo(Constant.URL_8)); 53 | mData.add(new DownloadInfo(Constant.URL_9)); 54 | 55 | 56 | mAdapter = new LimitDownloadAdapter(this,mData); 57 | recycler_view.setLayoutManager(new LinearLayoutManager(this)); 58 | recycler_view.setAdapter(mAdapter); 59 | // 取消item刷新的动画 60 | ((SimpleItemAnimator)recycler_view.getItemAnimator()).setSupportsChangeAnimations(false); 61 | 62 | /** 63 | * newFixedThreadPool 可以控制最大并发数的线程池。超过最大并发数的线程进入等待队列。 64 | */ 65 | mLimitThreadPool = Executors.newFixedThreadPool(2); 66 | 67 | } 68 | 69 | @Subscribe (threadMode = ThreadMode.MAIN) 70 | public void update(DownloadInfo info){ 71 | if (DownloadInfo.DOWNLOAD.equals(info.getDownloadStatus())){ 72 | 73 | mAdapter.updateProgress(info); 74 | 75 | }else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){ 76 | 77 | mAdapter.updateProgress(info); 78 | 79 | }else if (DownloadInfo.DOWNLOAD_PAUSE.equals(info.getDownloadStatus())){ 80 | mAdapter.updateProgress(info); 81 | Toast.makeText(this,"下载暂停",Toast.LENGTH_SHORT).show(); 82 | 83 | }else if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){ 84 | 85 | mAdapter.updateProgress(info); 86 | Toast.makeText(this,"下载取消",Toast.LENGTH_SHORT).show(); 87 | 88 | }else if (DownloadInfo.DOWNLOAD_WAIT.equals(info.getDownloadStatus())){ 89 | 90 | mAdapter.updateProgress(info); 91 | Toast.makeText(this,"等待下载",Toast.LENGTH_SHORT).show(); 92 | 93 | }else if (DownloadInfo.DOWNLOAD_ERROR.equals(info.getDownloadStatus())){ 94 | 95 | Toast.makeText(this,"下载出错",Toast.LENGTH_SHORT).show(); 96 | } 97 | } 98 | 99 | @Override 100 | protected void onDestroy() { 101 | super.onDestroy(); 102 | EventBus.getDefault().unregister(this); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import com.tbruyelle.rxpermissions2.RxPermissions; 11 | 12 | import io.reactivex.functions.Consumer; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private RxPermissions mRxPermission; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | mRxPermission = new RxPermissions(this); 23 | } 24 | 25 | public void singleDownload(View view){ 26 | 27 | mRxPermission.request(Manifest.permission.READ_EXTERNAL_STORAGE , Manifest.permission.WRITE_EXTERNAL_STORAGE) 28 | .subscribe(new Consumer() { 29 | @Override 30 | public void accept(Boolean aBoolean) throws Exception { 31 | if (aBoolean){ 32 | Intent intent = new Intent(MainActivity.this,SingleActivity.class); 33 | startActivity(intent); 34 | }else{ 35 | Toast.makeText(MainActivity.this , "请打开读写权限" , Toast.LENGTH_SHORT).show(); 36 | } 37 | } 38 | }); 39 | } 40 | 41 | public void multipleDownload(View view){ 42 | mRxPermission.request(Manifest.permission.READ_EXTERNAL_STORAGE , Manifest.permission.WRITE_EXTERNAL_STORAGE) 43 | .subscribe(new Consumer() { 44 | @Override 45 | public void accept(Boolean aBoolean) throws Exception { 46 | if (aBoolean){ 47 | Intent intent = new Intent(MainActivity.this,MultipleActivity.class); 48 | startActivity(intent); 49 | }else{ 50 | Toast.makeText(MainActivity.this , "请打开读写权限" , Toast.LENGTH_SHORT).show(); 51 | } 52 | } 53 | }); 54 | } 55 | 56 | public void limitDownload(View view){ 57 | mRxPermission.request(Manifest.permission.READ_EXTERNAL_STORAGE , Manifest.permission.WRITE_EXTERNAL_STORAGE) 58 | .subscribe(new Consumer() { 59 | @Override 60 | public void accept(Boolean aBoolean) throws Exception { 61 | if (aBoolean){ 62 | Intent intent = new Intent(MainActivity.this,LimitActivity.class); 63 | startActivity(intent); 64 | }else{ 65 | Toast.makeText(MainActivity.this , "请打开读写权限" , Toast.LENGTH_SHORT).show(); 66 | } 67 | } 68 | }); 69 | } 70 | 71 | 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/MultipleActivity.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.SimpleItemAnimator; 9 | import android.widget.Toast; 10 | 11 | import com.zs.demo.downloadfile.adapter.DownloadAdapter; 12 | import com.zs.demo.downloadfile.download.DownloadInfo; 13 | 14 | import org.greenrobot.eventbus.EventBus; 15 | import org.greenrobot.eventbus.Subscribe; 16 | import org.greenrobot.eventbus.ThreadMode; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by zs 23 | * Date:2018年 09月 11日 24 | * Time:17:57 25 | * ————————————————————————————————————— 26 | * About: 多个文件同时下载 27 | * ————————————————————————————————————— 28 | */ 29 | public class MultipleActivity extends AppCompatActivity { 30 | 31 | private RecyclerView recycler_view; 32 | private DownloadAdapter mAdapter; 33 | private List mData = new ArrayList<>(); 34 | 35 | @Override 36 | protected void onCreate(@Nullable Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_recycler); 39 | EventBus.getDefault().register(this); 40 | recycler_view = findViewById(R.id.recycler_view); 41 | 42 | mData.add(new DownloadInfo(Constant.URL_1)); 43 | mData.add(new DownloadInfo(Constant.URL_2)); 44 | mData.add(new DownloadInfo(Constant.URL_3)); 45 | mData.add(new DownloadInfo(Constant.URL_4)); 46 | mData.add(new DownloadInfo(Constant.URL_5)); 47 | mData.add(new DownloadInfo(Constant.URL_6)); 48 | mData.add(new DownloadInfo(Constant.URL_7)); 49 | mData.add(new DownloadInfo(Constant.URL_8)); 50 | mData.add(new DownloadInfo(Constant.URL_9)); 51 | 52 | 53 | mAdapter = new DownloadAdapter(mData); 54 | recycler_view.setLayoutManager(new LinearLayoutManager(this)); 55 | recycler_view.setAdapter(mAdapter); 56 | // 取消item刷新的动画 57 | ((SimpleItemAnimator)recycler_view.getItemAnimator()).setSupportsChangeAnimations(false); 58 | 59 | } 60 | 61 | @Subscribe (threadMode = ThreadMode.MAIN) 62 | public void update(DownloadInfo info){ 63 | if (DownloadInfo.DOWNLOAD.equals(info.getDownloadStatus())){ 64 | 65 | mAdapter.updateProgress(info); 66 | 67 | }else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){ 68 | 69 | mAdapter.updateProgress(info); 70 | 71 | }else if (DownloadInfo.DOWNLOAD_PAUSE.equals(info.getDownloadStatus())){ 72 | 73 | Toast.makeText(this,"下载暂停",Toast.LENGTH_SHORT).show(); 74 | 75 | }else if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){ 76 | 77 | mAdapter.updateProgress(info); 78 | Toast.makeText(this,"下载取消",Toast.LENGTH_SHORT).show(); 79 | 80 | }else if (DownloadInfo.DOWNLOAD_ERROR.equals(info.getDownloadStatus())){ 81 | 82 | Toast.makeText(this,"下载出错",Toast.LENGTH_SHORT).show(); 83 | } 84 | } 85 | 86 | @Override 87 | protected void onDestroy() { 88 | super.onDestroy(); 89 | EventBus.getDefault().unregister(this); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/SingleActivity.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.Toast; 10 | 11 | import com.zs.demo.downloadfile.download.DownloadInfo; 12 | import com.zs.demo.downloadfile.download.DownloadManager; 13 | 14 | import org.greenrobot.eventbus.EventBus; 15 | import org.greenrobot.eventbus.Subscribe; 16 | import org.greenrobot.eventbus.ThreadMode; 17 | 18 | /** 19 | * Created by zs 20 | * Date:2018年 09月 12日 21 | * Time:17:17 22 | * ————————————————————————————————————— 23 | * About:单个下载 24 | * ————————————————————————————————————— 25 | */ 26 | public class SingleActivity extends AppCompatActivity{ 27 | 28 | private ProgressBar main_progress; 29 | private Button main_btn_down; 30 | private Button main_btn_pause; 31 | private Button main_btn_cancel; 32 | private DownloadInfo downloadInfo; 33 | 34 | @Override 35 | protected void onCreate(@Nullable Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_single); 38 | EventBus.getDefault().register(this); 39 | main_progress = findViewById(R.id.main_progress); 40 | main_btn_down = findViewById(R.id.main_btn_down); 41 | main_btn_pause = findViewById(R.id.main_btn_pause); 42 | main_btn_cancel = findViewById(R.id.main_btn_cancel); 43 | 44 | main_btn_down.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View view) { 47 | DownloadManager.getInstance().download(Constant.URL_1); 48 | } 49 | }); 50 | 51 | main_btn_pause.setOnClickListener(new View.OnClickListener() { 52 | @Override 53 | public void onClick(View view) { 54 | DownloadManager.getInstance().pauseDownload(Constant.URL_1); 55 | } 56 | }); 57 | 58 | main_btn_cancel.setOnClickListener(new View.OnClickListener() { 59 | @Override 60 | public void onClick(View view) { 61 | DownloadManager.getInstance().cancelDownload(downloadInfo); 62 | } 63 | }); 64 | } 65 | 66 | @Subscribe(threadMode = ThreadMode.MAIN) 67 | public void update(DownloadInfo info){ 68 | if (info.getUrl() != Constant.URL_1){ 69 | return; 70 | } 71 | if (DownloadInfo.DOWNLOAD.equals(info.getDownloadStatus())){ 72 | downloadInfo = info; 73 | if (info.getTotal() == 0){ 74 | main_progress.setProgress(0); 75 | }else{ 76 | float progress = info.getProgress() * main_progress.getMax() / info.getTotal(); 77 | main_progress.setProgress((int) progress); 78 | } 79 | 80 | }else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){ 81 | 82 | main_progress.setProgress(main_progress.getMax()); 83 | 84 | }else if (DownloadInfo.DOWNLOAD_PAUSE.equals(info.getDownloadStatus())){ 85 | 86 | Toast.makeText(this,"下载暂停",Toast.LENGTH_SHORT).show(); 87 | 88 | }else if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){ 89 | 90 | main_progress.setProgress(0); 91 | Toast.makeText(this,"下载取消",Toast.LENGTH_SHORT).show(); 92 | 93 | }else if (DownloadInfo.DOWNLOAD_ERROR.equals(info.getDownloadStatus())){ 94 | 95 | Toast.makeText(this,"下载出错",Toast.LENGTH_SHORT).show(); 96 | 97 | } 98 | } 99 | 100 | @Override 101 | protected void onDestroy() { 102 | super.onDestroy(); 103 | EventBus.getDefault().unregister(this); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/adapter/DownloadAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.Button; 7 | import android.widget.ProgressBar; 8 | 9 | import com.zs.demo.downloadfile.R; 10 | import com.zs.demo.downloadfile.download.DownloadInfo; 11 | import com.zs.demo.downloadfile.download.DownloadManager; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Created by zs 17 | * Date:2018年 09月 11日 18 | * Time:18:06 19 | * ————————————————————————————————————— 20 | * About: 21 | * ————————————————————————————————————— 22 | */ 23 | public class DownloadAdapter extends RecyclerView.Adapter { 24 | 25 | private List mdata; 26 | 27 | public DownloadAdapter(List mdata) { 28 | this.mdata = mdata; 29 | } 30 | 31 | /** 32 | * 更新下载进度 33 | * @param info 34 | */ 35 | public void updateProgress(DownloadInfo info){ 36 | for (int i = 0; i < mdata.size(); i++){ 37 | if (mdata.get(i).getUrl().equals(info.getUrl())){ 38 | mdata.set(i,info); 39 | notifyItemChanged(i); 40 | break; 41 | } 42 | } 43 | } 44 | 45 | @Override 46 | public UploadHolder onCreateViewHolder(ViewGroup parent, int viewType) { 47 | View view = View.inflate(parent.getContext(), R.layout.item_download_layout,null); 48 | return new UploadHolder(view); 49 | } 50 | 51 | @Override 52 | public void onBindViewHolder(UploadHolder holder, int position) { 53 | 54 | final DownloadInfo info = mdata.get(position); 55 | if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){ 56 | holder.main_progress.setProgress(0); 57 | }else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){ 58 | holder.main_progress.setProgress(holder.main_progress.getMax()); 59 | }else { 60 | if (info.getTotal() == 0){ 61 | holder.main_progress.setProgress(0); 62 | }else { 63 | float d = info.getProgress() * holder.main_progress.getMax() / info.getTotal(); 64 | holder.main_progress.setProgress((int) d); 65 | } 66 | } 67 | holder.main_btn_down.setOnClickListener(new View.OnClickListener() { 68 | @Override 69 | public void onClick(View view) { 70 | DownloadManager.getInstance().download(info.getUrl()); 71 | } 72 | }); 73 | 74 | holder.main_btn_pause.setOnClickListener(new View.OnClickListener() { 75 | @Override 76 | public void onClick(View view) { 77 | DownloadManager.getInstance().pauseDownload(info.getUrl()); 78 | } 79 | }); 80 | 81 | holder.main_btn_cancel.setOnClickListener(new View.OnClickListener() { 82 | @Override 83 | public void onClick(View view) { 84 | DownloadManager.getInstance().cancelDownload(info); 85 | } 86 | }); 87 | } 88 | 89 | @Override 90 | public int getItemCount() { 91 | return mdata.size(); 92 | } 93 | 94 | public class UploadHolder extends RecyclerView.ViewHolder{ 95 | 96 | private ProgressBar main_progress; 97 | private Button main_btn_down; 98 | private Button main_btn_pause; 99 | private Button main_btn_cancel; 100 | 101 | public UploadHolder(View itemView) { 102 | super(itemView); 103 | main_progress = itemView.findViewById(R.id.main_progress); 104 | main_btn_down = itemView.findViewById(R.id.main_btn_down); 105 | main_btn_pause = itemView.findViewById(R.id.main_btn_pause); 106 | main_btn_cancel = itemView.findViewById(R.id.main_btn_cancel); 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/adapter/LimitDownloadAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | 10 | import com.zs.demo.downloadfile.R; 11 | import com.zs.demo.downloadfile.download.DownloadInfo; 12 | import com.zs.demo.downloadfile.download.limit.DownloadLimitManager; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Created by zs 18 | * Date:2018年 09月 11日 19 | * Time:18:06 20 | * ————————————————————————————————————— 21 | * About: 22 | * ————————————————————————————————————— 23 | */ 24 | public class LimitDownloadAdapter extends RecyclerView.Adapter { 25 | 26 | private Context mContext; 27 | private List mdata; 28 | 29 | public LimitDownloadAdapter(Context context , List mdata) { 30 | this.mContext = context; 31 | this.mdata = mdata; 32 | } 33 | 34 | /** 35 | * 更新下载进度 36 | * @param info 37 | */ 38 | public void updateProgress(DownloadInfo info){ 39 | for (int i = 0; i < mdata.size(); i++){ 40 | if (mdata.get(i).getUrl().equals(info.getUrl())){ 41 | mdata.set(i,info); 42 | notifyItemChanged(i); 43 | break; 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public UploadHolder onCreateViewHolder(ViewGroup parent, int viewType) { 50 | View view = View.inflate(parent.getContext(), R.layout.item_download_layout,null); 51 | return new UploadHolder(view); 52 | } 53 | 54 | @Override 55 | public void onBindViewHolder(UploadHolder holder, int position) { 56 | 57 | final DownloadInfo info = mdata.get(position); 58 | if (DownloadLimitManager.getInstance().getWaitUrl(info.getUrl())){ 59 | holder.main_btn_down.setText("等待"); 60 | }else if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){ 61 | holder.main_progress.setProgress(0); 62 | holder.main_btn_down.setText("下载"); 63 | }else if (DownloadInfo.DOWNLOAD_PAUSE.equals(info.getDownloadStatus())){ 64 | holder.main_btn_down.setText("等待"); 65 | }else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){ 66 | holder.main_progress.setProgress(holder.main_progress.getMax()); 67 | holder.main_btn_down.setText("完成"); 68 | }else { 69 | if (info.getTotal() == 0){ 70 | holder.main_progress.setProgress(0); 71 | }else { 72 | float d = info.getProgress() * holder.main_progress.getMax() / info.getTotal(); 73 | holder.main_progress.setProgress((int) d); 74 | holder.main_btn_down.setText("下载中"); 75 | } 76 | } 77 | holder.main_btn_down.setOnClickListener(new View.OnClickListener() { 78 | @Override 79 | public void onClick(View view) { 80 | // ((LimitActivity)mContext).download(info.getUrl()); 81 | DownloadLimitManager.getInstance().download(info.getUrl()); 82 | } 83 | }); 84 | 85 | holder.main_btn_pause.setOnClickListener(new View.OnClickListener() { 86 | @Override 87 | public void onClick(View view) { 88 | DownloadLimitManager.getInstance().pauseDownload(info.getUrl()); 89 | } 90 | }); 91 | 92 | holder.main_btn_cancel.setOnClickListener(new View.OnClickListener() { 93 | @Override 94 | public void onClick(View view) { 95 | DownloadLimitManager.getInstance().cancelDownload(info); 96 | } 97 | }); 98 | } 99 | 100 | @Override 101 | public int getItemCount() { 102 | return mdata.size(); 103 | } 104 | 105 | public class UploadHolder extends RecyclerView.ViewHolder{ 106 | 107 | private ProgressBar main_progress; 108 | private Button main_btn_down; 109 | private Button main_btn_pause; 110 | private Button main_btn_cancel; 111 | 112 | public UploadHolder(View itemView) { 113 | super(itemView); 114 | main_progress = itemView.findViewById(R.id.main_progress); 115 | main_btn_down = itemView.findViewById(R.id.main_btn_down); 116 | main_btn_pause = itemView.findViewById(R.id.main_btn_pause); 117 | main_btn_cancel = itemView.findViewById(R.id.main_btn_cancel); 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/download/DownloadIO.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.download; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | /** 7 | * Created by zs 8 | * Date:2018年 09月 12日 9 | * Time:14:26 10 | * ————————————————————————————————————— 11 | * About: 12 | * ————————————————————————————————————— 13 | */ 14 | public class DownloadIO { 15 | public static void closeAll(Closeable... closeables){ 16 | if(closeables == null){ 17 | return; 18 | } 19 | for (Closeable closeable : closeables) { 20 | if(closeable!=null){ 21 | try { 22 | closeable.close(); 23 | } catch (IOException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/download/DownloadInfo.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.download; 2 | 3 | /** 4 | * Created by zs 5 | * Date:2018年 09月 12日 6 | * Time:13:50 7 | * ————————————————————————————————————— 8 | * About: 下载管理 9 | * ————————————————————————————————————— 10 | */ 11 | public class DownloadInfo { 12 | 13 | /** 14 | * 下载状态 15 | */ 16 | public static final String DOWNLOAD = "download"; // 下载中 17 | public static final String DOWNLOAD_PAUSE = "pause"; // 下载暂停 18 | public static final String DOWNLOAD_WAIT = "wait"; // 等待下载 19 | public static final String DOWNLOAD_CANCEL = "cancel"; // 下载取消 20 | public static final String DOWNLOAD_OVER = "over"; // 下载结束 21 | public static final String DOWNLOAD_ERROR = "error"; // 下载出错 22 | 23 | public static final long TOTAL_ERROR = -1;//获取进度失败 24 | 25 | private String url; 26 | private String fileName; 27 | private String downloadStatus; 28 | private long total; 29 | private long progress; 30 | 31 | public DownloadInfo(String url) { 32 | this.url = url; 33 | } 34 | 35 | public DownloadInfo(String url, String downloadStatus) { 36 | this.url = url; 37 | this.downloadStatus = downloadStatus; 38 | } 39 | 40 | public String getUrl() { 41 | return url; 42 | } 43 | 44 | public String getFileName() { 45 | return fileName; 46 | } 47 | 48 | public void setFileName(String fileName) { 49 | this.fileName = fileName; 50 | } 51 | 52 | public long getTotal() { 53 | return total; 54 | } 55 | 56 | public void setTotal(long total) { 57 | this.total = total; 58 | } 59 | 60 | public long getProgress() { 61 | return progress; 62 | } 63 | 64 | public void setProgress(long progress) { 65 | this.progress = progress; 66 | } 67 | 68 | public String getDownloadStatus() { 69 | return downloadStatus; 70 | } 71 | 72 | public void setDownloadStatus(String downloadStatus) { 73 | this.downloadStatus = downloadStatus; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/download/DownloadManager.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.download; 2 | 3 | import com.zs.demo.downloadfile.Constant; 4 | 5 | import org.greenrobot.eventbus.EventBus; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | import io.reactivex.Observable; 15 | import io.reactivex.ObservableEmitter; 16 | import io.reactivex.ObservableOnSubscribe; 17 | import io.reactivex.ObservableSource; 18 | import io.reactivex.android.schedulers.AndroidSchedulers; 19 | import io.reactivex.functions.Function; 20 | import io.reactivex.functions.Predicate; 21 | import io.reactivex.schedulers.Schedulers; 22 | import okhttp3.Call; 23 | import okhttp3.OkHttpClient; 24 | import okhttp3.Request; 25 | import okhttp3.Response; 26 | 27 | /** 28 | * Created by zs 29 | * Date:2018年 09月 12日 30 | * Time:13:56 31 | * ————————————————————————————————————— 32 | * About: 下载管理 33 | * ————————————————————————————————————— 34 | */ 35 | public class DownloadManager { 36 | 37 | private static final AtomicReference INSTANCE = new AtomicReference<>(); 38 | private OkHttpClient mClient; 39 | private HashMap downCalls; //用来存放各个下载的请求 40 | 41 | public static DownloadManager getInstance() { 42 | for (; ; ) { 43 | DownloadManager current = INSTANCE.get(); 44 | if (current != null) { 45 | return current; 46 | } 47 | current = new DownloadManager(); 48 | if (INSTANCE.compareAndSet(null, current)) { 49 | return current; 50 | } 51 | } 52 | } 53 | 54 | private DownloadManager() { 55 | downCalls = new HashMap<>(); 56 | mClient = new OkHttpClient.Builder().build(); 57 | } 58 | 59 | /** 60 | * 查看是否在下载任务中 61 | * @param url 62 | * @return 63 | */ 64 | public boolean getDownloadUrl(String url){ 65 | return downCalls.containsKey(url); 66 | } 67 | 68 | /** 69 | * 开始下载 70 | * 71 | * @param url 下载请求的网址 72 | */ 73 | public void download(String url) { 74 | 75 | Observable.just(url) 76 | .filter(new Predicate() { // 过滤 call的map中已经有了,就证明正在下载,则这次不下载 77 | @Override 78 | public boolean test(String s) { 79 | return !downCalls.containsKey(s); 80 | } 81 | }) 82 | .map(new Function() { // 生成 DownloadInfo 83 | @Override 84 | public DownloadInfo apply(String s) { 85 | return createDownInfo(s); 86 | } 87 | }) 88 | .map(new Function() { // 如果已经下载,重新命名 89 | @Override 90 | public DownloadInfo apply(Object o) { 91 | return getRealFileName((DownloadInfo)o); 92 | } 93 | }) 94 | .flatMap(new Function>() { // 下载 95 | @Override 96 | public ObservableSource apply(DownloadInfo downloadInfo) { 97 | return Observable.create(new DownloadSubscribe(downloadInfo)); 98 | } 99 | }) 100 | .observeOn(AndroidSchedulers.mainThread()) // 在主线程中回调 101 | .subscribeOn(Schedulers.io()) // 在子线程中执行 102 | .subscribe(new DownloadObserver()); // 添加观察者,监听下载进度 103 | } 104 | 105 | /** 106 | * 下载取消或者暂停 107 | * @param url 108 | */ 109 | public void pauseDownload(String url) { 110 | Call call = downCalls.get(url); 111 | if (call != null) { 112 | call.cancel();//取消 113 | } 114 | downCalls.remove(url); 115 | } 116 | 117 | /** 118 | * 取消下载 删除本地文件 119 | * @param info 120 | */ 121 | public void cancelDownload(DownloadInfo info){ 122 | pauseDownload(info.getUrl()); 123 | info.setProgress(0); 124 | info.setDownloadStatus(DownloadInfo.DOWNLOAD_CANCEL); 125 | EventBus.getDefault().post(info); 126 | Constant.deleteFile(info.getFileName()); 127 | } 128 | 129 | /** 130 | * 创建DownInfo 131 | * 132 | * @param url 请求网址 133 | * @return DownInfo 134 | */ 135 | private DownloadInfo createDownInfo(String url) { 136 | DownloadInfo downloadInfo = new DownloadInfo(url); 137 | long contentLength = getContentLength(url);//获得文件大小 138 | downloadInfo.setTotal(contentLength); 139 | String fileName = url.substring(url.lastIndexOf("/")); 140 | downloadInfo.setFileName(fileName); 141 | return downloadInfo; 142 | } 143 | 144 | /** 145 | * 如果文件已下载重新命名新文件名 146 | * @param downloadInfo 147 | * @return 148 | */ 149 | private DownloadInfo getRealFileName(DownloadInfo downloadInfo) { 150 | String fileName = downloadInfo.getFileName(); 151 | long downloadLength = 0, contentLength = downloadInfo.getTotal(); 152 | File path = new File(Constant.FILE_PATH); 153 | if (!path.exists()) { 154 | path.mkdir(); 155 | } 156 | File file = new File(Constant.FILE_PATH, fileName); 157 | if (file.exists()) { 158 | //找到了文件,代表已经下载过,则获取其长度 159 | downloadLength = file.length(); 160 | } 161 | //之前下载过,需要重新来一个文件 162 | int i = 1; 163 | while (downloadLength >= contentLength) { 164 | int dotIndex = fileName.lastIndexOf("."); 165 | String fileNameOther; 166 | if (dotIndex == -1) { 167 | fileNameOther = fileName + "(" + i + ")"; 168 | } else { 169 | fileNameOther = fileName.substring(0, dotIndex) 170 | + "(" + i + ")" + fileName.substring(dotIndex); 171 | } 172 | File newFile = new File(Constant.FILE_PATH, fileNameOther); 173 | file = newFile; 174 | downloadLength = newFile.length(); 175 | i++; 176 | } 177 | //设置改变过的文件名/大小 178 | downloadInfo.setProgress(downloadLength); 179 | downloadInfo.setFileName(file.getName()); 180 | return downloadInfo; 181 | } 182 | 183 | private class DownloadSubscribe implements ObservableOnSubscribe { 184 | private DownloadInfo downloadInfo; 185 | 186 | public DownloadSubscribe(DownloadInfo downloadInfo) { 187 | this.downloadInfo = downloadInfo; 188 | } 189 | 190 | @Override 191 | public void subscribe(ObservableEmitter e) throws Exception { 192 | String url = downloadInfo.getUrl(); 193 | long downloadLength = downloadInfo.getProgress();//已经下载好的长度 194 | long contentLength = downloadInfo.getTotal();//文件的总长度 195 | //初始进度信息 196 | e.onNext(downloadInfo); 197 | Request request = new Request.Builder() 198 | //确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分 199 | .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength) 200 | .url(url) 201 | .build(); 202 | Call call = mClient.newCall(request); 203 | downCalls.put(url, call);//把这个添加到call里,方便取消 204 | 205 | Response response = call.execute(); 206 | File file = new File(Constant.FILE_PATH, downloadInfo.getFileName()); 207 | InputStream is = null; 208 | FileOutputStream fileOutputStream = null; 209 | try { 210 | is = response.body().byteStream(); 211 | fileOutputStream = new FileOutputStream(file, true); 212 | byte[] buffer = new byte[2048];//缓冲数组2kB 213 | int len; 214 | while ((len = is.read(buffer)) != -1) { 215 | fileOutputStream.write(buffer, 0, len); 216 | downloadLength += len; 217 | downloadInfo.setProgress(downloadLength); 218 | e.onNext(downloadInfo); 219 | } 220 | fileOutputStream.flush(); 221 | downCalls.remove(url); 222 | } finally { 223 | //关闭IO流 224 | DownloadIO.closeAll(is, fileOutputStream); 225 | 226 | } 227 | e.onComplete();//完成 228 | } 229 | } 230 | 231 | 232 | 233 | /** 234 | * 获取下载长度 235 | * 236 | * @param downloadUrl 237 | * @return 238 | */ 239 | private long getContentLength(String downloadUrl) { 240 | Request request = new Request.Builder() 241 | .url(downloadUrl) 242 | .build(); 243 | try { 244 | Response response = mClient.newCall(request).execute(); 245 | if (response != null && response.isSuccessful()) { 246 | long contentLength = response.body().contentLength(); 247 | response.close(); 248 | return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength; 249 | } 250 | } catch (IOException e) { 251 | e.printStackTrace(); 252 | } 253 | return DownloadInfo.TOTAL_ERROR; 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/download/DownloadObserver.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.download; 2 | 3 | import android.util.Log; 4 | 5 | import org.greenrobot.eventbus.EventBus; 6 | 7 | import io.reactivex.Observer; 8 | import io.reactivex.disposables.Disposable; 9 | 10 | /** 11 | * Created by zs 12 | * Date:2018年 09月 12日 13 | * Time:13:50 14 | * ————————————————————————————————————— 15 | * About: 观察者 16 | * ————————————————————————————————————— 17 | */ 18 | public class DownloadObserver implements Observer { 19 | 20 | public Disposable d;//可以用于取消注册的监听者 21 | public DownloadInfo downloadInfo; 22 | 23 | @Override 24 | public void onSubscribe(Disposable d) { 25 | this.d = d; 26 | } 27 | 28 | @Override 29 | public void onNext(DownloadInfo value) { 30 | this.downloadInfo = value; 31 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD); 32 | EventBus.getDefault().post(downloadInfo); 33 | } 34 | 35 | @Override 36 | public void onError(Throwable e) { 37 | Log.d("My_Log","onError"); 38 | if (DownloadManager.getInstance().getDownloadUrl(downloadInfo.getUrl())){ 39 | DownloadManager.getInstance().pauseDownload(downloadInfo.getUrl()); 40 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ERROR); 41 | EventBus.getDefault().post(downloadInfo); 42 | }else{ 43 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_PAUSE); 44 | EventBus.getDefault().post(downloadInfo); 45 | } 46 | 47 | } 48 | 49 | @Override 50 | public void onComplete() { 51 | Log.d("My_Log","onComplete"); 52 | if (downloadInfo != null){ 53 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_OVER); 54 | EventBus.getDefault().post(downloadInfo); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/download/limit/DownloadLimitManager.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.download.limit; 2 | 3 | import com.zs.demo.downloadfile.Constant; 4 | import com.zs.demo.downloadfile.download.DownloadIO; 5 | import com.zs.demo.downloadfile.download.DownloadInfo; 6 | 7 | import org.greenrobot.eventbus.EventBus; 8 | 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | 18 | import io.reactivex.Observable; 19 | import io.reactivex.ObservableEmitter; 20 | import io.reactivex.ObservableOnSubscribe; 21 | import io.reactivex.ObservableSource; 22 | import io.reactivex.android.schedulers.AndroidSchedulers; 23 | import io.reactivex.functions.Function; 24 | import io.reactivex.functions.Predicate; 25 | import io.reactivex.schedulers.Schedulers; 26 | import okhttp3.Call; 27 | import okhttp3.OkHttpClient; 28 | import okhttp3.Request; 29 | import okhttp3.Response; 30 | 31 | /** 32 | * Created by zs 33 | * Date:2018年 09月 12日 34 | * Time:13:56 35 | * ————————————————————————————————————— 36 | * About: 下载管理(限制最大个数) 37 | * ————————————————————————————————————— 38 | */ 39 | public class DownloadLimitManager { 40 | 41 | private static final AtomicReference INSTANCE = new AtomicReference<>(); 42 | private OkHttpClient mClient; 43 | private HashMap downCalls; // 用来存放各个下载的请求 44 | private List downWait; // 用来存放等待下载的请求 45 | private int maxCount = 2; // 同时下载的最大个数 46 | 47 | public static DownloadLimitManager getInstance() { 48 | for (; ; ) { 49 | DownloadLimitManager current = INSTANCE.get(); 50 | if (current != null) { 51 | return current; 52 | } 53 | current = new DownloadLimitManager(); 54 | if (INSTANCE.compareAndSet(null, current)) { 55 | return current; 56 | } 57 | } 58 | } 59 | 60 | private DownloadLimitManager() { 61 | downCalls = new HashMap<>(); 62 | mClient = new OkHttpClient.Builder().build(); 63 | downWait = new ArrayList<>(); 64 | } 65 | 66 | /** 67 | * 查看是否在下载任务中 68 | * @param url 69 | * @return 70 | */ 71 | public boolean getDownloadUrl(String url){ 72 | return downCalls.containsKey(url); 73 | } 74 | 75 | /** 76 | * 查看是否在等待任务中 77 | * @param url 78 | * @return 79 | */ 80 | public boolean getWaitUrl(String url){ 81 | for (String item : downWait){ 82 | if (item.equals(url)){ 83 | return true; 84 | } 85 | } 86 | return false; 87 | } 88 | 89 | /** 90 | * 开始下载 91 | * 92 | * @param url 下载请求的网址 93 | */ 94 | public void download(String url) { 95 | 96 | Observable.just(url) 97 | .filter(new Predicate() { // 过滤 call的map中已经有了,就证明正在下载,则这次不下载 98 | @Override 99 | public boolean test(String s) { 100 | boolean flag = downCalls.containsKey(s); 101 | if (flag){ 102 | // 如果已经在下载,查找下一个文件进行下载 103 | downNext(); 104 | return false; 105 | }else{ 106 | // 判断如果正在下载的个数达到最大限制,存到等下下载列表中 107 | if (downCalls.size() >= maxCount){ 108 | if (!getWaitUrl(s)){ 109 | downWait.add(s); 110 | DownloadInfo info = new DownloadInfo(s , DownloadInfo.DOWNLOAD_WAIT); 111 | EventBus.getDefault().post(info); 112 | } 113 | return false; 114 | }else{ 115 | return true; 116 | } 117 | } 118 | } 119 | }) 120 | .flatMap(new Function>() { // 生成 DownloadInfo 121 | @Override 122 | public ObservableSource apply(String s) { 123 | return Observable.just(createDownInfo(s)); 124 | } 125 | }) 126 | .map(new Function() { // 如果已经下载,重新命名 127 | @Override 128 | public DownloadInfo apply(Object o) { 129 | return getRealFileName((DownloadInfo)o); 130 | } 131 | }) 132 | .flatMap(new Function>() { // 下载 133 | @Override 134 | public ObservableSource apply(DownloadInfo downloadInfo) { 135 | return Observable.create(new DownloadSubscribe(downloadInfo)); 136 | } 137 | }) 138 | .observeOn(AndroidSchedulers.mainThread()) // 在主线程中回调 139 | .subscribeOn(Schedulers.io()) // 在子线程中执行 140 | .subscribe(new DownloadLimitObserver()); // 添加观察者,监听下载进度 141 | 142 | } 143 | 144 | /** 145 | * 下载等待下载中的第一条 146 | */ 147 | public void downNext(){ 148 | if (downCalls.size() < maxCount && downWait.size() > 0){ 149 | download(downWait.get(0)); 150 | downWait.remove(0); 151 | } 152 | } 153 | 154 | /** 155 | * 下载取消或者暂停 156 | * @param url 157 | */ 158 | public void pauseDownload(String url) { 159 | Call call = downCalls.get(url); 160 | if (call != null) { 161 | call.cancel();//取消 162 | } 163 | downCalls.remove(url); 164 | downNext(); 165 | } 166 | 167 | /** 168 | * 取消下载 删除本地文件 169 | * @param info 170 | */ 171 | public void cancelDownload(DownloadInfo info){ 172 | pauseDownload(info.getUrl()); 173 | info.setProgress(0); 174 | info.setDownloadStatus(DownloadInfo.DOWNLOAD_CANCEL); 175 | EventBus.getDefault().post(info); 176 | Constant.deleteFile(info.getFileName()); 177 | } 178 | 179 | /** 180 | * 创建DownInfo 181 | * 182 | * @param url 请求网址 183 | * @return DownInfo 184 | */ 185 | private DownloadInfo createDownInfo(String url) { 186 | DownloadInfo downloadInfo = new DownloadInfo(url); 187 | long contentLength = getContentLength(url);//获得文件大小 188 | downloadInfo.setTotal(contentLength); 189 | String fileName = url.substring(url.lastIndexOf("/")); 190 | downloadInfo.setFileName(fileName); 191 | return downloadInfo; 192 | } 193 | 194 | /** 195 | * 如果文件已下载重新命名新文件名 196 | * @param downloadInfo 197 | * @return 198 | */ 199 | private DownloadInfo getRealFileName(DownloadInfo downloadInfo) { 200 | String fileName = downloadInfo.getFileName(); 201 | long downloadLength = 0, contentLength = downloadInfo.getTotal(); 202 | File path = new File(Constant.FILE_PATH); 203 | if (!path.exists()) { 204 | path.mkdir(); 205 | } 206 | File file = new File(Constant.FILE_PATH, fileName); 207 | if (file.exists()) { 208 | //找到了文件,代表已经下载过,则获取其长度 209 | downloadLength = file.length(); 210 | } 211 | //之前下载过,需要重新来一个文件 212 | int i = 1; 213 | while (downloadLength >= contentLength) { 214 | int dotIndex = fileName.lastIndexOf("."); 215 | String fileNameOther; 216 | if (dotIndex == -1) { 217 | fileNameOther = fileName + "(" + i + ")"; 218 | } else { 219 | fileNameOther = fileName.substring(0, dotIndex) 220 | + "(" + i + ")" + fileName.substring(dotIndex); 221 | } 222 | File newFile = new File(Constant.FILE_PATH, fileNameOther); 223 | file = newFile; 224 | downloadLength = newFile.length(); 225 | i++; 226 | } 227 | //设置改变过的文件名/大小 228 | downloadInfo.setProgress(downloadLength); 229 | downloadInfo.setFileName(file.getName()); 230 | return downloadInfo; 231 | } 232 | 233 | private class DownloadSubscribe implements ObservableOnSubscribe { 234 | private DownloadInfo downloadInfo; 235 | 236 | public DownloadSubscribe(DownloadInfo downloadInfo) { 237 | this.downloadInfo = downloadInfo; 238 | } 239 | 240 | @Override 241 | public void subscribe(ObservableEmitter e) throws Exception { 242 | String url = downloadInfo.getUrl(); 243 | long downloadLength = downloadInfo.getProgress();//已经下载好的长度 244 | long contentLength = downloadInfo.getTotal();//文件的总长度 245 | //初始进度信息 246 | e.onNext(downloadInfo); 247 | Request request = new Request.Builder() 248 | //确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分 249 | .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength) 250 | .url(url) 251 | .build(); 252 | Call call = mClient.newCall(request); 253 | downCalls.put(url, call);//把这个添加到call里,方便取消 254 | 255 | Response response = call.execute(); 256 | File file = new File(Constant.FILE_PATH, downloadInfo.getFileName()); 257 | InputStream is = null; 258 | FileOutputStream fileOutputStream = null; 259 | try { 260 | is = response.body().byteStream(); 261 | fileOutputStream = new FileOutputStream(file, true); 262 | byte[] buffer = new byte[2048];//缓冲数组2kB 263 | int len; 264 | while ((len = is.read(buffer)) != -1) { 265 | fileOutputStream.write(buffer, 0, len); 266 | downloadLength += len; 267 | downloadInfo.setProgress(downloadLength); 268 | e.onNext(downloadInfo); 269 | } 270 | fileOutputStream.flush(); 271 | downCalls.remove(url); 272 | downNext(); 273 | } finally { 274 | //关闭IO流 275 | DownloadIO.closeAll(is, fileOutputStream); 276 | 277 | } 278 | e.onComplete();//完成 279 | } 280 | } 281 | 282 | 283 | 284 | /** 285 | * 获取下载长度 286 | * 287 | * @param downloadUrl 288 | * @return 289 | */ 290 | private long getContentLength(String downloadUrl) { 291 | Request request = new Request.Builder() 292 | .url(downloadUrl) 293 | .build(); 294 | try { 295 | Response response = mClient.newCall(request).execute(); 296 | if (response != null && response.isSuccessful()) { 297 | long contentLength = response.body().contentLength(); 298 | response.close(); 299 | return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength; 300 | } 301 | } catch (IOException e) { 302 | e.printStackTrace(); 303 | } 304 | return DownloadInfo.TOTAL_ERROR; 305 | } 306 | 307 | } 308 | -------------------------------------------------------------------------------- /app/src/main/java/com/zs/demo/downloadfile/download/limit/DownloadLimitObserver.java: -------------------------------------------------------------------------------- 1 | package com.zs.demo.downloadfile.download.limit; 2 | 3 | import android.util.Log; 4 | 5 | import com.zs.demo.downloadfile.download.DownloadInfo; 6 | 7 | import org.greenrobot.eventbus.EventBus; 8 | 9 | import io.reactivex.Observer; 10 | import io.reactivex.disposables.Disposable; 11 | 12 | /** 13 | * Created by zs 14 | * Date:2018年 09月 12日 15 | * Time:13:50 16 | * ————————————————————————————————————— 17 | * About: 观察者 18 | * ————————————————————————————————————— 19 | */ 20 | public class DownloadLimitObserver implements Observer { 21 | 22 | public Disposable d;//可以用于取消注册的监听者 23 | public DownloadInfo downloadInfo; 24 | 25 | @Override 26 | public void onSubscribe(Disposable d) { 27 | this.d = d; 28 | } 29 | 30 | @Override 31 | public void onNext(DownloadInfo value) { 32 | this.downloadInfo = value; 33 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD); 34 | EventBus.getDefault().post(downloadInfo); 35 | } 36 | 37 | @Override 38 | public void onError(Throwable e) { 39 | Log.d("My_Log","onError"); 40 | if (DownloadLimitManager.getInstance().getDownloadUrl(downloadInfo.getUrl())){ 41 | DownloadLimitManager.getInstance().pauseDownload(downloadInfo.getUrl()); 42 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ERROR); 43 | EventBus.getDefault().post(downloadInfo); 44 | }else{ 45 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_PAUSE); 46 | EventBus.getDefault().post(downloadInfo); 47 | } 48 | 49 | } 50 | 51 | @Override 52 | public void onComplete() { 53 | Log.d("My_Log","onComplete"); 54 | if (downloadInfo != null){ 55 | downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_OVER); 56 | EventBus.getDefault().post(downloadInfo); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |