安卓队列缓存文件,包括断点续传
生活随笔
收集整理的這篇文章主要介紹了
安卓队列缓存文件,包括断点续传
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
想起來之前做視頻緩存的工具類,沒事記一下,中間用了一個開源的緩存的進度庫,其他的都是自己寫的
其中網絡請求時用的OkHttp3
緩存進度庫用的是:
compile 'io.github.lizhangqu:coreprogress:1.0.2'封裝的工具類:DownloadUtil.java
import android.text.TextUtils; import android.util.Log;import com.hkzr.docopad.minterface.OnDownloadListener;import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;import io.github.lizhangqu.coreprogress.ProgressHelper; import io.github.lizhangqu.coreprogress.ProgressUIListener; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody;/*** 創 建: lt* 作 用: 斷點下載的類* 使用方法:* 注意事項:*/public class DownloadUtil {private Call call;private static DownloadUtil downloadUtil;private OkHttpClient okHttpClient;private List<Object[]> list;//call,url,startBytes,path,OnDownloadListenerprivate OnDownloadListener onDownloadListener;private final static int ADD = 0;private final static int CANCEL = 1;private final static int SET = 2;private DownloadUtil() {okHttpClient = new OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS)//設置超時時間之類的.readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).build();list = new ArrayList<>();}/*** 單例初始化*/public static DownloadUtil init() {if (downloadUtil == null) {synchronized (DownloadUtil.class) {if (downloadUtil == null) {downloadUtil = new DownloadUtil();}}}return downloadUtil;}/*** 添加下載請求** @param startBytes 從第幾個字節開始* @param path 保存的路徑* @param url 下載鏈接* @param onDownloadListener 回調*/public void addDownload(long startBytes, String path, String url, OnDownloadListener onDownloadListener) {//添加Range頭//傳入鏈接Call call = okHttpClient.newCall(new Request.Builder().addHeader("RANGE", "bytes=" + startBytes + "-").url(url).get().build());list.add(new Object[]{call, url, startBytes, path, onDownloadListener});loop(ADD);}/*** 下載請求** @param startBytes 從第幾個字節開始* @param path 保存的路徑*/private void download(final long startBytes, final String path) {Log.i("lllttt", "DownloadUtil : " + startBytes);//回調try {call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {//失敗了if (onDownloadListener != null) {onDownloadListener.onFailure(e);}}@Overridepublic void onResponse(Call call, Response response) {ResponseBody body = response.body();ResponseBody responseBody = ProgressHelper.withProgress(body, new ProgressUIListener() {@Overridepublic void onUIProgressChanged(long numBytes, long totalBytes, float percent, float speed) {if (onDownloadListener != null) {onDownloadListener.onProgress(numBytes, totalBytes, percent, speed);}}@Overridepublic void onUIProgressStart(long totalBytes) {if (onDownloadListener != null) {onDownloadListener.onStart(totalBytes);}}@Overridepublic void onUIProgressFinish() {if (onDownloadListener != null) {onDownloadListener.onFinish();}try {list.remove(0);} catch (Exception e) {e.printStackTrace();}DownloadUtil.this.call = null;onDownloadListener = null;loop(ADD);}});save(responseBody, startBytes, path);}});} catch (Exception e) {e.printStackTrace();}}private void save(ResponseBody body, long startsPoint, String path) {InputStream in = body.byteStream();FileChannel channelOut = null;// 隨機訪問文件,可以指定斷點續傳的起始位置RandomAccessFile randomAccessFile = null;try {randomAccessFile = new RandomAccessFile(path, "rwd");//Chanel NIO中的用法,由于RandomAccessFile沒有使用緩存策略,直接使用會使得下載速度變慢,親測緩存下載3.3秒的文件,用普通的RandomAccessFile需要20多秒。channelOut = randomAccessFile.getChannel();randomAccessFile.seek(startsPoint);// 內存映射,直接使用RandomAccessFile,是用其seek方法指定下載的起始位置,使用緩存下載,在這里指定下載位置。MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE, startsPoint, body.contentLength());byte[] buffer = new byte[1024];int len = 0;while ((len = in.read(buffer)) != -1) {mappedBuffer.put(buffer, 0, len);}} catch (Exception e) {e.printStackTrace();} finally {try {in.close();if (channelOut != null) {channelOut.close();}if (randomAccessFile != null) {randomAccessFile.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 停止下載(暫停)** @param url 下載路徑*/public void cancelDownload(String url) {loop(CANCEL, url);}/*** 停止所有*/public void cancelAllDownload() {if (list != null)list.clear();if (call != null)call.cancel();call = null;if (onDownloadListener != null)onDownloadListener.onFailure(null);this.onDownloadListener = null;}/*** 開始某個下載任務** @param url 鏈接*/public void startDownLoad(long startBytes, String path, String url, OnDownloadListener onDownloadListener) {if (startBytes < 0) //todostartBytes = 0;//查詢有沒有這個下載鏈接,有就讓他挪到第一位,沒有就添加到第一位if (list != null && list.size() == 1 && !isHave(url)) {//如果只有幾個暫停的,并開啟了一個暫停的,并把第二個暫停的打開//應該把那個下載中的放為等待中,這個為開始下載Object[] objects = list.get(0);this.call.cancel();this.call = null;this.onDownloadListener = null;Call call = okHttpClient.newCall(new Request.Builder().addHeader("RANGE", "bytes=" + startBytes + "-").url(url).get().build());list.add(0, new Object[]{call, url, startBytes, path, onDownloadListener});loop(ADD);}if (isHave(url)) {//挪到第一位Object[] objs = null;for (Object[] objects : list) {if (objects[1].equals(url)) {objs = objects;break;}}if (list == null || list.size() <= 1) {loop(ADD);} else {list.remove(objs);list.add(1, objs);loop(CANCEL, String.valueOf(list.get(0)[1]));}} else {//添加到第一位Call c = okHttpClient.newCall(new Request.Builder().addHeader("RANGE", "bytes=" + startBytes + "-").url(url).get().build());list.add(0, new Object[]{c, url, startBytes, path, onDownloadListener});if (call != null)this.call.cancel();this.call = null;loop(ADD);}}/*** 判斷這個任務是否在運行** @param url 鏈接*/public boolean isRun(String url) {if (list == null || list.size() == 0) {return false;}return list.get(0)[1].equals(url);}/*** 判斷這個任務是否存在** @param url 鏈接*/public boolean isHave(String url) {if (list == null || list.size() == 0) {return false;}for (Object[] obj : list) {if (TextUtils.equals((String) obj[1], url))return true;}return false;}/*** 判斷是否還有任務*/public boolean isHave() {if (list == null)return false;return !(list.size() == 0);}/*** 給沒有回調的下載請求設置回調** @param url 視頻下載鏈接* @param onDownloadListener 下載回調*/public void setListener(String url, OnDownloadListener onDownloadListener) {loop(SET, url, onDownloadListener);}private void loop(int i) {loop(i, null);}private void loop(int i, String url) {loop(i, url, null);}/*** 輪詢*/private void loop(int i, String url, OnDownloadListener onDownloadListener) {if (list == null || list.size() == 0)return;switch (i) {case ADD:if (call != null)return;Object[] objects = list.get(0);this.call = (Call) objects[0];this.onDownloadListener = (OnDownloadListener) objects[4];download((long) objects[2], (String) objects[3]);break;case CANCEL:for (int j = 0; j < list.size(); j++) {Object[] objs = list.get(j);if (objs[1].equals(url)) {//如果url一樣((Call) objs[0]).cancel();list.remove(objs);if (j == 0) {call = null;this.onDownloadListener = null;loop(ADD);}}}break;case SET:for (int j = 0; j < list.size(); j++) {if (list.get(j)[1].equals(url)) {//如果url一樣list.get(j)[4] = onDownloadListener;if (j == 0)this.onDownloadListener = onDownloadListener;}}break;}}}回調:OnDownloadListener.java
/*** 創 建: lt* 作 用: 下載的回調* 使用方法:* 注意事項:*/public interface OnDownloadListener {/*** 下載失敗*/void onFailure(Exception e);/*** 下載開始** @param totalBytes 一共多少字節*/void onStart(long totalBytes);/*** 下載完成*/void onFinish();/*** 下載中(不是第一次的話,會使總字節數發生變化,所以要在第一次下載的時候保存總字節數,然后自己計算百分比)** @param numBytes 已經下載了多少字節* @param totalBytes 總字節* @param percent 萬分比(乘以100為百分比)* @param speed 下載速度(* 1000 / 1024 / 1024 =M/s?)*/void onProgress(long numBytes, long totalBytes, float percent, float speed); }下載用的服務:VideoCacheService.java
import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message;import com.hkzr.docopad.app.App; import com.hkzr.docopad.greendao.VideoCacheInfoDao; import com.hkzr.docopad.minterface.OnDownloadListener; import com.hkzr.docopad.model.VideoCacheInfo; import com.hkzr.docopad.utils.DownloadUtil; import com.hkzr.docopad.utils.NetUtil; import com.hkzr.docopad.utils.SDCardUtil; import com.hkzr.docopad.utils.ToastUtil;import java.util.ArrayList; import java.util.List;public class VideoCacheService extends Service {private DownloadUtil init;private boolean isCheck = false;//是否在檢查網絡和存儲空間中public VideoCacheService() {}@Overridepublic IBinder onBind(Intent intent) {return new BindVideoCache();}@Overridepublic void onCreate() {super.onCreate();init = DownloadUtil.init();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();}public void oneLoad(String videoName, long videoSize, String videoUrl,int lookProgress, String videoId, long videoDuration, String videoImg, String savePath) {//如果sd卡可用,并且大于視頻大小就可以走//如果內部存儲大于視頻大小就可以走if (!(SDCardUtil.isSDCardEnabled() && SDCardUtil.getSDCardAllSize() > videoSize || SDCardUtil.getAvailableInternalMemorySize() > videoSize)) {ToastUtil.showToast("磁盤已滿,請清理足夠的存儲空間");return;}VideoCacheInfo videoCacheInfo = new VideoCacheInfo(null,//idvideoName,//視頻名稱videoSize,//視頻大小videoUrl,//視頻路徑0,//下載的百分比進度lookProgress,//播放的百分比進度videoId,//視頻的idvideoDuration,//視頻持續時長,毫秒單位?videoImg,//視頻預覽圖片savePath,//保存路徑0,//下載字節數VideoCacheInfo.STAUS_NO_START);//狀態,未開始App.ormDao.getVideoCacheInfoDao().insert(videoCacheInfo);load(0, savePath, videoUrl, null);}public void load(final long startBytes, String path, final String url, final OnDownloadListener onDownloadListener) {//如果磁盤滿了或者無網絡,則無需添加到隊列中if (!NetUtil.isNetworkAvailable(getApplicationContext())) {ToastUtil.showToast("當前無網絡,請檢查網絡鏈接后重試");if (onDownloadListener != null)onDownloadListener.onFailure(null);return;}init.addDownload(startBytes, path, url, onDownloadListener);if (!isCheck) {isCheck = true;handler.sendEmptyMessage(0);}}public void cancel(String url) {init.cancelDownload(url);for (VideoCacheInfo info : App.ormDao.getVideoCacheInfoDao().queryBuilder().where(VideoCacheInfoDao.Properties.VideoUrl.eq(url)).list()) {info.setStatus(VideoCacheInfo.STATUS_PAUSED);App.ormDao.getVideoCacheInfoDao().update(info);}}public void cancelAll() {init.cancelAllDownload();for (VideoCacheInfo info : App.ormDao.loadAll(VideoCacheInfo.class)) {if (info.getStatus() != VideoCacheInfo.STATUS_FINISH && info.getStatus() != VideoCacheInfo.STATUS_PAUSED) {info.setStatus(VideoCacheInfo.STAUS_NO_START);App.ormDao.getVideoCacheInfoDao().update(info);}}}public boolean isHave(String url) {return init.isHave(url);}public void setListener(String url, OnDownloadListener onDownloadListener) {if (!NetUtil.isNetworkAvailable(getApplicationContext())) {ToastUtil.showToast("當前無網絡,請檢查網絡鏈接后重試");if (onDownloadListener != null)onDownloadListener.onFailure(null);return;}init.setListener(url, onDownloadListener);}public void startDownLoad(long startBytes, String path, String url, OnDownloadListener onDownloadListener) {if (!NetUtil.isNetworkAvailable(getApplicationContext())) {ToastUtil.showToast("當前無網絡,請檢查網絡鏈接后重試");if (onDownloadListener != null)onDownloadListener.onFailure(null);return;}for (VideoCacheInfo info : App.ormDao.getVideoCacheInfoDao().queryBuilder().where(VideoCacheInfoDao.Properties.VideoUrl.eq(url)).list()) {info.setStatus(VideoCacheInfo.STATUS_STARTED);App.ormDao.getVideoCacheInfoDao().update(info);}init.startDownLoad(startBytes, path, url, onDownloadListener);if (!isCheck) {isCheck = true;handler.sendEmptyMessage(0);}}public void startAll() {List<VideoCacheInfo> videoCacheInfos = App.ormDao.getVideoCacheInfoDao().loadAll();if (videoCacheInfos == null || videoCacheInfos.size() == 0)return;List<VideoCacheInfo> list = new ArrayList<>();for (VideoCacheInfo info : videoCacheInfos) {if ((info.getStatus() == VideoCacheInfo.STAUS_NO_START || info.getStatus() == VideoCacheInfo.STATUS_STARTED) && !isHave(info.getVideoUrl())) {//表示需要下載,并且沒在隊列中list.add(info);}}if (list.size() == 0)return;for (VideoCacheInfo info : list) {load(info.getDownloadSpeed(), info.getLocalPath(), info.getVideoUrl(), null);}}Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {if (!init.isHave()) {//如果沒有任務了就不檢查了isCheck = false;return;}//還有任務就檢查網絡和內部存儲if (!NetUtil.isNetworkAvailable(getApplicationContext())) {//沒有網絡就提醒,并暫停所有任務init.cancelAllDownload();ToastUtil.showToast("當前無網絡,已暫停緩存任務");isCheck=false;return;}if ((SDCardUtil.isSDCardEnabled() && SDCardUtil.getSDCardAllSize() < 60 * 1024)|| SDCardUtil.getAvailableInternalMemorySize() < 60 * 1024 * 1024) {init.cancelAllDownload();ToastUtil.showToast("存儲空間不足,已暫停緩存任務");isCheck=false;return;}handler.sendEmptyMessageDelayed(0, 6000);}};public boolean isRun(String url) {return init.isRun(url);}public class BindVideoCache extends Binder {/*** 第一次添加下載請求** @param videoName 視頻名稱* @param videoSize 視頻大小* @param videoUrl 視頻路徑* @param lookProgress 播放的百分比進度* @param videoId 視頻的id* @param videoDuration 視頻持續時長,毫秒單位?* @param videoImg 視頻預覽圖片* @param savePath 保存路徑*/public void mOneLoad(String videoName, long videoSize, String videoUrl,int lookProgress, String videoId, long videoDuration, String videoImg, String savePath) {//ps:其實這個方法根據設計模式原則應該是直接傳一個對象的,懶得改了oneLoad(videoName, videoSize, videoUrl,lookProgress, videoId, videoDuration, videoImg, savePath);}/*** 開始下載(非第一次下載)** @param startBytes 從第幾個字節開始* @param path 保存的路徑* @param url 下載鏈接* @param onDownloadListener 回調*/public void mLoad(long startBytes, String path, String url, OnDownloadListener onDownloadListener) {load(startBytes, path, url, onDownloadListener);}/*** 停止下載(暫停)** @param url 下載路徑*/public void mCancel(String url) {cancel(url);}/*** 停止所有*/public void mCancelAll() {cancelAll();}/*** 判斷緩存隊列中是否存在該任務** @param url 任務鏈接* @return 是否存在*/public boolean mIsHave(String url) {return isHave(url);}/*** 設置回調** @param url 視頻鏈接* @param onDownloadListener 下載回調*/public void mSetListener(String url, OnDownloadListener onDownloadListener) {setListener(url, onDownloadListener);}/*** 開始下載(暫停或停止后)** @param startBytes 從第幾個字節開始* @param path 保存的路徑* @param url 下載鏈接* @param onDownloadListener 回調*/public void mStartDownLoad(long startBytes, String path, String url, OnDownloadListener onDownloadListener) {startDownLoad(startBytes, path, url, onDownloadListener);}/*** 判斷是否正在下載** @param url 視頻鏈接*/public boolean mIsRun(String url) {return isRun(url);}/*** 是否在緩存中或緩存過** @param vid 視頻id*/public boolean isVideoCacheOrCacheing(String vid) {List<VideoCacheInfo> list = App.ormDao.getVideoCacheInfoDao().queryBuilder().where(VideoCacheInfoDao.Properties.VideoId.eq(vid)).list();if (list != null && list.size() > 0) {//表示緩存過了或在緩存隊列中return true;}return false;}/*** 加載并開始所有等待中和緩存中的任務,無監聽* 如果已被加載,則不理會*/public void mStartAll() {startAll();}}}附加一個緩存時使用的Bean類,使用了GreenDao來保存下載的進度和本地磁盤位置等信息:VideoCacheInfo.java
import org.greenrobot.greendao.annotation.Entity; import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Keep; import org.greenrobot.greendao.annotation.Transient;import java.io.Serializable;/*** 作 用: 緩存視頻的bean類*/@Entity public class VideoCacheInfo implements Serializable {/*** 0: 未開始, 正等待* 1: 已經開始* 2: 已停止,未完成* 3: 已暫停* 4: 已完成*/@Transientpublic static final int STAUS_NO_START = 0;@Transientpublic static final int STATUS_STARTED = 1;@Transientpublic static final int STATUS_STOPED = 2;@Transientpublic static final int STATUS_PAUSED = 3;@Transientpublic static final int STATUS_FINISH = 4;@Id(autoincrement = true)private Long id;@Transientprivate HistoryBean history;private String videoName;//視頻名稱private long videoSize;//視頻大小private String videoUrl;//視頻鏈接private int downProgress;//下載進度private int lookProgress = -1;//觀看進度private String videoId;//服務器idprivate long videoDuration;//視頻持續時間private String videoIcon;//圖片靜態圖片private String localPath;//本地存放路徑private long downloadSpeed = 0;//下載字節數private int status = STAUS_NO_START;//狀態@Transientprivate boolean isCheck;@Generated(hash = 1303373854)public VideoCacheInfo(Long id, String videoName, long videoSize,String videoUrl, int downProgress, int lookProgress, String videoId,long videoDuration, String videoIcon, String localPath,long downloadSpeed, int status) {this.id = id;this.videoName = videoName;this.videoSize = videoSize;this.videoUrl = videoUrl;this.downProgress = downProgress;this.lookProgress = lookProgress;this.videoId = videoId;this.videoDuration = videoDuration;this.videoIcon = videoIcon;this.localPath = localPath;this.downloadSpeed = downloadSpeed;this.status = status;}@Generated(hash = 843470261)public VideoCacheInfo() {}public int getLookProgress() {return lookProgress;}public void setLookProgress(int lookProgress) {this.lookProgress = lookProgress;}public boolean isCheck() {return isCheck;}public void setCheck(boolean check) {isCheck = check;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getVideoName() {return videoName;}public void setVideoName(String videoName) {this.videoName = videoName;}public long getVideoSize() {return videoSize;}public void setVideoSize(long videoSize) {this.videoSize = videoSize;}public String getVideoUrl() {return videoUrl;}public void setVideoUrl(String videoUrl) {this.videoUrl = videoUrl;}public int getDownProgress() {return downProgress;}public void setDownProgress(int downProgress) {this.downProgress = downProgress;}public String getVideoId() {return videoId;}public void setVideoId(String videoId) {this.videoId = videoId;}public long getVideoDuration() {return videoDuration;}public void setVideoDuration(long videoDuration) {this.videoDuration = videoDuration;}public String getVideoIcon() {return videoIcon;}public void setVideoIcon(String videoIcon) {this.videoIcon = videoIcon;}public long getDownloadSpeed() {return downloadSpeed;}public void setDownloadSpeed(long downloadSpeed) {this.downloadSpeed = downloadSpeed;}@Keeppublic HistoryBean getHistory() {return history;}@Keeppublic void setHistory(HistoryBean history) {this.history = history;}public String getLocalPath() {return localPath;}public void setLocalPath(String localPath) {this.localPath = localPath;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}@Overridepublic String toString() {return "VideoCacheInfo{" +"id=" + id +", history=" + history +", videoName='" + videoName + '\'' +", videoSize='" + videoSize + '\'' +", videoUrl='" + videoUrl + '\'' +", downProgress=" + downProgress +", videoId='" + videoId + '\'' +", videoDuration=" + videoDuration +", videoIcon='" + videoIcon + '\'' +", localPath='" + localPath + '\'' +", downloadSpeed=" + downloadSpeed +", status=" + status +'}';}}有幫助或者有問題可以留言
總結
以上是生活随笔為你收集整理的安卓队列缓存文件,包括断点续传的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Studio 打包 V1
- 下一篇: 学习Kotlin(一)为什么使用Kotl