几个通用的类,迷你型的Android下载框架
1) 从输入流中获取数据并以字节数组返回,这种输入流可以来自Android本地也可以来自网络。
代码 1 import java.io.ByteArrayOutputStream;
2
3 import java.io.InputStream;
4
5
6
7 public class StreamTool {
8
9 /**
10
11 * 从输入流获取数据
12
13 * @param inputStream
14
15 * @return
16
17 * @throws Exception
18
19 */
20
21 public static byte [] readInputStream(InputStream inputStream) throws Exception {
22
23 byte [] buffer = new byte [ 1024 ]; // 你可以根据实际需要调整缓存大小
24
25 int len = - 1 ;
26
27 ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
28
29 while ( (len = inputStream.read(buffer)) != - 1 ){
30
31 outSteam.write(buffer, 0 , len);
32
33 }
34
35 outSteam.close();
36
37 inputStream.close();
38
39 return outSteam.toByteArray();
40
41 }
42
43 }
44
45
2) 通过Android客户端上传数据到服务器:可以上传简单的表单,也可以方便的上传带有附件的文件,此类远远比Android自身的HttpClient更高效、更易于使用:
代码 import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
public class HttpRequester {
/**
* 直接通过HTTP协议提交数据到服务器,实现如下面表单提交功能:
* :8080/ssi/fileload/test.do" enctype="multipart/form-data">
* @param actionUrl 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.cnblogs.com/guoshiandroid或http://192.168.1.10:8080这样的路径测试)
* @param params 请求参数 key为参数名,value为参数值
* @param file 上传文件
*/
public static String post(String actionUrl, Map < String, String > params, FormFile[] files) {
try {
String BOUNDARY = " ---------7d4a6d158c9 " ; // 数据分隔线
String MULTIPART_FORM_DATA = " multipart/form-data " ;
URL url = new URL(actionUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout( 5 * 1000 );
conn.setDoInput( true ); // 允许输入
conn.setDoOutput( true ); // 允许输出
conn.setUseCaches( false ); // 不使用Cache
conn.setRequestMethod( " POST " );
conn.setRequestProperty( " Connection " , " Keep-Alive " );
conn.setRequestProperty( " Charset " , " UTF-8 " );
conn.setRequestProperty( " Content-Type " , MULTIPART_FORM_DATA + " ; boundary= " + BOUNDARY);
StringBuilder sb = new StringBuilder();
for (Map.Entry < String, String > entry : params.entrySet()) { // 构建表单字段内容
sb.append( " -- " );
sb.append(BOUNDARY);
sb.append( " /r/n " );
sb.append( " Content-Disposition: form-data; name=/ "" + entry.getKey() + " / " /r/n/r/n " );
sb.append(entry.getValue());
sb.append( " /r/n " );
}
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
outStream.write(sb.toString().getBytes()); // 发送表单字段数据
for (FormFile file : files){ // 发送文件数据
StringBuilder split = new StringBuilder();
split.append( " -- " );
split.append(BOUNDARY);
split.append( " /r/n " );
split.append( " Content-Disposition: form-data;name=/ "" + file.getFormname()+ " / " ;filename=/ "" + file.getFilname() + " / " /r/n " );
split.append( " Content-Type: " + file.getContentType() + " /r/n/r/n " );
outStream.write(split.toString().getBytes());
if (file.getInStream() != null ){
byte [] buffer = new byte [ 1024 ];
int len = 0 ;
while ((len = file.getInStream().read(buffer)) !=- 1 ){
outStream.write(buffer, 0 , len);
}
file.getInStream().close();
} else {
outStream.write(file.getData(), 0 , file.getData().length);
}
outStream.write( " /r/n " .getBytes());
}
byte [] end_data = ( " -- " + BOUNDARY + " --/r/n " ).getBytes(); // 数据结束标志
outStream.write(end_data);
outStream.flush();
int cah = conn.getResponseCode();
if (cah != 200 ) throw new RuntimeException( " 请求url失败 " );
InputStream is = conn.getInputStream();
int ch;
StringBuilder b = new StringBuilder();
while ( (ch = is.read()) != - 1 ){
b.append(( char )ch);
}
outStream.close();
conn.disconnect();
return b.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 提交数据到服务器
* @param actionUrl 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.cnblogs.com/guoshiandroid或http://192.168.1.10:8080这样的路径测试)
* @param params 请求参数 key为参数名,value为参数值
* @param file 上传文件
*/
public static String post(String actionUrl, Map < String, String > params, FormFile file) {
return post(actionUrl, params, new FormFile[]{file});
}
public static byte [] postFromHttpClient(String path, Map < String, String > params, String encode) throws Exception{
List < NameValuePair > formparams = new ArrayList < NameValuePair > (); // 用于存放请求参数
for (Map.Entry < String, String > entry : params.entrySet()){
formparams.add( new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, " UTF-8 " );
HttpPost httppost = new HttpPost(path);
httppost.setEntity(entity);
HttpClient httpclient = new DefaultHttpClient(); // 看作是浏览器
HttpResponse response = httpclient.execute(httppost); // 发送post请求
return StreamTool.readInputStream(response.getEntity().getContent());
}
/**
* 发送请求
* @param path 请求路径
* @param params 请求参数 key为参数名称 value为参数值
* @param encode 请求参数的编码
*/
public static byte [] post(String path, Map < String, String > params, String encode) throws Exception{
// String params = "method=save&name="+ URLEncoder.encode("国士工作室", "UTF-8")+ "&age=28&"; // 需要发送的参数
StringBuilder parambuilder = new StringBuilder( "" );
if (params != null && ! params.isEmpty()){
for (Map.Entry < String, String > entry : params.entrySet()){
parambuilder.append(entry.getKey()).append( " = " )
.append(URLEncoder.encode(entry.getValue(), encode)).append( " & " );
}
parambuilder.deleteCharAt(parambuilder.length() - 1 );
}
byte [] data = parambuilder.toString().getBytes();
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput( true ); // 允许对外发送请求参数
conn.setUseCaches( false ); // 不进行缓存
conn.setConnectTimeout( 5 * 1000 );
conn.setRequestMethod( " POST " );
// 下面设置http请求头
conn.setRequestProperty( " Accept " , " image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* " );
conn.setRequestProperty( " Accept-Language " , " zh-CN " );
conn.setRequestProperty( " User-Agent " , " Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729) " );
conn.setRequestProperty( " Content-Type " , " application/x-www-form-urlencoded " );
conn.setRequestProperty( " Content-Length " , String.valueOf(data.length));
conn.setRequestProperty( " Connection " , " Keep-Alive " );
// 发送参数
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
outStream.write(data); // 把参数发送出去
outStream.flush();
outStream.close();
if (conn.getResponseCode() == 200 ){
return StreamTool.readInputStream(conn.getInputStream());
}
return null ;
}
}
2,Inventing the Wheel(发明轮子)。
发明轮子?不错,发明轮子!我们不仅要发明轮子,更要成为努力成为世界上发明轮子的主导力量,唯有这样,才能谈的上中华名族软件大业的真正强大。在Android,要发明轮子,就是我们要主动的是解决一些世界上他人未解决的难题或者创造新的编程框架或者对Android进行深度的改造以适合自己的业务发展需要。Google发布了Android后不久,中国移动便投入了大量的人力和物力,在Android的基础上创建融入自己业务并开发、封装了新的功能的和框架的OMS,这是Android中发明轮子的一个非常重要的例子。可能你会说,这发明轮子也太难了吧,别急,我们慢慢来,开发一个框架特定领域的框架吧!你可能会一脸无辜的说,开发一个框架是说的那么容易吗?当然不是啦。但是也并非不可能,首先,我们分析一下框架的魅力的源泉,看看Spring、Struts等Java EE框架,在看看.NET框架,当然也可以看看发展的如火如荼、层出不穷的PHP框架,她们的强大和魅力的源泉都在于:IoC(Inversion of Control)。
Don't call us, we'll call you(别找我,我会来找你的)。我们下面就自己发明一个轮子的模型,实际展示一个框架最初核心的类,让你一饱眼福:
1) 下面的类是文件下载类,支持文件的多线程断点续传,使用该类的即可安全、高效的下载任何类型的二进制文件:
代码 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cn.itcast.service.FileService;
import android.content.Context;
import android.util.Log;
/**
* 文件下载器
*/
public class FileDownloader {
private Context context;
private FileService fileService;
private static final String TAG = " FileDownloader " ;
/* 已下载文件大小 */
private int downloadSize = 0 ;
/* 原始文件大小 */
private int fileSize = 0 ;
/* 线程数 */
private DownloadThread[] threads;
/* 下载路径 */
private URL url;
/* 本地保存文件 */
private File saveFile;
/* 下载记录文件 */
private File logFile;
/* 缓存各线程最后下载的位置 */
private Map < Integer, Integer > data = new ConcurrentHashMap < Integer, Integer > ();
/* 每条线程下载的大小 */
private int block;
private String downloadUrl; // 下载路径
/**
* 获取线程数
*/
public int getThreadSize() {
return threads.length;
}
/**
* 获取文件大小
* @return
*/
public int getFileSize() {
return fileSize;
}
/**
* 累计已下载大小
* @param size
*/
protected synchronized void append( int size) {
downloadSize += size;
}
/**
* 更新指定线程最后下载的位置
* @param threadId 线程id
* @param pos 最后下载的位置
*/
protected void update( int threadId, int pos) {
this .data.put(threadId, pos);
}
/**
* 保存记录文件
*/
protected synchronized void saveLogFile() {
this .fileService.update( this .downloadUrl, this .data);
}
/**
* 构建文件下载器
* @param downloadUrl 下载路径
* @param fileSaveDir 文件保存目录
* @param threadNum 下载线程数
*/
public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {
try {
this .context = context;
this .downloadUrl = downloadUrl;
fileService = new FileService(context);
this .url = new URL(downloadUrl);
if ( ! fileSaveDir.exists()) fileSaveDir.mkdirs();
this .threads = new DownloadThread[threadNum];
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout( 6 * 1000 );
conn.setRequestMethod( " GET " );
conn.setRequestProperty( " Accept " , " image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* " );
conn.setRequestProperty( " Accept-Language " , " zh-CN " );
conn.setRequestProperty( " Referer " , downloadUrl);
conn.setRequestProperty( " Charset " , " UTF-8 " );
conn.setRequestProperty( " User-Agent " , " Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729) " );
conn.setRequestProperty( " Connection " , " Keep-Alive " );
conn.connect();
printResponseHeader(conn);
if (conn.getResponseCode() == 200 ) {
this .fileSize = conn.getContentLength(); // 根据响应获取文件大小
if ( this .fileSize <= 0 ) throw new RuntimeException( " 1无法获知文件大小 " );
String filename = getFileName(conn);
this .saveFile = new File(fileSaveDir, filename); /* 保存文件 */
Map < Integer, Integer > logdata = fileService.getData(downloadUrl);
if (logdata.size() > 0 ){
for (Map.Entry < Integer, Integer > entry : logdata.entrySet())
data.put(entry.getKey(), entry.getValue() + 1 );
}
this .block = this .fileSize / this .threads.length + 1 ;
if ( this .data.size() == this .threads.length){
for ( int i = 0 ; i < this .threads.length; i ++ ) {
this .downloadSize += this .data.get(i + 1 ) - ( this .block * i);
}
print( " 已经下载的长度 " + this .downloadSize);
}
} else {
throw new RuntimeException( " 2服务器响应错误 " );
}
} catch (Exception e) {
print(e.toString());
throw new RuntimeException( " 3连接不到下载路径 " );
}
}
/**
* 获取文件名
*/
private String getFileName(HttpURLConnection conn) {
String filename = this .url.toString().substring( this .url.toString().lastIndexOf( ' / ' ) + 1 );
if (filename == null || "" .equals(filename.trim())){ // 如果获取不到文件名称
for ( int i = 0 ;; i ++ ) {
String mine = conn.getHeaderField(i);
if (mine == null ) break ;
if ( " content-disposition " .equals(conn.getHeaderFieldKey(i).toLowerCase())){
Matcher m = Pattern.compile( " .*filename=(.*) " ).matcher(mine.toLowerCase());
if (m.find()) return m.group( 1 );
}
}
filename = UUID.randomUUID() + " .tmp " ; // 默认取一个文件名
}
return filename;
}
/**
* 开始下载文件
* @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null
* @return 已下载文件大小
* @throws Exception
*/
public int download(DownloadProgressListener listener) throws Exception{
try {
if ( this .data.size() != this .threads.length){
this .data.clear();
for ( int i = 0 ; i < this .threads.length; i ++ ) {
this .data.put(i + 1 , this .block * i);
}
}
for ( int i = 0 ; i < this .threads.length; i ++ ) {
int downLength = this .data.get(i + 1 ) - ( this .block * i);
if (downLength < this .block && this .data.get(i + 1 ) < this .fileSize){ // 该线程未完成下载时,继续下载
RandomAccessFile randOut = new RandomAccessFile( this .saveFile, " rw " );
if ( this .fileSize > 0 ) randOut.setLength( this .fileSize);
randOut.seek( this .data.get(i + 1 ));
this .threads[i] = new DownloadThread( this , this .url, randOut, this .block, this .data.get(i + 1 ), i + 1 );
this .threads[i].setPriority( 7 );
this .threads[i].start();
} else {
this .threads[i] = null ;
}
}
this .fileService.save( this .downloadUrl, this .data);
boolean notFinish = true ; // 下载未完成
while (notFinish) { // 循环判断是否下载完毕
Thread.sleep( 900 );
notFinish = false ; // 假定下载完成
for ( int i = 0 ; i < this .threads.length; i ++ ){
if ( this .threads[i] != null && ! this .threads[i].isFinish()) {
notFinish = true ; // 下载没有完成
if ( this .threads[i].getDownLength() == - 1 ){ // 如果下载失败,再重新下载
RandomAccessFile randOut = new RandomAccessFile( this .saveFile, " rw " );
randOut.seek( this .data.get(i + 1 ));
this .threads[i] = new DownloadThread( this , this .url, randOut, this .block, this .data.get(i + 1 ), i + 1 );
this .threads[i].setPriority( 7 );
this .threads[i].start();
}
}
}
if (listener != null ) listener.onDownloadSize( this .downloadSize);
}
fileService.delete( this .downloadUrl);
} catch (Exception e) {
print(e.toString());
throw new Exception( " 下载失败 " );
}
return this .downloadSize;
}
/**
* 获取Http响应头字段
* @param http
* @return
*/
public static Map < String, String > getHttpResponseHeader(HttpURLConnection http) {
Map < String, String > header = new LinkedHashMap < String, String > ();
for ( int i = 0 ;; i ++ ) {
String mine = http.getHeaderField(i);
if (mine == null ) break ;
header.put(http.getHeaderFieldKey(i), mine);
}
return header;
}
/**
* 打印Http头字段
* @param http
*/
public static void printResponseHeader(HttpURLConnection http){
Map < String, String > header = getHttpResponseHeader(http);
for (Map.Entry < String, String > entry : header.entrySet()){
String key = entry.getKey() != null ? entry.getKey() + " : " : "" ;
print(key + entry.getValue());
}
}
private static void print(String msg){
Log.i(TAG, msg);
}
public static void main(String[] args) {
/* FileDownloader loader = new FileDownloader(context, " http://browse.babasport.com/ejb3/ActivePort.exe ",
new File("D://androidsoft//test"), 2);
loader.getFileSize();//得到文件总大小
try {
loader.download(new DownloadProgressListener(){
public void onDownloadSize(int size) {
print("已经下载:"+ size);
}
});
} catch (Exception e) {
e.printStackTrace();
} */
}
}
2) 下面的类是真正支持下载的线程类:
代码 import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.util.Log;
public class DownloadThread extends Thread {
private static final String TAG = " DownloadThread " ;
private RandomAccessFile saveFile;
private URL downUrl;
private int block;
/* 下载开始位置 */
private int threadId = - 1 ;
private int startPos;
private int downLength;
private boolean finish = false ;
private FileDownloader downloader;
public DownloadThread(FileDownloader downloader, URL downUrl, RandomAccessFile saveFile, int block, int startPos, int threadId) {
this .downUrl = downUrl;
this .saveFile = saveFile;
this .block = block;
this .startPos = startPos;
this .downloader = downloader;
this .threadId = threadId;
this .downLength = startPos - (block * (threadId - 1 ));
}
@Override
public void run() {
if (downLength < block){ // 未下载完成
try {
HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();
http.setRequestMethod( " GET " );
http.setRequestProperty( " Accept " , " image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* " );
http.setRequestProperty( " Accept-Language " , " zh-CN " );
http.setRequestProperty( " Referer " , downUrl.toString());
http.setRequestProperty( " Charset " , " UTF-8 " );
http.setRequestProperty( " Range " , " bytes= " + this .startPos + " - " );
http.setRequestProperty( " User-Agent " , " Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729) " );
http.setRequestProperty( " Connection " , " Keep-Alive " );
InputStream inStream = http.getInputStream();
int max = 1024 * 1024 ;
byte [] buffer = new byte [max];
int offset = 0 ;
print( " 线程 " + this .threadId + " 从位置 " + this .startPos + " 开始下载 " );
while (downLength < block && (offset = inStream.read(buffer, 0 , max)) != - 1 ) {
saveFile.write(buffer, 0 , offset);
downLength += offset;
downloader.update( this .threadId, block * (threadId - 1 ) + downLength);
downloader.saveLogFile();
downloader.append(offset);
int spare = block - downLength; // 求剩下的字节数
if (spare < max) max = ( int ) spare;
}
saveFile.close();
inStream.close();
print( " 线程 " + this .threadId + " 完成下载 " );
this .finish = true ;
this .interrupt();
} catch (Exception e) {
this .downLength = - 1 ;
print( " 线程 " + this .threadId + " : " + e);
}
}
}
private static void print(String msg){
Log.i(TAG, msg);
}
/**
* 下载是否完成
* @return
*/
public boolean isFinish() {
return finish;
}
/**
* 已经下载的内容大小
* @return 如果返回值为-1,代表下载失败
*/
public long getDownLength() {
return downLength;
}
}
3) 下面为监听器接口,会实时显示下载的大小,在实际使用的时候建议采用匿名类的方式构建此接口:
public interface DownloadProgressListener {
public void onDownloadSize( int size);
}
上面的三个文件在一起就构建起了一个迷你型的Android下载框架,这个下载框架可以用于下载任何类型的二进制文件,以后需要下载的时候直接使用即可。其中IoC非常直接的体现就是DownloadProgressListener ,在使用的时候只需要只需要传入该接口一个实现实例即可自动的获取实时的下载长度。
更多相关文章
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- Python技巧匿名函数、回调函数和高阶函数
- python list.sort()根据多个关键字排序的方法实现
- 在Android中使用Timer,并创建一个应用程序
- Android消息机制入门一
- android 超简单的下载功能,进度条 异步下载
- Android(安卓)SDK源码提取Python脚本(4.0SDK源码下载地址)
- android 环境变量搭建
- android 中管理短信