交互乱码的根本原因就是平台两端的字符编码不一致
URLEncoder.encode()
方法对参数进行编码。RandomAccessFile
对象将指定的区块写入到客户端对应的区块中。图中已经显示得很清楚了,每个线程下载那些区块的数据,接下来我们主需要把这些图片中的数据抽取成为代码就可以了。
// 线程数
int threadCount = 3;
// 得到文件的大小
int fileLength = 10;
// 计算出区块的大小
int blockSize = fielLength / threadCount;
// 计算出个区块的开始和结尾
线程0:0 * blockSize --- (0+1)*blockSize - 1
线程1:1 * blockSize --- (1+1)*blockSize - 1
线程2:2 * blockSize --- fileLength
// 可以总结出的公式为:
threadId * blockSize --- (threadId +1)*blockSize - 1
HttpUrlConnection
对象设置Range
头信息,明确请求文件的开始与结束位置;RandomAccessFile
对象的seek
方法定位到指定的写入点代码中省略了很多没有必要的注释,只保留了关键性的
// 多线程下载
public class MultiThreadedDownload {
private static String path = "http://192.168.1.101:8080/FeiQ.exe";
// 下载线程数
private static final int threadCount = 3;
public static void main(String[] args) throws Exception {
new Thread() {
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5 * 1000);
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
// 响应成功
if (200 == responseCode) {
// ★ 获取文件的长度
int fileLength = conn.getContentLength();
System.out.println("文件大小:" + fileLength);
// ★ 根据服务器资源的大小,在客户端创建一个同样大小的空文件
String fileName = UUID.randomUUID().toString() + ".exe";
// ☆ 这里使用到了RandomAccessFile对象,可以使用它随机读写文件,详细的请看JDK
RandomAccessFile emptyFile = new RandomAccessFile(fileName, "rw");
// ☆ 设置空文件大小
emptyFile.setLength(fileLength);
// ☆ 根据线程数目,计算出下载的区块大小
int blockSize = fileLength / threadCount;
for (int threadId = 0; threadId < threadCount; threadId++) {
// ★计算好各个区块的开始索引和结束索引
int startIndex = threadId * blockSize; // 开始索引
int endIndex = (threadId + 1) * blockSize - 1; // 结束索引
if (threadId == (threadCount - 1)) {
endIndex = fileLength - 1;
}
System.out.println("区块" + threadId + ":(" + startIndex + "," + endIndex + ")");
// ★开启子线程下载
new MyDownloadThread(fileName, startIndex, endIndex, threadId).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
// 线程下载的内部类
static class MyDownloadThread extends Thread {
private int endIndex;
private int startIndex;
private String fileName;
private int threadId;
public MyDownloadThread(String fileName, int startIndex, int endIndex, int threadId) {
this.fileName = fileName;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5 * 1000);
conn.setRequestMethod("GET");
// ★ 设置下载的区块范围
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
int responseCode = conn.getResponseCode();
// 部分请求成功
if (206 == responseCode) {
System.out.println(threadId + "线程请求成功,准备写入" + "(" + startIndex + "," + endIndex + ")");
InputStream in = conn.getInputStream();
// 拿到写入对象
RandomAccessFile writeFile = new RandomAccessFile(fileName, "rw");
// ★ 定位写入点
writeFile.seek(startIndex);
int len = 0;
byte[] buffer = new byte[1024 * 1024];
while ((len = in.read(buffer)) != -1) {
writeFile.write(buffer, 0, len);
}
writeFile.close();
System.out.println(threadId + "写入完成");
}
} catch (Exception e) {
e.printStackTrace();
}
};
}
}
这次的程序是基于上一个多线程下载的。主要的改变集中在MyDownloadThread
类中,其中重点不同的已经用★标注了。
// 线程下载的内部类
static class MyDownloadThread extends Thread {
private int endIndex;
private int startIndex;
private String fileName;
private int threadId;
public MyDownloadThread(String fileName, int startIndex, int endIndex, int threadId) {
this.fileName = fileName;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5 * 1000);
conn.setRequestMethod("GET");
// ① ★★★不同之处,读取当前线程已下载的文件进度
int readedProgress = readProgress(threadId);
startIndex += readedProgress;
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
int responseCode = conn.getResponseCode();
if (206 == responseCode) {
System.out.println(threadId + "线程请求成功,准备写入" + "(" + startIndex + "," + endIndex + ")");
InputStream in = conn.getInputStream();
RandomAccessFile writeFile = new RandomAccessFile(fileName, "rw");
writeFile.seek(startIndex);
// ② ★★★计算当前线程所负责的区块已经下载的进度
int total = 0;
int len = 0;
byte[] buffer = new byte[1024 * 1024];
while ((len = in.read(buffer)) != -1) {
writeFile.write(buffer, 0, len);
// ③ ★★★ 把当前线程已下载的文件进度写入到文件中
total += len;
writeProgress(threadId, total);
}
writeFile.close();
// ④ ★★★ 删除记录的文件
File file = new File(String.valueOf(threadId));
if (file.exists()) {
file.delete();
}
synchronized (MultiThreadedDownload.class) {
runningThreadCount--;
if (runningThreadCount == 0) {
System.out.println("文件下载完成");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取当前线程已下载的文件进度
*/
private int readProgress(int threadId) throws Exception {
File file = new File(String.valueOf(threadId));
if (!file.exists()) {
return 0;
}
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String result = br.readLine();
if (result == null || "".equals(result)) {
return 0;
}
return Integer.parseInt(result);
}
/**
* 把当前线程已下载的文件进度写入到文件中
*/
private void writeProgress(int threadId, int total) throws Exception {
RandomAccessFile write = new RandomAccessFile(String.valueOf(threadId), "rwd");
write.write(String.valueOf(total).getBytes());
write.close();
};
}
经过上面的两个小示例,想做出来这样的一个界面和功能就比较容易了
通过输入下载的线程数目,在一个LinearLayout
中动态添加ProgressBar
,然后开启线程,并根据区块大小和当前进度设置ProgressBar
,一个简易的下载程序就出来了。
当然,这个程序只是个Demo,还有很多很多很多不完善的地方。而此Demo中的大部分代码都是直接从上面复制过来的,唯一不同的就是使用ProgressBar
显示和更新进度。下面是代码
先是布局文件
activity_main.xml
<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_thread_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入线程的个数" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="click"
android:text="下载" />
<LinearLayout
android:id="@+id/ll_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
item.xml
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
像之前一样,我把非本节的注释全部去除了,只留下了不同的地方,并使用★作为标记
public class MainActivity extends Activity {
private String path = "http://192.168.1.101:8080/FeiQ.exe";
// 下载线程数
private int threadCount = 3;
private int runningThreadCount = 0;
private EditText et_thread_number;
private LinearLayout ll_layout;
private List<ProgressBar> pbs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_thread_number = (EditText) findViewById(R.id.et_thread_number);
ll_layout = (LinearLayout) findViewById(R.id.ll_layout);
pbs = new ArrayList<ProgressBar>();
}
public void click(View v) {
// 获取线程数
runningThreadCount = threadCount = Integer.parseInt(et_thread_number.getText().toString().trim());
// ★★★ 根据线程数目动态添加Bar
for (int i = 0; i < threadCount; i++) {
ProgressBar child = (ProgressBar) View.inflate(getApplicationContext(), R.layout.item, null);
ll_layout.addView(child);
pbs.add(child);
}
// .... 省略 (开启线程访问网络等等)
}
class MyDownloadThread extends Thread {
private int endIndex;
private int startIndex;
private String fileName;
private int threadId;
public MyDownloadThread(String fileName, int startIndex, int endIndex, int threadId) {
this.fileName = fileName;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5 * 1000);
conn.setRequestMethod("GET");
int readedProgress = readProgress(threadId);
startIndex += readedProgress;
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
int responseCode = conn.getResponseCode();
if (206 == responseCode) {
System.out.println(threadId + "线程请求成功,准备写入" + "(" + startIndex + "," + endIndex + ")");
InputStream in = conn.getInputStream();
RandomAccessFile writeFile = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/" + fileName, "rw");
writeFile.seek(startIndex);
// ★★★ 获取到Bar对象
ProgressBar bar = pbs.get(threadId);
// ★★★ 设置当前线程Bar的最大值
bar.setMax(endIndex - startIndex);
int total = 0;
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
writeFile.write(buffer, 0, len);
total += len;
// ★★★ 设置当前进度
bar.setProgress(total);
writeProgress(threadId, total);
}
writeFile.close();
File file = new File(Environment.getExternalStorageDirectory() + "/" + String.valueOf(threadId));
if (file.exists()) {
file.delete();
}
synchronized (MyDownloadThread.class) {
runningThreadCount--;
if (runningThreadCount == 0) {
System.out.println("文件下载完成");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
文件下载的原理和Demo算是写完了,但是自己写Bug很多,也不健壮。
而且作为程序员切记不要重复发明轮子,在GitHub上的开源项目XUtils就为我们提供了非常简洁的多线程下载文件的操作。
看一下下面,是不是非常的简单?
更多详细的信息,请参看这里:https://github.com/wyouflf/xUtils
HttpUtils http = new HttpUtils();
http.send(HttpRequest.HttpMethod.GET,
"http://www.lidroid.com",
new RequestCallBack<String>(){
@Override
public void onLoading(long total, long current, boolean isUploading) {
testTextView.setText(current + "/" + total);
}
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
textView.setText(responseInfo.result);
}
@Override
public void onStart() {
}
@Override
public void onFailure(HttpException error, String msg) {
}
});
原文地址:http://blog.csdn.net/biezhihua/article/details/44209225