Android开发实现多线程下载

释放双眼,带上耳机,听听看~!

 

一、概述

说到Android中的文件下载,Android API中明确要求将耗时的操作放到一个子线程中执行,文件的下载无疑是需要耗费时间的,所以要将文件的下载放到子线程中执行。下面,我们一起来实现一个Android中利用多线程下载文件的小例子。

二、服务端准备

在这个小例子中我以下载有道词典为例,在网上下载有道词典的安装包,在eclipse中新建项目web,将下载的有道词典安装包放置在WebContent目录下,并将项目发布到Tomcat中,具体如下图所示

三、Android实现

1、布局

界面上自上而下放置一个TextView,用来提示文本框中输入的信息,一个文本框用来输入网络中下载文件的路径,一个Button按钮,点击下载文件,一个ProgressBar显示下载进度,一个TextView显示下载的百分比。具体布局内容如下:

<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="下载路径" />
<EditText
android:id="@+id/ed_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="http://192.168.0.170:8080/web/youdao.exe"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="download"/>
<ProgressBar
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"/>
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="下载:0%"/>
</LinearLayout>

2、自定义ProgressBarListener监听器接口

新建自定义ProgressBarListener监听器接口,这个接口中定义两个方法,void getMax(int length)用来获取下载文件的长度,void getDownload(int length);用来获取每次下载的长度,这个方法中主要是在多线程中调用,子线程中获取到的数据传递到这两个接口方法中,然后在这两个接口方法中通过Handler将相应的长度信息传递到主线程,更新界面显示信息,具体代码实现如下:

package com.example.inter;
/**
* 自定义进度条监听器
* @author liuyazhuang
*
*/
public interface ProgressBarListener {
/**
* 获取文件的长度
* @param length
*/
void getMax(int length);
/**
* 获取每次下载的长度
* @param length
*/
void getDownload(int length);
}

3、自定义线程类DownloadThread

这里通过继承Thread的方式来实现自定义线程操作,在这个类中主要是实现文件的下载操作,在这个类中,定义了一系列与下载有关的实例变量来控制下载的数据,同时通过自定义监听器ProgressBarListener中的void getDownload(int length)方法来跟新界面显示的进度信息。
具体实现如下:

package com.example.download;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import com.example.inter.ProgressBarListener;
/**
* 自定义线程类
* @author liuyazhuang
*
*/
public class DownloadThread extends Thread {
//下载的线程id
private int threadId;
//下载的文件路径
private String path;
//保存的文件
private File file;
//下载的进度条更新的监听器
private ProgressBarListener listener;
//每条线程下载的数据量
private int block;
//下载的开始位置
private int startPosition;
//下载的结束位置
private int endPosition;
public DownloadThread(int threadId, String path, File file, ProgressBarListener listener, int block) {
this.threadId = threadId;
this.path = path;
this.file = file;
this.listener = listener;
this.block = block;
this.startPosition = threadId * block;
this.endPosition = (threadId + 1) * block - 1;
}
@Override
public void run() {
super.run();
try {
//创建RandomAccessFile对象
RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
//跳转到开始位置
accessFile.seek(startPosition);
URL url = new URL(path);
//打开http链接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置超时时间
conn.setConnectTimeout(5000);
//指定请求方式为GET方式
conn.setRequestMethod("GET");
//指定下载的位置
conn.setRequestProperty("Range", "bytes="+startPosition + "-" + endPosition);
//不用再去判断状态码是否为200
InputStream in = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
while((len = in.read(buffer)) != -1){
accessFile.write(buffer, 0, len);
//更新下载进度
listener.getDownload(len);
}
accessFile.close();
in.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}

4、新建DownloadManager类

这个类主要是对下载过程的管理,包括下载设置下载后文件要保存的位置,计算多线程中每个线程的数据下载量等等。
具体实现如下:

package com.example.download;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.Environment;
import com.example.inter.ProgressBarListener;
/**
* 文件下载管理器
* @author liuyazhuang
*
*/
public class DownloadManager {
//下载线程的数量
private static final int TREAD_SIZE = 3;
private File file;
/**
* 下载文件的方法
* @param path:下载文件的路径
* @param listener:自定义的下载文件监听接口
* @throws Exception
*/
public void download(String path, ProgressBarListener listener) throws Exception{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if(conn.getResponseCode() == 200){
int filesize = conn.getContentLength();
//设置进度条的最大长度
listener.getMax(filesize);
//创建一个和服务器大小一样的文件
file = new File(Environment.getExternalStorageDirectory(), this.getFileName(path));
RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
accessFile.setLength(filesize);
//要关闭RandomAccessFile对象
accessFile.close();
//计算出每条线程下载的数据量
int block = filesize % TREAD_SIZE == 0 ? (filesize / TREAD_SIZE) : (filesize / TREAD_SIZE +1 );
//开启线程下载
for(int i = 0; i < TREAD_SIZE; i++){
new DownloadThread(i, path, file, listener, block).start();
}
}
}
/**
* 截取路径中的文件名称
* @param path:要截取文件名称的路径
* @return:截取到的文件名称
*/
private String getFileName(String path){
return path.substring(path.lastIndexOf("/") + 1);
}
}

5、完善MainActivity

在这个类中首先,找到页面中的各个控件,实现Button按钮的onClick事件,在onClick事件中开启一个线程进行下载操作,同时子线程中获取到的数据,通过handler与Message机制传递到主线程,更新界面显示。
具体实现如下:

package com.example.multi;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.example.download.DownloadManager;
import com.example.inter.ProgressBarListener;
/**
* MainActivity整个应用程序的入口
* @author liuyazhuang
*
*/
public class MainActivity extends Activity {
protected static final int ERROR_DOWNLOAD = 0;
protected static final int SET_PROGRESS_MAX = 1;
protected static final int UPDATE_PROGRESS = 2;
private EditText ed_path;
private ProgressBar pb;
private TextView tv_info;
private DownloadManager manager;
//handler操作
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case ERROR_DOWNLOAD:
//提示用户下载失败
Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
break;
case SET_PROGRESS_MAX:
//得到最大值
int max = (Integer) msg.obj;
//设置进度条的最大值
pb.setMax(max);
break;
case UPDATE_PROGRESS:
//获取当前下载的长度
int currentprogress = pb.getProgress();
//获取新下载的长度
int len = (Integer) msg.obj;
//计算当前总下载长度
int crrrentTotalProgress = currentprogress + len;
pb.setProgress(crrrentTotalProgress);
//获取总大小
int maxProgress = pb.getMax();
//计算百分比
float value = (float)currentprogress / (float)maxProgress;
int percent = (int) (value * 100);
//显示下载的百分比
tv_info.setText("下载:"+percent+"%");
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.ed_path = (EditText) super.findViewById(R.id.ed_path);
this.pb = (ProgressBar) super.findViewById(R.id.pb);
this.tv_info = (TextView) super.findViewById(R.id.tv_info);
this.manager = new DownloadManager();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void download(View v){
final String path = ed_path.getText().toString();
//下载
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
manager.download(path, new ProgressBarListener() {
@Override
public void getMax(int length) {
// TODO Auto-generated method stub
Message message = new Message();
message.what = SET_PROGRESS_MAX;
message.obj = length;
mHandler.sendMessage(message);
}
@Override
public void getDownload(int length) {
// TODO Auto-generated method stub
Message message = new Message();
message.what = UPDATE_PROGRESS;
message.obj = length;
mHandler.sendMessage(message);
}
});
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
Message message = new Message();
message.what = ERROR_DOWNLOAD;
mHandler.sendMessage(message);
}
}
}).start();
}
}

6、增加权限

最后,别忘了给应用授权,这里要用到Android联网授权和向SD卡中写入文件的权限。
具体实现如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.multi"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.multi.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

四、运行效果

提醒:大家可以到这个链接来获取完整的代码示例。

 

人已赞赏
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索