源代码备份

This commit is contained in:
TC999
2024-08-20 16:54:35 +08:00
parent c4db18da39
commit e2a5f92e23
791 changed files with 90314 additions and 2 deletions

1
PublicComponent/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,13 @@
apply plugin: 'com.novoda.bintray-release'
publish {
// artifactId = 'aria-compiler'
// uploadName = 'AriaCompiler'
artifactId = 'publicComponent'
uploadName = 'PublicComponent'
userOrg = rootProject.ext.userOrg
groupId = rootProject.ext.groupId
publishVersion = rootProject.ext.publishVersion
desc = rootProject.ext.desc
website = rootProject.ext.website
licences = rootProject.ext.licences
}

View File

@ -0,0 +1,42 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
debug{
debuggable true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
}
//apply from: 'bintray-release.gradle'
ext{
PUBLISH_ARTIFACT_ID = 'public'
}
apply from: '../gradle/mavenCentral-release.gradle'

View File

21
PublicComponent/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arialyy.aria.publiccomponent" >
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View File

@ -0,0 +1,240 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.core;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.config.AppConfig;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.config.DGroupConfig;
import com.arialyy.aria.core.config.DownloadConfig;
import com.arialyy.aria.core.config.UploadConfig;
import com.arialyy.aria.core.config.XMLReader;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
public class AriaConfig {
private static final String TAG = "AriaConfig";
public static final String DOWNLOAD_TEMP_DIR = "/Aria/temp/download/";
public static final String UPLOAD_TEMP_DIR = "/Aria/temp/upload/";
public static final String IGNORE_CLASS_KLASS = "shadow$_klass_";
public static final String IGNORE_CLASS_MONITOR = "shadow$_monitor_";
private static volatile AriaConfig Instance;
private static Context APP;
private DownloadConfig mDConfig;
private UploadConfig mUConfig;
private AppConfig mAConfig;
private DGroupConfig mDGConfig;
/**
* 是否已经联网true 已经联网
*/
private static boolean isConnectedNet = true;
private Handler mAriaHandler;
private AriaConfig(Context context) {
APP = context.getApplicationContext();
}
public static AriaConfig init(Context context) {
if (Instance == null) {
synchronized (AriaConfig.class) {
if (Instance == null) {
Instance = new AriaConfig(context);
Instance.initData();
}
}
}
return Instance;
}
public static AriaConfig getInstance() {
if (Instance == null) {
ALog.e(TAG, "请使用init()初始化");
}
return Instance;
}
public Context getAPP() {
return APP;
}
private void initData() {
initConfig();
regNetCallBack(APP);
}
public DownloadConfig getDConfig() {
return mDConfig;
}
public UploadConfig getUConfig() {
return mUConfig;
}
public AppConfig getAConfig() {
return mAConfig;
}
public DGroupConfig getDGConfig() {
return mDGConfig;
}
public synchronized Handler getAriaHandler() {
if (mAriaHandler == null) {
mAriaHandler = new Handler(Looper.getMainLooper());
}
return mAriaHandler;
}
/**
* 注册网络监听,只有配置了检查网络{@link AppConfig#isNetCheck()}才会注册事件
*/
private void regNetCallBack(Context context) {
isConnectedNet = isNetworkAvailable();
if (!getAConfig().isNetCheck()) {
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return;
}
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() {
@Override public void onLost(Network network) {
super.onLost(network);
isConnectedNet = isNetworkAvailable();
ALog.d(TAG, "onLost, isConnectNet = " + isConnectedNet);
}
@Override public void onAvailable(Network network) {
super.onAvailable(network);
isConnectedNet = true;
ALog.d(TAG, "onAvailable, isConnectNet = true");
}
});
}
}
public boolean isNetworkAvailable() {
// 获取手机所有连接管理对象包括对wi-fi,net等连接的管理
ConnectivityManager connectivityManager =
(ConnectivityManager) getAPP().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager == null) {
return false;
} else {
// 获取NetworkInfo对象
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (NetworkInfo info : networkInfo) {
// 判断当前网络状态是否为连接状态
if (info.getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
public boolean isConnectedNet() {
return isConnectedNet;
}
/**
* 初始化配置文件
*/
private void initConfig() {
mDConfig = Configuration.getInstance().downloadCfg;
mUConfig = Configuration.getInstance().uploadCfg;
mAConfig = Configuration.getInstance().appCfg;
mDGConfig = Configuration.getInstance().dGroupCfg;
File xmlFile = new File(APP.getFilesDir().getPath() + Configuration.XML_FILE);
File tempDir = new File(APP.getFilesDir().getPath() + "/temp");
if (!xmlFile.exists()) {
loadConfig();
} else {
try {
String md5Code = CommonUtil.getFileMD5(xmlFile);
File file = new File(APP.getFilesDir().getPath() + "/temp.xml");
if (file.exists()) {
file.delete();
}
FileUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"),
file.getPath());
if (!CommonUtil.checkMD5(md5Code, file) || !Configuration.getInstance().configExists()) {
loadConfig();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (tempDir.exists()) {
File newDir = new File(APP.getFilesDir().getPath() + AriaConfig.DOWNLOAD_TEMP_DIR);
newDir.mkdirs();
tempDir.renameTo(newDir);
}
}
/**
* 加载配置文件
*/
private void loadConfig() {
try {
XMLReader helper = new XMLReader();
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(APP.getAssets().open("aria_config.xml"), helper);
FileUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"),
APP.getFilesDir().getPath() + Configuration.XML_FILE);
} catch (ParserConfigurationException | IOException | SAXException e) {
ALog.e(TAG, e.toString());
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.core;
import java.net.InetAddress;
/**
* Created by Aria.Lao on 2017/10/24.
* ftp url 信息链接实体
*/
public class FtpUrlEntity implements Cloneable {
/**
* 如ftp://127.0.0.1:21/download/AriaPrj.zip
* remotePath便是download/AriaPrj.zip
*/
public String remotePath;
public String account;
/**
* 是否是ftps
* {@code true}ftps协议的地址{@code false}不是ftps协议的地址
*/
public boolean isFtps = false;
/**
* 原始url
*/
public String url;
/**
* ftp协议ftp
*/
public String scheme;
/**
* 登录的用户名
*/
public String user;
/**
* 密码
*/
public String password;
/**
* 端口
*/
public String port;
/**
* 主机域名
*/
public String hostName;
/**
* 是否需要登录
*/
public boolean needLogin = false;
/**
* 有效的ip地址
*/
public InetAddress validAddr;
/**
* 连接协议
* {@link ProtocolType}
*/
public String protocol = ProtocolType.Default;
/**
* 安全模式 true 隐式false 显式
*/
public boolean isImplicit = true;
public IdEntity idEntity;
@Override public FtpUrlEntity clone() {
FtpUrlEntity entity = null;
try {
entity = (FtpUrlEntity) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return entity;
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.core;
/**
* 证书信息
*/
public class IdEntity {
/**
* 私钥证书路径
*/
public String prvKey;
/**
* 私钥证书密码
*/
public String prvPass;
/**
* 公钥证书路径
*/
public String pubKey;
/**
* knowhost文件路径
*/
public String knowHost;
/**
* ca 证书密码
*/
public String storePass;
/**
* ca证书路径
*/
public String storePath;
/**
* ca证书别名
*/
public String keyAlias;
}

View File

@ -0,0 +1,26 @@
/*
* 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.core;
public interface ProtocolType {
String Default = "TLS";
String SSL = "SSL";
String SSLv3 = "SSLv3";
String TLS = "TLS";
String TLSv1 = "TLSv1";
String TLSv1_1 = "TLSv1.1";
String TLSv1_2 = "TLSv1.2";
}

View File

@ -0,0 +1,132 @@
/*
* 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.core;
import com.arialyy.aria.core.common.BaseOption;
import com.arialyy.aria.core.inf.IEventHandler;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.core.processor.FtpInterceptHandler;
import com.arialyy.aria.core.processor.IBandWidthUrlConverter;
import com.arialyy.aria.core.processor.IFtpUploadInterceptor;
import com.arialyy.aria.core.processor.IHttpFileLenAdapter;
import com.arialyy.aria.core.processor.IHttpFileNameAdapter;
import com.arialyy.aria.core.processor.IKeyUrlConverter;
import com.arialyy.aria.core.processor.ILiveTsUrlConverter;
import com.arialyy.aria.core.processor.ITsMergeHandler;
import com.arialyy.aria.core.processor.IVodTsUrlConverter;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 任务配置参数
*
* @author lyy
* Date: 2019-09-10
*/
public class TaskOptionParams {
private static List<Class> PROCESSORES = new ArrayList<>();
/**
* 普通参数
*/
private Map<String, Object> params = new HashMap<>();
/**
* 事件处理对象
*/
private Map<String, IEventHandler> handler = new HashMap<>();
static {
PROCESSORES.add(FtpInterceptHandler.class);
PROCESSORES.add(IBandWidthUrlConverter.class);
PROCESSORES.add(IFtpUploadInterceptor.class);
PROCESSORES.add(IHttpFileLenAdapter.class);
PROCESSORES.add(IHttpFileNameAdapter.class);
PROCESSORES.add(ILiveTsUrlConverter.class);
PROCESSORES.add(ITsMergeHandler.class);
PROCESSORES.add(IVodTsUrlConverter.class);
PROCESSORES.add(IKeyUrlConverter.class);
}
/**
* 设置任务参数
*
* @param option 任务配置
*/
public void setParams(BaseOption option) {
List<Field> fields = CommonUtil.getAllFields(option.getClass());
for (Field field : fields) {
field.setAccessible(true);
try {
if (PROCESSORES.contains(field.getType())) {
Object eventHandler = field.get(option);
if (eventHandler != null) {
setObjs(field.getName(), (IEventHandler) eventHandler);
}
} else {
Object params = field.get(option);
if (params != null) {
setParams(field.getName(), params);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 设置普通参数
*
* @param key {@link IOptionConstant}
*/
public TaskOptionParams setParams(String key, Object value) {
params.put(key, value);
return this;
}
/**
* 设置对象参数
*/
public TaskOptionParams setObjs(String key, IEventHandler handler) {
this.handler.put(key, handler);
return this;
}
public Map<String, Object> getParams() {
return params;
}
public Object getParam(String key) {
return params.get(key);
}
public IEventHandler getHandler(String key) {
return handler.get(key);
}
public Map<String, IEventHandler> getHandler() {
return handler;
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.core;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.orm.annotation.Ignore;
import com.arialyy.aria.orm.annotation.NoNull;
import com.arialyy.aria.orm.annotation.Unique;
import java.util.List;
/**
* Created by laoyuyu on 2018/3/21.
* 任务上传或下载的任务记录
*/
public class TaskRecord extends DbEntity {
//public static final int TYPE_HTTP_FTP = 0;
//public static final int TYPE_M3U8_VOD = 1;
//public static final int TYPE_M3U8_LIVE = 2;
@Ignore
public List<ThreadRecord> threadRecords;
/**
* 任务线程数
*/
public int threadNum;
/**
* 任务文件路径
*/
public String filePath;
/**
* 文件长度
*/
public long fileLength;
/**
* 任务文件名
*/
@NoNull
public String fileName;
/**
* 是否是任务组的子任务记录
* {@code true}是
*/
public boolean isGroupRecord = false;
/**
* 下载任务组名
*/
public String dGroupHash;
/**
* 上传组任务名,暂时没有用
*/
@Ignore
@Deprecated
public String uGroupHash;
/**
* 是否是分块{@code true}是,{@code false} 不是
*/
public boolean isBlock = false;
/**
* 任务类型
* {@link ITaskWrapper}
*/
public int taskType = 0;
/**
* m3u8文件码率
*/
public long bandWidth = 0;
}

View File

@ -0,0 +1,68 @@
/*
* 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.core;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity;
/**
* Created by laoyuyu on 2018/5/8.
* 任务的线程记录
*/
public class ThreadRecord extends DbEntity {
/**
* 任务的文件路径,不是当前线程记录的的分块文件路径
*/
public String taskKey;
/**
* 开始位置
*/
public long startLocation;
/**
* 结束位置
*/
public long endLocation;
/**
* 线程是否完成
* {@code true}完成,{@code false}未完成
*/
public boolean isComplete = false;
/**
* 线程id
*/
public int threadId = 0;
/**
* 分块长度
*/
public long blockLen = 0;
/**
* 线程类型
* {@link ITaskWrapper}
*/
public int threadType = 0;
/**
* ts文件的下载地址
*/
public String tsUrl;
}

View File

@ -0,0 +1,267 @@
/*
* 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.core.common;
import android.os.Parcel;
import android.os.Parcelable;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.orm.annotation.Default;
import com.arialyy.aria.orm.annotation.Ignore;
import java.io.Serializable;
/**
* Created by AriaL on 2017/6/29.
*/
public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable, Serializable {
/**
* 速度
*/
@Ignore private long speed = 0;
/**
* 单位转换后的速度
*/
@Ignore private String convertSpeed;
/**
* 下载失败计数每次开始都重置为0
*/
@Ignore private int failNum = 0;
/**
* 剩余时间单位s
*/
@Ignore private int timeLeft = Integer.MAX_VALUE;
/**
* 扩展字段
*/
private String str;
/**
* 文件大小
*/
private long fileSize = 0;
/**
* 转换后的文件大小
*/
private String convertFileSize;
/**
* 任务状态{@link IEntity}
*/
@Default("3")
private int state = STATE_WAIT;
/**
* 当前下载进度
*/
private long currentProgress = 0;
/**
* 完成时间
*/
private long completeTime;
/**
* 进度百分比
*/
private int percent;
@Default("false")
private boolean isComplete = false;
/**
* 上一次停止时间
*/
private long stopTime = 0;
@Ignore
private int netCode = 200;
public int getNetCode() {
return netCode;
}
public void setNetCode(int netCode) {
this.netCode = netCode;
}
/**
* 获取剩余时间单位s
* 如果是m3u8任务无法获取剩余时间m2u8任务如果需要获取剩余时间请设置文件长度{@link #setFileSize(long)}
*/
public int getTimeLeft() {
return timeLeft;
}
public void setTimeLeft(int timeLeft) {
this.timeLeft = timeLeft;
}
public boolean isComplete() {
return isComplete;
}
public void setComplete(boolean complete) {
isComplete = complete;
}
public String getConvertFileSize() {
return convertFileSize;
}
public void setConvertFileSize(String convertFileSize) {
this.convertFileSize = convertFileSize;
}
public int getFailNum() {
return failNum;
}
public void setFailNum(int failNum) {
this.failNum = failNum;
}
public long getSpeed() {
return speed;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public String getConvertSpeed() {
return convertSpeed;
}
public void setConvertSpeed(String convertSpeed) {
this.convertSpeed = convertSpeed;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public long getCurrentProgress() {
return currentProgress;
}
public void setCurrentProgress(long currentProgress) {
this.currentProgress = currentProgress;
}
public long getCompleteTime() {
return completeTime;
}
public void setCompleteTime(long completeTime) {
this.completeTime = completeTime;
}
public int getPercent() {
return percent;
}
public void setPercent(int percent) {
this.percent = percent;
}
public long getStopTime() {
return stopTime;
}
public void setStopTime(long stopTime) {
this.stopTime = stopTime;
}
public long getId() {
return getRowID();
}
/**
* 实体唯一标识符
* 下载实体下载url
* 上传实体:文件路径
* 下载任务组:组名
* ftp文件夹下载下载url
*/
public abstract String getKey();
/**
* 实体驱动的下载任务类型
*
* @return {@link ITaskWrapper#D_FTP}、{@link ITaskWrapper#D_FTP_DIR}、{@link
* ITaskWrapper#U_HTTP}...
*/
public abstract int getTaskType();
public AbsEntity() {
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.rowID);
dest.writeLong(this.speed);
dest.writeString(this.convertSpeed);
dest.writeInt(this.failNum);
dest.writeString(this.str);
dest.writeLong(this.fileSize);
dest.writeString(this.convertFileSize);
dest.writeInt(this.state);
dest.writeLong(this.currentProgress);
dest.writeLong(this.completeTime);
dest.writeInt(this.percent);
dest.writeByte(this.isComplete ? (byte) 1 : (byte) 0);
dest.writeLong(this.stopTime);
}
protected AbsEntity(Parcel in) {
this.rowID = in.readLong();
this.speed = in.readLong();
this.convertSpeed = in.readString();
this.failNum = in.readInt();
this.str = in.readString();
this.fileSize = in.readLong();
this.convertFileSize = in.readString();
this.state = in.readInt();
this.currentProgress = in.readLong();
this.completeTime = in.readLong();
this.percent = in.readInt();
this.isComplete = in.readByte() != 0;
this.stopTime = in.readLong();
}
}

View File

@ -0,0 +1,103 @@
/*
* 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.core.common;
import android.os.Parcel;
import android.os.Parcelable;
import com.arialyy.aria.orm.annotation.Unique;
import java.util.ArrayList;
import java.util.List;
/**
* Created by AriaL on 2017/6/3.
*/
public abstract class AbsGroupEntity extends AbsEntity implements Parcelable {
/**
* 组合任务等hash为 为子任务地址相加的url的Md5
* ftpdir为ftpdir下载地址
*/
@Unique protected String groupHash;
/**
* 任务组别名
*/
private String alias;
/**
* 任务组下载文件的文件夹地址
*/
@Unique private String dirPath;
/**
* 子任务url地址
*/
private List<String> urls = new ArrayList<>();
public String getDirPath() {
return dirPath;
}
public void setDirPath(String dirPath) {
this.dirPath = dirPath;
}
public List<String> getUrls() {
return urls;
}
public void setUrls(List<String> urls) {
this.urls = urls;
}
/**
* 组合任务等hash为 为子任务地址相加的url的Md5
* ftpdir为ftpdir下载地址
*/
public String getGroupHash() {
return groupHash;
}
public String getAlias() {
return alias;
}
@Override public String getKey() {
return groupHash;
}
public void setAlias(String alias) {
this.alias = alias;
}
public AbsGroupEntity() {
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(this.groupHash);
dest.writeString(this.alias);
}
protected AbsGroupEntity(Parcel in) {
super(in);
this.groupHash = in.readString();
this.alias = in.readString();
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.core.common;
import android.os.Parcel;
import android.os.Parcelable;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.annotation.Default;
/**
* Created by AriaL on 2017/6/3.
*/
public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
/**
* 服务器地址
*/
private String url;
/**
* 文件名
*/
private String fileName;
/**
* 是否是任务组里面的下载实体
*/
@Default("false")
private boolean isGroupChild = false;
@Default("false")
private boolean isRedirect = false; //是否重定向
private String redirectUrl; //重定向链接
/**
* 任务类型
* {@link ITaskWrapper}
*/
private int taskType;
public void setTaskType(int taskType) {
this.taskType = taskType;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isGroupChild() {
return isGroupChild;
}
public void setGroupChild(boolean groupChild) {
isGroupChild = groupChild;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean redirect) {
isRedirect = redirect;
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public abstract String getFilePath();
public AbsNormalEntity() {
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(this.url);
dest.writeString(this.fileName);
dest.writeByte(this.isGroupChild ? (byte) 1 : (byte) 0);
dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0);
dest.writeString(this.redirectUrl);
}
protected AbsNormalEntity(Parcel in) {
super(in);
this.url = in.readString();
this.fileName = in.readString();
this.isGroupChild = in.readByte() != 0;
this.isRedirect = in.readByte() != 0;
this.redirectUrl = in.readString();
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.core.common;
import com.arialyy.aria.util.CommonUtil;
public abstract class BaseOption {
protected final String TAG;
public BaseOption() {
TAG = CommonUtil.getClassName(getClass());
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.core.common;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
/**
* Created by AriaL on 2018/3/3.
* 获取文件信息完成后 回调给下载线程的信息
*/
public class CompleteInfo {
/**
* 自定义的状态码
*/
public int code;
public ITaskWrapper wrapper;
public Object obj;
public CompleteInfo() {
}
public CompleteInfo(int code, ITaskWrapper wrapper) {
this.code = code;
this.wrapper = wrapper;
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.core.common;
public enum ErrorCode {
ERROR_CODE_NORMAL(0, "正常"), ERROR_CODE_TASK_ID_NULL(1, "任务id为空的错误码"),
ERROR_CODE_URL_NULL(2, "url 为空"), ERROR_CODE_URL_INVALID(3, "url 无效"),
ERROR_CODE_PAGE_NUM(4, "page和num不能小于1"), ERROR_CODE_GROUP_URL_NULL(5, "组合任务url列表为空"),
ERROR_CODE_UPLOAD_FILE_NULL(7, "上传文件不存在"),
ERROR_CODE_MEMBER_WARNING(8, "为了防止内存泄漏,请使用静态的成员类(public static class xxx)或文件类(A.java)"),
ERROR_CODE_TASK_NOT_EXIST(9, "任务信息不存在");
public int code;
public String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.core.common;
/**
* FTP 连接模式
*/
public interface FtpConnectionMode {
/**
* 被动模式
*/
int DATA_CONNECTION_MODE_PASV = 0;
/**
* 主动模式
*/
int DATA_CONNECTION_MODE_ACTIVITY = 1;
}

View File

@ -0,0 +1,225 @@
/*
* 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.core.common;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.loader.ILoaderVisitor;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.core.wrapper.RecordWrapper;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DbDataHelper;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* 处理任务记录,分配线程区间
*/
public abstract class RecordHandler implements IRecordHandler {
protected final String TAG = CommonUtil.getClassName(this);
@Deprecated private File mConfigFile;
private TaskRecord mTaskRecord;
private AbsTaskWrapper mTaskWrapper;
private AbsNormalEntity mEntity;
protected String mFilePath;
protected long mFileSize;
public RecordHandler(AbsTaskWrapper wrapper) {
mTaskWrapper = wrapper;
mEntity = (AbsNormalEntity) mTaskWrapper.getEntity();
}
public AbsTaskWrapper getWrapper() {
return mTaskWrapper;
}
public AbsNormalEntity getEntity() {
return mEntity;
}
@Override public void onPre() {
}
/**
* 获取任务记录,如果任务记录存在,检查任务记录
* 检查记录 对于分块任务: 子分块不存在或被删除,子线程将重新下载
* 对于普通任务: 预下载文件不存在,则任务任务呗删除
* 如果任务记录不存在或线程记录不存在,初始化记录
*
* @return 任务记录
*/
@Override
public TaskRecord getRecord(long fileSize) {
mFileSize = fileSize;
mConfigFile = new File(CommonUtil.getFileConfigPath(false, mEntity.getFileName()));
if (mConfigFile.exists()) {
convertDb();
} else {
onPre();
mTaskRecord = DbDataHelper.getTaskRecord(getFilePath(), mEntity.getTaskType());
if (mTaskRecord == null) {
initRecord(true);
}else if (mTaskRecord.threadRecords == null || mTaskRecord.threadRecords.size() == 0){
if (mTaskRecord.threadRecords == null){
mTaskRecord.threadRecords = new ArrayList<>();
}
initRecord(false);
}
handlerTaskRecord(mTaskRecord);
}
saveRecord();
return mTaskRecord;
}
/**
* convertDb 是兼容性代码 从3.4.1开始,线程配置信息将存储在数据库中。 将配置文件的内容复制到数据库中,并将配置文件删除
*/
private void convertDb() {
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?",
getFilePath());
if (records == null || records.size() == 0) {
Properties pro = FileUtil.loadConfig(mConfigFile);
if (pro.isEmpty()) {
ALog.d(TAG, "老版本的线程记录为空,任务为新任务");
initRecord(true);
return;
}
Set<Object> keys = pro.keySet();
// 老版本记录是5s存一次但是5s中内如果线程执行完成record记录是没有的只有state记录...
// 第一步应该是record 和 state去重取正确的线程数
Set<Integer> set = new HashSet<>();
for (Object key : keys) {
String str = String.valueOf(key);
int i = Integer.parseInt(str.substring(str.length() - 1));
set.add(i);
}
int threadNum = set.size();
if (threadNum == 0) {
ALog.d(TAG, "线程数为空,任务为新任务");
initRecord(true);
return;
}
mTaskWrapper.setNewTask(false);
mTaskRecord = createTaskRecord(threadNum);
mTaskRecord.isBlock = false;
File tempFile = new File(getFilePath());
for (int i = 0; i < threadNum; i++) {
ThreadRecord tRecord = new ThreadRecord();
tRecord.taskKey = mTaskRecord.filePath;
Object state = pro.getProperty(tempFile.getName() + STATE + i);
Object record = pro.getProperty(tempFile.getName() + RECORD + i);
if (state != null && Integer.parseInt(String.valueOf(state)) == 1) {
tRecord.isComplete = true;
continue;
}
if (record != null) {
long temp = Long.parseLong(String.valueOf(record));
tRecord.startLocation = temp > 0 ? temp : 0;
} else {
tRecord.startLocation = 0;
}
mTaskRecord.threadRecords.add(tRecord);
}
FileUtil.deleteFile(mConfigFile);
}
}
/**
* 初始化任务记录,分配线程区间,如果任务记录不存在,则创建新的任务记录
*
* @param newRecord {@code true} 需要创建新{@link TaskRecord}
*/
private void initRecord(boolean newRecord) {
if (newRecord) {
mTaskRecord = createTaskRecord(initTaskThreadNum());
}
mTaskWrapper.setNewTask(true);
int requestType = mTaskWrapper.getRequestType();
if (requestType == ITaskWrapper.M3U8_LIVE) {
return;
}
long blockSize = getFileSize() / mTaskRecord.threadNum;
// 处理线程区间记录
for (int i = 0; i < mTaskRecord.threadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr = createThreadRecord(mTaskRecord, i, startL, endL);
mTaskRecord.threadRecords.add(tr);
}
}
/**
* 保存任务记录
*/
private void saveRecord() {
mTaskRecord.threadNum = mTaskRecord.threadRecords.size();
mTaskRecord.save();
if (mTaskRecord.threadRecords != null && !mTaskRecord.threadRecords.isEmpty()) {
DbEntity.saveAll(mTaskRecord.threadRecords);
}
ALog.d(TAG, String.format("保存记录,线程记录数:%s", mTaskRecord.threadRecords.size()));
}
protected long getFileSize() {
return mFileSize;
}
/**
* 获取任务路径
*
* @return 任务文件路径
*/
private String getFilePath() {
if (mEntity instanceof DownloadEntity) {
return ((DownloadEntity) mTaskWrapper.getEntity()).getFilePath();
} else {
return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath();
}
}
@Override public void accept(ILoaderVisitor visitor) {
visitor.addComponent(this);
}
@Override public boolean checkTaskCompleted() {
if (mTaskRecord == null
|| mTaskRecord.threadRecords == null
|| mTaskRecord.threadRecords.isEmpty()) {
return false;
}
int completeNum = 0;
for (ThreadRecord tr : mTaskRecord.threadRecords) {
if (tr.isComplete) {
completeNum++;
}
}
return completeNum != 0 && completeNum == mTaskRecord.threadNum;
}
}

View File

@ -0,0 +1,207 @@
/*
* 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.core.common;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.io.IOException;
/**
* 任务记录帮助类,用于处理统一的逻辑
*
* @author lyy
* Date: 2019-09-19
*/
public class RecordHelper {
private String TAG = CommonUtil.getClassName(getClass());
private AbsTaskWrapper mWrapper;
protected TaskRecord mTaskRecord;
public RecordHelper(AbsTaskWrapper wrapper, TaskRecord record) {
mWrapper = wrapper;
mTaskRecord = record;
}
/**
* 处理非分块的,多线程任务
*/
public void handleMultiRecord() {
// 默认线程分块长度
long blockSize = mWrapper.getEntity().getFileSize() / mTaskRecord.threadRecords.size();
File temp = new File(mTaskRecord.filePath);
boolean fileExists = false;
if (!temp.exists()) {
createPlaceHolderFile(temp);
} else {
if (temp.length() != mWrapper.getEntity().getFileSize()) {
FileUtil.deleteFile(temp);
createPlaceHolderFile(temp);
}
fileExists = true;
}
// 处理文件被删除的情况
if (!fileExists) {
ALog.w(TAG, String.format("文件【%s】被删除重新分配线程区间", mTaskRecord.filePath));
for (int i = 0; i < mTaskRecord.threadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr = mTaskRecord.threadRecords.get(i);
tr.startLocation = startL;
tr.isComplete = false;
//最后一个线程的结束位置即为文件的总长度
if (tr.threadId == (mTaskRecord.threadNum - 1)) {
endL = mWrapper.getEntity().getFileSize();
}
tr.endLocation = endL;
}
}
//mWrapper.setNewTask(false);
}
/**
* 创建非分块的占位文件
*/
private void createPlaceHolderFile(File temp) {
BufferedRandomAccessFile tempFile;
try {
tempFile = new BufferedRandomAccessFile(temp, "rw");
tempFile.setLength(mWrapper.getEntity().getFileSize());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 处理分块任务的记录分块文件blockFileLen长度必须需要小于等于线程区间threadRectLen的长度
*/
public void handleBlockRecord() {
// 默认线程分块长度
long normalRectLen = mWrapper.getEntity().getFileSize() / mTaskRecord.threadRecords.size();
for (ThreadRecord tr : mTaskRecord.threadRecords) {
long threadRect = tr.blockLen;
File temp =
new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, tr.threadId));
if (!temp.exists()) {
ALog.i(TAG, String.format("分块文件【%s】不存在该分块将重新开始", temp.getPath()));
tr.isComplete = false;
tr.startLocation = tr.threadId * normalRectLen;
} else {
if (!tr.isComplete) {
ALog.i(TAG, String.format(
"startLocation = %s; endLocation = %s; block = %s; tempLen = %s; threadId = %s",
tr.startLocation, tr.endLocation, threadRect, temp.length(), tr.threadId));
long blockFileLen = temp.length(); // 磁盘中的分块文件长度
/*
* 检查磁盘中的分块文件
*/
if (blockFileLen > threadRect) {
ALog.i(TAG, String.format("分块【%s】错误分块长度【%s】 > 线程区间长度【%s】将重新开始该分块",
tr.threadId, blockFileLen, threadRect));
temp.delete();
tr.startLocation = tr.threadId * threadRect;
continue;
}
//正常情况下该线程的startLocation的位置
long realLocation = tr.threadId * normalRectLen + blockFileLen;
/*
* 检查记录文件
*/
if (blockFileLen == threadRect && blockFileLen != 0) {
ALog.i(TAG, String.format("分块【%s】已完成更新记录", temp.getPath()));
tr.startLocation = blockFileLen;
tr.isComplete = true;
} else if (tr.startLocation != realLocation) { // 处理记录小于分块文件长度的情况
ALog.i(TAG, String.format("修正分块【%s】的进度记录为%s", temp.getPath(), realLocation));
tr.startLocation = realLocation;
} else {
ALog.i(TAG, String.format("修正分块【%s】的进度记录为%s", temp.getPath(), realLocation));
tr.startLocation = realLocation;
tr.isComplete = false;
}
} else {
ALog.i(TAG, String.format("分块【%s】已完成", temp.getPath()));
}
}
}
//mWrapper.setNewTask(false);
}
/**
* 处理单线程的任务的记录
*/
public void handleSingleThreadRecord() {
// mTaskRecord.isBlock是为了兼容以前的文件格式
File file = new File(
mTaskRecord.isBlock ? String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0)
: mTaskRecord.filePath);
ThreadRecord tr = mTaskRecord.threadRecords.get(0);
if (!file.exists()) {
// 目标文件
File targetFile = new File(mTaskRecord.filePath);
// 处理组合任务其中一个子任务完成的情况
if (tr.isComplete
&& targetFile.exists()
&& targetFile.length() != 0
&& 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);
tr.startLocation = 0;
tr.isComplete = false;
tr.endLocation = mWrapper.getEntity().getFileSize();
} else if (file.length() != 0 && file.length() == mWrapper.getEntity().getFileSize()) {
ALog.d(TAG, "文件长度一致,线程完成");
tr.isComplete = true;
} else {
if (file.length() != tr.startLocation) {
ALog.i(TAG, String.format("修正【%s】的进度记录为%s", file.getPath(), file.length()));
tr.startLocation = file.length();
tr.isComplete = false;
}
}
//mWrapper.setNewTask(false);
}
/**
* 处理不支持断点的记录
*/
public void handleNoSupportBPRecord() {
ThreadRecord tr = mTaskRecord.threadRecords.get(0);
tr.startLocation = 0;
tr.endLocation = mWrapper.getEntity().getFileSize();
tr.taskKey = mTaskRecord.filePath;
tr.blockLen = tr.endLocation;
tr.isComplete = false;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.core.common;
/**
* Created by lyy on 2017/1/23.
* url请求方式目前支持GET、POST
*/
public enum RequestEnum {
GET("GET"), POST("POST");
public String name;
RequestEnum(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.core.common;
import android.os.Handler;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import java.io.File;
/**
* 子线程下载信息类
*/
public class SubThreadConfig {
public static final int TYPE_HTTP = 1;
public static final int TYPE_FTP = 2;
public static final int TYPE_M3U8_PEER = 3;
public static final int TYPE_HTTP_DG_SUB = 4;
public static final int TYPE_FTP_DG_SUB = 5;
public AbsTaskWrapper taskWrapper;
public boolean isBlock = false;
// 启动的线程
public int startThreadNum;
// 真正的下载地址如果是30x则是30x后的地址
public String url;
public File tempFile;
// 线程记录
public ThreadRecord record;
// 状态处理器
public Handler stateHandler;
// m3u8切片索引
public int peerIndex;
// 线程任务类型
public int threadType = TYPE_HTTP;
// 进度更新间隔,单位:毫秒
public long updateInterval = 1000;
// 扩展数据
public Object obj;
// 忽略失败
public boolean ignoreFailure = false;
/**
* 转换线程任务类型
*
* @param requestType {@link AbsTaskWrapper#getRequestType()}
* @return {@link #threadType}
*/
public static int getThreadType(int requestType) {
int threadType = SubThreadConfig.TYPE_HTTP;
switch (requestType) {
case ITaskWrapper.D_HTTP:
case ITaskWrapper.U_HTTP:
threadType = SubThreadConfig.TYPE_HTTP;
break;
case ITaskWrapper.D_FTP:
case ITaskWrapper.U_FTP:
threadType = SubThreadConfig.TYPE_FTP;
break;
case ITaskWrapper.D_FTP_DIR:
threadType = SubThreadConfig.TYPE_FTP_DG_SUB;
break;
case ITaskWrapper.DG_HTTP:
threadType = SubThreadConfig.TYPE_HTTP_DG_SUB;
break;
case ITaskWrapper.M3U8_LIVE:
case ITaskWrapper.M3U8_VOD:
threadType = SubThreadConfig.TYPE_M3U8_PEER;
break;
}
return threadType;
}
/**
* 根据配置肚脐更新间隔
*
* @param requestType {@link AbsTaskWrapper#getRequestType()}
* @return {@link #updateInterval}
*/
public static long getUpdateInterval(int requestType) {
long updateInterval = 1000;
switch (requestType) {
case ITaskWrapper.D_HTTP:
case ITaskWrapper.D_FTP:
case ITaskWrapper.M3U8_LIVE:
case ITaskWrapper.M3U8_VOD:
updateInterval = AriaConfig.getInstance().getDConfig().getUpdateInterval();
break;
case ITaskWrapper.D_FTP_DIR:
case ITaskWrapper.DG_HTTP:
updateInterval = AriaConfig.getInstance().getDGConfig().getUpdateInterval();
break;
case ITaskWrapper.U_HTTP:
case ITaskWrapper.U_FTP:
updateInterval = AriaConfig.getInstance().getUConfig().getUpdateInterval();
}
return updateInterval;
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.core.config;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.AriaCrashHandler;
import java.io.Serializable;
/**
* 应用配置
*/
public class AppConfig extends BaseConfig implements Serializable {
/**
* 是否使用{@link AriaCrashHandler}来捕获异常 {@code true} 使用;{@code false} 不使用
*/
boolean useAriaCrashHandler;
/**
* 设置Aria的日志级别
*
* {@link ALog#LOG_LEVEL_VERBOSE}
*/
int logLevel;
/**
* 是否检查网络,{@code true}检查网络
*/
boolean netCheck = true;
/**
* 是否使用广播 除非无法使用注解,否则不建议使用广播来接受任务 {@code true} 使用广播,{@code false} 不适用广播
*/
boolean useBroadcast = false;
/**
* 断网的时候是否重试,{@code true}断网也重试;{@code false}断网不重试,直接走失败的回调
*/
boolean notNetRetry = false;
public boolean isNotNetRetry() {
return notNetRetry;
}
public AppConfig setNotNetRetry(boolean notNetRetry) {
this.notNetRetry = notNetRetry;
save();
return this;
}
public boolean isUseBroadcast() {
return useBroadcast;
}
public AppConfig setUseBroadcast(boolean useBroadcast) {
this.useBroadcast = useBroadcast;
save();
return this;
}
public boolean isNetCheck() {
return netCheck;
}
public AppConfig setNetCheck(boolean netCheck) {
this.netCheck = netCheck;
save();
return this;
}
public AppConfig setLogLevel(int level) {
this.logLevel = level;
ALog.LOG_LEVEL = level;
save();
return this;
}
public int getLogLevel() {
return logLevel;
}
public boolean getUseAriaCrashHandler() {
return useAriaCrashHandler;
}
public AppConfig setUseAriaCrashHandler(boolean useAriaCrashHandler) {
this.useAriaCrashHandler = useAriaCrashHandler;
if (useAriaCrashHandler) {
Thread.setDefaultUncaughtExceptionHandler(new AriaCrashHandler());
} else {
Thread.setDefaultUncaughtExceptionHandler(null);
}
save();
return this;
}
@Override int getType() {
return TYPE_APP;
}
}

View File

@ -0,0 +1,51 @@
package com.arialyy.aria.core.config;
import android.text.TextUtils;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
import java.io.Serializable;
abstract class BaseConfig implements Serializable {
private static final String TAG = "BaseConfig";
static final int TYPE_DOWNLOAD = 1;
static final int TYPE_UPLOAD = 2;
static final int TYPE_APP = 3;
static final int TYPE_DGROUP = 4;
/**
* 类型
*
* @return {@link #TYPE_DOWNLOAD}、{@link #TYPE_UPLOAD}、{@link #TYPE_APP}、{@link #TYPE_DGROUP}
*/
abstract int getType();
/**
* 保存配置
*/
void save() {
String basePath = AriaConfig.getInstance().getAPP().getFilesDir().getPath();
String path = null;
switch (getType()) {
case TYPE_DOWNLOAD:
path = Configuration.DOWNLOAD_CONFIG_FILE;
break;
case TYPE_UPLOAD:
path = Configuration.UPLOAD_CONFIG_FILE;
break;
case TYPE_APP:
path = Configuration.APP_CONFIG_FILE;
break;
case TYPE_DGROUP:
path = Configuration.DGROUP_CONFIG_FILE;
break;
}
if (!TextUtils.isEmpty(path)) {
String tempPath = String.format("%s%s", basePath, path);
FileUtil.deleteFile(tempPath);
FileUtil.writeObjToFile(tempPath, this);
} else {
ALog.e(TAG, String.format("保存配置失败,配置类型:%s原因路径错误", getType()));
}
}
}

View File

@ -0,0 +1,219 @@
/*
* 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.core.config;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.io.Serializable;
/**
* 通用任务配置
*/
public abstract class BaseTaskConfig extends BaseConfig implements Serializable {
protected String TAG = CommonUtil.getClassName(getClass());
/**
* 设置写文件buff大小该数值大小不能小于2048数值变小下载速度会变慢
*/
int buffSize = 8192;
/**
* 进度刷新间隔默认1秒
*/
long updateInterval = 1000;
/**
* 旧任务数
*/
public int oldMaxTaskNum = 2;
/**
* 任务队列最大任务数, 默认为2
*/
int maxTaskNum = 2;
/**
* 下载失败重试次数默认为10
*/
int reTryNum = 10;
/**
* 设置重试间隔单位为毫秒默认2000毫秒
*/
int reTryInterval = 2000;
/**
* 设置url连接超时时间单位为毫秒默认5000毫秒
*/
int connectTimeOut = 5000;
/**
* 是否需要转换速度单位转换完成后为1b/s、1k/s、1m/s、1g/s、1t/s如果不需要将返回byte长度
*/
boolean isConvertSpeed = false;
/**
* 执行队列类型
*/
String queueMod = "wait";
/**
* 设置IO流读取时间单位为毫秒默认20000毫秒该时间不能少于10000毫秒
*/
int iOTimeOut = 20 * 1000;
/**
* 设置最大下载/上传速度单位kb, 为0表示不限速
*/
int maxSpeed = 0;
/**
* 设置https ca 证书信息path 为assets目录下的CA证书完整路径
*/
String caPath;
/**
* name 为CA证书名
*/
String caName;
public String getCaPath() {
return caPath;
}
public BaseConfig setCaPath(String caPath) {
this.caPath = caPath;
save();
return this;
}
public String getCaName() {
return caName;
}
public BaseConfig setCaName(String caName) {
this.caName = caName;
save();
return this;
}
public BaseTaskConfig setMaxTaskNum(int maxTaskNum) {
oldMaxTaskNum = this.maxTaskNum;
this.maxTaskNum = maxTaskNum;
save();
return this;
}
public int getMaxSpeed() {
return maxSpeed;
}
public BaseTaskConfig setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
save();
return this;
}
public long getUpdateInterval() {
return updateInterval;
}
/**
* 设置进度更新间隔该设置对正在运行的任务无效默认为1000毫秒
*
* @param updateInterval 不能小于0
*/
public BaseTaskConfig setUpdateInterval(long updateInterval) {
if (updateInterval <= 0) {
ALog.w("Configuration", "进度更新间隔不能小于0");
return this;
}
this.updateInterval = updateInterval;
save();
return this;
}
public String getQueueMod() {
return queueMod;
}
public BaseTaskConfig setQueueMod(String queueMod) {
this.queueMod = queueMod;
save();
return this;
}
public int getMaxTaskNum() {
return maxTaskNum;
}
public int getReTryNum() {
return reTryNum;
}
public BaseTaskConfig setReTryNum(int reTryNum) {
this.reTryNum = reTryNum;
save();
return this;
}
public int getReTryInterval() {
return reTryInterval;
}
public BaseTaskConfig setReTryInterval(int reTryInterval) {
this.reTryInterval = reTryInterval;
save();
return this;
}
public boolean isConvertSpeed() {
return isConvertSpeed;
}
public BaseTaskConfig setConvertSpeed(boolean convertSpeed) {
isConvertSpeed = convertSpeed;
save();
return this;
}
public int getConnectTimeOut() {
return connectTimeOut;
}
public BaseTaskConfig setConnectTimeOut(int connectTimeOut) {
this.connectTimeOut = connectTimeOut;
save();
return this;
}
public int getIOTimeOut() {
return iOTimeOut;
}
public BaseTaskConfig setIOTimeOut(int iOTimeOut) {
this.iOTimeOut = iOTimeOut;
save();
return this;
}
public int getBuffSize() {
return buffSize;
}
public BaseTaskConfig setBuffSize(int buffSize) {
this.buffSize = buffSize;
save();
return this;
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.core.config;
public interface ConfigType {
int DOWNLOAD = 1;
int UPLOAD = 2;
int APP = 3;
int D_GROUP = 4;
}

View File

@ -0,0 +1,117 @@
/*
* 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.core.config;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
/**
* Created by lyy on 2016/12/8. 信息配置 kotlin 方式有bug不能将public去掉
*/
public final class Configuration {
private static final String TAG = "Configuration";
private static volatile Configuration INSTANCE = null;
public static final String XML_FILE = "/Aria/aria_config.xml";
static final String DOWNLOAD_CONFIG_FILE = "/Aria/AriaDownload.cfg";
static final String UPLOAD_CONFIG_FILE = "/Aria/AriaUpload.cfg";
static final String APP_CONFIG_FILE = "/Aria/AriaApp.cfg";
static final String DGROUP_CONFIG_FILE = "/Aria/AriaDGroup.cfg";
public DownloadConfig downloadCfg;
public UploadConfig uploadCfg;
public AppConfig appCfg;
public DGroupConfig dGroupCfg;
private Configuration() {
//删除老版本的配置文件
String basePath = AriaConfig.getInstance().getAPP().getFilesDir().getPath();
del351Config(basePath);
File newDCfg = new File(String.format("%s%s", basePath, DOWNLOAD_CONFIG_FILE));
File newUCfg = new File(String.format("%s%s", basePath, UPLOAD_CONFIG_FILE));
File newACfg = new File(String.format("%s%s", basePath, APP_CONFIG_FILE));
File dgCfg = new File(String.format("%s%s", basePath, DGROUP_CONFIG_FILE));
// 加载下载配置
if (newDCfg.exists()) {
downloadCfg = (DownloadConfig) FileUtil.readObjFromFile(newDCfg.getPath());
}
if (downloadCfg == null) {
downloadCfg = new DownloadConfig();
}
// 加载上传配置
if (newUCfg.exists()) {
uploadCfg = (UploadConfig) FileUtil.readObjFromFile(newUCfg.getPath());
}
if (uploadCfg == null) {
uploadCfg = new UploadConfig();
}
// 加载app配置
if (newACfg.exists()) {
appCfg = (AppConfig) FileUtil.readObjFromFile(newACfg.getPath());
}
if (appCfg == null) {
appCfg = new AppConfig();
}
// 加载下载类型组合任务的配置
if (dgCfg.exists()) {
dGroupCfg = (DGroupConfig) FileUtil.readObjFromFile(dgCfg.getPath());
}
if (dGroupCfg == null) {
dGroupCfg = new DGroupConfig();
}
}
public static Configuration getInstance() {
if (INSTANCE == null) {
synchronized (AppConfig.class) {
INSTANCE = new Configuration();
}
}
return INSTANCE;
}
/**
* 检查配置文件是否存在,只要{@link DownloadConfig}、{@link UploadConfig}、{@link AppConfig}、{@link
* DGroupConfig}其中一个不存在 则任务配置文件不存在
*
* @return {@code true}配置存在,{@code false}配置不存在
*/
public boolean configExists() {
String basePath = AriaConfig.getInstance().getAPP().getFilesDir().getPath();
return (new File(String.format("%s%s", basePath, DOWNLOAD_CONFIG_FILE))).exists()
&& (new File(String.format("%s%s", basePath, UPLOAD_CONFIG_FILE))).exists()
&& (new File(String.format("%s%s", basePath, APP_CONFIG_FILE))).exists()
&& (new File(String.format("%s%s", basePath, DGROUP_CONFIG_FILE))).exists();
}
/**
* 删除3.5.2之前版本的配置文件从3.5.2开始配置文件的保存不再使用properties文件
*/
private void del351Config(String basePath) {
File oldDCfg = new File(String.format("%s/Aria/DownloadConfig.properties", basePath));
if (oldDCfg.exists()) { // 只需要判断一个
File oldUCfg = new File(String.format("%s/Aria/UploadConfig.properties", basePath));
File oldACfg = new File(String.format("%s/Aria/AppConfig.properties", basePath));
oldDCfg.delete();
oldUCfg.delete();
oldACfg.delete();
// 删除配置触发更新
File temp = new File(String.format("%s%s", basePath, XML_FILE));
if (temp.exists()) {
temp.delete();
}
}
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.core.config;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.event.DGMaxNumEvent;
import com.arialyy.aria.core.event.DSpeedEvent;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.util.ALog;
import java.io.Serializable;
/**
* 下载类型的组合任务
*/
public class DGroupConfig extends BaseTaskConfig implements Serializable {
/**
* 能同时下载的子任务最大任务数默认3
*/
int subMaxTaskNum = 3;
/**
* 子任务失败时回调stop默认true
*/
private boolean subFailAsStop = true;
/**
* 子任务重试次数默认为5
*/
int subReTryNum = 5;
/**
* 子任务下载失败时的重试间隔单位为毫秒默认2000毫秒-
*/
int subReTryInterval = 2000;
private DownloadConfig subConfig;
DGroupConfig() {
getSubConfig();
}
@Override int getType() {
return TYPE_DGROUP;
}
@Override public DGroupConfig setMaxSpeed(int maxSpeed) {
super.setMaxSpeed(maxSpeed);
EventMsgUtil.getDefault().post(new DSpeedEvent(maxSpeed));
return this;
}
public DownloadConfig getSubConfig() {
subConfig = AriaConfig.getInstance().getDConfig();
return subConfig;
}
public DGroupConfig setMaxTaskNum(int maxTaskNum) {
if (maxTaskNum <= 0) {
ALog.e(TAG, "组合任务最大任务数不能小于0");
return this;
}
super.setMaxTaskNum(maxTaskNum);
EventMsgUtil.getDefault().post(new DGMaxNumEvent(maxTaskNum));
return this;
}
public int getSubMaxTaskNum() {
return subMaxTaskNum;
}
public DGroupConfig setSubMaxTaskNum(int subMaxTaskNum) {
this.subMaxTaskNum = subMaxTaskNum;
save();
return this;
}
public int getSubReTryNum() {
return subReTryNum;
}
public DGroupConfig setSubReTryNum(int subReTryNum) {
this.subReTryNum = subReTryNum;
subConfig.reTryNum = subReTryNum;
save();
return this;
}
public int getSubReTryInterval() {
return subReTryInterval;
}
public DGroupConfig setSubReTryInterval(int subReTryInterval) {
this.subReTryInterval = subReTryInterval;
subConfig.reTryInterval = subReTryInterval;
save();
return this;
}
public boolean isSubFailAsStop() {
return subFailAsStop;
}
public DGroupConfig setSubFailAsStop(boolean subFailAsStop) {
this.subFailAsStop = subFailAsStop;
save();
return this;
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.core.config;
import com.arialyy.aria.core.event.DMaxNumEvent;
import com.arialyy.aria.core.event.DSpeedEvent;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.util.ALog;
import java.io.Serializable;
/**
* 下载配置
*/
public class DownloadConfig extends BaseTaskConfig implements Serializable {
/**
* 下载线程数下载线程数不能小于1
* 注意:
* 1、线程下载数改变后新的下载任务才会生效
* 2、如果任务大小小于1m该设置不会生效
* 3、从3.4.1开始如果线程数为1文件初始化时将不再预占用对应长度的空间下载多少byte则占多大的空间
* 对于采用多线程的任务或旧任务,依然采用原来的文件空间占用方式;
*/
int threadNum = 3;
/**
* 多线程下载是否使用块下载模式,{@code true}使用,{@code false}不使用
* 注意:
* 1、使用分块模式在读写性能底下的手机上合并文件需要的时间会更加长
* 2、优点是使用多线程的块下载初始化时文件初始化时将不会预占用对应长度的空间
* 3、只对新的多线程下载任务有效 4、只对多线程的任务有效
*/
boolean useBlock = true;
/**
* 设置http下载获取文件大小是否使用Head请求。true使用head请求false使用默认的get请求
*/
boolean useHeadRequest = false;
public boolean isUseHeadRequest() {
return useHeadRequest;
}
public DownloadConfig setUseHeadRequest(boolean useHeadRequest) {
this.useHeadRequest = useHeadRequest;
save();
return this;
}
public boolean isUseBlock() {
return useBlock;
}
@Override public DownloadConfig setMaxSpeed(int maxSpeed) {
super.setMaxSpeed(maxSpeed);
EventMsgUtil.getDefault().post(new DSpeedEvent(maxSpeed));
return this;
}
public DownloadConfig setUseBlock(boolean useBlock) {
this.useBlock = useBlock;
save();
return this;
}
public DownloadConfig setMaxTaskNum(int maxTaskNum) {
if (maxTaskNum <= 0) {
ALog.e(TAG, "下载任务最大任务数不能小于0");
return this;
}
super.setMaxTaskNum(maxTaskNum);
EventMsgUtil.getDefault().post(new DMaxNumEvent(maxTaskNum));
return this;
}
public DownloadConfig setThreadNum(int threadNum) {
this.threadNum = threadNum;
save();
return this;
}
public int getThreadNum() {
return threadNum;
}
DownloadConfig() {
}
@Override int getType() {
return TYPE_DOWNLOAD;
}
}

View File

@ -0,0 +1,5 @@
package com.arialyy.aria.core.config;
public class TTaskConfigAdapeter {
}

View File

@ -0,0 +1,51 @@
/*
* 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.core.config;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.event.UMaxNumEvent;
import com.arialyy.aria.core.event.USpeedEvent;
import com.arialyy.aria.util.ALog;
import java.io.Serializable;
/**
* 上传配置
*/
public class UploadConfig extends BaseTaskConfig implements Serializable {
UploadConfig() {
}
@Override public UploadConfig setMaxSpeed(int maxSpeed) {
super.setMaxSpeed(maxSpeed);
EventMsgUtil.getDefault().post(new USpeedEvent(maxSpeed));
return this;
}
public UploadConfig setMaxTaskNum(int maxTaskNum) {
if (maxTaskNum <= 0){
ALog.e(TAG, "上传任务最大任务数不能小于0");
return this;
}
super.setMaxTaskNum(maxTaskNum);
EventMsgUtil.getDefault().post(new UMaxNumEvent(maxTaskNum));
return this;
}
@Override int getType() {
return TYPE_UPLOAD;
}
}

View File

@ -0,0 +1,277 @@
/*
* 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.core.config;
import android.text.TextUtils;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Created by lyy on 2017/5/22. 读取配置文件
*/
public class XMLReader extends DefaultHandler {
private final String TAG = CommonUtil.getClassName(this);
private DownloadConfig mDownloadConfig = Configuration.getInstance().downloadCfg;
private UploadConfig mUploadConfig = Configuration.getInstance().uploadCfg;
private AppConfig mAppConfig = Configuration.getInstance().appCfg;
private DGroupConfig mDGroupConfig = Configuration.getInstance().dGroupCfg;
private int mType;
@Override public void startDocument() throws SAXException {
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
super.startElement(uri, localName, qName, attributes);
switch (qName) {
case "download":
mType = ConfigType.DOWNLOAD;
break;
case "upload":
mType = ConfigType.UPLOAD;
break;
case "app":
mType = ConfigType.APP;
break;
case "dGroup":
mType = ConfigType.D_GROUP;
break;
}
if (mType == ConfigType.DOWNLOAD || mType == ConfigType.UPLOAD || mType == ConfigType.D_GROUP) {
String value = attributes.getValue("value");
switch (qName) {
case "threadNum": // 线程数
int threadNum = checkInt(value) ? Integer.parseInt(value) : 3;
if (threadNum < 1) {
ALog.w(TAG, "下载线程数不能小于 1");
threadNum = 1;
}
setField("threadNum", threadNum, ConfigType.DOWNLOAD);
break;
case "maxTaskNum": //最大任务书
int maxTaskNum = checkInt(value) ? Integer.parseInt(value) : 2;
if (maxTaskNum < 1) {
ALog.w(TAG, "任务队列数不能小于 1");
maxTaskNum = 2;
}
setField("maxTaskNum", maxTaskNum, mType);
break;
case "reTryNum": //任务重试次数
setField("reTryNum", checkInt(value) ? Integer.parseInt(value) : 0, mType);
break;
case "connectTimeOut": // 连接超时时间
setField("connectTimeOut", checkInt(value) ? Integer.parseInt(value) : 5 * 1000,
mType);
break;
case "iOTimeOut": //io流超时时间
int iOTimeOut = checkInt(value) ? Integer.parseInt(value) : 10 * 1000;
if (iOTimeOut < 10 * 1000) {
iOTimeOut = 10 * 1000;
}
setField("iOTimeOut", iOTimeOut, mType);
break;
case "reTryInterval": //失败重试间隔
int reTryInterval = checkInt(value) ? Integer.parseInt(value) : 2 * 1000;
if (reTryInterval < 2 * 1000) {
reTryInterval = 2 * 1000;
}
setField("reTryInterval", reTryInterval, mType);
break;
case "buffSize": //缓冲大小
int buffSize = checkInt(value) ? Integer.parseInt(value) : 8192;
if (buffSize < 2048) {
buffSize = 2048;
}
setField("buffSize", buffSize, mType);
break;
case "ca": // ca证书
String caName = attributes.getValue("name");
String caPath = attributes.getValue("path");
setField("caName", caName, mType);
setField("caPath", caPath, mType);
break;
case "convertSpeed": // 是否转换速度
setField("isConvertSpeed", !checkBoolean(value) || Boolean.parseBoolean(value),
mType);
break;
case "maxSpeed": // 最大速度
int maxSpeed = checkInt(value) ? Integer.parseInt(value) : 0;
setField("maxSpeed", maxSpeed, mType);
break;
case "queueMod": // 队列类型
String mod = "now";
if (!TextUtils.isEmpty(value) && (value.equalsIgnoreCase("now") || value.equalsIgnoreCase(
"wait"))) {
mod = value;
}
setField("queueMod", mod, mType);
break;
case "updateInterval": // 进度更新时间
setField("updateInterval", checkLong(value) ? Long.parseLong(value) : 1000,
mType);
break;
case "useBlock": // 是否使用分块任务
setField("useBlock", checkBoolean(value) ? Boolean.valueOf(value) : false,
ConfigType.DOWNLOAD);
break;
case "subMaxTaskNum": // 子任务最大任务数
int subMaxTaskNum = checkInt(value) ? Integer.parseInt(value) : 3;
setField("subMaxTaskNum", subMaxTaskNum, ConfigType.D_GROUP);
break;
case "subFailAsStop": // 子任务失败时回调stop
setField("subFailAsStop", checkBoolean(value) ? Boolean.valueOf(value) : false,
ConfigType.D_GROUP);
break;
case "subReTryNum": // 子任务重试次数
int subReTryNum = checkInt(value) ? Integer.parseInt(value) : 5;
setField("subReTryNum", subReTryNum, ConfigType.D_GROUP);
break;
case "subReTryInterval": // 子任务重试间隔
int subReTryInterval = checkInt(value) ? Integer.parseInt(value) : 2000;
setField("subReTryInterval", subReTryInterval, ConfigType.D_GROUP);
break;
case "useHeadRequest": // 是否使用head请求
boolean useHeadRequest = checkBoolean(value) ? Boolean.valueOf(value) : false;
setField("useHeadRequest", useHeadRequest, ConfigType.DOWNLOAD);
break;
}
} else if (mType == ConfigType.APP) {
String value = attributes.getValue("value");
switch (qName) {
case "useAriaCrashHandler": // 是否捕捉崩溃日志
setField("useAriaCrashHandler", checkBoolean(value) ? Boolean.valueOf(value) : true,
ConfigType.APP);
break;
case "logLevel": // 日记等级
int level = checkInt(value) ? Integer.parseInt(value) : ALog.LOG_LEVEL_VERBOSE;
if (level < ALog.LOG_LEVEL_VERBOSE || level > ALog.LOG_CLOSE) {
ALog.w(TAG, "level【" + level + "】错误");
level = ALog.LOG_LEVEL_VERBOSE;
}
setField("logLevel", level, ConfigType.APP);
break;
case "netCheck": // 是否检查网络
setField("netCheck", checkBoolean(value) ? Boolean.valueOf(value) : false,
ConfigType.APP);
break;
case "useBroadcast": // 是否使用广播
setField("useBroadcast", checkBoolean(value) ? Boolean.valueOf(value) : false,
ConfigType.APP);
break;
case "notNetRetry": // 没有网络也重试
setField("notNetRetry", checkBoolean(value) ? Boolean.valueOf(value) : false,
ConfigType.APP);
break;
}
}
}
private void setField(String key, Object value, int type) {
if (type == ConfigType.DOWNLOAD) {
setField(DownloadConfig.class, mDownloadConfig, key, value);
} else if (type == ConfigType.UPLOAD) {
setField(UploadConfig.class, mUploadConfig, key, value);
} else if (type == ConfigType.APP) {
setField(AppConfig.class, mAppConfig, key, value);
} else if (type == ConfigType.D_GROUP) {
setField(DGroupConfig.class, mDGroupConfig, key, value);
}
}
private void setField(Class clazz, Object target, String key, Object value) {
Field field = CommonUtil.getField(clazz, key);
try {
field.set(target, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 检查是否int值是否合法
*
* @return {@code true} 合法
*/
private boolean checkInt(String value) {
if (TextUtils.isEmpty(value)) {
return false;
}
try {
int l = Integer.parseInt(value);
return l >= 0;
} catch (NumberFormatException e) {
e.printStackTrace();
return false;
}
}
/**
* 检查是否long值是否合法
*
* @return {@code true} 合法
*/
private boolean checkLong(String value) {
if (TextUtils.isEmpty(value)) {
return false;
}
try {
Long l = Long.parseLong(value);
return true;
} catch (NumberFormatException e) {
e.printStackTrace();
return false;
}
}
/**
* 检查boolean值是否合法
*
* @return {@code true} 合法
*/
private boolean checkBoolean(String value) {
return !TextUtils.isEmpty(value) && (value.equalsIgnoreCase("true") || value.equalsIgnoreCase(
"false"));
}
@Override public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
}
@Override public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
}
@Override public void endDocument() throws SAXException {
super.endDocument();
mDownloadConfig.save();
mUploadConfig.save();
mAppConfig.save();
mDGroupConfig.save();
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.core.download;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import java.util.List;
/**
* 组合任务实体包裹器,用于加载和任务相关的参数,如:组合任务实体{@link DownloadGroupEntity}
*/
public abstract class AbsGroupTaskWrapper<ENTITY extends AbsEntity, SUB extends AbsTaskWrapper>
extends AbsTaskWrapper<ENTITY> {
public AbsGroupTaskWrapper(ENTITY entity) {
super(entity);
}
public abstract List<SUB> getSubTaskWrapper();
public abstract void setSubTaskWrapper(List<SUB> subTaskWrapper);
/**
* {@code true} 忽略任务冲突不考虑组任务hash冲突的情况
*/
private boolean ignoreTaskOccupy = false;
public boolean isIgnoreTaskOccupy() {
return ignoreTaskOccupy;
}
public void setIgnoreTaskOccupy(boolean ignoreTaskOccupy) {
this.ignoreTaskOccupy = ignoreTaskOccupy;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.core.download;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.orm.AbsDbWrapper;
import com.arialyy.aria.orm.annotation.Many;
import com.arialyy.aria.orm.annotation.One;
import com.arialyy.aria.orm.annotation.Wrapper;
import java.util.List;
/**
* Created by laoyuyu on 2018/3/30.
* 任务组实体和子任务实体的关系
*/
@Wrapper
public class DGEntityWrapper extends AbsDbWrapper {
@One
public DownloadGroupEntity groupEntity;
@Many(parentColumn = "groupHash", entityColumn = "groupHash")
public List<DownloadEntity> subEntity;
@Override protected void handleConvert() {
if (subEntity != null && !subEntity.isEmpty()) {
groupEntity.setSubEntities(subEntity);
}
}
}

View File

@ -0,0 +1,89 @@
/*
* 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.core.download;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.config.DGroupConfig;
import java.util.ArrayList;
import java.util.List;
/**
* Created by AriaL on 2017/7/1. 任务组的任务实体修饰器
*/
public class DGTaskWrapper extends AbsGroupTaskWrapper<DownloadGroupEntity, DTaskWrapper> {
private List<DTaskWrapper> subWrappers;
private boolean unknownSize = false;
/**
* 保存临时设置的文件夹路径
*/
private String dirPathTemp;
/**
* 子任务文件名
*/
private List<String> subNameTemp = new ArrayList<>();
public DGTaskWrapper(DownloadGroupEntity entity) {
super(entity);
}
public List<String> getSubNameTemp() {
return subNameTemp;
}
public void setSubNameTemp(List<String> subNameTemp) {
this.subNameTemp = subNameTemp;
}
public String getDirPathTemp() {
return dirPathTemp;
}
public void setDirPathTemp(String mDirPathTemp) {
this.dirPathTemp = mDirPathTemp;
}
@Override
public void setSubTaskWrapper(List<DTaskWrapper> subTaskEntities) {
this.subWrappers = subTaskEntities;
}
public boolean isUnknownSize() {
return unknownSize;
}
public void setUnknownSize(boolean unknownSize) {
this.unknownSize = unknownSize;
}
@Override public String getKey() {
return getEntity().getKey();
}
@Override public DGroupConfig getConfig() {
return Configuration.getInstance().dGroupCfg;
}
@Override public List<DTaskWrapper> getSubTaskWrapper() {
if (subWrappers == null) {
subWrappers = new ArrayList<>();
}
return subWrappers;
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.core.download;
import com.arialyy.aria.core.TaskOptionParams;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.config.DownloadConfig;
import com.arialyy.aria.core.inf.ITaskOption;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ComponentUtil;
/**
* Created by lyy on 2017/1/23. 下载任务实体和下载实体为一对一关系,下载实体删除,任务实体自动删除
*/
public class DTaskWrapper extends AbsTaskWrapper<DownloadEntity> {
/**
* 所属的任务组组名如果不属于任务组则为null
*/
private String groupHash;
/**
* 该任务是否属于任务组
*/
private boolean isGroupTask = false;
/**
* M3u8任务配置信息
*/
private ITaskOption m3u8Option;
private TaskOptionParams m3u8Params = new TaskOptionParams();
/**
* 文件下载url的临时保存变量
*/
private String mTempUrl;
/**
* 文件保存路径的临时变量
*/
private String mTempFilePath;
public DTaskWrapper(DownloadEntity entity) {
super(entity);
}
public ITaskOption getM3u8Option() {
return m3u8Option;
}
public void generateM3u8Option(Class<? extends ITaskOption> clazz) {
m3u8Option = ComponentUtil.getInstance().buildTaskOption(clazz, m3u8Params);
}
public TaskOptionParams getM3U8Params() {
if (m3u8Params == null) {
m3u8Params = new TaskOptionParams();
}
return m3u8Params;
}
/**
* Task实体对应的key下载url
*/
@Override public String getKey() {
return getEntity().getKey();
}
@Override public DownloadConfig getConfig() {
if (isGroupTask) {
return Configuration.getInstance().dGroupCfg.getSubConfig();
} else {
return Configuration.getInstance().downloadCfg;
}
}
public String getGroupHash() {
return groupHash;
}
public boolean isGroupTask() {
return isGroupTask;
}
public void setGroupHash(String groupHash) {
this.groupHash = groupHash;
}
public void setGroupTask(boolean groupTask) {
isGroupTask = groupTask;
}
public String getTempUrl() {
return mTempUrl;
}
public void setTempUrl(String mTempUrl) {
this.mTempUrl = mTempUrl;
}
public String getTempFilePath() {
return mTempFilePath;
}
public void setTempFilePath(String mTempFilePath) {
this.mTempFilePath = mTempFilePath;
}
}

View File

@ -0,0 +1,214 @@
/*
* 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.core.download;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.orm.annotation.Ignore;
import com.arialyy.aria.orm.annotation.Unique;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/**
* Created by lyy on 2015/12/25.
* 下载实体
*/
public class DownloadEntity extends AbsNormalEntity implements Parcelable, Cloneable {
@Unique private String downloadPath; //保存路径
/**
* 所属任务组
*/
private String groupHash;
/**
* 从服务器的返回信息中获取的文件md5信息如果服务器没有返回则不会设置该信息
* 如果你已经设置了该任务的MD5信息Aria也不会从服务器返回的信息中获取该信息
*/
private String md5Code;
/**
* 从服务器的返回信息中获取的文件描述信息
*/
private String disposition;
/**
* 从disposition获取到的文件名如果可以获取到则会赋值到这个字段
*/
private String serverFileName;
@Ignore
private M3U8Entity m3U8Entity;
/**
* 获取m3u8数据信息
*
* @return 如果m3u8信息为空则返回null
*/
public M3U8Entity getM3U8Entity() {
if (TextUtils.isEmpty(downloadPath)) {
ALog.e("DownloadEntity", "文件保存路径为空获取m3u8实体之前需要设置文件保存路径");
return null;
}
if (m3U8Entity == null) {
m3U8Entity = DbEntity.findFirst(M3U8Entity.class, "filePath=?", downloadPath);
}
return m3U8Entity;
}
/**
* 设置进来的地址,如果需要获取真实的下载地址,请使用{@link #getRealUrl()}
*/
@Override public String getKey() {
return getUrl();
}
public String getRealUrl(){
return isRedirect() ? getRedirectUrl() : getUrl();
}
@Override public int getTaskType() {
int type;
if (getUrl() == null) {
type = ITaskWrapper.ERROR;
} else if (getUrl().startsWith("http")) {
M3U8Entity temp = getM3U8Entity();
if (temp == null) {
type = ITaskWrapper.D_HTTP;
} else {
type = temp.isLive() ? ITaskWrapper.M3U8_LIVE : ITaskWrapper.M3U8_VOD;
}
} else if (getUrl().startsWith("ftp")) {
type = ITaskWrapper.D_FTP;
} else if (getUrl().startsWith("sftp")) {
type = ITaskWrapper.D_SFTP;
} else {
type = ITaskWrapper.ERROR;
}
return type;
}
public DownloadEntity() {
}
public String getMd5Code() {
return md5Code;
}
public void setMd5Code(String md5Code) {
this.md5Code = md5Code;
}
public String getDisposition() {
return TextUtils.isEmpty(disposition) ? "" : CommonUtil.decryptBASE64(disposition);
}
public void setDisposition(String disposition) {
this.disposition = disposition;
}
public String getServerFileName() {
return serverFileName;
}
public void setServerFileName(String serverFileName) {
this.serverFileName = serverFileName;
}
public String getGroupHash() {
return groupHash;
}
public void setGroupHash(String groupHash) {
this.groupHash = groupHash;
}
@Override
public String getFilePath() {
return downloadPath;
}
public DownloadEntity setFilePath(String filePath) {
this.downloadPath = filePath;
return this;
}
@Override public DownloadEntity clone() throws CloneNotSupportedException {
return (DownloadEntity) super.clone();
}
@Override public String toString() {
return "DownloadEntity{"
+ "downloadPath='"
+ downloadPath
+ '\''
+ ", groupHash='"
+ groupHash
+ '\''
+ ", fileName='"
+ getFileName()
+ '\''
+ ", md5Code='"
+ md5Code
+ '\''
+ ", disposition='"
+ disposition
+ '\''
+ ", serverFileName='"
+ serverFileName
+ '\''
+ '}';
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(this.downloadPath);
dest.writeString(this.groupHash);
dest.writeString(this.md5Code);
dest.writeString(this.disposition);
dest.writeString(this.serverFileName);
dest.writeParcelable(this.m3U8Entity, flags);
}
protected DownloadEntity(Parcel in) {
super(in);
this.downloadPath = in.readString();
this.groupHash = in.readString();
this.md5Code = in.readString();
this.disposition = in.readString();
this.serverFileName = in.readString();
this.m3U8Entity = in.readParcelable(M3U8Entity.class.getClassLoader());
}
public static final Creator<DownloadEntity> CREATOR = new Creator<DownloadEntity>() {
@Override public DownloadEntity createFromParcel(Parcel source) {
return new DownloadEntity(source);
}
@Override public DownloadEntity[] newArray(int size) {
return new DownloadEntity[size];
}
};
}

View File

@ -0,0 +1,86 @@
/*
* 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.core.download;
import android.os.Parcel;
import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsGroupEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.annotation.Ignore;
import java.util.ArrayList;
import java.util.List;
/**
* Created by AriaL on 2017/6/29. 下载任务组实体
*/
public class DownloadGroupEntity extends AbsGroupEntity {
@Ignore private List<DownloadEntity> subEntities;
/**
* 子任务实体列表
*/
public List<DownloadEntity> getSubEntities() {
if (subEntities == null) {
subEntities = new ArrayList<>();
}
return subEntities;
}
public void setSubEntities(List<DownloadEntity> subTasks) {
this.subEntities = subTasks;
}
public void setGroupHash(String key) {
this.groupHash = key;
}
@Override public int getTaskType() {
if (getSubEntities() == null || getSubEntities().isEmpty() || TextUtils.isEmpty(
getSubEntities().get(0).getUrl())) {
return ITaskWrapper.ERROR;
}
return (groupHash.startsWith("ftp") || groupHash.startsWith("sftp")) ? ITaskWrapper.D_FTP_DIR
: ITaskWrapper.DG_HTTP;
}
public DownloadGroupEntity() {
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedList(this.subEntities);
}
protected DownloadGroupEntity(Parcel in) {
super(in);
this.subEntities = in.createTypedArrayList(DownloadEntity.CREATOR);
}
public static final Creator<DownloadGroupEntity> CREATOR = new Creator<DownloadGroupEntity>() {
@Override public DownloadGroupEntity createFromParcel(Parcel source) {
return new DownloadGroupEntity(source);
}
@Override public DownloadGroupEntity[] newArray(int size) {
return new DownloadGroupEntity[size];
}
};
}

View File

@ -0,0 +1,295 @@
/*
* 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.core.download;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.orm.annotation.Default;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.DbDataHelper;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
/**
* M3U8实体信息
*/
public class M3U8Entity extends DbEntity implements Parcelable {
/**
* 文件保存路径
*/
private String filePath;
/**
* 当前peer的位置
*/
private int peerIndex;
/**
* peer总数
*/
private int peerNum;
/**
* 是否是直播true 直播
*/
private boolean isLive;
/**
* 缓存目录
*/
private String cacheDir;
/**
* 加密key保存地址
*/
public String keyPath;
/**
* 加密key的下载地址
*/
public String keyUrl;
/**
* 加密算法
*/
public String method;
/**
* key的iv值
*/
public String iv;
/**
* key的格式可能为空
*/
public String keyFormat;
/**
* key的格式版本默认为1如果是多个版本使用"/"分隔,如:"1", "1/2", or "1/2/5"
*/
@Default("1")
public String keyFormatVersion = "1";
public String getKeyFormat() {
return keyFormat;
}
public void setKeyFormat(String keyFormat) {
this.keyFormat = keyFormat;
}
public String getKeyFormatVersion() {
return keyFormatVersion;
}
public void setKeyFormatVersion(String keyFormatVersion) {
this.keyFormatVersion = keyFormatVersion;
}
public String getKeyPath() {
return keyPath;
}
public void setKeyPath(String keyPath) {
this.keyPath = keyPath;
}
public String getKeyUrl() {
return keyUrl;
}
public void setKeyUrl(String keyUrl) {
this.keyUrl = keyUrl;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getIv() {
return iv;
}
public void setIv(String iv) {
this.iv = iv;
}
public boolean isLive() {
return isLive;
}
/**
* 获取m3u8切片
* 如果任务未完成,则返回所有已下载完成的切片;
* 如果任务已完成如果你设置了合并分块的请求返回null如果没有设置该请求则返回所有已下载完成的切片
*/
public List<PeerInfo> getCompletedPeer() {
if (TextUtils.isEmpty(getCacheDir())) {
ALog.w("M3U8Entity", "任务未下载,获取切片失败");
return null;
}
List<PeerInfo> peers = new ArrayList<>();
TaskRecord taskRecord = DbDataHelper.getTaskRecord(filePath,
isLive ? ITaskWrapper.M3U8_LIVE : ITaskWrapper.M3U8_VOD);
File cacheDir = new File(getCacheDir());
if ((taskRecord == null
|| taskRecord.threadRecords == null
|| taskRecord.threadRecords.isEmpty())
&& !cacheDir.exists()) {
return null;
}
// 处理任务完成的情况
if (taskRecord == null
|| taskRecord.threadRecords == null
|| taskRecord.threadRecords.isEmpty()
&& cacheDir.exists()) {
String[] files = cacheDir.list(new FilenameFilter() {
@Override public boolean accept(File dir, String name) {
return name.endsWith(".ts");
}
});
for (String fileName : files) {
PeerInfo peerInfo =
new PeerInfo(Integer.parseInt(fileName.substring(0, fileName.lastIndexOf(".ts"))),
getCacheDir().concat("/").concat(fileName));
peers.add(peerInfo);
}
return peers;
}
// 任务未完成的情况
if (taskRecord.threadRecords != null
&& !taskRecord.threadRecords.isEmpty()
&& cacheDir.exists()) {
for (ThreadRecord tr : taskRecord.threadRecords) {
if (!tr.isComplete) {
continue;
}
String peerPath = String.format("%s/%s.ts", cacheDir, tr.threadId);
if (new File(peerPath).exists()) {
PeerInfo peerInfo = new PeerInfo(tr.threadId, peerPath);
peers.add(peerInfo);
}
}
return peers;
}
return null;
}
public String getCacheDir() {
return cacheDir;
}
public void setCacheDir(String cacheDir) {
this.cacheDir = cacheDir;
}
public void setLive(boolean live) {
isLive = live;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public int getPeerIndex() {
return peerIndex;
}
public void setPeerIndex(int peerIndex) {
this.peerIndex = peerIndex;
}
public int getPeerNum() {
return peerNum;
}
public void setPeerNum(int peerNum) {
this.peerNum = peerNum;
}
public M3U8Entity() {
}
public static class PeerInfo {
public PeerInfo(int peerId, String peerPath) {
this.peerId = peerId;
this.peerPath = peerPath;
}
public int peerId;
public String peerPath;
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.filePath);
dest.writeInt(this.peerIndex);
dest.writeInt(this.peerNum);
dest.writeByte(this.isLive ? (byte) 1 : (byte) 0);
dest.writeString(this.cacheDir);
dest.writeString(this.keyPath);
dest.writeString(this.keyUrl);
dest.writeString(this.method);
dest.writeString(this.iv);
}
protected M3U8Entity(Parcel in) {
this.filePath = in.readString();
this.peerIndex = in.readInt();
this.peerNum = in.readInt();
this.isLive = in.readByte() != 0;
this.cacheDir = in.readString();
this.keyPath = in.readString();
this.keyUrl = in.readString();
this.method = in.readString();
this.iv = in.readString();
}
public static final Creator<M3U8Entity> CREATOR = new Creator<M3U8Entity>() {
@Override public M3U8Entity createFromParcel(Parcel source) {
return new M3U8Entity(source);
}
@Override public M3U8Entity[] newArray(int size) {
return new M3U8Entity[size];
}
};
}

View File

@ -0,0 +1,28 @@
/*
* 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.core.event;
/**
* 组合任务最大下载任务数事件
*/
public class DGMaxNumEvent {
public int maxNum;
public DGMaxNumEvent(int maxNum) {
this.maxNum = maxNum;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.core.event;
/**
* 最大下载任务数事件
*/
public class DMaxNumEvent {
public int maxNum;
public DMaxNumEvent(int maxNum) {
this.maxNum = maxNum;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.core.event;
public class DSpeedEvent {
public int speed;
public DSpeedEvent(int speed) {
this.speed = speed;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.core.event;
public class ErrorEvent {
public long taskId;
public String errorMsg;
public ErrorEvent(long taskId, String errorMsg) {
this.taskId = taskId;
this.errorMsg = errorMsg;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.core.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注解需要接收消息的方法,方法中只能有一个参数
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Event {
//Class value();
}

View File

@ -0,0 +1,32 @@
/*
* 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.core.event;
/**
* 事件消息信息
*/
class EventMethodInfo {
/**
* 被{@link Event}注解的事件方法
*/
String methodName;
/**
* 该方法对应的参数类型
*/
Class<?> param;
}

View File

@ -0,0 +1,153 @@
/*
* 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.core.event;
import com.arialyy.aria.util.ALog;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 消息发送工具
*/
public class EventMsgUtil {
private static final String TAG = "EventUtil";
private static EventMsgUtil defaultInstance;
private Map<Object, List<EventMethodInfo>> mEventMethods =
new ConcurrentHashMap<>();
private ArrayBlockingQueue<Object> mEventQueue = new ArrayBlockingQueue<>(10);
private ExecutorService mPool = Executors.newFixedThreadPool(5);
private EventMsgUtil() {
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(new Runnable() {
@Override public void run() {
while (true) {
try {
Object info = mEventQueue.take();
sendEvent(info);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
private void sendEvent(final Object param) {
mPool.submit(new Runnable() {
@Override public void run() {
Set<Object> keys = mEventMethods.keySet();
for (Object key : keys) {
List<EventMethodInfo> list = mEventMethods.get(key);
if (list != null && !list.isEmpty()) {
for (EventMethodInfo info : list) {
try {
if (info.param == param.getClass()) {
Method method = key.getClass().getDeclaredMethod(info.methodName, info.param);
method.setAccessible(true);
method.invoke(key, param);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
});
}
public static EventMsgUtil getDefault() {
if (defaultInstance == null) {
synchronized (EventMsgUtil.class) {
if (defaultInstance == null) {
defaultInstance = new EventMsgUtil();
}
}
}
return defaultInstance;
}
/**
* 注册事件
*/
public void register(Object obj) {
Method[] methods = obj.getClass().getDeclaredMethods();
for (Method method : methods) {
method.setAccessible(true);
if (method.getAnnotation(Event.class) == null) {
continue;
}
Class<?>[] clazz = method.getParameterTypes();
if (clazz.length == 0 || clazz.length > 1) {
ALog.e(TAG,
String.format("%s.%s参数数量为0或参数数量大于1", obj.getClass().getName(), method.getName()));
continue;
}
int modifier = method.getModifiers();
if (Modifier.isStatic(modifier) || Modifier.isAbstract(modifier) || Modifier.isFinal(
modifier)) {
ALog.e(TAG, "注册的方法不能使用final、static、abstract修饰");
continue;
}
EventMethodInfo methodInfo = new EventMethodInfo();
methodInfo.methodName = method.getName();
methodInfo.param = clazz[0];
List<EventMethodInfo> list = mEventMethods.get(obj);
if (list == null) {
list = new ArrayList<>();
mEventMethods.put(obj, list);
}
list.add(methodInfo);
}
}
public void unRegister(Object obj) {
for (Iterator<Map.Entry<Object, List<EventMethodInfo>>> iter =
mEventMethods.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<Object, List<EventMethodInfo>> entry = iter.next();
if (entry.getKey().equals(obj)) {
entry.getValue().clear();
iter.remove();
}
}
}
/**
* 发送事件,接收消息的方法需要使用{@link Event}注解
*/
public void post(Object param) {
synchronized (EventMsgUtil.class) {
try {
mEventQueue.offer(param, 2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.core.event;
public class PeerIndexEvent {
public int peerIndex;
public long createTime;
public String key;
public PeerIndexEvent(String key, int peerIndex) {
this.peerIndex = peerIndex;
this.key = key;
createTime = System.currentTimeMillis();
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.core.event;
/**
* 最大上传任务数事件
*/
public class UMaxNumEvent {
public int maxNum;
public UMaxNumEvent(int maxNum) {
this.maxNum = maxNum;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.core.event;
public class USpeedEvent {
public int speed;
public USpeedEvent(int speed) {
this.speed = speed;
}
}

View File

@ -0,0 +1,375 @@
/*
* 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.core.group;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IDGroupListener;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.loader.IInfoTask;
import com.arialyy.aria.core.loader.ILoader;
import com.arialyy.aria.core.loader.ILoaderVisitor;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.loader.IThreadTaskBuilder;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 组合任务加载器
*/
public abstract class AbsGroupLoader implements ILoaderVisitor, ILoader {
protected final String TAG = CommonUtil.getClassName(getClass());
private long mCurrentLocation = 0;
private IDGroupListener mListener;
private ScheduledThreadPoolExecutor mTimer;
private long mUpdateInterval;
private boolean isStop = false, isCancel = false;
private Handler mScheduler;
private SimpleSubQueue mSubQueue = SimpleSubQueue.newInstance();
private Map<String, AbsSubDLoadUtil> mExeLoader = new WeakHashMap<>();
private Map<String, DTaskWrapper> mCache = new WeakHashMap<>();
private DGTaskWrapper mGTWrapper;
private GroupRunState mState;
protected IInfoTask mInfoTask;
protected AbsGroupLoader(AbsTaskWrapper groupWrapper, IEventListener listener) {
mListener = (IDGroupListener) listener;
mGTWrapper = (DGTaskWrapper) groupWrapper;
mUpdateInterval = Configuration.getInstance().downloadCfg.getUpdateInterval();
}
/**
* 处理任务
*/
protected abstract void handlerTask(Looper looper);
/**
* 创建子任务加载器工具
*
* @param needGetFileInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息
*/
protected abstract AbsSubDLoadUtil createSubLoader(DTaskWrapper wrapper, boolean needGetFileInfo);
protected IDGroupListener getListener() {
return mListener;
}
protected DGTaskWrapper getWrapper() {
return mGTWrapper;
}
protected GroupRunState getState() {
return mState;
}
public Handler getScheduler() {
return mScheduler;
}
/**
* 初始化组合任务状态
*/
private void initState(Looper looper) {
mState = new GroupRunState(getWrapper().getKey(), mListener, mSubQueue);
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) {
long fileLen = checkFileExists(wrapper.getEntity().getFilePath());
if (wrapper.getEntity().getState() == IEntity.STATE_COMPLETE
&& fileLen > 0
&& fileLen == wrapper.getEntity().getFileSize()) {
//mState.updateCompleteNum();
mCurrentLocation += wrapper.getEntity().getFileSize();
} else {
if (fileLen <= 0) {
wrapper.getEntity().setCurrentProgress(0);
}
wrapper.getEntity().setState(IEntity.STATE_POST_PRE);
mCache.put(wrapper.getKey(), wrapper);
mCurrentLocation += wrapper.getEntity().getCurrentProgress();
}
}
if (getWrapper().getSubTaskWrapper().size() != mState.getCompleteNum()) {
getWrapper().setState(IEntity.STATE_POST_PRE);
}
mState.updateProgress(mCurrentLocation);
mScheduler = new Handler(looper, SimpleSchedulers.newInstance(mState, mGTWrapper.getKey()));
}
/**
* 检查文件是否存在,需要检查普通任务和分块任务的
*
* @param filePath 文件路径
* @return 文件存在返回文件长度,不存在返回-1
*/
private long checkFileExists(String filePath) {
File temp = new File(filePath);
if (temp.exists()) {
return temp.length();
}
File block = new File(String.format(IRecordHandler.SUB_PATH, filePath, 0));
if (block.exists()) {
return block.length();
} else {
return -1;
}
}
@Override public String getKey() {
return mGTWrapper.getKey();
}
/**
* 启动子任务下载
*
* @param url 子任务下载地址
*/
void startSubTask(String url) {
if (!checkSubTask(url, "开始")) {
return;
}
if (!mState.isRunning.get()) {
startTimer();
}
AbsSubDLoadUtil d = getDownloader(url, false);
if (d != null && !d.isRunning()) {
mSubQueue.startTask(d);
}
}
/**
* 停止子任务下载
*
* @param url 子任务下载地址
*/
void stopSubTask(String url) {
if (!checkSubTask(url, "停止")) {
return;
}
AbsSubDLoadUtil d = getDownloader(url, false);
if (d != null && d.isRunning()) {
mSubQueue.stopTask(d);
}
}
/**
* 检查子任务
*
* @param url 子任务url
* @param type 任务类型
* @return {@code true} 任务可以下载
*/
private boolean checkSubTask(String url, String type) {
DTaskWrapper wrapper = mCache.get(url);
if (wrapper != null) {
if (wrapper.getState() == IEntity.STATE_COMPLETE) {
ALog.w(TAG, "任务【" + url + "】已完成," + type + "失败");
return false;
}
} else {
ALog.w(TAG, "任务组中没有该任务【" + url + "】," + type + "失败");
return false;
}
return true;
}
/**
* 通过地址获取下载器
*
* @param url 子任务下载地址
*/
private AbsSubDLoadUtil getDownloader(String url, boolean needGetFileInfo) {
AbsSubDLoadUtil d = mExeLoader.get(url);
if (d == null) {
return createSubLoader(mCache.get(url), needGetFileInfo);
}
return d;
}
@Override public boolean isRunning() {
return mState != null && mState.isRunning.get();
}
@Override public void cancel() {
isCancel = true;
if (mInfoTask != null){
mInfoTask.cancel();
}
closeTimer();
mSubQueue.removeAllTask();
mListener.onCancel();
}
@Override public void stop() {
if (mInfoTask != null){
mInfoTask.stop();
}
isStop = true;
if (mSubQueue.getExecSize() == 0) {
mListener.onStop(mGTWrapper.getEntity().getCurrentProgress());
} else {
mSubQueue.stopAllTask();
}
closeTimer();
}
@Override public void run() {
checkComponent();
if (isStop || isCancel) {
closeTimer();
return;
}
startRunningFlow();
}
/**
* 开始进度流程
*/
private void startRunningFlow() {
closeTimer();
Looper.prepare();
Looper looper = Looper.myLooper();
if (looper == Looper.getMainLooper()) {
throw new IllegalThreadStateException("不能在主线程程序中调用Loader");
}
initState(looper);
getState().setSubSize(getWrapper().getSubTaskWrapper().size());
if (getState().getCompleteNum() != 0
&& getState().getCompleteNum() == getState().getSubSize()) {
mListener.onComplete();
return;
}
startTimer();
handlerTask(looper);
Looper.loop();
}
/**
* 组合任务获取完成子任务的信息后调用
*/
protected void onPostStart() {
if (isBreak()) {
return;
}
getListener().onPostPre(getWrapper().getEntity().getFileSize());
if (getWrapper().getEntity().getFileSize() > 0) {
getListener().onResume(getWrapper().getEntity().getCurrentProgress());
} else {
getListener().onStart(getWrapper().getEntity().getCurrentProgress());
}
}
private synchronized void startTimer() {
mState.isRunning.set(true);
mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
if (!mState.isRunning.get()) {
closeTimer();
} else if (mCurrentLocation >= 0) {
long t = 0;
for (DTaskWrapper te : mGTWrapper.getSubTaskWrapper()) {
if (te.getState() == IEntity.STATE_COMPLETE) {
t += te.getEntity().getFileSize();
} else {
t += te.getEntity().getCurrentProgress();
}
}
mCurrentLocation = t;
mState.updateProgress(mCurrentLocation);
mListener.onProgress(t);
}
}
}, 0, mUpdateInterval, TimeUnit.MILLISECONDS);
}
/**
* 启动子任务下载器
*/
protected void startSubLoader(AbsSubDLoadUtil loader) {
mExeLoader.put(loader.getKey(), loader);
mSubQueue.startTask(loader);
}
@Override public boolean isBreak() {
if (isCancel || isStop) {
//ALog.d(TAG, "isCancel = " + isCancel + ", isStop = " + isStop);
ALog.d(TAG, String.format("任务【%s】已停止或取消了", mGTWrapper.getKey()));
return true;
}
return false;
}
private synchronized void closeTimer() {
if (mTimer != null && !mTimer.isShutdown()) {
mTimer.shutdown();
}
}
protected void fail(AriaException e, boolean needRetry) {
closeTimer();
getListener().onFail(needRetry, e);
}
@Override public long getCurrentProgress() {
return mCurrentLocation;
}
/**
* @deprecated 组合任务不需要实现这个,记录交由其子任务处理
*/
@Deprecated
@Override public void addComponent(IRecordHandler recordHandler) {
}
/**
* @deprecated 组合任务不需要实现这个,线程创建交有子任务处理
*/
@Deprecated
@Override public void addComponent(IThreadTaskBuilder builder) {
}
/**
* @deprecated 组合任务不需要实现这个,其内部是一个子任务调度器,并不是线程状态管理器
*/
@Deprecated
@Override public void addComponent(IThreadStateManager threadState) {
}
/**
* 检查组件: {@link #mInfoTask}
*/
private void checkComponent() {
if (mInfoTask == null) {
throw new NullPointerException(("文件信息组件为空"));
}
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.core.group;
import com.arialyy.aria.core.inf.IUtil;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.loader.LoaderStructure;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/**
* Created by AriaL on 2017/6/30.
* 任务组核心逻辑
*/
public abstract class AbsGroupLoaderUtil implements IUtil {
protected String TAG = CommonUtil.getClassName(getClass());
private IEventListener mListener;
protected AbsGroupLoader mLoader;
private AbsTaskWrapper mTaskWrapper;
private boolean isStop = false, isCancel = false;
@Override public IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener) {
mTaskWrapper = taskWrapper;
mListener = listener;
mLoader = getLoader();
return this;
}
protected abstract AbsGroupLoader getLoader();
protected abstract LoaderStructure buildLoaderStructure();
public IEventListener getListener() {
return mListener;
}
public AbsTaskWrapper getTaskWrapper() {
return mTaskWrapper;
}
@Override public String getKey() {
return mTaskWrapper.getKey();
}
@Override public long getFileSize() {
return mTaskWrapper.getEntity().getFileSize();
}
@Override public long getCurrentLocation() {
return mLoader.getCurrentProgress();
}
@Override public boolean isRunning() {
return mLoader.isRunning();
}
public void startSubTask(String url) {
getLoader().startSubTask(url);
}
public void stopSubTask(String url) {
getLoader().stopSubTask(url);
}
/**
* 取消下载
*/
@Override public void cancel() {
isCancel = true;
mLoader.cancel();
}
/**
* 停止下载
*/
@Override public void stop() {
isStop = true;
mLoader.stop();
}
@Override public void start() {
if (isStop || isCancel) {
ALog.w(TAG, "启动组合任务失败,任务已停止或已取消");
return;
}
mListener.onPre();
buildLoaderStructure();
new Thread(mLoader).start();
}
}

View File

@ -0,0 +1,165 @@
/*
* 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.core.group;
import android.os.Handler;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IUtil;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.loader.LoaderStructure;
import com.arialyy.aria.core.loader.SubLoader;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/**
* 子任务下载器工具,需要在线程池中执行
*/
public abstract class AbsSubDLoadUtil implements IUtil, Runnable {
protected final String TAG = CommonUtil.getClassName(getClass());
protected SubLoader mDLoader;
private DTaskWrapper mWrapper;
private Handler mSchedulers;
private boolean needGetInfo;
private boolean isStop = false, isCancel = false;
private String parentKey;
/**
* @param schedulers 调度器
* @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息
*/
protected AbsSubDLoadUtil(Handler schedulers, boolean needGetInfo, String parentKey) {
mSchedulers = schedulers;
this.parentKey = parentKey;
this.needGetInfo = needGetInfo;
}
@Override public IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener) {
mWrapper = (DTaskWrapper) taskWrapper;
mDLoader = getLoader();
return this;
}
/**
* 创建加载器
*/
protected abstract SubLoader getLoader();
protected abstract LoaderStructure buildLoaderStructure();
public String getParentKey() {
return parentKey;
}
protected boolean isNeedGetInfo() {
return needGetInfo;
}
public Handler getSchedulers() {
return mSchedulers;
}
@Override public String getKey() {
return mDLoader.getKey();
}
public DTaskWrapper getWrapper() {
return mWrapper;
}
public DownloadEntity getEntity() {
return mWrapper.getEntity();
}
public TaskRecord getRecord(){
return getLoader().getRecord();
}
@Override public void run() {
if (isStop || isCancel) {
return;
}
buildLoaderStructure();
new Thread(mDLoader).start();
}
/**
* 请在线程池中使用
*/
@Deprecated
@Override public void start() {
throw new AssertionError("请在线程池中使用");
}
/**
* 重新开始任务
*/
void reStart() {
if (mDLoader != null) {
mDLoader.retryTask();
}
}
/**
* @deprecated 子任务不实现这个
*/
@Deprecated
@Override public long getFileSize() {
return -1;
}
/**
* 子任务不实现这个
*/
@Deprecated
@Override public long getCurrentLocation() {
return -1;
}
@Override public boolean isRunning() {
return mDLoader != null && mDLoader.isRunning();
}
@Override public void cancel() {
if (isCancel) {
ALog.w(TAG, "子任务已取消");
return;
}
isCancel = true;
if (mDLoader != null && isRunning()) {
mDLoader.cancel();
} else {
mSchedulers.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
}
}
@Override public void stop() {
if (isStop) {
ALog.w(TAG, "任务已停止");
return;
}
isStop = true;
if (mDLoader != null && isRunning()) {
mDLoader.stop();
} else {
mSchedulers.obtainMessage(ISchedulers.STOP, this).sendToTarget();
}
}
}

View File

@ -0,0 +1,189 @@
/*
* 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.core.group;
import com.arialyy.aria.core.listener.IDGroupListener;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 组合任务执行中的状态信息
*/
public final class GroupRunState {
private String TAG = "GroupRunState";
/**
* 子任务数
*/
private int mSubSize;
/**
* 已经完成的任务数
*/
private AtomicInteger mCompleteNum = new AtomicInteger();
/**
* 失败的任务数
*/
private AtomicInteger mFailNum = new AtomicInteger();
/**
* 停止的任务数
*/
private AtomicInteger mStopNum = new AtomicInteger();
/**
* 当前进度
*/
private long mProgress;
/**
* 组合任务监听
*/
IDGroupListener listener;
/**
* 子任务队列
*/
SimpleSubQueue queue;
/**
* 是否在执行
*/
AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* 子任务失败、停止记录,用于当子任务失败重新被用户点击开始时,更新{@link #mStopNum}或{@link #mFailNum}
* 保存的数据为子任务key
*/
private Set<String> mFailTemp = new HashSet<>(), mStopTemp = new HashSet<>();
private String mGroupHash;
GroupRunState(String groupHash, IDGroupListener listener, SimpleSubQueue queue) {
this.listener = listener;
this.queue = queue;
mGroupHash = groupHash;
}
public void setSubSize(int subSize) {
mSubSize = subSize;
}
/**
* 组合任务是否正在自行
*
* @return {@code true}组合任务正在执行
*/
public boolean isRunning() {
return isRunning.get();
}
public void setRunning(boolean running) {
isRunning.set(running);
}
String getGroupHash() {
return mGroupHash;
}
/**
* 获取组合任务子任务数
*/
public int getSubSize() {
return mSubSize;
}
/**
* 获取失败的数量
*/
public int getFailNum() {
return mFailNum.get();
}
/**
* 获取停止的数量
*/
public int getStopNum() {
return mStopNum.get();
}
/**
* 获取完成的数量
*/
public int getCompleteNum() {
return mCompleteNum.get();
}
/**
* 获取当前组合任务总进度
*/
public long getProgress() {
return mProgress;
}
/**
* 更新完成的数量mCompleteNum + 1
*/
public void updateCompleteNum() {
mCompleteNum.getAndIncrement();
}
/**
* 更新任务进度
*/
public void updateProgress(long newProgress) {
this.mProgress = newProgress;
}
/**
* 当子任务开始时,更新停止\失败的任务数
*
* @param key {@link AbsTaskWrapper#getKey()}
*/
public void updateCount(String key) {
if (mFailTemp.contains(key)) {
mFailTemp.remove(key);
mFailNum.getAndDecrement();
} else if (mStopTemp.contains(key)) {
mStopTemp.remove(key);
mStopNum.getAndDecrement();
}
}
/**
* 统计子任务停止的数量
*
* @param key {@link AbsTaskWrapper#getKey()}
*/
public void countStopNum(String key) {
mStopTemp.add(key);
mStopNum.getAndIncrement();
}
/**
* 统计子任务失败的数量
*
* @param key {@link AbsTaskWrapper#getKey()}
*/
public void countFailNum(String key) {
mFailTemp.add(key);
mFailNum.getAndIncrement();
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.core.group;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.task.AbsGroupTask;
/**
* Created by lyy on 2017/9/8.
* 任务组参数传递
*/
public final class GroupSendParams<GROUP_TASK extends AbsGroupTask, ENTITY extends AbsNormalEntity> {
public GROUP_TASK groupTask;
public ENTITY entity;
public GroupSendParams() {
}
public GroupSendParams(GROUP_TASK groupTask, ENTITY entity) {
this.groupTask = groupTask;
this.entity = entity;
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.core.group;
import com.arialyy.aria.core.loader.AbsNormalLoader;
import com.arialyy.aria.core.inf.IUtil;
import com.arialyy.aria.core.config.DGroupConfig;
/**
* 组合任务子任务队列
*
* @param <Fileer> {@link AbsNormalLoader}下载器
*/
interface ISubQueue<Fileer extends IUtil> {
/**
* 添加任务
*/
void addTask(Fileer fileer);
/**
* 开始任务
* 如果执行队列没有达到上限,则启动任务。
* 如果执行队列已经到达上限,则将任务添加到等待队列总。
* 队列上限配置{@link DGroupConfig#setSubMaxTaskNum(int)}
*/
void startTask(Fileer fileer);
/**
* 停止单个任务,如果缓存队列中有等待中的任务,则启动等待中的任务
*/
void stopTask(Fileer fileer);
/**
* 停止全部任务,停止所有正在执行的任务,并清空所有等待中的端服务
*/
void stopAllTask();
/**
* 修改最大任务数
*
* @param num 任务数不能小于1
*/
void modifyMaxExecNum(int num);
/**
* 从执行队列中移除任务,一般用于任务完成的情况
*/
void removeTaskFromExecQ(Fileer fileer);
/**
* 删除任务,如果缓存队列中有等待中的任务,则启动等待中的任务
*/
void removeTask(Fileer fileer);
/**
* 停止全部任务,停止所有正在执行的任务,并清空所有等待中的端服务
*/
void removeAllTask();
/**
* 获取下一个任务
*/
Fileer getNextTask();
/**
* 清空缓存队列和执行队列
*/
void clear();
}

View File

@ -0,0 +1,230 @@
/*
* 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.core.group;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.exception.ExceptionFactory;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.NetUtils;
import java.io.File;
/**
* 组合任务子任务调度器,用于调度任务的开始、停止、失败、完成等情况
* 该调度器生命周期和{@link AbsGroupLoaderUtil}生命周期一致
*/
final class SimpleSchedulers implements Handler.Callback {
private final String TAG = CommonUtil.getClassName(this);
private SimpleSubQueue mQueue;
private GroupRunState mGState;
private String mKey; // 组合任务的key
private SimpleSchedulers(GroupRunState state, String key) {
mQueue = state.queue;
mGState = state;
mKey = key;
}
static SimpleSchedulers newInstance(GroupRunState state, String key) {
return new SimpleSchedulers(state, key);
}
@Override public boolean handleMessage(Message msg) {
Bundle b = msg.getData();
if (b == null) {
ALog.w(TAG, "组合任务子任务调度数据为空");
return true;
}
String threadName = b.getString(IThreadStateManager.DATA_THREAD_NAME);
AbsSubDLoadUtil loaderUtil = mQueue.getLoaderUtil(threadName);
if (loaderUtil == null) {
ALog.e(TAG, String.format("子任务loader不存在state%skey%s", msg.what, threadName));
return true;
}
long curLocation = b.getLong(IThreadStateManager.DATA_THREAD_LOCATION,
loaderUtil.getLoader().getWrapper().getEntity().getCurrentProgress());
// 处理状态
switch (msg.what) {
case IThreadStateManager.STATE_RUNNING:
long range = (long) msg.obj;
mGState.listener.onSubRunning(loaderUtil.getEntity(), range);
break;
case IThreadStateManager.STATE_PRE:
mGState.listener.onSubPre(loaderUtil.getEntity());
mGState.updateCount(loaderUtil.getKey());
break;
case IThreadStateManager.STATE_START:
mGState.listener.onSubStart(loaderUtil.getEntity());
break;
case IThreadStateManager.STATE_STOP:
handleStop(loaderUtil, curLocation);
ThreadTaskManager.getInstance().removeSingleTaskThread(mKey, threadName);
break;
case IThreadStateManager.STATE_COMPLETE:
handleComplete(loaderUtil);
ThreadTaskManager.getInstance().removeSingleTaskThread(mKey, threadName);
break;
case IThreadStateManager.STATE_FAIL:
boolean needRetry = b.getBoolean(IThreadStateManager.DATA_RETRY, false);
handleFail(loaderUtil, needRetry);
ThreadTaskManager.getInstance().removeSingleTaskThread(mKey, threadName);
break;
}
return true;
}
/**
* 处理子任务失败的情况
* 1、子任务失败次数大于等于配置的重试次数才能认为子任务停止
* 2、stopNum + failNum + completeNum + cacheNum == subSize则认为组合任务停止
* 3、failNum == subSize只有全部的子任务都失败了才能任务组合任务失败
*
* @param needRetry true 需要重试false 不需要重试
*/
private synchronized void handleFail(final AbsSubDLoadUtil loaderUtil, boolean needRetry) {
Log.d(TAG, String.format("handleFail, size = %s, completeNum = %s, failNum = %s, stopNum = %s",
mGState.getSubSize(), mGState.getCompleteNum(), mGState.getFailNum(),
mGState.getSubSize()));
Configuration config = Configuration.getInstance();
int num = config.dGroupCfg.getSubReTryNum();
boolean isNotNetRetry = config.appCfg.isNotNetRetry();
if (!needRetry
|| (!NetUtils.isConnected(AriaConfig.getInstance().getAPP()) && !isNotNetRetry)
|| loaderUtil.getLoader() == null // 如果获取不到文件信息loader为空
|| loaderUtil.getEntity().getFailNum() > num) {
mQueue.removeTaskFromExecQ(loaderUtil);
mGState.listener.onSubFail(loaderUtil.getEntity(),
ExceptionFactory.getException(ExceptionFactory.TYPE_GROUP,
String.format("任务组子任务【%s】下载失败下载地址【%s】", loaderUtil.getEntity().getFileName(),
loaderUtil.getEntity().getUrl()), null));
mGState.countFailNum(loaderUtil.getKey());
if (mGState.getFailNum() == mGState.getSubSize()
|| mGState.getStopNum() + mGState.getFailNum() + mGState.getCompleteNum()
== mGState.getSubSize()) {
mGState.isRunning.set(false);
if (mGState.getCompleteNum() > 0
&& Configuration.getInstance().dGroupCfg.isSubFailAsStop()) {
ALog.e(TAG, String.format("任务组【%s】停止", mGState.getGroupHash()));
mGState.listener.onStop(mGState.getProgress());
return;
}
mGState.listener.onFail(false,
ExceptionFactory.getException(ExceptionFactory.TYPE_GROUP,
String.format("任务组【%s】下载失败", mGState.getGroupHash()), null));
return;
}
startNext();
return;
}
SimpleSubRetryQueue.getInstance().offer(loaderUtil);
}
/**
* 处理子任务停止的情况
* 1、所有的子任务已经停止则认为组合任务停止
* 2、completeNum + failNum + stopNum = subSize则认为组合任务停止
*/
private synchronized void handleStop(AbsSubDLoadUtil loadUtil, long curLocation) {
Log.d(TAG, String.format("handleStop, size = %s, completeNum = %s, failNum = %s, stopNum = %s",
mGState.getSubSize(), mGState.getCompleteNum(), mGState.getFailNum(),
mGState.getSubSize()));
mGState.listener.onSubStop(loadUtil.getEntity(), curLocation);
mGState.countStopNum(loadUtil.getKey());
if (mGState.getStopNum() == mGState.getSubSize()
|| mGState.getStopNum()
+ mGState.getCompleteNum()
+ mGState.getFailNum()
+ mQueue.getCacheSize()
== mGState.getSubSize()) {
mGState.isRunning.set(false);
mGState.listener.onStop(mGState.getProgress());
return;
}
startNext();
}
/**
* 处理子任务完成的情况,有以下三种情况
* 1、已经没有缓存的子任务并且停止的子任务是数{@link GroupRunState#getStopNum()} ()}为0失败的子任数{@link
* GroupRunState#getFailNum()}为0则认为组合任务已经完成
* 2、已经没有缓存的子任务并且停止的子任务是数{@link GroupRunState#getCompleteNum()}不为0或者失败的子任数{@link
* GroupRunState#getFailNum()}不为0则认为组合任务被停止
* 3、只有有缓存的子任务则任务组合任务没有完成
*/
private synchronized void handleComplete(AbsSubDLoadUtil loader) {
ALog.d(TAG, String.format("子任务【%s】完成", loader.getEntity().getFileName()));
Log.d(TAG,
String.format("handleComplete, size = %s, completeNum = %s, failNum = %s, stopNum = %s",
mGState.getSubSize(), mGState.getCompleteNum(), mGState.getFailNum(),
mGState.getStopNum()));
TaskRecord record = loader.getRecord();
if (record != null && record.isBlock) {
File partFile =
new File(String.format(IRecordHandler.SUB_PATH, record.filePath, 0));
partFile.renameTo(new File(record.filePath));
}
ThreadTaskManager.getInstance().removeTaskThread(loader.getKey());
mGState.listener.onSubComplete(loader.getEntity());
mQueue.removeTaskFromExecQ(loader);
mGState.updateCompleteNum();
if (mGState.getCompleteNum() + mGState.getFailNum() + mGState.getStopNum()
== mGState.getSubSize()) {
if (mGState.getStopNum() == 0 && mGState.getFailNum() == 0) {
mGState.listener.onComplete();
} else if (mGState.getStopNum() == 0
&& !Configuration.getInstance().dGroupCfg.isSubFailAsStop()) {
mGState.listener.onFail(false,
ExceptionFactory.getException(ExceptionFactory.TYPE_GROUP,
String.format("任务组【%s】下载失败", mGState.getGroupHash()), null));
} else {
mGState.listener.onStop(mGState.getProgress());
}
mGState.isRunning.set(false);
return;
}
startNext();
}
/**
* 如果有等待中的任务,则启动下一任务
*/
private void startNext() {
if (mQueue.isStopAll()) {
return;
}
AbsSubDLoadUtil next = mQueue.getNextTask();
if (next != null) {
ALog.d(TAG, String.format("启动任务:%s", next.getEntity().getFileName()));
mQueue.startTask(next);
return;
}
ALog.i(TAG, "没有下一子任务");
}
}

View File

@ -0,0 +1,201 @@
/*
* 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.core.group;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 组合任务队列,该队列生命周期和{@link AbsGroupLoaderUtil}生命周期一致
*/
final class SimpleSubQueue implements ISubQueue<AbsSubDLoadUtil> {
private final String TAG = CommonUtil.getClassName(getClass());
/**
* 缓存下载器
*/
private Map<String, AbsSubDLoadUtil> mCache = new ConcurrentHashMap<>();
/**
* 执行中的下载器
*/
private Map<String, AbsSubDLoadUtil> mExec = new ConcurrentHashMap<>();
/**
* 最大执行任务数
*/
private int mMaxExecSize;
/**
* 是否停止任务任务
*/
private boolean isStopAll = false;
private SimpleSubQueue() {
mMaxExecSize = Configuration.getInstance().dGroupCfg.getSubMaxTaskNum();
}
static SimpleSubQueue newInstance() {
return new SimpleSubQueue();
}
synchronized AbsSubDLoadUtil getLoaderUtil(String key) {
AbsSubDLoadUtil sub = mExec.get(key);
if (sub != null) {
return sub;
}
return mCache.get(key);
}
/**
* 获取缓存队列大小
*/
int getCacheSize() {
return mCache.size();
}
public int getExecSize(){
return mExec.size();
}
boolean isStopAll() {
return isStopAll;
}
@Override public void addTask(AbsSubDLoadUtil fileer) {
mCache.put(fileer.getKey(), fileer);
}
@Override public void startTask(AbsSubDLoadUtil fileer) {
if (mExec.size() < mMaxExecSize) {
mCache.remove(fileer.getKey());
mExec.put(fileer.getKey(), fileer);
ALog.d(TAG,
String.format("开始执行子任务:%skey: %s", fileer.getEntity().getFileName(), fileer.getKey()));
fileer.run();
return;
}
ALog.d(TAG, String.format("执行队列已满任务进入缓存器中key: %s", fileer.getKey()));
addTask(fileer);
}
@Override public void stopTask(AbsSubDLoadUtil fileer) {
fileer.stop();
mExec.remove(fileer.getKey());
}
@Override public void stopAllTask() {
isStopAll = true;
ALog.d(TAG, "停止组合任务");
mCache.clear();
Set<String> keys = mExec.keySet();
for (String key : keys) {
AbsSubDLoadUtil loader = mExec.get(key);
if (loader != null) {
ALog.d(TAG, String.format("停止子任务:%s", loader.getEntity().getFileName()));
loader.stop();
}
}
}
@Override public void modifyMaxExecNum(int num) {
if (num < 1) {
ALog.e(TAG, String.format("修改组合任务子任务队列数失败num: %s", num));
return;
}
if (num == mMaxExecSize) {
ALog.i(TAG, String.format("忽略此次修改oldSize: %s, num: %s", mMaxExecSize, num));
return;
}
int oldSize = mMaxExecSize;
mMaxExecSize = num;
int diff = Math.abs(oldSize - num);
if (oldSize < num) { // 处理队列变小的情况,该情况下将停止队尾任务,并将这些任务添加到缓存队列中
if (mExec.size() > num) {
Set<String> keys = mExec.keySet();
List<AbsSubDLoadUtil> caches = new ArrayList<>();
int i = 0;
for (String key : keys) {
if (i > num) {
caches.add(mExec.get(key));
}
i++;
}
Collection<AbsSubDLoadUtil> temp = mCache.values();
mCache.clear();
for (AbsSubDLoadUtil cache : caches) {
addTask(cache);
}
for (AbsSubDLoadUtil t : temp) {
addTask(t);
}
}
return;
}
// 处理队列变大的情况,该情况下将增加任务
if (mExec.size() < num) {
for (int i = 0; i < diff; i++) {
AbsSubDLoadUtil next = getNextTask();
if (next != null) {
startTask(next);
} else {
ALog.d(TAG, "子任务中没有缓存任务");
}
}
}
}
@Override public void removeTaskFromExecQ(AbsSubDLoadUtil fileer) {
mExec.remove(fileer.getKey());
}
@Override public void removeTask(AbsSubDLoadUtil fileer) {
removeTaskFromExecQ(fileer);
mCache.remove(fileer.getKey());
}
@Override public void removeAllTask() {
ALog.d(TAG, "删除组合任务");
Set<String> keys = mExec.keySet();
for (String key : keys) {
AbsSubDLoadUtil loader = mExec.get(key);
if (loader != null) {
ALog.d(TAG, String.format("停止子任务:%s", loader.getEntity().getFileName()));
loader.cancel();
}
}
}
@Override public AbsSubDLoadUtil getNextTask() {
Iterator<String> keys = mCache.keySet().iterator();
if (keys.hasNext()) {
return mCache.get(keys.next());
}
return null;
}
@Override public void clear() {
mCache.clear();
mExec.clear();
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.core.group;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 子任务重试队列
*/
final class SimpleSubRetryQueue {
private volatile static SimpleSubRetryQueue INSTANCE = null;
private ExecutorService pool = new ThreadPoolExecutor(5, 100,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
public synchronized static SimpleSubRetryQueue getInstance() {
if (INSTANCE == null) {
synchronized (SimpleSubRetryQueue.class) {
INSTANCE = new SimpleSubRetryQueue();
}
}
return INSTANCE;
}
private SimpleSubRetryQueue() {
}
void offer(AbsSubDLoadUtil subDLoadUtil) {
pool.submit(subDLoadUtil.getLoader());
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.core.group;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.RecordHelper;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.RecordUtil;
import java.util.ArrayList;
/**
* 子任务记录处理
*/
public class SubRecordHandler extends RecordHandler {
public SubRecordHandler(AbsTaskWrapper wrapper) {
super(wrapper);
}
@Override public void handlerTaskRecord(TaskRecord record) {
RecordHelper helper = new RecordHelper(getWrapper(), record);
if (getWrapper().isSupportBP() && record.threadNum > 1) {
if (record.isBlock) {
helper.handleBlockRecord();
} else {
helper.handleMultiRecord();
}
} else if (!getWrapper().isSupportBP()) {
helper.handleNoSupportBPRecord();
} else {
helper.handleSingleThreadRecord();
}
}
@Override
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
ThreadRecord tr;
tr = new ThreadRecord();
tr.taskKey = record.filePath;
tr.threadId = threadId;
tr.startLocation = startL;
tr.isComplete = false;
tr.threadType = record.taskType;
//最后一个线程的结束位置即为文件的总长度
if (threadId == (record.threadNum - 1)) {
endL = getFileSize();
}
tr.endLocation = endL;
tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum);
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;
int requestType = getWrapper().getRequestType();
if (requestType == ITaskWrapper.D_HTTP || requestType == ITaskWrapper.DG_HTTP) {
record.isBlock = Configuration.getInstance().downloadCfg.isUseBlock();
} else {
record.isBlock = false;
}
record.taskType = requestType;
record.isGroupRecord = getEntity().isGroupChild();
if (record.isGroupRecord) {
if (getEntity() instanceof DownloadEntity) {
record.dGroupHash = ((DownloadEntity) getEntity()).getGroupHash();
}
}
return record;
}
@Override public int initTaskThreadNum() {
int requestTpe = getWrapper().getRequestType();
if (requestTpe == ITaskWrapper.U_HTTP
|| (requestTpe == ITaskWrapper.D_HTTP && (!getWrapper().isSupportBP())
)) {
return 1;
}
int threadNum = Configuration.getInstance().downloadCfg.getThreadNum();
return getFileSize() <= IRecordHandler.SUB_LEN
? 1
: threadNum;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.core.inf;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventFlag {
}

View File

@ -0,0 +1,60 @@
/*
* 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.core.inf;
import com.arialyy.aria.orm.annotation.Ignore;
/**
* Created by lyy on 2017/2/23.
*/
public interface IEntity {
/**
* 其它状态
*/
@Ignore int STATE_OTHER = -1;
/**
* 失败状态
*/
@Ignore int STATE_FAIL = 0;
/**
* 完成状态
*/
@Ignore int STATE_COMPLETE = 1;
/**
* 停止状态
*/
@Ignore int STATE_STOP = 2;
/**
* 等待状态
*/
@Ignore int STATE_WAIT = 3;
/**
* 正在执行
*/
@Ignore int STATE_RUNNING = 4;
/**
* 预处理
*/
@Ignore int STATE_PRE = 5;
/**
* 预处理完成
*/
@Ignore int STATE_POST_PRE = 6;
/**
* 删除任务
*/
@Ignore int STATE_CANCEL = 7;
}

View File

@ -0,0 +1,25 @@
/*
* 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.core.inf;
/**
* 事件处理对象
*
* @author lyy
* Date: 2019-09-10
*/
public interface IEventHandler {
}

View File

@ -0,0 +1,52 @@
/*
* 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.core.inf;
/**
* @author lyy
* Date: 2019-09-10
*/
public interface IOptionConstant {
// ftp 任务设置常量
String ftpUrlEntity = "urlEntity";
String charSet = "charSet";
String clientConfig = "clientConfig";
String uploadInterceptor = "uploadInterceptor";
// http
String useServerFileName = "useServerFileName";
String requestEnum = "requestEnum";
String fileLenAdapter = "fileLenAdapter";
String params = "params";
String formFields = "formFields";
String headers = "headers";
String proxy = "proxy";
// m3u8 vod
String bandWidth = "bandWidth";
String cacheDir = "cacheDir";
String generateIndexFileTemp = "generateIndexFileTemp";
String bandWidthUrlConverter = "bandWidthUrlConverter";
String mergeFile = "mergeFile";
String mergeHandler = "mergeHandler";
String vodUrlConverter = "vodUrlConverter";
String maxTsQueueNum = "maxTsQueueNum";
String jumpIndex = "jumpIndex";
// m3u8 live
String liveTsUrlConverter = "liveTsUrlConverter";
String liveUpdateInterval = "liveUpdateInterval";
}

View File

@ -0,0 +1,24 @@
/*
* 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.core.inf;
/**
* Created by AriaL on 2017/6/29.
* 任务信息设置接口
*/
public interface ITaskOption {
}

View File

@ -0,0 +1,78 @@
/*
* 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.core.inf;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.loader.ILoaderComponent;
/**
* 线程任务状态
*/
public interface IThreadStateManager extends ILoaderComponent {
int STATE_STOP = 0x01;
int STATE_FAIL = 0x02;
int STATE_CANCEL = 0x03;
int STATE_COMPLETE = 0x04;
int STATE_RUNNING = 0x05;
int STATE_UPDATE_PROGRESS = 0x06;
int STATE_PRE = 0x07;
int STATE_START = 0x08;
String DATA_RETRY = "DATA_RETRY";
String DATA_ERROR_INFO = "DATA_ERROR_INFO";
String DATA_THREAD_NAME = "DATA_THREAD_NAME";
String DATA_THREAD_LOCATION = "DATA_THREAD_LOCATION";
String DATA_ADD_LEN = "DATA_ADD_LEN"; // 增加的长度
/**
* 任务是否已经失败
*
* @return true 任务已失败
*/
boolean isFail();
/**
* 任务是否已经完成
*
* @return true 任务已完成
*/
boolean isComplete();
/**
* 获取当前任务进度
*
* @return 任务当前进度
*/
long getCurrentProgress();
/**
* 更新当前进度
*
* @param currentProgress 当前进度
*/
void updateCurrentProgress(long currentProgress);
/**
* 设置消息循环体
*/
void setLooper(TaskRecord taskRecord, Looper looper);
/**
* 创建handler 回调
*/
Handler.Callback getHandlerCallback();
}

View File

@ -0,0 +1,72 @@
/*
* 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.core.inf;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/**
* Created by lyy on 2016/10/31.
* 任务功能接口
*/
public interface IUtil {
IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener);
/**
* 获取任务标志
*
* @return {@link DownloadEntity#getKey()}、{@link DownloadGroupEntity#getKey()}、{@link
* UploadEntity#getKey()}
*/
String getKey();
/**
* 获取文件大小
*/
long getFileSize();
/**
* 获取当前位置
*/
long getCurrentLocation();
/**
* 任务是否正在执行
*
* @return {@code true} 任务正在执行
*/
boolean isRunning();
/**
* 取消
*/
void cancel();
/**
* 停止
*/
void stop();
/**
* 开始
*/
void start();
}

View File

@ -0,0 +1,23 @@
/*
* 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.core.inf;
public interface Suggest {
String TASK_CONTROLLER = "after use #add()、#create()、#stop()、#cancel()、#resume()、#save()?";
String TO_CONTROLLER = "after use #controller()?";
}

View File

@ -0,0 +1,23 @@
package com.arialyy.aria.core.inf;
public interface TaskSchedulerType {
int TYPE_DEFAULT = 1;
/**
* 停止当前任务并且不自动启动下一任务
*/
int TYPE_STOP_NOT_NEXT = 2;
/**
* 停止任务并让当前任务处于等待状态
*/
int TYPE_STOP_AND_WAIT = 3;
/**
* 删除任务并且不通知回调
*/
int TYPE_CANCEL_AND_NOT_NOTIFY = 4;
/**
* 重置状态并启动任务
*/
int TYPE_START_AND_RESET_STATE = 5;
}

View File

@ -0,0 +1,60 @@
/*
* 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.core.listener;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.task.DownloadTask;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DeleteDRecord;
/**
* 下载监听类
*/
public class BaseDListener extends BaseListener implements IDLoadListener {
@Override
public void onPostPre(long fileSize) {
mEntity.setFileSize(fileSize);
mEntity.setConvertFileSize(CommonUtil.formatFileSize(fileSize));
saveData(IEntity.STATE_POST_PRE, -1);
sendInState2Target(ISchedulers.POST_PRE);
}
@Override
public void supportBreakpoint(boolean support) {
if (!support) {
sendInState2Target(ISchedulers.NO_SUPPORT_BREAK_POINT);
}
}
@Override protected void handleCancel() {
int sType = getTask(DownloadTask.class).getSchedulerType();
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
DeleteDRecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
//RecordUtil.delTaskRecord(mEntity.getFilePath(), IRecordHandler.TYPE_DOWNLOAD,
// mTaskWrapper.isRemoveFile(), false);
} else {
//RecordUtil.delTaskRecord(mEntity.getFilePath(), IRecordHandler.TYPE_DOWNLOAD,
// mTaskWrapper.isRemoveFile(), true);
DeleteDRecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
}
}
}

View File

@ -0,0 +1,195 @@
/*
* 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.core.listener;
import android.os.Handler;
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.task.AbsTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.ErrorHelp;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public abstract class BaseListener implements IEventListener {
protected String TAG = getClass().getSimpleName();
static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度
protected SoftReference<Handler> outHandler;
private long mLastLen; //上一次发送长度
private boolean isFirst = true;
private AbsTask mTask;
long mLastSaveTime;
protected AbsEntity mEntity;
protected AbsTaskWrapper mTaskWrapper;
private boolean isConvertSpeed;
private long mUpdateInterval;
@Override public IEventListener setParams(AbsTask task, Handler outHandler) {
this.outHandler = new SoftReference<>(outHandler);
mTask = new WeakReference<>(task).get();
mEntity = mTask.getTaskWrapper().getEntity();
mTaskWrapper = mTask.getTaskWrapper();
isConvertSpeed = mTaskWrapper.getConfig().isConvertSpeed();
mUpdateInterval = mTaskWrapper.getConfig().getUpdateInterval();
mLastLen = mEntity.getCurrentProgress();
mLastSaveTime = System.currentTimeMillis();
TAG = CommonUtil.getClassName(getClass());
return this;
}
protected <TASK extends AbsTask> TASK getTask(Class<TASK> clazz) {
return (TASK) mTask;
}
@Override public void onPre() {
saveData(IEntity.STATE_PRE, -1);
sendInState2Target(ISchedulers.PRE);
}
@Override public void onStart(long startLocation) {
saveData(IEntity.STATE_RUNNING, startLocation);
sendInState2Target(ISchedulers.START);
}
@Override public void onResume(long resumeLocation) {
saveData(IEntity.STATE_RUNNING, resumeLocation);
sendInState2Target(ISchedulers.RESUME);
}
@Override public void onProgress(long currentLocation) {
mEntity.setCurrentProgress(currentLocation);
long speed = currentLocation - mLastLen;
if (isFirst) {
speed = 0;
isFirst = false;
}
handleSpeed(speed);
sendInState2Target(ISchedulers.RUNNING);
if (System.currentTimeMillis() - mLastSaveTime >= RUN_SAVE_INTERVAL) {
saveData(IEntity.STATE_RUNNING, currentLocation);
mLastSaveTime = System.currentTimeMillis();
}
mLastLen = currentLocation;
}
@Override public void onStop(long stopLocation) {
saveData(mTask.getSchedulerType() == TaskSchedulerType.TYPE_STOP_AND_WAIT ? IEntity.STATE_WAIT
: IEntity.STATE_STOP, stopLocation);
handleSpeed(0);
sendInState2Target(ISchedulers.STOP);
}
@Override public void onComplete() {
saveData(IEntity.STATE_COMPLETE, mEntity.getFileSize());
handleSpeed(0);
sendInState2Target(ISchedulers.COMPLETE);
}
@Override public void onCancel() {
saveData(IEntity.STATE_CANCEL, -1);
handleSpeed(0);
if (mTask.getSchedulerType() != TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
ALog.d(TAG, "删除任务完成");
sendInState2Target(ISchedulers.CANCEL);
}
}
@Override public void onFail(boolean needRetry, AriaException e) {
mEntity.setFailNum(mEntity.getFailNum() + 1);
saveData(IEntity.STATE_FAIL, mEntity.getCurrentProgress());
handleSpeed(0);
mTask.setNeedRetry(needRetry);
mTask.putExpand(AbsTask.ERROR_INFO_KEY, e);
sendInState2Target(ISchedulers.FAIL);
if (e != null) {
String error = ALog.getExceptionString(e);
ALog.e(TAG, error);
ErrorHelp.saveError(e.getMessage(), error);
}
}
private void handleSpeed(long speed) {
if (mUpdateInterval != 1000) {
speed = speed * 1000 / mUpdateInterval;
}
if (isConvertSpeed) {
mEntity.setConvertSpeed(CommonUtil.formatFileSize(speed < 0 ? 0 : speed) + "/s");
}
mEntity.setSpeed(speed < 0 ? 0 : speed);
int taskType = mTaskWrapper.getRequestType();
if (taskType != ITaskWrapper.M3U8_VOD && taskType != ITaskWrapper.M3U8_LIVE) {
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));
}
}
}
/**
* 处理任务完成后的情况
*/
private void handleComplete() {
mEntity.setComplete(true);
mEntity.setCompleteTime(System.currentTimeMillis());
mEntity.setCurrentProgress(mEntity.getFileSize());
mEntity.setPercent(100);
handleSpeed(0);
}
/**
* 处理任务取消
*/
protected abstract void handleCancel();
/**
* 将任务状态发送给下载器
*
* @param state {@link ISchedulers#START}
*/
protected void sendInState2Target(int state) {
if (outHandler.get() != null) {
outHandler.get().obtainMessage(state, mTask).sendToTarget();
}
}
protected void saveData(int state, long location) {
mEntity.setState(state);
if (state == IEntity.STATE_CANCEL) {
handleCancel();
return;
} else if (state == IEntity.STATE_STOP) {
mEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
handleComplete();
}
if (location > 0) {
mEntity.setCurrentProgress(location);
}
mEntity.update();
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.core.listener;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.task.UploadTask;
import com.arialyy.aria.util.DeleteURecord;
/**
* 下载监听类
*/
public class BaseUListener extends BaseListener implements IUploadListener {
@Override protected void handleCancel() {
int sType = getTask(UploadTask.class).getSchedulerType();
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
DeleteURecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
} else {
DeleteURecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
}
}
}

View File

@ -0,0 +1,204 @@
/*
* 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.core.listener;
import android.os.Handler;
import com.arialyy.aria.core.download.DownloadEntity;
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.task.AbsTask;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DeleteDGRecord;
import com.arialyy.aria.util.ErrorHelp;
import static com.arialyy.aria.core.task.AbsTask.ERROR_INFO_KEY;
/**
* Created by Aria.Lao on 2017/7/20. 任务组下载事件
*/
public class DownloadGroupListener extends BaseListener implements IDGroupListener {
private GroupSendParams<DownloadGroupTask, DownloadEntity> mSeedEntity;
@Override public IEventListener setParams(AbsTask task, Handler outHandler) {
IEventListener listener = super.setParams(task, outHandler);
mSeedEntity = new GroupSendParams<>();
mSeedEntity.groupTask = (DownloadGroupTask) task;
return listener;
}
@Override
public void onSubPre(DownloadEntity subEntity) {
handleSubSpeed(subEntity, 0);
saveSubState(IEntity.STATE_PRE, subEntity);
sendInState2Target(ISchedulers.SUB_PRE, subEntity);
}
@Override
public void supportBreakpoint(boolean support, DownloadEntity subEntity) {
}
@Override
public void onSubStart(DownloadEntity subEntity) {
handleSubSpeed(subEntity, 0);
saveSubState(IEntity.STATE_RUNNING, subEntity);
sendInState2Target(ISchedulers.SUB_START, subEntity);
}
@Override
public void onSubStop(DownloadEntity subEntity, long stopLocation) {
subEntity.setCurrentProgress(stopLocation);
handleSubSpeed(subEntity, 0);
saveSubState(IEntity.STATE_STOP, subEntity);
saveCurrentLocation();
sendInState2Target(ISchedulers.SUB_STOP, subEntity);
}
@Override
public void onSubComplete(DownloadEntity subEntity) {
handleSubSpeed(subEntity, 0);
saveSubState(IEntity.STATE_COMPLETE, subEntity);
saveCurrentLocation();
sendInState2Target(ISchedulers.SUB_COMPLETE, subEntity);
}
@Override
public void onSubFail(DownloadEntity subEntity, AriaException e) {
handleSubSpeed(subEntity, 0);
saveSubState(IEntity.STATE_FAIL, subEntity);
saveCurrentLocation();
mSeedEntity.groupTask.putExpand(ERROR_INFO_KEY, e);
sendInState2Target(ISchedulers.SUB_FAIL, subEntity);
if (e != null) {
e.printStackTrace();
ErrorHelp.saveError("", ALog.getExceptionString(e));
}
}
@Override
public void onSubCancel(DownloadEntity subEntity) {
handleSubSpeed(subEntity, 0);
saveSubState(IEntity.STATE_CANCEL, subEntity);
saveCurrentLocation();
sendInState2Target(ISchedulers.SUB_CANCEL, subEntity);
}
@Override
public void onSubRunning(DownloadEntity subEntity, long currentProgress) {
handleSubSpeed(subEntity, currentProgress);
if (System.currentTimeMillis() - mLastSaveTime >= RUN_SAVE_INTERVAL) {
saveSubState(IEntity.STATE_RUNNING, subEntity);
mLastSaveTime = System.currentTimeMillis();
}
sendInState2Target(ISchedulers.SUB_RUNNING, subEntity);
}
private void handleSubSpeed(DownloadEntity subEntity, long currentProgress) {
if (currentProgress == 0) {
subEntity.setSpeed(0);
subEntity.setConvertSpeed("0kb/s");
return;
}
long speed = currentProgress - subEntity.getCurrentProgress();
subEntity.setSpeed(speed);
subEntity.setConvertSpeed(
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));
}
}
/**
* 将任务状态发送给下载器
*
* @param state {@link ISchedulers#START}
*/
private void sendInState2Target(int state, DownloadEntity subEntity) {
if (outHandler.get() != null) {
mSeedEntity.entity = subEntity;
outHandler.get().obtainMessage(state, ISchedulers.IS_SUB_TASK, 0, mSeedEntity).sendToTarget();
}
}
private void saveSubState(int state, DownloadEntity subEntity) {
subEntity.setState(state);
if (state == IEntity.STATE_STOP) {
subEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
subEntity.setComplete(true);
subEntity.setCompleteTime(System.currentTimeMillis());
subEntity.setCurrentProgress(subEntity.getFileSize());
subEntity.setPercent(100);
subEntity.setConvertSpeed("0kb/s");
subEntity.setSpeed(0);
}
subEntity.update();
}
private void saveCurrentLocation() {
DownloadGroupEntity dgEntity = (DownloadGroupEntity) mEntity;
if (dgEntity.getSubEntities() == null || dgEntity.getSubEntities().isEmpty()) {
ALog.w(TAG, "保存进度失败子任务为null");
return;
}
long location = 0;
for (DownloadEntity e : dgEntity.getSubEntities()) {
location += e.getCurrentProgress();
}
if (location > mEntity.getFileSize()) {
location = mEntity.getFileSize();
}
mEntity.setCurrentProgress(location);
mEntity.update();
}
@Override
public void onPostPre(long fileSize) {
mEntity.setFileSize(fileSize);
mEntity.setConvertFileSize(CommonUtil.formatFileSize(fileSize));
saveData(IEntity.STATE_POST_PRE, -1);
sendInState2Target(ISchedulers.POST_PRE);
}
@Override
public void supportBreakpoint(boolean support) {
}
@Override protected void handleCancel() {
int sType = getTask(DownloadGroupTask.class).getSchedulerType();
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
DeleteDGRecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
} else {
DeleteDGRecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
}
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.core.listener;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.exception.AriaException;
/**
* Created by Aria.Lao on 2017/7/20.
* 下载任务组事件
*/
public interface IDGroupListener extends IDLoadListener {
/**
* 子任务预处理
*/
void onSubPre(DownloadEntity subEntity);
/**
* 子任务支持断点回调
*
* @param support true,支持false 不支持
*/
void supportBreakpoint(boolean support, DownloadEntity subEntity);
/**
* 子任务开始下载\恢复下载
*/
void onSubStart(DownloadEntity subEntity);
/**
* 子任务停止下载
*/
void onSubStop(DownloadEntity subEntity, long stopLocation);
/**
* 子任务下载完成
*/
void onSubComplete(DownloadEntity subEntity);
/**
* 子任务下载失败
*/
void onSubFail(DownloadEntity subEntity, AriaException e);
/**
* 子任务取消下载
*/
void onSubCancel(DownloadEntity subEntity);
/**
* 子任务执行中
*
* @param currentProgress 当前进度
*/
void onSubRunning(DownloadEntity subEntity, long currentProgress);
}

View File

@ -0,0 +1,35 @@
/*
* 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.core.listener;
/**
* 下载监听
*/
public interface IDLoadListener extends IEventListener {
/**
* 预处理完成,准备下载---开始下载之间
*/
void onPostPre(long fileSize);
/**
* 支持断点回调
*
* @param support true,支持false 不支持
*/
void supportBreakpoint(boolean support);
}

View File

@ -0,0 +1,72 @@
/*
* 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.core.listener;
import android.os.Handler;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.exception.AriaException;
/**
* Created by Aria.Lao on 2017/7/18.
* 基础事件
*/
public interface IEventListener {
IEventListener setParams(AbsTask task, Handler outHandler);
/**
* 预处理有时有些地址链接比较慢这时可以先在这个地方出来一些界面上的UI如按钮的状态
*/
void onPre();
/**
* 开始
*/
void onStart(long startLocation);
/**
* 恢复位置
*/
void onResume(long resumeLocation);
/**
* 下载监听
*/
void onProgress(long currentLocation);
/**
* 停止
*/
void onStop(long stopLocation);
/**
* 下载完成
*/
void onComplete();
/**
* 取消下载
*/
void onCancel();
/**
* 下载失败
*
* @param needRetry 是否需要重试{@code true} 需要
* @param e 失败信息
*/
void onFail(boolean needRetry, AriaException e);
}

View File

@ -0,0 +1,185 @@
/*
* 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.core.listener;
import android.os.Handler;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.upload.UploadEntity;
/**
* Created by lyy on 2016/11/2. 调度器功能接口
*/
public interface ISchedulers extends Handler.Callback {
String ARIA_TASK_INFO_ACTION = "ARIA_TASK_INFO_ACTION";
/**
* 广播接收器中通过TASK_TYPE字段获取任务类型 {@link ITask#DOWNLOAD}、{@link ITask#DOWNLOAD_GROUP}、{@link
* ITask#UPLOAD}、{@link ITask#DOWNLOAD_GROUP_SUB}
*/
String TASK_TYPE = "ARIA_TASK_TYPE";
/**
* 广播接收器中通过TASK_STATE字段获取任务状态
*
* 普通任务的有: {@link #NO_SUPPORT_BREAK_POINT}、{@link #PRE}、{@link
* #POST_PRE}、{@link #START}、{@link #STOP}、{@link #FAIL}、{@link #CANCEL}、{@link #COMPLETE}、{@link
* #RUNNING}、{@link #RESUME}、{@link #WAIT}
*
* 子任务的有:{@link #SUB_PRE}、{@link #SUB_START}、{@link
* #SUB_STOP}、{@link #SUB_CANCEL}、{@link #SUB_FAIL}、{@link #SUB_RUNNING}、{@link #SUB_COMPLETE}
*/
String TASK_STATE = "ARIA_TASK_STATE";
/**
* 广播接收器中通过TASK_ENTITY字段获取任务实体 {@link DownloadEntity}、{@link UploadEntity}、{@link
* DownloadGroupEntity}
*/
String TASK_ENTITY = "ARIA_TASK_ENTITY";
/**
* 任务速度单位byte/s
*/
String TASK_SPEED = "ARIA_TASK_SPEED";
/**
* 任务进度
*/
String TASK_PERCENT = "ARIA_TASK_PERCENT";
/**
* M3U8地址
*/
String DATA_M3U8_URL = "DATA_M3U8_URL";
/**
* 当前下载完成的切片地址
*/
String DATA_M3U8_PEER_PATH = "DATA_M3U8_PEER_PATH";
/**
* 当前下载完成的切片索引
*/
String DATA_M3U8_PEER_INDEX = "DATA_M3U8_PEER_INDEX";
/**
* 组合任务任务标志
*/
int IS_SUB_TASK = 0xd1;
/**
* m3u8切片任务标志
*/
int IS_M3U8_PEER = 0xd2;
/**
* 任务不支持断点
*/
int NO_SUPPORT_BREAK_POINT = 9;
/**
* 任务预加载
*/
int PRE = 0;
/**
* 任务预加载完成
*/
int POST_PRE = 1;
/**
* 任务开始
*/
int START = 2;
/**
* 任务停止
*/
int STOP = 3;
/**
* 任务失败
*/
int FAIL = 4;
/**
* 任务取消
*/
int CANCEL = 5;
/**
* 任务完成
*/
int COMPLETE = 6;
/**
* 任务处理中
*/
int RUNNING = 7;
/**
* 恢复任务
*/
int RESUME = 8;
/**
* 等待
*/
int WAIT = 10;
/**
* 检查信息失败
*/
int CHECK_FAIL = 11;
/**
* 组合任务子任务预处理
*/
int SUB_PRE = 0xa1;
/**
* 组合任务子任务开始
*/
int SUB_START = 0xa2;
/**
* 组合任务子任务停止
*/
int SUB_STOP = 0xa3;
/**
* 组合任务子任务取消
*/
int SUB_CANCEL = 0xa4;
/**
* 组合任务子任务失败
*/
int SUB_FAIL = 0xa5;
/**
* 组合任务子任务执行执行中
*/
int SUB_RUNNING = 0xa6;
/**
* 组合任务子任务完成
*/
int SUB_COMPLETE = 0xa7;
/**
* M3U8切片开始下载
*/
int M3U8_PEER_START = 0xb1;
/**
* M3U8切片下载完成
*/
int M3U8_PEER_COMPLETE = 0xb2;
/**
* M3U8切片下载失败
*/
int M3U8_PEER_FAIL = 0xb3;
}

View File

@ -0,0 +1,24 @@
/*
* 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.core.listener;
/**
* Created by lyy on 2017/2/9.
* 上传监听
*/
public interface IUploadListener extends IEventListener {
}

View File

@ -0,0 +1,340 @@
/*
* 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.core.loader;
import android.os.Looper;
import android.util.Log;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by AriaL on 2017/7/1.
* 任务执行器,用于处理任务的开始,停止
* 流程:
* 1、获取任务记录
* 2、创建任务状态管理器用于管理任务的状态
* 3、创建文件信息获取器获取文件信息根据文件信息执行任务
* 4、创建线程任务执行下载、上传操作
*/
public abstract class AbsNormalLoader<T extends AbsTaskWrapper> implements ILoaderVisitor, ILoader {
protected final String TAG = CommonUtil.getClassName(getClass());
private IEventListener mListener;
protected T mTaskWrapper;
protected File mTempFile;
private List<IThreadTask> mTask = new ArrayList<>();
private ScheduledThreadPoolExecutor mTimer;
/**
* 进度刷新间隔
*/
private long mUpdateInterval = 1000;
protected TaskRecord mRecord;
protected boolean isCancel = false, isStop = false;
private boolean isRuning = false;
protected IRecordHandler mRecordHandler;
protected IThreadStateManager mStateManager;
protected IInfoTask mInfoTask;
protected IThreadTaskBuilder mTTBuilder;
protected AbsNormalLoader(T wrapper, IEventListener listener) {
mListener = listener;
mTaskWrapper = wrapper;
}
/**
* 启动线程任务
*/
protected abstract void handleTask(Looper looper);
/**
* 获取文件长度
*/
public abstract long getFileSize();
protected IEventListener getListener() {
return mListener;
}
protected IThreadStateManager getStateManager() {
return mStateManager;
}
public String getKey() {
return mTaskWrapper.getKey();
}
public List<IThreadTask> getTaskList() {
return mTask;
}
/**
* 重置任务状态
*/
private void resetState() {
closeTimer();
if (mTask != null && mTask.size() != 0) {
for (int i = 0; i < mTask.size(); i++) {
mTask.get(i).breakTask();
}
mTask.clear();
}
}
@Override public void run() {
checkComponent();
if (isRunning()) {
ALog.d(TAG, String.format("任务【%s】正在执行启动任务失败", mTaskWrapper.getKey()));
return;
}
startFlow();
}
/**
* 开始流程
*/
private void startFlow() {
if (isBreak()) {
return;
}
Looper.prepare();
Looper looper = Looper.myLooper();
if (looper == Looper.getMainLooper()) {
throw new IllegalThreadStateException("不能在主线程程序中调用Loader");
}
isRuning = true;
resetState();
onPostPre();
handleTask(looper);
Looper.loop();
}
/**
* 预处理完成
*/
protected void onPostPre() {
}
/**
* 延迟启动定时器
*/
protected long delayTimer() {
return 1000;
}
/**
* 启动进度获取定时器
*/
protected synchronized void startTimer() {
if (isBreak()) {
return;
}
ALog.d(TAG, String.format("启动定时器delayTimer = %s, updateInterval = %s", delayTimer(),
mUpdateInterval));
closeTimer();
try {
mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
// 线程池中是不抛异常的没有日志很难定位问题需要手动try-catch
try {
if (mStateManager == null) {
ALog.e(TAG, "stateManager is null");
} else if (mStateManager.isComplete()
|| mStateManager.isFail()
|| !isRunning()
|| isBreak()) {
//ALog.d(TAG, "isComplete = " + mStateManager.isComplete()
// + "; isFail = " + mStateManager.isFail()
// + "; isRunning = " + isRunning()
// + "; isBreak = " + isBreak());
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
closeTimer();
onDestroy();
} else if (mStateManager.getCurrentProgress() >= 0) {
Log.d(TAG, "running...");
mListener.onProgress(mStateManager.getCurrentProgress());
} else {
Log.d(TAG, "未知状态");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, delayTimer(), mUpdateInterval, TimeUnit.MILLISECONDS);
} catch (Exception e) {
ALog.e(TAG, "启动定时器失败");
e.printStackTrace();
}
}
private synchronized void closeTimer() {
if (mTimer != null && !mTimer.isShutdown()) {
mTimer.shutdown();
}
}
public void onDestroy() {
isRuning = false;
}
/**
* 设置定时器更新间隔
*
* @param interval 单位毫秒不能小于0
*/
protected void setUpdateInterval(long interval) {
if (interval < 0) {
ALog.w(TAG, "更新间隔不能小于0默认为1000毫秒");
return;
}
mUpdateInterval = interval;
}
@Override
public synchronized boolean isRunning() {
boolean b = ThreadTaskManager.getInstance().taskIsRunning(mTaskWrapper.getKey());
//ALog.d(TAG, "isRunning = " + b);
return b && isRuning;
}
@Override final public synchronized void cancel() {
if (isCancel) {
ALog.d(TAG, String.format("任务【%s】正在删除删除任务失败", mTaskWrapper.getKey()));
return;
}
if (mInfoTask != null){
mInfoTask.cancel();
}
closeTimer();
isCancel = true;
onCancel();
for (int i = 0; i < mTask.size(); i++) {
IThreadTask task = mTask.get(i);
if (task != null && !task.isThreadComplete()) {
task.cancel();
}
}
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
onPostCancel();
onDestroy();
mListener.onCancel();
}
/**
* 删除线程任务前的操作
*/
protected void onCancel() {
}
/**
* 删除操作处理完成
*/
protected void onPostCancel() {
}
final public synchronized void stop() {
if (isStop) {
return;
}
if (mInfoTask != null){
mInfoTask.stop();
}
closeTimer();
isStop = true;
onStop();
for (int i = 0; i < mTask.size(); i++) {
IThreadTask task = mTask.get(i);
if (task != null && !task.isThreadComplete()) {
task.stop();
}
}
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
onPostStop();
onDestroy();
mListener.onStop(getCurrentProgress());
}
/**
* 停止线程任务前的操作
*/
protected void onStop() {
}
/**
* 停止操作完成
*/
protected void onPostStop() {
}
/**
* 重试任务
*/
public void retryTask() {
ALog.w(TAG, String.format("任务【%s】开始重试", mTaskWrapper.getKey()));
startFlow();
}
/**
* 任务是否已经中断
*
* @return {@code true}中断
*/
public boolean isBreak() {
if (isCancel || isStop) {
//closeTimer();
ALog.d(TAG, "isCancel = " + isCancel + ", isStop = " + isStop);
ALog.d(TAG, String.format("任务【%s】已停止或取消了", mTaskWrapper.getKey()));
return true;
}
return false;
}
/**
* 检查组件: {@link #mRecordHandler}、{@link #mInfoTask}、{@link #mStateManager}、{@link #mTTBuilder}
*/
protected void checkComponent() {
if (mRecordHandler == null) {
throw new NullPointerException("任务记录组件为空");
}
if (mInfoTask == null) {
throw new NullPointerException(("文件信息组件为空"));
}
if (mStateManager == null) {
throw new NullPointerException("任务状态管理组件为空");
}
if (mTTBuilder == null) {
throw new NullPointerException("线程任务组件为空");
}
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.core.loader;
import com.arialyy.aria.core.inf.IUtil;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/**
* Created by lyy on 2015/8/25.
* HTTP\FTP单任务下载工具
*/
public abstract class AbsNormalLoaderUtil implements IUtil {
protected String TAG = CommonUtil.getClassName(getClass());
private IEventListener mListener;
protected AbsNormalLoader mLoader;
private AbsTaskWrapper mTaskWrapper;
private boolean isStop = false, isCancel = false;
protected AbsNormalLoaderUtil() {
}
@Override public IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener) {
mTaskWrapper = taskWrapper;
mListener = listener;
mLoader = getLoader();
return this;
}
/**
* 获取加载器
*/
public abstract AbsNormalLoader getLoader();
/**
* 获取构造器
*/
public abstract LoaderStructure BuildLoaderStructure();
@Override public String getKey() {
return mTaskWrapper.getKey();
}
@Override public long getFileSize() {
return mLoader.getFileSize();
}
/**
* 获取当前下载位置
*/
@Override public long getCurrentLocation() {
return mLoader.getCurrentProgress();
}
@Override public boolean isRunning() {
return mLoader.isRunning();
}
/**
* 取消下载
*/
@Override public void cancel() {
isCancel = true;
mLoader.cancel();
onCancel();
}
protected void onCancel() {
}
/**
* 停止下载
*/
@Override public void stop() {
isStop = true;
mLoader.stop();
onStop();
}
protected void onStop() {
}
/**
* 多线程断点续传下载文件,开始下载
*/
@Override public void start() {
if (isStop || isCancel) {
ALog.w(TAG, "启动任务失败,任务已停止或已取消");
return;
}
mListener.onPre();
// 如果网址没有变,而服务器端端文件改变,以下代码就没有用了
//if (mTaskWrapper.getEntity().getFileSize() <= 1
// || mTaskWrapper.isRefreshInfo()
// || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP
// || mTaskWrapper.getState() == IEntity.STATE_FAIL) {
// new Thread(createInfoThread()).create();
//} else {
// mDownloader.create();
//}
BuildLoaderStructure();
new Thread(mLoader).start();
onStart();
}
protected void onStart() {
}
public boolean isStop() {
return isStop;
}
public boolean isCancel() {
return isCancel;
}
public IEventListener getListener() {
return mListener;
}
protected void fail(AriaException e, boolean needRetry) {
if (isStop || isCancel) {
return;
}
mListener.onFail(needRetry, e);
mLoader.onDestroy();
}
public AbsTaskWrapper getTaskWrapper() {
return mTaskWrapper;
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.core.loader;
import android.os.Handler;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.task.IThreadTaskAdapter;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
public abstract class AbsNormalTTBuilderAdapter {
protected String TAG = CommonUtil.getClassName(this);
protected AbsTaskWrapper wrapper;
private File tempFile;
public AbsNormalTTBuilderAdapter() {
}
protected void setWrapper(AbsTaskWrapper wrapper) {
this.wrapper = wrapper;
tempFile = new File(((AbsNormalEntity) wrapper.getEntity()).getFilePath());
}
/**
* 创建线程任务适配器
*/
public abstract IThreadTaskAdapter getAdapter(SubThreadConfig config);
/**
* 处理新任务
*
* @param record 任务记录
* @param totalThreadNum 任务的线程总数
* @return {@code true}创建新任务成功
*/
public abstract boolean handleNewTask(TaskRecord record, int totalThreadNum);
/**
* SubThreadConfig 模版,如果不使用该方法创建配置,则默认使用{@link #createNormalSubThreadConfig(Handler, ThreadRecord,
* boolean, int)}创建配置
*/
protected SubThreadConfig getSubThreadConfig(Handler stateHandler, ThreadRecord threadRecord,
boolean isBlock, int startNum) {
return createNormalSubThreadConfig(stateHandler, threadRecord, isBlock, startNum);
}
private SubThreadConfig createNormalSubThreadConfig(Handler stateHandler,
ThreadRecord threadRecord,
boolean isBlock, int startNum) {
SubThreadConfig config = new SubThreadConfig();
config.url = getEntity().isRedirect() ? getEntity().getRedirectUrl() : getEntity().getUrl();
config.tempFile =
isBlock ? new File(
String.format(IRecordHandler.SUB_PATH, tempFile.getPath(), threadRecord.threadId))
: tempFile;
config.isBlock = isBlock;
config.startThreadNum = startNum;
config.taskWrapper = wrapper;
config.record = threadRecord;
config.stateHandler = stateHandler;
config.threadType = SubThreadConfig.getThreadType(wrapper.getRequestType());
config.updateInterval = SubThreadConfig.getUpdateInterval(wrapper.getRequestType());
return config;
}
protected AbsNormalEntity getEntity() {
return (AbsNormalEntity) wrapper.getEntity();
}
protected File getTempFile() {
return tempFile;
}
}

View File

@ -0,0 +1,313 @@
/*
* 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.core.loader;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程任务管理器,用于处理多线程下载时任务的状态回调
*/
public class GroupSubThreadStateManager implements IThreadStateManager {
private final String TAG = CommonUtil.getClassName(this);
/**
* 任务状态回调
*/
private Handler mHandler;//SimpleSchedulers
private int mThreadNum; // 启动的线程总数
private AtomicInteger mCancelNum = new AtomicInteger(0); // 已经取消的线程的数
private AtomicInteger mStopNum = new AtomicInteger(0); // 已经停止的线程数
private AtomicInteger mFailNum = new AtomicInteger(0); // 失败的线程数
private AtomicInteger mCompleteNum = new AtomicInteger(0); // 完成的线程数
private long mProgress; //当前总进度
private TaskRecord mTaskRecord; // 任务记录
private Looper mLooper;
private String mKey;
/**
* @param handler 任务事件
*/
public GroupSubThreadStateManager(Handler handler,String key) {
mHandler = handler;
mKey = key;
}
@Override public void setLooper(TaskRecord taskRecord, Looper looper) {
mTaskRecord = taskRecord;
mThreadNum = mTaskRecord.threadNum;
mLooper = looper;
}
private void checkLooper() {
if (mTaskRecord == null) {
throw new NullPointerException("任务记录为空");
}
if (mLooper == null) {
throw new NullPointerException("Looper为空");
}
}
private Handler.Callback callback = new Handler.Callback() {
@Override public boolean handleMessage(Message msg) {
checkLooper();
switch (msg.what) {
case STATE_STOP:
mStopNum.getAndIncrement();
if (isStop()) {
quitLooper();
}
sendMessageFromMsg(msg);
break;
case STATE_CANCEL:
mCancelNum.getAndIncrement();
if (isCancel()) {
quitLooper();
}
sendMessageFromMsg(msg);
break;
case STATE_FAIL:
mFailNum.getAndIncrement();
if (isFail()) {
sendMessageFromMsg(msg);
/* Bundle b = msg.getData();
mListener.onFail(b.getBoolean(DATA_RETRY, false),
(AriaException) b.getSerializable(DATA_ERROR_INFO));*/
quitLooper();
}
//sendMessageFromMsg(msg);
break;
case STATE_COMPLETE:
mCompleteNum.getAndIncrement();
if (isComplete()) {
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
//if (mTaskRecord.taskType == ITaskWrapper.D_SFTP) {
// mergerSFtp();
// mListener.onComplete();
//} else
if (mTaskRecord.isBlock) {
/*if (mergeFile()) {
mListener.onComplete();
} else {
mListener.onFail(false, null);
}*/
if (!mergeFile()) {
Bundle b=msg.getData();
b.putBoolean(IThreadStateManager.DATA_RETRY, false);
msg.setData(b);
msg.what = STATE_FAIL;
sendMessageFromMsg(msg);
}
sendMessageFromMsg(msg);
} else {
sendMessageFromMsg(msg);
//mListener.onComplete();
}
quitLooper();
}else if (isFail()) {
sendMessageFromMsg(msg);
quitLooper();
}
break;
case STATE_RUNNING:
Bundle b = msg.getData();
if (b != null) {
long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0);
mProgress += len;
}
msg.obj=mProgress;
sendMessageFromMsg(msg);
break;
case STATE_UPDATE_PROGRESS:
if (msg.obj == null) {
mProgress = updateBlockProgress();
} else if (msg.obj instanceof Long) {
mProgress = (long) msg.obj;
}
msg.obj=mProgress;
sendMessageFromMsg(msg);
break;
}
return false;
}
public void sendMessageFromMsg(Message msg){
Message mMsg=mHandler.obtainMessage();
Bundle b=mMsg.getData();
b.putString(IThreadStateManager.DATA_THREAD_NAME,mKey);
msg.setData(b);
mMsg.copyFrom(msg);
mHandler.sendMessage(mMsg);
}
};
@Override public void updateCurrentProgress(long currentProgress) {
mProgress = currentProgress;
}
/**
* 退出looper循环
*/
private void quitLooper() {
mLooper.quit();
}
/**
* 获取当前任务下载进度
*
* @return 当前任务下载进度
*/
@Override
public long getCurrentProgress() {
return mProgress;
}
@Override public Handler.Callback getHandlerCallback() {
return callback;
}
/**
* 所有子线程是否都已经停止
*/
public boolean isStop() {
//ALog.d(TAG,
// String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mStopNum.get() == mThreadNum || mStopNum.get() + mCompleteNum.get() == mThreadNum;
}
/**
* 所有子线程是否都已经失败
*/
@Override
public boolean isFail() {
//ALog.d(TAG,
// String.format("isFail; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum.get() != mThreadNum
&& (mFailNum.get() == mThreadNum || mFailNum.get() + mCompleteNum.get() == mThreadNum);
}
/**
* 所有子线程是否都已经完成
*/
@Override
public boolean isComplete() {
//ALog.d(TAG,
// String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum.get() == mThreadNum;
}
/**
* 所有子线程是否都已经取消
*/
public boolean isCancel() {
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCancelNum.get() == mThreadNum;
}
/**
* 更新分块任务s的真实进度
*/
private long updateBlockProgress() {
long size = 0;
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) {
File temp = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 合并sftp的分块
*/
private boolean mergerSFtp() {
if (mTaskRecord.threadNum == 1) {
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(new File(mTaskRecord.filePath));
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength);
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
return true;
}
/**
* 合并文件
*
* @return {@code true} 合并成功,{@code false}合并失败
*/
private boolean mergeFile() {
if (mTaskRecord.threadNum == 1) {
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(new File(mTaskRecord.filePath));
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败下载长度超出文件真实长度downloadLen: %sfileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
@Override public void accept(ILoaderVisitor visitor) {
visitor.addComponent(this);
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.core.loader;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.exception.AriaException;
/**
* 任务信息采集
*/
public interface IInfoTask extends ILoaderComponent {
/**
* 执行任务
*/
void run();
/**
* 设置回调
*/
void setCallback(Callback callback);
/**
* 任务停止
*/
void stop();
/**
* 任务取消
*/
void cancel();
interface Callback {
/**
* 处理完成
*
* @param info 一些回调的信息
*/
void onSucceed(String key, CompleteInfo info);
/**
* 请求失败
*
* @param e 错误信息
*/
void onFail(AbsEntity entity, AriaException e, boolean needRetry);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.loader;
public interface ILoader extends Runnable {
//void start();
/**
* 任务是否在执行
*
* @return true 任务执行中
*/
boolean isRunning();
void cancel();
void stop();
/**
* 任务是否被中断(停止,取消)
*
* @return true 任务中断false 任务没有中断
*/
boolean isBreak();
String getKey();
long getCurrentProgress();
}

View File

@ -0,0 +1,47 @@
/*
* 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.core.loader;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.common.SubThreadConfig;
/**
* @author lyy
* Date: 2019-09-18
*/
public interface ILoaderAdapter {
/**
* 处理新任务
*
* @param record 任务记录
* @param totalThreadNum 任务的线程总数
* @return {@code true}创建新任务成功
*/
boolean handleNewTask(TaskRecord record, int totalThreadNum);
/**
* 创建线程任务
*/
IThreadTask createThreadTask(SubThreadConfig config);
/**
* 处理任务记录
*/
IRecordHandler recordHandler(AbsTaskWrapper wrapper);
}

View File

@ -0,0 +1,25 @@
/*
* 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.core.loader;
/**
* 加载器部件
*/
public interface ILoaderComponent {
void accept(ILoaderVisitor visitor);
}

View File

@ -0,0 +1,44 @@
/*
* 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.core.loader;
import com.arialyy.aria.core.inf.IThreadStateManager;
/**
* 加载器访问者
*/
public interface ILoaderVisitor {
/**
* 处理任务记录
*/
void addComponent(IRecordHandler recordHandler);
/**
* 处理任务的文件信息
*/
void addComponent(IInfoTask infoTask);
/**
* 线程状态
*/
void addComponent(IThreadStateManager threadState);
/**
* 构造线程任务
*/
void addComponent(IThreadTaskBuilder builder);
}

View File

@ -0,0 +1,87 @@
/*
* 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.core.loader;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
/**
* @author lyy
* Date: 2019-09-18
*/
public interface IRecordHandler extends ILoaderComponent {
int TYPE_DOWNLOAD = 1;
int TYPE_UPLOAD = 2;
int TYPE_M3U8_VOD = 3;
int TYPE_M3U8_LIVE = 4;
String STATE = "_state_";
String RECORD = "_record_";
/**
* 小于1m的文件不启用多线程
*/
long SUB_LEN = 1024 * 1024;
/**
* 分块文件路径,文件路径.线程id.part
*/
String SUB_PATH = "%s.%s.part";
/**
* 获取任务记录
*/
TaskRecord getRecord(long fileSize);
/**
* 记录处理前的操作,可用来删除任务记录
*/
void onPre();
/**
* 处理任务记录
*/
void handlerTaskRecord(TaskRecord record);
/**
* 处理线程任务
*
* @param record 任务记录
* @param threadId 线程id
* @param startL 线程开始位置
* @param endL 线程结束位置
*/
ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL);
/**
* 新任务创建任务记录
*/
TaskRecord createTaskRecord(int threadNum);
/**
* 配置新任务的线程数
*
* @return 新任务的线程数
*/
int initTaskThreadNum();
/**
* 检查任务是否已完成
*
* @return true 任务已完成
*/
boolean checkTaskCompleted();
}

View File

@ -0,0 +1,37 @@
/*
* 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.core.loader;
import android.os.Handler;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.task.IThreadTask;
import java.util.List;
/**
* 线程任务构造器
*/
public interface IThreadTaskBuilder extends ILoaderComponent {
/**
* 构造线程任务
*/
List<IThreadTask> buildThreadTask(TaskRecord record, Handler stateHandler);
/**
* 获取创建的线程任务数,需要先调用{@link #buildThreadTask(TaskRecord, Handler)}方法才能获取创建的线程任务数
*/
int getCreatedThreadNum();
}

View File

@ -0,0 +1,45 @@
/*
* 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.core.loader;
import com.arialyy.aria.core.inf.IThreadStateManager;
import java.util.ArrayList;
import java.util.List;
public class LoaderStructure {
private List<ILoaderComponent> parts = new ArrayList<>();
public void accept(ILoaderVisitor visitor) {
for (ILoaderComponent part : parts) {
part.accept(visitor);
}
}
/**
* 将组件加入到集合,必须添加以下集合:
* 1 {@link IRecordHandler}
* 2 {@link IInfoTask}
* 3 {@link IThreadStateManager}
* 4 {@link IThreadTaskBuilder}
*
* @param component 待添加的组件
*/
public LoaderStructure addComponent(ILoaderComponent component) {
parts.add(component);
return this;
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.core.loader;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IDLoadListener;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.util.List;
/**
* 单文件
*/
public class NormalLoader<T extends AbsTaskWrapper> extends AbsNormalLoader<T> {
private int startThreadNum; //启动的线程数
protected boolean isComplete = false;
private Looper looper;
public NormalLoader(T wrapper, IEventListener listener) {
super(wrapper, listener);
mTempFile = new File(getEntity().getFilePath());
EventMsgUtil.getDefault().register(this);
setUpdateInterval(wrapper.getConfig().getUpdateInterval());
}
public AbsNormalEntity getEntity() {
return (AbsNormalEntity) mTaskWrapper.getEntity();
}
@Override public long getFileSize() {
return getEntity().getFileSize();
}
/**
* 设置最大下载/上传速度AbsFtpInfoThread
*
* @param maxSpeed 单位为kb
*/
protected void setMaxSpeed(int maxSpeed) {
for (IThreadTask threadTask : getTaskList()) {
if (threadTask != null && startThreadNum > 0) {
threadTask.setMaxSpeed(maxSpeed / startThreadNum);
}
}
}
@Override public void onDestroy() {
super.onDestroy();
EventMsgUtil.getDefault().unRegister(this);
}
///**
// * 如果使用"Content-Disposition"中的文件名,需要更新{@link #mTempFile}的路径
// */
//public void updateTempFile() {
// if (!mTempFile.getPath().equals(getEntity().getFilePath())) {
// boolean b = mTempFile.renameTo(new File(getEntity().getFilePath()));
// ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败"));
// }
//}
protected Looper getLooper() {
return looper;
}
/**
* 启动单线程任务
*/
@Override
public void handleTask(Looper looper) {
if (isBreak() || isComplete) {
return;
}
this.looper = looper;
mInfoTask.run();
}
protected void startThreadTask() {
if (isBreak()){
return;
}
if (getListener() instanceof IDLoadListener) {
((IDLoadListener) getListener()).onPostPre(getEntity().getFileSize());
}
File file = new File(getEntity().getFilePath());
if (file.getParentFile() != null && !file.getParentFile().exists()) {
FileUtil.createDir(file.getPath());
}
// 处理记录、初始化状态管理器
mRecord = mRecordHandler.getRecord(getFileSize());
mStateManager.setLooper(mRecord, looper);
getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord,
new Handler(looper, mStateManager.getHandlerCallback())));
startThreadNum = mTTBuilder.getCreatedThreadNum();
mStateManager.updateCurrentProgress(getEntity().getCurrentProgress());
if (mStateManager.getCurrentProgress() > 0) {
getListener().onResume(mStateManager.getCurrentProgress());
} else {
getListener().onStart(mStateManager.getCurrentProgress());
}
// 启动线程任务
for (IThreadTask threadTask : getTaskList()) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask);
}
// 启动定时器
startTimer();
}
@Override public long getCurrentProgress() {
return isRunning() ? mStateManager.getCurrentProgress() : getEntity().getCurrentProgress();
}
@Override public void addComponent(IRecordHandler recordHandler) {
mRecordHandler = recordHandler;
if (recordHandler.checkTaskCompleted()) {
mRecord.deleteData();
isComplete = true;
getListener().onComplete();
}
}
@Override public void addComponent(IInfoTask infoTask) {
mInfoTask = infoTask;
infoTask.setCallback(new IInfoTask.Callback() {
@Override public void onSucceed(String key, CompleteInfo info) {
startThreadTask();
}
@Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) {
getListener().onFail(needRetry, e);
}
});
}
@Override public void addComponent(IThreadStateManager threadState) {
mStateManager = threadState;
}
@Override public void addComponent(IThreadTaskBuilder builder) {
mTTBuilder = builder;
}
}

View File

@ -0,0 +1,166 @@
package com.arialyy.aria.core.loader;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.task.ThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.util.ArrayList;
import java.util.List;
public final class NormalTTBuilder implements IThreadTaskBuilder {
protected String TAG = CommonUtil.getClassName(this);
private Handler mStateHandler;
private AbsTaskWrapper mWrapper;
private TaskRecord mRecord;
private int mTotalThreadNum;
private int mStartThreadNum;
private AbsNormalTTBuilderAdapter mAdapter;
public NormalTTBuilder(AbsTaskWrapper wrapper, AbsNormalTTBuilderAdapter adapter) {
if (wrapper instanceof DGTaskWrapper) {
throw new AssertionError("NormalTTBuilder 不适用于组合任务");
}
mWrapper = wrapper;
mAdapter = adapter;
mAdapter.setWrapper(wrapper);
}
protected AbsNormalEntity getEntity() {
return (AbsNormalEntity) mWrapper.getEntity();
}
public AbsNormalTTBuilderAdapter getAdapter() {
return mAdapter;
}
/**
* 创建线程任务
*/
private IThreadTask createThreadTask(SubThreadConfig config) {
ThreadTask task = new ThreadTask(config);
task.setAdapter(mAdapter.getAdapter(config));
return task;
}
/**
* 启动断点任务时,创建单线程任务
*
* @param record 线程记录
* @param startNum 启动的线程数
*/
private IThreadTask createSingThreadTask(ThreadRecord record, int startNum) {
return createThreadTask(
mAdapter.getSubThreadConfig(mStateHandler, record, mRecord.isBlock, startNum));
}
/**
* 处理不支持断点的任务
*/
private List<IThreadTask> handleNoSupportBP() {
List<IThreadTask> list = new ArrayList<>();
mStartThreadNum = 1;
mRecord.isBlock = false;
mRecord.update();
IThreadTask task = createSingThreadTask(mRecord.threadRecords.get(0), 1);
list.add(task);
return list;
}
/**
* 处理支持断点的任务
*/
private List<IThreadTask> handleBreakpoint() {
long fileLength = getEntity().getFileSize();
long blockSize = fileLength / mTotalThreadNum;
long currentProgress = 0;
List<IThreadTask> threadTasks = new ArrayList<>(mTotalThreadNum);
mRecord.fileLength = fileLength;
if (mWrapper.isNewTask() && !mAdapter.handleNewTask(mRecord, mTotalThreadNum)) {
ALog.e(TAG, "初始化线程任务失败");
return null;
}
for (ThreadRecord tr : mRecord.threadRecords) {
if (!tr.isComplete) {
mStartThreadNum++;
}
}
for (int i = 0; i < mTotalThreadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr = mRecord.threadRecords.get(i);
if (tr.isComplete) {//该线程已经完成
currentProgress += endL - startL;
ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mWrapper.getKey(), i));
Message msg = mStateHandler.obtainMessage();
msg.what = IThreadStateManager.STATE_COMPLETE;
Bundle b = msg.getData();
if (b == null) {
b = new Bundle();
}
b.putString(IThreadStateManager.DATA_THREAD_NAME,
CommonUtil.getThreadName(getEntity().getKey(), tr.threadId));
msg.setData(b);
msg.sendToTarget();
continue;
}
//如果有记录,则恢复任务
long r = tr.startLocation;
//记录的位置需要在线程区间中
if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) {
currentProgress += r - startL;
}
ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", getEntity().getFileName(), i));
IThreadTask task = createSingThreadTask(tr, mStartThreadNum);
if (task == null) {
ALog.e(TAG, "创建线程任务失败");
return null;
}
threadTasks.add(task);
}
if (currentProgress != getEntity().getCurrentProgress()) {
ALog.d(TAG, String.format("进度修正,当前进度:%s", currentProgress));
getEntity().setCurrentProgress(currentProgress);
}
//mStateManager.updateProgress(currentProgress);
return threadTasks;
}
private List<IThreadTask> handleTask() {
if (mWrapper.isSupportBP()) {
return handleBreakpoint();
} else {
return handleNoSupportBP();
}
}
@Override public List<IThreadTask> buildThreadTask(TaskRecord record, Handler stateHandler) {
mRecord = record;
mStateHandler = stateHandler;
mTotalThreadNum = mRecord.threadNum;
return handleTask();
}
@Override public int getCreatedThreadNum() {
return mStartThreadNum;
}
@Override public void accept(ILoaderVisitor visitor) {
visitor.addComponent(this);
}
}

View File

@ -0,0 +1,291 @@
/*
* 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.core.loader;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程任务管理器,用于处理多线程下载时任务的状态回调
*/
public class NormalThreadStateManager implements IThreadStateManager {
private final String TAG = CommonUtil.getClassName(this);
/**
* 任务状态回调
*/
private IEventListener mListener;
private int mThreadNum; // 启动的线程总数
private AtomicInteger mCancelNum = new AtomicInteger(0); // 已经取消的线程的数
private AtomicInteger mStopNum = new AtomicInteger(0); // 已经停止的线程数
private AtomicInteger mFailNum = new AtomicInteger(0); // 失败的线程数
private AtomicInteger mCompleteNum = new AtomicInteger(0); // 完成的线程数
private long mProgress; //当前总进度
private TaskRecord mTaskRecord; // 任务记录
private Looper mLooper;
/**
* @param listener 任务事件
*/
public NormalThreadStateManager(IEventListener listener) {
mListener = listener;
}
@Override public void setLooper(TaskRecord taskRecord, Looper looper) {
mTaskRecord = taskRecord;
mThreadNum = mTaskRecord.threadNum;
mLooper = looper;
}
private void checkLooper() {
if (mTaskRecord == null) {
throw new NullPointerException("任务记录为空");
}
if (mLooper == null) {
throw new NullPointerException("Looper为空");
}
}
private Handler.Callback callback = new Handler.Callback() {
@Override public boolean handleMessage(Message msg) {
checkLooper();
switch (msg.what) {
case STATE_STOP:
mStopNum.getAndIncrement();
if (isStop()) {
quitLooper();
}
break;
case STATE_CANCEL:
mCancelNum.getAndIncrement();
if (isCancel()) {
quitLooper();
}
break;
case STATE_FAIL:
mFailNum.getAndIncrement();
if (isFail()) {
Bundle b = msg.getData();
mListener.onFail(b.getBoolean(DATA_RETRY, false),
(AriaException) b.getSerializable(DATA_ERROR_INFO));
quitLooper();
}
break;
case STATE_COMPLETE:
mCompleteNum.getAndIncrement();
if (isComplete()) {
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
//if (mTaskRecord.taskType == ITaskWrapper.D_SFTP) {
// mergerSFtp();
// mListener.onComplete();
//} else
if (mTaskRecord.isBlock || mTaskRecord.threadNum == 1) {
if (mergeFile()) {
mListener.onComplete();
} else {
mListener.onFail(false, null);
}
quitLooper();
break;
}
mListener.onComplete();
quitLooper();
}
break;
case STATE_RUNNING:
Bundle b = msg.getData();
if (b != null) {
long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0);
mProgress += len;
}
break;
case STATE_UPDATE_PROGRESS:
if (msg.obj == null) {
mProgress = updateBlockProgress();
} else if (msg.obj instanceof Long) {
mProgress = (long) msg.obj;
}
break;
}
return false;
}
};
@Override public void updateCurrentProgress(long currentProgress) {
mProgress = currentProgress;
}
/**
* 退出looper循环
*/
private void quitLooper() {
mLooper.quit();
}
/**
* 获取当前任务下载进度
*
* @return 当前任务下载进度
*/
@Override
public long getCurrentProgress() {
return mProgress;
}
@Override public Handler.Callback getHandlerCallback() {
return callback;
}
/**
* 所有子线程是否都已经停止
*/
public boolean isStop() {
//ALog.d(TAG,
// String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mStopNum.get() == mThreadNum || mStopNum.get() + mCompleteNum.get() == mThreadNum;
}
/**
* 所有子线程是否都已经失败
*/
@Override
public boolean isFail() {
//ALog.d(TAG,
// String.format("isFail; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum.get() != mThreadNum
&& (mFailNum.get() == mThreadNum || mFailNum.get() + mCompleteNum.get() == mThreadNum);
}
/**
* 所有子线程是否都已经完成
*/
@Override
public boolean isComplete() {
//ALog.d(TAG,
// String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum.get() == mThreadNum;
}
/**
* 所有子线程是否都已经取消
*/
public boolean isCancel() {
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCancelNum.get() == mThreadNum;
}
/**
* 更新分块任务s的真实进度
*/
private long updateBlockProgress() {
long size = 0;
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) {
File temp = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 合并sftp的分块
*/
private boolean mergerSFtp() {
if (mTaskRecord.threadNum == 1) {
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(new File(mTaskRecord.filePath));
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength);
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
return true;
}
/**
* 合并文件
*
* @return {@code true} 合并成功,{@code false}合并失败
*/
private boolean mergeFile() {
if (mTaskRecord.threadNum == 1) {
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists()){
//没有获得文件长度:不支持断点续传
if (mTaskRecord.fileLength == 0 && targetFile.length() != 0) {
return true;
}
if (targetFile.length() != 0 && targetFile.length() == mTaskRecord.fileLength) {
return true;
}
}
FileUtil.deleteFile(targetFile);
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(targetFile);
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败下载长度超出文件真实长度downloadLen: %sfileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
@Override public void accept(ILoaderVisitor visitor) {
visitor.addComponent(this);
}
}

View File

@ -0,0 +1,291 @@
/*
* 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.core.loader;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.task.ThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 子任务加载器
*/
public final class SubLoader implements ILoader, ILoaderVisitor {
private String TAG = CommonUtil.getClassName(this);
// 是否需要获取信息
private boolean needGetInfo = true;
private Handler schedulers;
private boolean isCancel = false, isStop = false;
private AbsTaskWrapper wrapper;
private IInfoTask infoTask;
private IThreadTaskBuilder ttBuild;
private IRecordHandler recordHandler;
private List<IThreadTask> mTask = new ArrayList<>();
private String parentKey;
private TaskRecord record;
protected IThreadStateManager mStateManager;
public SubLoader(AbsTaskWrapper wrapper, Handler schedulers) {
this.wrapper = wrapper;
this.schedulers = schedulers;
}
public AbsTaskWrapper getWrapper() {
return wrapper;
}
/**
* 发送状态到调度器
*
* @param state {@link IThreadStateManager}
*/
private void sendNormalState(int state) {
Message msg = schedulers.obtainMessage();
Bundle b = msg.getData();
if (b == null) {
b = new Bundle();
}
b.putString(IThreadStateManager.DATA_THREAD_NAME, getKey());
msg.what = state;
msg.setData(b);
msg.sendToTarget();
}
/**
* 发送失败的状态
*/
private void sendFailState(boolean needRetry) {
Message msg = schedulers.obtainMessage();
Bundle b = msg.getData();
if (b == null) {
b = new Bundle();
}
b.putString(IThreadStateManager.DATA_THREAD_NAME, getKey());
b.putBoolean(IThreadStateManager.DATA_RETRY, needRetry);
msg.what = IThreadStateManager.STATE_FAIL;
msg.setData(b);
msg.sendToTarget();
}
private void handlerTask() {
if (isBreak()) {
return;
}
Looper looper = Looper.myLooper();
if (looper == null) {
Looper.prepare();
looper = Looper.myLooper();
}
record = recordHandler.getRecord(wrapper.getEntity().getFileSize());
if (record == null) {
ALog.d(TAG, "子任务记录为空");
sendFailState(false);
return;
}
if (record.threadRecords != null
&& !TextUtils.isEmpty(record.filePath)
&& new File(record.filePath).exists()
&& !record.threadRecords.isEmpty()
&& record.threadRecords.get(0).isComplete) {
ALog.d(TAG, "子任务已完成key" + wrapper.getKey());
sendNormalState(IThreadStateManager.STATE_COMPLETE);
return;
}
List<IThreadTask> task =
ttBuild.buildThreadTask(record, new Handler(looper, mStateManager.getHandlerCallback()));
mStateManager.setLooper(record, looper);
if (task == null || task.isEmpty()) {
ALog.e(TAG, "创建子任务的线程任务失败key" + wrapper.getKey());
sendFailState(false);
return;
}
if (TextUtils.isEmpty(parentKey)) {
ALog.e(TAG, "parentKey为空");
sendFailState(false);
return;
}
sendNormalState(IThreadStateManager.STATE_PRE);
mTask.addAll(task);
try {
for (IThreadTask iThreadTask : mTask) {
ThreadTaskManager.getInstance().startThread(parentKey, iThreadTask);
}
sendNormalState(IThreadStateManager.STATE_START);
mStateManager.updateCurrentProgress(getWrapper().getEntity().getCurrentProgress());
} catch (Exception e) {
e.printStackTrace();
}
Looper.loop();
}
public TaskRecord getRecord() {
return record;
}
public void setParentKey(String parentKey) {
this.parentKey = parentKey;
}
public void setNeedGetInfo(boolean needGetInfo) {
this.needGetInfo = needGetInfo;
}
public void retryTask() {
try {
if (!mTask.isEmpty()) {
for (IThreadTask iThreadTask : mTask) {
iThreadTask.call();
}
return;
}
ALog.e(TAG, "子任务的线程任务为空");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override public void stop() {
if (isStop) {
ALog.w(TAG, "子任务已停止");
return;
}
isStop = true;
if (infoTask != null){
infoTask.stop();
}
for (IThreadTask iThreadTask : mTask) {
iThreadTask.stop();
}
}
@Override public boolean isRunning() {
if (mTask.isEmpty()) {
return false;
}
for (IThreadTask iThreadTask : mTask) {
if (!iThreadTask.isBreak()) return true;
}
return false;
}
@Override public void cancel() {
if (isCancel) {
ALog.w(TAG, "子任务已取消");
return;
}
isCancel = true;
if (infoTask != null){
infoTask.cancel();
}
for (IThreadTask iThreadTask : mTask) {
iThreadTask.cancel();
}
}
@Override public boolean isBreak() {
if (isCancel || isStop) {
ALog.d(TAG, "isCancel = " + isCancel + ", isStop = " + isStop);
ALog.d(TAG, String.format("任务【%s】已停止或取消了", wrapper.getKey()));
return true;
}
return false;
}
/**
* 线程名一个子任务的loader只有一个线程使用线程名标示key
*
* @return {@link ThreadTask#getThreadName()}
*/
@Override public String getKey() {
return CommonUtil.getThreadName(wrapper.getKey(), 0);
}
@Override public long getCurrentProgress() {
return isRunning() ? mStateManager.getCurrentProgress()
: getWrapper().getEntity().getCurrentProgress();
}
@Override public void addComponent(IRecordHandler recordHandler) {
this.recordHandler = recordHandler;
}
@Override public void addComponent(IInfoTask infoTask) {
this.infoTask = infoTask;
infoTask.setCallback(new IInfoTask.Callback() {
@Override public void onSucceed(String key, CompleteInfo info) {
handlerTask();
}
@Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) {
sendFailState(needRetry);
}
});
}
@Override public void addComponent(IThreadStateManager threadState) {
mStateManager = threadState;
}
@Override public void addComponent(IThreadTaskBuilder builder) {
ttBuild = builder;
}
@Override public void run() {
checkComponent();
if (isBreak()) {
return;
}
if (needGetInfo) {
infoTask.run();
return;
}
handlerTask();
}
/**
* 检查组件: {@link #recordHandler}、{@link #infoTask}、{@link #ttBuild}
*/
private void checkComponent() {
if (recordHandler == null) {
throw new NullPointerException("任务记录组件为空");
}
if (infoTask == null) {
throw new NullPointerException(("文件信息组件为空"));
}
if (ttBuild == null) {
throw new NullPointerException("线程任务组件为空");
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.core.loader;
import android.os.Handler;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.task.IThreadTask;
import java.util.List;
public class SubTTBuilder implements IThreadTaskBuilder{
@Override public List<IThreadTask> buildThreadTask(TaskRecord record, Handler stateHandler) {
return null;
}
@Override public int getCreatedThreadNum() {
return 1;
}
@Override public void accept(ILoaderVisitor visitor) {
visitor.addComponent(this);
}
}

View File

@ -0,0 +1,275 @@
/*
* 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.core.loader;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程任务管理器,用于处理多线程下载时任务的状态回调
*/
public class UploadThreadStateManager implements IThreadStateManager {
private final String TAG = CommonUtil.getClassName(this);
/**
* 任务状态回调
*/
private IEventListener mListener;
private int mThreadNum; // 启动的线程总数
private AtomicInteger mCancelNum = new AtomicInteger(0); // 已经取消的线程的数
private AtomicInteger mStopNum = new AtomicInteger(0); // 已经停止的线程数
private AtomicInteger mFailNum = new AtomicInteger(0); // 失败的线程数
private AtomicInteger mCompleteNum = new AtomicInteger(0); // 完成的线程数
private long mProgress; //当前总进度
private TaskRecord mTaskRecord; // 任务记录
private Looper mLooper;
/**
* @param listener 任务事件
*/
public UploadThreadStateManager(IEventListener listener) {
mListener = listener;
}
@Override public void setLooper(TaskRecord taskRecord, Looper looper) {
mTaskRecord = taskRecord;
mThreadNum = mTaskRecord.threadNum;
mLooper = looper;
}
private void checkLooper() {
if (mTaskRecord == null) {
throw new NullPointerException("任务记录为空");
}
if (mLooper == null) {
throw new NullPointerException("Looper为空");
}
}
private Handler.Callback callback = new Handler.Callback() {
@Override public boolean handleMessage(Message msg) {
checkLooper();
switch (msg.what) {
case STATE_STOP:
mStopNum.getAndIncrement();
if (isStop()) {
quitLooper();
}
break;
case STATE_CANCEL:
mCancelNum.getAndIncrement();
if (isCancel()) {
quitLooper();
}
break;
case STATE_FAIL:
mFailNum.getAndIncrement();
if (isFail()) {
Bundle b = msg.getData();
mListener.onFail(b.getBoolean(DATA_RETRY, false),
(AriaException) b.getSerializable(DATA_ERROR_INFO));
quitLooper();
}
break;
case STATE_COMPLETE:
mCompleteNum.getAndIncrement();
if (isComplete()) {
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
//上传文件不需要合并文件
mListener.onComplete();
quitLooper();
}
break;
case STATE_RUNNING:
Bundle b = msg.getData();
if (b != null) {
long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0);
mProgress += len;
}
break;
case STATE_UPDATE_PROGRESS:
if (msg.obj == null) {
mProgress = updateBlockProgress();
} else if (msg.obj instanceof Long) {
mProgress = (long) msg.obj;
}
break;
}
return false;
}
};
@Override public void updateCurrentProgress(long currentProgress) {
mProgress = currentProgress;
}
/**
* 退出looper循环
*/
private void quitLooper() {
mLooper.quit();
}
/**
* 获取当前任务下载进度
*
* @return 当前任务下载进度
*/
@Override
public long getCurrentProgress() {
return mProgress;
}
@Override public Handler.Callback getHandlerCallback() {
return callback;
}
/**
* 所有子线程是否都已经停止
*/
public boolean isStop() {
//ALog.d(TAG,
// String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mStopNum.get() == mThreadNum || mStopNum.get() + mCompleteNum.get() == mThreadNum;
}
/**
* 所有子线程是否都已经失败
*/
@Override
public boolean isFail() {
//ALog.d(TAG,
// String.format("isFail; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum.get() != mThreadNum
&& (mFailNum.get() == mThreadNum || mFailNum.get() + mCompleteNum.get() == mThreadNum);
}
/**
* 所有子线程是否都已经完成
*/
@Override
public boolean isComplete() {
//ALog.d(TAG,
// String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum.get() == mThreadNum;
}
/**
* 所有子线程是否都已经取消
*/
public boolean isCancel() {
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCancelNum.get() == mThreadNum;
}
/**
* 更新分块任务s的真实进度
*/
private long updateBlockProgress() {
long size = 0;
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) {
File temp = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 合并sftp的分块
*/
private boolean mergerSFtp() {
if (mTaskRecord.threadNum == 1) {
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(new File(mTaskRecord.filePath));
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength);
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
return true;
}
/**
* 合并文件
*
* @return {@code true} 合并成功,{@code false}合并失败
*/
private boolean mergeFile() {
if (mTaskRecord.threadNum == 1) {
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() == mTaskRecord.fileLength){
return true;
}
FileUtil.deleteFile(targetFile);
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(targetFile);
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败下载长度超出文件真实长度downloadLen: %sfileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
@Override public void accept(ILoaderVisitor visitor) {
visitor.addComponent(this);
}
}

View File

@ -0,0 +1,283 @@
/*
* 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.core.manager;
import android.text.TextUtils;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程任务管理器
*/
public class ThreadTaskManager {
private final String TAG = CommonUtil.getClassName(this);
private static volatile ThreadTaskManager INSTANCE = null;
private static final int CORE_POOL_NUM = 20;
private static final ReentrantLock LOCK = new ReentrantLock();
private ThreadPoolExecutor mExePool;
private Map<String, Set<FutureContainer>> mThreadTasks = new ConcurrentHashMap<>();
public static synchronized ThreadTaskManager getInstance() {
if (INSTANCE == null) {
INSTANCE = new ThreadTaskManager();
}
return INSTANCE;
}
private ThreadTaskManager() {
mExePool = new ThreadPoolExecutor(CORE_POOL_NUM, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
mExePool.allowsCoreThreadTimeOut();
}
/**
* 删除所有线程任务
*/
public void removeAllThreadTask() {
if (mThreadTasks.isEmpty()) {
return;
}
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
for (Set<FutureContainer> threads : mThreadTasks.values()) {
for (FutureContainer container : threads) {
if (container.future.isDone() || container.future.isCancelled()) {
continue;
}
container.threadTask.destroy();
}
threads.clear();
}
mThreadTasks.clear();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 启动线程任务
*
* @param key 任务对应的key{@link AbsTaskWrapper#getKey()}
* @param threadTask 线程任务{@link IThreadTask}
*/
public void startThread(String key, IThreadTask threadTask) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return;
}
key = getKey(key);
Set<FutureContainer> temp = mThreadTasks.get(key);
if (temp == null) {
temp = new HashSet<>();
mThreadTasks.put(key, temp);
}
FutureContainer container = new FutureContainer();
container.threadTask = threadTask;
container.future = mExePool.submit(threadTask);
temp.add(container);
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 任务是否在执行
*
* @param key 任务的key
* @return {@code true} 任务正在运行
*/
public boolean taskIsRunning(String key) {
return mThreadTasks.get(getKey(key)) != null;
}
/**
* 停止任务的所有线程
*
* @param key 任务对应的key{@link AbsTaskWrapper#getKey()}
*/
public void removeTaskThread(String key) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return;
}
key = getKey(key);
Set<FutureContainer> temp = mThreadTasks.get(key);
if (temp != null && temp.size() > 0) {
for (FutureContainer container : temp) {
if (container.future.isDone() || container.future.isCancelled()) {
continue;
}
container.threadTask.destroy();
}
temp.clear();
mThreadTasks.remove(key);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 根据线程名删除任务的中的线程
*
* @param key 任务的key如果是组合任务则为组合任务的key
* @param threadName 线程名
* @return true 删除线程成功false 删除线程失败
*/
public boolean removeSingleTaskThread(String key, String threadName) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return false;
}
if (TextUtils.isEmpty(threadName)) {
ALog.e(TAG, "线程名为空");
return false;
}
key = getKey(key);
Set<FutureContainer> temp = mThreadTasks.get(key);
if (temp != null && temp.size() > 0) {
FutureContainer tempC = null;
for (FutureContainer container : temp) {
if (container.threadTask.getThreadName().equals(threadName)) {
tempC = container;
break;
}
}
if (tempC != null) {
tempC.threadTask.destroy();
temp.remove(tempC);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
return false;
}
/**
* 删除单个线程任务
*
* @param key 任务的key
* @param task 线程任务
*/
public boolean removeSingleTaskThread(String key, IThreadTask task) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return false;
}
if (task == null) {
ALog.e(TAG, "线程任务为空");
return false;
}
key = getKey(key);
Set<FutureContainer> temp = mThreadTasks.get(key);
if (temp != null && temp.size() > 0) {
FutureContainer tempC = null;
for (FutureContainer container : temp) {
if (container.threadTask == task) {
tempC = container;
break;
}
}
if (tempC != null) {
task.destroy();
temp.remove(tempC);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
return false;
}
/**
* 重试线程任务
*
* @param task 线程任务
*/
public void retryThread(IThreadTask task) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return;
}
try {
if (task == null || task.isDestroy()) {
ALog.e(TAG, "线程为空或线程已经中断");
return;
}
} catch (Exception e) {
ALog.e(TAG, "", e);
return;
}
mExePool.submit(task);
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* map中的key
*
* @param key 任务的key{@link AbsTaskWrapper#getKey()}
* @return 转换后的map中的key
*/
private String getKey(String key) {
return CommonUtil.getStrMd5(key);
}
private class FutureContainer {
Future future;
IThreadTask threadTask;
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.core.processor;
import android.text.TextUtils;
/**
* Ftp上传拦截器处理只针对新任务有效
*
* 如果使用者同时实现{@link Builder#resetFileName(String)}和{@link Builder#coverServerFile}
* 将默认使用{@link Builder#coverServerFile}
*/
public class FtpInterceptHandler {
private boolean coverServerFile;
private String newFileName;
private FtpInterceptHandler() {
}
public boolean isCoverServerFile() {
return coverServerFile;
}
public String getNewFileName() {
return newFileName;
}
public static final class Builder {
private boolean coverServerFile = false;
private String newFileName;
private boolean stopUpload = false;
/**
* 如果ftp服务器端已经有同名文件控制是否覆盖远端的同名文件
* 如果你不希望覆盖远端文件,可以使用{@link #resetFileName(String)}
*
* @return {@code true} 如果ftp服务器端已经有同名文件覆盖服务器端的同名文件
*/
public Builder coverServerFile() {
coverServerFile = true;
return this;
}
/**
* 如果ftp服务器端已经有同名文件修改该文件上传到远端的文件名该操作不会修改本地文件名
* 如果你希望覆盖远端的同名文件,可以使用{@link #coverServerFile()}
*/
public Builder resetFileName(String newFileName) {
this.newFileName = newFileName;
return this;
}
/**
* 如果你希望停止上传任务,可以调用该方法
*/
public Builder stopUpload() {
stopUpload = true;
return this;
}
/**
* 如果使用者同时实现{@link Builder#resetFileName(String)}和{@link Builder#coverServerFile}
* 将默认使用{@link Builder#coverServerFile}
*/
public FtpInterceptHandler build() {
FtpInterceptHandler handler = new FtpInterceptHandler();
if (coverServerFile) {
handler.coverServerFile = true;
} else if (!TextUtils.isEmpty(newFileName)) {
handler.newFileName = newFileName;
}
return handler;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.core.processor;
import com.arialyy.aria.core.inf.IEventHandler;
/**
* M3U8 bandWidth 码率url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*/
public interface IBandWidthUrlConverter extends IEventHandler {
/**
* 转换码率地址为可用的http地址对于某些服务器返回的切片信息有可能是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param m3u8Url m3u8url地址
* @param bandWidthUrl 原始码率地址
* @return 可正常访问的http地址
*/
String convert(String m3u8Url, String bandWidthUrl);
}

Some files were not shown because too many files have changed in this diff Show More