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 ...
随机推荐
- 迟来SQLHelper
机房收费系统个人重构版敲完登陆系统之后往后敲了几个窗口,对于那些数据库连接SqlConnenction.SqlConnamd等常常敲反复的代码,之前也看过其它人的博客,这个东西不用还真不行. SqlH ...
- webdynpro 下拉列表控件
现在界面上添加下拉列表的控件DropDownByKey 在context中创建新的node,和属性DP 返回界面,绑定DP到控件DropDownByKey的SelectedKey 初始方法中代码如下: ...
- TPanel的默认颜色存储在dfm中,读取后在Paint函数中设置刷子的颜色,然后填充整个背景
声明如下: TCustomPanel = class(TCustomControl) private FFullRepaint: Boolean; FParentBackgroundSet: Bool ...
- c# in depth之泛型的实现
1.默认值表达式 如果已经明确了要处理的类型,也就知道了它的“默认”值.不知道要引用的类型,就不能直接指定默认值.不能使用null,因为它可能不是一个引用类型,不能使用0,因为它可能不是数值类型.虽然 ...
- Spring MVC Controller与jquery ajax请求处理json
在用 spring mvc 写应用的时候发现jquery传递的[json数组对象]参数后台接收不到,多订单的处理,ajax请求: "}]}]} $.ajax({ url : url, typ ...
- 手机SIM卡无法识别解决方案
SIM卡是工作中测试用的,经常插拔到不同的手机,前两天SIM卡放到手机中都能正常识别,今天插入到另一款手机中发现无法识别.心里糟了,是不是卡坏了,根据之 前的直觉,在公司找了一块橡皮,在SIM卡的芯片 ...
- POJ 1273 Drainage Ditches(网络流,最大流)
Description Every time it rains on Farmer John's fields, a pond forms over Bessie's favorite clover ...
- 绑定运行计划sql_plan_baseline
--因为生产环境运行的sql变化较快,版本号公布比較频繁,造成sql的运行计划不是非常稳定.常常会有一些性能非常查的sql出现 --对于这些sql,我们能够使用sql_plan_baseline对运行 ...
- 使用TWebBrowser时存在内存泄漏问题的解决方案(使用SetProcessWorkingSetSize函数,或者修改OleCtrls.pas源码解决问题)
用TWebBrower不断打开多个网页,多某些版本的操作系统上运行一段时间后,发现占用系统内存达几百M,直到关闭程序后,占用的内存才能释放. 这个问题在网有很多讨论,比较多人的建议办法是用SetPro ...
- 暂停和屏蔽右键网页中的Flash
如何暂停网页中的Flash?原理很简单,就是屏蔽Flash的消息即可.屏蔽右键也可以通过此方法 直接贴代码吧,加了注释,很容易就能懂了 新建工程,加一个WebBrowser,再加两个按钮.Flash ...