parent
d5cc67391e
commit
61ba26e58a
@ -0,0 +1,33 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package com.arialyy.aria.http.download |
||||||
|
|
||||||
|
import com.arialyy.aria.core.listener.AbsEventListener |
||||||
|
import com.arialyy.aria.core.task.DownloadTask |
||||||
|
import com.arialyy.aria.util.FileUtils |
||||||
|
|
||||||
|
class HttpDEventListener(task: DownloadTask) : AbsEventListener(task) { |
||||||
|
|
||||||
|
override fun handleCancel() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun handleComplete() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -1,249 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.http.download; |
|
||||||
|
|
||||||
import com.arialyy.aria.core.common.RequestEnum; |
|
||||||
import com.arialyy.aria.core.task.AbsThreadTaskAdapter; |
|
||||||
import com.arialyy.aria.core.task.ThreadConfig; |
|
||||||
import com.arialyy.aria.exception.AriaHTTPException; |
|
||||||
import com.arialyy.aria.http.ConnectionHelp; |
|
||||||
import com.arialyy.aria.http.HttpOption; |
|
||||||
import com.arialyy.aria.http.request.IRequest; |
|
||||||
import com.arialyy.aria.util.ALog; |
|
||||||
import com.arialyy.aria.util.BufferedRandomAccessFile; |
|
||||||
import java.io.BufferedInputStream; |
|
||||||
import java.io.FileOutputStream; |
|
||||||
import java.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.io.OutputStreamWriter; |
|
||||||
import java.net.HttpURLConnection; |
|
||||||
import java.net.MalformedURLException; |
|
||||||
import java.net.URLEncoder; |
|
||||||
import java.nio.ByteBuffer; |
|
||||||
import java.nio.channels.Channels; |
|
||||||
import java.nio.channels.FileChannel; |
|
||||||
import java.nio.channels.ReadableByteChannel; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
import timber.log.Timber; |
|
||||||
|
|
||||||
/** |
|
||||||
* Created by lyy on 2017/1/18. 下载线程 |
|
||||||
*/ |
|
||||||
final class HttpDThreadTaskAdapter extends AbsThreadTaskAdapter { |
|
||||||
|
|
||||||
HttpDThreadTaskAdapter(ThreadConfig threadConfig) { |
|
||||||
super(threadConfig); |
|
||||||
} |
|
||||||
|
|
||||||
private HttpDTaskOption getTaskOption() { |
|
||||||
return (HttpDTaskOption) getThreadConfig().getOption(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override protected void handlerThreadTask() { |
|
||||||
HttpURLConnection conn = null; |
|
||||||
BufferedInputStream is = null; |
|
||||||
BufferedRandomAccessFile file = null; |
|
||||||
try { |
|
||||||
HttpDTaskOption taskOption = getTaskOption(); |
|
||||||
HttpOption option = taskOption.getHttpOption(); |
|
||||||
conn = IRequest.Companion.getRequest(option).getDConnection(taskOption.getSourUrl(), option); |
|
||||||
if (!taskOption.isSupportResume()) { |
|
||||||
Timber.w("this task not support resume, url: %s", taskOption.getSourUrl()); |
|
||||||
}else { |
|
||||||
conn.setRequestProperty("Range", |
|
||||||
String.format("bytes=%s-%s", getThreadConfig().getBlockRecord().getStartLocation(), |
|
||||||
(getThreadConfig().getBlockRecord().getEndLocation() - 1))); |
|
||||||
} |
|
||||||
|
|
||||||
ConnectionHelp.setConnectParam(mTaskOption, conn); |
|
||||||
conn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); |
|
||||||
conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数
|
|
||||||
if (mTaskOption.isChunked()) { |
|
||||||
conn.setDoInput(true); |
|
||||||
conn.setChunkedStreamingMode(0); |
|
||||||
} |
|
||||||
conn.connect(); |
|
||||||
|
|
||||||
is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); |
|
||||||
if (mTaskOption.isChunked()) { |
|
||||||
readChunked(is); |
|
||||||
} else if (getThreadConfig().isBlock) { |
|
||||||
readDynamicFile(is); |
|
||||||
} else { |
|
||||||
//创建可设置位置的文件
|
|
||||||
file = |
|
||||||
new BufferedRandomAccessFile(getThreadConfig().tempFile, "rwd", |
|
||||||
getTaskConfig().getBuffSize()); |
|
||||||
//设置每条线程写入文件的位置
|
|
||||||
if (getThreadRecord().startLocation > 0) { |
|
||||||
file.seek(getThreadRecord().startLocation); |
|
||||||
} |
|
||||||
readNormal(is, file); |
|
||||||
handleComplete(); |
|
||||||
} |
|
||||||
} catch (MalformedURLException e) { |
|
||||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), |
|
||||||
getEntity().getFilePath(), getEntity().getUrl()), e), false); |
|
||||||
} catch (IOException e) { |
|
||||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), |
|
||||||
getEntity().getFilePath(), getEntity().getUrl()), e), true); |
|
||||||
} catch (ArrayIndexOutOfBoundsException e) { |
|
||||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), |
|
||||||
getEntity().getFilePath(), getEntity().getUrl()), e), false); |
|
||||||
} catch (Exception e) { |
|
||||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), |
|
||||||
getEntity().getFilePath(), getEntity().getUrl()), e), false); |
|
||||||
} finally { |
|
||||||
try { |
|
||||||
if (file != null) { |
|
||||||
file.close(); |
|
||||||
} |
|
||||||
if (is != null) { |
|
||||||
is.close(); |
|
||||||
} |
|
||||||
if (conn != null) { |
|
||||||
conn.getInputStream().close(); |
|
||||||
conn.disconnect(); |
|
||||||
} |
|
||||||
} catch (IOException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 读取chunked数据 |
|
||||||
*/ |
|
||||||
private void readChunked(InputStream is) { |
|
||||||
FileOutputStream fos = null; |
|
||||||
try { |
|
||||||
fos = new FileOutputStream(getThreadConfig().tempFile, true); |
|
||||||
byte[] buffer = new byte[getTaskConfig().getBuffSize()]; |
|
||||||
int len; |
|
||||||
while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { |
|
||||||
if (getThreadTask().isBreak()) { |
|
||||||
break; |
|
||||||
} |
|
||||||
if (mSpeedBandUtil != null) { |
|
||||||
mSpeedBandUtil.limitNextBytes(len); |
|
||||||
} |
|
||||||
fos.write(buffer, 0, len); |
|
||||||
progress(len); |
|
||||||
} |
|
||||||
handleComplete(); |
|
||||||
} catch (IOException e) { |
|
||||||
fail(new AriaHTTPException( |
|
||||||
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(), |
|
||||||
getThreadConfig().url), e), true); |
|
||||||
} finally { |
|
||||||
if (fos != null) { |
|
||||||
try { |
|
||||||
fos.close(); |
|
||||||
} catch (IOException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 动态长度文件读取方式 |
|
||||||
*/ |
|
||||||
private void readDynamicFile(InputStream is) { |
|
||||||
FileOutputStream fos = null; |
|
||||||
FileChannel foc = null; |
|
||||||
ReadableByteChannel fic = null; |
|
||||||
try { |
|
||||||
int len; |
|
||||||
fos = new FileOutputStream(getThreadConfig().tempFile, true); |
|
||||||
foc = fos.getChannel(); |
|
||||||
fic = Channels.newChannel(is); |
|
||||||
|
|
||||||
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); |
|
||||||
//如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。
|
|
||||||
|
|
||||||
while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) { |
|
||||||
if (getThreadTask().isBreak()) { |
|
||||||
break; |
|
||||||
} |
|
||||||
if (mSpeedBandUtil != null) { |
|
||||||
mSpeedBandUtil.limitNextBytes(len); |
|
||||||
} |
|
||||||
if (getRangeProgress() + len >= getThreadRecord().endLocation) { |
|
||||||
len = (int) (getThreadRecord().endLocation - getRangeProgress()); |
|
||||||
bf.flip(); |
|
||||||
fos.write(bf.array(), 0, len); |
|
||||||
bf.compact(); |
|
||||||
progress(len); |
|
||||||
break; |
|
||||||
} else { |
|
||||||
bf.flip(); |
|
||||||
foc.write(bf); |
|
||||||
bf.compact(); |
|
||||||
progress(len); |
|
||||||
} |
|
||||||
} |
|
||||||
handleComplete(); |
|
||||||
} catch (IOException e) { |
|
||||||
fail(new AriaHTTPException( |
|
||||||
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(), |
|
||||||
getThreadConfig().url), e), true); |
|
||||||
} finally { |
|
||||||
try { |
|
||||||
if (fos != null) { |
|
||||||
fos.flush(); |
|
||||||
fos.close(); |
|
||||||
} |
|
||||||
if (foc != null) { |
|
||||||
foc.close(); |
|
||||||
} |
|
||||||
if (fic != null) { |
|
||||||
fic.close(); |
|
||||||
} |
|
||||||
} catch (IOException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 读取普通的文件流 |
|
||||||
*/ |
|
||||||
private void readNormal(InputStream is, BufferedRandomAccessFile file) |
|
||||||
throws IOException { |
|
||||||
byte[] buffer = new byte[getTaskConfig().getBuffSize()]; |
|
||||||
int len; |
|
||||||
while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { |
|
||||||
if (getThreadTask().isBreak()) { |
|
||||||
break; |
|
||||||
} |
|
||||||
if (mSpeedBandUtil != null) { |
|
||||||
mSpeedBandUtil.limitNextBytes(len); |
|
||||||
} |
|
||||||
file.write(buffer, 0, len); |
|
||||||
progress(len); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override public void cancel() { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Override public void stop() { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
@ -1,265 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package com.arialyy.aria.core.manager; |
|
||||||
|
|
||||||
import android.text.TextUtils; |
|
||||||
import com.arialyy.aria.core.task.ITask; |
|
||||||
import com.arialyy.aria.core.task.IThreadTask; |
|
||||||
import com.arialyy.aria.util.ALog; |
|
||||||
import com.arialyy.aria.util.CommonUtil; |
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||||
import java.util.concurrent.Future; |
|
||||||
import java.util.concurrent.SynchronousQueue; |
|
||||||
import java.util.concurrent.ThreadPoolExecutor; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
import java.util.concurrent.locks.ReentrantLock; |
|
||||||
|
|
||||||
/** |
|
||||||
* 线程任务管理器 |
|
||||||
*/ |
|
||||||
@Deprecated |
|
||||||
public class ThreadTaskManager1 { |
|
||||||
private final String TAG = CommonUtil.getClassName(this); |
|
||||||
private static volatile ThreadTaskManager INSTANCE = null; |
|
||||||
private static final int CORE_POOL_NUM = 20; |
|
||||||
private static final ReentrantLock LOCK = new ReentrantLock(); |
|
||||||
private final ThreadPoolExecutor mExePool; |
|
||||||
private final Map<Integer, Set<FutureContainer>> mThreadTasks = new ConcurrentHashMap<>(); |
|
||||||
|
|
||||||
public static synchronized ThreadTaskManager getInstance() { |
|
||||||
if (INSTANCE == null) { |
|
||||||
INSTANCE = new ThreadTaskManager(); |
|
||||||
} |
|
||||||
return INSTANCE; |
|
||||||
} |
|
||||||
|
|
||||||
private ThreadTaskManager() { |
|
||||||
mExePool = new ThreadPoolExecutor(CORE_POOL_NUM, Integer.MAX_VALUE, |
|
||||||
60L, TimeUnit.SECONDS, |
|
||||||
new SynchronousQueue<Runnable>()); |
|
||||||
mExePool.allowsCoreThreadTimeOut(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 删除所有线程任务 |
|
||||||
*/ |
|
||||||
public void removeAllThreadTask() { |
|
||||||
if (mThreadTasks.isEmpty()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
try { |
|
||||||
LOCK.tryLock(2, TimeUnit.SECONDS); |
|
||||||
for (Set<FutureContainer> threads : mThreadTasks.values()) { |
|
||||||
for (FutureContainer container : threads) { |
|
||||||
if (container.future.isDone() || container.future.isCancelled()) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
container.threadTask.destroy(); |
|
||||||
} |
|
||||||
threads.clear(); |
|
||||||
} |
|
||||||
mThreadTasks.clear(); |
|
||||||
} catch (InterruptedException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
LOCK.unlock(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 启动线程任务 |
|
||||||
* |
|
||||||
* @param taskId {@link ITask#getTaskId()} |
|
||||||
* @param threadTask 线程任务{@link IThreadTask} |
|
||||||
*/ |
|
||||||
public void startThread(Integer taskId, IThreadTask threadTask) { |
|
||||||
try { |
|
||||||
LOCK.tryLock(2, TimeUnit.SECONDS); |
|
||||||
if (mExePool.isShutdown()) { |
|
||||||
ALog.e(TAG, "线程池已经关闭"); |
|
||||||
return; |
|
||||||
} |
|
||||||
Set<FutureContainer> temp = mThreadTasks.get(taskId); |
|
||||||
if (temp == null) { |
|
||||||
temp = new HashSet<>(); |
|
||||||
mThreadTasks.put(taskId, temp); |
|
||||||
} |
|
||||||
FutureContainer container = new FutureContainer(); |
|
||||||
container.threadTask = threadTask; |
|
||||||
container.future = mExePool.submit(threadTask); |
|
||||||
temp.add(container); |
|
||||||
} catch (Exception e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
LOCK.unlock(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 任务是否在执行 |
|
||||||
* |
|
||||||
* @return {@code true} 任务正在运行 |
|
||||||
*/ |
|
||||||
public boolean taskIsRunning(Integer taskId) { |
|
||||||
return mThreadTasks.get(taskId) != null; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 停止任务的所有线程 |
|
||||||
*/ |
|
||||||
public void removeTaskThread(Integer taskId) { |
|
||||||
try { |
|
||||||
LOCK.tryLock(2, TimeUnit.SECONDS); |
|
||||||
if (mExePool.isShutdown()) { |
|
||||||
ALog.e(TAG, "线程池已经关闭"); |
|
||||||
return; |
|
||||||
} |
|
||||||
Set<FutureContainer> temp = mThreadTasks.get(taskId); |
|
||||||
if (temp != null && temp.size() > 0) { |
|
||||||
for (FutureContainer container : temp) { |
|
||||||
if (container.future.isDone() || container.future.isCancelled()) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
container.threadTask.destroy(); |
|
||||||
} |
|
||||||
temp.clear(); |
|
||||||
mThreadTasks.remove(taskId); |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
LOCK.unlock(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 根据线程名删除任务的中的线程 |
|
||||||
* |
|
||||||
* @param threadName 线程名 |
|
||||||
* @return true 删除线程成功;false 删除线程失败 |
|
||||||
*/ |
|
||||||
public boolean removeSingleTaskThread(Integer taskId, String threadName) { |
|
||||||
try { |
|
||||||
LOCK.tryLock(2, TimeUnit.SECONDS); |
|
||||||
if (mExePool.isShutdown()) { |
|
||||||
ALog.e(TAG, "线程池已经关闭"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (TextUtils.isEmpty(threadName)) { |
|
||||||
ALog.e(TAG, "线程名为空"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
Set<FutureContainer> temp = mThreadTasks.get(taskId); |
|
||||||
if (temp != null && temp.size() > 0) { |
|
||||||
FutureContainer tempC = null; |
|
||||||
for (FutureContainer container : temp) { |
|
||||||
if (container.threadTask.getThreadName().equals(threadName)) { |
|
||||||
tempC = container; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (tempC != null) { |
|
||||||
tempC.threadTask.destroy(); |
|
||||||
temp.remove(tempC); |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
LOCK.unlock(); |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 删除单个线程任务 |
|
||||||
* |
|
||||||
* @param task 线程任务 |
|
||||||
*/ |
|
||||||
public boolean removeSingleTaskThread(Integer taskId, IThreadTask task) { |
|
||||||
try { |
|
||||||
LOCK.tryLock(2, TimeUnit.SECONDS); |
|
||||||
if (mExePool.isShutdown()) { |
|
||||||
ALog.e(TAG, "线程池已经关闭"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (task == null) { |
|
||||||
ALog.e(TAG, "线程任务为空"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
Set<FutureContainer> temp = mThreadTasks.get(taskId); |
|
||||||
if (temp != null && temp.size() > 0) { |
|
||||||
FutureContainer tempC = null; |
|
||||||
for (FutureContainer container : temp) { |
|
||||||
if (container.threadTask == task) { |
|
||||||
tempC = container; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (tempC != null) { |
|
||||||
task.destroy(); |
|
||||||
temp.remove(tempC); |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
LOCK.unlock(); |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 重试线程任务 |
|
||||||
* |
|
||||||
* @param task 线程任务 |
|
||||||
*/ |
|
||||||
public void retryThread(IThreadTask task) { |
|
||||||
try { |
|
||||||
LOCK.tryLock(2, TimeUnit.SECONDS); |
|
||||||
if (mExePool.isShutdown()) { |
|
||||||
ALog.e(TAG, "线程池已经关闭"); |
|
||||||
return; |
|
||||||
} |
|
||||||
try { |
|
||||||
if (task == null || task.isDestroy()) { |
|
||||||
ALog.e(TAG, "线程为空或线程已经中断"); |
|
||||||
return; |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
ALog.e(TAG, "", e); |
|
||||||
return; |
|
||||||
} |
|
||||||
mExePool.submit(task); |
|
||||||
} catch (Exception e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
LOCK.unlock(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static class FutureContainer { |
|
||||||
Future future; |
|
||||||
IThreadTask threadTask; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,82 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package com.arialyy.aria.core.task |
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap |
||||||
|
import java.util.concurrent.TimeUnit.SECONDS |
||||||
|
import java.util.concurrent.locks.ReentrantLock |
||||||
|
|
||||||
|
object ThreadTaskManager2 { |
||||||
|
private val mThreadTasks: ConcurrentHashMap<Int, MutableSet<IThreadTask>> = ConcurrentHashMap() |
||||||
|
private val LOCK = ReentrantLock() |
||||||
|
|
||||||
|
/** |
||||||
|
* 任务是否在执行 |
||||||
|
* |
||||||
|
* @return `true` 任务正在运行 |
||||||
|
*/ |
||||||
|
fun taskIsRunning(taskId: Int): Boolean { |
||||||
|
return mThreadTasks[taskId] != null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* stop thread task |
||||||
|
* @param isRemoveTask if true, remove task and block |
||||||
|
*/ |
||||||
|
fun stopThreadTask(taskId: Int, isRemoveTask: Boolean = false) { |
||||||
|
try { |
||||||
|
LOCK.tryLock(2, SECONDS) |
||||||
|
val threadTaskList: MutableSet<IThreadTask>? = mThreadTasks[taskId] |
||||||
|
threadTaskList?.forEach { |
||||||
|
if (isRemoveTask) { |
||||||
|
it.cancel() |
||||||
|
return@forEach |
||||||
|
} |
||||||
|
it.stop() |
||||||
|
} |
||||||
|
mThreadTasks.remove(taskId) |
||||||
|
|
||||||
|
} catch (e: Exception) { |
||||||
|
e.printStackTrace() |
||||||
|
} finally { |
||||||
|
LOCK.unlock() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 删除所有线程任务 |
||||||
|
*/ |
||||||
|
fun removeAllThreadTask() { |
||||||
|
if (mThreadTasks.isEmpty()) { |
||||||
|
return |
||||||
|
} |
||||||
|
try { |
||||||
|
LOCK.tryLock(2, SECONDS) |
||||||
|
for (threads in mThreadTasks.values) { |
||||||
|
for (tt in threads) { |
||||||
|
tt.stop() |
||||||
|
} |
||||||
|
threads.clear() |
||||||
|
} |
||||||
|
mThreadTasks.clear() |
||||||
|
} catch (e: InterruptedException) { |
||||||
|
e.printStackTrace() |
||||||
|
} finally { |
||||||
|
LOCK.unlock() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,388 @@ |
|||||||
|
package com.arialyy.aria.util |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.* |
||||||
|
import android.database.Cursor |
||||||
|
import android.net.Uri |
||||||
|
import android.os.Build |
||||||
|
import android.os.Environment |
||||||
|
import android.provider.* |
||||||
|
import androidx.core.content.FileProvider |
||||||
|
import com.arialyy.aria.core.DuaContext |
||||||
|
import timber.log.Timber |
||||||
|
import java.io.* |
||||||
|
|
||||||
|
/** |
||||||
|
* # FileUri |
||||||
|
* |
||||||
|
* - Uri & Path Tool |
||||||
|
* |
||||||
|
* @author javakam |
||||||
|
* @date 2020/8/24 11:24 |
||||||
|
*/ |
||||||
|
object FileUri { |
||||||
|
|
||||||
|
//Android R |
||||||
|
//---------------------------------------------------------------- |
||||||
|
|
||||||
|
/** |
||||||
|
* `MANAGE_EXTERNAL_STORAGE` 权限检查 |
||||||
|
* |
||||||
|
* @return `true` Have permission |
||||||
|
*/ |
||||||
|
fun isExternalStorageManager(): Boolean = |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Environment.isExternalStorageManager() else false |
||||||
|
|
||||||
|
/** |
||||||
|
* 跳转到 `MANAGE_EXTERNAL_STORAGE` 权限设置页面 |
||||||
|
* |
||||||
|
* @return `true` Has been set |
||||||
|
*/ |
||||||
|
fun jumpManageAppAllFilesPermissionSetting( |
||||||
|
context: Context, |
||||||
|
isNewTask: Boolean = false, |
||||||
|
): Boolean { |
||||||
|
if (isExternalStorageManager()) return true |
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
||||||
|
try { |
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) |
||||||
|
intent.data = Uri.parse("package:${context.packageName}") |
||||||
|
if (isNewTask) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
||||||
|
context.startActivity(intent) |
||||||
|
} catch (e: Exception) { |
||||||
|
Timber.e("jumpManageAppAllFilesPermissionSetting: $e") |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
//从 FilePath 中获取 Uri (Get Uri from FilePath) |
||||||
|
//---------------------------------------------------------------- |
||||||
|
|
||||||
|
fun getUriByPath(path: String?): Uri? = |
||||||
|
if (path.isNullOrBlank()) null else getUriByFile(File(path)) |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a content URI for a given file. |
||||||
|
* |
||||||
|
* @param file The file. |
||||||
|
* @param isOriginal true content:// or file:// ; false file://xxx |
||||||
|
* @return a content URI for a given file |
||||||
|
*/ |
||||||
|
fun getUriByFile(file: File?, isOriginal: Boolean = false): Uri? { |
||||||
|
return if (isOriginal) Uri.fromFile(file) |
||||||
|
else { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
||||||
|
val authority = DuaContext.context.packageName + AUTHORITY |
||||||
|
FileProvider.getUriForFile(DuaContext.context, authority, file ?: return null) |
||||||
|
} else { |
||||||
|
Uri.fromFile(file) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun getShareUri(path: String?): Uri? = |
||||||
|
if (path.isNullOrBlank()) null else getUriByFile(File(path), isOriginal = false) |
||||||
|
|
||||||
|
/** |
||||||
|
* @return content:// or file:// |
||||||
|
*/ |
||||||
|
fun getShareUri(file: File?): Uri? = getUriByFile(file, isOriginal = false) |
||||||
|
|
||||||
|
fun getOriginalUri(path: String?): Uri? = |
||||||
|
if (path.isNullOrBlank()) null else getUriByFile(File(path), isOriginal = true) |
||||||
|
|
||||||
|
/** |
||||||
|
* @return file://xxx |
||||||
|
*/ |
||||||
|
fun getOriginalUri(file: File?): Uri? = getUriByFile(file, isOriginal = true) |
||||||
|
|
||||||
|
//获取Uri对应的文件路径, Compatible with API 26 |
||||||
|
//---------------------------------------------------------------- |
||||||
|
|
||||||
|
/** |
||||||
|
* ### Get the file path through Uri |
||||||
|
* |
||||||
|
* - Need permission: RequiresPermission(permission.READ_EXTERNAL_STORAGE) |
||||||
|
* |
||||||
|
* - Modified from: https://github.com/coltoscosmin/FileUtils/blob/master/FileUtils.java |
||||||
|
* |
||||||
|
* @return file path |
||||||
|
*/ |
||||||
|
fun getPathByUri(uri: Uri?): String? { |
||||||
|
return uri?.use { |
||||||
|
Timber.i( |
||||||
|
"FileUri getPathByUri -> " + |
||||||
|
"Uri: " + uri + |
||||||
|
", Authority: " + uri.authority + |
||||||
|
", Fragment: " + uri.fragment + |
||||||
|
", Port: " + uri.port + |
||||||
|
", Query: " + uri.query + |
||||||
|
", Scheme: " + uri.scheme + |
||||||
|
", Host: " + uri.host + |
||||||
|
", Segments: " + uri.pathSegments.toString() |
||||||
|
) |
||||||
|
|
||||||
|
// 以 file:// 开头的使用第三方应用打开 (open with third-party applications starting with file://) |
||||||
|
if (ContentResolver.SCHEME_FILE.equals(uri.scheme, ignoreCase = true)) return getDataColumn( |
||||||
|
uri |
||||||
|
) |
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt") |
||||||
|
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT |
||||||
|
|
||||||
|
// Before 4.4 , API 19 content:// 开头, 比如 content://media/external/images/media/123 |
||||||
|
if (!isKitKat && ContentResolver.SCHEME_CONTENT.equals(uri.scheme, true)) { |
||||||
|
if (isGooglePhotosUri(uri)) return uri.lastPathSegment |
||||||
|
return getDataColumn(uri) |
||||||
|
} |
||||||
|
|
||||||
|
val context = DuaContext.context |
||||||
|
// After 4.4 , API 19 |
||||||
|
// DocumentProvider |
||||||
|
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { |
||||||
|
// LocalStorageProvider |
||||||
|
if (isLocalStorageDocument(uri)) { |
||||||
|
// The path is the id |
||||||
|
return DocumentsContract.getDocumentId(uri); |
||||||
|
} |
||||||
|
// ExternalStorageProvider |
||||||
|
if (isExternalStorageDocument(uri)) { |
||||||
|
val docId = DocumentsContract.getDocumentId(uri) |
||||||
|
val split = docId.split(":").toTypedArray() |
||||||
|
val type = split[0] |
||||||
|
if ("primary".equals(type, ignoreCase = true)) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
||||||
|
return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) |
||||||
|
.toString() + File.separator + split[1] |
||||||
|
} else { |
||||||
|
@Suppress("DEPRECATION") |
||||||
|
return Environment.getExternalStorageDirectory() |
||||||
|
.toString() + File.separator + split[1] |
||||||
|
} |
||||||
|
} else if ("home".equals(type, ignoreCase = true)) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
||||||
|
return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) |
||||||
|
.toString() + File.separator + "documents" + File.separator + split[1] |
||||||
|
} else { |
||||||
|
@Suppress("DEPRECATION") |
||||||
|
return Environment.getExternalStorageDirectory() |
||||||
|
.toString() + File.separator + "documents" + File.separator + split[1] |
||||||
|
} |
||||||
|
} else { |
||||||
|
@Suppress("DEPRECATION") |
||||||
|
val sdcardPath = |
||||||
|
Environment.getExternalStorageDirectory() |
||||||
|
.toString() + File.separator + "documents" + File.separator + split[1] |
||||||
|
return if (sdcardPath.startsWith("file://")) { |
||||||
|
sdcardPath.replace("file://", "") |
||||||
|
} else { |
||||||
|
sdcardPath |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// DownloadsProvider |
||||||
|
else if (isDownloadsDocument(uri)) { |
||||||
|
val id = DocumentsContract.getDocumentId(uri) |
||||||
|
if (id != null && id.startsWith("raw:")) { |
||||||
|
return id.substring(4) |
||||||
|
} |
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { |
||||||
|
val contentUriPrefixesToTry = arrayOf( |
||||||
|
"content://downloads/public_downloads", |
||||||
|
"content://downloads/my_downloads", |
||||||
|
"content://downloads/all_downloads" |
||||||
|
) |
||||||
|
for (contentUriPrefix in contentUriPrefixesToTry) { |
||||||
|
val contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), id.toLong()) |
||||||
|
try { |
||||||
|
val path = getDataColumn(contentUri) |
||||||
|
if (!path.isNullOrBlank()) return path |
||||||
|
} catch (e: Exception) { |
||||||
|
Timber.e(e.toString()) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
//testPath(uri) |
||||||
|
return getDataColumn(uri) |
||||||
|
} |
||||||
|
} |
||||||
|
// MediaProvider |
||||||
|
else if (isMediaDocument(uri)) { |
||||||
|
val docId = DocumentsContract.getDocumentId(uri) |
||||||
|
val split = docId.split(":").toTypedArray() |
||||||
|
val contentUri: Uri? = when (split[0]) { |
||||||
|
"image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI |
||||||
|
"video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI |
||||||
|
"audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI |
||||||
|
"download" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
||||||
|
MediaStore.Downloads.EXTERNAL_CONTENT_URI |
||||||
|
} else null |
||||||
|
else -> null |
||||||
|
} |
||||||
|
val selectionArgs = arrayOf(split[1]) |
||||||
|
return getDataColumn(contentUri, "_id=?", selectionArgs) |
||||||
|
} |
||||||
|
|
||||||
|
//GoogleDriveProvider |
||||||
|
else if (isGoogleDriveUri(uri)) { |
||||||
|
return getGoogleDriveFilePath(uri, context) |
||||||
|
} |
||||||
|
} |
||||||
|
// MediaStore (and general) |
||||||
|
else if ("content".equals(uri.scheme, ignoreCase = true)) { |
||||||
|
// Return the remote address |
||||||
|
if (isGooglePhotosUri(uri)) { |
||||||
|
return uri.lastPathSegment |
||||||
|
} |
||||||
|
// Google drive legacy provider |
||||||
|
else if (isGoogleDriveUri(uri)) { |
||||||
|
return getGoogleDriveFilePath(uri, context) |
||||||
|
} |
||||||
|
// Huawei |
||||||
|
else if (isHuaWeiUri(uri)) { |
||||||
|
val uriPath = getDataColumn(uri) ?: uri.toString() |
||||||
|
//content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/ |
||||||
|
if (uriPath.startsWith("/root")) { |
||||||
|
return uriPath.replace("/root".toRegex(), "") |
||||||
|
} |
||||||
|
} |
||||||
|
return getDataColumn(uri) |
||||||
|
} |
||||||
|
return getDataColumn(uri) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* BUG : 部分机型进入"文件管理器" 执行到 cursor.getColumnIndexOrThrow(column);出现 |
||||||
|
* Caused by: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: [] |
||||||
|
* |
||||||
|
* Fixed : |
||||||
|
* https://stackoverflow.com/questions/42508383/illegalargumentexception-column-data-does-not-exist |
||||||
|
* |
||||||
|
*/ |
||||||
|
private fun getDataColumn( |
||||||
|
uri: Uri?, |
||||||
|
selection: String? = null, |
||||||
|
selectionArgs: Array<String>? = null |
||||||
|
): String? { |
||||||
|
@Suppress("DEPRECATION") |
||||||
|
val column = MediaStore.Files.FileColumns.DATA |
||||||
|
val projection = arrayOf(column) |
||||||
|
try { |
||||||
|
DuaContext.context.contentResolver.query( |
||||||
|
uri ?: return null, |
||||||
|
projection, |
||||||
|
selection, |
||||||
|
selectionArgs, |
||||||
|
null |
||||||
|
)?.use { c: Cursor -> |
||||||
|
if (c.moveToFirst()) { |
||||||
|
val columnIndex = c.getColumnIndex(column) |
||||||
|
return c.getString(columnIndex) |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e: Throwable) { |
||||||
|
Timber.e("getDataColumn -> ${e.message}") |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
//The Uri to check |
||||||
|
//---------------------------------------------------------------- |
||||||
|
|
||||||
|
private fun getGoogleDriveFilePath(uri: Uri, context: Context): String? { |
||||||
|
context.contentResolver.query(uri, null, null, null, null)?.use { c: Cursor -> |
||||||
|
/* |
||||||
|
Get the column indexes of the data in the Cursor, |
||||||
|
move to the first row in the Cursor, get the data, and display it. |
||||||
|
*/ |
||||||
|
val nameIndex: Int = c.getColumnIndex(OpenableColumns.DISPLAY_NAME) |
||||||
|
//val sizeIndex: Int = c.getColumnIndex(OpenableColumns.SIZE) |
||||||
|
if (!c.moveToFirst()) { |
||||||
|
return uri.toString() |
||||||
|
} |
||||||
|
val name: String = c.getString(nameIndex) |
||||||
|
//val size = c.getLong(sizeIndex).toString() |
||||||
|
val file = File(context.cacheDir, name) |
||||||
|
|
||||||
|
var inputStream: InputStream? = null |
||||||
|
var outputStream: FileOutputStream? = null |
||||||
|
try { |
||||||
|
inputStream = context.contentResolver.openInputStream(uri) |
||||||
|
outputStream = FileOutputStream(file) |
||||||
|
var read = 0 |
||||||
|
val maxBufferSize = 1 * 1024 * 1024 |
||||||
|
val bytesAvailable: Int = inputStream?.available() ?: 0 |
||||||
|
val bufferSize = bytesAvailable.coerceAtMost(maxBufferSize) |
||||||
|
val buffers = ByteArray(bufferSize) |
||||||
|
while (inputStream?.read(buffers)?.also { read = it } != -1) { |
||||||
|
outputStream.write(buffers, 0, read) |
||||||
|
} |
||||||
|
} catch (e: Exception) { |
||||||
|
e.printStackTrace() |
||||||
|
} finally { |
||||||
|
inputStream?.close() |
||||||
|
outputStream?.close() |
||||||
|
} |
||||||
|
return file.path |
||||||
|
} |
||||||
|
return uri.toString() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param uri The Uri to check. |
||||||
|
* @return Whether the Uri authority is Google Photos. |
||||||
|
*/ |
||||||
|
fun isGooglePhotosUri(uri: Uri?): Boolean { |
||||||
|
return "com.google.android.apps.photos.content".equals(uri?.authority, true) |
||||||
|
} |
||||||
|
|
||||||
|
fun isGoogleDriveUri(uri: Uri?): Boolean { |
||||||
|
return "com.google.android.apps.docs.storage.legacy" == uri?.authority || "com.google.android.apps.docs.storage" == uri?.authority |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param uri The Uri to check. |
||||||
|
* @return Whether the Uri authority is local. |
||||||
|
*/ |
||||||
|
fun isLocalStorageDocument(uri: Uri?): Boolean { |
||||||
|
return AUTHORITY.equals(uri?.authority, true) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param uri The Uri to check. |
||||||
|
* @return Whether the Uri authority is ExternalStorageProvider. |
||||||
|
*/ |
||||||
|
private fun isExternalStorageDocument(uri: Uri?): Boolean { |
||||||
|
return "com.android.externalstorage.documents".equals(uri?.authority, true) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param uri The Uri to check. |
||||||
|
* @return Whether the Uri authority is DownloadsProvider. |
||||||
|
*/ |
||||||
|
private fun isDownloadsDocument(uri: Uri?): Boolean { |
||||||
|
return "com.android.providers.downloads.documents".equals(uri?.authority, true) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param uri The Uri to check. |
||||||
|
* @return Whether the Uri authority is MediaProvider. |
||||||
|
*/ |
||||||
|
private fun isMediaDocument(uri: Uri?): Boolean { |
||||||
|
return "com.android.providers.media.documents".equals(uri?.authority, true) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/ |
||||||
|
* |
||||||
|
* @param uri |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private fun isHuaWeiUri(uri: Uri?): Boolean { |
||||||
|
return "com.huawei.hidisk.fileprovider".equals(uri?.authority, true) |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue