android多线程下载技术详解
黑马程序员安卓教程:Android下的多线程下载器

Android下的多线程下载器在文档“javase下的多线程断点下载”中,我们已经了解了多线程断点下载的核心逻辑和代码,下面我们将在Android中实现多线程断点下载,其实质就是将javase多线程断点下载的核心代码移植到Android工程下。
具体步骤如下:1.新建工程【File】→【new】→【Android Application Project】命名为:06_android多线程下载器。
2.编写布局根据设计需求,编写布局文件,效果如图1-1所示:图1-1所示图1-1对应的xml布局文件代码如例1-1:例1-1例1-1在布局文件中定义了关键控件的id,已经点击按钮的响应事件。
当用户点击按钮时,则读取请求路径和请求线程数并向服务端发送请求,多线程下载指定路径下的资源到客户端。
3.查找控件在Activity中查找相关控件,代码如例1-2:4.编写点击事件编写按钮的点击事件download,从服务端请求下载数据。
代码如例1-3所示:例1-3是点击事件的响应事件,当点击按钮时先要读取请求路径输入框和请求线程输入框的值,并判断其值是否为空。
若请求路径为空则弹出toast提示;若请求线程为空则使用默认的线程数3,不为空则需要将该请求线程值转化为int类型的数值。
在判断之后便是请求网络资源的方法,即例1-3处省略部分。
由于请求网络资源是一个耗时操作,因此我们应该新建一个子线程,在该子线程的run方法中添加断点下载资源的方法。
如例1-4所示:例1-4开辟多个线程开始下载网络资源,每一个线程下载指定区间的一部分数据,关于下载方法DownloadThread()我们在javase多线程断点下载中已经详细的阐述过,在此不再讲述。
需要指出的是:在javase多线程断点下载中,我们是在工程目录下新建文件用来保存当前线程已经下载的位置,由于Android系统的文件目录是只读的,不可修改。
所以我们在保存记录当前线程的下载位置时需要指定到具体的目录下,如指定到SD卡中,代码如例1-5:例1-5保存当前线程的下载位置到SD卡中,那么在读该文件数据时也需要在该SD卡指定的路径下载读取,如例1-6:运行程序,logcat打印信息如图1-2所示:图1-2所示打开DDMS的文件浏览视图,观察下载的exe文件,如图1-3所示:图1-3所示。
Android下使用Http协议实现多线程断点续传下载

0.使用多线程下载会提升文件下载的速度,那么多线程下载文件的过程是:(1)首先获得下载文件的长度,然后设置本地文件的长度HttpURLConnection.getContentLength();RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");file.setLength(filesize);//设置本地文件的长度(2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。
例如10M大小,使用3个线程来下载,线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M下载开始位置:线程id*每条线程下载的数据长度 = ?下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?(3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止代码如下:HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");(4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");threadfile.seek(2097152);//从文件的什么位置开始写入数据1.多线程下载的核心代码示例Java代码1.public class MulThreadDownload2.{3. /**4. * 多线程下载5. * @param args6. */7. public static void main(String[] args)8. {9. String path = "/QQWubiSetup.exe";10. try11. {12. new MulThreadDownload().download(path, 3);13. }14. catch (Exception e)15. {16. e.printStackTrace();17. }18. }19. /**20. * 从路径中获取文件名称21. * @param path 下载路径22. * @return23. */24. public static String getFilename(String path)25. {26. return path.substring(stIndexOf('/')+1);27. }28. /**29. * 下载文件30. * @param path 下载路径31. * @param threadsize 线程数32. */33. public void download(String path, int threadsize) throwsException34. {35. URL url = new URL(path);36. HttpURLConnection conn = (HttpURLConnection)url.openConnection();37. conn.setRequestMethod("GET");38. conn.setConnectTimeout(5 * 1000);39. //获取要下载的文件的长度40. int filelength = conn.getContentLength();41. //从路径中获取文件名称42. String filename = getFilename(path);43. File saveFile = new File(filename);44. RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");45. //设置本地文件的长度和下载文件相同46. accessFile.setLength(filelength);47. accessFile.close();48. //计算每条线程下载的数据长度49. int block = filelength%threadsize==0? filelength/threadsize : filelength/threadsize+1;50. for(int threadid=0 ; threadid < threadsize ; threadid++){51. new DownloadThread(url, saveFile, block, threadid).start();52. }53. }54.55. private final class DownloadThread extends Thread56. {57. private URL url;58. private File saveFile;59. private int block;//每条线程下载的数据长度60. private int threadid;//线程id61. public DownloadThread(URL url, File saveFile, int block, int threadid)62. {63. this.url = url;64. this.saveFile = saveFile;65. this.block = block;66. this.threadid = threadid;67. }68.@Override69. public void run()70. {71. //计算开始位置公式:线程id*每条线程下载的数据长度= ?72. //计算结束位置公式:(线程id +1)*每条线程下载的数据长度-1 =?73. int startposition = threadid * block;74. int endposition = (threadid + 1 ) * block - 1;75. try76. {77. RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");78. //设置从什么位置开始写入数据79. accessFile.seek(startposition);80. HttpURLConnection conn = (HttpURLConnection)url.openConnection();81. conn.setRequestMethod("GET");82. conn.setConnectTimeout(5 * 1000);83. conn.setRequestProperty("Range", "bytes="+ startposition+ "-"+ endposition);84. InputStream inStream = conn.getInputStream();85. byte[] buffer = new byte[1024];86. int len = 0;87. while( (len=inStream.read(buffer)) != -1 )88. {89. accessFile.write(buffer, 0, len);90. }91. inStream.close();92. accessFile.close();93. System.out.println("线程id:"+ threadid+ "下载完成");94. }95. catch (Exception e)96. {97. e.printStackTrace();98. }99. }100. }101.}2.多线程断点下载功能,这里把断点数据保存到数据库中:注意代码注释的理解(0)主Activity,关键点使用Handler更新进度条与开启线程下载避免ANR若不使用Handler却要立即更新进度条数据,可使用://resultView.invalidate(); UI线程中立即更新进度条方法//resultView.postInvalidate(); 非UI线程中立即更新进度条方法Java代码1./**2. * 注意这里的设计思路,UI线程与参数保存问题,避免ANR问题,UI控件的显示3. * @author kay4. *5. */6.public class DownloadActivity extends Activity7.{8. private EditText downloadpathText;9. private TextView resultView;10. private ProgressBar progressBar;11.12.@Override13. public void onCreate(Bundle savedInstanceState)14. {15. super.onCreate(savedInstanceState);16. setContentView(yout.main);17.18. downloadpathText = (EditText) this.findViewById(R.id.downloadpath);19. progressBar = (ProgressBar) this.findViewById(R.id.downloadbar);20. resultView = (TextView) this.findViewById(R.id.result);21. Button button = (Button) this.findViewById(R.id.button);22. button.setOnClickListener(new View.OnClickListener()23. {24.@Override25. public void onClick(View v)26. {27. //取得下载路径28. String path = downloadpathText.getText().toString();29. //判断SDCard是否存在30. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))31. {32. //下载操作33. download(path, Environment.getExternalStorageDirectory());34. }35. else36. {37. Toast.makeText(DownloadActivity.this, R.string.sdcarderror, 1).show();38. }39. }40. });41. }42. //主线程(UI线程)43. //业务逻辑正确,但是该程序运行的时候有问题(可能出现ANR问题)44. //对于显示控件的界面更新只是由UI线程负责,如果是在非UI线程更新控件的属性值,更新后的显示界面不会反映到屏幕上45. /**46. * 参数类型:因为启动一个线程还需要使用到上面方法的参数,而主方法启动后很快就会销毁,47. * 那么使用Final可以解决参数丢失的问题48. * path 注意是Final类型49. * savedir 注意是Final类型50. */51. private void download(final String path, final File savedir)52. {53. //这里开启一个线程避免ANR错误54. new Thread(new Runnable()55. {56.@Override57. public void run()58. {59. FileDownloader loader = new FileDownloader(DownloadActivity.this, path, savedir, 3);60. //设置进度条的最大刻度为文件的长度61. progressBar.setMax(loader.getFileSize());62. try63. {64. loader.download(new DownloadProgressListener()65. {66. /**67. * 注意这里的设计,显示进度条数据需要使用Handler来处理68. * 因为非UI线程更新后的数据不能被刷新69. */70.@Override71. public void onDownloadSize(int size)72. {73. //实时获知文件已经下载的数据长度74. Message msg = new Message();75. //设置消息标签76. msg.what = 1;77. msg.getData().putInt("size", size);78. //使用Handler对象发送消息79. handler.sendMessage(msg);80. }81. });82. }83. catch (Exception e)84. {85. //发送一个空消息到消息队列86. handler.obtainMessage(-1).sendToTarget();87. /**88. * 或者使用下面的方法发送一个空消息89. * Message msg = new Message();90. * msg.what = 1;91. * handler.sendMessage(msg);92. */93. }94. }95. }).start();96. }97.98.99. /**Handler原理:当Handler被创建时会关联到创建它的当前线程的消息队列,该类用于往消息队列发送消息100. *101. * 消息队列中的消息由当前线程内部进行处理102. */103. private Handler handler = new Handler()104. {105. //重写Handler里面的handleMessage方法处理消息106.@Override107. public void handleMessage(Message msg)108. {109. switch (msg.what)110. {111. case 1:112. //进度条显示113. progressBar.setProgress(msg.getData().getInt("size"));114. float num = (float)progressBar.getProg ress()/(float)progressBar.getMax();115. int result = (int)(num*100);116. //resultView.invalidate(); UI线程中立即更新进度条方法117. //resultView.postInvalidate(); 非UI线程中立即更新进度条方法118. resultView.setText(result+ "%"); 119. //判断是否下载成功120. if(progressBar.getProgress()==progress Bar.getMax())121. {122. Toast.makeText(DownloadActivity.th is, R.string.success, 1).show();123. }124. break;125. case -1:126. Toast.makeText(DownloadActivity.this, R.string.error, 1).show();127. break;128. }129. }130. };131.132.}(1)下载类:注意计算每条线程的下载长度与下载起始位置的方法Java代码1.public class DownloadThread extends Thread2.{3. private static final String TAG = "DownloadThread";4. private File saveFile;5. private URL downUrl;6. private int block;7. //下载开始位置8. private int threadId = -1;9. //下载文件长度10. private int downLength;11. private boolean finish = false;12. private FileDownloader downloader;13. public DownloadThread(FileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId)14. {15. this.downUrl = downUrl;16. this.saveFile = saveFile;17. this.block = block;18. this.downloader = downloader;19. this.threadId = threadId;20. this.downLength = downLength;21. }22.23.@Override24. public void run()25. {26. //未下载完成27. if(downLength < block)28. {29. try30. {31. HttpURLConnection http = (HttpURLConnection)downUrl.openConnection();32. http.setConnectTimeout(5 * 1000);33. http.setRequestMethod("GET");34. 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, applicati on/vnd.ms-excel, application/vnd.ms-powerpoint, application/ms word, */*");35. http.setRequestProperty("Accept-Language", "zh-CN");36. http.setRequestProperty("Referer", downUrl.toString());37. http.setRequestProperty("Charset", "UTF-8");38. //下载开始位置:线程id*每条线程下载的数据长度 = ?39. int startPos = block * (threadId - 1) + downLength;40. //下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?41. int endPos = block * threadId -1;42. //设置获取实体数据的范围43. http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);44. 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)");45. http.setRequestProperty("Connection", "Keep-Alive");46.47. InputStream inStream = http.getInputStream();48. byte[] buffer = new byte[1024];49. int offset = 0;50. print("Thread " + this.threadId + " start download from position "+ startPos);51. RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");52. threadfile.seek(startPos);53. while ((offset = inStream.read(buffer, 0, 1024)) != -1)54. {55. threadfile.write(buffer, 0, offset);56. downLength += offset;57. downloader.update(this.threadId, downLength);58. downloader.append(offset);59. }60. threadfile.close();61. inStream.close();62. print("Thread " + this.threadId + " downloadfinish");63. //标记是否完成64. this.finish = true;65. }66. catch (Exception e)67. {68. this.downLength = -1;69. print("Thread "+ this.threadId+ ":"+ e);70. }71. }72. }73.74. private static void print(String msg)75. {76. Log.i(TAG, msg);77. }78.79. /**80. * 下载是否完成81. * @return82. */83. public boolean isFinish()84. {85. return finish;86. }87.88. /**89. * 已经下载的内容大小90. * @return 如果返回值为-1,代表下载失败91. */92. public long getDownLength()93. {94. return downLength;95. }96.}文件下载器,使用Java代码1./**2. * 文件下载器,使用这个类的方法如下示例:3. * FileDownloader loader = new FileDownloader(context, "http:///ejb3/ActivePort.exe",4. * new File("D:\\androidsoft\\test"), 2);5. * loader.getFileSize();//得到文件总大小6. * try {7. * loader.download(new DownloadProgressListener(){8. * public void onDownloadSize(int size) {9. * print("已经下载:"+ size);10. * }11. * });12. * }13. * catch (Exception e)14. * {15. * e.printStackTrace();16. * }17. */18.public class FileDownloader19.{20. private static final String TAG = "FileDownloader";21. private Context context;22. private FileService fileService;23. //已下载文件长度24. private int downloadSize = 0;25. //原始文件长度26. private int fileSize = 0;27. ///线程数28. private DownloadThread[] threads;29. //本地保存文件30. private File saveFile;31. //缓存各线程下载的长度32. private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();33. //每条线程下载的长度34. private int block;35. //下载路径36. private String downloadUrl;37. //获取线程数38. public int getThreadSize()39. {40. return threads.length;41. }42. /**43. * 获取文件大小44. * @return45. */46. public int getFileSize()47. {48. return fileSize;49. }50. /**51. * 累计已下载大小52. * @param size53. */54. protected synchronized void append(int size)55. {56. downloadSize += size;57. }58. /**59. * 更新指定线程最后下载的位置60. * @param threadId 线程id61. * @param pos 最后下载的位置62. */63. protected synchronized void update(int threadId, int pos)64. {65. this.data.put(threadId, pos);66. this.fileService.update(this.downloadUrl, this.data);67. }68. /**69. * 文件下载构造器70. * @param downloadUrl 下载路径71. * @param fileSaveDir 文件保存目录72. * @param threadNum 下载线程数73. */74. public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum)75. {76. try77. {78. this.context = context;79. this.downloadUrl = downloadUrl;80. fileService = new FileService(this.context);81. URL url = new URL(this.downloadUrl);82. if(!fileSaveDir.exists()) fileSaveDir.mkdirs();83. this.threads = new DownloadThread[threadNum];84. HttpURLConnection conn = (HttpURLConnection) url.openConnection();85. conn.setConnectTimeout(5*1000);86. conn.setRequestMethod("GET");87. conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-fla sh, application/xaml+xml, application/vnd.ms-xpsdocument, appl ication/x-ms-xbap, application/x-ms-application, application/v nd.ms-excel, application/vnd.ms-powerpoint, application/msword , */*");88. conn.setRequestProperty("Accept-Language", "zh-CN");89. conn.setRequestProperty("Referer", downloadUrl);90. conn.setRequestProperty("Charset", "UTF-8");91. conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR1.1.4322; .NET CLR2.0.50727; .NET CLR3.0.04506.30; .NET CLR3.0.4506.2152; .NET CLR 3.5.30729)");92. conn.setRequestProperty("Connection", "Keep-Alive");93. conn.connect();94. printResponseHeader(conn);95. if (conn.getResponseCode()==200)96. {97. //根据响应获取文件大小98. this.fileSize = conn.getContentLength();99. if (this.fileSize <= 0) throw new RuntimeException("Unkown file size ");100. //获取文件名称101. String filename = getFileName(conn); 102. //构建保存文件103. this.saveFile = new File(fileSaveDir, file name);104. //获取下载记录105. Map<Integer, Integer> logdata = fileService.getData(downloadUrl);106. //如果存在下载记录107. if(logdata.size()>0)108. {109. //把各条线程已经下载的数据长度放入data 中110. for(Map.Entry<Integer, Integer> entry : logdata.entrySet())111. data.put(entry.getKey(), entry.get Value());112. }113. //下面计算所有线程已经下载的数据长度114. if(this.data.size()==this.threads.length)115. {116. for (int i = 0; i < this.threads.lengt h; i++)117. {118. this.downloadSize += this.data.get (i+1);119. }120. print("已经下载的长度"+ this.downloadSize);121. }122. //计算每条线程下载的数据长度123. this.block = (this.fileSize % this.threads .length)==0? this.fileSize / this.threads.length : this.fileSi ze / this.threads.length + 1;124. }125. else126. {127. throw new RuntimeException("server no resp onse ");128. }129. }130. catch (Exception e)131. {132. print(e.toString());133. throw new RuntimeException("don't connection t his url");134. }135. }136. /**137. * 获取文件名138. */139. private String getFileName(HttpURLConnection conn) 140. {141. String filename = this.downloadUrl.substring(this.stIndexOf('/') + 1);142. if(filename==null || "".equals(filename.trim())){/ /如果获取不到文件名称143. for (int i = 0;; i++) {144. String mine = conn.getHeaderField(i); 145. if (mine == null) break;146. if("content-disposition".equals(conn.getHe aderFieldKey(i).toLowerCase())){147. Matcher m = pile(".*filenam e=(.*)").matcher(mine.toLowerCase());148. if(m.find()) return m.group(1); 149. }150. }151. filename = UUID.randomUUID()+ ".tmp";//默认取一个文件名152. }153. return filename;154. }155.156. /**157. * 开始下载文件158. * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null159. * @return 已下载文件大小160. * @throws Exception161. */162. public int download(DownloadProgressListener listener) throws Exception{163. try164. {165. //创建本地文件166. RandomAccessFile randOut = new RandomAccessFil e(this.saveFile, "rw");167. if(this.fileSize>0) randOut.setLength(this.fil eSize);168. randOut.close();169. URL url = new URL(this.downloadUrl);170. if(this.data.size() != this.threads.length) 171. {172. this.data.clear();173. for (int i = 0; i < this.threads.length; i ++)174. {175. //初始化每条线程已经下载的数据长度为176. this.data.put(i+1, 0);177. }178. }179. //开启线程进行下载180. for (int i = 0; i < this.threads.length; i++)181. {182. int downLength = this.data.get(i+1); 183. //判断线程是否已经完成下载,否则继续下载184. if(downLength < this.block && this.downloa dSize<this.fileSize)185. {186. this.threads[i] = new DownloadThread(t his, url, this.saveFile, this.block, this.data.get(i+1), i+1);187. this.threads[i].setPriority(7);//可删除这条188. this.threads[i].start();189. }190. else191. {192. this.threads[i] = null;193. }194. }195. this.fileService.save(this.downloadUrl, this.d ata);196. //下载未完成197. boolean notFinish = true;198. // 循环判断所有线程是否完成下载199. while (notFinish)200. {201. Thread.sleep(900);202. //假定全部线程下载完成203. notFinish = false;204. for (int i = 0; i < this.threads.length; i ++)205. {206. //如果发现线程未完成下载207. if (this.threads[i] != null && !this.t hreads[i].isFinish())208. {209. //设置标志为下载没有完成210. notFinish = true;211. //如果下载失败,再重新下载212. if(this.threads[i].getDownLength() == -1)213. {214. this.threads[i] = new Download Thread(this, url, this.saveFile, this.block, this.data.get(i+1 ), i+1);215. this.threads[i].setPriority(7) ;216. this.threads[i].start(); 217. }218. }219. }220. //通知目前已经下载完成的数据长度221. if(listener!=null) listener.onDownloadSize (this.downloadSize);222. }223. //删除数据库中下载信息224. fileService.delete(this.downloadUrl);225. }226. catch (Exception e)227. {228. print(e.toString());229. throw new Exception("file download fail"); 230. }231. return this.downloadSize;232. }233.234. /**235. * 获取Http响应头字段236. * @param http237. * @return238. */239. public static Map<String, String> getHttpResponseHeade r(HttpURLConnection http) {240. Map<String, String> header = new LinkedHashMap<Str ing, String>();241. for (int i = 0;; i++) {242. String mine = http.getHeaderField(i); 243. if (mine == null) break;244. header.put(http.getHeaderFieldKey(i), mine);245. }246. return header;247. }248. /**249. * 打印Http头字段250. * @param http251. */252. public static void printResponseHeader(HttpURLConnecti on http)253. {254. Map<String, String> header = getHttpResponseHeader (http);255. for(Map.Entry<String, String> entry : header.entry Set())256. {257. String key = entry.getKey()!=null ? entry.getK ey()+ ":" : "";258. print(key+ entry.getValue());259. }260. }261. private static void print(String msg)262. {263. Log.i(TAG, msg);264. }265.}Java代码1.public interface DownloadProgressListener2.{3. public void onDownloadSize(int size);4.}(2)文件操作,断点数据库存储Java代码1.public class DBOpenHelper extends SQLiteOpenHelper2.{3. private static final String DBNAME = "itcast.db";4. private static final int VERSION = 1;5.6. public DBOpenHelper(Context context)7. {8. super(context, DBNAME, null, VERSION);9. }10.11.@Override12. public void onCreate(SQLiteDatabase db)13. {14. db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");15. }16.@Override17. public void onUpgrade(SQLiteDatabase db, int oldVersion,int newVersion)18. {19. db.execSQL("DROP TABLE IF EXISTS filedownlog");20. onCreate(db);21. }22.}Java代码1./**2. * 文件下载业务bean3. */4.public class FileService5.{6. private DBOpenHelper openHelper;7. public FileService(Context context)8. {9. openHelper = new DBOpenHelper(context);10. }11. /**12. * 获取每条线程已经下载的文件长度13. * @param path14. * @return15. */16. public Map<Integer, Integer> getData(String path)17. {18. SQLiteDatabase db = openHelper.getReadableDatabase();19. Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path});20. Map<Integer, Integer> data = new HashMap<Integer, Integer>();21. while(cursor.moveToNext())22. {23. data.put(cursor.getInt(0), cursor.getInt(1));24. }25. cursor.close();26. db.close();27. return data;28. }29. /**30. * 保存每条线程已经下载的文件长度31. * @param path32. * @param map33. */34. public void save(String path, Map<Integer, Integer> map)35. {//int threadid, int position36. SQLiteDatabase db = openHelper.getWritableDatabase();37. db.beginTransaction();38. try39. {40. for(Map.Entry<Integer, Integer> entry : map.entrySet())41. {42. db.execSQL("insert into filedownlog(downpath,threadid, downlength) values(?,?,?)",43. new Object[]{path, entry.getKey(), entry.getValue()});44. }45. db.setTransactionSuccessful();46. }47. finally48. {49. db.endTransaction();50. }51. db.close();52. }53. /**54. * 实时更新每条线程已经下载的文件长度55. * @param path56. * @param map57. */58. public void update(String path, Map<Integer, Integer> map)59. {60. SQLiteDatabase db = openHelper.getWritableDatabase();61. db.beginTransaction();62. try{63. for(Map.Entry<Integer, Integer> entry : map.entrySet()){64. db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?",65. new Object[]{entry.getValue(), path,entry.getKey()});66. }67. db.setTransactionSuccessful();68. }finally{69. db.endTransaction();70. }71. db.close();72. }73. /**74. * 当文件下载完成后,删除对应的下载记录75. * @param path76. */77. public void delete(String path)78. {79. SQLiteDatabase db = openHelper.getWritableDatabase();80. db.execSQL("delete from filedownlog where downpath=?", new Object[]{path});81. db.close();82. }83.}。
黑马程序员安卓教程:Javase下实现多线程下载编码

Javase下实现多线程下载编码多线程下载各个步骤的逻辑我们都已经讲述过,下面我们通过一个案例来实现多线程下载的具体实现。
由于android对标准的javase完全支持,我们可以先创建一个java项目,在该项目中实现多线程下载的功能,这样做也方便了测试。
1.部署服务端运行tomcat服务器,在tomcat安装路径的ROOT目录下放置下载资源(参考:当前计算机的D:\soft\tomcat\apache-tomcat-7.0.52\webapps\ROOT),如图1-1所示:图1-1所示从图1-1可知该下载资源是QQ6.6.exe。
启动tomcat,在浏览器中访问该下载资源(路径为:http://localhost:8080/QQ6.6.exe),效果如图1-2所示:图1-2所示从图1-2可知该下载资源已经完成部署。
2.编写客户端在eclipse中编写一个Java工程,实现多线程从tomcat下载.exe文件。
(1)创建工程【File】→【new】→【Java Project】命名为:多线程下载(2)使用默认包名,创建一个类MultiDownload,工程目录结构如图1-3所示:图1-3所示(3)在多线程下载类(即MultiDownload.class)中,编写下载方法,具体步骤如下:●等分服务端资源首先,发送http请求,得到服务端的返回数据,该返回数据中包含了多个响应报文头,调用getContentLength方法获取返回数据体的长度。
代码如例1-1:运行程序,控制台打印如图1-4所示信息:图1-4所示从图1-4可知:我们已经获取了下载文件的大小,即58694840字节。
对比tomcat中ROOT下的资源文件可以发现控制台打印出的文件大小和该文件的实际大小一致。
其次,假设将该下载资源等分为3份,每一份对应一个线程来下载,则每一个线程下载的数据长度如图1-5所示:每一个线程下载的长度:58694840/3=19564946.66666对应公式:总数/线程个数(threadcount)=每个区块大小(blocksize)第0个线程下载区段第1个线程下载区段第2个线程下载区段0*blocksize~1*blocksize-11*blocksize~2*blocksize-12*blocksize~3*blocksize-1第n个线程下载区段n*blocksize~(n+1)*blocksize-1最后一个线程m下载区段m*blocksize~文件总长度-1图1-5所示根据图1-5所示的计算公式,我们可以在程序中处理每一个线程下载的数据长度,具体代码如例1-2:运行程序,控制台打印如图1-6信息:图1-6所示创建与下载资源等大的空文创建一个RandomAccessFile的对象,调用该对象的setLength方法指定该文件的长度,具体代码如例1-3:运行程序,在工程目录下可以看到新建的空文件,如图1-7所示:图1-7所示此时,浏览该temp.exe文件,效果如图1-8所示:图1-8所示从图1-8得知:此时该文件没有任何数据,都是空字符。
Android实现多线程断点下载

Android实现多线程断点下载⽬录QDownload1、如何使⽤1.1、导⼊依赖1.2、初始化下载组件1.3、核⼼控制器DownloadManager1.4、监听下载进度1.5、下载相关的操作1.6、应⽤市场apk下载的⼀个场景总结QDownloadQDownload是基于Android平台实现的下载框架。
API简洁易上⼿,只需5分钟即可实现⼀个多任务、多线程、断点下载的功能⽀持功能如下:⽀持多个下载任务同时下载单个任务⽀持开多个线程下载⽀持断点下载,在断⽹、进程被划掉可恢复下载⾃动校验服务器⽂件服务器是否⽀持断点下载,如果不⽀持则会开启单线程任务下载⽀持应⽤全局监听下载进度回调⽀持下载速度显⽰⽀持添加下载任务,暂停下载,恢复下载,取消下载⽀持批量暂停,批量恢复下载先贴个效果图主页多任务多线程断点下载页⾯1、如何使⽤1.1、导⼊依赖implementation 'com.qw.download:download:1.0.0-alpha01' 1.2、初始化下载组件public class MyApplication extends Application{@Overridepublic void onCreate() {super.onCreate();//初始化下载组件(可在⼦线程中做)DownloadManager.init(this);}}1.3、核⼼控制器DownloadManager api如下public class DownloadManager {private static DownloadManager mInstance;private final Context context;private DownloadManager(Context context) {}//初始化组件public static void init(Context context) {}//开启下载public static void add(DownloadEntry entry) {}//暂停下载public static void pause(DownloadEntry entry) {}//暂停所有任务public static void pauseAll() {}//恢复下载public static void resume(DownloadEntry entry) {}//恢复所有任务public static void recoverAll() {}}1.4、监听下载进度需要监听下载进度可通过注册DownloadWatcher来监听下载信息的变化private DownloadWatcher watcher = new DownloadWatcher() {@Overrideprotected void onDataChanged(DownloadEntiry entry) {//这⾥监听下载的实时信息mDownloadInfoLabel.setText(entry.toString());Log.e("MainActivity", entry.toString());}};@Overrideprotected void onResume() {super.onResume();//注册观察者DownloadManager.addObserver(watcher);}@Overrideprotected void onPause() {super.onPause();//移除观察者DownloadManager.removeObserver(watcher);}其中第3⾏DownloadEntiry entry存储了下载相关信息public class DownloadEntry implements Serializable {public String id;//下载的唯⼀标识public String url;//下载⽂件的url地址public boolean isSupportRange;//是否⽀持断点续传public long contentLength;//⽂件长度public long currentLength;//已下载⽂件长度public State state;//任务状态public HashMap<Integer, Long> ranges;//存储每个线程下载开始区块public int speed;//下载速度单位s}其中state有如下⼏个值public enum State {IDLE,//空闲CONNECT,//连接中(⽤户预取⽂件信息)ING,//下载中PAUSED,//已暂停CANCELLED,//已取消ERROR,//错误DONE,//完成WAIT//等待}1.5、下载相关的操作添加⼀个下载通过DownloadEntry构建下载实体public void addDownload(String id,String url){//id为任务的⼀个唯⼀标识,url为⽂件地址DownloadEntry entry = DownloadEntry.obtain(id,url)//添加⼀个下载任务DownloadManager.add(entry)}暂停下载可通过下载任务id 然后调⽤DownloadManager.findById(id)函数获取DownloadEntry public void pauseDownload(DownloadEntry entry){//添加⼀个下载任务DownloadManager.pause(entry)}恢复下载public void resumeDownload(DownloadEntry entry){//添加⼀个下载任务DownloadManager.resume(entry)}暂停所有下载任务public void pauseAll(DownloadEntry entry){//添加⼀个下载任务DownloadManager.resume(entry)}恢复所有下载任务public void recoverAll(DownloadEntry entry){//添加⼀个下载任务DownloadManager.recoverAll(entry)}1.6、应⽤市场apk下载的⼀个场景ApkEntry实体数据⽤来描述apk的基本信息public class ApkEntry {public String id;//包idpublic String cover;//apk 图标public String name;//apk 名称public String url;//apk 下载地址public long length;//apk ⼤⼩public String id() {if (TextUtils.isEmpty(id)) {//如果服务端没有返回唯⼀标记则⽤url的md5值作为下载唯⼀标识id = FileUtilities.getMd5FileName(url);}return id;}}这是我要下载这个apk就可以这么做public void downloadApk(ApkEntry apkEntry){//1、先check当前apk是否在下载DownloadEntry entry = DownloadManager.findById(apkEntry.id())if(entry==null || entry.state==State.PAUSED || entry.state==State.ERROR || entry.state==State.CANCELLED){//这四种情况:没有下载任务 || 任务是暂停 || 下载失败 || 任务已被取消DownloadManager.add(entry)}}暂停apk下载public void pauseDownloadApk(ApkEntry apkEntry){//1、先check当前apk是否在下载任务中DownloadEntry entry = DownloadManager.findById(apkEntry.id())if(entry!=null){DownloadManager.pause(entry)}}总结到此这篇关于Android实现多线程断点下载的⽂章就介绍到这了,更多相关Android多线程断点下载内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!。
Android应用开发中的多线程编程技术详解

Android应用开发中的多线程编程技术详解在Android应用中,当我们需要执行一些耗时的操作,例如网络请求、磁盘读写等,如果在主线程中执行,会导致UI界面卡顿,用户体验下降。
而解决这个问题的办法就是使用多线程编程技术。
本文将详细介绍Android应用开发中的多线程编程技术。
1. 线程的基础知识在Java中,线程就是一个独立的执行单元,它可以独立运行,也可以和其它线程一起协作完成任务。
而在Android中,我们常用的线程有两种:主线程和子线程。
主线程是Android应用的UI线程,负责响应用户的交互事件,执行UI更新操作;而子线程则是我们在应用中创建的线程,用于执行耗时的操作。
在Java中,有两种方式来创建线程:继承Thread类和实现Runnable接口。
其中,实现Runnable接口是更加常用的做法,因为它能够避免单继承的限制。
下面是一个简单的示例代码,实现了一个通过实现Runnable接口创建子线程的例子:```public class MyThread implements Runnable {@Overridepublic void run() {// 子线程要执行的任务}}// 在主线程中创建子线程MyThread myThread = new MyThread();Thread thread = new Thread(myThread);thread.start();```当我们创建了一个子线程后,它就会独立地运行在一个新的线程中。
在子线程中,我们可以执行一些耗时的操作,并且不会卡住主线程。
2. Android中的主线程在Android应用中,主线程是非常重要的,它负责维护UI界面的显示和交互,处理用户的输入事件,并更新UI的状态。
因此,在主线程中执行一些耗时的操作,就会使得UI界面出现卡顿现象,用户体验大大降低。
为了避免这种情况,Android系统引入了一种机制,叫做ANR (Application Not Responding)。
黑马程序员安卓教程:Android多线程下载器UI实现

//在本地SD卡上创建文件raf用来保存下载的数据,同时设置每一个子线程写入文件raf数据的开始位置raf.seek(startindex)
..........
inttotal = 0;//代表当前线程下载的总大小
//while循环将返回的数据流写入到本地文件中
while(( len = is.read(buffer))!=-1){
raf.write(buffer, 0, len);
total+=len;
intcurrentposition = startindex+total;
//当前线程下载的位置保存到其对应的临时文件中
.......
//while循环中不断的更新了进度条
intcurrentsize = lastDownloadsize+total;//当前进度
conn.setRequestProperty("Range","bytes="+startindex+"-"+endindex);
}
intcode = conn.getResponseCode();
//3.根据返回码,判断数据请求是否成功,并读取服务端返回数据流
if(code == 206){
//读取服务端返回的数据流conn.getInputStream()
pbs.get(threadid).setMax(totalblocksize);
pbs.get(threadid).setProgress(currentsize);
}
is.close();
raf.close();
System.out.println("线程:"+threadid+"下载完毕了。");
Android 多线程详解

Thread类常用方法(1)public static Thread currentThread():返回当前调用的线程(2)public final String getName():返回当前线程名称(3)public long getId():返回当前线程标识id(4)public final void setName(String threadName):设置线程名称(5)public void start():执行一个新的线程(6)public final void stop():关闭一个线程,不推荐使用因为该方法可能会产生死锁,推荐使用在Thread中设置标识符结束运行的代码(7)public static void sleep(long time):等待一段时间后在运行线程,单位是毫秒进程概念在同一时间内执行多个程序的操作系统都有进程的概念。
一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。
在进程的概念中,每一个进程的内部数据和状态都是完全独立的。
在Windows操作系统下我们可以通过〈Ctrl+Alt+Del〉组合键查看进程,在UNIX和Linux操作系统下是通过PS命令查看进程的。
打开Windows 当前运行的进程,如图在Windows操作系统中一个进程就是一个exe或dll程序,它们相互独立,互相也可以通信,在Android操作系统中进程间的通信应用也是很多Android空进程线程概念多线程指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。
多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。
线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制。
但与进程不同的是,同类的多个线程共享一块内存空间和一组系统资源,所以系统在各个线程之间切换时,资源占用要比进程小得多,正因如此,线程也被称为轻量级进程。
一个进程中可以包含多个线程。
Android 多线程下载图片

很多时候我们需要在Android设备上下载远程服务器上的图片进行显示,今天eoeAndroid整理出两种比较好的方法来实现远程图片的下载。
方法一、直接通过Android提供的Http类访问远程服务器,这里AndroidHttpClient是SDK 2.2中新出的方法,API Level为8,大家需要注意下,静态访问可以直接调用,如果SDK版本较低可以考虑Apache的Http库,当然HttpURLConnection 或URLConnection也可以。
Java代码:1.static Bitmap downloadBitmapByCwj(String url) {2.final AndroidHttpClient client =AndroidHttpClient.newInstance("Android123");3.final HttpGet getRequest = new HttpGet(url);4.5.try {6.HttpResponse response = client.execute(getRequest);7.final int statusCode = response.getStatusLine().getStatusCode();8.if (statusCode != HttpStatus.SC_OK) {9.Log.e("cwjDebug", "Error " + statusCode + " while retrieving bitmapfrom " + url);10.return null;11.}12.13.final HttpEntity entity = response.getEntity();14.if (entity != null) {15.InputStream inputStream = null;16.try {17.inputStream = entity.getContent();18.final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);19.return bitmap;20.} finally {21.if (inputStream != null) {22.inputStream.close();23.}24.entity.consumeContent();25.}26.}27.} catch (Exception e) {28.getRequest.abort();29.Log.e("android123Debug", "Error while retrieving bitmap from " +url, e.toString());30.} finally {31.if (client != null) {32.client.close();33.}34.}35.return null;36.}复制代码这里eoeAndroid提醒大家,BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据,主要原理就是检查是否到文件末端,告诉http类是否继续。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Android 多线程、断点续传下载技术1.为什么使用该技术?答:(1)之所以采用多线程下载是因为考虑到手机,及移动设备的cup处理能力,让下载任务多抢占cup资源,从而加快了下载的速度,提高了用户体验(2)断点续传技术,就是在下载过程中如果网络出现问题,导致文件没有下载完,那么下次下载时,接着上次终端位置继续下载,从而减轻了服务器的负担。
2.下面我们就开始建一个多线程下载的项目,来体验多线程下载的优势,项目的结构如下2.1设计UImain.xml代码如下:<?xml version="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="/apk/res /android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="@string/path"/><EditTextandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="/kcn/pc/K anbox_10012.exe"android:id="@+id/path"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/button"android:id="@+id/button"/><ProgressBarandroid:layout_width="fill_parent"android:layout_height="20px"style="?android:attr/progressBarStyleHorizontal"android:id="@+id/downloadbar"/><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:gravity="center"android:id="@+id/result"/></LinearLayout>其中引用的string.xml如下:<?xml version="1.0"encoding="utf-8"?><resources><string name="hello">Hello World, SmartDownload!</string><string name="app_name">SMART多线程断点下载器</string><string name="path">下载路径</string><string name="button">下载</string><string name="success">下载完成</string><string name="error">下载失败</string><string name="sdcarderror">SDCard不存在或者写保护</string></resources>3.数据库阶段:3.1编写数据库工具类DBOpenHelerpackage com.smart.db;import android.content.Context;import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper;public class DBOpenHelper extends SQLiteOpenHelper {private static final String DBNAME = "smart.db";private static final int VERSION = 1;public DBOpenHelper(Context context) {super(context, DBNAME, null, VERSION);}@Overridepublic voiddb.execSQL("CREATE TABLE IF NOT EXISTS SmartFileDownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS SmartFileDownlog");onCreate(db);}}3.2对各个线程的下载记录进行数据库的操作,编写Fileservice 类代码如下package com.smart.db;import java.util.HashMap;import java.util.Map;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase; /*** 业务bean**/public class FileService {private DBOpenHelper openHelper;public FileService(Context context) { openHelper = new DBOpenHelper(context);}/*** 获取每条线程已经下载的文件长度* @param path* @return*/public Map<Integer, Integer> getData(String path){openHelper.getReadableDatabase();Cursor cursor = db.rawQuery("select threadid, downlength from SmartFileDownlog where downpath=?", new String[]{path});Map<Integer, Integer> data = newHashMap<Integer, Integer>();while(cursor.moveToNext()){data.put(cursor.getInt(0),cursor.getInt(1));}cursor.close();db.close();return data;}/*** 保存每条线程已经下载的文件长度* @param path* @param map*/public void save(String path, Map<Integer, Integer> map){//int threadid, int positionopenHelper.getWritableDatabase();db.beginTransaction();try{for(Map.Entry<Integer, Integer> entry : map.entrySet()){db.execSQL("insert intoSmartFileDownlog(downpath, threadid, downlength) values(?,?,?)",new Object[]{path, entry.getKey(), entry.getValue()});}db.setTransactionSuccessful();}finally{db.endTransaction();}db.close();}/*** 实时更新每条线程已经下载的文件长度* @param path* @param map*/public void update(String path, Map<Integer, Integer> map){SQLiteDatabase db =openHelper.getWritableDatabase();db.beginTransaction();try{for(Map.Entry<Integer, Integer> entry : map.entrySet()){db.execSQL("update SmartFileDownlog set downlength=? where downpath=? and threadid=?",new Object[]{entry.getValue(), path, entry.getKey()});}db.setTransactionSuccessful();}finally{db.endTransaction();}db.close();}/*** 当文件下载完成后,删除对应的下载记录* @param path*/public void delete(String path){SQLiteDatabase db =openHelper.getWritableDatabase();db.execSQL("delete from SmartFileDownlog where downpath=?", new Object[]{path});db.close();}}4.实现文件下载阶段4.1建立SmartFileDownloader类用来实现文件的下载功能代码如下package com.smart.impl;import java.io.File;import java.io.RandomAccessFile;import .HttpURLConnection;import .URL;import java.util.LinkedHashMap;import java.util.Map;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher;import java.util.regex.Pattern;import android.content.Context;import android.util.Log;import com.smart.db.FileService;public class SmartFileDownloader {private static final String TAG = "SmartFileDownloader";private Context context;private FileService fileService;/* 已下载文件长度 */private int downloadSize = 0;/* 原始文件长度 */private int fileSize = 0;/* 线程数 */private SmartDownloadThread[] threads;/* 本地保存文件 */private File saveFile;/* 缓存各线程下载的长度*/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 SmartFileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {try {this.context = context;this.downloadUrl = downloadUrl;fileService = newFileService(this.context);URL url = new URL(this.downloadUrl);if(!fileSaveDir.exists())fileSaveDir.mkdirs();this.threads = newSmartDownloadThread[threadNum];HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5*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 CLR2.0.50727; .NET CLR3.0.04506.30; .NET CLR3.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("Unkown file size ");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());}this.block = (this.fileSize %this.threads.length)==0? this.fileSize /this.threads.length : 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);}print("已经下载的长度"+this.downloadSize);}}else{throw new RuntimeException("server no response ");}} catch (Exception e) {print(e.toString());throw new RuntimeException("don't connection this url");}}/*** 获取文件名*/private String getFileName(HttpURLConnection conn) {String filename =this.downloadUrl.substring(stI ndexOf('/') + 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.getHeader FieldKey(i).toLowerCase())){Matcher m =pile(".*filename=(.*)").matcher(mine.t oLowerCase());if(m.find()) return m.group(1);}}filename = UUID.randomUUID()+ ".tmp";//默认取一个文件名}return filename;}/*** 开始下载文件* @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null* @return已下载文件大小* @throws Exception*/public intdownload(SmartDownloadProgressListener listener) throws Exception{try {RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");if(this.fileSize>0)randOut.setLength(this.fileSize);randOut.close();URL url = new URL(this.downloadUrl);if(this.data.size() !=this.threads.length){this.data.clear();//清除数据for (int i = 0; i < this.threads.length; i++) {this.data.put(i+1, 0);}}for(int i = 0; i < this.threads.length; i++) {int downLength = this.data.get(i+1);if(downLength < this.block &&this.downloadSize<this.fileSize){ //该线程未完成下载时,继续下载this.threads[i] = new SmartDownloadThread(this, url, this.saveFile, 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){//如果下载失败,再重新下载this.threads[i] = new SmartDownloadThread(this, url, this.saveFile, 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("file download fail");}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 voidprintResponseHeader(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);}}4.2下载过程中的线程实现建立SmartDownloadThread类具体代码如下:package com.smart.impl;import java.io.File;import java.io.InputStream;import java.io.RandomAccessFile;import .HttpURLConnection;import .URL;import android.util.Log;public class SmartDownloadThread extends Thread { private static final String TAG = "SmartDownloadThread";private File saveFile;private URL downUrl;private int block;/* *下载开始位置 */private int threadId = -1;private int downLength;private boolean finish = false;private SmartFileDownloader downloader;public SmartDownloadThread(SmartFileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) {this.downUrl = downUrl;this.saveFile = saveFile;this.block = block;this.downloader = downloader;this.threadId = threadId;this.downLength = downLength;}@Overridepublic void run() {if(downLength < block){//未下载完成try {HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();http.setConnectTimeout(5 * 1000);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");int startPos = block * (threadId - 1) + downLength;//开始位置int endPos = block * threadId -1;//结束位置http.setRequestProperty("Range","bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR2.0.50727; .NET CLR3.0.04506.30; .NET CLR3.0.4506.2152; .NET CLR 3.5.30729)");http.setRequestProperty("Connection", "Keep-Alive");InputStream inStream =http.getInputStream();byte[] buffer = new byte[1024];int offset = 0;print("Thread "+ this.threadId+ " start download from position "+ startPos);RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");threadfile.seek(startPos);while((offset = inStream.read(buffer, 0, 1024)) != -1) {threadfile.write(buffer, 0, offset);downLength += offset;downloader.update(this.threadId, downLength);downloader.saveLogFile();downloader.append(offset);}threadfile.close();inStream.close();print("Thread " + this.threadId + " download finish");this.finish = true;} catch (Exception e) {this.downLength = -1;print("Thread "+ 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;}}4.3 建立interface SmartDownloadProgressListener 侦听线程的下载进度代码如下:package com.smart.impl;public interface SmartDownloadProgressListener { public void onDownloadSize(int size);}5.建立activity 实现下载的触发,和界面的实时更新具体代码如下:package com.smart.activoty.download;import java.io.File;import android.app.Activity;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;importcom.smart.impl.SmartDownloadProgressListener; import com.smart.impl.SmartFileDownloader;public class SmartDownloadActivity extends Activity {private ProgressBar downloadbar;private EditText pathText;private TextView resultView;private Handler handler = new Handler(){@Override//信息public void handleMessage(Message msg) { switch (msg.what) {case 1:int size = msg.getData().getInt("size");downloadbar.setProgress(size);float result =(float)downloadbar.getProgress()/(float)downloadbar.getMax();int p = (int)(result*100);resultView.setText(p+"%");if(downloadbar.getProgress()==downloadbar.getM ax())Toast.makeText(SmartDownloadActivity.this, R.string.success, 1).show();break;case -1:Toast.makeText(SmartDownloadActivity.this, R.string.error, 1).show();break;}}};@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(yout.main);Button button =(Button)this.findViewById(R.id.button);downloadbar =(ProgressBar)this.findViewById(R.id.downloadbar); pathText =(EditText)this.findViewById(R.id.path);resultView =(TextView)this.findViewById(R.id.result);button.setOnClickListener(newView.OnClickListener() {@Overridepublic void onClick(View v) {String path =pathText.getText().toString();if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){File dir =Environment.getExternalStorageDirectory();//文件保存目录download(path, dir);}else{Toast.makeText(SmartDownloadActivity.this,R.string.sdcarderror, 1).show();}}});}//对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI 线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错private void download(final String path, final File dir){new Thread(new Runnable() {@Overridepublic void run() {try {SmartFileDownloader loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3);int length = loader.getFileSize();//获取文件的长度downloadbar.setMax(length);loader.download(new SmartDownloadProgressListener(){@Overridepublic void onDownloadSize(int size) {//可以实时得到文件下载的长度Message msg = new Message();msg.what = 1;msg.getData().putInt("size", size);handler.sendMessage(msg);}});} catch (Exception e) {Message msg = new Message();//信息提示msg.what = -1;msg.getData().putString("error", "下载失败");//如果下载错误,显示提示失败!handler.sendMessage(msg);}}}).start();//开始}}6.到此为止运行会报错,因为没有向sdcard的协数据的权限,访问Internet的权限这需要在AndroidManifest.xml配置具体代码如下:<?xml version="1.0"encoding="utf-8"?><manifestxmlns:android="/apk/res /android"package="com.smart.activoty.download"android:versionCode="1"android:versionName="1.0"><application android:icon="@drawable/icon" android:label="@string/app_name"><activityandroid:name=".SmartDownloadActivity"android:label="@string/app_name"> <intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="UNCHER"/> </intent-filter></activity></application><uses-sdk android:minSdkVersion="8"/><!-- 访问internet权限 --><uses-permissionandroid:name="android.permission.INTERNET"/><!-- 在SDCard中创建与删除文件权限 --><uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FI LESYSTEMS"/><!-- 往SDCard写入数据权限 --><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_S TORAGE"/></manifest>。