Android开发中经常会遇到文件的下载,而下载的时间与网络状态和被下载文件的大小等因素有关。本文会对单线程下载和多线程下载做简要说明。无论哪种操作,最终都是基于HTTP(HTTPS)的网络访问。
先看看基本的一个操作流程,然后对于每一个操作步骤逐一实现就可以了。
基本流程:
- 设置连接属性;
- 建立连接;
- 获得需要的资源;
- 释放资源;
说明:这个流程是相对的,每个人可以根据具体情况而定。:)
1、在普通Java项目中下载文件: 为了简化问题,我们先不考虑在Android中实现过程,仅仅实现一个下载并保存网络资源的操作。
代码如下:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileDownload {
/**
* @param args
*/
public static void main(String[] args) {
downloadFile("http://image.tianjimedia.com/uploadImages/2012/153/2H4Y79KZF2X3.jpg", "image.jpg");
downloadFile("http://www.google.com", "message.txt");
}
private static void downloadFile(String urlPath, String filePath) {
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
byte[] data = null;
try {
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(6 * 1000);
conn.connect();
if (conn.getResponseCode() == 200) {
inputStream = conn.getInputStream();
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = -1;
while ((length = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, length);
}
data = byteArrayOutputStream.toByteArray();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
if (data != null) {
File file = new File(filePath);
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
outputStream.write(data);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
说明:
1、InputStream inputStream = null;和ByteArrayOutputStream byteArrayOutputStream = null;之所以在try-catch外定义,是为了可以在finally代码块中可以访问,并释放该资源。在回收资源时,最好把不同资源放在单独的try-catch块中做资源释放操作,否则可能造成资源泄漏。
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
......
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
如果采用上述资源释放代码,当inputStream和byteArrayOutputStream都不为null时,如果inputStream.close();发生异常,将导致byteArrayOutputStream.close();不被执行,从而没有释放该资源。
2、conn.setRequestMethod("GET");和conn.setConnectTimeout(6 * 1000);是设置连接属性。还有其它属性,如果需要的话,可以进行设定。
3、conn.connect();建立连接。
4、conn.getInputStream();获得连接后的输入流,从中可以获得数据。
5、在finally代码块中对申请的资源进行释放。
运行该项目,会在项目的根目录下看到两个文件,image.jpg和message.txt,如图所示:
2、在Android项目中下载文件:
a、对UI的设计:
主Activity中有两个Button,分别启动另外的两个Activity,来实现图片的下载显示和网页内容的获得并显示。布局文件就补贴出来了,会在附件中提供源码。
b、主Activity没有什么好说的,下面对另外两个Activity做详细说明:
DownloadImageActivity:
package com.anhuioss.download;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
public class DownloadImageActivity extends Activity implements OnClickListener {
private EditText filePathEditText;
private Button downloadButton;
private ImageView imageDisplay;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download_image);
filePathEditText = (EditText) findViewById(R.id.adi_image_path);
downloadButton = (Button) findViewById(R.id.adi_button_download);
imageDisplay = (ImageView) findViewById(R.id.adi_image);
downloadButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == downloadButton) {
if (!Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
return;
}
String path = filePathEditText.getText().toString();
if (!TextUtils.isEmpty(path)) {
downloadButton.setEnabled(false);
imageDisplay.setImageDrawable(null);
downloadFile(path, Environment.getExternalStorageDirectory() + File.separator + "image.jpg");
}
}
}
private void downloadFile(final String urlPath, final String filePath) {
new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
byte[] data = null;
try {
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(6 * 1000);
conn.connect();
if (conn.getResponseCode() == 200) {
inputStream = conn.getInputStream();
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = -1;
while ((length = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, length);
}
data = byteArrayOutputStream.toByteArray();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
if (data != null) {
File file = new File(filePath);
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
outputStream.write(data);
handler.post(new Runnable() {
@Override
public void run() {
downloadButton.setEnabled(true);
Drawable drawable = null;
try {
drawable = Drawable.createFromPath(filePath);
} catch (Error e) {
e.printStackTrace();
}
imageDisplay.setImageDrawable(drawable);
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}).start();
}
}
说明:
1、private Handler handler = new Handler();handler的作用是在下载线程中完成下载后在UI线程里面更新UI。
2、由于示例中需要用到SDCARD,所以在做下载操作时,如果没有挂载SDCARD时,不进行下载处理:
if (!Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
return;
}
3、
handler.post(new Runnable() {
@Override
public void run() {
downloadButton.setEnabled(true);
Drawable drawable = null;
try {
drawable = Drawable.createFromPath(filePath);
} catch (Error e) {
e.printStackTrace();
}
imageDisplay.setImageDrawable(drawable);
}
});
利用handler的post方法,在UI线程里面更新UI。首先使能按钮,其次创建drawable,最后显示图片。
3、运行项目,查看效果: 在运行前,不要忘记在Manifest文件中添加网络访问和访问SDCARD的权限哦!
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
下载图片的效果如下:
获取网页内容的页面效果:
4、多线程文件下载,直接看普通Java项目中相关操作的代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileDownloadMultiThread {
public static void main(String[] args) {
downloadFile();
}
private static void downloadFile() {
String fileName = "mysql.chm";
String urlPath = "http://192.168.1.106/mysql.chm";
// 下载开始前,创建RandomAccessFile并设置文件大小
RandomAccessFile file = null;
try {
// 创建URL对象
URL url = new URL(urlPath);
// 创建HttpURL连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求属性
conn.setConnectTimeout(6 * 1000);
conn.setRequestMethod("GET");
// 连接
conn.connect();
// 需要下载文件的长度
int fileLength = conn.getContentLength();
// 创建RandomAccessFile并设置文件大小
file = new RandomAccessFile(fileName, "rw");
file.setLength(fileLength);
// 启动的线程数量
int threadCount = 6;
// 计算每个线程需要下载数据的长度
int threadDownloadLength = fileLength % 3 == 0 ? fileLength / 3 : fileLength / 3 + 1;
// 创建threadCount个下载线程
for (int i = 0; i < threadCount; i++) {
// 计算开始位置
int startPosition = i * threadDownloadLength;
// 创建匿名下载线程对象
new MultiDownloadThread(fileName, urlPath, startPosition).start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 资源释放
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
static class MultiDownloadThread extends Thread {
private String urlPath;
private int startPosition;
private RandomAccessFile accessFile;
public MultiDownloadThread(String fileName, String urlPath, int startPosition) {
this.urlPath = urlPath;
this.startPosition = startPosition;
try {
accessFile = new RandomAccessFile(fileName, "rw");
accessFile.seek(startPosition);
} catch (Exception e) {
e.printStackTrace();
}
}
public void run () {
InputStream inputStream = null;
try {
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(6 * 1000);
conn.setRequestMethod("GET");
// 设置HTTP的Range字段,指定该线程从文件的什么位置开始下载
conn.setRequestProperty("Range", "bytes=" + startPosition + "-");
conn.connect();
inputStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int readLength = -1;
int totalLength = 0;
while ((readLength = inputStream.read(buffer)) != -1) {
accessFile.write(buffer, 0, readLength);
totalLength = totalLength + readLength;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (accessFile != null) {
try {
accessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
说明:
1、RandomAccessFile类创建目标文件并设置文件大小;
2、HttpURLConnection.setRequestProperty("Range", "bytes=" + startPosition + "-");方法指定下载位置;
3、RandomAccessFile.seek();设置文件指针偏移量,保存下载内容到指定位置;
4、由于线程的创建和销毁过程,线程之间的上下文切换等都需要系统资源,所以threadCount不是越大越好,而是根据具体情况进行设定;
4、在Android上实现多线程下载的方法类似,注意Android环境的特性就可以了,再次不再赘述!:)
5、多说一句: HTTP(HTTPS)的连接到网络上的数据资源的方式和设置多种多样,本例仅仅是从一个角度进行说明,希望对你有所帮助!如果需要Android项目源码,请查看附件!:)