增加获取执行中的任务api

增加获取剩余时间的api
修复重构loader导致的m3u8问题
pull/617/head
laoyuyu 5 years ago
parent b1a2f232fa
commit 432ae1c145
  1. 20
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
  2. 33
      Aria/src/main/java/com/arialyy/aria/core/download/target/GroupNormalTarget.java
  3. 30
      Aria/src/main/java/com/arialyy/aria/core/queue/AbsTaskQueue.java
  4. 1
      Aria/src/main/java/com/arialyy/aria/core/queue/DTaskQueue.java
  5. 10
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
  6. 2
      DEV_LOG.md
  7. 2
      FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoaderUtil.java
  8. 5
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUFileInfoTask.java
  9. 23
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUThreadTaskAdapter.java
  10. 99
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java
  11. 7
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveStateManager.java
  12. 52
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveLoader.java
  13. 5
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveUtil.java
  14. 39
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodLoader.java
  15. 3
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodUtil.java
  16. 19
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java
  17. 8
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodStateManager.java
  18. 17
      PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsEntity.java
  19. 8
      PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHelper.java
  20. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java
  21. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoader.java
  22. 7
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseListener.java
  23. 18
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/DownloadGroupListener.java
  24. 15
      PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java
  25. 3
      PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java
  26. 36
      PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTask.java
  27. 43
      PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java
  28. 34
      PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java
  29. 1
      PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java
  30. 1
      app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java
  31. 3
      app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java
  32. 6
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveDLoadActivity.java
  33. 2
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDLoadActivity.java
  34. 5
      app/src/main/res/layout/activity_download_group.xml
  35. 10
      app/src/main/res/layout/activity_m3u8_live.xml
  36. 5
      app/src/main/res/layout/activity_single.xml
  37. 15
      app/src/main/res/layout/layout_content_single.xml

@ -36,6 +36,8 @@ import com.arialyy.aria.core.download.target.HttpNormalTarget;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.scheduler.TaskSchedulers;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.orm.DbEntity;
@ -302,6 +304,24 @@ public class DownloadReceiver extends AbsReceiver {
return DbDataHelper.getDGEntityByHash(url);
}
/**
* 获取执行中的任务
*
* @return 没有执行中的任务返回null
*/
public List<DownloadEntity> getDRunningTask() {
return DTaskQueue.getInstance().getRunningTask(DownloadEntity.class);
}
/**
* 获取执行中的任务
*
* @return 没有执行中的任务返回null
*/
public List<DownloadGroupEntity> getDGRunningTask() {
return DGroupTaskQueue.getInstance().getRunningTask(DownloadGroupEntity.class);
}
/**
* 下载任务是否存在
*

@ -17,8 +17,10 @@ package com.arialyy.aria.core.download.target;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.HttpOption;
import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog;
import java.util.List;
/**
@ -101,6 +103,37 @@ public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
return mConfigHandler.setSubFileName(subTaskFileName);
}
/**
* 如果无法获取到组合任务到总长度请调用该方法
* 请注意
* 1如果组合任务到子任务数过多请不要使用该标志位否则Aria将需要消耗大量的时间获取组合任务的总长度直到获取完成组合任务总长度后才会执行下载
* 2如果你的知道组合任务的总长度请使用{@link #setFileSize(long)}设置组合任务的长度
* 3由于网络或其它原因的存在这种方式获取的组合任务大小有可能是不准确的
*/
public GroupNormalTarget unknownSize() {
((DGTaskWrapper) getTaskWrapper()).setUnknownSize(true);
return this;
}
/**
* 任务组总任务大小任务组是一个抽象的概念没有真实的数据实体任务组的大小是Aria动态获取子任务大小相加而得到的
* 如果你知道当前任务组总大小你也可以调用该方法给任务组设置大小
*
* 为了更好的用户体验组合任务最好设置文件大小默认需要强制设置文件大小如果无法获取到总长度请调用{@link #unknownSize()}
*
* @param fileSize 任务组总大小
*/
public GroupNormalTarget setFileSize(long fileSize) {
if (fileSize <= 0) {
ALog.e(TAG, "文件大小不能小于 0");
return this;
}
if (getEntity().getFileSize() <= 1 || getEntity().getFileSize() != fileSize) {
getEntity().setFileSize(fileSize);
}
return this;
}
@Override public boolean isRunning() {
return mConfigHandler.isRunning();
}

@ -16,6 +16,8 @@
package com.arialyy.aria.core.queue;
import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.manager.TaskWrapperManager;
@ -31,6 +33,8 @@ import com.arialyy.aria.core.task.DownloadTask;
import com.arialyy.aria.core.task.UploadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lyy on 2017/2/23. 任务队列
@ -64,7 +68,33 @@ public abstract class AbsTaskQueue<TASK extends AbsTask, TASK_WRAPPER extends Ab
abstract int getQueueType();
/**
* 获取执行中的任务
*
* @return 没有执行中的任务返回null
*/
public <T extends AbsEntity> List<T> getRunningTask(Class<T> type) {
List<TASK> exeTask = mExecutePool.getAllTask();
List<TASK> cacheTask = mCachePool.getAllTask();
List<T> entities = new ArrayList<>();
if (exeTask != null && !exeTask.isEmpty()) {
for (TASK task : exeTask) {
entities.add((T) task.getTaskWrapper().getEntity());
}
}
if (cacheTask != null && !cacheTask.isEmpty()) {
for (TASK task : cacheTask) {
entities.add((T) task.getTaskWrapper().getEntity());
}
}
return entities.isEmpty() ? null : entities;
}
@Override public boolean taskIsRunning(String key) {
if (TextUtils.isEmpty(key)) {
ALog.w(TAG, "key为空,无法确认任务是否执行");
return false;
}
TASK task = mExecutePool.getTask(key);
if (task == null && ThreadTaskManager.getInstance().taskIsRunning(key)) {
ThreadTaskManager.getInstance().removeTaskThread(key);

@ -18,6 +18,7 @@ package com.arialyy.aria.core.queue;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.event.DMaxNumEvent;
import com.arialyy.aria.core.event.Event;
import com.arialyy.aria.core.event.EventMsgUtil;

@ -28,6 +28,7 @@ import com.arialyy.aria.core.common.ProxyHelper;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.core.scheduler.TaskSchedulers;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.upload.target.FtpBuilderTarget;
@ -230,6 +231,15 @@ public class UploadReceiver extends AbsReceiver {
ITask.UPLOAD));
}
/**
* 获取执行中的任务
*
* @return 没有执行中的任务返回null
*/
public List<UploadEntity> getURunningTask() {
return UTaskQueue.getInstance().getRunningTask(UploadEntity.class);
}
/**
* 删除所有任务
*

@ -5,6 +5,8 @@
- 添加ftp服务器标志 https://github.com/AriaLyy/Aria/issues/580
- 重构loader模块,让loader模块的代码更加清晰,去除一些不必要的线程创建
- 修复ftp上传完成后,删除服务器端的文件,无法重新下载的问题
- 增加获取执行中的任务api,详情见:https://aria.laoyuyu.me/aria_doc/api/task_list.html
- 增加获取剩余时间的api,详情见:https://aria.laoyuyu.me/aria_doc/start/task_explain.html
+ v_3.8.1 (2019/12/22)
- 修复一个表创建失败的问题 https://github.com/AriaLyy/Aria/issues/570
- 修复一个非分块模式下导致下载失败的问题 https://github.com/AriaLyy/Aria/issues/571

@ -42,7 +42,7 @@ public final class FtpDLoaderUtil extends AbsNormalLoaderUtil {
public LoaderStructure BuildLoaderStructure() {
LoaderStructure structure = new LoaderStructure();
structure.addComponent(new FtpDRecordHandler(getTaskWrapper()))
structure.addComponent(new FtpDRecordHandler((DTaskWrapper) getTaskWrapper()))
.addComponent(new NormalThreadStateManager(getListener()))
.addComponent(new FtpDFileInfoTask((DTaskWrapper) getTaskWrapper()))
.addComponent(new FtpDTTBuilder(getTaskWrapper()));

@ -60,10 +60,7 @@ final class FtpUFileInfoTask extends AbsFtpInfoTask<UploadEntity, UTaskWrapper>
return;
}
//为了防止编码错乱,需要使用原始字符串
if (files.length == 0) {
handleFile(getRemotePath(), null);
}
handleFile(getRemotePath(), files.length == 0 ? null : files[0]);
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
//服务器上没有该文件路径,表示该任务为新的上传任务

@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
*/
final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
private String dir, remotePath;
private boolean storeFail = false;
private boolean storeSuccess = false;
private ScheduledThreadPoolExecutor timer;
private FTPClient client = null;
private boolean isTimeOut = true;
@ -77,7 +77,11 @@ final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
file.seek(getThreadRecord().startLocation);
}
boolean complete = upload(file);
if (!complete || getThreadTask().isBreak()) {
if (getThreadTask().isBreak()) {
return;
}
if (!complete){
fail(new AriaIOException(TAG, "ftp文件上传失败"), false);
return;
}
ALog.i(TAG,
@ -158,12 +162,11 @@ final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
private boolean upload(final BufferedRandomAccessFile bis)
throws IOException {
fa = new FtpFISAdapter(bis);
storeFail = false;
storeSuccess = false;
startTimer();
final long fileSize = getThreadConfig().tempFile.length();
try {
ALog.d(TAG, String.format("remotePath: %s", remotePath));
client.storeFile(remotePath, fa, new OnFtpInputStreamListener() {
storeSuccess = client.storeFile(remotePath, fa, new OnFtpInputStreamListener() {
boolean isStoped = false;
@Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred,
@ -181,7 +184,7 @@ final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
progress(bytesTransferred);
} catch (IOException e) {
e.printStackTrace();
storeFail = true;
storeSuccess = false;
}
}
});
@ -192,15 +195,11 @@ final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
if (e.getMessage().contains("AriaIOException caught while copying")) {
e.printStackTrace();
} else {
fail(new AriaIOException(TAG, msg, e), !storeFail);
fail(new AriaIOException(TAG, msg, e), !storeSuccess);
}
return false;
} finally {
fa.close();
closeClient(client);
}
if (storeFail) {
return false;
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
@ -212,6 +211,6 @@ final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
closeClient(client);
return false;
}
return true;
return storeSuccess;
}
}

@ -0,0 +1,99 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.m3u8.live;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.m3u8.M3U8TaskOption;
import com.arialyy.aria.util.RecordUtil;
import java.util.ArrayList;
/**
* 直播m3u8文件处理器
*/
final class LiveRecordHandler extends RecordHandler {
private M3U8TaskOption mOption;
LiveRecordHandler(AbsTaskWrapper wrapper) {
super(wrapper);
}
public void setOption(M3U8TaskOption option) {
mOption = option;
}
@Override public void onPre() {
super.onPre();
RecordUtil.delTaskRecord(getEntity().getFilePath(), IRecordHandler.TYPE_DOWNLOAD);
}
/**
* @deprecated 直播文件不需要处理任务记录
*/
@Deprecated
@Override public void handlerTaskRecord(TaskRecord record) {
if (record.threadRecords == null) {
record.threadRecords = new ArrayList<>();
}
}
/**
* @deprecated 交由{@link #createThreadRecord(TaskRecord, String, int)} 处理
*/
@Override
@Deprecated
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
return null;
}
/**
* 创建线程记录
*
* @param taskRecord 任务记录
* @param tsUrl ts下载地址
* @param threadId 线程id
*/
ThreadRecord createThreadRecord(TaskRecord taskRecord, String tsUrl, int threadId) {
ThreadRecord tr = new ThreadRecord();
tr.taskKey = taskRecord.filePath;
tr.isComplete = false;
tr.tsUrl = tsUrl;
tr.threadType = getEntity().getTaskType();
tr.threadId = threadId;
tr.startLocation = 0;
taskRecord.threadRecords.add(tr);
return tr;
}
@Override public TaskRecord createTaskRecord(int threadNum) {
TaskRecord record = new TaskRecord();
record.fileName = getEntity().getFileName();
record.filePath = getEntity().getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = true;
record.taskType = getEntity().getTaskType();
record.bandWidth = mOption.getBandWidth();
return record;
}
@Override public int initTaskThreadNum() {
return 1;
}
}

@ -15,6 +15,7 @@
*/
package com.arialyy.aria.m3u8.live;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@ -80,7 +81,11 @@ final class LiveStateManager implements IThreadStateManager {
msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex);
break;
case STATE_RUNNING:
mProgress += (long) msg.obj;
Bundle b = msg.getData();
if (b != null) {
long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0);
mProgress += len;
}
break;
case STATE_FAIL:
mLoader.notifyLock(false, peerIndex);

@ -68,11 +68,9 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
private Condition mCondition = LOCK.newCondition();
private LinkedBlockingQueue<ExtInfo> mPeerQueue = new LinkedBlockingQueue<>();
private ExtInfo mCurExtInfo;
private LiveStateManager mManager;
private M3U8InfoTask mInfoTask;
private ScheduledThreadPoolExecutor mTimer;
private List<String> mPeerUrls = new ArrayList<>();
private Looper mLooper;
M3U8LiveLoader(DTaskWrapper wrapper, M3U8Listener listener) {
super(wrapper, listener);
@ -94,8 +92,22 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
if (isBreak()) {
return;
}
mLooper = looper;
// 处理记录
getRecordHandler().setOption(mM3U8Option);
mRecord = getRecordHandler().getRecord(0);
// 初始化状态管理器
getStateManager().setLooper(mRecord, looper);
getStateManager().setLoader(this);
mStateHandler = new Handler(looper, getStateManager().getHandlerCallback());
// 循环获取直播文件列表
startLoaderLiveInfo();
// 启动定时器
startTimer();
new Thread(new Runnable() {
@Override public void run() {
String cacheDir = getCacheDir();
@ -127,6 +139,14 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
}).start();
}
@Override protected LiveStateManager getStateManager() {
return (LiveStateManager) super.getStateManager();
}
private LiveRecordHandler getRecordHandler() {
return (LiveRecordHandler) mRecordHandler;
}
@Override public long getFileSize() {
return mTempFile.length();
}
@ -165,20 +185,14 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
* 配置config
*/
private ThreadTask createThreadTask(String cacheDir, int indexId, String tsUrl) {
ThreadRecord record = new ThreadRecord();
record.taskKey = mRecord.filePath;
record.isComplete = false;
record.tsUrl = tsUrl;
record.threadType = getEntity().getTaskType();
record.threadId = indexId;
mRecord.threadRecords.add(record);
ThreadRecord tr = getRecordHandler().createThreadRecord(mRecord, tsUrl, indexId);
SubThreadConfig config = new SubThreadConfig();
config.url = tsUrl;
config.tempFile = new File(getTsFilePath(cacheDir, indexId));
config.isBlock = mRecord.isBlock;
config.taskWrapper = mTaskWrapper;
config.record = record;
config.record = tr;
config.stateHandler = mStateHandler;
config.peerIndex = indexId;
config.threadType = SubThreadConfig.getThreadType(ITaskWrapper.M3U8_LIVE);
@ -219,15 +233,10 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
if (isSuccess) {
// 合并成功,删除缓存文件
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
FileUtil.deleteFile(pp);
}
File cDir = new File(cacheDir);
if (cDir.exists()) {
cDir.delete();
}
FileUtil.deleteDir(cDir);
return true;
} else {
ALog.e(TAG, "合并失败");
@ -323,10 +332,7 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
* 需要在{@link #addComponent(IRecordHandler)} 后调用
*/
@Override public void addComponent(IThreadStateManager threadState) {
mManager = (LiveStateManager) threadState;
mManager.setLooper(mRecordHandler.getRecord(0), mLooper);
mManager.setLoader(this);
mStateHandler = new Handler(mLooper, mManager.getHandlerCallback());
mStateManager = threadState;
}
/**
@ -344,7 +350,7 @@ final class M3U8LiveLoader extends BaseM3U8Loader {
if (mInfoTask == null) {
throw new NullPointerException(("文件信息组件为空"));
}
if (mManager == null) {
if (mStateManager == null) {
throw new NullPointerException("任务状态管理组件为空");
}
}

@ -23,9 +23,7 @@ import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.http.HttpTaskOption;
import com.arialyy.aria.m3u8.M3U8InfoTask;
import com.arialyy.aria.m3u8.M3U8Listener;
import com.arialyy.aria.m3u8.M3U8RecordHandler;
import com.arialyy.aria.m3u8.M3U8TaskOption;
import com.arialyy.aria.util.CommonUtil;
/**
* M3U8直播文件下载工具对于直播来说需要定时更新m3u8文件
@ -37,7 +35,6 @@ import com.arialyy.aria.util.CommonUtil;
* 5不处理直播切片下载失败的状态
*/
public class M3U8LiveUtil extends AbsNormalLoaderUtil {
private final String TAG = CommonUtil.getClassName(getClass());
public M3U8LiveUtil(AbsTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
@ -57,7 +54,7 @@ public class M3U8LiveUtil extends AbsNormalLoaderUtil {
@Override public LoaderStructure BuildLoaderStructure() {
LoaderStructure structure = new LoaderStructure();
structure.addComponent(new M3U8RecordHandler(getTaskWrapper()))
structure.addComponent(new LiveRecordHandler(getTaskWrapper()))
.addComponent(new M3U8InfoTask(getTaskWrapper()))
.addComponent(new LiveStateManager(getTaskWrapper(), getListener()));
structure.accept(getLoader());

@ -73,7 +73,6 @@ final class M3U8VodLoader extends BaseM3U8Loader {
private Condition mJumpCondition = JUMP_LOCK.newCondition();
private SparseArray<ThreadRecord> mBeforePeer = new SparseArray<>();
private SparseArray<ThreadRecord> mAfterPeer = new SparseArray<>();
private VodStateManager mManager;
private PeerIndexEvent mCurrentEvent;
private String mCacheDir;
private int aIndex = 0, bIndex = 0;
@ -136,11 +135,6 @@ final class M3U8VodLoader extends BaseM3U8Loader {
}
}
@Override protected void onPostPre() {
super.onPostPre();
initData();
}
@Override public boolean isBreak() {
return super.isBreak() || isDestroy;
}
@ -154,6 +148,22 @@ final class M3U8VodLoader extends BaseM3U8Loader {
}
private void startThreadTask() {
// 处理任务记录
((VodRecordHandler) mRecordHandler).setOption(mM3U8Option);
mRecord = mRecordHandler.getRecord(0);
// 处理任务管理器
mStateHandler = new Handler(mLooper, getStateManager().getHandlerCallback());
getStateManager().setVodLoader(this);
getStateManager().setLooper(mRecord, mLooper);
// 初始化ts数据
initData();
// 启动定时器
startTimer();
// 启动线程开始下载ts切片
Thread th = new Thread(new Runnable() {
@Override public void run() {
while (!isBreak()) {
@ -258,7 +268,7 @@ final class M3U8VodLoader extends BaseM3U8Loader {
mCompleteNum++;
}
}
mManager.updateStateCount();
getStateManager().updateStateCount();
if (mCompleteNum <= 0) {
getListener().onStart(0);
} else {
@ -416,7 +426,7 @@ final class M3U8VodLoader extends BaseM3U8Loader {
String.format("beforeSize = %s, afterSize = %s, mCompleteNum = %s", mBeforePeer.size(),
mAfterPeer.size(), mCompleteNum));
ALog.i(TAG, String.format("完成处理数据的操作,将优先下载【%s】之后的切片", mCurrentEvent.peerIndex));
mManager.updateStateCount();
getStateManager().updateStateCount();
try {
JUMP_LOCK.lock();
@ -487,7 +497,6 @@ final class M3U8VodLoader extends BaseM3U8Loader {
@Override public void addComponent(IRecordHandler recordHandler) {
mRecordHandler = recordHandler;
mRecord = mRecordHandler.getRecord(0);
}
@Override public void addComponent(IInfoTask infoTask) {
@ -543,10 +552,7 @@ final class M3U8VodLoader extends BaseM3U8Loader {
* 需要在 {@link #addComponent(IRecordHandler)}后调用
*/
@Override public void addComponent(IThreadStateManager threadState) {
mManager = (VodStateManager) threadState;
mStateHandler = new Handler(mLooper, mManager.getHandlerCallback());
mManager.setVodLoader(this);
mManager.setLooper(mRecord, mLooper);
mStateManager = threadState;
}
/**
@ -557,6 +563,11 @@ final class M3U8VodLoader extends BaseM3U8Loader {
}
@Override
protected VodStateManager getStateManager() {
return (VodStateManager) mStateManager;
}
@Override protected void checkComponent() {
if (mRecordHandler == null) {
throw new NullPointerException("任务记录组件为空");
@ -564,7 +575,7 @@ final class M3U8VodLoader extends BaseM3U8Loader {
if (mInfoTask == null) {
throw new NullPointerException(("文件信息组件为空"));
}
if (mManager == null) {
if (getStateManager() == null) {
throw new NullPointerException("任务状态管理组件为空");
}
}

@ -24,7 +24,6 @@ import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.http.HttpTaskOption;
import com.arialyy.aria.m3u8.M3U8InfoTask;
import com.arialyy.aria.m3u8.M3U8Listener;
import com.arialyy.aria.m3u8.M3U8RecordHandler;
import com.arialyy.aria.m3u8.M3U8TaskOption;
/**
@ -54,7 +53,7 @@ public final class M3U8VodUtil extends AbsNormalLoaderUtil {
@Override public LoaderStructure BuildLoaderStructure() {
LoaderStructure structure = new LoaderStructure();
structure.addComponent(new M3U8RecordHandler(getTaskWrapper()))
structure.addComponent(new VodRecordHandler(getTaskWrapper()))
.addComponent(new M3U8InfoTask(getTaskWrapper()))
.addComponent(new VodStateManager(getTaskWrapper(), (M3U8Listener) getListener()));
structure.accept(getLoader());

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.m3u8;
package com.arialyy.aria.m3u8.vod;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
@ -21,11 +21,12 @@ import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.M3U8Entity;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.m3u8.BaseM3U8Loader;
import com.arialyy.aria.m3u8.M3U8InfoTask;
import com.arialyy.aria.m3u8.M3U8TaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
import java.util.ArrayList;
@ -33,19 +34,15 @@ import java.util.ArrayList;
* @Author lyy
* @Date 2019-09-24
*/
public final class M3U8RecordHandler extends RecordHandler {
final class VodRecordHandler extends RecordHandler {
private M3U8TaskOption mOption;
public M3U8RecordHandler(DTaskWrapper wrapper) {
VodRecordHandler(DTaskWrapper wrapper) {
super(wrapper);
mOption = (M3U8TaskOption) wrapper.getM3u8Option();
}
@Override public void onPre() {
super.onPre();
if (getWrapper().getRequestType() == ITaskWrapper.M3U8_LIVE) {
RecordUtil.delTaskRecord(getEntity().getFilePath(), IRecordHandler.TYPE_DOWNLOAD);
}
public void setOption(M3U8TaskOption option) {
mOption = option;
}
/**

@ -52,7 +52,6 @@ public final class VodStateManager implements IThreadStateManager {
private int cancelNum = 0; // 已经取消的线程的数
private int stopNum = 0; // 已经停止的线程数
private int failNum = 0; // 失败的线程数
private long percent; //当前总进度,百分比进度
private long progress;
private TaskRecord taskRecord; // 任务记录
private Looper looper;
@ -164,7 +163,11 @@ public final class VodStateManager implements IThreadStateManager {
}
break;
case STATE_RUNNING:
progress += (long) msg.obj;
Bundle b = msg.getData();
if (b != null) {
long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0);
progress += len;
}
break;
}
return true;
@ -226,7 +229,6 @@ public final class VodStateManager implements IThreadStateManager {
int percent = completeNum * 100 / taskRecord.threadRecords.size();
getEntity().setPercent(percent);
getEntity().update();
this.percent = percent;
}
@Override public boolean isFail() {

@ -41,6 +41,11 @@ public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable,
*/
@Ignore private int failNum = 0;
/**
* 剩余时间单位s
*/
@Ignore private int timeLeft = Integer.MAX_VALUE;
/**
* 扩展字段
*/
@ -81,6 +86,18 @@ public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable,
*/
private long stopTime = 0;
/**
* 获取剩余时间单位s
* 如果是m3u8任务无法获取剩余时间m2u8任务如果需要获取剩余时间请设置文件长度{@link #setFileSize(long)}
*/
public int getTimeLeft() {
return timeLeft;
}
public void setTimeLeft(int timeLeft) {
this.timeLeft = timeLeft;
}
public boolean isComplete() {
return isComplete;
}

@ -156,10 +156,18 @@ public class RecordHelper {
: mTaskRecord.filePath);
ThreadRecord tr = mTaskRecord.threadRecords.get(0);
if (!file.exists()) {
// 目标文件
File targetFile = new File(mTaskRecord.filePath);
// 处理组合任务其中一个子任务完成的情况
if (tr.isComplete && targetFile.exists() && targetFile.length() == mWrapper.getEntity()
.getFileSize()) {
tr.isComplete = true;
} else {
ALog.w(TAG, String.format("文件【%s】不存在,任务将重新开始", file.getPath()));
tr.startLocation = 0;
tr.isComplete = false;
tr.endLocation = mWrapper.getEntity().getFileSize();
}
} else if (file.length() > mWrapper.getEntity().getFileSize()) {
ALog.i(TAG, String.format("文件【%s】错误,任务重新开始", file.getPath()));
FileUtil.deleteFile(file);

@ -46,7 +46,7 @@ public class SubThreadConfig {
public int peerIndex;
// 线程任务类型
public int threadType = TYPE_HTTP;
// 更新间隔,单位:毫秒
// 进度更新间隔,单位:毫秒
public long updateInterval = 1000;
/**

@ -272,8 +272,8 @@ public abstract class AbsGroupLoader implements ILoaderVisitor, ILoader {
mListener.onComplete();
return;
}
startTimer();
handlerTask(looper);
startTimer();
Looper.loop();
}

@ -140,6 +140,13 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
mEntity.setPercent((int) (mEntity.getFileSize() <= 0 ? 0
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
}
if (mEntity.getFileSize() != 0) {
if (speed == 0) {
mEntity.setTimeLeft(Integer.MAX_VALUE);
} else {
mEntity.setTimeLeft((int) ((mEntity.getFileSize() - mEntity.getCurrentProgress()) / speed));
}
}
}
/**

@ -22,7 +22,6 @@ import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.group.GroupSendParams;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.exception.BaseException;
@ -113,18 +112,25 @@ public class DownloadGroupListener
}
private void handleSubSpeed(DownloadEntity subEntity, long currentProgress) {
if (currentProgress == 0){
if (currentProgress == 0) {
subEntity.setSpeed(0);
subEntity.setConvertSpeed("0kb/s");
return;
}
long range = currentProgress - subEntity.getCurrentProgress() ;
subEntity.setSpeed(range);
long speed = currentProgress - subEntity.getCurrentProgress();
subEntity.setSpeed(speed);
subEntity.setConvertSpeed(
range <= 0 ? "" : String.format("%s/s", CommonUtil.formatFileSize(range)));
speed <= 0 ? "" : String.format("%s/s", CommonUtil.formatFileSize(speed)));
subEntity.setPercent((int) (subEntity.getFileSize() <= 0 ? 0
: subEntity.getCurrentProgress() * 100 / subEntity.getFileSize()));
subEntity.setCurrentProgress(currentProgress);
if (speed == 0) {
subEntity.setTimeLeft(Integer.MAX_VALUE);
} else {
subEntity.setTimeLeft(
(int) ((subEntity.getFileSize() - subEntity.getCurrentProgress()) / speed));
}
}
/**
@ -150,8 +156,6 @@ public class DownloadGroupListener
subEntity.setPercent(100);
subEntity.setConvertSpeed("0kb/s");
subEntity.setSpeed(0);
ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey()));
RecordUtil.delTaskRecord(subEntity.getFilePath(), IRecordHandler.TYPE_DOWNLOAD, false, false);
}
subEntity.update();
}

@ -41,7 +41,6 @@ import java.util.concurrent.TimeUnit;
*/
public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
protected final String TAG = CommonUtil.getClassName(getClass());
;
private IEventListener mListener;
protected AbsTaskWrapper mTaskWrapper;
protected File mTempFile;
@ -127,7 +126,6 @@ public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
isRuning = true;
resetState();
onPostPre();
startTimer();
handleTask(looper);
Looper.loop();
}
@ -149,7 +147,7 @@ public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
/**
* 启动进度获取定时器
*/
private synchronized void startTimer() {
protected synchronized void startTimer() {
if (isBreak()) {
return;
}
@ -157,7 +155,11 @@ public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
if (mStateManager.isComplete()
// 线程池中是不抛异常的,没有日志,很难定位问题,需要手动try-catch
try {
if (mStateManager == null) {
ALog.e(TAG, "stateManager is null");
} else if (mStateManager.isComplete()
|| mStateManager.isFail()
|| !isRunning()
|| isBreak()) {
@ -171,11 +173,14 @@ public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
} else if (mStateManager.getCurrentProgress() >= 0) {
mListener.onProgress(mStateManager.getCurrentProgress());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, delayTimer(), mUpdateInterval, TimeUnit.MILLISECONDS);
}
public synchronized void closeTimer() {
private synchronized void closeTimer() {
if (mTimer != null && !mTimer.isShutdown()) {
mTimer.shutdown();
}

@ -125,6 +125,9 @@ public class NormalLoader extends AbsNormalLoader {
for (IThreadTask threadTask : getTaskList()) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask);
}
// 启动定时器
startTimer();
}
@Override public long getCurrentProgress() {

@ -18,6 +18,7 @@ package com.arialyy.aria.core.task;
import android.content.Context;
import android.os.Handler;
import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IUtil;
import com.arialyy.aria.core.inf.TaskSchedulerType;
@ -71,6 +72,25 @@ public abstract class AbsTask<TASK_WRAPPER extends AbsTaskWrapper>
return mUtil;
}
/**
* 获取剩余时间单位s
* 如果是m3u8任务无法获取剩余时间m2u8任务如果需要获取剩余时间请设置文件长度{@link AbsEntity#setFileSize(long)}
*/
public int getTimeLeft() {
return getTaskWrapper().getEntity().getTimeLeft();
}
/**
* 转换时间
* 时间1 小时显示分秒显示样式 00:20
* 时间 1 小时显示时分秒显示样式 01:11:12
* 时间 1 显示天时分显示样式 1d 01:11
* 时间 7 显示样式
*/
public String getConvertTimeLeft() {
return CommonUtil.formatTime(getTaskWrapper().getEntity().getTimeLeft());
}
/**
* 添加扩展数据 读取扩展数据{@link #getExpand(String)}
*/
@ -194,12 +214,16 @@ public abstract class AbsTask<TASK_WRAPPER extends AbsTaskWrapper>
@Override public void start(int type) {
mSchedulerType = type;
mUtil = createUtil();
if (mUtil == null){
ALog.e(TAG, "任务工具创建失败");
return;
}
if (type == TaskSchedulerType.TYPE_START_AND_RESET_STATE) {
if (getUtil().isRunning()) {
ALog.e(TAG, String.format("任务【%s】重启失败", getTaskName()));
return;
}
mUtil = createUtil();
mUtil.start();
ALog.d(TAG, String.format("任务【%s】重启成功", getTaskName()));
return;
@ -216,6 +240,11 @@ public abstract class AbsTask<TASK_WRAPPER extends AbsTaskWrapper>
}
@Override public void stop(int type) {
mUtil = createUtil();
if (mUtil == null){
ALog.e(TAG, "任务工具创建失败");
return;
}
isStop = true;
mSchedulerType = type;
getUtil().stop();
@ -226,6 +255,11 @@ public abstract class AbsTask<TASK_WRAPPER extends AbsTaskWrapper>
}
@Override public void cancel(int type) {
mUtil = createUtil();
if (mUtil == null){
ALog.e(TAG, "任务工具创建失败");
return;
}
isCancel = true;
mSchedulerType = type;
getUtil().cancel();

@ -67,8 +67,8 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
private long mRangeProgress, mLastRangeProgress;
private IThreadTaskAdapter mAdapter;
private ThreadRecord mRecord;
private String mThreadNmae;
private long updateInterval = 1000; // 更新间隔
private String mThreadName;
private long updateInterval; // 更新间隔
private Thread mConfigThread = new Thread(new Runnable() {
@Override public void run() {
@ -243,8 +243,8 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
}
@Override public String getThreadName() {
return mThreadNmae == null ? CommonUtil.getThreadName(getConfig().url, getThreadId())
: mThreadNmae;
return mThreadName == null ? (mThreadName =
CommonUtil.getThreadName(getConfig().url, getThreadId())) : mThreadName;
}
/**
@ -287,7 +287,7 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
bundle.putLong(IThreadStateManager.DATA_THREAD_LOCATION, mRangeProgress);
msg.what = state;
int reqType = getConfig().threadType;
if (reqType == ITaskWrapper.M3U8_VOD || reqType == ITaskWrapper.M3U8_LIVE) {
if (reqType == SubThreadConfig.TYPE_M3U8_PEER) {
sendM3U8Info(state, msg);
}
@ -299,10 +299,10 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
}
private void sendM3U8Info(int state, Message msg) {
Bundle bundle = msg.getData();
if (state != IThreadStateManager.STATE_UPDATE_PROGRESS) {
msg.obj = this;
}
Bundle bundle = msg.getData();
if ((state == IThreadStateManager.STATE_COMPLETE || state == IThreadStateManager.STATE_FAIL)) {
bundle.putString(ISchedulers.DATA_M3U8_URL, getConfig().url);
bundle.putString(ISchedulers.DATA_M3U8_PEER_PATH, getConfig().tempFile.getPath());
@ -313,6 +313,8 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
@Override public synchronized void updateCompleteState() {
ALog.i(TAG, String.format("任务【%s】线程__%s__完成", getTaskWrapper().getKey(), mRecord.threadId));
writeConfig(true, mRecord.endLocation);
// 进度发送不是实时的,发送完成任务前,需要更新一次进度
sendRunningState();
updateState(IThreadStateManager.STATE_COMPLETE, null);
}
@ -334,17 +336,7 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
}
// 不需要太频繁发送,以减少消息队列的压力
if (System.currentTimeMillis() - mLastSendProgressTime > updateInterval) {
Message msg = mStateHandler.obtainMessage();
Bundle b = msg.getData();
if (b == null) {
b = new Bundle();
msg.setData(b);
}
b.putString(IThreadStateManager.DATA_THREAD_NAME, getThreadName());
b.putLong(IThreadStateManager.DATA_ADD_LEN, mRangeProgress - mLastRangeProgress);
msg.what = IThreadStateManager.STATE_RUNNING;
msg.obj = mRangeProgress;
msg.sendToTarget();
sendRunningState();
mLastRangeProgress = mRangeProgress;
mLastSendProgressTime = System.currentTimeMillis();
}
@ -358,6 +350,23 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
}
}
/**
* 发送执行中的数据
*/
private void sendRunningState() {
Message msg = mStateHandler.obtainMessage();
Bundle b = msg.getData();
if (b == null) {
b = new Bundle();
msg.setData(b);
}
b.putString(IThreadStateManager.DATA_THREAD_NAME, getThreadName());
b.putLong(IThreadStateManager.DATA_ADD_LEN, mRangeProgress - mLastRangeProgress);
msg.what = IThreadStateManager.STATE_RUNNING;
msg.obj = mRangeProgress;
msg.sendToTarget();
}
@Override
public long getThreadProgress() {
return mRangeProgress;

@ -48,6 +48,7 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
@ -794,6 +795,39 @@ public class CommonUtil {
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "tb";
}
/**
* 转换时间
* 时间1 小时显示分秒显示样式 00:20
* 时间 1 小时显示时分秒显示样式 01:11:12
* 时间 1 显示天时分显示样式 1d 01:11
* 时间 7 显示样式
*
* @param seconds 单位为s的时间
*/
public static String formatTime(int seconds) {
String standardTime;
if (seconds <= 0) {
standardTime = "00:00";
} else if (seconds < 60) {
standardTime = String.format(Locale.getDefault(), "00:%02d", seconds % 60);
} else if (seconds < 3600) {
standardTime = String.format(Locale.getDefault(), "%02d:%02d", seconds / 60, seconds % 60);
} else if (seconds < 86400) {
standardTime =
String.format(Locale.getDefault(), "%02d:%02d:%02d", seconds / 3600, seconds % 3600 / 60,
seconds % 60);
} else if (seconds < 604800) {
standardTime =
String.format(Locale.getDefault(), "%dd %02d:%02d", seconds / 86400,
seconds % 86400 / 3600,
seconds % 3600);
} else {
standardTime = "∞";
}
return standardTime;
}
/**
* 通过文件名获取下载配置文件路径
*

@ -128,6 +128,7 @@ public class ComponentUtil {
break;
}
if (className == null) {
ALog.e(TAG, "不识别的类名:" + className);
return null;
}
T util = null;

@ -204,6 +204,7 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
getBinding().setProgress(task.getPercent());
}
getBinding().setSpeed(task.getConvertSpeed());
getBinding().setTimeLeft(task.getConvertTimeLeft());
}
}

@ -155,10 +155,11 @@ public class DownloadGroupActivity extends BaseActivity<ActivityDownloadGroupBin
+ task.getPercent()
+ ", speed = "
+ task.getConvertSpeed()
+ "current_p = "
+ ", current_p = "
+ task.getCurrentProgress());
getBinding().setProgress(task.getPercent());
getBinding().setSpeed(task.getConvertSpeed());
getBinding().setTimeLeft(task.getConvertTimeLeft());
//Log.d(TAG, "sub_len = " + task.getEntity().getSubEntities().size());
mChildList.updateChildProgress(task.getEntity().getSubEntities());
}

@ -187,7 +187,7 @@ public class M3U8LiveDLoadActivity extends BaseActivity<ActivityM3u8LiveBinding>
@Download.onTaskFail
void taskFail(DownloadTask task, Exception e) {
if (task.getKey().equals(mUrl)) {
if (task != null && task.getKey().equals(mUrl)) {
Toast.makeText(M3U8LiveDLoadActivity.this, getString(R.string.download_fail),
Toast.LENGTH_SHORT)
.show();
@ -216,14 +216,14 @@ public class M3U8LiveDLoadActivity extends BaseActivity<ActivityM3u8LiveBinding>
switch (view.getId()) {
case R.id.start:
if (Aria.download(this).load(mTaskId).isRunning()) {
Aria.download(this).load(mEntity.getId()).stop();
Aria.download(this).load(mTaskId).stop();
} else {
startD();
}
break;
case R.id.cancel:
if (mTaskId != -1){
Aria.download(this).load(mEntity.getId()).cancel(true);
Aria.download(this).load(mTaskId).cancel(true);
mTaskId = -1;
}
break;

@ -210,7 +210,7 @@ public class M3U8VodDLoadActivity extends BaseActivity<ActivityM3u8VodBinding> {
@Download.onTaskRunning
protected void running(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
//ALog.d(TAG, "isRunning");
ALog.d(TAG, "m3u8 void running, p = " + task.getPercent() + ", speed = " + task.getConvertSpeed());
getBinding().setProgress(task.getPercent());
getBinding().setSpeed(task.getConvertSpeed());
}

@ -19,6 +19,10 @@
name="stateStr"
type="String"
/>
<variable
name="timeLeft"
type="String"
/>
</data>
@ -40,6 +44,7 @@
bind:progress="@{progress}"
bind:speed="@{speed}"
bind:stateStr="@{stateStr}"
bind:timeLeft="@{timeLeft}"
/>
<com.arialyy.simple.widget.SubStateLinearLayout

@ -105,6 +105,16 @@
style="?buttonBarButtonStyle"
/>
<Button
android:id="@+id/cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onClick"
android:text="@string/cancel"
style="?buttonBarButtonStyle"
/>
</LinearLayout>

@ -33,6 +33,10 @@
name="viewModel"
type="com.arialyy.simple.core.download.SingleTaskActivity"
/>
<variable
name="timeLeft"
type="String"
/>
</data>
<LinearLayout
@ -74,6 +78,7 @@
bind:progress="@{progress}"
bind:speed="@{speed}"
bind:stateStr="@{stateStr}"
bind:timeLeft="@{timeLeft}"
/>
</LinearLayout>

@ -13,6 +13,10 @@
name="speed"
type="String"
/>
<variable
name="timeLeft"
type="String"
/>
<variable
name="progress"
type="int"
@ -64,6 +68,7 @@
android:layout_below="@+id/progressBar"
android:orientation="horizontal"
>
<TextView
android:id="@+id/speed"
android:layout_width="wrap_content"
@ -74,6 +79,16 @@
android:textColor="@color/black"
/>
<TextView
android:id="@+id/time_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:text="@{timeLeft}"
android:textColor="@color/black"
/>
<Button
android:id="@+id/start"
android:layout_width="0dp"

Loading…
Cancel
Save