网络资源下载时断点续传的实现

断点续传用到的知识点:
1.使用RandomAccessFile设定文件大小并于指定位置开始读数据[randomAccessFile.seek(position)]。    
2.请求资源链接时指定所请求数据的返回范围。
    httpURLConnection.setRequestProperty("Range", "bytes=" + start + "-" + (contentLength - 1));

 

效果图如下[CSDN]:

下载gif

(相当抱歉,这个动画的时间太长了)

以下代码中的NetworkTool为通过个人编程经验封装好的网络工具类,强力推荐,当然也欢迎拍砖。
使用NetworkTool访问一个网络链接并获取数据的小示例为:

HttpURLConnection httpConn = NetworkTool.openUrl(context, url);
int respondCode = NetworkTool.connect(httpConn);
if (respondCode == HttpURLConnection.HTTP_OK) {
byte[] data = NetworkTool.fetchData_doClose(httpConn);
String content = new String(data);
data = null;
// parse content
} else {
// handles something
}
NetworkTool.disconnect(httpConn);

代码中的DontPressWithParentButton可用于ListView中,当点击该Button时不会触发ListView中的OnItemClickListener。实现方法为重写Button的setPressed(boolean)方法

@Override
public void setPressed(boolean pressed) {
if (pressed && ((View) getParent()).isPressed()) {
return;
}
super.setPressed(pressed);
}

代码如下:
lab.sodino.downloadbreak.ActDownload.java

package lab.sodino.downloadbreak;
import java.io.File;
import java.text.DecimalFormat;
import lab.sodino.downloadbreak.bean.BeanDownload;
import lab.sodino.downloadbreak.util.LogOut;
import lab.sodino.downloadbreak.util.NetworkTool;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class ActDownload extends Activity {
/** 下载存放地:"/sdcard/sodino/"。 */
public static final String RES_LOAD_FOLDER = File.separator + "sdcard" + File.separator
+ "sodino" + File.separator;
/** 刷新进度。 */
public static final int REFRESH = 1;
public static final int CODE = 10;
private BeanDownload bean;
private TextView txtName;
private TextView txtProgress;
private TextView txtSize;
private ProgressBar progressBar;
private Button btnAction;
private Handler handler;
private BtnListener btnListener;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.l_download);
initBeanDownload();
initViews$Handler();
}
private void initBeanDownload() {
bean = new BeanDownload();
bean.name = "微信.apk";
// 请找个可以无需跳转直接下载的地址
bean.url = "http://XXOO.com/weixin20android16.apk";
bean.state = BeanDownload.STATE_INTERRUPTED;
bean.size = bean.loadedSize = 0l;
bean.enable = true;
}
private void initViews$Handler() {
txtName = (TextView) findViewById(R.id.txtName);
txtName.setText(bean.name);
txtProgress = (TextView) findViewById(R.id.txtProgress);
txtProgress.setText(getProgressTxt(bean));
txtSize = (TextView) findViewById(R.id.txtSize);
txtSize.setText(formatSizeTxt(bean.size));
progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setProgress(getProgressInt(bean, progressBar.getMax()));
btnListener = new BtnListener();
btnAction = (Button) findViewById(R.id.btnAction);
btnAction.setOnClickListener(btnListener);
btnAction.setText(getTxt(bean));
btnAction.setEnabled(isEnable(bean));
// handler
handler = new Handler() {
public void handleMessage(Message msg) {
txtProgress.setText(getProgressTxt(bean));
txtSize.setText(formatSizeTxt(bean.size));
progressBar.setProgress(getProgressInt(bean, progressBar.getMax()));
btnAction.setText(getTxt(bean));
btnAction.setEnabled(isEnable(bean));
}
};
}
private void pauseDownload() {
bean.enable = false;
handler.sendEmptyMessage(REFRESH);
}
private void doDownload() {
handler.sendEmptyMessage(REFRESH);
new DownloadThread().start();
}
private void reloadDownload() {
bean.size = bean.loadedSize = 0;
bean.enable = true;
doDownload();
}
private void installDownload() {
Intent intent = new Intent(Intent.ACTION_VIEW);
String filePath = RES_LOAD_FOLDER + bean.name;
intent.setDataAndType(Uri.parse("file://" + filePath),
"application/vnd.android.package-archive");
// 如果仅是简单的startActivity(intent),会造成onCreate()再执行一次。
ActDownload.this.startActivityForResult(intent, CODE);
}
class BtnListener implements Button.OnClickListener {
public void onClick(View v) {
LogOut.out(this, "state:" + bean.state);
switch (bean.state) {
case BeanDownload.STATE_LOADING:
// 点击了"暂停"
pauseDownload();
break;
case BeanDownload.STATE_INTERRUPTED:
// 点击了"继续"
doDownload();
break;
case BeanDownload.STATE_DOWNLOAD_FAIL:
// 点击了"重载"
reloadDownload();
break;
case BeanDownload.STATE_COMPLETED:
// 点击了"安装"
installDownload();
break;
}
}
}
class DownloadThread extends Thread {
public void run() {
bean.state = BeanDownload.STATE_LOADING;
bean.enable = true;
NetworkTool.download2File(ActDownload.this, bean, handler);
LogOut.out(this, "size:" + bean.size + " loaded:" + bean.loadedSize + " enable:"
+ bean.enable);
// 测试“重载”请释放下面代码的注释然后等待下载正常结束
// bean.loadedSize = 0;
if (bean.size > 0 && bean.loadedSize == bean.size) {
String localPath = RES_LOAD_FOLDER + bean.name;
File tmpFile = new File(localPath + ".tmp");
tmpFile.renameTo(new File(localPath));
bean.enable = false;
bean.state = BeanDownload.STATE_COMPLETED;
} else {
if (bean.enable == false) {
bean.state = BeanDownload.STATE_INTERRUPTED;
} else {
bean.state = BeanDownload.STATE_DOWNLOAD_FAIL;
}
}
LogOut.out(this, "state=" + bean.state);
handler.sendEmptyMessage(REFRESH);
}
}
public static String getProgressTxt(BeanDownload bean) {
String resStr = "0%";
if (bean.size != 0) {
double result = bean.loadedSize * 1.0 / bean.size;
DecimalFormat decFormat = new DecimalFormat("#.#%");
resStr = decFormat.format(result);
}
return resStr;
}
private String formatSizeTxt(long size) {
String sizeTxt = "未知";
if (size > 0) {
size = size >> 10;
sizeTxt = String.valueOf(size) + "k";
}
return sizeTxt;
}
public static int getProgressInt(BeanDownload bean, int max) {
int result = (bean.size > 0) ? (int) (bean.loadedSize * max * 1.0 / bean.size) : 0;
return result;
}
private String getTxt(BeanDownload bean) {
String txt = "安装";
switch (bean.state) {
case BeanDownload.STATE_COMPLETED:
txt = "安装";
break;
case BeanDownload.STATE_LOADING:
txt = "暂停";
break;
case BeanDownload.STATE_INTERRUPTED:
txt = "继续";
break;
case BeanDownload.STATE_DOWNLOAD_FAIL:
txt = "重载";
break;
}
return txt;
}
private boolean isEnable(BeanDownload bean) {
boolean enable = true;
if (bean.enable == false && bean.state == BeanDownload.STATE_LOADING) {
enable = false;
}
return enable;
}
}

lab.sodino.downloadbreak.bean.BeanDownload.java

package lab.sodino.downloadbreak.bean;
/**
* @author Sodino E-mail:sodinoopen@hotmail.com
* @version Time:2011-6-8 下午11:33:10
*/
public class BeanDownload {
/** 正在下载数据。Button应显示“暂停”。 */
public static final int STATE_LOADING = 0;
/** 数据全部下载完成。Button应显示“安装”。 */
public static final int STATE_COMPLETED = 1;
/** 数据下载过程中被暂停。Button应显示“继续”。 */
public static final int STATE_INTERRUPTED = 2;
/** 下载安装包失败。Button应显示“失败”。 */
public static final int STATE_DOWNLOAD_FAIL = 3;
public String name;
public long size;
public long loadedSize;
public String url;
public int state;
public boolean enable;
}

lab.sodino.downloadbreak.ui.DontPressWithParentButton.java

package lab.sodino.downloadbreak.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
/**
* @author Sodino E-mail:sodinoopen@hotmail.com
* @version Time:2011-6-5 下午08:37:27
*/
public class DontPressWithParentButton extends Button {
public DontPressWithParentButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setPressed(boolean pressed) {
if (pressed && ((View) getParent()).isPressed()) {
return;
}
super.setPressed(pressed);
}
}

lab.sodino.downloadbreak.util.NetworkTool.java

package lab.sodino.downloadbreak.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import lab.sodino.downloadbreak.ActDownload;
import lab.sodino.downloadbreak.bean.BeanDownload;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
/**
* 管理联网操作,包括管理url参数、下载APK包、获取任务字符串。<br/>
*
* @author Sodino E-mail:sodinoopen@hotmail.com
* @version Time:2011-4-6 下午03:42:50
*/
public class NetworkTool {
/**
* 开启一个HTTP链接。
*/
public static HttpURLConnection openUrl(Context context, String urlStr) {
LogOut.out("Network", "urlStr[" + urlStr + "]");
URL urlURL = null;
HttpURLConnection httpConn = null;
try {
urlURL = new URL(urlStr);
// 需要android.permission.ACCESS_NETWORK_STATE
// 在没有网络的情况下,返回值为null。
NetworkInfo networkInfo = ((ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
// 如果是使用的运营商网络
if (networkInfo != null) {
if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
// 获取默认代理主机ip
String host = android.net.Proxy.getDefaultHost();
// 获取端口
int port = android.net.Proxy.getDefaultPort();
if (host != null && port != -1) {
// 封装代理連接主机IP与端口号。
InetSocketAddress inetAddress = new InetSocketAddress(host, port);
// 根据URL链接获取代理类型,本链接适用于TYPE.HTTP
java.net.Proxy.Type proxyType = java.net.Proxy.Type.valueOf(urlURL
.getProtocol().toUpperCase());
java.net.Proxy javaProxy = new java.net.Proxy(proxyType, inetAddress);
httpConn = (HttpURLConnection) urlURL.openConnection(javaProxy);
} else {
httpConn = (HttpURLConnection) urlURL.openConnection();
}
} else {
httpConn = (HttpURLConnection) urlURL.openConnection();
}
httpConn.setDoInput(true);
} else {
// LogOut.out(this, "No Avaiable Network");
}
} catch (NullPointerException npe) {
npe.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return httpConn;
}
/** 启动链接并将RespondCode值返回。 */
public static int connect(HttpURLConnection httpConn) {
int code = -1;
if (httpConn != null) {
try {
httpConn.connect();
code = httpConn.getResponseCode();
} catch (IOException e) {
e.printStackTrace();
}
}
LogOut.out("NetworkTool", "respond_code=" + code);
return code;
}
/**
* 将指定的HTTP链接内容存储到指定的的文件中。<br/>
* 返回值仅当参考。<br/>
*
* @param httpConn
* @param filePath
* 指定存储的文件路径。
*/
public static boolean download2File(HttpURLConnection httpConn, String filePath) {
boolean result = true;
File file = new File(filePath);
FileOutputStream fos = null;
byte[] data = new byte[1024];
int readLength = -1;
InputStream is = null;
try {
fos = new FileOutputStream(file);
is = httpConn.getInputStream();
while ((readLength = is.read(data)) != -1) {
fos.write(data, 0, readLength);
fos.flush();
}
fos.flush();
} catch (IOException ie) {
result = false;
ie.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException ie) {
ie.printStackTrace();
}
}
return result;
}
/**
* 将bean资源下载。<br/>
* 支持断点续传。
*
*/
public static void download2File(Context context, BeanDownload bean, Handler handler) {
String filePath = ActDownload.RES_LOAD_FOLDER + bean.name + ".tmp";
HttpURLConnection httpConn = null;
File file = new File(filePath);
RandomAccessFile randomFile = null;
FileOutputStream fos = null;
int dataBlockLength = 2048;
byte[] data = new byte[dataBlockLength];
int readLength = -1;
InputStream is = null;
try {
if (bean.size <= 0) {
bean.loadedSize = 0;
if (file.getParentFile().exists() == false) {
file.getParentFile().mkdirs();
}
if (file.exists() == false) {
file.createNewFile();
}
// 采用普通的下载方式
fos = new FileOutputStream(file);
httpConn = openUrl(context, bean.url);
int respondCode = connect(httpConn);
LogOut.out("NetworkTool", "respondCode=" + respondCode);
if (respondCode == HttpURLConnection.HTTP_OK) {
bean.size = httpConn.getContentLength();
is = httpConn.getInputStream();
while ((readLength = is.read(data)) != -1 && bean.enable) {
fos.write(data, 0, readLength);
bean.loadedSize += readLength;
handler.sendEmptyMessage(ActDownload.REFRESH);
}
}
} else {
// 采用断点续传方式
randomFile = new RandomAccessFile(file, "rw");
randomFile.setLength(bean.size);
httpConn = openUrl(context, bean.url);
httpConn.setRequestProperty("Range", "bytes=" + bean.loadedSize + "-"
+ (bean.size - 1));
int respondCode = connect(httpConn);
if (respondCode == HttpURLConnection.HTTP_PARTIAL) {
is = httpConn.getInputStream();
while ((readLength = is.read(data)) != -1 && bean.enable) {
randomFile.seek(bean.loadedSize);
randomFile.write(data, 0, readLength);
bean.loadedSize += readLength;
handler.sendEmptyMessage(ActDownload.REFRESH);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (httpConn != null) {
disconnect(httpConn);
}
if (fos != null) {
fos.close();
}
if (randomFile != null) {
randomFile.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/** 读取HttpURLConnection的数据并关闭相关流。 */
public static byte[] fetchData_doClose(HttpURLConnection httpConn) {
byte[] data = null;
ByteArrayOutputStream baos = null;
InputStream is = null;
int read = -1;
try {
baos = new ByteArrayOutputStream();
is = httpConn.getInputStream();
while ((read = is.read()) != -1) {
baos.write(read);
}
data = baos.toByteArray();
} catch (IOException ie) {
ie.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (baos != null) {
baos.close();
}
if (httpConn != null) {
httpConn.disconnect();
}
} catch (IOException ie) {
ie.printStackTrace();
}
}
return data;
}
public static void disconnect(HttpURLConnection httpConn) {
if (httpConn != null) {
httpConn.disconnect();
}
}
}

/res/drawable/l_download.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dip"
android:layout_marginBottom="0dip">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
android:orientation="horizontal">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/txtName"
android:singleLine="true"
android:ellipsize="middle"
android:textColor="#ffffffff"
android:textSize="15sp"
></TextView>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/txtProgress"
android:textColor="#ffffffff"
android:textSize="10sp"
android:layout_marginRight="10dip"
></TextView>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/txtSize"
android:textColor="#ffffffff"
android:textSize="10sp"
></TextView>
</LinearLayout>
<ProgressBar android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal" mce_style="?android:attr/progressBarStyleHorizontal"
></ProgressBar>
</LinearLayout>
<lab.sodino.downloadbreak.ui.DontPressWithParentButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="60dip"
android:minHeight="30dip"
android:id="@+id/btnAction"
android:textColor="#ff000000"
android:textStyle="bold"
android:layout_marginRight="5dip"
android:layout_marginLeft="5dip"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
android:focusable="false"
android:focusableInTouchMode="false"
></lab.sodino.downloadbreak.ui.DontPressWithParentButton>
</LinearLayout>

所要添加的权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

posted on 2013-04-19 13:50  楠妮儿  阅读(234)  评论(0编辑  收藏  举报

导航