package com.gh.download.cache; import com.danikula.videocache.file.FileNameGenerator; import com.danikula.videocache.file.Md5FileNameGenerator; import com.gh.common.AppExecutor; import com.halo.assistant.HaloApp; import com.shuyu.gsyvideoplayer.utils.StorageUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class CacheManager { private static final AtomicReference INSTANCE = new AtomicReference<>(); private volatile ConcurrentHashMap downCalls; private OkHttpClient mClient; private File cacheDirectory = StorageUtils.getIndividualCacheDirectory(HaloApp.getInstance().getApplication()); private FileNameGenerator generator = new Md5FileNameGenerator(); private final String TEMP_POSTFIX = ".download"; // private final int preLength = 5 * 1024 * 1024;//预加载大小 public static CacheManager getInstance() { for (; ; ) { CacheManager current = INSTANCE.get(); if (current != null) { return current; } current = new CacheManager(); if (INSTANCE.compareAndSet(null, current)) { return current; } } } private CacheManager() { downCalls = new ConcurrentHashMap<>(); mClient = new OkHttpClient.Builder().build(); } private synchronized ConcurrentHashMap getDownCalls() { return downCalls; } public void download(String url, CacheObserver cacheObserver) { //当前url已下载完成则不再下载 for (File file : getAllFile()) { if (file.getName().equals(generator.generate(url))) { return; } } Observable.just(url) .filter(s -> !getDownCalls().containsKey(s))//call的map已经有了,就证明正在下载,则这次不下载 .flatMap(s -> Observable.just(createDownInfo(s))) .map(this::getRealFileName) .flatMap(cacheInfo -> Observable.create(new DownloadSubscribe(cacheInfo))) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(cacheObserver); } public void cancel(String url) { Call call = getDownCalls().get(url); if (call != null) { call.cancel(); } getDownCalls().remove(url); } /** * 创建DownInfo * * @param url 请求网址 * @return DownInfo */ private CacheInfo createDownInfo(String url) { CacheInfo cacheInfo = new CacheInfo(url); long contentLength = getContentLength(url);//获得文件大小 cacheInfo.setTotal(contentLength); String fileName = generator.generate(url) + TEMP_POSTFIX; cacheInfo.setFileName(fileName); return cacheInfo; } private CacheInfo getRealFileName(CacheInfo cacheInfo) { String fileName = cacheInfo.getFileName(); long downloadLength = 0; if (!cacheDirectory.exists()) { cacheDirectory.mkdir(); } File file = new File(cacheDirectory, fileName); if (file.exists()) { //找到了文件,代表已经下载过,则获取其长度 downloadLength = file.length(); } //设置改变过的文件名/大小 cacheInfo.setProgress(downloadLength); cacheInfo.setFileName(file.getName()); return cacheInfo; } private class DownloadSubscribe implements ObservableOnSubscribe { private CacheInfo cacheInfo; public DownloadSubscribe(CacheInfo cacheInfo) { this.cacheInfo = cacheInfo; } @Override public void subscribe(ObservableEmitter e) throws Exception { String url = cacheInfo.getUrl(); final long[] downloadLength = {cacheInfo.getProgress()};//已经下载好的长度 long contentLength = cacheInfo.getTotal();//文件的总长度 // if (downloadLength[0] >= preLength) { if (downloadLength[0] >= contentLength) { e.onComplete(); return; } e.onNext(cacheInfo); Request request = new Request.Builder() // .addHeader("RANGE", "bytes=" + downloadLength[0] + "-" + (contentLength > preLength ? preLength : contentLength)) .addHeader("RANGE", "bytes=" + downloadLength[0] + "-" + contentLength) .url(url) .build(); Call call = mClient.newCall(request); getDownCalls().put(url, call); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { File file = new File(cacheDirectory, cacheInfo.getFileName()); InputStream is = null; FileOutputStream fileOutputStream = null; try { is = response.body().byteStream(); fileOutputStream = new FileOutputStream(file, true); byte[] buffer = new byte[2048]; int len; while (downCalls.get(url) != null && !call.isCanceled() && (len = is.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, len); downloadLength[0] += len; cacheInfo.setProgress(downloadLength[0]); e.onNext(cacheInfo); } fileOutputStream.flush(); getDownCalls().remove(url); } catch (Exception ex) { ex.printStackTrace(); } finally { if (is != null) { is.close(); } if (fileOutputStream != null) { fileOutputStream.close(); } } if (file.length() == contentLength) { file.renameTo(new File(file.getPath().substring(0, file.getPath().lastIndexOf(TEMP_POSTFIX)))); } e.onComplete(); } }); } } /** * 获取下载长度 * * @param downloadUrl * @return */ public long getContentLength(String downloadUrl) { long contentLength = CacheInfo.TOTAL_ERROR; Request request = new Request.Builder() .url(downloadUrl) .build(); Response response = null; try { response = mClient.newCall(request).execute(); if (response.isSuccessful() && response.body() != null) { long length = response.body().contentLength(); contentLength = length == 0 ? CacheInfo.TOTAL_ERROR : length; } } catch (Exception e) { e.printStackTrace(); } finally { if (response != null) { response.close(); } } return contentLength; } public void removeAllCall() { AppExecutor.getIoExecutor().execute(() -> { for (Map.Entry entry : getDownCalls().entrySet()) { entry.getValue().cancel(); } AppExecutor.getUiExecutor().execute(() -> getDownCalls().clear()); }); } private List getAllFile() { try { if (cacheDirectory.exists() && cacheDirectory.isDirectory()) { File[] files = cacheDirectory.listFiles(); return Arrays.asList(files); } } catch (Exception e) { e.printStackTrace(); return new ArrayList<>(); } return new ArrayList<>(); } }