上篇我们讲到了android中下载的断点续传问题,今天我们开始学习下载的多线程问题。本次的多线程源码下载:androdi中多线程下载的实现代码。有关断点续传的问题,请参见博客:android程序---->android多线程下载(一)
目录导航
- android中多线程下载的思路
- android中多线程中的原理说明
- android中多线程下载的实现
- 友情链接
android中多线程下载的思路
一、 多线程下载的步骤说明:
第一步: 我们要获得下载资源的的长度,用http请求中HttpURLConnection的getContentLength()方法
第二步:在本地创建一个文件,设计其长度。File file = new File()
第三步:根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
第四步:从下载的位置下载数据,通过connection.setRequestProperty("Range", "bytes=" + start + "-" + end)方法;
第五步:保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
二、 根据文件长度和线程数计算每条线程下载的数据长度和下载位置:
第一个线程从0~32字节处写入
第二个线程从33~65字节范围内写入
第三个线程从66~100字节范围内写入
android中多线程中的原理说明
对于多线程的下载,有两个需要学习的知识点就是
1. connection.setRequestProperty("Range", "bytes=" + start + "-" + end)方法,它用于请求指定范围内的数据。
2. RandomAccessFile类的seek方法从指定位置开始写入数据到文件:
一、 connection.setRequestProperty("Range", "bytes=" + start + "-" + end)方法:
我们写一个Java类进行测试,请求文件我本机上的android.txt文件是:http://192.168.250.232:8080/android.txt
测试类如下,Http请求2到10位置之间的数据,由于从0开始,所以请求的数据应该有12个字符为:name is huhx
package com.huhx.mutilthread;
import java.net.HttpURLConnection;
import java.net.URL;
public class MultiThread {
public static void main(String[] args) {
HttpURLConnection connection = null;
try {
URL url = new URL("http://192.168.250.232:8080/android.txt");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Range", "bytes=" + 3 + "-" + 14);
connection.setReadTimeout(5000);
connection.setRequestMethod("GET");
int length = -1;
if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
length = connection.getContentLength();
}
if (length < 0) {
return;
}
byte[] buffer = new byte[length];
while ((length = connection.getInputStream().read(buffer)) != -1) {
System.out.println(new String(buffer));
System.out.println("length: " + buffer.length);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
}
}
如我们所想打印结果如下:
二、 RandomAccessFile类的seek方法与多线程:
我们写一个线程Download,用于写入指定位置的数据到文件:
package com.huhx.randomfile;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
*
* @author huhx
*
*/
public class Download implements Runnable {
private String content;
private int start;
public Download(String content, int start) {
this.content = content;
this.start = start;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ", " + content);
File file = new File("output.txt");
RandomAccessFile accessFile = null;
try {
accessFile = new RandomAccessFile(file, "rwd");
accessFile.seek(start);
accessFile.write(content.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
accessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在MainTest类中,开启三个线程写入数据:
我们用一个string[]数组进行模拟,从服务器通过setRequestProperty方法设置Range参数得到的数据:
package com.huhx.randomfile;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author huhx
*
*/
public class MainTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
String[] contents = {"hello", "world", "linux"};
for(int i = 0; i < 3; i ++) {
Download download = new Download(contents[i], i * contents[i].length());
executorService.execute(download);
}
executorService.shutdown();
}
}
打印结果如下:
pool-1-thread-2, world
pool-1-thread-1, hello
pool-1-thread-3, linux
并且output.txt文件内容如下,三个线程需要读的顺序有先后,但是最终的结果还是我们预期要的:
helloworldlinux
android中多线程下载的实现
有了上述android中多线程中原理的测试的理解,相信我们对于android中使用多线程下载有了比较不错的印象。现在我们通过实例,来讲述整个实现过程。本次的案例,为也避免内容的冗余,没有加断点续传的功能。项目结构如下:
在手机的存储卡,得到下载完成的文件:
一、 在MainActivity中初始化开始下载按钮,绑定开始下载事件,传递参数文件的下载地址和文件的名称:
package com.example.linux.multithreaddownload;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
/**
* writer: huhx
*/
public class MainActivity extends AppCompatActivity {
private Button startButon;
private String fileUrl = "";
private String fileName = "linux.apk";
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
textView.setText(fileName);
startButon = (Button) findViewById(R.id.start);
startButon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, DownloadService.class);
intent.setAction(DownloadService.DOWNLOAD_START);
intent.putExtra("fileUrl", fileUrl);
intent.putExtra("fileName", fileName);
startService(intent);
}
});
}
}
二、 在DownloadService的onStartCommand方法中,启动一个线程去请求下载文件的大小:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DOWNLOAD_START.equals(intent.getAction())) {
fileUrl = intent.getStringExtra("fileUrl");
fileName = intent.getStringExtra("fileName");
// 开启多线程下载
new InitThread(fileUrl, fileName).start();
}
return super.onStartCommand(intent, flags, startId);
}
在InitThread线程中,代码如下,获取下载文件的长度,并发送下载的消息:
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 初始化子线程
*/
class InitThread extends Thread {
private String fileUrl;
private String fileName;
public InitThread(String fileUrl, String fileName) {
this.fileUrl = fileUrl;
this.fileName = fileName;
}
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(5000);
int length = -1;
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// 获取文件的长度
length = connection.getContentLength();
}
if (length <= 0) {
return;
}
handler.obtainMessage(DOWNLOAD, length).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
}
}
定义一个handler,用于消息的捕获及处理,在handleMessage方法中调用Download的download方法去下载文件:
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWNLOAD:
int fileLength = (int) msg.obj;
Download download = new Download();
download.download(fileUrl, fileName, fileLength);break;
}
}
};
二、 在Download的类中,download方法用于开启多个线程去下载文件:
package com.example.linux.multithreaddownload;
import android.content.Intent;
import android.os.Environment;
import android.widget.Toast;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Linux on 2016/4/10.
*/
public class Download {
final static int THREAD_NUMBER = 3;
private ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUMBER);
public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/download/";
public void download(String fileUrl, String fileName, int fileLength) {
int block = fileLength / THREAD_NUMBER;
File dir = new File(DOWNLOAD_PATH);
if (!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, fileName);
for (int i = 0; i < THREAD_NUMBER; i++) {
long start = i * block;
long end = (i + 1) * block - 1;
if (i == THREAD_NUMBER - 1) {
end = fileLength;
}
DownloadThread downloadThread = new DownloadThread(fileUrl, file.getAbsolutePath(), start, end);
executor.execute(downloadThread);
}
executor.shutdown();
}
}
四、 在DownloadThread中具体去执行下载文件的任务:
package com.example.linux.multithreaddownload;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created by huhxon 2016/4/10.
*/
public class DownloadThread implements Runnable {
private String fileUrl;
private String filePath;
private long start;
private long end;
public DownloadThread(String filUrl, String filePath, long start, long end) {
this.fileUrl = filUrl;
this.filePath = filePath;
this.start = start;
this.end = end;
}
@Override
public void run() {
Log.i("Main", "thread: " + Thread.currentThread().getName() + ", start: " + start + ", end: " + end);
HttpURLConnection connection = null;
RandomAccessFile raf = null;
InputStream is = null;
try {
URL url = new URL(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setRequestProperty("Range", "bytes=" + start + "-" + end);
raf = new RandomAccessFile(new File(filePath), "rwd");
raf.seek(start);
if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
is = connection.getInputStream();
byte[] buffer = new byte[4 * 1024];
int len;
while ((len = is.read(buffer)) != -1) {
raf.write(buffer, 0, len);
}
}
Log.i("Main", Thread.currentThread().getName() + "完成下载");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
raf.close();
is.close();
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五、 在AndroidManifest.xml文件中定义服务和声明权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<service android:name=".DownloadService" android:exported="true" />
六、 打印日志结果如下:
04-10 20:41:19.491 313-567/com.example.linux.multithreaddownload I/Main: thread: pool-1-thread-3, start: 2265146, end: 3397719
04-10 20:41:19.493 313-566/com.example.linux.multithreaddownload I/Main: thread: pool-1-thread-2, start: 1132573, end: 2265145
04-10 20:41:19.503 313-565/com.example.linux.multithreaddownload I/Main: thread: pool-1-thread-1, start: 0, end: 1132572
04-10 20:41:36.642 313-565/com.example.linux.multithreaddownload I/Main: pool-1-thread-1完成下载
04-10 20:41:37.119 313-567/com.example.linux.multithreaddownload I/Main: pool-1-thread-3完成下载
04-10 20:41:37.254 313-566/com.example.linux.multithreaddownload I/Main: pool-1-thread-2完成下载
友情链接
- 关于android中断点续传下载 android程序---->android多线程下载(一)
- androdi中多线程下载的实现代码