android 视频断点下载,android——文件断点续传下载(二)

4cbda71ff1b2

GIF.gif

上篇博客写的是单个文件单个线程的断点续传下载(文件断点续传下载(一)),这里是在之前的基础上进行了扩展,变成了多文件,多线程同时下载/暂停,同时添加了通知栏下载状态显示,虽然代码是基于之前的,还是做了不少改动。

1、activity中显示的是一个列表,点击事件的触发是在列表的条目中,就需要在点击开始时,将点击事件通过接口回调到activity中,进行权限的申请

2、当多任务、多线程同时操作时,利用BroadcastReceiver进行通信会导致界面不有点卡,就改用了Messenger+Handler进行通信

3、需要对Notification做8.0的适配

4、利用TimerTask、Timer进行消息的轮询

5、改用线程池对线程的管理

先看下列表适配器中的逻辑,很简单,

public class FileListAdapter extends BaseAdapter {

private Context mContext;

private List list;

public FileListAdapter(Context context, List list) {

this.mContext = context;

this.list = list;

}

@Override

public int getCount() {

return list == null ? 0 : list.size();

}

@Override

public Object getItem(int position) {

return list.get(position);

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(final int position, View convertView, ViewGroup parent) {

ViewHolder viewHolder;

final FileInfo fileInfo = list.get(position);

if (convertView == null) {

viewHolder = new ViewHolder();

convertView = LayoutInflater.from(mContext).inflate(R.layout.file_list_adapter_item, parent, false);

viewHolder.tvFileName = convertView.findViewById(R.id.tv_file_name);

viewHolder.progressBar = convertView.findViewById(R.id.progress_bar);

viewHolder.btnStop = convertView.findViewById(R.id.btn_stop);

viewHolder.btnStart = convertView.findViewById(R.id.btn_start);

viewHolder.progressBar.setMax(100);

viewHolder.tvFileName.setText(fileInfo.fileName);

viewHolder.btnStart.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

if (listener != null) {

listener.doStart(position, fileInfo);

}

}

});

viewHolder.btnStop.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

if (listener != null) {

listener.doStop(position, fileInfo);

}

}

});

convertView.setTag(viewHolder);

} else {

viewHolder = (ViewHolder) convertView.getTag();

}

viewHolder.progressBar.setProgress(fileInfo.finished);

return convertView;

}

/**

* 更新列表项中条目下载进度

*/

public void updateProgress(List fileInfos) {

this.list = fileInfos;

notifyDataSetChanged();

}

class ViewHolder {

TextView tvFileName;

ProgressBarView progressBar;

Button btnStop, btnStart;

}

private onItemClick listener;

public void setOnItemClick(onItemClick listener) {

this.listener = listener;

}

public interface onItemClick {

/**

* 开始下载

*

* @param position

*/

void doStart(int position, FileInfo fileInfo);

/**

* 暂停下载

*

* @param position

*/

void doStop(int position, FileInfo fileInfo);

}

}

不过有点细节需要注意的,赋值一次后不需要变动的数据,就不需要数据复用了,这样会提高一些性能,这里文件名称的赋值、ProgressBar的初始化、开始/暂停的点击事件就没有进行复用,然后在回调事件中去做权限的申请、开启下载/暂停下载的逻辑处理。

之前是通过startService的方式开启一个Service的,这里没有采用该方式,用的是绑定服务的方式,在onCreate()方法中绑定一个Service;

//绑定service

Intent intent = new Intent(this, DownLoadService.class);

bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);

Service绑定后,就会回调ServiceConnection中的onServiceConnected()方法,在该方法中利用Messenger同时在Service的onBind()方法中也利用Messenger进行activity和Service数据通信双向绑定;

ServiceConnection serviceConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

//绑定回调

//获得service中的Messenger

mServiceMessenger = new Messenger(service);

//创建activity中的Messenger

Messenger messenger = new Messenger(mHandler);

//创建一个消息

Message message = new Message();

message.what = DownLoadService.MSG_BIND;

message.replyTo = messenger;

//使用service的messenger发送activity中的Messenger

try {

mServiceMessenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

//解绑回调

}

};

@Override

public IBinder onBind(Intent intent) {

//创建一个Messenger对象

Messenger messenger = new Messenger(mHandler);

//返回Messenger的binder

return messenger.getBinder();

}

Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

switch (msg.what) {

case MSG_INIT:

mSGINIT(msg);

break;

case MSG_BIND:

//处理绑定的Messenger

mActivityMessenger = msg.replyTo;

break;

case MSG_START:

mSGSTART(msg);

break;

case MSG_STOP:

mSGSTOP(msg);

break;

}

}

};

实现了activity和service的通信绑定,在点击开始或者暂停时就可以Messenger给service发送消息了;

private void doStart(FileInfo fileInfo) {

//将开始下载的文件id存储在集合中

if (fileIds.contains(fileInfo.id)) {

//正在下载提示用户 并返回

Toast.makeText(this, "该文件正在下载中...", Toast.LENGTH_LONG).show();

return;

}

//将文件id添加到集合中

fileIds.add(fileInfo.id);

Message message = new Message();

message.what = DownLoadService.MSG_START;

message.obj = fileInfo;

try {

mServiceMessenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

Log.e("doStart-->", "doStart");

}

private void doStop(FileInfo fileInfo) {

//暂停下载时将存储的下载文件id移除

for (int i = 0; i < fileIds.size(); i++) {

Integer integer = fileIds.get(i);

if (integer == fileInfo.id) {

fileIds.remove(integer);

break;

}

}

Log.e("doStop-->", "doStop");

Message message = new Message();

message.what = DownLoadService.MSG_STOP;

message.obj = fileInfo;

try {

mServiceMessenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

}

在开始下载时需要将下载文件的id缓存的集合中,用于管理用户多次点击同一个正在下载的文件,在暂停下载或下载完成后在从集合中移除对应的文件id,消息发送后,在Service中Handler的handerMessage()方法中根据msg.whate进行处理。

开始下载时还是和之前一样,在子线程中获取文件的大小,在本地创建文件夹,然后通过handler通知进行下载,只是改成了利用线程池进行线程的管理;

/**

* 开始下载

*

* @param msg

*/

private void mSGSTART(Message msg) {

FileInfo fileInfo = (FileInfo) msg.obj;

Log.e(TAG, ACTION_START);

//开启线程

InitThread thread = new InitThread(fileInfo);

DownLoadTask.sExecutorService.execute(thread);

}

在下载的时候需要对多线程、多任务进行管理,同时开始时通过消息告诉activity去开启消息通知栏,显示下载进度;

/**

* 初始化处理

*

* @param msg

*/

private void mSGINIT(Message msg) {

FileInfo fileInfo = (FileInfo) msg.obj;

Log.e(TAG, MSG_INIT + "");

//启动下载任务

DownLoadTask task = new DownLoadTask(DownLoadService.this, fileInfo, 1, mActivityMessenger);

task.downLoad();

//把下载任务添加到集合中

mTasks.put(fileInfo.id, task);

//给Activity发送消息

Message message = new Message();

message.what = MSG_START;

message.obj = fileInfo;

try {

mActivityMessenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

}

在activity的handler中就可以通知开启通知状态栏,

/**

* 开始下载通知通知栏

*

* @param msg

*/

private void mSGSTART(Message msg) {

//显示一个通知

FileInfo info = (FileInfo) msg.obj;

if (info != null) {

mNotificationUtil.showNotification(info);

}

}

/**

* 通知的工具类

*/

public class NotificationUtil {

//实例化Notification通知管理类

private NotificationManager mNotificationManager;

//管理Notification的集合

private Map mNotification;

private Context mContext;

public NotificationUtil(Context context) {

mContext = context;

mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

mNotification = new HashMap<>();

}

/**

* 显示notification

*/

public void showNotification(FileInfo fileInfo) {

//是否存在该文件下载通知

if (!mNotification.containsKey(fileInfo.id)) {

//android8.0 notification适配

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT);

channel.canBypassDnd();//可否绕过请勿打扰模式

channel.enableLights(true);//有新消息时手机有闪光

channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);//锁屏显示通知

channel.setLightColor(Color.argb(100, 100, 100, 100));//指定闪光时的灯光颜色

channel.canShowBadge();//有新消息时桌面角标显示

channel.enableVibration(true);//振动

AudioAttributes audioAttributes = channel.getAudioAttributes();//获取系统响铃

channel.getGroup();//获取通知渠道组

channel.setBypassDnd(true);//设置可以绕过请勿打扰模式

channel.setVibrationPattern(new long[]{100, 100, 200});//振动模式

channel.shouldShowLights();//是否会闪灯

mNotificationManager.createNotificationChannel(channel);

}

//不存在就新增

//创建通知的对象

Notification.Builder notification;

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {

//android8.0 notification适配

notification = new Notification.Builder(mContext)

.setChannelId("channel_id");

} else {

notification = new Notification.Builder(mContext);

}

//设置滚动文字

notification.setTicker(fileInfo.fileName + "开始下载")

.setWhen(System.currentTimeMillis())//设置通知显示的时间

.setSmallIcon(R.mipmap.ic_launcher_round)//设置图标

.setContentTitle(fileInfo.fileName)

.setContentText(fileInfo.fileName + "下载中...")

.setAutoCancel(true);//设置通知的特性 FLAG_AUTO_CANCEL点击通知栏自动消失

//设置点击通知栏的操作

Intent intent = new Intent(mContext, MainActivity.class);

PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

notification.setContentIntent(pendingIntent);

//发出通知广播

mNotificationManager.notify(fileInfo.id, notification.build());

//把通知加到集合中

mNotification.put(fileInfo.id, notification);

}

}

/**

* 取消通知

*

* @param id

*/

public void cancleNotification(int id) {

//取消通知

mNotificationManager.cancel(id);

//同时删除集合中的通知

mNotification.remove(id);

}

/**

* 更新通知进度

*

* @param id

* @param progress

*/

public void updateNotification(int id, int progress) {

Notification.Builder notification = mNotification.get(id);

if (notification != null) {

//更新进度条

notification.setProgress(100, progress, false);

//重新发送通知

mNotificationManager.notify(id, notification.build());

}

}

}

消息通知栏是通过NotificationUtil工具类来管理的,调用showNotification()方法创建并显示一个通知状态栏,在进度更新时调用updateNotification()方法,下载完成或者取消通知状态栏调用cancleNotification()就可以了,NotificationUtil类中已经对Notification做了8.0的适配处理,关于Notification的8.0适配网上很多资料,不过需要注意在调用setChannelId()时设置的id要和NotificationChannel创建时传入的id一致;

NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT);

在DownLoadTask中的downLoad()中还是要根据数据库查询的结果进行下载,数据库中如果有下载记录,直接将查询到的下载记录传入到下载线程中,如果没有就需要新增一个下载任务实例,在新增的时候需要注意,要根据传入的单个任务的下载线程个数来弄,要处理好每个线程的开始下载位置和结束下载位置;

public void downLoad() {

//读取数据库的线程信息

List threadInfos = dao.queryThread(mFileInfo.url);

if (threadInfos.size() == 0) {

//获得每个线程下载的长度 分线程下载,多个线程下载一个文件

int length = mFileInfo.length / mThreadCount;

for (int i = 0; i < mThreadCount; i++) {

ThreadInfo threadInfo = new ThreadInfo();

threadInfo.id = i;

threadInfo.url = mFileInfo.url;

threadInfo.thread_start = length * i;

threadInfo.thread_end = (i + 1) * length - 1;

threadInfo.finished = 0;

if (i == mThreadCount - 1) {

//最后一个线程下载的结束位置

threadInfo.thread_end = mFileInfo.length;

}

//添加到线程集合信息

threadInfos.add(threadInfo);

//向数据库中插入一条信息

if (!dao.isExists(threadInfo.url, threadInfo.id)) {

dao.insertThread(threadInfo);

}

}

}

threadList = new ArrayList<>();

//启动多个线程进行下载

for (ThreadInfo threadInfo : threadInfos) {

DownLoadThread downLoadThread = new DownLoadThread(threadInfo);

DownLoadTask.sExecutorService.execute(downLoadThread);

threadList.add(downLoadThread);

}

//启动定时任务

mTimer.schedule(timerTask, 1000, 1000);

}

在开始的同时开启一个Timer进行轮询;

TimerTask timerTask = new TimerTask() {

@Override

public void run() {

if (isPause) {

//暂停时要将定时器清除掉

cancelTimer();

}

//发送消息修改activity进度

Message message = new Message();

message.what = DownLoadService.MSG_UPDATE;

message.arg1 = mFinished * 100 / mFileInfo.length;

message.arg2 = mFileInfo.id;

Log.e("DownLoadService", mFinished + "");

try {

messenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

}

};

/**

* 清除定时器

*/

private void cancelTimer(){

//取消定时器

timerTask.cancel();

timerTask = null;

mTimer.cancel();

mTimer = null;

}

在下载任务进行的同时会每隔一段时间回调TimerTask中的run方法,通过Messenger向activity传递下载的进度,在暂停下载是需要将Timer移除并置为null。

在DownLoadThread线程中下载完毕后做法和之前一样,多了要将Timer移除并置为null;

/**

* 下载线程

*/

class DownLoadThread extends Thread {

private ThreadInfo mThreadInfo;

//线程是否执行完毕

public boolean isFinished = false;

public DownLoadThread(ThreadInfo threadInfo) {

mThreadInfo = threadInfo;

}

@Override

public void run() {

super.run();

HttpURLConnection conn = null;

RandomAccessFile raf = null;

InputStream input = null;

try {

URL url = new URL(mThreadInfo.url);

conn = (HttpURLConnection) url.openConnection();

conn.setReadTimeout(6000);

conn.setRequestMethod("GET");

//设置下载位置

int start = mThreadInfo.thread_start + mThreadInfo.finished;

conn.setRequestProperty("Range", "bytes=" + start + "-" + mThreadInfo.thread_end);

//设置一个文件写入位置

File file = new File(DownLoadService.DOWNLOAD_PATH, mFileInfo.fileName);

raf = new RandomAccessFile(file, "rwd");

//设置文件写入位置

raf.seek(start);

mFinished += mThreadInfo.finished;

//开始下载了

if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {

//读取数据

input = conn.getInputStream();

byte[] buffer = new byte[1024 * 4];

int len = -1;

while ((len = input.read(buffer)) != -1) {

//写入文件

raf.write(buffer, 0, len);

//下载进度发送广播给activity

//累加整个文件的进度

mFinished += len;

//累加每个线程下载的进度

int currentFinished = mThreadInfo.finished;

mThreadInfo.finished = currentFinished + len;

//下载暂停是要把进度保存在数据库中

if (isPause) {

dao.updateThread(mThreadInfo.url, mThreadInfo.id, mThreadInfo.finished);

return;

}

}

//标识线程执行完毕

isFinished = true;

//检查下载任务是否执行完毕

checkAllThreadsFinished();

}

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if (conn != null) {

conn.disconnect();

}

if (raf != null) {

raf.close();

}

if (input != null) {

input.close();

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

/**

* 判断是否所有线程执行完毕

*/

private synchronized void checkAllThreadsFinished() {

boolean allFinished = true;

//遍历线程集合判断线程是否执行完毕

for (DownLoadThread downLoadThread : threadList) {

if (!downLoadThread.isFinished) {

allFinished = false;

break;

}

}

if (allFinished) {

cancelTimer();

//发送消息告诉activity下载完毕

Message message = new Message();

message.what = DownLoadService.MSG_FINISHED;

message.obj = mFileInfo;

try {

messenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

//删除线程信息

dao.deleteThread(mFileInfo.url);

}

}

在activity更新下载进度时要根据对应的文件id去更新进度;

/**

* 下载进度更新

*

* @param msg

*/

private void mSGUPDATE(Message msg) {

int finished = msg.arg1;

int id = msg.arg2;

updataItem(id, finished);

//更新通知进度

mNotificationUtil.updateNotification(id, finished);

}

/**

* 下载完成后更新

*

* @param msg

*/

private void mSGFINISHED(Message msg) {

//更新进度为0

FileInfo fileInfo = (FileInfo) msg.obj;

//下载完成后移除存储的文件id

for (int i = 0; i < fileIds.size(); i++) {

Integer integer = fileIds.get(i);

if (integer == fileInfo.id) {

fileIds.remove(integer);

break;

}

}

if (fileInfo != null) {

updataItem(fileInfo.id, fileInfo.length);

}

Log.e("fileInfo.length-->", fileInfo.length + "");

Toast.makeText(MainActivity.this, fileInfo.fileName + "下载完毕", Toast.LENGTH_LONG).show();

//下载完毕后取消通知

mNotificationUtil.cancleNotification(fileInfo.id);

}

private void updataItem(int id, int finished) {

for (int i = 0; i < lists.size(); i++) {

FileInfo fileInfo = lists.get(i);

if (fileInfo.id == id) {

lists.remove(i);

fileInfo.finished = finished;

lists.add(i, fileInfo);

break;

}

}

adapter.updateProgress(lists);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值