Android多线程文件下载器
本应用实现的是输入文件的网络的地址,点击button開始下载,下载过程中有进度条和后面的文本提示进度,
下载过程中button不可点击,防止反复的下载,完成下载后会进行Toast的提示显示,
而且回复button的可点击性,进度条也会清空,当然假设下载中途结束应用进程就会进行进度的保存,
下次下载相同的文件时就会从进度记录进行下载,节省流量和时间
应用须要的应用权限:
訪问网络权限
<uses-permission android:name="android.permission.INTERNET"/>
外部储存的写入权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
布局文件代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" > <EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="请输入下载文件的地址"
android:singleLine="true"
android:text="http://172.22.64.193:8080/test.exe" >
</EditText> <TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" > <TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content" > <ProgressBar
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="8" /> <TextView
android:id="@+id/tv_progressNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center"
android:text="0%" />
</TableRow>
</TableLayout> <Button
android:id="@+id/bt_startDownlode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startDownlode"
android:text="開始下载" /> </LinearLayout>
核心代码
package com.examp.mutildownloader; import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL; import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast; /**
* Android下的多线程下载,断点下载
*
* @author MartinDong
*
*/
public class MainActivity extends Activity { // sd卡的路径
private static final File sd = Environment.getExternalStorageDirectory();
private static final int DOWNLODE_ERROR = 1;
private static final int DOWNLODE_SUCCESS = 2;
private static final int DOWNLODE_DELETE_TEMP = 3;
public static final int PROGRESS_NUMBER_CHANGE = 4; // 定义线程个数
private static int threadCount = 3;
// 定义当前存货的线程个数
private static int runningThread = 3;
// 组件的获取
private EditText et_path;
private ProgressBar pb;
private Button bt_startDownlode;
private TextView tv_progressNumber;
// 定义存储的文件名
private String filename;
// 设置进度条的进度
// 进度条的数据
public int progressTemp = 0; // 定义消息处理
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWNLODE_ERROR:
Toast.makeText(getApplicationContext(), "下载失败....",
Toast.LENGTH_SHORT).show();
break;
case DOWNLODE_SUCCESS:
// 设置button可用
bt_startDownlode.setEnabled(true);
// 清空进度
progressTemp = 0;
pb.setProgress(progressTemp);
// 文本清0
tv_progressNumber.setText("0%");
Toast.makeText(getApplicationContext(), "下载成功....",
Toast.LENGTH_SHORT).show();
break;
case DOWNLODE_DELETE_TEMP:
Toast.makeText(getApplicationContext(), "删除进度文件....",
Toast.LENGTH_SHORT).show();
break;
case PROGRESS_NUMBER_CHANGE:
tv_progressNumber.setText(pb.getProgress() * 100 / pb.getMax()
+ "%");
break;
}
} }; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); et_path = (EditText) findViewById(R.id.et_path);
// 获取组件
pb = (ProgressBar) findViewById(R.id.pb);
bt_startDownlode = (Button) findViewById(R.id.bt_startDownlode);
tv_progressNumber = (TextView) findViewById(R.id.tv_progressNumber); } public void startDownlode(View view) {
// 设置button不可点击//防止反复提交
bt_startDownlode.setEnabled(false); // 从控件中获取下载的路径
final String path = et_path.getText().toString().trim();
// 获取输入地址的最后一个"/"出现的位置
int lastIndex = path.lastIndexOf("/");
// 获取文件名及格式
filename = path.substring(lastIndex + 1, path.length());
System.out.println("文件名为===============" + filename); // 推断路径是否有效
if (TextUtils.isEmpty(path)) {
Toast.makeText(this, "请输入有效的下载路径......", Toast.LENGTH_SHORT).show();
return;
}
// 为了避免与主线程冲突,开启子线程完毕,避免anr问题
new Thread() {
public void run() {
try {
// 1,连接到server,获取一个文件,获取文件的大小跟server的文件一样的暂时文件
// String path = "http://172.22.64.193:8080/tomcat.css";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
// 设置超时
conn.setConnectTimeout(5000);
// 设置请求方式
conn.setRequestMethod("GET");
// 获取server的返回码
int code = conn.getResponseCode();
// 推断返回码
if (code == 200) {
// 获取返回的长度
int length = conn.getContentLength(); // 设置进度条的最大值
pb.setMax(length); System.out.println("文件总长度:" + length); // 在client创建出一个跟server大小一致的暂时文件
RandomAccessFile raf = new RandomAccessFile(sd + "/"
+ filename, "rwd");
// 指定暂时文件的大小
raf.setLength(length);
// 释放资源
raf.close(); // 平均每个线程的文件大小
int blockSize = length / threadCount; // 设置活跃的线程
runningThread = threadCount;
for (int threadId = 1; threadId <= threadCount; threadId++) {
// 线程開始的下载位置
int startIndex = (threadId - 1) * blockSize;
// 线程的结束位置
int endIndex = threadId * blockSize - 1;
// 推断是否是最后一个线程
if (threadId == threadCount) {
// 设置结束的位置为到文件的最后
endIndex = length;
}
System.out.println("线程:" + threadId
+ "下载:開始位置>>>>>>>>" + startIndex
+ "结束>>>>>>>>>>" + endIndex); new DownlodeThread(path, threadId, startIndex,
endIndex).start();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} };
}.start(); } /**
* 下载文件的子线程类,每个线程下载相应位置文件数据
*
* @author MartinDong
*
*/
public class DownlodeThread extends Thread { private String path;
private int threadId;
private int startIndex;
private int endIndex; /**
*
* @param path
* 文件的下载路径
* @param threadId
* 线程id
* @param startIndex
* 线程開始的位置
* @param endIndex
* 线程结束的位置
*/
public DownlodeThread(String path, int threadId, int startIndex,
int endIndex) {
this.path = path;
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
} @Override
public void run() {
try {
// 检查是否存在下载历史的文件
File tempFile = new File(sd + "/" + threadId + ".txt");// =========================断点记录操作===============================
if (tempFile.exists() && tempFile.length() > 0) {
// 文件输入流
FileInputStream fis = new FileInputStream(tempFile);
// 中间变量,缓存的作用
byte[] tempBuffer = new byte[1024];
// 获取进度文件的数据大小
int length = fis.read(tempBuffer);
// 获取进度文件的数据
String historyData = new String(tempBuffer, 0, length);
// 将进度数据装换为整型
int historyDataInt = Integer.parseInt(historyData);
// 假设是断点传送,初始化进度条的进度
// 获取每个线程已经下载了的进度
int temp = historyDataInt - startIndex;
// 为进度又一次赋值
progressTemp += temp; // 改动真正的下载位置
startIndex = historyDataInt; fis.close();
}// =========================断点记录操作=============================== // 将地址转换为URL
URL url = new URL(path);
// 获取http连接
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
// 设置连接的请求方式
conn.setRequestMethod("GET");
// 重要:请求server下载部分的文件,指定文件的位置
conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
+ endIndex); System.out
.println("线程:" + threadId + "真实開始的下载进度:" + startIndex);
// 设置超时时间
conn.setReadTimeout(5000);
// 得到server的状态码,200表示请求的所有资源得到响应=== ok,206请求的部分资源得到响应=== ok
int code = conn.getResponseCode();
System.out.println("code:" + code); if (code == 206) {
// 返回的是指定位置的文件流
InputStream is = conn.getInputStream();
// 创建一个暂时的文件
RandomAccessFile raf = new RandomAccessFile(sd + "/"
+ filename, "rwd");
// 移动指针,到指定的文件位置,
raf.seek(startIndex); // 创建中间缓冲字节数组
byte[] buffer = new byte[1024];
// 读取文件的大小
int length = 0; // 定义已经下载的数据长度,用作断点下载的记录=========================断点记录操作===============================
int downlodeTotal = 0; // 循环写入
while ((length = is.read(buffer)) != -1) {
// 定义一个记录线程的记录文件=========================断点记录操作===============================
RandomAccessFile historyFile = new RandomAccessFile(sd
+ "/" + threadId + ".txt", "rwd");
// 向文件里写入数据
raf.write(buffer, 0, length);
// 记录已经下载的文件长度
downlodeTotal += length;
// 将已经下载的文件长度和開始的读取位置相加,得到已经读取的文件位置
historyFile.write((downlodeTotal + startIndex + "")
.getBytes());
historyFile.close();// =========================断点记录操作=============================== // 保持同步的更新
synchronized (MainActivity.this) {
// 将每个线程的读取文件的大小累加到下载进度上
progressTemp += length;
// 更新界面上的Progress的进度条的长度
pb.setProgress(progressTemp);
// 特殊情况ProgressBar ProgressDialog
// 是能够直接在子线程中跟新ui的,内部代码有过特殊的处理
// 使用Message.obtain();降低Message对象的创建
Message msg = Message.obtain();
msg.what = PROGRESS_NUMBER_CHANGE;
handler.sendMessage(msg);
} }
is.close();
raf.close();
System.out.println("线程:" + threadId + "完成下载............"); } else {
System.out.println("线程:" + threadId
+ "下载失败请又一次下载............");
// 向消息处理器发送信息
Message msg = new Message();
msg.what = DOWNLODE_ERROR;
handler.sendMessage(msg);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
synchronized (MainActivity.this) {
// 进行线程数量的变化操作
runningThread--;
// 假设当前存活的线程为0,运行进度文件统一销毁的操作
if (runningThread == 0) {
// 假设完成下载,清除进度文件
for (int threadIndex = 1; threadIndex <= threadCount; threadIndex++) {
// 这里创建的是与线程文件相应的文件,文件名称能够自己定义,方便起见是採用的是线程的ID表示
File temp = new File(sd + "/" + threadIndex
+ ".txt");
// 运行文件删除的操作
temp.delete();
}
System.out.println("完成下载,删除进度文件.............");
// 向消息处理器发送信息
// 向消息处理器发送信息
Message msg = new Message();
msg.what = DOWNLODE_SUCCESS;
handler.sendMessage(msg);
}
} }
}
} }
Demo下载地址:
Android多线程文件下载器的更多相关文章
- Android多线程文件下载
版本信息 apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion " ...
- Android实现网络多线程文件下载
实现原理 (1)首先获得下载文件的长度,然后设置本地文件的长度. (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置. 如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M ...
- android 多线程
本章讲述在android开发中,多线程的应用.多线程能够处理耗时的操作并优化程序的性能.本章主要介绍知识点,AsyncTask,Java线程池,ThreadPoolExecutor线程池类.本章案例只 ...
- 无废话Android之smartimageview使用、android多线程下载、显式意图激活另外一个activity,检查网络是否可用定位到网络的位置、隐式意图激活另外一个activity、隐式意图的配置,自定义隐式意图、在不同activity之间数据传递(5)
1.smartimageview使用 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&q ...
- android 多线程断点续传下载
今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...
- 使用Vitamio打造自己的Android万能播放器(7)——在线播放(下载视频)
前言 本章将实现非常实用的功能——下载在线视频.涉及到多线程.线程更新UI等技术,还需思考产品的设计,如何将新加的功能更好的融入到现有的产品中,并不是简单的加一个界面就行了,欢迎大家交流产品设计和技术 ...
- Android多线程.断点续传下载
多线程,可断点续传的demo!最早写于2010.7! /** * @brief 主界面 * @author lixp */ public class HomeActivity exten ...
- Android 图片压缩器
概述 Android 图片压缩器:一款高效的图片压缩器库,支持批量压缩,异步压缩.多线程多任务压缩,压缩比设置等特性. 详细 代码下载:http://www.demodashi.com/demo/12 ...
- Android多线程断点下载的代码流程解析
Step 1:创建一个用来记录线程下载信息的表 创建数据库表,于是乎我们创建一个数据库的管理器类,继承SQLiteOpenHelper类 重写onCreate()与onUpgrade()方法 DBOp ...
随机推荐
- Qt Style Sheets Examples(QT真是有很全的文档)
http://doc.qt.io/qt-5/stylesheet-examples.html http://doc.qt.io/qt-4.8/stylesheet.html
- 用js实现插入排序
话不多说,直接上代码 html源码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " ...
- 旧版QT的名称:qt-win-commercial-4.4.3-vc60.exe
qt-win-commercial-4.4.3-vc60.exeqt-vsaddin-collection-2.1.4.exeqt-win-commercial-4.4.3-v2005.exeqt-v ...
- mysql READ-COMMITTED 模式下 行锁不会升级到表级锁
mysql> select sn,id,info from s100 group by id; +-----+------+------+ | sn | id | info | +-----+- ...
- 5350.support
3G6200N3G6200NL3G300MAIR3GIIALL02393GALL0256NALL5002ALL5003ARGUS_ATP52B,ASL26555AWM002EVBAWAPN2403BC ...
- grep、sed、awk、perl、js、vim等对正则表达式的支持的差别
grep.sed.awk.perl等对正则表达式的支持的差别 grep 2.5.1 egrep 2.5.1 sed 3.02 sed 4.07 awk 3.1.1 perl 5.8.0 vim 6.1 ...
- [置顶] 自己动手写Web容器之TomJetty之六:动态页面引入
传送门 ☞ 1.Web服务内功经脉 传送门 ☞ 2.让服务动起来 传送门 ☞ 3.掀起请求盖头来 传送门 ☞ 4.静态页面起步 传送门 ☞ 5.包装请求参数 在上一节,我们已经完成了TomJetty服 ...
- java中synchronized的使用方法与具体解释
Java语言的keyword.当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多仅仅有一个线程运行该段代码. 一.当两个并发线程訪问同一个对象object中的这个synchronized ...
- cocos2dx触屏响应(单点触摸)CCTouchBegan,CCTouchMove,CCTouchEnd
今天白白跟大家分享一下cocos2dx单点触摸经验. cocos2dx触摸CCTouch类的单点触摸有四个函数CCTouchBegan,CCTouchMove,CCTouchEnd,CCTouchCa ...
- 条款38 通过复合塑膜出has-a或"依据某物实现"
结论: 复合的意义和public继承全然不同. (public继承參考:条款32 确定你的public继承塑模出is-a关系) 在应用域,复合意味着has-a(有一个).在实现域,复合意味着is-im ...