From 9e590a609ebe389785a41e48d8d5e2e5ec3639ef Mon Sep 17 00:00:00 2001 From: AriaLyy <511455842@qq.com> Date: Wed, 22 Feb 2017 20:09:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8B=E8=BD=BD=E9=80=9F?= =?UTF-8?q?=E5=BA=A6=E6=85=A2=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 2 +- .../aria/core/task/ConnectionHelp.java | 1 + .../arialyy/aria/core/task/DownloadUtil.java | 38 +- .../aria/core/task/SingleThreadTask.java | 40 ++- .../aria/util/BufferedRandomAccessFile.java | 328 ++++++++++++++++++ .../single_task/SingleTaskActivity.java | 63 ++++ 6 files changed, 449 insertions(+), 23 deletions(-) create mode 100644 Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java diff --git a/.idea/misc.xml b/.idea/misc.xml index fbb68289..5d199810 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java b/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java index 8d264d5a..38ee2867 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java @@ -69,6 +69,7 @@ class ConnectionHelp { "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"); ////用于处理Disconnect 不起作用问题 //conn.setRequestProperty("Connection", "close"); + conn.setRequestProperty("Connection", "Keep-Alive"); return conn; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java index 21c8af7c..6f14d883 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java @@ -20,6 +20,7 @@ import android.content.Context; import android.util.Log; import android.util.SparseArray; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.CommonUtil; import java.io.File; import java.io.IOException; @@ -34,27 +35,27 @@ import java.util.concurrent.Executors; * Created by lyy on 2015/8/25. * 下载工具类 */ -final class DownloadUtil implements IDownloadUtil, Runnable { +public class DownloadUtil implements IDownloadUtil, Runnable { private static final String TAG = "DownloadUtil"; /** * 线程数 */ - private final int THREAD_NUM; + private final int THREAD_NUM; //下载监听 - private IDownloadListener mListener; - private int mConnectTimeOut = 5000 * 4; //连接超时时间 - private int mReadTimeOut = 5000 * 20; //流读取的超时时间 - private boolean isNewTask = true; + private IDownloadListener mListener; + private int mConnectTimeOut = 5000 * 4; //连接超时时间 + private int mReadTimeOut = 5000 * 20; //流读取的超时时间 + private boolean isNewTask = true; private boolean isSupportBreakpoint = true; - private Context mContext; - private DownloadEntity mDownloadEntity; + private Context mContext; + private DownloadEntity mDownloadEntity; private ExecutorService mFixedThreadPool; - private File mDownloadFile; //下载的文件 - private File mConfigFile;//下载信息配置文件 + private File mDownloadFile; //下载的文件 + private File mConfigFile;//下载信息配置文件 private SparseArray mTask = new SparseArray<>(); private DownloadStateConstance mConstance; - DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { + public DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { this(context, entity, downloadListener, 3); } @@ -259,7 +260,10 @@ final class DownloadUtil implements IDownloadUtil, Runnable { int fileLength = conn.getContentLength(); //必须建一个文件 CommonUtil.createFile(mDownloadFile.getPath()); - RandomAccessFile file = new RandomAccessFile(mDownloadFile.getPath(), "rwd"); + //RandomAccessFile file = new RandomAccessFile(mDownloadFile.getPath(), "rwd"); + ////设置文件长度 + //file.setLength(fileLength); + BufferedRandomAccessFile file = new BufferedRandomAccessFile(mDownloadFile.getPath(), "rwd", 8192); //设置文件长度 file.setLength(fileLength); mListener.onPostPre(fileLength); @@ -358,12 +362,12 @@ final class DownloadUtil implements IDownloadUtil, Runnable { */ final static class ConfigEntity { //文件大小 - long FILE_SIZE; + long FILE_SIZE; String DOWNLOAD_URL; - int THREAD_ID; - long START_LOCATION; - long END_LOCATION; - File TEMP_FILE; + int THREAD_ID; + long START_LOCATION; + long END_LOCATION; + File TEMP_FILE; boolean isSupportBreakpoint = true; String CONFIG_FILE_PATH; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java index f98d724c..07e94bca 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java @@ -15,7 +15,11 @@ */ package com.arialyy.aria.core.task; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.Log; +import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.CommonUtil; import java.io.File; import java.io.IOException; @@ -33,7 +37,7 @@ import java.util.Properties; final class SingleThreadTask implements Runnable { private static final String TAG = "SingleThreadTask"; // TODO: 2017/2/22 不能使用1024 否则最大速度不能超过3m - private static final int BUF_SIZE = 8196; + private static final int BUF_SIZE = 8192; private DownloadUtil.ConfigEntity mConfigEntity; private String mConfigFPath; private long mChildCurrentLocation = 0; @@ -77,9 +81,11 @@ final class SingleThreadTask implements Runnable { conn.setReadTimeout(mConstance.READ_TIME_OUT); //设置读取流的等待时间,必须设置该参数 is = conn.getInputStream(); //创建可设置位置的文件 - RandomAccessFile file = new RandomAccessFile(mConfigEntity.TEMP_FILE, "rwd"); - //设置每条线程写入文件的位置 + BufferedRandomAccessFile + file = new BufferedRandomAccessFile(mConfigEntity.TEMP_FILE, "rwd", 8192); + //设置文件长度 file.seek(mConfigEntity.START_LOCATION); + byte[] buffer = new byte[BUF_SIZE]; int len; //当前子线程的下载位置 @@ -175,12 +181,36 @@ final class SingleThreadTask implements Runnable { * 下载中 */ private void progress(long len) { - synchronized (LOCK) { + //synchronized (LOCK) { mChildCurrentLocation += len; mConstance.CURRENT_LOCATION += len; + //mListener.onProgress(mConstance.CURRENT_LOCATION); + //mHandler.post(t); + //handler.obtainMessage().sendToTarget(); + //} + mHandler.sendEmptyMessage(1); + } + Handler mHandler = new Handler(Looper.getMainLooper()){ + @Override public void handleMessage(Message msg) { + super.handleMessage(msg); mListener.onProgress(mConstance.CURRENT_LOCATION); } - } + }; + + Thread t = new Thread(new Runnable() { + @Override public void run() { + mListener.onProgress(mConstance.CURRENT_LOCATION); + } + }); + + //Handler handler = new Handler(){ + // @Override public void handleMessage(Message msg) { + // super.handleMessage(msg); + // mListener.onProgress(mConstance.CURRENT_LOCATION); + // } + //}; + + Thread thread = new Thread(); /** * 取消下载 diff --git a/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java b/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java new file mode 100644 index 00000000..167dbdf7 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java @@ -0,0 +1,328 @@ +/** + * 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.*; +import java.util.Arrays; + +//import org.apache.log4j.Logger; + +/** + * A BufferedRandomAccessFile is like a + * RandomAccessFile, but it uses a private buffer so that most + * operations do not require a disk access. + *

+ * + * Note: The operations on this class are unmonitored. Also, the correct + * functioning of the RandomAccessFile 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 index 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 BufferedRandomAccessFile on file + * in mode mode, 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 BufferedRandomAccessFile on the file named + * name in mode mode, 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 this.curr at position pos. + * If pos does not fall in the current buffer, it flushes the + * current buffer and loads the correct one.

+ * + * On exit from this routine this.curr == this.hi iff pos + * 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_; // start 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java index 7bc60d51..4d0bdff2 100644 --- a/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java @@ -33,6 +33,8 @@ import butterknife.Bind; import com.arialyy.aria.core.AMTarget; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.core.task.DownloadUtil; +import com.arialyy.aria.core.task.IDownloadListener; import com.arialyy.aria.core.task.Task; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.util.show.L; @@ -198,6 +200,67 @@ public class SingleTaskActivity extends BaseActivity { .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") .setDownloadName("test.apk") .start(); + //DownloadEntity entity = new DownloadEntity(); + //entity.setDownloadUrl(DOWNLOAD_URL); + //entity.setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk"); + //entity.setFileName("test.apk"); + //DownloadUtil util = new DownloadUtil(this, entity, new IDownloadListener() { + // long fileSize = 1; + // @Override public void supportBreakpoint(boolean support) { + // + // } + // + // @Override public void onCancel() { + // + // } + // + // @Override public void onFail() { + // + // } + // + // @Override public void onPre() { + // + // } + // + // @Override public void onPostPre(long fileSize) { + // this.fileSize = fileSize; + // } + // + // @Override public void onProgress(long currentLocation) { + // long current = currentLocation; + // long len = fileSize; + // if (len == 0) { + // mPb.setProgress(0); + // } else { + // mPb.setProgress((int) ((current * 100) / len)); + // } + // } + // + // @Override public void onChildComplete(long finishLocation) { + // + // } + // + // @Override public void onStart(long startLocation) { + // + // } + // + // @Override public void onChildResume(long resumeLocation) { + // + // } + // + // @Override public void onResume(long resumeLocation) { + // + // } + // + // @Override public void onStop(long stopLocation) { + // + // } + // + // @Override public void onComplete() { + // + // } + //}); + //util.startDownload(); } private void stop() {