Android Studio 下載視頻到本地
最近在研究視頻下載到本地的問題,像愛奇藝,騰訊視頻,迅雷看看等等一些視頻播放器,如果在一個(gè)播放器里面視頻下載到一半用戶退出App之后,再次登錄從頭開始,那么就太可悲了,所以在做視頻音頻類的項(xiàng)目時(shí),要實(shí)現(xiàn)的一個(gè)功能就是斷點(diǎn)續(xù)傳,就是將用戶下載的視頻或者音頻等以字節(jié)流的形式存入數(shù)據(jù)庫,下次用戶再次下載時(shí),將繼續(xù)上次數(shù)據(jù)庫的接著下載,這樣用戶體驗(yàn)就會很好,也大大節(jié)省了成本.
好了廢話不多說,開始今天的正題.
一、先上效果圖


二、使用GreenDao我們需要導(dǎo)入依賴
1.以下在項(xiàng)目gradle依賴中添加
compile 'org.greenrobot:greendao:3.2.2'//依賴 在最后一行插入
apply plugin: 'org.greenrobot.greendao' //greenDao在第二行插入
greendao {//在依賴導(dǎo)入?yún)R總添加一個(gè)自動添加數(shù)據(jù)庫表名的配置依賴
schemaVersion 1 //數(shù)據(jù)庫版本號
daoPackage 'com.example.greendaodemo.database' //設(shè)置時(shí)生成代碼的目錄
targetGenDir 'src/main/java' //設(shè)置DaoMaster、DaoSession、Dao目錄
}
2.以下在工程gradle依賴中添加
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
3.既然我們要聯(lián)網(wǎng)下載,讀寫權(quán)限肯定不能忘記了
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
三、當(dāng)然還要有實(shí)體類寫入實(shí)體類后,build下make project一下,讓他自動生成GreenDao的三個(gè)類
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Generated;
/**
* author:Created by ZhangPengFei.
* Email: 1271396448@qq.com
* data: 2018/1/18
*/
@Entity
public class User {
@Id
private Long id;
private Integer thread_id;
private Integer start_pos;
private Integer end_pos;
private Integer compelete_size;
private String url;
@Generated(hash = 2041931179)
public User(Long id, Integer thread_id, Integer start_pos, Integer end_pos,
Integer compelete_size, String url) {
this.id = id;
this.thread_id = thread_id;
this.start_pos = start_pos;
this.end_pos = end_pos;
this.compelete_size = compelete_size;
this.url = url;
}
@Generated(hash = 586692638)
public User() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getThread_id() {
return this.thread_id;
}
public void setThread_id(Integer thread_id) {
this.thread_id = thread_id;
}
public Integer getStart_pos() {
return this.start_pos;
}
public void setStart_pos(Integer start_pos) {
this.start_pos = start_pos;
}
public Integer getEnd_pos() {
return this.end_pos;
}
public void setEnd_pos(Integer end_pos) {
this.end_pos = end_pos;
}
public Integer getCompelete_size() {
return this.compelete_size;
}
public void setCompelete_size(Integer compelete_size) {
this.compelete_size = compelete_size;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
}
四、本人使用了單例模式,所以在全局配置里初始化了數(shù)據(jù)庫的操作
import android.app.Application;
import com.example.greendaodemo.database.DaoMaster;
import com.example.greendaodemo.database.DaoSession;
import com.example.greendaodemo.database.UserDao;
/**
* author:Created by ZhangPengFei.
* Email: 1271396448@qq.com
* data: 2018/1/18
*/
public class App extends Application {
public static UserDao userDao;
@Override
public void onCreate() {
super.onCreate();
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "zpf.db", null);
DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb());
DaoSession daoSession = daoMaster.newSession();
userDao = daoSession.getUserDao();
}
}
五、因?yàn)槲覀兪嵌嗑€程,所以寫一個(gè)保存每個(gè)線程下載信息的類
public class DownLoadInfo {
/**
* 保存每個(gè)下載線程下載信息類
*
*/
private int threadId;// 下載器id
private int startPos;// 開始點(diǎn)
private int endPos;// 結(jié)束點(diǎn)
private int compeleteSize;// 完成度
private String url;// 下載文件的URL地址
public DownLoadInfo(int threadId, int startPos, int endPos,
int compeleteSize, String url) {
this.threadId = threadId;
this.startPos = startPos;
this.endPos = endPos;
this.compeleteSize = compeleteSize;
this.url = url;
}
public DownLoadInfo() {
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getThreadId() {
return threadId;
}
public void setThreadId(int threadId) {
this.threadId = threadId;
}
public int getStartPos() {
return startPos;
}
public void setStartPos(int startPos) {
this.startPos = startPos;
}
public int getEndPos() {
return endPos;
}
public void setEndPos(int endPos) {
this.endPos = endPos;
}
public int getCompeleteSize() {
return compeleteSize;
}
public void setCompeleteSize(int compeleteSize) {
this.compeleteSize = compeleteSize;
}
@Override
public String toString() {
return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos
+ ", endPos=" + endPos + ", compeleteSize=" + compeleteSize
+ "]";
}
}
六、寫一個(gè)用來記錄的類,修改,添加和下載完清空數(shù)據(jù)庫的操作
import android.util.Log;
import com.example.greendaodemo.database.UserDao;
import java.util.ArrayList;
import java.util.List;
import static com.bwie.mtd.App.userDao;
/**
* author:Created by ZhangPengFei.
* Email: 1271396448@qq.com
* data: 2018/1/18
*/
public class DownLoadSqlTool {
/**
* 創(chuàng)建下載的具體信息
*/
public void insertInfos(List<DownLoadInfo> infos) {
for (DownLoadInfo info : infos) {
User user = new User(null, info.getThreadId(), info.getStartPos(), info.getEndPos(), info.getCompeleteSize(), info.getUrl());
userDao.insert(user);
}
}
/**
* 得到下載具體信息
*/
public List<DownLoadInfo> getInfos(String urlstr) {
List<DownLoadInfo> list = new ArrayList<DownLoadInfo>();
List<User> list1 = userDao.queryBuilder().where(UserDao.Properties.Url.eq(urlstr)).build().list();
for (User user : list1) {
DownLoadInfo infoss = new DownLoadInfo(
user.getThread_id(), user.getStart_pos(), user.getEnd_pos(),
user.getCompelete_size(), user.getUrl());
Log.d("main-----", infoss.toString());
list.add(infoss);
}
return list;
}
/**
* 更新數(shù)據(jù)庫中的下載信息
*/
public void updataInfos(int threadId, int compeleteSize, String urlstr) {
User user = userDao.queryBuilder()
.where(UserDao.Properties.Thread_id.eq(threadId), UserDao.Properties.Url.eq(urlstr)).build().unique();
user.setCompelete_size(compeleteSize);
userDao.update(user);
}
/**
* 下載完成后刪除數(shù)據(jù)庫中的數(shù)據(jù)
*/
public void delete(String url) {
userDao.deleteAll();
}
}
七、多線程下載的實(shí)踐類
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* author:Created by ZhangPengFei.
* Email: 1271396448@qq.com
* data: 2018/1/18
*/
public class DownloadHttpTool {
/**
* 利用Http協(xié)議進(jìn)行多線程下載具體實(shí)踐類
*/
private static final String TAG = DownloadHttpTool.class.getSimpleName();
private int threadCount;//線程數(shù)量
private String urlstr;//URL地址
private Context mContext;
private Handler mHandler;
private List<DownLoadInfo> downloadInfos;//保存下載信息的類
private String localPath;//目錄
private String fileName;//文件名
private int fileSize;
private DownLoadSqlTool sqlTool;//文件信息保存的數(shù)據(jù)庫操作類
private enum Download_State {
Downloading, Pause, Ready;//利用枚舉表示下載的三種狀態(tài)
}
private Download_State state = Download_State.Ready;//當(dāng)前下載狀態(tài)
private int globalCompelete = 0;//所有線程下載的總數(shù)
public DownloadHttpTool(int threadCount, String urlString,
String localPath, String fileName, Context context, Handler handler) {
super();
this.threadCount = threadCount;
this.urlstr = urlString;
this.localPath = localPath;
this.mContext = context;
this.mHandler = handler;
this.fileName = fileName;
sqlTool = new DownLoadSqlTool();
}
//在開始下載之前需要調(diào)用ready方法進(jìn)行配置
public void ready() {
Log.w(TAG, "ready");
globalCompelete = 0;
downloadInfos = sqlTool.getInfos(urlstr);
if (downloadInfos.size() == 0) {
initFirst();
} else {
File file = new File(localPath + "/" + fileName);
if (!file.exists()) {
sqlTool.delete(urlstr);
initFirst();
} else {
fileSize = downloadInfos.get(downloadInfos.size() - 1)
.getEndPos();
for (DownLoadInfo info : downloadInfos) {
globalCompelete += info.getCompeleteSize();
}
Log.w(TAG, "globalCompelete:::" + globalCompelete);
}
}
}
public void start() {
Log.w(TAG, "start");
if (downloadInfos != null) {
if (state == Download_State.Downloading) {
return;
}
state = Download_State.Downloading;
for (DownLoadInfo info : downloadInfos) {
Log.v(TAG, "startThread");
new DownloadThread(info.getThreadId(), info.getStartPos(),
info.getEndPos(), info.getCompeleteSize(),
info.getUrl()).start();
}
}
}
public void pause() {
state = Download_State.Pause;
}
public void delete() {
compelete();
File file = new File(localPath + "/" + fileName);
file.delete();
}
public void compelete() {
sqlTool.delete(urlstr);
}
public int getFileSize() {
return fileSize;
}
public int getCompeleteSize() {
return globalCompelete;
}
//第一次下載初始化
private void initFirst() {
Log.w(TAG, "initFirst");
try {
URL url = new URL(urlstr);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
fileSize = connection.getContentLength();
Log.w(TAG, "fileSize::" + fileSize);
File fileParent = new File(localPath);
if (!fileParent.exists()) {
fileParent.mkdir();
}
File file = new File(fileParent, fileName);
if (!file.exists()) {
file.createNewFile();
}
// 本地訪問文件
RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
accessFile.setLength(fileSize);
accessFile.close();
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
int range = fileSize / threadCount;
downloadInfos = new ArrayList<DownLoadInfo>();
for (int i = 0; i < threadCount - 1; i++) {
DownLoadInfo info = new DownLoadInfo(i, i * range, (i + 1) * range
- 1, 0, urlstr);
downloadInfos.add(info);
}
DownLoadInfo info = new DownLoadInfo(threadCount - 1, (threadCount - 1)
* range, fileSize - 1, 0, urlstr);
downloadInfos.add(info);
sqlTool.insertInfos(downloadInfos);
}
//自定義下載線程
private class DownloadThread extends Thread {
private int threadId;
private int startPos;
private int endPos;
private int compeleteSize;
private String urlstr;
private int totalThreadSize;
public DownloadThread(int threadId, int startPos, int endPos,
int compeleteSize, String urlstr) {
this.threadId = threadId;
this.startPos = startPos;
this.endPos = endPos;
totalThreadSize = endPos - startPos + 1;
this.urlstr = urlstr;
this.compeleteSize = compeleteSize;
}
@Override
public void run() {
HttpURLConnection connection = null;
RandomAccessFile randomAccessFile = null;
InputStream is = null;
try {
randomAccessFile = new RandomAccessFile(localPath + "/"
+ fileName, "rwd");
randomAccessFile.seek(startPos + compeleteSize);
URL url = new URL(urlstr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes="
+ (startPos + compeleteSize) + "-" + endPos);
is = connection.getInputStream();
byte[] buffer = new byte[1024];
int length = -1;
while ((length = is.read(buffer)) != -1) {
randomAccessFile.write(buffer, 0, length);
compeleteSize += length;
Message message = Message.obtain();
message.what = threadId;
message.obj = urlstr;
message.arg1 = length;
mHandler.sendMessage(message);
sqlTool.updataInfos(threadId, compeleteSize, urlstr);
Log.w(TAG, "Threadid::" + threadId + " compelete::"
+ compeleteSize + " total::" + totalThreadSize);
if (compeleteSize >= totalThreadSize) {
break;
}
if (state != Download_State.Downloading) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
randomAccessFile.close();
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
八、再寫一個(gè)接口類
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* author:Created by ZhangPengFei.
* Email: 1271396448@qq.com
* data: 2018/1/18
*/
public class DownloadUtil {
private DownloadHttpTool mDownloadHttpTool;
private OnDownloadListener onDownloadListener;
private int fileSize;
private int downloadedSize = 0;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
int length = msg.arg1;
synchronized (this) {//加鎖保證已下載的正確性
downloadedSize += length;
}
if (onDownloadListener != null) {
onDownloadListener.downloadProgress(downloadedSize);
}
if (downloadedSize >= fileSize) {
mDownloadHttpTool.compelete();
if (onDownloadListener != null) {
onDownloadListener.downloadEnd();
}
}
}
};
public DownloadUtil(int threadCount, String filePath, String filename,
String urlString, Context context) {
mDownloadHttpTool = new DownloadHttpTool(threadCount, urlString,
filePath, filename, context, mHandler);
}
//下載之前首先異步線程調(diào)用ready方法獲得文件大小信息,之后調(diào)用開始方法
public void start() {
new AsyncTask<Void,Void,Void>() {
@Override
protected Void doInBackground(Void... arg0) {
// TODO Auto-generated method stub
mDownloadHttpTool.ready();
return null;
}
@Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
fileSize = mDownloadHttpTool.getFileSize();
downloadedSize = mDownloadHttpTool.getCompeleteSize();
Log.w("Tag", "downloadedSize::" + downloadedSize);
if (onDownloadListener != null) {
onDownloadListener.downloadStart(fileSize);
}
mDownloadHttpTool.start();
}
}.execute();
}
public void pause() {
mDownloadHttpTool.pause();
}
public void delete(){
mDownloadHttpTool.delete();
}
public void reset(){
mDownloadHttpTool.delete();
start();
}
public void setOnDownloadListener(OnDownloadListener onDownloadListener) {
this.onDownloadListener = onDownloadListener;
}
//下載回調(diào)接口
public interface OnDownloadListener {
public void downloadStart(int fileSize);
public void downloadProgress(int downloadedSize);//記錄當(dāng)前所有線程下總和
public void downloadEnd();
}
}
九、寫一下自己的布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.mtd.MainActivity"> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="7.5dp" android:layout_centerInParent="true" android:layout_marginRight="8dp" android:max="100" android:progress="100" android:visibility="visible" /> <TextView android:id="@+id/tv_Progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/progressBar" android:layout_centerHorizontal="true" android:layout_marginLeft="23dp" android:layout_marginStart="23dp" android:layout_marginTop="18dp" android:text="" /> <Button android:id="@+id/downLoad" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="100dp" android:text="下載" /> <Button android:id="@+id/pause" android:layout_toRightOf="@id/downLoad" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="48dp" android:layout_marginStart="48dp" android:text="暫停" /> </RelativeLayout>
十、奉上自己的Java代碼MainActivity類
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private final String videoUrl = "http://2449.vod.myqcloud.com/2449_22ca37a6ea9011e5acaaf51d105342e3.f20.mp4";
private TextView tv_progress;//進(jìn)度顯示
private ProgressBar progressBar;//進(jìn)度條
private Button downLoad;//下載按鈕
private Button pause;//暫停按鈕
private String path;//下載路徑
private int max;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_progress = (TextView) findViewById(R.id.tv_Progress);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
downLoad = (Button) findViewById(R.id.downLoad);
pause = (Button) findViewById(R.id.pause);
path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/local";
final DownloadUtil downloadUtil = new DownloadUtil(4, path, "drum.mp4", videoUrl, this);
downloadUtil.setOnDownloadListener(new DownloadUtil.OnDownloadListener() {
@Override
public void downloadStart(int fileSize) {
Log.i("TAG---fileSize", fileSize + "");
max = fileSize;//文件總長度
progressBar.setMax(fileSize);
}
@Override
public void downloadProgress(int downloadedSize) {
Log.i("TAG---downloadedSize", downloadedSize + "");
progressBar.setProgress(downloadedSize);
tv_progress.setText((int) downloadedSize * 100 / max + "%");
}
@Override
public void downloadEnd() {
Log.i("TAG---end", "End");
}
});
/**
* 下載的點(diǎn)擊事件
*/
downLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
downloadUtil.start();
}
});
/**
* 暫停的點(diǎn)擊事件
*/
pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
downloadUtil.pause();
}
});
}
}
相關(guān)文章
Android里實(shí)現(xiàn)退出主程序的提示代碼
當(dāng)用戶選擇"確定",就退出當(dāng)前的對話框。其中,有個(gè)很重要的函數(shù),Activity.finish(),通過調(diào)用這個(gè)函數(shù),退出當(dāng)前運(yùn)行的整個(gè)Android程序2013-06-06
關(guān)于Android多渠道打包的進(jìn)階知識
前一篇文章主要介紹了關(guān)于Android程序的多渠道打包方法,這一篇文章介紹了多渠道打包的進(jìn)階知識,還不會的同學(xué)快進(jìn)來學(xué)習(xí)下吧,建議收藏以防迷路2021-08-08
Android實(shí)現(xiàn)簡單計(jì)時(shí)器功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡單計(jì)時(shí)器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
Android通知欄微技巧一些需要注意的小細(xì)節(jié)
這篇文章主要介紹了Android通知欄微技巧,那些你所沒關(guān)注過的小細(xì)節(jié),小編把此文分享到腳本之家平臺,需要的朋友可以參考下2018-04-04
Android應(yīng)用中使用及實(shí)現(xiàn)系統(tǒng)“分享”接口實(shí)例
為了應(yīng)用的推廣、傳播,很多的應(yīng)用中都有“分享”功能,這篇文章主要介紹了Android應(yīng)用中使用及實(shí)現(xiàn)系統(tǒng)“分享”接口實(shí)例,有興趣的可以了解一下。2016-12-12
Android自定義驗(yàn)證碼輸入框的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Android自定義驗(yàn)證碼輸入框的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
android app進(jìn)行代碼混淆實(shí)例詳解
本文主要節(jié)誒是android app進(jìn)行代碼混淆,這里整理了詳細(xì)的資料,及實(shí)現(xiàn)代碼,有興趣的小伙伴可以參考下2016-09-09
Android Socket通信實(shí)現(xiàn)簡單聊天室
這篇文章主要為大家詳細(xì)介紹了Android網(wǎng)絡(luò)編程之Socket通信實(shí)現(xiàn)簡單聊天室,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03

