临近过年,今天将花了两天时间写的下载模块贡献出来,以前我也是天天需要什么功能,就天天查百度,现在自己也能开源一点东西了,也是非常很开心的,hahaha。
离职前写了音乐播放器的音乐下载界面,自己封装了下载线程,并使用多线程断点续传下载音乐,感觉多线程下载音乐也并不比单线程下载快多少,就一个4M的音乐来说,开3条线程下了46s,单线程下了60s,如果同时现在3个音乐,那么多线程下载相当于开了9 个线程,速度尽然和单线程的速度持平,有时候还会慢于3条线程,于是我放弃了多线程断点续传,还是老老实实写单线程的吧(- -!我觉得应该是我的下载线程的毛病,哈哈)。。。
于是今天就用了xutils3的网络访问模块,实现的需求是。
1、APP关闭后(kill),重新开启时,能记录当前下载进度,断点续传。
2、音乐能增删,暂停,显示下载速度和下载进度。
其实都写好了哈,主要说说编码思路。
通过Acitivity的bindService,可以调用service的bind来传递需要下载的文件信息到service,service通过调用xutils的带进度的下载模块,来回调下载信息,同时service通过下载观察者不断观察下载进度,将进度传递给Activity,之后setAdapter即可。
里面最主要的东西就是DownloadService,所有的代码逻辑都是这个类处理的。
这个类有个问题,
就是文件名的获取是根据Url倒叙截取/后面的文件来命名的,如果你的url不符合规则,请自行定义。
private String getfileName(String url) {//根据下载地址给下载文件命名
try{
return url.substring(url.lastIndexOf("/") + 1);
}catch (Exception ex){
return mBinder.getCurrentListSize()+"";//返回一个数字
}
}
权限问题
代码中加了6.0的动态权限判断,因为要操作SD卡,所以这里也要注意。
因为我没有6.0的测试手机,所以这里虽然都写了,但是只是作为提示使用,需要你自己更改逻辑位置,如果是6.0以下的,并没有问题的 。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Toast.makeText(getBaseContext(),"已获取SD卡权限",Toast.LENGTH_SHORT).show();
//6.0系统在这里进行service的初始化
break;
case 1:
Toast.makeText(getBaseContext(),"SD卡权限未开启",Toast.LENGTH_SHORT).show();
break;
}
}
};
//判断权限方法
private void initCheckSelfPermission() {
//判断是否有内存卡权限
CheckSelfPermission.getSDCard(this);
}
//动态权限回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
doNext(requestCode,grantResults);
}
//动态权限回调,yes和no点击
private void doNext(int requestCode, int[] grantResults) {
if (requestCode == DownLoadConstant.WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d("权限获取成功");
handler.sendEmptyMessage(0);
} else {
handler.sendEmptyMessage(1);
}
}
}
最关键的是DownloadService ,bind里有对外提供的增删改查方法
在下载的过程中,有缓存GetFileSharePreance 不断在保存下载的urls下载地址列表和文件下载进度,为APP下次重启提供断点,里面有详细注释。
/**
* 作者:朱亮 on 2017/1/17 15:36
* 邮箱:171422696@qq.com
* 下载服务类,执行下载任务,并将进度传递到Activity中(这里用一句话描述这个方法的作用)
*/
public class DownloadService extends Service {
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_STOP = "ACTION_STOP";
public static final String ACTION_DELETE = "ACTION_DELETE";
private List<OnDownLoadBackListener> loadBackListeners = new ArrayList<OnDownLoadBackListener>();//注册一个下载观察者
private static final int DOWNLOADSERVICEID = 1;//下载监听传递值
private FileInfo fileInfo;//下载对象
private MyBinder myBinder = new MyBinder();
private ArrayList<FileInfo> mCurretList;//当前服务中的正在下载列表
private ArrayList<FileInfo> mCurretListOut;//输出的下载列表
private GetFileSharePreance preance;
private HashMap<Integer,DownloanThread> map;//创建动态对象
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
mCurretList = new ArrayList<>();
mCurretListOut = new ArrayList<>();
map = new HashMap<Integer,DownloanThread>();
preance = BaseApplication.getFileSharePreance();//获取缓存实例,这里的缓存主要用在APP关闭后,重新进入下载列表后显示使用
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
// 获得Activity穿来的参数
fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
if (ACTION_START.equals(intent.getAction())) {
startDownLoad(fileInfo);//开始下载
} else if (ACTION_STOP.equals(intent.getAction())) {
stopDownLoad(fileInfo);//停止下载
} else if (ACTION_DELETE.equals(intent.getAction())) {
deleteDownLoad(fileInfo);//删除下载
}
}
return super.onStartCommand(intent, flags, startId);
}
//开始下载逻辑处理方法
private void startDownLoad(FileInfo fileInfo) {
if (fileInfo != null && !TextUtils.isEmpty(fileInfo.getUrl())) {
preance.setUrlList(mCurretList);//从缓存地址集合中移除这一条(保存最新的地址集合)
map.put(fileInfo.getId(),new DownloanThread());
map.get(fileInfo.getId()).Download(fileInfo);//开始下载
SPUtils.setLong(getApplicationContext(), SPKey.CURRENTLIST_SIZE, fileInfo.getId());//设置下载ID自增
sendHandler();
}
}
//停止下载逻辑处理方法
private void stopDownLoad(FileInfo fileInfo) {
if (fileInfo != null && !TextUtils.isEmpty(fileInfo.getUrl())) {
if (map.get(fileInfo.getId()) != null) {
map.get(fileInfo.getId()).downLoadCancel();//下载线程停止
}
sendHandler();
}
}
//删除下载逻辑处理方法
private void deleteDownLoad(FileInfo fileInfo) {
if (fileInfo != null && !TextUtils.isEmpty(fileInfo.getUrl())) {
for (int x = 0; x < mCurretList.size(); x++) {//如果是正在下载的列表,先暂停下载。
if (mCurretList.get(x).getUrl().equals(fileInfo.getUrl())) {//如果匹配到了
preance.delete(mCurretList.get(x).getUrl());//删除下载缓存
if(map.get(fileInfo.getId()) != null){
map.get(fileInfo.getId()).downLoadCancel();//从下载线程停止
}
mCurretList.remove(mCurretList.get(x));//从下载列表移除这条
preance.setUrlList(mCurretList);//从缓存地址集合中移除这一条(保存最新的地址集合)
preance.delete(fileInfo.getUrl());//删除缓存数据
}
}
sendHandler();
}
}
public class MyBinder extends Binder {
//注册一个下载观察者
public void registDownLoadListener(OnDownLoadBackListener loadBackListener) {
loadBackListeners.add(loadBackListener);
}
//取消一个观察者
public void unregistDownLoadListener(OnDownLoadBackListener loadBackListener) {
loadBackListeners.remove(loadBackListener);
}
//设置当前的下载列表(会清空当前的下载列表哦,慎用,如果需要请用 appendToCurrentList())
public void setCurrentList(ArrayList list) {
mCurretList.clear();
if (list != null) {
mCurretList.addAll(list);
}
}
//获取当前的正在下载的所有下载信息列表
public ArrayList<FileInfo> getCurrentList() {
return mCurretList;
}
//从缓存中获取下载信息
public ArrayList<FileInfo> getCurrentListFShareP(){
ArrayList<FileInfo> infos = new ArrayList<>();
ArrayList<String> list = preance.getUrlList();//先获取下载url集合
for(int x = 0 ; x < list.size() ; x++){//根据url地址查询下载的缓存数据
infos.add(preance.getFilePre(list.get(x)));
}
setCurrentList(infos);
return infos;
}
//下载次数自增
public int getCurrentListSize() {
long l = SPUtils.getLong(getApplicationContext(), SPKey.CURRENTLIST_SIZE);
return (int) l + 1;
}
//添加当前的下载列表(相同下载地址文件过滤)
public void appendToCurrentList(FileInfo info) {
if (info != null) {
// 只添加当前下载列表中没有的
boolean existed = false;
for (int j = 0; j < mCurretList.size(); j++) {
if ((mCurretList.get(j).getUrl()).equals(info.getUrl())) {
existed = true;
}
}
if (!existed) {
mCurretList.add(info);
startDownLoad(info);//开始下载
}
}
}
//停止下载
public void stopDownLoad(FileInfo fileInfo) {
if (fileInfo != null && !TextUtils.isEmpty(fileInfo.getUrl())) {
stopDownLoad(fileInfo);
}
}
//删除当前的某一个列表
public void deleyeCurrentList(FileInfo fileInfo) {
deleteDownLoad(fileInfo);
}
//删除所有的下载列表信息(未编写)
public void deleteAll(){
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWNLOADSERVICEID:
for (int x = 0; x < loadBackListeners.size(); x++) {//有多少个界面在观察下载数据
mCurretListOut.clear();
for(int j = 0 ; j < mCurretList.size() ;j++){//正在下载列表中的数据
if(map.get(mCurretList.get(j).getId()) == null){//假如下载线程没有开启
mCurretListOut.add(mCurretList.get(j));
}else {//从下载线程中取数据
mCurretListOut.add(map.get(mCurretList.get(j).getId()).getFileInfo());
}
}
loadBackListeners.get(x).onDownloadSize(mCurretListOut);//传递给不同的观察者
}
//当下载的时候,延迟不断执行这句话,让所有的观察者不至于失去下载的各种信息,这里控制视图更新频率
handler.sendEmptyMessageDelayed(
DOWNLOADSERVICEID, 1000);
break;
default:
super.handleMessage(msg);
}
}
};
//发送hanlde
private void sendHandler(){
if (!handler.hasMessages(DOWNLOADSERVICEID)) {//如果hanlde的观察者没有了,重新发送一个
handler.sendEmptyMessage(DOWNLOADSERVICEID);
}
}
}
下载的实际类DownloanThread,名字看起来是个线程,其实不是,只是将实际下载类分离出来,调用xutils的下载方法和取消下载方法,提供一个获取下载文件详细信息方法。
//获取下载中的文件信息
public FileInfo getFileInfo(){
return new FileInfo(fileInfo.getId(),fileInfo.getUrl(),fileInfo.getFileName(),length,downlenth,downType);
}
在这里回调文件的下载信息
cancelable = x.http().get(params, new Callback.ProgressCallback() {
@Override
public void onWaiting() {
LogUtils.e("等待中...");
downType = 0;
}
@Override
public void onStarted() {
LogUtils.e("开始下载。。。");
downType = 1;
}
@Override
public void onLoading(long total, long current, boolean isDownloading) {
LogUtils.e("tital = "+total + " current = "+current + " isDownLoading = "+isDownloading);
length = (int) total;
downlenth = (int) current;
downType = 2;
preance.update(new FileInfo(info.getId(),info.getUrl(),info.getFileName(),(int)total,(int)current,downType));
}
@Override
public void onSuccess(File result) {
LogUtils.e("下载成功");//在这里下载成功的数据写进数据库里即可(已下载界面的数据从这里来的,哈哈,那个界面自己写)
downType = 3;
}
@Override
public void onError(Throwable ex, boolean isOnCallback) {
LogUtils.e("下载失败");
downType = 4;
}
@Override
public void onCancelled(CancelledException cex) {
LogUtils.e("下载取消");
downType = 5;
}
@Override
public void onFinished() {
LogUtils.e("下载完成");
}
});
已下载的界面没有写代码,偷个懒,请自己写哈