组件化功能基本完成
This commit is contained in:
153
PublicComponent/src/main/java/com/arialyy/aria/util/ALog.java
Normal file
153
PublicComponent/src/main/java/com/arialyy/aria/util/ALog.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,}";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user