组件化功能基本完成

This commit is contained in:
laoyuyu
2019-09-29 20:07:21 +08:00
parent 7ee1347ab4
commit 2a92321785
337 changed files with 5186 additions and 3250 deletions

View File

@@ -0,0 +1,153 @@
/*
* 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.util;
import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by Aria.Lao on 2017/10/25.
* Aria日志工具
*/
public class ALog {
public static final boolean DEBUG = true;
public static final int LOG_LEVEL_VERBOSE = 2;
public static final int LOG_LEVEL_DEBUG = 3;
public static final int LOG_LEVEL_INFO = 4;
public static final int LOG_LEVEL_WARN = 5;
public static final int LOG_LEVEL_ERROR = 6;
public static final int LOG_LEVEL_ASSERT = 7;
public static final int LOG_CLOSE = 8;
public static final int LOG_DEFAULT = LOG_LEVEL_DEBUG;
public static int LOG_LEVEL = LOG_DEFAULT;
public static int v(String tag, String msg) {
return println(Log.VERBOSE, tag, msg);
}
public static int d(String tag, String msg) {
return println(Log.DEBUG, tag, msg);
}
public static int i(String tag, String msg) {
return println(Log.INFO, tag, msg);
}
public static int w(String tag, String msg) {
return println(Log.WARN, tag, msg);
}
public static int e(String tag, String msg) {
return println(Log.ERROR, tag, msg);
}
public static int e(String tag, Throwable e) {
String msg = getExceptionString(e);
ErrorHelp.saveError(tag, "", msg);
return println(Log.ERROR, tag, msg);
}
/**
* 打印MAp,debug级别日志
*/
public static void m(String tag, Map map) {
if (LOG_LEVEL <= Log.DEBUG) {
Set set = map.entrySet();
if (set.size() < 1) {
d(tag, "[]");
return;
}
int i = 0;
String[] s = new String[set.size()];
for (Object aSet : set) {
Map.Entry entry = (Map.Entry) aSet;
s[i] = entry.getKey() + " = " + entry.getValue() + ",\n";
i++;
}
println(Log.DEBUG, tag, Arrays.toString(s));
}
}
/**
* 打印JSON,debug级别日志
*/
public static void j(String tag, String jsonStr) {
if (LOG_LEVEL <= Log.DEBUG) {
String message;
try {
if (jsonStr.startsWith("{")) {
JSONObject jsonObject = new JSONObject(jsonStr);
message = jsonObject.toString(4); //这个是核心方法
} else if (jsonStr.startsWith("[")) {
JSONArray jsonArray = new JSONArray(jsonStr);
message = jsonArray.toString(4);
} else {
message = jsonStr;
}
} catch (JSONException e) {
message = jsonStr;
}
println(Log.DEBUG, tag, message);
}
}
/**
* 将异常信息转换为字符串
*/
public static String getExceptionString(Throwable ex) {
if (ex == null) {
return "";
}
StringBuilder err = new StringBuilder();
err.append("ExceptionDetailed:\n");
err.append("====================Exception Info====================\n");
err.append(ex.toString());
err.append("\n");
StackTraceElement[] stack = ex.getStackTrace();
for (StackTraceElement stackTraceElement : stack) {
err.append(stackTraceElement.toString()).append("\n");
}
Throwable cause = ex.getCause();
if (cause != null) {
err.append("【Caused by】: ");
err.append(cause.toString());
err.append("\n");
StackTraceElement[] stackTrace = cause.getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
err.append(stackTraceElement.toString()).append("\n");
}
}
err.append("===================================================");
return err.toString();
}
private static int println(int level, String tag, String msg) {
if (LOG_LEVEL <= level) {
return Log.println(level, tag, TextUtils.isEmpty(msg) ? "null" : msg);
} else {
return -1;
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Aria.Lao on 2017/10/25.
* 程序异常日志捕获器
*/
public class AriaCrashHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler mDefaultHandler;
private ExecutorService mExecutorService;
public AriaCrashHandler() {
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
mExecutorService = Executors.newSingleThreadExecutor();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
ex.printStackTrace();
//ALog.d(thread.getName(), ex.getLocalizedMessage());
handleException(thread.getName(), ex);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mDefaultHandler.uncaughtException(thread, ex);
exit();
}
}
/**
* 处理异常
*/
private void handleException(final String name, final Throwable ex) {
if (ex == null) {
return;
}
mExecutorService.execute(new Runnable() {
@Override
public void run() {
ErrorHelp.saveError(name, "", ALog.getExceptionString(ex));
}
});
}
/**
* 退出当前应用
*/
private void exit() {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.util;
/**
* 速度限制
*/
public class BandwidthLimiter {
public static int maxBandWith = 2 * 1024; //KB
/* KB */
private static Long KB = 1024L;
/* The smallest count chunk length in bytes */
private static Long CHUNK_LENGTH = 1024L;
/* How many bytes will be sent or receive */
private int bytesWillBeSentOrReceive = 0;
/* When the last piece was sent or receive */
private long lastPieceSentOrReceiveTick = System.nanoTime();
/* Default rate is 1024KB/s */
private int maxRate = 1024;
/* Time cost for sending CHUNK_LENGTH bytes in nanoseconds */
private long timeCostPerChunk = (1000000000L * CHUNK_LENGTH)
/ (this.maxRate * KB);
/**
* Initialize a BandwidthLimiter object with a certain rate.
*
* @param maxRate the download or upload speed in KBytes
*/
public BandwidthLimiter(int maxRate, int threadNum) {
if (threadNum > 1) {
maxRate = maxRate / threadNum;
}
this.setMaxRate(maxRate);
}
/**
* Set the max upload or download rate in KB/s. maxRate must be grater than
* 0. If maxRate is zero, it means there is no bandwidth limit.
*
* @param maxRate If maxRate is zero, it means there is no bandwidth limit.
* @throws IllegalArgumentException
*/
public synchronized void setMaxRate(int maxRate)
throws IllegalArgumentException {
if (maxRate < 0) {
throw new IllegalArgumentException("maxRate can not less than 0");
}
this.maxRate = maxRate;
if (maxRate == 0) {
this.timeCostPerChunk = 0;
} else {
this.timeCostPerChunk = (1000000000L * CHUNK_LENGTH)
/ (this.maxRate * KB);
}
}
/**
* Next 1 byte should do bandwidth limit.
*/
public synchronized void limitNextBytes() {
this.limitNextBytes(1);
}
/**
* Next len bytes should do bandwidth limit
*/
public synchronized void limitNextBytes(int len) {
this.bytesWillBeSentOrReceive += len;
/* We have sent CHUNK_LENGTH bytes */
while (!Thread.currentThread().isInterrupted() && this.bytesWillBeSentOrReceive > CHUNK_LENGTH) {
long nowTick = System.nanoTime();
long missedTime = this.timeCostPerChunk
- (nowTick - this.lastPieceSentOrReceiveTick);
if (missedTime > 0) {
try {
Thread.currentThread().sleep(missedTime / 1000000,
(int) (missedTime % 1000000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.bytesWillBeSentOrReceive -= CHUNK_LENGTH;
this.lastPieceSentOrReceiveTick = nowTick
+ (missedTime > 0 ? missedTime : 0);
}
}
}

View File

@@ -0,0 +1,329 @@
/*
* 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.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
/**
* A <code>BufferedRandomAccessFile</code> is like a
* <code>RandomAccessFile</code>, but it uses a private buffer so that most
* operations do not require a disk access.
* <P>
*
* Note: The operations on this class are unmonitored. Also, the correct
* functioning of the <code>RandomAccessFile</code> methods that are not
* overridden here relies on the implementation of those methods in the
* superclass.
* Author : Avinash Lakshman ( alakshman@facebook.com) Prashant Malik ( pmalik@facebook.com )
*/
public final class BufferedRandomAccessFile extends RandomAccessFile {
//private static final Logger logger_ = Logger.getLogger(BufferedRandomAccessFile.class);
static final int LogBuffSz_ = 16; // 64K buffer
public static final int BuffSz_ = (1 << LogBuffSz_);
static final long BuffMask_ = ~(((long) BuffSz_) - 1L);
/*
* This implementation is based on the buffer implementation in Modula-3's
* "Rd", "Wr", "RdClass", and "WrClass" interfaces.
*/
private boolean dirty_; // true iff unflushed bytes exist
private boolean closed_; // true iff the file is closed
private long curr_; // current position in file
private long lo_, hi_; // bounds on characters in "buff"
private byte[] buff_; // local buffer
private long maxHi_; // this.lo + this.buff.length
private boolean hitEOF_; // buffer contains last file block?
private long diskPos_; // disk position
/*
* To describe the above fields, we introduce the following abstractions for
* the file "f":
*
* len(f) the length of the file curr(f) the current position in the file
* c(f) the abstract contents of the file disk(f) the contents of f's
* backing disk file closed(f) true iff the file is closed
*
* "curr(f)" is an peerIndex in the closed interval [0, len(f)]. "c(f)" is a
* character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if
* "c(f)" contains unflushed writes not reflected in "disk(f)". The flush
* operation has the effect of making "disk(f)" identical to "c(f)".
*
* A file is said to be *valid* if the following conditions hold:
*
* V1. The "closed" and "curr" fields are correct:
*
* f.closed == closed(f) f.curr == curr(f)
*
* V2. The current position is either contained in the buffer, or just past
* the buffer:
*
* f.lo <= f.curr <= f.hi
*
* V3. Any (possibly) unflushed characters are stored in "f.buff":
*
* (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo])
*
* V4. For all characters not covered by V3, c(f) and disk(f) agree:
*
* (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] ==
* disk(f)[i])
*
* V5. "f.dirty" is true iff the buffer contains bytes that should be
* flushed to the file; by V3 and V4, only part of the buffer can be dirty.
*
* f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo])
*
* V6. this.maxHi == this.lo + this.buff.length
*
* Note that "f.buff" can be "null" in a valid file, since the range of
* characters in V3 is empty when "f.lo == f.curr".
*
* A file is said to be *ready* if the buffer contains the current position,
* i.e., when:
*
* R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi
*
* When a file is ready, reading or writing a single byte can be performed
* by reading or writing the in-memory buffer without performing a disk
* operation.
*/
/**
* Open a new <code>BufferedRandomAccessFile</code> on <code>file</code>
* in mode <code>mode</code>, which should be "r" for reading only, or
* "rw" for reading and writing.
*/
public BufferedRandomAccessFile(File file, String mode) throws IOException {
super(file, mode);
this.init(0);
}
public BufferedRandomAccessFile(File file, String mode, int size) throws IOException {
super(file, mode);
this.init(size);
}
/**
* Open a new <code>BufferedRandomAccessFile</code> on the file named
* <code>name</code> in mode <code>mode</code>, which should be "r" for
* reading only, or "rw" for reading and writing.
*/
public BufferedRandomAccessFile(String name, String mode) throws IOException {
super(name, mode);
this.init(0);
}
public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException {
super(name, mode);
this.init(size);
}
private void init(int size) {
this.dirty_ = this.closed_ = false;
this.lo_ = this.curr_ = this.hi_ = 0;
this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_];
this.maxHi_ = (long) BuffSz_;
this.hitEOF_ = false;
this.diskPos_ = 0L;
}
public void close() throws IOException {
this.flush();
this.closed_ = true;
super.close();
}
/**
* Flush any bytes in the file's buffer that have not yet been written to
* disk. If the file was created read-only, this method is a no-op.
*/
public void flush() throws IOException {
this.flushBuffer();
}
/* Flush any dirty bytes in the buffer to disk. */
private void flushBuffer() throws IOException {
if (this.dirty_) {
if (this.diskPos_ != this.lo_) super.seek(this.lo_);
int len = (int) (this.curr_ - this.lo_);
super.write(this.buff_, 0, len);
this.diskPos_ = this.curr_;
this.dirty_ = false;
}
}
/*
* Read at most "this.buff.length" bytes into "this.buff", returning the
* number of bytes read. If the return result is less than
* "this.buff.length", then EOF was read.
*/
private int fillBuffer() throws IOException {
int cnt = 0;
int rem = this.buff_.length;
while (rem > 0) {
int n = super.read(this.buff_, cnt, rem);
if (n < 0) break;
cnt += n;
rem -= n;
}
if ((cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length))) {
// make sure buffer that wasn't read is initialized with -1
Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff);
}
this.diskPos_ += cnt;
return cnt;
}
/*
* This method positions <code>this.curr</code> at position <code>pos</code>.
* If <code>pos</code> does not fall in the current buffer, it flushes the
* current buffer and loads the correct one.<p>
*
* On exit from this routine <code>this.curr == this.hi</code> iff <code>pos</code>
* is at or past the end-of-file, which can only happen if the file was
* opened in read-only mode.
*/
public void seek(long pos) throws IOException {
if (pos >= this.hi_ || pos < this.lo_) {
// seeking outside of current buffer -- flush and read
this.flushBuffer();
this.lo_ = pos & BuffMask_; // create at BuffSz boundary
this.maxHi_ = this.lo_ + (long) this.buff_.length;
if (this.diskPos_ != this.lo_) {
super.seek(this.lo_);
this.diskPos_ = this.lo_;
}
int n = this.fillBuffer();
this.hi_ = this.lo_ + (long) n;
} else {
// seeking inside current buffer -- no read required
if (pos < this.curr_) {
// if seeking backwards, we must flush to maintain V4
this.flushBuffer();
}
}
this.curr_ = pos;
}
public long getFilePointer() {
return this.curr_;
}
public long length() throws IOException {
return Math.max(this.curr_, super.length());
}
public int read() throws IOException {
if (this.curr_ >= this.hi_) {
// test for EOF
// if (this.hi < this.maxHi) return -1;
if (this.hitEOF_) return -1;
// slow path -- read another buffer
this.seek(this.curr_);
if (this.curr_ == this.hi_) return -1;
}
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
this.curr_++;
return ((int) res) & 0xFF; // convert byte -> int
}
public int read(byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
if (this.curr_ >= this.hi_) {
// test for EOF
// if (this.hi < this.maxHi) return -1;
if (this.hitEOF_) return -1;
// slow path -- read another buffer
this.seek(this.curr_);
if (this.curr_ == this.hi_) return -1;
}
len = Math.min(len, (int) (this.hi_ - this.curr_));
int buffOff = (int) (this.curr_ - this.lo_);
System.arraycopy(this.buff_, buffOff, b, off, len);
this.curr_ += len;
return len;
}
public void write(int b) throws IOException {
if (this.curr_ >= this.hi_) {
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
// at EOF -- bump "hi"
this.hi_++;
} else {
// slow path -- write current buffer; read next one
this.seek(this.curr_);
if (this.curr_ == this.hi_) {
// appending to EOF -- bump "hi"
this.hi_++;
}
}
}
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
this.curr_++;
this.dirty_ = true;
}
public void write(byte[] b) throws IOException {
this.write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
while (len > 0) {
int n = this.writeAtMost(b, off, len);
off += n;
len -= n;
this.dirty_ = true;
}
}
/*
* Write at most "len" bytes to "b" starting at position "off", and return
* the number of bytes written.
*/
private int writeAtMost(byte[] b, int off, int len) throws IOException {
if (this.curr_ >= this.hi_) {
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
// at EOF -- bump "hi"
this.hi_ = this.maxHi_;
} else {
// slow path -- write current buffer; read next one
this.seek(this.curr_);
if (this.curr_ == this.hi_) {
// appending to EOF -- bump "hi"
this.hi_ = this.maxHi_;
}
}
}
len = Math.min(len, (int) (this.hi_ - this.curr_));
int buffOff = (int) (this.curr_ - this.lo_);
System.arraycopy(b, off, this.buff_, buffOff, len);
this.curr_ += len;
return len;
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.util;
import android.text.TextUtils;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.ParamException;
import java.io.File;
import java.util.List;
/**
* Created by Lyy on 2016/9/23.
* 检查帮助类
*/
public class CheckUtil {
private static final String TAG = "CheckUtil";
/**
* 检查ftp上传路径,如果ftp上传路径为空,抛出空指针异常
* 如果ftp上传路径不是以"ftp"或"sftp",抛出参数异常
*
* @param ftpUrl ftp上传路径
*/
public static void checkFtpUploadUrl(String ftpUrl) {
if (TextUtils.isEmpty(ftpUrl)) {
throw new ParamException("ftp上传路径为空");
} else if (!ftpUrl.startsWith("ftp") || !ftpUrl.startsWith("sftp")) {
throw new ParamException("ftp上传路径无效");
}
}
/**
* 判空
*/
public static void checkNull(Object obj) {
if (obj == null) throw new IllegalArgumentException("不能传入空对象");
}
/**
* 检查分页数据,需要查询的页数,从1开始,如果page小于1 或 num 小于1,则抛出{@link NullPointerException}
*
* @param page 从1 开始
* @param num 每页数量
*/
public static void checkPageParams(int page, int num) {
if (page < 1 || num < 1) throw new NullPointerException("page和num不能小于1");
}
/**
* 检查下载实体
*/
public static void checkDownloadEntity(DownloadEntity entity) {
checkUrlInvalidThrow(entity.getUrl());
entity.setUrl(entity.getUrl());
checkPath(entity.getDownloadPath());
}
/**
* 检测下载链接是否为null
*/
public static void checkPath(String path) {
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("保存路径不能为null");
}
}
/**
* 检测url是否合法,如果url不合法,将抛异常
*/
public static void checkTaskId(long taskId) {
if (taskId < 0) {
throw new IllegalArgumentException("任务id不能小于0");
}
}
/**
* 检测url是否合法,如果url不合法,将抛异常
*/
public static void checkUrlInvalidThrow(String url) {
if (TextUtils.isEmpty(url)) {
throw new IllegalArgumentException("url不能为null");
} else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) {
throw new IllegalArgumentException("url错误");
}
int index = url.indexOf("://");
if (index == -1) {
throw new IllegalArgumentException("url不合法");
}
}
/**
* 检测url是否合法,如果url不合法,将抛出{@link IllegalArgumentException}异常
*/
public static void checkUrl(String url) {
if (TextUtils.isEmpty(url)) {
throw new NullPointerException("url为空");
} else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) {
throw new IllegalArgumentException(String.format("url【%s】错误", url));
}
int index = url.indexOf("://");
if (index == -1) {
throw new IllegalArgumentException(String.format("url【%s】不合法", url));
}
}
/**
* 检测url是否合法
*
* @return {@code true} 合法,{@code false} 非法
*/
public static boolean checkUrlNotThrow(String url) {
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "url不能为null");
return false;
} else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) {
ALog.e(TAG, "url【" + url + "】错误");
return false;
}
int index = url.indexOf("://");
if (index == -1) {
ALog.e(TAG, "url【" + url + "】不合法");
}
return true;
}
/**
* 检测下载链接组是否为null
*/
public static void checkDownloadUrls(List<String> urls) {
if (urls == null || urls.isEmpty()) {
throw new IllegalArgumentException("链接组不能为null");
}
}
/**
* 检查下载任务组保存路径
*/
public static void checkDownloadPaths(List<String> paths) {
if (paths == null || paths.isEmpty()) {
throw new IllegalArgumentException("链接保存路径不能为null");
}
}
/**
* 检测上传地址是否为null
*/
public static void checkUploadPath(String uploadPath) {
if (TextUtils.isEmpty(uploadPath)) {
throw new IllegalArgumentException("上传地址不能为null");
}
File file = new File(uploadPath);
if (!file.exists()) {
throw new IllegalArgumentException("上传文件不存在");
}
}
/**
* 检查任务实体
*/
public static void checkTaskEntity(AbsTaskWrapper entity) {
if (entity instanceof DTaskWrapper) {
checkDownloadTaskEntity(((DTaskWrapper) entity).getEntity());
} else if (entity instanceof UTaskWrapper) {
checkUploadTaskEntity(((UTaskWrapper) entity).getEntity());
}
}
/**
* 检查命令实体
*
* @param checkType 删除命令和停止命令不需要检查下载链接和保存路径
* @return {@code false}实体无效
*/
public static boolean checkCmdEntity(AbsTaskWrapper entity, boolean checkType) {
boolean b = false;
if (entity instanceof DTaskWrapper) {
DownloadEntity entity1 = ((DTaskWrapper) entity).getEntity();
if (entity1 == null) {
ALog.e(TAG, "下载实体不能为空");
} else if (checkType && TextUtils.isEmpty(entity1.getUrl())) {
ALog.e(TAG, "下载链接不能为空");
} else if (checkType && TextUtils.isEmpty(entity1.getDownloadPath())) {
ALog.e(TAG, "保存路径不能为空");
} else {
b = true;
}
} else if (entity instanceof UTaskWrapper) {
UploadEntity entity1 = ((UTaskWrapper) entity).getEntity();
if (entity1 == null) {
ALog.e(TAG, "上传实体不能为空");
} else if (TextUtils.isEmpty(entity1.getFilePath())) {
ALog.e(TAG, "上传文件路径不能为空");
} else {
b = true;
}
}
return b;
}
/**
* 检查上传实体是否合法
*/
private static void checkUploadTaskEntity(UploadEntity entity) {
if (entity == null) {
throw new NullPointerException("上传实体不能为空");
} else if (TextUtils.isEmpty(entity.getFilePath())) {
throw new IllegalArgumentException("上传文件路径不能为空");
} else if (TextUtils.isEmpty(entity.getFileName())) {
throw new IllegalArgumentException("上传文件名不能为空");
}
}
/**
* 检测下载实体是否合法
* 合法(true)
*
* @param entity 下载实体
*/
private static void checkDownloadTaskEntity(DownloadEntity entity) {
if (entity == null) {
throw new NullPointerException("下载实体不能为空");
} else if (TextUtils.isEmpty(entity.getUrl())) {
throw new IllegalArgumentException("下载链接不能为空");
} else if (TextUtils.isEmpty(entity.getFileName())) {
throw new NullPointerException("文件名不能为null");
} else if (TextUtils.isEmpty(entity.getDownloadPath())) {
throw new NullPointerException("文件保存路径不能为null");
}
}
}

View File

@@ -0,0 +1,796 @@
/*
* 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.util;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Base64;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.TaskOptionParams;
import com.arialyy.aria.core.inf.IEventHandler;
import com.arialyy.aria.core.inf.ITaskOption;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import dalvik.system.DexFile;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by lyy on 2016/1/22. 通用工具
*/
public class CommonUtil {
private static final String TAG = "CommonUtil";
public static final String SERVER_CHARSET = "ISO-8859-1";
private static long lastClickTime;
/**
* 检查sql的expression是否合法
*
* @return false 不合法
*/
public static boolean checkSqlExpression(String... expression) {
if (expression.length == 0) {
ALog.e(TAG, "sql语句表达式不能为null");
return false;
}
if (expression.length == 1) {
ALog.e(TAG, String.format("表达式需要写入参数,参数信息:%s", Arrays.toString(expression)));
return false;
}
String where = expression[0];
if (!where.contains("?")) {
ALog.e(TAG, String.format("请在where语句的'='后编写?,参数信息:%s", Arrays.toString(expression)));
return false;
}
Pattern pattern = Pattern.compile("\\?");
Matcher matcher = pattern.matcher(where);
int count = 0;
while (matcher.find()) {
count++;
}
if (count < expression.length - 1) {
ALog.e(TAG, String.format("条件语句的?个数不能小于参数个数,参数信息:%s", Arrays.toString(expression)));
return false;
}
if (count > expression.length - 1) {
ALog.e(TAG, String.format("条件语句的?个数不能大于参数个数, 参数信息:%s", Arrays.toString(expression)));
return false;
}
return true;
}
/**
* 是否是快速点击,500ms内快速点击无效
*
* @return true 快速点击
*/
public static boolean isFastDoubleClick() {
long time = System.currentTimeMillis();
long timeD = time - lastClickTime;
if (0 < timeD && timeD < 500) {
ALog.i(TAG, "操作太频繁了,缓一下吧~");
return true;
}
lastClickTime = time;
return false;
}
/**
* 将字符串转换为Ftp服务器默认的ISO-8859-1编码
*
* @param charSet 原字符串编码s
* @param str 需要转换的字符串
* @return 转换后的字符串
*/
public static String convertFtpChar(String charSet, String str)
throws UnsupportedEncodingException {
return new String(str.getBytes(charSet), SERVER_CHARSET);
}
/**
* 获取某包下所有类
*
* @param className 过滤的类名
* @return 类的完整名称
*/
public static List<String> getPkgClassNames(Context context, String className) {
List<String> classNameList = new ArrayList<>();
String pPath = context.getPackageCodePath();
File dir = new File(pPath).getParentFile();
String[] paths = dir.list();
if (paths == null) {
classNameList.addAll(getPkgClassName(pPath, className));
} else {
String dPath = dir.getPath();
for (String path : dir.list()) {
String fPath = dPath + "/" + path;
if (!fPath.endsWith(".apk")) {
continue;
}
classNameList.addAll(getPkgClassName(fPath, className));
}
}
return classNameList;
}
/**
* 获取指定包名下的所有类
*
* @param path dex路径
* @param filterClass 需要过滤的类
*/
public static List<String> getPkgClassName(String path, String filterClass) {
List<String> list = new ArrayList<>();
try {
File file = new File(path);
if (!file.exists()) {
ALog.w(TAG, String.format("路径【%s】下的Dex文件不存在", path));
return list;
}
DexFile df = new DexFile(path);//通过DexFile查找当前的APK中可执行文件
Enumeration<String> enumeration = df.entries();//获取df中的元素 这里包含了所有可执行的类名 该类名包含了包名+类名的方式
while (enumeration.hasMoreElements()) {
String _className = enumeration.nextElement();
if (!_className.contains(filterClass)) {
continue;
}
if (_className.contains(filterClass)) {
list.add(_className);
}
}
df.close();
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
* 拦截window.location.replace数据
*
* @return 重定向url
*/
public static String getWindowReplaceUrl(String text) {
if (TextUtils.isEmpty(text)) {
ALog.e(TAG, "拦截数据为null");
return null;
}
String reg = Regular.REG_WINLOD_REPLACE;
Pattern p = Pattern.compile(reg);
Matcher m = p.matcher(text);
if (m.find()) {
String s = m.group();
s = s.substring(9, s.length() - 2);
return s;
}
return null;
}
/**
* 获取sdcard app的缓存目录
*
* @return "/mnt/sdcard/Android/data/{package_name}/files/"
*/
public static String getAppPath(Context context) {
//判断是否存在sd卡
boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(
android.os.Environment.getExternalStorageState());
if (!sdExist) {
return null;
} else {
//获取sd卡路径
File file = context.getExternalFilesDir(null);
String dir;
if (file != null) {
dir = file.getPath() + "/";
} else {
dir = Environment.getExternalStorageDirectory().getPath()
+ "/Android/data/"
+ context.getPackageName()
+ "/files/";
}
return dir;
}
}
/**
* 获取map泛型类型
*
* @param map list类型字段
* @return 泛型类型
*/
public static Class[] getMapParamType(Field map) {
Class type = map.getType();
if (!type.isAssignableFrom(Map.class)) {
ALog.d(TAG, "字段类型不是Map");
return null;
}
Type fc = map.getGenericType();
if (fc == null) {
ALog.d(TAG, "该字段没有泛型参数");
return null;
}
if (fc instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) fc;
Type[] types = pt.getActualTypeArguments();
Class[] clazz = new Class[2];
clazz[0] = (Class) types[0];
clazz[1] = (Class) types[1];
return clazz;
}
return null;
}
/**
* 获取list泛型类型
*
* @param list list类型字段
* @return 泛型类型
*/
public static Class getListParamType(Field list) {
Class type = list.getType();
if (!type.isAssignableFrom(List.class)) {
ALog.d(TAG, "字段类型不是List");
return null;
}
Type fc = list.getGenericType(); // 关键的地方,如果是List类型,得到其Generic的类型
if (fc == null) {
ALog.d(TAG, "该字段没有泛型参数");
return null;
}
if (fc instanceof ParameterizedType) { //如果是泛型参数的类型
ParameterizedType pt = (ParameterizedType) fc;
return (Class) pt.getActualTypeArguments()[0]; //得到泛型里的class类型对象。
}
return null;
}
/**
* 分割获取url,协议,ip/域名,端口,内容
*
* @param url 输入的url{@code String url = "ftp://z:z@dygod18.com:21211/[电影天堂www.dy2018.com]猩球崛起3:终极之战BD国英双语中英双字.mkv";}
*/
public static FtpUrlEntity getFtpUrlInfo(String url) {
Uri uri = Uri.parse(url);
String userInfo = uri.getUserInfo(), remotePath = uri.getPath();
ALog.d(TAG,
String.format("scheme = %s, user = %s, host = %s, port = %s, path = %s", uri.getScheme(),
userInfo, uri.getHost(), uri.getPort(), remotePath));
FtpUrlEntity entity = new FtpUrlEntity();
entity.url = url;
entity.hostName = uri.getHost();
entity.port = uri.getPort() == -1 ? "21" : String.valueOf(uri.getPort());
if (!TextUtils.isEmpty(userInfo)) {
String[] temp = userInfo.split(":");
if (temp.length == 2) {
entity.user = temp[0];
entity.password = temp[1];
} else {
entity.user = userInfo;
}
}
entity.scheme = uri.getScheme();
entity.remotePath = TextUtils.isEmpty(remotePath) ? "/" : remotePath;
return entity;
}
/**
* 转换Url
*
* @param url 原地址
* @return 转换后的地址
*/
public static String convertUrl(String url) {
Uri uri = Uri.parse(url);
url = uri.toString();
if (hasDoubleCharacter(url)) {
//预先处理空格,URLEncoder只会把空格转换为+
url = url.replaceAll(" ", "%20");
//匹配双字节字符(包括汉字在内)
String regex = Regular.REG_DOUBLE_CHAR_AND_SPACE;
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(url);
Set<String> strs = new HashSet<>();
while (m.find()) {
strs.add(m.group());
}
try {
for (String str : strs) {
url = url.replaceAll(str, URLEncoder.encode(str, "UTF-8"));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return url;
}
/**
* 判断是否有双字节字符(包括汉字在内) 和空格、制表符、回车
*
* @param chineseStr 需要进行判断的字符串
* @return {@code true}有双字节字符,{@code false} 无双字节字符
*/
public static boolean hasDoubleCharacter(String chineseStr) {
char[] charArray = chineseStr.toCharArray();
for (char aCharArray : charArray) {
if (((aCharArray >= 0x0391) && (aCharArray <= 0xFFE5)) || (aCharArray == 0x0d) || (aCharArray
== 0x0a) || (aCharArray == 0x20)) {
return true;
}
}
return false;
}
/**
* base64 解密字符串
*
* @param str 被加密的字符串
* @return 解密后的字符串
*/
public static String decryptBASE64(String str) {
return new String(Base64.decode(str.getBytes(), Base64.DEFAULT));
}
/**
* base64 加密字符串
*
* @param str 需要加密的字符串
* @return 加密后的字符串
*/
public static String encryptBASE64(String str) {
return Base64.encodeToString(str.getBytes(), Base64.DEFAULT);
}
/**
* 字符串编码转换
*/
public static String strCharSetConvert(String oldStr, String charSet) {
try {
return new String(oldStr.getBytes(), charSet);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据下载任务组的url创建key
*
* @return urls 为 null 或者 size为0,返回""
*/
public static String getMd5Code(List<String> urls) {
if (urls == null || urls.size() < 1) return "";
String md5 = "";
StringBuilder sb = new StringBuilder();
for (String url : urls) {
sb.append(url);
}
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sb.toString().getBytes());
md5 = new BigInteger(1, md.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
ALog.e(TAG, e.getMessage());
}
return md5;
}
/**
* 获取字符串的md5
*
* @return 字符串为空或获取md5失败,则返回""
*/
public static String getStrMd5(String str) {
if (TextUtils.isEmpty(str)) return "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
return new BigInteger(1, md.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
ALog.e(TAG, e.getMessage());
}
return "";
}
/**
* 获取CPU核心数
*/
public static int getCoresNum() {
//Private Class to display only CPU devices in the directory listing
class CpuFilter implements FileFilter {
@Override public boolean accept(File pathname) {
//Check if filename is "cpu", followed by a single digit number
return Pattern.matches("cpu[0-9]", pathname.getName());
}
}
try {
//Get directory containing CPU info
File dir = new File("/sys/devices/system/cpu/");
//Filter to only list the devices we care about
File[] files = dir.listFiles(new CpuFilter());
ALog.d(TAG, "CPU Count: " + files.length);
//Return the number of cores (virtual CPU devices)
return files.length;
} catch (Exception e) {
//Print exception
ALog.d(TAG, "CPU Count: Failed.");
e.printStackTrace();
//Default to return 1 core
return 1;
}
}
/**
* 通过流创建文件
*/
public static void createFileFormInputStream(InputStream is, String path) {
try {
FileOutputStream fos = new FileOutputStream(path);
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) {
fos.write(buf, 0, len);
}
is.close();
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 校验文件MD5码
*/
public static boolean checkMD5(String md5, File updateFile) {
if (TextUtils.isEmpty(md5) || updateFile == null) {
ALog.e(TAG, "MD5 string empty or updateFile null");
return false;
}
String calculatedDigest = getFileMD5(updateFile);
if (calculatedDigest == null) {
ALog.e(TAG, "calculatedDigest null");
return false;
}
return calculatedDigest.equalsIgnoreCase(md5);
}
/**
* 校验文件MD5码
*/
public static boolean checkMD5(String md5, InputStream is) {
if (TextUtils.isEmpty(md5) || is == null) {
ALog.e(TAG, "MD5 string empty or updateFile null");
return false;
}
String calculatedDigest = getFileMD5(is);
if (calculatedDigest == null) {
ALog.e(TAG, "calculatedDigest null");
return false;
}
return calculatedDigest.equalsIgnoreCase(md5);
}
/**
* 获取文件MD5码
*/
public static String getFileMD5(File updateFile) {
InputStream is;
try {
is = new FileInputStream(updateFile);
} catch (FileNotFoundException e) {
ALog.e(TAG, e);
return null;
}
return getFileMD5(is);
}
/**
* 获取文件MD5码
*/
public static String getFileMD5(InputStream is) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
ALog.e(TAG, e);
return null;
}
byte[] buffer = new byte[8192];
int read;
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
// Fill to 32 chars
output = String.format("%32s", output).replace(' ', '0');
return output;
} catch (IOException e) {
throw new RuntimeException("Unable to process file for MD5", e);
} finally {
try {
is.close();
} catch (IOException e) {
ALog.e(TAG, e);
}
}
}
/**
* 创建隐性的Intent
*/
public static Intent createIntent(String packageName, String action) {
Uri.Builder builder = new Uri.Builder();
builder.scheme(packageName);
Uri uri = builder.build();
Intent intent = new Intent(action);
intent.setData(uri);
return intent;
}
/**
* 存储字符串到配置文件
*
* @param preName 配置文件名
* @param key 存储的键值
* @param value 需要存储的字符串
* @return 成功标志
*/
public static Boolean putString(String preName, Context context, String key, String value) {
SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pre.edit();
editor.putString(key, value);
return editor.commit();
}
/**
* 从配置文件读取字符串
*
* @param preName 配置文件名
* @param key 字符串键值
* @return 键值对应的字符串, 默认返回""
*/
public static String getString(String preName, Context context, String key) {
SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE);
return pre.getString(key, "");
}
/**
* 获取所有字段,包括父类的字段
*/
public static List<Field> getAllFields(Class clazz) {
List<Field> fields = new ArrayList<>();
Class personClazz = clazz.getSuperclass();
if (personClazz != null) {
Class rootClazz = personClazz.getSuperclass();
if (rootClazz != null) {
Collections.addAll(fields, rootClazz.getDeclaredFields());
}
Collections.addAll(fields, personClazz.getDeclaredFields());
}
Collections.addAll(fields, clazz.getDeclaredFields());
return fields;
}
/**
* 获取当前类里面的所在字段
*/
public static Field[] getFields(Class clazz) {
Field[] fields;
fields = clazz.getDeclaredFields();
if (fields.length == 0) {
Class superClazz = clazz.getSuperclass();
if (superClazz != null) {
fields = getFields(superClazz);
}
}
return fields;
}
/**
* 获取类里面的指定对象,如果该类没有则从父类查询
*/
public static Field getField(Class clazz, String name) {
Field field = null;
try {
field = clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
try {
field = clazz.getField(name);
} catch (NoSuchFieldException e1) {
if (clazz.getSuperclass() == null) {
return field;
} else {
field = getField(clazz.getSuperclass(), name);
}
}
}
if (field != null) {
field.setAccessible(true);
}
return field;
}
/**
* 字符串转hashcode
*/
public static int keyToHashCode(String str) {
int total = 0;
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch == '-') ch = (char) 28; // does not contain the same last 5 bits as any letter
if (ch == '\'') ch = (char) 29; // nor this
total = (total * 33) + (ch & 0x1F);
}
return total;
}
/**
* 将key转换为16进制码
*
* @param key 缓存的key
* @return 转换后的key的值, 系统便是通过该key来读写缓存
*/
public static String keyToHashKey(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* 将普通字符串转换为16位进制字符串
*/
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("0x");
if (src == null || src.length <= 0) {
return null;
}
char[] buffer = new char[2];
for (byte aSrc : src) {
buffer[0] = Character.forDigit((aSrc >>> 4) & 0x0F, 16);
buffer[1] = Character.forDigit(aSrc & 0x0F, 16);
stringBuilder.append(buffer);
}
return stringBuilder.toString();
}
/**
* 获取对象名
*
* @param obj 对象
* @return 对象名
*/
public static String getClassName(Object obj) {
String[] arrays = obj.getClass().getName().split("\\.");
return arrays[arrays.length - 1];
}
/**
* 获取对象名
*
* @param clazz clazz
* @return 对象名
*/
public static String getClassName(Class clazz) {
String[] arrays = clazz.getName().split("\\.");
return arrays[arrays.length - 1];
}
/**
* 格式化文件大小
*
* @param size file.length() 获取文件大小
*/
public static String formatFileSize(double size) {
if (size < 0) {
return "0kb";
}
double kiloByte = size / 1024;
if (kiloByte < 1) {
return size + "b";
}
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "kb";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "mb";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "gb";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "tb";
}
/**
* 通过文件名获取下载配置文件路径
*
* @param fileName 文件名
*/
public static String getFileConfigPath(boolean isDownload, String fileName) {
return AriaConfig.getInstance().getAPP().getFilesDir().getPath() + (isDownload
? AriaConfig.DOWNLOAD_TEMP_DIR
: AriaConfig.UPLOAD_TEMP_DIR) + fileName + ".properties";
}
}

View File

@@ -0,0 +1,243 @@
/*
* 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.util;
import android.os.Handler;
import com.arialyy.aria.core.TaskOptionParams;
import com.arialyy.aria.core.inf.IEventHandler;
import com.arialyy.aria.core.inf.ITaskOption;
import com.arialyy.aria.core.inf.IUtil;
import com.arialyy.aria.core.listener.BaseListener;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 组件工具,用于跨组件创建对应的工具类
*
* @Author lyy
* @Date 2019-09-23
*/
public class ComponentUtil {
public static final int COMPONENT_TYPE_HTTP = 1;
public static final int COMPONENT_TYPE_FTP = 2;
public static final int COMPONENT_TYPE_M3U8 = 3;
private static volatile ComponentUtil INSTANCE = null;
private String TAG = CommonUtil.getClassName(getClass());
private ComponentUtil() {
}
public static ComponentUtil getInstance() {
if (INSTANCE == null) {
synchronized (ComponentUtil.class) {
if (INSTANCE == null) {
INSTANCE = new ComponentUtil();
}
}
}
return INSTANCE;
}
/**
* 检查组件是否存在,不存在抛出异常退出
*
* @param componentType 组件类型{@link #COMPONENT_TYPE_FTP}, {@link #COMPONENT_TYPE_M3U8}, {@link
* #COMPONENT_TYPE_HTTP}
* @return true 组件存在
*/
public boolean checkComponentExist(int componentType) {
String errorStr = "", className = null;
switch (componentType) {
case COMPONENT_TYPE_M3U8:
className = "com.arialyy.aria.m3u8.M3U8TaskOption";
errorStr = "m3u8插件不存在,请添加m3u8插件";
break;
case COMPONENT_TYPE_FTP:
className = "com.arialyy.aria.ftp.FtpTaskOption";
errorStr = "ftp插件不存在,请添加ftp插件";
break;
case COMPONENT_TYPE_HTTP:
className = "com.arialyy.aria.http.HttpTaskOption";
errorStr = "http插件不存在,请添加http插件";
break;
}
try {
getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new IllegalArgumentException(errorStr);
}
return true;
}
/**
* 创建下载工具
*
* @return 返回下载工具,创建失败返回null
*/
public <T extends IUtil> T buildUtil(ITaskWrapper wrapper, IEventListener listener) {
int requestType = wrapper.getRequestType();
String className = null;
switch (requestType) {
case ITaskWrapper.M3U8_LIVE:
className = "com.arialyy.aria.m3u8.live.M3U8LiveUtil";
break;
case ITaskWrapper.M3U8_VOD:
className = "com.arialyy.aria.m3u8.vod.M3U8VodUtil";
break;
case ITaskWrapper.D_FTP:
className = "com.arialyy.aria.ftp.download.FtpDLoaderUtil";
break;
case ITaskWrapper.D_HTTP:
className = "com.arialyy.aria.http.download.HttpDLoaderUtil";
break;
case ITaskWrapper.U_FTP:
className = "com.arialyy.aria.ftp.upload.FtpULoaderUtil";
break;
case ITaskWrapper.U_HTTP:
className = "com.arialyy.aria.http.upload.HttpULoaderUtil";
break;
case ITaskWrapper.D_FTP_DIR:
className = "com.arialyy.aria.http.download.DGroupLoaderUtil";
break;
case ITaskWrapper.DG_HTTP:
className = "com.arialyy.aria.ftp.download.FtpDirDLoaderUtil";
break;
}
if (className == null) {
return null;
}
T util = null;
try {
Class<T> clazz = (Class<T>) getClass().getClassLoader().loadClass(className);
Class[] paramTypes = { AbsTaskWrapper.class, IEventListener.class };
Object[] params = { wrapper, listener };
Constructor<T> con = clazz.getConstructor(paramTypes);
util = con.newInstance(params);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return util;
}
/**
* 创建任务事件监听
*
* @param wrapperType 任务类型{@link ITaskWrapper}
* @return 返回事件监听,如果创建失败返回null
*/
public <T extends IEventListener> T buildListener(int wrapperType, ITask task,
Handler outHandler) {
String className = null, errorStr = "请添加FTP插件";
switch (wrapperType) {
case ITaskWrapper.M3U8_LIVE:
case ITaskWrapper.M3U8_VOD:
className = "com.arialyy.aria.m3u8.M3U8Listener";
errorStr = "请添加m3u8插件";
break;
case ITaskWrapper.D_FTP:
case ITaskWrapper.D_HTTP:
className = "com.arialyy.aria.core.listener.BaseDListener";
break;
case ITaskWrapper.U_FTP:
case ITaskWrapper.U_HTTP:
className = "com.arialyy.aria.core.listener.BaseUListener";
break;
case ITaskWrapper.DG_HTTP:
case ITaskWrapper.D_FTP_DIR:
className = "com.arialyy.aria.core.listener.DownloadGroupListener";
break;
}
if (className == null) {
return null;
}
T listener = null;
try {
Class<T> clazz = (Class<T>) getClass().getClassLoader().loadClass(className);
Class[] paramTypes = { AbsTask.class, Handler.class };
Object[] params = { task, outHandler };
Constructor<T> con = clazz.getConstructor(paramTypes);
listener = con.newInstance(params);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(errorStr);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return listener;
}
/**
* 构建TaskOption信息
*
* @param clazz 实现{@link ITaskOption}接口的配置信息类
* @param params 任务配置信息参数
* @return 构建失败,返回null
*/
public <T extends ITaskOption> T buildTaskOption(Class<T> clazz, TaskOptionParams params) {
Field[] fields = CommonUtil.getFields(clazz);
T taskOption = null;
try {
taskOption = clazz.newInstance();
for (Field field : fields) {
Class type = field.getType();
String key = field.getName();
if (type != SoftReference.class) {
Object obj = params.getParams().get(key);
if (obj == null) {
continue;
}
field.set(taskOption, obj);
} else {
IEventHandler handler = params.getHandler().get(key);
if (handler == null) {
continue;
}
field.set(taskOption, new SoftReference<>(handler));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return taskOption;
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.util;
import com.arialyy.aria.core.wrapper.RecordWrapper;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.download.DGEntityWrapper;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.orm.DbEntity;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 数据库帮助类
*/
public class DbDataHelper {
/**
* 获取任务记录
*
* @param filePath 文件地址
* @return 没有记录返回null,有记录则返回任务记录
*/
public static TaskRecord getTaskRecord(String filePath) {
List<RecordWrapper> record =
DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?", filePath);
if (record == null || record.size() == 0) {
return null;
}
return record.get(0).taskRecord;
}
/**
* 获取组合任务实体、ftpDir任务实体
*
* @param groupHash 组合任务Hash
* @return 实体不存在,返回null
*/
public static DownloadGroupEntity getDGEntity(String groupHash) {
List<DGEntityWrapper> wrapper =
DbEntity.findRelationData(DGEntityWrapper.class, "DownloadGroupEntity.groupHash=?",
groupHash);
return wrapper == null || wrapper.size() == 0 ? null : wrapper.get(0).groupEntity;
}
/**
* 获取组合任务实体、ftpDir任务实体
*
* @param taskId 组合任务id
* @return 实体不存在,返回null
*/
public static DownloadGroupEntity getDGEntity(long taskId) {
List<DGEntityWrapper> wrapper =
DbEntity.findRelationData(DGEntityWrapper.class, "DownloadGroupEntity.rowid=?",
String.valueOf(taskId));
return wrapper == null || wrapper.size() == 0 ? null : wrapper.get(0).groupEntity;
}
/**
* 创建HTTP子任务实体
*/
public static List<DownloadEntity> createHttpSubTask(String groupHash, List<String> urls) {
List<DownloadEntity> list = new ArrayList<>();
for (int i = 0, len = urls.size(); i < len; i++) {
String url = urls.get(i);
DownloadEntity entity = new DownloadEntity();
entity.setUrl(url);
entity.setFilePath(groupHash + "_" + i);
int lastIndex = url.lastIndexOf(File.separator);
entity.setFileName(url.substring(lastIndex + 1));
entity.setGroupHash(groupHash);
entity.setGroupChild(true);
list.add(entity);
}
return list;
}
/**
* 通过Ftp下载地址获取组合任务实体
*
* @param ftpUrl ftp下载地址
*/
public static DownloadGroupEntity getOrCreateFtpDGEntity(String ftpUrl) {
List<DGEntityWrapper> wrapper =
DbEntity.findRelationData(DGEntityWrapper.class, "DownloadGroupEntity.groupHash=?",
ftpUrl);
DownloadGroupEntity groupEntity;
if (wrapper != null && !wrapper.isEmpty()) {
groupEntity = wrapper.get(0).groupEntity;
if (groupEntity == null) {
groupEntity = new DownloadGroupEntity();
}
} else {
groupEntity = new DownloadGroupEntity();
}
groupEntity.setGroupHash(ftpUrl);
return groupEntity;
}
/**
* 创建任务组子任务的任务实体
*/
public static List<DTaskWrapper> createDGSubTaskWrapper(DownloadGroupEntity dge) {
List<DTaskWrapper> list = new ArrayList<>();
for (DownloadEntity entity : dge.getSubEntities()) {
DTaskWrapper taskEntity = new DTaskWrapper(entity);
taskEntity.setGroupHash(dge.getKey());
taskEntity.setGroupTask(true);
list.add(taskEntity);
}
return list;
}
}

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.util;
import android.annotation.SuppressLint;
import com.arialyy.aria.core.AriaConfig;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by Aria.Lao on 2017/8/29.
* 错误帮助类
*/
public class ErrorHelp {
/**
* 保存错误信息
*
* @param msg 错误提示
* @param ex 异常
*/
public static void saveError(String tag, String msg, String ex) {
writeLogToFile(tag, String.format("\nmsg【%s】\nException:%s", msg, ex));
}
/**
* 返回日志路径
*
* @return "/mnt/sdcard/Android/data/{package_name}/files/log/*"
*/
private static String getLogPath() {
String path = String.format("%slog/AriaCrash_%s.log",
CommonUtil.getAppPath(AriaConfig.getInstance().getAPP()),
getData("yyyy-MM-dd_HH_mm_ss"));
File log = new File(path);
if (!log.getParentFile().exists()) {
log.getParentFile().mkdirs();
}
if (!log.exists()) {
try {
log.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return path;
}
/**
* 把日志记录到文件
*/
private static int writeLogToFile(String tag, String message) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(getData("yyyy-MM-dd HH:mm:ss"));
stringBuffer.append(" ");
stringBuffer.append(tag);
stringBuffer.append(" ");
stringBuffer.append(message);
stringBuffer.append("\n\n");
PrintWriter writer = null;
try {
File file = new File(getLogPath());
if (!file.exists()) {
FileUtil.createFile(file.getPath());
}
writer = new PrintWriter(new FileWriter(file.getPath(), true));
writer.append(stringBuffer);
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
return 0;
}
@SuppressLint("SimpleDateFormat")
private static String getData(String format) {
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.format(date);
}
}

View File

@@ -0,0 +1,744 @@
/*
* 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.util;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import com.arialyy.aria.core.AriaConfig;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.SequenceInputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Scanner;
import java.util.regex.Pattern;
/**
* 文件操作工具类
*/
public class FileUtil {
private static final String TAG = "FileUtil";
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
private static final String EXTERNAL_STORAGE_PATH =
Environment.getExternalStorageDirectory().getPath();
/**
* 创建目录 当目录不存在的时候创建文件,否则返回false
*/
public static boolean createDir(String path) {
File file = new File(path);
if (!file.exists()) {
if (!file.mkdirs()) {
ALog.d(TAG, "创建失败,请检查路径和是否配置文件权限!");
return false;
}
return true;
}
return false;
}
/**
* 创建文件 当文件不存在的时候就创建一个文件。 如果文件存在,先删除原文件,然后重新创建一个新文件
*
* @return {@code true} 创建成功、{@code false} 创建失败
*/
public static boolean createFile(String path) {
if (TextUtils.isEmpty(path)) {
ALog.e(TAG, "文件路径不能为null");
return false;
}
File file = new File(path);
if (file.getParentFile() == null || !file.getParentFile().exists()) {
ALog.d(TAG, "目标文件所在路径不存在,准备创建……");
if (!createDir(file.getParent())) {
ALog.d(TAG, "创建目录文件所在的目录失败!文件路径【" + path + "】");
}
}
// 创建目标文件
if (file.exists()) {
final File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
if (file.renameTo(to)) {
to.delete();
} else {
file.delete();
}
}
try {
if (file.createNewFile()) {
//ALog.d(TAG, "创建文件成功:" + file.getAbsolutePath());
return true;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return false;
}
/**
* 创建文件名,如果url链接有后缀名,则使用url中的后缀名
*
* @return url 的 hashKey
*/
public static String createFileName(String url) {
int end = url.indexOf("?");
String tempUrl, fileName = "";
if (end > 0) {
tempUrl = url.substring(0, end);
int tempEnd = tempUrl.lastIndexOf("/");
if (tempEnd > 0) {
fileName = tempUrl.substring(tempEnd + 1);
}
} else {
int tempEnd = url.lastIndexOf("/");
if (tempEnd > 0) {
fileName = url.substring(tempEnd + 1);
}
}
if (TextUtils.isEmpty(fileName)) {
fileName = CommonUtil.keyToHashKey(url);
}
return fileName;
}
/**
* 删除文件
*
* @param path 文件路径
* @return {@code true}删除成功、{@code false}删除失败
*/
public static boolean deleteFile(String path) {
if (TextUtils.isEmpty(path)) {
ALog.e(TAG, "删除文件失败,路径为空");
return false;
}
return deleteFile(new File(path));
}
/**
* 删除文件
*
* @param file 文件路径
* @return {@code true}删除成功、{@code false}删除失败
*/
public static boolean deleteFile(File file) {
if (file.exists()) {
final File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
if (file.renameTo(to)) {
return to.delete();
} else {
return file.delete();
}
}
return false;
}
/**
* 将对象写入文件
*
* @param filePath 文件路径
* @param data data数据必须实现{@link Serializable}接口
*/
public static void writeObjToFile(String filePath, Object data) {
if (!(data instanceof Serializable)) {
ALog.e(TAG, "对象写入文件失败,data数据必须实现Serializable接口");
return;
}
FileOutputStream ops = null;
try {
if (!createFile(filePath)) {
return;
}
ops = new FileOutputStream(filePath);
ObjectOutputStream oops = new ObjectOutputStream(ops);
oops.writeObject(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ops != null) {
try {
ops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 从文件中读取对象
*
* @param filePath 文件路径
* @return 如果读取成功,返回相应的Obj对象,读取失败,返回null
*/
public static Object readObjFromFile(String filePath) {
if (TextUtils.isEmpty(filePath)) {
ALog.e(TAG, "文件路径为空");
return null;
}
File file = new File(filePath);
if (!file.exists()) {
ALog.e(TAG, String.format("文件【%s】不存在", filePath));
return null;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(filePath);
ObjectInputStream oois = new ObjectInputStream(fis);
return oois.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 合并文件
*
* @param targetPath 目标文件
* @param subPaths 碎片文件路径
* @return {@code true} 合并成功,{@code false}合并失败
*/
public static boolean mergeFile(String targetPath, List<String> subPaths) {
File file = new File(targetPath);
FileOutputStream fos = null;
FileChannel foc = null;
try {
if (!file.exists()) {
file.createNewFile();
}
fos = new FileOutputStream(targetPath);
foc = fos.getChannel();
List<FileInputStream> streams = new LinkedList<>();
for (String subPath : subPaths) {
File f = new File(subPath);
if (!f.exists()) {
ALog.d(TAG, String.format("合并文件失败,文件【%s】不存在", subPath));
for (FileInputStream fis : streams) {
fis.close();
}
streams.clear();
return false;
}
streams.add(new FileInputStream(subPath));
}
Enumeration<FileInputStream> en = Collections.enumeration(streams);
SequenceInputStream sis = new SequenceInputStream(en);
ReadableByteChannel fic = Channels.newChannel(sis);
ByteBuffer bf = ByteBuffer.allocate(8196);
while (fic.read(bf) != -1) {
bf.flip();
foc.write(bf);
bf.compact();
}
fic.close();
sis.close();
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (foc != null) {
foc.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 分割文件
*
* @param filePath 被分割的文件路径
* @param num 分割的块数
*/
public static void splitFile(String filePath, int num) {
try {
final File file = new File(filePath);
long size = file.length();
FileInputStream fis = new FileInputStream(file);
FileChannel fic = fis.getChannel();
long j = 0;
long block = size / num;
for (int i = 0; i < num; i++) {
if (i == num - 1) {
block = size - block * (num - 1);
}
String subPath = file.getPath() + "." + i + ".part";
ALog.d(TAG, String.format("block = %s", block));
File subFile = new File(subPath);
if (!subFile.exists()) {
subFile.createNewFile();
}
FileOutputStream fos = new FileOutputStream(subFile);
FileChannel sfoc = fos.getChannel();
ByteBuffer bf = ByteBuffer.allocate(8196);
int len;
//fis.skip(block * i);
while ((len = fic.read(bf)) != -1) {
bf.flip();
sfoc.write(bf);
bf.compact();
j += len;
if (j >= block * (i + 1)) {
break;
}
}
ALog.d(TAG, String.format("len = %s", subFile.length()));
fos.close();
sfoc.close();
}
fis.close();
fic.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 检查SD内存空间是否充足
*
* @param filePath 文件保存路径
* @param fileSize 文件大小
* @return {@code false} 内存空间不足,{@code true}内存空间足够
*/
public static boolean checkSDMemorySpace(String filePath, long fileSize) {
List<String> dirs = FileUtil.getSDPathList(AriaConfig.getInstance().getAPP());
if (dirs == null || dirs.isEmpty()) {
return true;
}
for (String path : dirs) {
if (filePath.contains(path)) {
if (fileSize > 0 && fileSize > getAvailableExternalMemorySize(path)) {
return false;
}
}
}
return true;
}
/**
* sdcard 可用大小
*
* @param sdcardPath sdcard 根路径
* @return 单位为:byte
*/
public static long getAvailableExternalMemorySize(String sdcardPath) {
StatFs stat = new StatFs(sdcardPath);
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
return availableBlocks * blockSize;
}
/**
* sdcard 总大小
*
* @param sdcardPath sdcard 根路径
* @return 单位为:byte
*/
public static long getTotalExternalMemorySize(String sdcardPath) {
StatFs stat = new StatFs(sdcardPath);
long blockSize = stat.getBlockSize();
long totalBlocks = stat.getBlockCount();
return totalBlocks * blockSize;
}
/**
* 获取SD卡目录列表
*/
public static List<String> getSDPathList(Context context) {
List<String> paths;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
paths = getVolumeList(context);
} else {
List<String> mounts = readMountsFile();
List<String> volds = readVoldFile();
paths = compareMountsWithVold(mounts, volds);
}
return paths;
}
/**
* 读取下载配置文件
*/
public static Properties loadConfig(File file) {
Properties properties = new Properties();
FileInputStream fis = null;
if (!file.exists()) {
createFile(file.getPath());
}
try {
fis = new FileInputStream(file);
properties.load(fis);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return properties;
}
/**
* 保存配置文件
*/
public static void saveConfig(File file, Properties properties) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, false);
properties.store(fos, null);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.flush();
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* getSDPathList
*/
private static List<String> getVolumeList(final Context context) {
List<String> pathList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StorageManager manager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
List<StorageVolume> volumes = manager.getStorageVolumes();
for (StorageVolume volume : volumes) {
String state = volume.getState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
pathList.add(volume.toString());
}
}
} else {
pathList.addAll(getStorageDirectories());
}
if (pathList.isEmpty()) {
pathList = new ArrayList<>();
pathList.add(EXTERNAL_STORAGE_PATH);
}
LinkedHashMap<Integer, String> paths = new LinkedHashMap<>();
for (String path : pathList) {
File root = new File(path);
if (!root.exists() || !root.isDirectory() || !canWrite(path)) {
continue;
}
//去除mount的相同目录
int key = (root.getTotalSpace() + "-" + root.getUsableSpace()).hashCode();
String prevPath = paths.get(key);
if (!TextUtils.isEmpty(prevPath) && prevPath.length() < path.length()) {
continue;
}
paths.put(key, path);
}
List<String> list = new ArrayList<>();
for (Integer key : paths.keySet()) {
list.add(paths.get(key));
}
return list;
}
private static boolean canWrite(String dirPath) {
File dir = new File(dirPath);
if (dir.canWrite()) {
return true;
}
boolean canWrite;
File testWriteFile = null;
try {
testWriteFile = new File(dirPath, "tw.txt");
if (testWriteFile.exists()) {
testWriteFile.delete();
}
testWriteFile.createNewFile();
FileWriter writer = new FileWriter(testWriteFile);
writer.write(1);
writer.close();
canWrite = true;
} catch (Exception e) {
e.printStackTrace();
canWrite = false;
} finally {
try {
if (testWriteFile != null && testWriteFile.exists()) {
testWriteFile.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return canWrite;
}
/**
* Raturns all available SD-Cards in the system (include emulated) Warning: Hack! Based on Android
* source code of version 4.3
* (API 18) Because there is no standard way to get it.
* 4.2+
*
* @return paths to all available SD-Cards in the system (include emulated)
*/
public static List<String> getStorageDirectories() {
// Final set of paths
final List<String> rv = new ArrayList<>();
// Primary physical SD-CARD (not emulated)
final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
// All Secondary SD-CARDs (all exclude primary) separated by ":"
final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
// Primary emulated SD-CARD
final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
// Device has physical external storage; use plain paths.
if (TextUtils.isEmpty(rawExternalStorage)) {
// EXTERNAL_STORAGE undefined; falling back to default.
rv.add("/storage/sdcard0");
} else {
rv.add(rawExternalStorage);
}
} else {
// Device has emulated storage; external storage paths should have
// userId burned into them.
final String rawUserId;
//if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
final String[] folders = DIR_SEPORATOR.split(path);
final String lastFolder = folders[folders.length - 1];
boolean isDigit = false;
if (!TextUtils.isEmpty(lastFolder) && TextUtils.isDigitsOnly(lastFolder)) {
isDigit = true;
}
rawUserId = isDigit ? lastFolder : "";
//} else {
// rawUserId = "";
//}
// /storage/emulated/0[1,2,...]
if (TextUtils.isEmpty(rawUserId)) {
rv.add(rawExternalStorage);
} else {
rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
}
}
// Add all secondary storages
if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
// All Secondary SD-CARDs splited into array
final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
Collections.addAll(rv, rawSecondaryStorages);
}
// checkout SD-CARDs writable
for (int i = rv.size() - 1; i >= 0; i--) {
String path = rv.get(i);
File root = new File(path);
if (!root.exists() || !root.isDirectory() || !canWrite(path)) {
rv.remove(i);
}
}
return rv;
}
/**
* Scan the /proc/mounts file and look for lines like this: /dev/block/vold/179:1 /mnt/sdcard
* vfat
* rw,dirsync,nosuid,nodev,noexec ,relatime,uid=1000,gid=1015,fmask=0602,dmask=0602,allow_utime=0020,
* codepage=cp437,iocharset= iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0 When one is
* found, split it into its
* elements and then pull out the path to the that mount point and add it to the arraylist
*/
private static List<String> readMountsFile() {
// some mount files don't list the default
// path first, so we add it here to
// ensure that it is first in our list
List<String> mounts = new ArrayList<>();
mounts.add(EXTERNAL_STORAGE_PATH);
try {
Scanner scanner = new Scanner(new File("/proc/mounts"));
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (line.startsWith("/dev/block/vold/") || line.startsWith("/dev/block//vold/")) {//
String[] lineElements = line.split(" ");
// String partition = lineElements[0];
String element = lineElements[1];
// don't add the default mount path
// it's already in the list.
if (!element.equals(EXTERNAL_STORAGE_PATH)) {
mounts.add(element);
}
}
}
scanner.close();
} catch (Exception e) {
// e.printStackTrace();
}
return mounts;
}
private static List<String> readVoldFile() {
// read /etc/vold.conf or /etc/vold.fstab (it depends on version what
// clientConfig file is present)
List<String> vold = null;
Scanner scanner = null;
try {
try {
scanner = new Scanner(new File("/system/etc/vold.fstab"));
} catch (FileNotFoundException e1) {
// e1.printStackTrace();
scanner = new Scanner(new File("/system/etc/vold.conf"));
}
vold = new ArrayList<String>();
vold.add(EXTERNAL_STORAGE_PATH);
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (TextUtils.isEmpty(line)) {
continue;
}
line = line.trim();
if (line.startsWith("dev_mount")) {
String[] lineElements = line.split(" ");
if (lineElements.length < 3) {
continue;
}
String element = lineElements[2];
if (element.contains(":")) {
element = element.substring(0, element.indexOf(":"));
}
// ignore default path
if (!element.equals(EXTERNAL_STORAGE_PATH)) {
vold.add(element);
}
} else if (line.startsWith("mount_point")) {
String element = line.replaceAll("mount_point", "").trim();
if (!element.equals(EXTERNAL_STORAGE_PATH)) {
vold.add(element);
}
}
}
} catch (Exception e) {
// e.printStackTrace();
}
return vold;
}
private static List<String> compareMountsWithVold(List<String> mounts, List<String> volds) {
/*
* 有时候这两个list中的数据并不相同,我们只需要取两个list的交集部分。
*/
for (int i = mounts.size() - 1; i >= 0; i--) {
String mount = mounts.get(i);
File root = new File(mount);
// 判断目录是否存在并且可读
if (!root.exists() || !root.isDirectory() || !root.canWrite()) {
mounts.remove(i);
continue;
}
if (volds != null && !volds.contains(mount)) {
mounts.remove(i);
}
}
// 清除无用数据
if (volds != null) {
volds.clear();
}
return mounts;
}
public static long getTotalMemory() {
String file_path = "/proc/meminfo";// 系统内存信息文件
String ram_info;
String[] arrayOfRam;
long initial_memory = 0L;
try {
FileReader fr = new FileReader(file_path);
BufferedReader localBufferedReader = new BufferedReader(fr, 8192);
// 读取meminfo第一行,系统总内存大小
ram_info = localBufferedReader.readLine();
arrayOfRam = ram_info.split("\\s+");// 实现多个空格切割的效果
initial_memory =
Integer.valueOf(arrayOfRam[1]) * 1024;// 获得系统总内存,单位是KB,乘以1024转换为Byte
localBufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
return initial_memory;
}
public static long getAvailMemory(Context context) {
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo.availMem;
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.arialyy.aria.core.AriaConfig;
/**
* 跟网络相关的工具类
*/
public class NetUtils {
/**
* 没有网络
*/
public static final int NETWORK_TYPE_INVALID = 0;
/**
* wap网络
*/
public static final int NETWORK_TYPE_WAP = 1;
/**
* 2G网络
*/
public static final int NETWORK_TYPE_2G = 2;
/**
* 3G和3G以上网络,或统称为快速网络
*/
public static final int NETWORK_TYPE_3G = 3;
/**
* wifi网络
*/
public static final int NETWORK_TYPE_WIFI = 4;
/**
* 判断网络是否连接
*
* @return {@code true} 网络已连接、{@code false}网络未连接
*/
public static boolean isConnected(Context context) {
if (!AriaConfig.getInstance().getAConfig().isNetCheck()) {
return true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return AriaConfig.getInstance().isConnectedNet();
}
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
return ni != null && ni.isConnectedOrConnecting();
}
/**
* 判断是否是wifi连接
*/
public static boolean isWifi(Context context) {
return getNetWorkType(context) == NETWORK_TYPE_WIFI;
}
/**
* 获取网络状态,wifi,wap,2g,3g.
*
* @param context 上下文
* @return int 网络状态 {@link #NETWORK_TYPE_2G},{@link #NETWORK_TYPE_3G},
* {@link #NETWORK_TYPE_INVALID},{@link #NETWORK_TYPE_WAP},{@link #NETWORK_TYPE_WIFI}
*/
public static int getNetWorkType(Context context) {
int netWorkType = -1;
ConnectivityManager manager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
String type = networkInfo.getTypeName();
if (type.equalsIgnoreCase("WIFI")) {
netWorkType = NETWORK_TYPE_WIFI;
} else if (type.equalsIgnoreCase("MOBILE")) {
String proxyHost = android.net.Proxy.getDefaultHost();
netWorkType = TextUtils.isEmpty(proxyHost) ? (isFastMobileNetwork(context) ? NETWORK_TYPE_3G
: NETWORK_TYPE_2G) : NETWORK_TYPE_WAP;
}
} else {
netWorkType = NETWORK_TYPE_INVALID;
}
return netWorkType;
}
private static boolean isFastMobileNetwork(Context context) {
TelephonyManager telephonyManager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
switch (telephonyManager.getNetworkType()) {
case TelephonyManager.NETWORK_TYPE_1xRTT:
return false; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_CDMA:
return false; // ~ 14-64 kbps
case TelephonyManager.NETWORK_TYPE_EDGE:
return false; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return true; // ~ 400-1000 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return true; // ~ 600-1400 kbps
case TelephonyManager.NETWORK_TYPE_GPRS:
return false; // ~ 100 kbps
case TelephonyManager.NETWORK_TYPE_HSDPA:
return true; // ~ 2-14 Mbps
case TelephonyManager.NETWORK_TYPE_HSPA:
return true; // ~ 700-1700 kbps
case TelephonyManager.NETWORK_TYPE_HSUPA:
return true; // ~ 1-23 Mbps
case TelephonyManager.NETWORK_TYPE_UMTS:
return true; // ~ 400-7000 kbps
case TelephonyManager.NETWORK_TYPE_EHRPD:
return true; // ~ 1-2 Mbps
case TelephonyManager.NETWORK_TYPE_EVDO_B:
return true; // ~ 5 Mbps
case TelephonyManager.NETWORK_TYPE_HSPAP:
return true; // ~ 10-20 Mbps
case TelephonyManager.NETWORK_TYPE_IDEN:
return false; // ~25 kbps
case TelephonyManager.NETWORK_TYPE_LTE:
return true; // ~ 10+ Mbps
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
return false;
default:
return false;
}
}
}

View File

@@ -0,0 +1,389 @@
/*
* 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.util;
import android.text.TextUtils;
import com.arialyy.aria.core.inf.IRecordHandler;
import com.arialyy.aria.core.wrapper.RecordWrapper;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.download.M3U8Entity;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import java.io.File;
import java.util.List;
/**
* 任务记录处理工具
*/
public class RecordUtil {
private static final String TAG = "RecordUtil";
/**
* 删除任务组记录
*
* @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件;
* {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。
*/
public static void delGroupTaskRecord(String groupHash, boolean removeFile) {
if (TextUtils.isEmpty(groupHash)) {
ALog.e(TAG, "删除下载任务组记录失败,groupHash为null");
return;
}
DownloadGroupEntity groupEntity = DbDataHelper.getDGEntity(groupHash);
delGroupTaskRecord(groupEntity, removeFile, true);
}
/**
* 删除任务组记录
*
* @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件;
* {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。
*/
public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile,
boolean removeEntity) {
if (groupEntity == null) {
ALog.e(TAG, "删除下载任务组记录失败,任务组实体为null");
return;
}
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "dGroupHash=?", groupEntity.getGroupHash());
if (records == null || records.isEmpty()) {
ALog.w(TAG, "组任务记录删除失败,记录为null");
} else {
for (RecordWrapper record : records) {
if (record == null || record.taskRecord == null) {
continue;
}
// 删除分块文件
if (record.taskRecord.isBlock) {
for (int i = 0, len = record.taskRecord.threadNum; i < len; i++) {
File partFile =
new File(String.format(IRecordHandler.SUB_PATH, record.taskRecord.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
DbEntity.deleteData(ThreadRecord.class, "taskKey=?", record.taskRecord.filePath);
record.taskRecord.deleteData();
}
}
List<DownloadEntity> subs = groupEntity.getSubEntities();
if (subs != null) {
for (DownloadEntity sub : subs) {
File file = new File(sub.getFilePath());
if (file.exists() && (removeFile || !sub.isComplete())) {
file.delete();
}
}
}
// 删除文件夹
if (!TextUtils.isEmpty(groupEntity.getDirPath())) {
File dir = new File(groupEntity.getDirPath());
if (dir.exists() && (removeFile || !groupEntity.isComplete())) {
dir.delete();
}
}
if (removeEntity) {
DbEntity.deleteData(DownloadEntity.class, "groupHash=?", groupEntity.getGroupHash());
DbEntity.deleteData(DownloadGroupEntity.class, "groupHash=?", groupEntity.getGroupHash());
}
}
/**
* 删除任务记录,默认删除文件
*
* @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件;
* {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。
* 如果是上传任务,无论任务是否完成,都只删除记录
*/
public static void delTaskRecord(AbsNormalEntity entity, boolean removeFile) {
if (entity == null) return;
String filePath;
int type;
if (entity instanceof DownloadEntity) {
type = IRecordHandler.TYPE_DOWNLOAD;
filePath = ((DownloadEntity) entity).getDownloadPath();
} else if (entity instanceof UploadEntity) {
type = IRecordHandler.TYPE_UPLOAD;
filePath = ((UploadEntity) entity).getFilePath();
} else {
ALog.w(TAG, "删除记录失败,未知类型");
return;
}
File targetFile = new File(filePath);
TaskRecord record = getTaskRecord(filePath);
if (record == null) {
if (removeFile) {
removeTargetFile(targetFile);
}
removeRecord(filePath);
removeEntity(type, filePath);
return;
}
/*
* 处理任务未完成的情况
*/
if (!entity.isComplete()) {
if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { // 删除ts分片文件
removeTsCache(targetFile, record.bandWidth);
} else if (record.isBlock) { // 删除分块文件
removeBlockFile(record);
}
} else if (removeFile) { // 处理任务完成情况
if (record.taskType == TaskRecord.TYPE_M3U8_VOD) {
removeTsCache(targetFile, record.bandWidth);
}
removeTargetFile(targetFile);
}
// 成功与否都将删除记录
removeRecord(filePath);
}
/**
* 删除任务记录,默认删除文件
*
* @param filePath 文件路径
* @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件;
* {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。
* 如果是上传任务,无论任务是否完成,都只删除记录
* @param type {@link IRecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link IRecordHandler#TYPE_UPLOAD}
* 上传任务的记录
* @param removeEntity {@code true} 删除任务实体,
*/
public static void delTaskRecord(String filePath, int type, boolean removeFile,
boolean removeEntity) {
if (TextUtils.isEmpty(filePath)) {
throw new NullPointerException("删除记录失败,文件路径为空");
}
if (!filePath.startsWith("/")) {
throw new IllegalArgumentException(String.format("文件路径错误,filePath:%s", filePath));
}
if (type != IRecordHandler.TYPE_DOWNLOAD && type != IRecordHandler.TYPE_UPLOAD) {
throw new IllegalArgumentException("任务记录类型错误");
}
AbsEntity entity;
if (type == IRecordHandler.TYPE_DOWNLOAD) {
entity = DbEntity.findFirst(DownloadEntity.class, "downloadPath=?", filePath);
} else {
entity = DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath);
}
File targetFile = new File(filePath);
TaskRecord record = getTaskRecord(filePath);
if (entity == null || record == null) {
if (removeFile) {
removeTargetFile(targetFile);
}
removeRecord(filePath);
removeEntity(type, filePath);
return;
}
/*
* 处理任务未完成的情况
*/
if (!entity.isComplete()) {
if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { // 删除ts分片文件
removeTsCache(targetFile, record.bandWidth);
} else if (record.isBlock) { // 删除分块文件
removeBlockFile(record);
}
} else if (removeFile) { // 处理任务完成情况
if (record.taskType == TaskRecord.TYPE_M3U8_VOD) {
removeTsCache(targetFile, record.bandWidth);
}
removeTargetFile(targetFile);
}
// 成功与否都将删除记录
removeRecord(filePath);
if (removeEntity) {
removeEntity(type, filePath);
}
}
private static void removeTargetFile(File targetFile) {
if (targetFile.exists()) {
targetFile.delete();
}
}
private static void removeRecord(String filePath) {
ALog.i(TAG, "删除任务记录");
DbEntity.deleteData(ThreadRecord.class, "taskKey=?", filePath);
DbEntity.deleteData(TaskRecord.class, "filePath=?", filePath);
// 处理m3u8实体的删除
DbEntity.deleteData(M3U8Entity.class, "filePath=?", filePath);
}
/**
* 删除实体
*
* @param type {@link IRecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link IRecordHandler#TYPE_UPLOAD}
* 上传任务的记录
*/
private static void removeEntity(int type, String filePath) {
if (type == IRecordHandler.TYPE_DOWNLOAD) {
DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath);
} else {
DbEntity.deleteData(UploadEntity.class, "filePath=?", filePath);
}
}
/**
* 根据文件路径获取任务记录
*/
private static TaskRecord getTaskRecord(String filePath) {
List<RecordWrapper> recordWrapper =
DbEntity.findRelationData(RecordWrapper.class, "filePath=?", filePath);
if (recordWrapper == null
|| recordWrapper.isEmpty()
|| recordWrapper.get(0) == null
|| recordWrapper.get(0).taskRecord == null) {
return null;
} else {
return recordWrapper.get(0).taskRecord;
}
}
/**
* 删除多线程分块下载的分块文件
*/
private static void removeBlockFile(TaskRecord record) {
for (int i = 0, len = record.threadNum; i < len; i++) {
File partFile = new File(String.format(IRecordHandler.SUB_PATH, record.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
/**
* 删除ts文件
*/
private static void removeTsCache(File targetFile, long bandWidth) {
String cacheDir = null;
if (!targetFile.isDirectory()) {
cacheDir =
String.format("%s/.%s_%s", targetFile.getParent(), targetFile.getName(), bandWidth);
}
if (!TextUtils.isEmpty(cacheDir)) {
File cacheDirF = new File(cacheDir);
if (!cacheDirF.exists()) {
return;
}
File[] files = cacheDirF.listFiles();
for (File f : files) {
if (f.exists()) {
f.delete();
}
}
File cDir = new File(cacheDir);
if (cDir.exists()) {
cDir.delete();
}
}
}
/**
* 删除任务记录,默认删除文件,删除任务实体
*
* @param filePath 文件路径
* @param type {@link IRecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link IRecordHandler#TYPE_UPLOAD}
* 上传任务的记录
*/
public static void delTaskRecord(String filePath, int type) {
delTaskRecord(filePath, type, false, true);
}
/**
* 修改任务路径,修改文件路径和任务记录信息。如果是分块任务,则修改分块文件的路径。
*
* @param oldPath 旧的文件路径
* @param newPath 新的文件路径
*/
public static void modifyTaskRecord(String oldPath, String newPath) {
if (oldPath.equals(newPath)) {
ALog.w(TAG, "修改任务记录失败,新文件路径和旧文件路径一致");
return;
}
TaskRecord record = DbDataHelper.getTaskRecord(oldPath);
if (record == null) {
if (new File(oldPath).exists()) {
ALog.w(TAG, "修改任务记录失败,文件【" + oldPath + "】对应的任务记录不存在");
}
return;
}
if (!record.isBlock) {
File oldFile = new File(oldPath);
if (oldFile.exists()) {
oldFile.renameTo(new File(newPath));
}
}
record.filePath = newPath;
record.update();
// 修改线程记录
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
for (ThreadRecord tr : record.threadRecords) {
tr.taskKey = newPath;
File blockFile = new File(String.format(IRecordHandler.SUB_PATH, oldPath, tr.threadId));
if (blockFile.exists()) {
blockFile.renameTo(new File(String.format(IRecordHandler.SUB_PATH, newPath, tr.threadId)));
}
}
DbEntity.updateManyData(record.threadRecords);
}
}
/**
* 检查分块任务是否存在
*
* @param filePath 文件保存路径
* @return {@code true} 分块文件存在
*/
public static boolean blockTaskExists(String filePath) {
return new File(String.format(IRecordHandler.SUB_PATH, filePath, 0)).exists();
}
/**
* 获取分块文件的快大小
*
* @param fileLen 文件总长度
* @param blockId 分块id
* @param blockNum 分块数量
* @return 分块长度
*/
public static long getBlockLen(long fileLen, int blockId, int blockNum) {
final long averageLen = fileLen / blockNum;
return blockId == blockNum - 1 ? (fileLen - blockId * averageLen) : averageLen;
}
}

View File

@@ -0,0 +1,33 @@
package com.arialyy.aria.util;
/**
* Created by Aria.Lao on 2017/10/24.
* 正则表达式
*/
public interface Regular {
/**
* 获取文件名
*/
String REG_FILE_NAME = "[/|\\\\|//]";
/**
* IPV4地址匹配
*/
String REG_IP_V4 = "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])";
/**
* 匹配双字节字符、空格、制表符、换行符
*/
String REG_DOUBLE_CHAR_AND_SPACE = "[^\\x00-\\xff]|[\\s\\p{Zs}]";
/**
* 匹配window.location.replace
*/
String REG_WINLOD_REPLACE = "replace\\(\".*\"\\)";
/**
* 匹配BANDWIDTH
*/
String BANDWIDTH = "[0-9]{3,}";
}

View File

@@ -0,0 +1,193 @@
/*
* 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.util;
import android.text.TextUtils;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.ProtocolType;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.WeakHashMap;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Created by lyy on 2017/1/11.
* SSL证书工具
*/
public class SSLContextUtil {
private static final String TAG = "SSLContextUtil";
private static Map<String, SSLContext> SSL_CACHE = new WeakHashMap<>();
/**
* 从assets目录下加载证书
*
* @param caAlias CA证书别名
* @param caPath 保存在assets目录下的CA证书完整路径
* @param protocol 连接协议
*/
public static SSLContext getSSLContextFromAssets(String caAlias, String caPath,
@ProtocolType String protocol) {
if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)) {
return null;
}
try {
String cacheKey = getCacheKey(caAlias, caPath);
SSLContext sslContext = SSL_CACHE.get(cacheKey);
if (sslContext != null) {
return sslContext;
}
InputStream caInput = AriaConfig.getInstance().getAPP().getAssets().open(caPath);
Certificate ca = loadCert(caInput);
return createContext(caAlias, ca, protocol, cacheKey);
} catch (IOException | CertificateException e) {
e.printStackTrace();
}
return null;
}
/**
* 颁发服务器证书的 CA 未知
*
* @param caAlias CA证书别名
* @param caPath CA证书路径
* @param protocol 连接协议
*/
public static SSLContext getSSLContext(String caAlias, String caPath,
@ProtocolType String protocol) {
if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)) {
return null;
}
try {
String cacheKey = getCacheKey(caAlias, caPath);
SSLContext sslContext = SSL_CACHE.get(cacheKey);
if (sslContext != null) {
return sslContext;
}
Certificate ca = loadCert(new FileInputStream(caPath));
return createContext(caAlias, ca, protocol, cacheKey);
} catch (CertificateException | IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @param cacheKey 别名 + 证书路径,然后取md5
*/
private static SSLContext createContext(String caAlias, Certificate ca,
@ProtocolType String protocol, String cacheKey) {
try {
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry(caAlias, ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null);
// Create an SSLContext that uses our TrustManager
SSLContext context =
SSLContext.getInstance(TextUtils.isEmpty(protocol) ? ProtocolType.Default : protocol);
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
SSL_CACHE.put(cacheKey, context);
return context;
} catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException | KeyManagementException | UnrecoverableKeyException e) {
e.printStackTrace();
}
return null;
}
private static String getCacheKey(String alias, String path) {
return CommonUtil.getStrMd5(String.format("%s_%s", alias, path));
}
/**
* 加载CA证书
*
* @param is CA证书文件流
* @throws CertificateException
*/
private static Certificate loadCert(InputStream is) throws CertificateException, IOException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(is);
ALog.d(TAG, String.format("ca【%s】", ((X509Certificate) ca).getSubjectDN()));
is.close();
return ca;
}
/**
* 服务器证书不是由 CA 签署的,而是自签署时,获取默认的SSL
*/
public static SSLContext getDefaultSLLContext(@ProtocolType String protocol) {
SSLContext sslContext = null;
try {
sslContext =
SSLContext.getInstance(TextUtils.isEmpty(protocol) ? ProtocolType.Default : protocol);
sslContext.init(null, new TrustManager[] { trustManagers }, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
/**
* 创建自己的 TrustManager,这次直接信任服务器证书。这种方法具有前面所述的将应用与证书直接关联的所有弊端,但可以安全地操作。
*/
private static TrustManager trustManagers = new X509TrustManager() {
@Override public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
public static final HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}

View File

@@ -0,0 +1,496 @@
/*
* Copyright (c) 2014 Badoo Trading Limited
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Portions of documentation in this code are modifications based on work created and
* shared by Android Open Source Project and used according to terms described in the
* Apache License, Version 2.0
*/
package com.arialyy.aria.util;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Memory safer implementation of android.os.Handler
* Original implementation of Handlers always keeps hard reference to handler in queue of execution.
* If you create anonymous handler and post delayed message into it, it will keep all parent class
* for that time in memory even if it could be cleaned.
* This implementation is trickier, it will keep WeakReferences to runnables and messages,
* and GC could collect them once WeakHandler instance is not referenced any more
*
* @see android.os.Handler
*
* Created by Dmytro Voronkevych on 17/06/2014.
*/
@SuppressWarnings("unused") public class WeakHandler {
private final Handler.Callback mCallback;
// hard reference to Callback. We need to keep callback in memory
private final ExecHandler mExec;
private Lock mLock = new ReentrantLock();
@SuppressWarnings("ConstantConditions") @VisibleForTesting final ChainedRef mRunnables =
new ChainedRef(mLock, null);
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public WeakHandler() {
mCallback = null;
mExec = new ExecHandler();
}
/**
* Constructor associates this handler with the {@link Looper} for the
* current thread and takes a callback interface in which you can handle
* messages.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*
* @param callback The callback interface in which to handle messages, or null.
*/
public WeakHandler(@Nullable Handler.Callback callback) {
mCallback = callback; // Hard referencing body
mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler
}
/**
* Use the provided {@link Looper} instead of the default one.
*
* @param looper The looper, must not be null.
*/
public WeakHandler(@NonNull Looper looper) {
mCallback = null;
mExec = new ExecHandler(looper);
}
/**
* Use the provided {@link Looper} instead of the default one and take a callback
* interface in which to handle messages.
*
* @param looper The looper, must not be null.
* @param callback The callback interface in which to handle messages, or null.
*/
public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) {
mCallback = callback;
mExec = new ExecHandler(looper, new WeakReference<>(callback));
}
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(@NonNull Runnable r) {
return mExec.post(wrapRunnable(r));
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* The runnable will be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link android.os.SystemClock#uptimeMillis} time-base.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
return mExec.postAtTime(wrapRunnable(r), uptimeMillis);
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* The runnable will be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link android.os.SystemClock#uptimeMillis} time-base.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
* @see android.os.SystemClock#uptimeMillis
*/
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis);
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
* The runnable will be run on the thread to which this handler
* is attached.
*
* @param r The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed --
* if the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean postDelayed(Runnable r, long delayMillis) {
return mExec.postDelayed(wrapRunnable(r), delayMillis);
}
/**
* Posts a message to an object that implements Runnable.
* Causes the Runnable r to executed on the next iteration through the
* message queue. The runnable will be run on the thread to which this
* handler is attached.
* <b>This method is only for use in very special circumstances -- it
* can easily starve the message queue, cause ordering problems, or have
* other unexpected side-effects.</b>
*
* @param r The Runnable that will be executed.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean postAtFrontOfQueue(Runnable r) {
return mExec.postAtFrontOfQueue(wrapRunnable(r));
}
/**
* Remove any pending posts of Runnable r that are in the message queue.
*/
public final void removeCallbacks(Runnable r) {
final WeakRunnable runnable = mRunnables.remove(r);
if (runnable != null) {
mExec.removeCallbacks(runnable);
}
}
/**
* Remove any pending posts of Runnable <var>r</var> with Object
* <var>token</var> that are in the message queue. If <var>token</var> is null,
* all callbacks will be removed.
*/
public final void removeCallbacks(Runnable r, Object token) {
final WeakRunnable runnable = mRunnables.remove(r);
if (runnable != null) {
mExec.removeCallbacks(runnable, token);
}
}
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in callback,
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessage(Message msg) {
return mExec.sendMessage(msg);
}
/**
* Sends a Message containing only the what value.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessage(int what) {
return mExec.sendEmptyMessage(what);
}
/**
* Sends a Message containing only the what value, to be delivered
* after the specified amount of time elapses.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* @see #sendMessageDelayed(android.os.Message, long)
*/
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
return mExec.sendEmptyMessageDelayed(what, delayMillis);
}
/**
* Sends a Message containing only the what value, to be delivered
* at a specific time.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* @see #sendMessageAtTime(android.os.Message, long)
*/
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
return mExec.sendEmptyMessageAtTime(what, uptimeMillis);
}
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* callback, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
return mExec.sendMessageDelayed(msg, delayMillis);
}
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* You will receive it in callback, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
return mExec.sendMessageAtTime(msg, uptimeMillis);
}
/**
* Enqueue a message at the front of the message queue, to be processed on
* the next iteration of the message loop. You will receive it in
* callback, in the thread attached to this handler.
* <b>This method is only for use in very special circumstances -- it
* can easily starve the message queue, cause ordering problems, or have
* other unexpected side-effects.</b>
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessageAtFrontOfQueue(Message msg) {
return mExec.sendMessageAtFrontOfQueue(msg);
}
/**
* Remove any pending posts of messages with code 'what' that are in the
* message queue.
*/
public final void removeMessages(int what) {
mExec.removeMessages(what);
}
/**
* Remove any pending posts of messages with code 'what' and whose obj is
* 'object' that are in the message queue. If <var>object</var> is null,
* all messages will be removed.
*/
public final void removeMessages(int what, Object object) {
mExec.removeMessages(what, object);
}
/**
* Remove any pending posts of callbacks and sent messages whose
* <var>obj</var> is <var>token</var>. If <var>token</var> is null,
* all callbacks and messages will be removed.
*/
public final void removeCallbacksAndMessages(Object token) {
mExec.removeCallbacksAndMessages(token);
}
/**
* Check if there are any pending posts of messages with code 'what' in
* the message queue.
*/
public final boolean hasMessages(int what) {
return mExec.hasMessages(what);
}
/**
* Check if there are any pending posts of messages with code 'what' and
* whose obj is 'object' in the message queue.
*/
public final boolean hasMessages(int what, Object object) {
return mExec.hasMessages(what, object);
}
public final Looper getLooper() {
return mExec.getLooper();
}
private WeakRunnable wrapRunnable(@NonNull Runnable r) {
//noinspection ConstantConditions
if (r == null) {
throw new NullPointerException("Runnable can't be null");
}
final ChainedRef hardRef = new ChainedRef(mLock, r);
mRunnables.insertAfter(hardRef);
return hardRef.wrapper;
}
private static class ExecHandler extends Handler {
private final WeakReference<Handler.Callback> mCallback;
ExecHandler() {
mCallback = null;
}
ExecHandler(WeakReference<Handler.Callback> callback) {
mCallback = callback;
}
ExecHandler(Looper looper) {
super(looper);
mCallback = null;
}
ExecHandler(Looper looper, WeakReference<Handler.Callback> callback) {
super(looper);
mCallback = callback;
}
@Override public void handleMessage(@NonNull Message msg) {
if (mCallback == null) {
return;
}
final Handler.Callback callback = mCallback.get();
if (callback == null) { // Already disposed
return;
}
callback.handleMessage(msg);
}
}
static class WeakRunnable implements Runnable {
private final WeakReference<Runnable> mDelegate;
private final WeakReference<ChainedRef> mReference;
WeakRunnable(WeakReference<Runnable> delegate, WeakReference<ChainedRef> reference) {
mDelegate = delegate;
mReference = reference;
}
@Override public void run() {
final Runnable delegate = mDelegate.get();
final ChainedRef reference = mReference.get();
if (reference != null) {
reference.remove();
}
if (delegate != null) {
delegate.run();
}
}
}
static class ChainedRef {
@Nullable ChainedRef next;
@Nullable ChainedRef prev;
@NonNull final Runnable runnable;
@NonNull final WeakRunnable wrapper;
@NonNull Lock lock;
public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) {
this.runnable = r;
this.lock = lock;
this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this));
}
public WeakRunnable remove() {
lock.lock();
try {
if (prev != null) {
prev.next = next;
}
if (next != null) {
next.prev = prev;
}
prev = null;
next = null;
} finally {
lock.unlock();
}
return wrapper;
}
public void insertAfter(@NonNull ChainedRef candidate) {
lock.lock();
try {
if (this.next != null) {
this.next.prev = candidate;
}
candidate.next = this.next;
this.next = candidate;
candidate.prev = this;
} finally {
lock.unlock();
}
}
@Nullable public WeakRunnable remove(Runnable obj) {
lock.lock();
try {
ChainedRef curr = this.next; // Skipping head
while (curr != null) {
if (curr.runnable == obj) { // We do comparison exactly how Handler does inside
return curr.remove();
}
curr = curr.next;
}
} finally {
lock.unlock();
}
return null;
}
}
}