compile 'com.squareup.okhttp3:okhttp:3.6.0'
compile 'com.squareup.okio:okio:1.11.0'
defaultConfig {
·····
//最后一行加下面这句
// AndroidManifest.xml 里面UMENG_CHANNEL的value为 ${UMENG_CHANNEL_VALUE}
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]//添加一个默认渠道号
···
}
activity_main.xml
总布局:LinearLayout orientation---》vertical
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btn_download_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="downloadOrPauseAll"
android:text="全部下载" />
<Button
android:id="@+id/btn_cancel_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:onClick="cancelAll"
android:text="全部取消" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_file_name1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp" />
<TextView
android:id="@+id/tv_progress1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:text="0%" />
</LinearLayout>
<ProgressBar
android:id="@+id/pb_progress1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btn_download1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="downloadOrPause"
android:text="下载" />
<Button
android:id="@+id/btn_cancel1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:onClick="cancel"
android:text="取消" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_file_name2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp" />
<TextView
android:id="@+id/tv_progress2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:text="0%" />
</LinearLayout>
<ProgressBar
android:id="@+id/pb_progress2"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btn_download2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="downloadOrPause"
android:text="下载" />
<Button
android:id="@+id/btn_cancel2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:onClick="cancel"
android:text="取消" />
</LinearLayout>
DownloadListner
public interface DownloadListner {
void onFinished();
void onProgress(float progress);
void onPause();
void onCancel();
}
DownloadManager
public class DownloadManager {
private String DEFAULT_FILE_DIR;//默认下载目录
private Map<String, DownloadTask> mDownloadTasks;//文件下载任务索引,String为url,用来唯一区别并操作下载的文件
private static DownloadManager mInstance;
private static final String TAG = "DownloadManager";
/**
* 下载文件
*/
public void download(String... urls) {
//单任务开启下载或多任务开启下载
for (int i = 0, length = urls.length; i < length; i++) {
String url = urls[i];
if (mDownloadTasks.containsKey(url)) {
mDownloadTasks.get(url).start();
}
}
}
// 获取下载文件的名称
public String getFileName(String url) {
return url.substring(url.lastIndexOf("/") + 1);
}
/**
* 暂停
*/
public void pause(String... urls) {
//单任务暂停或多任务暂停下载
for (int i = 0, length = urls.length; i < length; i++) {
String url = urls[i];
if (mDownloadTasks.containsKey(url)) {
mDownloadTasks.get(url).pause();
}
}
}
/**
* 取消下载
*/
public void cancel(String... urls) {
//单任务取消或多任务取消下载
for (int i = 0, length = urls.length; i < length; i++) {
String url = urls[i];
if (mDownloadTasks.containsKey(url)) {
mDownloadTasks.get(url).cancel();
}
}
}
/**
* 添加下载任务
*/
public void add(String url, DownloadListner l) {
add(url, null, null, l);
}
/**
* 添加下载任务
*/
public void add(String url, String filePath, DownloadListner l) {
add(url, filePath, null, l);
}
/**
* 添加下载任务
*/
public void add(String url, String filePath, String fileName, DownloadListner l) {
if (TextUtils.isEmpty(filePath)) {//没有指定下载目录,使用默认目录
filePath = getDefaultDirectory();
}
if (TextUtils.isEmpty(fileName)) {
fileName = getFileName(url);
}
mDownloadTasks.put(url, new DownloadTask(new FilePoint(url, filePath, fileName), l));
}
/**
* 默认下载目录
* @return
*/
private String getDefaultDirectory() {
if (TextUtils.isEmpty(DEFAULT_FILE_DIR)) {
DEFAULT_FILE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "icheny" + File.separator;
}
return DEFAULT_FILE_DIR;
}
public static DownloadManager getInstance() {//管理器初始化
if (mInstance == null) {
synchronized (DownloadManager.class) {
if (mInstance == null) {
mInstance = new DownloadManager();
}
}
}
return mInstance;
}
public DownloadManager() {
mDownloadTasks = new HashMap<>();
}
/**
* 取消下载
*/
public boolean isDownloading(String... urls) {
//这里传一个url就是判断一个下载任务
//多个url数组适合下载管理器判断是否作操作全部下载或全部取消下载
boolean result = false;
for (int i = 0, length = urls.length; i < length; i++) {
String url = urls[i];
if (mDownloadTasks.containsKey(url)) {
result = mDownloadTasks.get(url).isDownloading();
}
}
return result;
}
}
DownloadTask
public class DownloadTask extends Handler { private final int THREAD_COUNT = 4;//线程数 private FilePoint mPoint; private long mFileLength; private boolean isDownloading = false; private int childCanleCount;//子线程取消数量 private int childPauseCount;//子线程暂停数量 private int childFinshCount; private HttpUtil mHttpUtil; private long[] mProgress; private File[] mCacheFiles; private File mTmpFile;//临时占位文件 private boolean pause;//是否暂停 private boolean cancel;//是否取消下载 private final int MSG_PROGRESS = 1;//进度 private final int MSG_FINISH = 2;//完成下载 private final int MSG_PAUSE = 3;//暂停 private final int MSG_CANCEL = 4;//暂停 private DownloadListner mListner;//下载回调监听 /** * 任务管理器初始化数据 * @param point * @param l */ DownloadTask(FilePoint point, DownloadListner l) { this.mPoint = point; this.mListner = l; this.mProgress = new long[THREAD_COUNT]; this.mCacheFiles = new File[THREAD_COUNT]; this.mHttpUtil = HttpUtil.getInstance(); } /** * 任务回调消息 * @param msg */ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (null == mListner) { return; } switch (msg.what) { case MSG_PROGRESS://进度 long progress = 0; for (int i = 0, length = mProgress.length; i < length; i++) { progress += mProgress[i]; } mListner.onProgress(progress * 1.0f / mFileLength); break; case MSG_PAUSE://暂停 childPauseCount++; if (childPauseCount % THREAD_COUNT != 0) return; resetStutus(); mListner.onPause(); break; case MSG_FINISH://完成 childFinshCount++; if (childFinshCount % THREAD_COUNT != 0) return; mTmpFile.renameTo(new File(mPoint.getFilePath(), mPoint.getFileName()));//下载完毕后,重命名目标文件名 resetStutus(); mListner.onFinished(); break; case MSG_CANCEL://取消 childCanleCount++; if (childCanleCount % THREAD_COUNT != 0) return; resetStutus(); mProgress = new long[THREAD_COUNT]; mListner.onCancel(); break; } } private static final String TAG = "DownloadTask"; public synchronized void start() { try { Log.e(TAG, "start: " + isDownloading + "\t" + mPoint.getUrl()); if (isDownloading) return; isDownloading = true; mHttpUtil.getContentLength(mPoint.getUrl(), new okhttp3.Callback() { @Override public void onResponse(Call call, Response response) throws IOException { if (response.code() != 200) { close(response.body()); resetStutus(); return; } // 获取资源大小 mFileLength = response.body().contentLength(); close(response.body()); // 在本地创建一个与资源同样大小的文件来占位 mTmpFile = new File(mPoint.getFilePath(), mPoint.getFileName() + ".tmp"); if (!mTmpFile.getParentFile().exists()) mTmpFile.getParentFile().mkdirs(); RandomAccessFile tmpAccessFile = new RandomAccessFile(mTmpFile, "rw"); tmpAccessFile.setLength(mFileLength); /*将下载任务分配给每个线程*/ long blockSize = mFileLength / THREAD_COUNT;// 计算每个线程理论上下载的数量.---1024 2047 /*为每个线程配置并分配任务 0,1,2,3*/ for (int threadId = 0; threadId < THREAD_COUNT; threadId++) { long startIndex = threadId * blockSize; // 线程开始下载的位置 long endIndex = (threadId + 1) * blockSize - 1; // 线程结束下载的位置 if (threadId == (THREAD_COUNT - 1)) { // 如果是最后一个线程,将剩下的文件全部交给这个线程完成 endIndex = mFileLength - 1; } download(startIndex, endIndex, threadId);// 开启线程下载 } } @Override public void onFailure(Call call, IOException e) { } }); } catch (IOException e) { e.printStackTrace(); resetStutus(); } } public void download(final long startIndex, final long endIndex, final int threadId) throws IOException { long newStartIndex = startIndex; // 分段请求网络连接,分段将文件保存到本地. // 加载下载位置缓存文件 final File cacheFile = new File(mPoint.getFilePath(), "thread" + threadId + "_" + mPoint.getFileName() + ".cache"); mCacheFiles[threadId] = cacheFile; final RandomAccessFile cacheAccessFile = new RandomAccessFile(cacheFile, "rwd"); if (cacheFile.exists()) {// 如果文件存在 String startIndexStr = cacheAccessFile.readLine(); try { newStartIndex = Integer.parseInt(startIndexStr);//重新设置下载起点 } catch (NumberFormatException e) { e.printStackTrace(); } } final long finalStartIndex = newStartIndex; mHttpUtil.downloadFileByRange(mPoint.getUrl(), finalStartIndex, endIndex, new okhttp3.Callback() { @Override public void onResponse(Call call, Response response) throws IOException { if (response.code() != 206) {// 206:请求部分资源成功码 resetStutus(); return; } InputStream is = response.body().byteStream();// 获取流 RandomAccessFile tmpAccessFile = new RandomAccessFile(mTmpFile, "rw");// 获取前面已创建的文件. tmpAccessFile.seek(finalStartIndex);// 文件写入的开始位置. /* 将网络流中的文件写入本地*/ byte[] buffer = new byte[1024 << 2]; int length = -1; int total = 0;// 记录本次下载文件的大小 long progress = 0; while ((length = is.read(buffer)) > 0) { if (cancel) { //关闭资源 close(cacheAccessFile, is, response.body()); cleanFile(cacheFile); sendEmptyMessage(MSG_CANCEL); return; } if (pause) { //关闭资源 close(cacheAccessFile, is, response.body()); //发送暂停消息 sendEmptyMessage(MSG_PAUSE); return; } tmpAccessFile.write(buffer, 0, length); total += length; progress = finalStartIndex + total; //将当前现在到的位置保存到文件中 cacheAccessFile.seek(0); cacheAccessFile.write((progress + "").getBytes("UTF-8")); //发送进度消息 mProgress[threadId] = progress - startIndex; sendEmptyMessage(MSG_PROGRESS); } //关闭资源 close(cacheAccessFile, is, response.body()); // 删除临时文件 cleanFile(cacheFile); //发送完成消息 sendEmptyMessage(MSG_FINISH); } @Override public void onFailure(Call call, IOException e) { isDownloading = false; } }); } /** * 关闭资源 * * @param closeables */ private void close(Closeable... closeables) { int length = closeables.length; try { for (int i = 0; i < length; i++) { Closeable closeable = closeables[i]; if (null != closeable) closeables[i].close(); } } catch (IOException e) { e.printStackTrace(); } finally { for (int i = 0; i < length; i++) { closeables[i] = null; } } } /** * 删除临时文件 */ private void cleanFile(File... files) { for (int i = 0, length = files.length; i < length; i++) { if (null != files[i]) files[i].delete(); } } /** * 暂停 */ public void pause() { pause = true; } /** * 取消 */ public void cancel() { cancel = true; cleanFile(mTmpFile); if (!isDownloading) { if (null != mListner) { cleanFile(mCacheFiles); resetStutus(); mListner.onCancel(); } } } /** * 重置下载状态 */ private void resetStutus() { pause = false; cancel = false; isDownloading = false; } public boolean isDownloading() { return isDownloading; } }
FilePoint
public class FilePoint { private String fileName;//文件名 private String url;//下载地址 private String filePath;//下载目录 public FilePoint(String url) { this.url = url; } public FilePoint(String filePath, String url) { this.filePath = filePath; this.url = url; } public FilePoint(String url, String filePath, String fileName) { this.url = url; this.filePath = filePath; this.fileName = fileName; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } }
HttpUtil
public class HttpUtil { private OkHttpClient mOkHttpClient; private static HttpUtil mInstance; private final static long CONNECT_TIMEOUT = 60;//超时时间,秒 private final static long READ_TIMEOUT = 60;//读取时间,秒 private final static long WRITE_TIMEOUT = 60;//写入时间,秒 /** * @param url 下载链接 * @param startIndex 下载起始位置 * @param endIndex 结束为止 * @param callback 回调 * @throws IOException */ public void downloadFileByRange(String url, long startIndex, long endIndex, Callback callback) throws IOException { // 创建一个Request // 设置分段下载的头信息。 Range:做分段数据请求,断点续传指示下载的区间。格式: Range bytes=0-1024或者bytes:0-1024 Request request = new Request.Builder().header("RANGE", "bytes=" + startIndex + "-" + endIndex) .url(url) .build(); doAsync(request, callback); } public void getContentLength(String url, Callback callback) throws IOException { // 创建一个Request Request request = new Request.Builder() .url(url) .build(); doAsync(request, callback); } /** * 异步请求 */ private void doAsync(Request request, Callback callback) throws IOException { //创建请求会话 okhttp3.Call call = mOkHttpClient.newCall(request); //同步执行会话请求 call.enqueue(callback); } /** * 同步请求 */ private Response doSync(Request request) throws IOException { //创建请求会话 okhttp3.Call call = mOkHttpClient.newCall(request); //同步执行会话请求 return call.execute(); } /** * @return HttpUtil实例对象 */ public static HttpUtil getInstance() { if (null == mInstance) { synchronized (HttpUtil.class) { if (null == mInstance) { mInstance = new HttpUtil(); } } } return mInstance; } /** * 构造方法,配置OkHttpClient */ public HttpUtil() { //创建okHttpClient对象 OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS) .readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS); mOkHttpClient = builder.build(); } }
//提示:下载的网址链接要准确MainActivity
public class MainActivity extends AppCompatActivity { TextView tv_file_name1, tv_progress1, tv_file_name2, tv_progress2; Button btn_download1, btn_download2, btn_download_all; ProgressBar pb_progress1, pb_progress2; DownloadManager mDownloadManager; String wechatUrl = "http://downmobile.kugou.com/Android/KugouPlayer/8948/KugouPlayer_219_V8.9.4.apk"; String qqUrl = "音乐网址"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initDownloads(); } /** * 初始化View控件 */ private void initViews() { tv_file_name1 = (TextView) findViewById(R.id.tv_file_name1); tv_progress1 = (TextView) findViewById(R.id.tv_progress1); pb_progress1 = (ProgressBar) findViewById(R.id.pb_progress1); btn_download1 = (Button) findViewById(R.id.btn_download1); tv_file_name1.setText("微信"); tv_file_name2 = (TextView) findViewById(R.id.tv_file_name2); tv_progress2 = (TextView) findViewById(R.id.tv_progress2); pb_progress2 = (ProgressBar) findViewById(R.id.pb_progress2); btn_download2 = (Button) findViewById(R.id.btn_download2); tv_file_name2.setText("qq"); btn_download_all = (Button) findViewById(R.id.btn_download_all); } private void initDownloads() { mDownloadManager = DownloadManager.getInstance(); mDownloadManager.add(wechatUrl, new DownloadListner() { @Override public void onFinished() { Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show(); } @Override public void onProgress(float progress) { pb_progress1.setProgress((int) (progress * 100)); tv_progress1.setText(String.format("%.2f", progress * 100) + "%"); } @Override public void onPause() { Toast.makeText(MainActivity.this, "暂停了!", Toast.LENGTH_SHORT).show(); } @Override public void onCancel() { tv_progress1.setText("0%"); pb_progress1.setProgress(0); btn_download1.setText("下载"); Toast.makeText(MainActivity.this, "下载已取消!", Toast.LENGTH_SHORT).show(); } }); mDownloadManager.add(qqUrl, new DownloadListner() { @Override public void onFinished() { Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show(); } @Override public void onProgress(float progress) { pb_progress2.setProgress((int) (progress * 100)); tv_progress2.setText(String.format("%.2f", progress * 100) + "%"); } @Override public void onPause() { Toast.makeText(MainActivity.this, "暂停了!", Toast.LENGTH_SHORT).show(); } @Override public void onCancel() { tv_progress2.setText("0%"); pb_progress2.setProgress(0); btn_download2.setText("下载"); Toast.makeText(MainActivity.this, "下载已取消!", Toast.LENGTH_SHORT).show(); } }); } /** * 下载或暂停下载 * * @param view */ public void downloadOrPause(View view) { switch (view.getId()) { case R.id.btn_download1: if (!mDownloadManager.isDownloading(wechatUrl)) { mDownloadManager.download(wechatUrl); btn_download1.setText("暂停"); } else { btn_download1.setText("下载"); mDownloadManager.pause(wechatUrl); } break; case R.id.btn_download2: if (!mDownloadManager.isDownloading(qqUrl)) { mDownloadManager.download(qqUrl); btn_download2.setText("暂停"); } else { btn_download2.setText("下载"); mDownloadManager.pause(qqUrl); } break; } } public void downloadOrPauseAll(View view) { if (!mDownloadManager.isDownloading(wechatUrl, qqUrl)) { btn_download1.setText("暂停"); btn_download2.setText("暂停"); btn_download_all.setText("全部暂停"); mDownloadManager.download(wechatUrl, qqUrl);//最好传入个String[]数组进去 } else { mDownloadManager.pause(wechatUrl, qqUrl); btn_download1.setText("下载"); btn_download2.setText("下载"); btn_download_all.setText("全部下载"); } } /** * 取消下载 * * @param view */ public void cancel(View view) { switch (view.getId()) { case R.id.btn_cancel1: mDownloadManager.cancel(wechatUrl); break; case R.id.btn_cancel2: mDownloadManager.cancel(qqUrl); break; } } public void cancelAll(View view) { mDownloadManager.cancel(wechatUrl, qqUrl); btn_download1.setText("下载"); btn_download2.setText("下载"); btn_download_all.setText("全部下载"); } }