最近的週末,一時無聊也用起了手機裏的愛奇藝App追劇,每次都是把視頻先緩存到本地習慣有的快進播放。突然好奇,想試試App的離線下載功能怎麼實現。想到以前在github上看到的daimajia做的一個專注動畫視頻的App(https://github.com/daimajia/AnimeTaste)的實現,想借鑑借鑑思路。本文的實習是在daimajia的下載模塊的基礎上實現愛奇藝的視頻下載效果,增加對下載的暫停、斷點下載功能和對下載使用功能的優化等。下面來說說視頻緩存的實現:
先看看愛奇藝的下載效果(沒有找到合適的gif製作軟件,下面截圖貼出圖片):
以下是我的實現下載模塊效果圖:
看完效果圖,以下來談談關於下載模塊的實現要點:
1. 對現在任務的監聽,通過觀察者模式實現對下載頁和通知欄的同時監聽;
2. 手機應用的內存大小有限,通過線程池來限制下載的線程個數(節約系統資源);
3. Http協議和文件的讀出位置重定向,保證了對斷點下載的支持;
下面我們來分析觀察者模式的實現,首選我們通過類圖說明:
我們的實現裏有三個觀察者,MissionListenerForAdapter觀察者對應下載詳情頁(DownloadActivity),MissionListenerForNotification觀察者對應通知欄,MissionSaver觀察者對應數據庫下載記錄。我們看下載任務Mission的代碼如下:
public class Mission implements Runnable {
public static int ID = 0;
private final int MissionID;
private String Uri;
private String SaveDir;
private String SaveName;
protected long mDownloaded;
protected long mFileSize;
private String mOriginFilename;
private String mExtension;
private String mShowName;
private long mPreviousNotifyTime;
private int mProgressNotifyInterval = 1;
private TimeUnit mTimeUnit = TimeUnit.SECONDS;
private long mLastSecondDownloadTime = 0;
private long mLastSecondDownload = 0;
private int mPreviousPercentage = -1;
protected long mStartTime;
protected long mEndTime;
public enum RESULT_STATUS {
STILL_DOWNLOADING, SUCCESS, FAILED
}
private RESULT_STATUS mResultStatus = RESULT_STATUS.STILL_DOWNLOADING;
private boolean isContinuing = false;
private boolean isDone = false;
private boolean isPaused = false;
private boolean isSuccess = false;
private boolean isCanceled = false;
private final Object o = new Object();
private List<MissionListener<Mission>> missionListeners;
private HashMap<String, Object> extras;
public Mission(String uri, String saveDir,long downloadedNum,long fileSize) {
MissionID = ID++;
Uri = uri;
SaveDir = saveDir;
this.mFileSize = fileSize;
mStartTime = System.currentTimeMillis();
mPreviousNotifyTime = mStartTime;
setOriginFileName(FileUtil.getFileName(uri));
setExtension(FileUtil.getFileExtension(uri));
SaveName = getOriginFileName() + "." + getExtension();
missionListeners = new ArrayList<MissionListener<Mission>>();
extras = new HashMap<String, Object>();
mDownloaded = downloadedNum;
}
public Mission(String uri, String saveDir, String saveFileName) {
this(uri, saveDir,0,0);
SaveName = getSafeFilename(saveFileName);
}
public Mission(String uri,String saveDir,String saveFileName,long downloadedNum,long fileSize){
this(uri,saveDir,downloadedNum,fileSize);
SaveName = getSafeFilename(saveFileName);
}
public Mission(String uri, String saveDir, String saveFilename,
String showName) {
this(uri, saveDir, saveFilename);
this.mShowName = showName;
}
//註冊回調接口
public void addMissionListener(MissionListener<Mission> listener) {
missionListeners.add(listener);
}
public void removeMissionListener(MissionListener<Mission> listener) {
missionListeners.remove(listener);
}
public void setProgressNotificateInterval(TimeUnit unit, int interval) {
mTimeUnit = unit;
mProgressNotifyInterval = interval;
}
@Override
public void run() {
notifyStart();
InputStream in = null;
RandomAccessFile fos = null;
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) new URL(Uri)
.openConnection();
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setRequestProperty("Range", "bytes=" + mDownloaded+"-");
//當文件沒有開始下載,記錄文件大小
if(mFileSize <= 0)
setFileSize(httpURLConnection.getContentLength());
in = httpURLConnection.getInputStream();
File accessFile = getSafeFile(getSaveDir(), getSaveName());
fos = new RandomAccessFile(accessFile, "rw");
fos.seek(mDownloaded);
byte data[] = new byte[1024];
int count;
notifyMetaDataReady();
while (!isCanceled() && (count = in.read(data, 0, 1024)) != -1) {
fos.write(data, 0, count);
mDownloaded += count;
notifyPercentageChange();
notifySpeedChange();
checkPaused();
}
if (isCanceled == false) {
notifyPercentageChange();
notifySuccess();
} else {
notifyCancel();
}
} catch (Exception e) {
notifyError(e);
} finally {
try {
if (in != null)
in.close();
if (fos != null)
fos.close();
} catch (IOException e) {
notifyError(e);
}
notifyFinish();
}
}
protected FileOutputStream getSafeOutputStream(String directory,
String filename) {
String filepath;
if (directory.lastIndexOf(File.separator) != directory.length() - 1) {
directory += File.separator;
}
File dir = new File(directory);
dir.mkdirs();
filepath = directory + filename;
File file = new File(filepath);
try {
file.createNewFile();
return new FileOutputStream(file.getCanonicalFile().toString());
} catch (Exception e) {
e.printStackTrace();
throw new Error("Can not get an valid output stream");
}
}
protected File getSafeFile(String directory, String filename) {
String filepath;
if (directory.lastIndexOf(File.separator) != directory.length() - 1) {
directory += File.separator;
}
File dir = new File(directory);
dir.mkdirs();
filepath = directory + filename;
File file = new File(filepath);
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
throw new Error("Can not Create A New File");
}
return file;
}
protected String getSafeFilename(String name) {
return name.replaceAll("[\\\\|\\/|\\:|\\*|\\?|\\\"|\\<|\\>|\\|]", "-");
}
protected void checkPaused() {
synchronized (o) {
while (isPaused) {
try {
notifyPause();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void pause() {
if (isPaused() || isDone())
return;
isPaused = true;
}
public void resume() {
if (!isPaused() || isDone()) {
return;
}
synchronized (o) {
isPaused = false;
o.notifyAll();
}
notifyResume();
}
public void cancel() {
isCanceled = true;
if (isPaused()) {
resume();
}
}
//通知進度改變
protected final void notifyPercentageChange() {
if (missionListeners != null && missionListeners.size() != 0) {
int currentPercentage = getPercentage();
if (mPreviousPercentage != currentPercentage) {
for (MissionListener<Mission> l : missionListeners) {
l.onPercentageChange(this);
}
mPreviousPercentage = currentPercentage;
}
}
}
//通知下載速度更新
protected final void notifySpeedChange() {
if (missionListeners != null && missionListeners.size() != 0) {
long currentNotifyTime = System.currentTimeMillis();
long notifyDuration = currentNotifyTime - mPreviousNotifyTime;
long spend = mTimeUnit.convert(notifyDuration,
TimeUnit.MILLISECONDS);
if (spend >= mProgressNotifyInterval) {
mPreviousNotifyTime = currentNotifyTime;
for (MissionListener<Mission> l : missionListeners) {
l.onSpeedChange(this);
}
}
long speedRecordDuration = currentNotifyTime
- mLastSecondDownloadTime;
if (TimeUnit.MILLISECONDS.toSeconds(speedRecordDuration) >= 1) {
mLastSecondDownloadTime = currentNotifyTime;
mLastSecondDownload = getDownloaded();
}
}
}
protected final void notifyAdd() {
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onAddMission(this);
}
}
}
protected final void notifyStart() {
isContinuing = true;
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onStart(this);
}
}
}
protected final void notifyPause() {
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onPause(this);
}
}
}
protected final void notifyResume() {
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onResume(this);
}
}
}
protected final void notifyCancel() {
isContinuing = false; //取消被通知到的時候,isContinuing爲fasle
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onCancel(this);
}
}
}
protected final void notifyMetaDataReady() {
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onMetaDataPrepared(this);
}
}
}
protected final void notifySuccess() {
mResultStatus = RESULT_STATUS.SUCCESS;
isDone = true;
isSuccess = true;
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onSuccess(this);
}
}
}
protected final void notifyError(Exception e) {
mResultStatus = RESULT_STATUS.FAILED;
isDone = true;
isSuccess = false;
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onError(this, e);
}
}
}
protected final void notifyFinish() {
isContinuing = false;
mEndTime = System.currentTimeMillis();
if (missionListeners != null && missionListeners.size() != 0) {
for (MissionListener<Mission> l : missionListeners) {
l.onFinish(this);
}
}
}
public String getUri() {
return Uri;
}
public String getSaveDir() {
return SaveDir;
}
public String getSaveName() {
return SaveName;
}
public long getDownloaded() {
return mDownloaded;
}
public long getFilesize() {
return mFileSize;
}
protected void setFileSize(int size) {
mFileSize = size;
}
public long getTimeSpend() {
if (mEndTime != 0) {
return mEndTime - mStartTime;
} else {
return System.currentTimeMillis() - mStartTime;
}
}
public String getAverageReadableSpeed() {
return FileUtil.getReadableSpeed(getDownloaded(), getTimeSpend(),
TimeUnit.MILLISECONDS);
}
public String getAccurateReadableSpeed() {
return FileUtil.getReadableSize(getDownloaded() - mLastSecondDownload,
false) + "/s";
}
public int getPercentage() {
if (mFileSize == 0) {
return 0;
} else {
return (int) (mDownloaded * 100.0f / mFileSize);
}
}
public String getAccurateReadableSize() {
return FileUtil.getReadableSize(getDownloaded());
}
public String getReadableSize() {
return FileUtil.getReadableSize(getFilesize());
}
public String getReadablePercentage() {
StringBuilder builder = new StringBuilder();
builder.append(getPercentage());
builder.append("%");
return builder.toString();
}
public void setOriginFileName(String name) {
mOriginFilename = name;
}
public String getOriginFileName() {
return mOriginFilename;
}
public void setExtension(String extension) {
mExtension = extension;
}
public String getExtension() {
return mExtension;
}
public String getShowName() {
if (mShowName == null || mShowName.length() == 0) {
return getSaveName();
} else {
return mShowName;
}
}
public int getMissionID() {
return MissionID;
}
public boolean isDownloading() {
return isContinuing;
}
public boolean isDone() {
return isDone;
}
public boolean isSuccess() {
return isSuccess;
}
public boolean isPaused() {
return isPaused;
}
public void setCanceled(boolean isCanceled){
this.isCanceled = isCanceled;
}
public boolean isCanceled() {
return isCanceled;
}
public RESULT_STATUS getResultStatus() {
return mResultStatus;
}
public void addExtraInformation(String key, Object value) {
extras.put(key, value);
}
public void removeExtraInformation(String key) {
extras.remove(key);
}
public Object getExtraInformation(String key) {
return extras.get(key);
}
public interface MissionListener<T extends Mission> {
public void onAddMission(T mission);
public void onStart(T mission);
public void onMetaDataPrepared(T mission);
public void onPercentageChange(T mission);
public void onSpeedChange(T mission);
public void onError(T mission, Exception e);
public void onSuccess(T mission);
public void onFinish(T mission);
public void onPause(T mission);
public void onResume(T mission);
public void onCancel(T mission);
}
}
通過代碼我們知道,Mission實現的Runnable接口,下載任務運行時候,開始通知註冊下載任務的觀察者們。
我們選擇MissionSaver觀察者的實習來分析:
public class MissionSaver implements Mission.MissionListener<Mission>{
private DownloadDetail getDownloadDetail(Mission mission){
Object obj = mission.getExtraInformation(mission.getUri());
DownloadDetail detail = null;
if(obj!=null)
detail = (DownloadDetail)obj;
return detail;
}
private void save(Mission mission){
DownloadDetail detail = getDownloadDetail(mission);
if(detail!=null){
DownloadRecord.save(detail,mission);
}
}
private void delete(Mission mission){
DownloadDetail detail = getDownloadDetail(mission);
if(detail!=null){
DownloadRecord.deleteOne(detail);
}
}
@Override
public void onStart(Mission mission) {
save(mission);
}
@Override
public void onMetaDataPrepared(Mission mission) {
}
@Override
public void onPercentageChange(Mission mission) {
}
@Override
public void onSpeedChange(Mission mission) {
}
@Override
public void onError(Mission mission, Exception e) {
save(mission);
}
@Override
public void onSuccess(Mission mission) {
save(mission);
}
@Override
public void onFinish(Mission mission) {
}
@Override
public void onPause(Mission mission) {
}
@Override
public void onResume(Mission mission) {
}
/*
* 此處的onCancel,目的是暫停下載任務退出當前線程,並不是取消下載,此時下載進度會被保存起來
*/
@Override
public void onCancel(Mission mission) {
save(mission);
}
@Override
public void onAddMission(Mission mission) {
}
}
通過此處的代碼我們知道,當我們會在下載任務開始(onStart),暫停(onCancel),成功(onSuccess)和錯誤(onError)的時候都會保存下載進度。因此,我們知道我們的下載模塊會存在一個問題,當我們強制關閉App進程的時候,我們的下載線程很可能來不及執行以上的某一個方法來保存下載進度,所以,該次下載信息丟失。但是,愛奇藝的的下載服務是一個獨立的進程,所以存在強制關閉App下載信息丟失來不及保存。下面我們來看線程池封裝,實現控制下載的線程次數,代碼實現如下:
public class DownloadManager {
private final String TAG = "DownLoadManager";
private static int MAX_MISSION_COUNT = 5; //設置線程池最大線程個數
private static DownloadManager Instance;
protected ThreadPoolExecutor mExecutorService;
protected HashMap<Integer,Mission> mMissionBook;
public static synchronized DownloadManager getInstance(){
if(Instance == null || Instance.mExecutorService.isShutdown()){
Instance = new DownloadManager();
}
return Instance;
}
private DownloadManager(){
mMissionBook = new HashMap<Integer, Mission>();
mExecutorService = new ThreadPoolExecutor(MAX_MISSION_COUNT,MAX_MISSION_COUNT,15,TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>()); //實例化線程池
}
public boolean containMission(int missionID){
return mMissionBook.containsKey(missionID);
}
public void addMission(Mission mission){
mMissionBook.put(mission.getMissionID(), mission);
mission.notifyAdd();
}
public void executeMission(Mission mission){
if(mission.isCanceled())
mission.setCanceled(false);
mExecutorService.execute(mission);
}
public void pauseMission(int missionID){
if(mMissionBook.containsKey(missionID)){
mMissionBook.get(missionID).pause();
}
}
public void resumeMission(int missionID){
if(mMissionBook.containsKey(missionID)){
mMissionBook.get(missionID).resume();
}
}
public void cancelMission(int missionID){
if(mMissionBook.containsKey(missionID)){
mMissionBook.get(missionID).cancel();
}
}
/*
* 停止所有任務
*/
public void surrenderMissions(){
for(Map.Entry<Integer, Mission> mission : mMissionBook.entrySet()){
mission.getValue().cancel();
mMissionBook.get(mission).cancel();
}
}
public static void setMaxMissionCount(int count){
if(Instance == null && count > 0)
MAX_MISSION_COUNT = count;
else
throw new IllegalStateException("Can not change max mission count after getInstance been called");
}
}
最後我們來看看下載幫助類DownloadHelper:
public class DownloadHelper {
private Context mContext;
private DownloadServiceBinder mDownloadServiceBinder;
private Boolean isConnected = false;
private Object o = new Object();
public DownloadHelper(Context context) {
mContext = context;
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mDownloadServiceBinder = (DownloadService.DownloadServiceBinder) service;
synchronized (o) {
isConnected = true;
o.notify();//當DownloadService連接上,通知下載線程繼續
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
isConnected = false;
}
};
public void startDownload(final DownloadDetail detail) {
final DownloadRecord record = new Select().from(DownloadRecord.class)
.where("DownloadId = ? ", detail.VideoId).executeSingle();
if (record != null) {
if (record.Status == DownloadRecord.STATUS.SUCCESS) {
new AlertDialog.Builder(mContext)
.setTitle(R.string.tip)
.setMessage(R.string.redownload_tips)
.setPositiveButton(R.string.yes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
File file = new File(record.SaveDir
+ record.SaveFileName);
if (file.exists() && file.isFile()) {
file.delete();
}
safeDownload(detail);
}
})
.setNegativeButton(R.string.no,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
}
}).create().show();
} else if (record.Status == DownloadRecord.STATUS.PAUSE) { // 繼續上次下載
safeDownload(record);
} else {
safeDownload(detail);
}
} else { // 新建下載
safeDownload(detail);
}
}
private void safeDownload(final DownloadRecord record) {
if (NetworkUtils.isNetworkAvailable(mContext)) {
if (NetworkUtils.isWifiConnected(mContext)) {
download(record);
} else {
new AlertDialog.Builder(mContext)
.setTitle(R.string.tip)
.setMessage(R.string.no_wifi_download)
.setPositiveButton(R.string.yes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
download(record);
}
}).setNegativeButton(R.string.no, null)
.create().show();
}
} else {
Toast.makeText(mContext, R.string.no_network, Toast.LENGTH_LONG)
.show();
}
}
private void safeDownload(final DownloadDetail detail) {
if (NetworkUtils.isNetworkAvailable(mContext)) {
if (NetworkUtils.isWifiConnected(mContext)) {
download(detail);
} else {
new AlertDialog.Builder(mContext)
.setTitle(R.string.tip)
.setMessage(R.string.no_wifi_download)
.setPositiveButton(R.string.yes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
download(detail);
}
}).setNegativeButton(R.string.no, null)
.create().show();
}
} else {
Toast.makeText(mContext, R.string.no_network, Toast.LENGTH_LONG)
.show();
}
}
/*
* 下載任務線程重新開始執行
*/
public void download(final Mission mission) {
new Thread() {
@Override
public void run() {
super.run();
if (!isDownloadServiceRunning()) {
mContext.startService(new Intent(mContext,
DownloadService.class));
}
if (mDownloadServiceBinder == null || isConnected == false) {
mContext.bindService(new Intent(mContext,
DownloadService.class), connection,
Context.BIND_AUTO_CREATE);
synchronized (o) {
while (!isConnected) {
try {
o.wait();//當DownloadService還未連接,下載線程被通知等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
mDownloadServiceBinder.continueDownload(mission);
}
}.start();
}
public void download(final DownloadRecord record) {
new Thread() {
@Override
public void run() {
super.run();
if (!isDownloadServiceRunning()) {
mContext.startService(new Intent(mContext,
DownloadService.class));
}
if (mDownloadServiceBinder == null || isConnected == false) {
mContext.bindService(new Intent(mContext,
DownloadService.class), connection,
Context.BIND_AUTO_CREATE);
synchronized (o) {
while (!isConnected) {
try {
o.wait();//當DownloadService還未連接,下載線程被通知等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
File file = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
Mission mission = new Mission(record.DownloadUrl,
file.getAbsolutePath() + "/Download/", record.FileName
+ "." + record.Extension, record.DownloadedSize,record.FileSize);
mission.addExtraInformation(mission.getUri(),
DownloadRecord.getDownloadDetail(record));
mDownloadServiceBinder.startDownload(mission);
}
}.start();
}
/*
* 將未完成的下載記錄添加到下載任務列表,並讓下載任務開始執行
*/
public void AddDownload(final DownloadRecord record) {
new Thread() {
@Override
public void run() {
super.run();
if (!isDownloadServiceRunning()) {
mContext.startService(new Intent(mContext,
DownloadService.class));
}
if (mDownloadServiceBinder == null || isConnected == false) {
mContext.bindService(new Intent(mContext,
DownloadService.class), connection,
Context.BIND_AUTO_CREATE);
synchronized (o) {
while (!isConnected) {
try {
o.wait();//當DownloadService還未連接,下載線程被通知等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
File file = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
Mission mission = new Mission(record.DownloadUrl,
file.getAbsolutePath() + "/Download/", record.FileName
+ "." + record.Extension, record.DownloadedSize,record.FileSize);
mission.addExtraInformation(mission.getUri(),
DownloadRecord.getDownloadDetail(record));
mDownloadServiceBinder.startDownload(mission);
}
}.start();
}
/*
* 將未完成的下載記錄添加到下載任務列表
*/
public void addDownloadMission(final List<DownloadRecord> records) {
new Thread() {
@Override
public void run() {
super.run();
if (!isDownloadServiceRunning()) {
mContext.startService(new Intent(mContext,
DownloadService.class));
}
if (mDownloadServiceBinder == null || isConnected == false) {
mContext.bindService(new Intent(mContext,
DownloadService.class), connection,
Context.BIND_AUTO_CREATE);
synchronized (o) {
while (!isConnected) {
try {
o.wait();//當DownloadService還未連接,下載線程被通知等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
File file = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
file.mkdirs();
for (DownloadRecord record : records) {
Mission mission = new Mission(record.DownloadUrl,
file.getAbsolutePath() + "/Download/",
record.FileName + "." + record.Extension,
record.DownloadedSize,record.FileSize);
mission.addExtraInformation(mission.getUri(),
DownloadRecord.getDownloadDetail(record));
mDownloadServiceBinder.AddDownload(mission);
}
}
}.start();
}
/*
* 將未完成的下載記錄添加到下載任務列表
*/
public void addDownloadMission(final DownloadRecord record) {
new Thread() {
@Override
public void run() {
super.run();
if (!isDownloadServiceRunning()) {
mContext.startService(new Intent(mContext,
DownloadService.class));
}
if (mDownloadServiceBinder == null || isConnected == false) {
mContext.bindService(new Intent(mContext,
DownloadService.class), connection,
Context.BIND_AUTO_CREATE);
synchronized (o) {
while (!isConnected) {
try {
o.wait();//當DownloadService還未連接,下載線程被通知等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
File file = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
file.mkdirs();
Mission mission = new Mission(record.DownloadUrl,
file.getAbsolutePath() + "/Download/", record.FileName
+ "." + record.Extension, record.DownloadedSize,record.FileSize);
mission.addExtraInformation(mission.getUri(),
DownloadRecord.getDownloadDetail(record));
mDownloadServiceBinder.AddDownload(mission);
}
}.start();
}
private void download(final DownloadDetail detail) {
new Thread() {
@Override
public void run() {
super.run();
if (!isDownloadServiceRunning()) {
mContext.startService(new Intent(mContext,
DownloadService.class));
}
if (mDownloadServiceBinder == null || isConnected == false) {
mContext.bindService(new Intent(mContext,
DownloadService.class), connection,
Context.BIND_AUTO_CREATE);
synchronized (o) {
while (!isConnected) {
try {
o.wait();//當DownloadService還未連接,下載線程被通知等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
File file = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
file.mkdirs();
Mission mission = new Mission(detail.DownloadUrl,
file.getAbsolutePath() + "/Download/", detail.FileName
+ "." + detail.Extension);
mission.addExtraInformation(mission.getUri(), detail);
mDownloadServiceBinder.startDownload(mission);
}
}.start();
}
/*
* 判斷下載服務是否在運行
*/
private boolean isDownloadServiceRunning() {
ActivityManager manager = (ActivityManager) mContext
.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager
.getRunningServices(Integer.MAX_VALUE)) {
if (DownloadService.class.getName().equals(
service.service.getClassName())) {
return true;
}
}
return false;
}
public void unbindDownloadService() {
if (isDownloadServiceRunning() && isConnected && connection != null) {
mContext.unbindService(connection);
}
}
}
以上簡單對常用App的下載模塊的部分代碼的分析,對比愛奇藝App下載模塊實現略顯簡陋。本代碼完全是個人業餘時間憑興趣搗鼓,現提供下載地址,供有興趣的園友自己琢磨。還有不成熟的地方和考慮不周存在的bug,還望路過的朋友不吝嗇指教,有好的點子歡迎一起學習。轉載請註明出處:http://blog.csdn.net/johnnyz1234/article/details/44280479