一、多线程下载原理:
1、在客户端创建出来一个文件,该文件大小与服务器上的文件大小完全性相同。
①、首先要知道服务器上文件的大小,通过相应头里 的content-length得到文件大小。
②、使用RandomAccessFile类随机创建一个文件 ,通过setLength方法设置文件大小。
2、开辟三个线程,进行下载,计算出每一个线程下载的数据块大小。
3、当三个线程都运行完毕后,所有的数据都已经下载好了。
二、javase中的文件下载案例:支持断点下载:
public classTestMutileDownload {
//服务器上要下载的资源的地址
public static final String path="http://192.168.1.247:8080/youdao.exe";
public static int threadcount;
public static void main(String[] args) throws Exception{
URL url = new URL(path);
//打开连接
HttpURLConnection conn =(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//设置请求头,User-Agent:表示资源来自哪里
conn.setRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setConnectTimeout(5000);//得到服务器响应状态码的时间
// 获取服务器文件的大小
int len =conn.getContentLength();
System.out.println("文件大小为"+len);
// 在本地创建出来一个大小跟服务器文件一样大的资源
File file = newFile("youdao.exe");
//创建下载的文件对象
RandomAccessFilerandomfile = new RandomAccessFile(file,"rwd");
randomfile.setLength(len);
// 假设只开启3个子线程
int blocksize = len/3;
for(int i =1;i<=3;i++){
threadcount = 0;
int startsize = (i-1)*blocksize;
int endsize = (i)*blocksize - 1;
if(i==3){
// if(endsize<=len)
endsize = len;
}
new Thread(new DownLoadTask(i, startsize, endsize)).start();
// 怎么才能知道3个子线程都执行完毕了呢?
}
}
}
线程类:
class DownLoadTaskimplements Runnable{
//线程的id
private int id;
//要下载的文件的开始位置
private int startsize;
// 要下载的文件的结束位置
private int endsize;
//HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");
public DownLoadTask(int id, int startsize, int endsize) {
this.id = id;
this.startsize =startsize;
this.endsize = endsize;
}
@Override
public void run() {
try {
// 每一个线程创建执行的时候 都创建一个id.txt的文件,这个文件用来记录当前线程下载的进度
File idfile = new File(id+".txt");
// 判断是否记录的有下载的位置信息
if(idfile.exists()){
FileInputStream fis =new FileInputStream(idfile);
byte[] result =StreamTools.getBytes(fis);
String numberstr = newString(result);
if(numberstr!=null&&(!"".equals(numberstr))){
int startposition = Integer.parseInt( numberstr);
if(startposition>startsize){
startsize =startposition; // 重新指定下载的开始位置
}
}
}
URL url = new URL(TestMutileDownload.path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Mozilla/4.0(compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setConnectTimeout(5000); //得到服务器响应状态码的时间
// 指定当前线程从服务器上的哪个位置下载文件y
if(startsize>endsize){
startsize=endsize-1;
}
conn.setRequestProperty("Range","bytes="+startsize+"-"+endsize);
System.out.println("线程id="+id+"开始位置"+startsize+"结束位置"+endsize);
InputStream is = conn.getInputStream();
File file = new File("youdao.exe");
RandomAccessFile randomfile = new RandomAccessFile(file, "rwd");
//指定指针开始的位置
randomfile.seek(startsize);
//randomfile.write(arg0, arg1, arg2);
byte[] buffer = new byte[1024];
int len = 0;
int total = 0;
while( (len = is.read(buffer))!=-1){
randomfile.write(buffer,0, len);
// 记录当前 线程下载的数据量 和对应的位置给记录
total +=len;
FileOutputStream idfos =new FileOutputStream(idfile);
idfos.write((startsize+total+"").getBytes());// 记录当前线程下载的位置信息
idfos.flush();
idfos.close();
}
randomfile.close();
is.close();
System.out.println("线程"+id+"下载完毕");
// //线程下载完毕后 擦屁股的操作
// if(idfile.exists()){
// idfile.delete();
// }
synchronized (TestMutileDownload.class) {
TestMutileDownload.threadcount++;
if(TestMutileDownload.threadcount>=3){
System.out.println("所有的线程都执行完毕了");
// 擦屁股的操作
for(int i=1;i<=3;i++){
File deletefile = newFile(i+".txt");
System.out.println(i+"删除"+ deletefile.delete());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、android的多线程下载:
1、设置布局:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="多线程断点下载器" />
<EditText
android:id="@+id/et_path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="http://192.168.1.247:8080/youdao.exe"
android:hint="请输入下载文件的路径" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal" //水平居中
android:orientation="horizontal" >
<Button
android:id="@+id/bt_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载" />
<Button
android:id="@+id/bt_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停" />
</LinearLayout>
<ProgressBar //进度条组件
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"//进度条的样式.
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_progress"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="进度" />
</LinearLayout>
2、业务代码:
public class DemoActivityextends Activity implements OnClickListener {
protected static final int ERROR = 404;
public static final int DOWNLOAD_FINISH = 200;
private EditText et_path;
private Button bt_download;
private Button bt_stop;
private ProgressBar pb;
private TextView tv_progress;
public static int threadcount ;
public int total; // 当前下载的进度
public int totallen ; // 总的文件大小
private Handler handler = new Handler(){
@Override
public voidhandleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if(msg.what==ERROR){
Toast.makeText(getApplicationContext(),"获取文件长度失败", 0).show();
bt_download.setClickable(true);
bt_download.setEnabled(true);
return;
}
if(msg.what==DOWNLOAD_FINISH){
bt_download.setClickable(true);
bt_download.setEnabled(true);
return;
}
int process = total*100/totallen;
String strprocess = "当前进度"+process+"%";
tv_progress.setText(strprocess);
}
};
public boolean flag;
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
et_path = (EditText)this.findViewById(R.id.et_path);
bt_download = (Button)this.findViewById(R.id.bt_download);
bt_stop = (Button)this.findViewById(R.id.bt_stop);
pb = (ProgressBar)this.findViewById(R.id.pb);
tv_progress = (TextView)this.findViewById(R.id.tv_progress);
//注册按钮的点击事件
bt_download.setOnClickListener(this);
bt_stop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_stop:
flag = false;
bt_download.setClickable(true);
bt_download.setEnabled(true);
break;
case R.id.bt_download:
final String path = et_path.getText().toString().trim();
if("".equals(path)){
Toast.makeText(this,"路径不能为空", 1).show();
return ;
}else{
// 开启子线程 连接服务器 获取文件的大小
bt_download.setClickable(false);
bt_download.setEnabled(false);
new Thread(){
public void run() {
try {
total = 0;
flag = true;
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Mozilla/4.0(compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setConnectTimeout(5000); //得到服务器响应状态码的时间
// 获取服务器文件的大小
totallen = conn.getContentLength();
//设置进度条的最大值
pb.setMax(totallen);
System.out.println("文件大小为"+totallen);
// 在本地创建出来一个大小跟服务器文件一样大的资源
File file = newFile(Environment.getExternalStorageDirectory(),getFileName(path));
RandomAccessFile randomfile = new RandomAccessFile(file, "rwd");
randomfile.setLength(totallen);
// 假设只开启3个子线程
int blocksize = totallen/3;
for(int i = 1;i<=3;i++){
threadcount = 0;
int startsize =(i-1)*blocksize;
int endsize =(i)*blocksize - 1;
if(i==3){
endsize = totallen;
}
new Thread(newDownLoadTask(i, startsize, endsize,path)).start();
// 怎么才能知道3个子线程都执行完毕了呢?
}
} catch (Exception e) {
e.printStackTrace();
// Toast.makeText(this, "下载出错", 0).show();
Message msg = new Message();
msg.what = ERROR;
handler.sendMessage(msg);
}
};
}.start();
}
break;
}
}
class DownLoadTask implements Runnable{
//线程的id
private int id;
//要下载的文件的开始位置
private int startsize;
// 要下载的文件的结束位置
private int endsize;
//HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");
private String path;
public DownLoadTask(intid, int startsize, int endsize,String path) {
this.id = id;
this.startsize = startsize;
this.endsize = endsize;
this.path = path;
}
@Override
public void run() {
try {
// 每一个线程创建执行的时候 都创建一个id.txt的文件,这个文件用来记录当前线程下载的进度
File idfile = newFile("/mnt/sdcard/"+id+".txt");
// 判断是否记录的有下载的位置信息
if(idfile.exists()){
FileInputStream fis = new FileInputStream(idfile);
byte[] result = StreamTools.getBytes(fis);
String numberstr = new String(result);
if(numberstr!=null&&(!"".equals(numberstr))){
int startposition =Integer.parseInt( numberstr); // 从文件里面获取到的位置信息
if(startposition>startsize){
int currentposition = startposition - startsize; // 当前线程已经下载的数据的大小
setProgreebarProgress(currentposition);
handler.sendEmptyMessage(0);
// handler.post(r);
startsize = startposition; // 重新指定下载的开始位置
}
}
}
URL url = new URL(path);
HttpURLConnection conn =(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setConnectTimeout(5000);//得到服务器响应状态码的时间
// 指定当前线程从服务器上的哪个位置下载文件y
if(startsize>endsize){
startsize=endsize-1;
}
conn.setRequestProperty("Range","bytes="+startsize+"-"+endsize);
System.out.println("线程id="+id+"开始位置"+startsize+"结束位置"+endsize);
InputStream is =conn.getInputStream();
File file = newFile(Environment.getExternalStorageDirectory(),getFileName(path));
RandomAccessFilerandomfile = new RandomAccessFile(file,"rwd");
randomfile.seek(startsize);
//randomfile.write(arg0,arg1, arg2);
byte[] buffer = newbyte[1024];
int len = 0;
int total = 0;
while( (len =is.read(buffer))!=-1){
randomfile.write(buffer, 0, len);
// 记录当前 线程下载的数据量 和对应的位置给记录
total +=len;
setProgreebarProgress(len);
handler.sendEmptyMessage(0);
FileOutputStream idfos = new FileOutputStream(idfile);
idfos.write((startsize+total+"").getBytes()); // 记录当前线程下载的位置信息
idfos.flush();
idfos.close();
if(!flag){
return ;
}
}
randomfile.close();
is.close();
System.out.println("线程"+id+"下载完毕");
// //线程下载完毕后 擦屁股的操作
// if(idfile.exists()){
// idfile.delete();
// }
synchronized (DemoActivity.this){
threadcount ++;
if(threadcount>=3){
System.out.println("所有的线程都执行完毕了");
// 擦屁股的操作
for(inti=1;i<=3;i++){
File deletefile = newFile("/mnt/sdcard/"+i+".txt");
System.out.println(i+"删除"+ deletefile.delete());
}
//通知主线程 bt_download.setClickable(true);
//bt_download.setEnabled(true);
Message msg = newMessage();
msg.what =DOWNLOAD_FINISH;
handler.sendMessage(msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized void setProgreebarProgress(int len){
total += len;
pb.setProgress(total);
// 第二种做法 就是每次得到下载的进度后 把数据存到文件里面
}
public String getFileName(String path){
int start =path.lastIndexOf("/")+1;
returnpath.substring(start);
}
}
只有创建View对象的线程,才可以更新view对象里的内容,
其实所有的view对象都是在主线程里面创建的 ,线程的名字叫main。
所用的与ui相关的界面都是在主线程里面创建的。
更新view显示基本原理:
当子线程要更新view里的内容时,就让子线程发送一个消息给主线程,主线程再根据消息的内容进行操作。
主线程里面有消息队列(message queue),可以存放一组消息。并且还有一个轮询器,定期的轮询消息队列,查看是否有消息。如果发现有消息,;轮询器会把消息取出来。
在主线程中创建消息的处理者handler对象,用于处理在消息队列中取出的消息。