`
simgsg
  • 浏览: 91345 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Android(五)数据存储之五网络多线程断点下载

 
阅读更多
<p>们编写的是Andorid的HTTP多线程断点下载应用程序。因为之间我们学习的学习积累,直接使用单线程下载HTTP文件对我们来说是一件非常简单的事。那么,多线程断点下载的难点在哪里?1.多线程下载,2.支持断点。 </p>
<p><strong>多线程下载:</strong> <br><img title="2010-03-03 传智播客—Android(五)数据存储之五网络多线程断点下载 - 长城 - 长城" src="http://lh4.ggpht.com/_FMPy3IVGHrs/S49SK45xqaI/AAAAAAAABms/B6bk9kJo_vU/632BF46FE91E9326216CA04E22AF3F9E09350A1C.jpg?imgmax=800" border="0" alt="2010-03-03 传智播客—Android(五)数据存储之五网络多线程断点下载 - 长城 - 长城" width="435" height="91"></p>
<p>如何才能从文件的指定位置处开始下载文件?(比如从50MB开始)这一点我们可以通过HTTP请求信息头来设置,还记得HTTP请求信息头的“Range”属性吗?</p>
<p><strong>断点:</strong></p>
<p>首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在Android中却不能这么做。通过老黎的试验,在Android平台中,我们需要向文件中写出下载的文件数据,还需要向另一个文件中写出下载进度,这样会出错。这样会导致有一个文件的内容没有被写出。所以我们就不能以文件的方式来保存下载进度,但可以通过数据库的方式保存下载进度。</p>
<p>这两大问题我们已经有了解决思路,那么就开始动手编写吧!</p>
<p><strong>1.</strong><strong>创建Android工程</strong></p>
<p>Project name:MulThreadDownloader</p>
<p>BuildTarget:Android2.1</p>
<p>Application name:多线程断点下载</p>
<p>Package name:com.changcheng.download</p>
<p>Create Activity:MulThreadDownloader</p>
<p>Min SDK Version:7</p>
<p><strong>2.AndroidManifest.xml</strong></p>
<p>&lt;?xml version=<em>"1.0"</em> encoding=<em>"utf-8"</em>?&gt;</p>
<p>&lt;manifest xmlns:android=<em>"http://schemas.android.com/apk/res/android"</em></p>
<p>package=<em>"com.changcheng.download"</em></p>
<p>android:versionCode=<em>"1"</em></p>
<p>android:versionName=<em>"1.0"</em>&gt;</p>
<p>&lt;application android:icon=<em>"@drawable/icon"</em> android:label=<em>"@string/app_name"</em>&gt;</p>
<p>&lt;activity android:name=<em>".MulThreadDownloader"</em></p>
<p>android:label=<em>"@string/app_name"</em>&gt;</p>
<p>&lt;intent-filter&gt;</p>
<p>&lt;action android:name=<em>"android.intent.action.MAIN"</em> /&gt;</p>
<p>&lt;category android:name=<em>"android.intent.category.LAUNCHER"</em> /&gt;</p>
<p>&lt;/intent-filter&gt;</p>
<p>&lt;/activity&gt;</p>
<p>&lt;/application&gt;</p>
<p>&lt;uses-sdk android:minSdkVersion=<em>"7"</em> /&gt;</p>
<p>&lt;!-- 在SDCard中创建与删除文件权限 --&gt;</p>
<p>&lt;uses-permission android:name=<em>"android.permission.MOUNT_UNMOUNT_FILESYSTEMS"</em>/&gt;</p>
<p>&lt;!-- 往SDCard写入数据权限 --&gt;</p>
<p>&lt;uses-permission android:name=<em>"android.permission.WRITE_EXTERNAL_STORAGE"</em>/&gt;</p>
<p>&lt;!-- 访问<span style="text-decoration: underline;">internet</span>权限 --&gt;</p>
<p>&lt;uses-permission android:name=<em>"android.permission.INTERNET"</em>/&gt;</p>
<p>&lt;/manifest&gt;</p>
<p><strong>3.strings.xml</strong></p>
<p>&lt;?xml version=<em>"1.0"</em> encoding=<em>"utf-8"</em>?&gt;</p>
<p>&lt;resources&gt;</p>
<p>&lt;string name=<em>"hello"</em>&gt;Hello World, DownloadActivity!&lt;/string&gt;</p>
<p>&lt;string name=<em>"app_name"</em>&gt;多线程断点下载&lt;/string&gt;</p>
<p>&lt;string name=<em>"path"</em>&gt;下载路径&lt;/string&gt;</p>
<p>&lt;string name=<em>"downloadbutton"</em>&gt;下载&lt;/string&gt;</p>
<p>&lt;string name=<em>"sdcarderror"</em>&gt;SDCard不存在或者写保护&lt;/string&gt;</p>
<p>&lt;/resources&gt;</p>
<p><strong>4.main.xml</strong></p>
<p>&lt;?xml version=<em>"1.0"</em> encoding=<em>"utf-8"</em>?&gt;</p>
<p>&lt;LinearLayout xmlns:android=<em>"http://schemas.android.com/apk/res/android"</em></p>
<p>android:orientation=<em>"vertical"</em></p>
<p>android:layout_width=<em>"fill_parent"</em></p>
<p>android:layout_height=<em>"fill_parent"</em></p>
<p>&gt;</p>
<p>&lt;!-- 下载路径 --&gt;</p>
<p>&lt;TextView</p>
<p>android:layout_width=<em>"fill_parent"</em></p>
<p>android:layout_height=<em>"wrap_content"</em></p>
<p>android:text=<em>"@string/path"</em></p>
<p>/&gt;</p>
<p>&lt;EditText</p>
<p>android:layout_width=<em>"fill_parent"</em></p>
<p>android:layout_height=<em>"wrap_content"</em></p>
<p>android:text=<em>"http://www.winrar.com.cn/download/wrar380sc.exe"</em></p>
<p>android:id=<em>"@+id/path"</em></p>
<p>/&gt;</p>
<p>&lt;!-- 下载按钮 --&gt;</p>
<p>&lt;Button</p>
<p>android:layout_width=<em>"wrap_content"</em></p>
<p>android:layout_height=<em>"wrap_content"</em></p>
<p>android:text=<em>"@string/downloadbutton"</em></p>
<p>android:id=<em>"@+id/button"</em></p>
<p>/&gt;</p>
<p>&lt;!-- 进度条 --&gt;</p>
<p>&lt;ProgressBar</p>
<p>android:layout_width=<em>"fill_parent"</em></p>
<p>android:layout_height=<em>"20dip"</em></p>
<p>style=<em>"?android:attr/progressBarStyleHorizontal"</em></p>
<p>android:id=<em>"@+id/downloadbar"</em>/&gt;</p>
<p>&lt;!-- 进度% --&gt;</p>
<p>&lt;TextView</p>
<p>android:layout_width=<em>"fill_parent"</em></p>
<p>android:layout_height=<em>"wrap_content"</em></p>
<p>android:gravity=<em>"center"</em></p>
<p>android:id=<em>"@+id/resultView"</em></p>
<p>/&gt;</p>
<p>&lt;/LinearLayout&gt;</p>
<p><strong>5.MulThreadDownloader</strong></p>
<p><strong>package</strong> com.changcheng.download;</p>
<p><strong>import</strong> java.io.File;</p>
<p><strong>import</strong> com.changcheng.net.download.DownloadProgressListener;</p>
<p><strong>import</strong> com.changcheng.net.download.FileDownloader;</p>
<p><strong>import</strong> com.changcheng.download.R;</p>
<p><strong>import</strong> android.app.Activity;</p>
<p><strong>import</strong> android.os.Bundle;</p>
<p><strong>import</strong> android.os.Environment;</p>
<p><strong>import</strong> android.os.Handler;</p>
<p><strong>import</strong> android.os.Message;</p>
<p><strong>import</strong> android.view.View;</p>
<p><strong>import</strong> android.widget.Button;</p>
<p><strong>import</strong> android.widget.EditText;</p>
<p><strong>import</strong> android.widget.ProgressBar;</p>
<p><strong>import</strong> android.widget.TextView;</p>
<p><strong>import</strong> android.widget.Toast;</p>
<p><strong>public</strong> <strong>class</strong> MulThreadDownloader <strong>extends</strong> Activity {</p>
<p><strong>private</strong> EditText pathText;</p>
<p><strong>private</strong> ProgressBar progressBar;</p>
<p><strong>private</strong> TextView resultView;</p>
<p><strong>private</strong> Handler handler = <strong>new</strong> Handler(){</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> handleMessage(Message msg) {</p>
<p><strong>if</strong>(!Thread.<em>currentThread</em>().isInterrupted()){</p>
<p><strong>switch</strong> (msg.what) {</p>
<p><strong>case</strong> 1:</p>
<p>// 获取当前文件下载的进度</p>
<p><strong>int</strong> size = msg.getData().getInt("size");</p>
<p>progressBar.setProgress(size);</p>
<p><strong>int</strong> result = (<strong>int</strong>)(((<strong>float</strong>)size/(<strong>float</strong>)progressBar.getMax()) * 100);</p>
<p>resultView.setText(result+ "%");</p>
<p><strong>if</strong>(progressBar.getMax() == size){</p>
<p>Toast.<em>makeText</em>(MulThreadDownloader.<strong>this</strong>, "文件下载完成", 1).show();</p>
<p>}</p>
<p><strong>break</strong>;</p>
<p><strong>case</strong> -1:</p>
<p>String error = msg.getData().getString("error");</p>
<p>Toast.<em>makeText</em>(MulThreadDownloader.<strong>this</strong>, error, 1).show();</p>
<p><strong>break</strong>;</p>
<p>}</p>
<p>}</p>
<p><strong>super</strong>.handleMessage(msg);</p>
<p>}</p>
<p>};</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> onCreate(Bundle savedInstanceState) {</p>
<p><strong>super</strong>.onCreate(savedInstanceState);</p>
<p>setContentView(R.layout.<em>main</em>);</p>
<p>pathText = (EditText)<strong>this</strong>.findViewById(R.id.<em>path</em>);</p>
<p>progressBar = (ProgressBar)<strong>this</strong>.findViewById(R.id.<em>downloadbar</em>);</p>
<p>resultView = (TextView)<strong>this</strong>.findViewById(R.id.<em>resultView</em>);</p>
<p>Button button = (Button)<strong>this</strong>.findViewById(R.id.<em>button</em>);</p>
<p>button.setOnClickListener(<strong>new</strong> View.OnClickListener() {</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> onClick(View v) {</p>
<p>String path = pathText.getText().toString();</p>
<p><strong>if</strong>(Environment.<em>getExternalStorageState</em>().equals(Environment.<em>MEDIA_MOUNTED</em>)){</p>
<p>//下载文件需要很长的时间,主线程是不能够长时间被阻塞,如果主线程被长时间阻塞, 那么<span style="text-decoration: underline;">Android</span>被回收应用</p>
<p>download(path, Environment.<em>getExternalStorageDirectory</em>());</p>
<p>}<strong>else</strong>{</p>
<p>Toast.<em>makeText</em>(MulThreadDownloader.<strong>this</strong>, R.string.<em>sdcarderror</em>, 1).show();</p>
<p>}</p>
<p>}</p>
<p>});</p>
<p>}</p>
<p>/**</p>
<p>* 下载文件</p>
<p>* <strong>@param</strong> path 下载路径</p>
<p>* <strong>@param</strong> saveDir 文件保存目录</p>
<p>*/</p>
<p>//对于<span style="text-decoration: underline;">Android</span>的UI控件,只能由主线程负责显示界面的更新,其他线程不能直接更新UI控件的显示</p>
<p><strong>public</strong> <strong>void</strong> download(<strong>final</strong> String path, <strong>final</strong> File saveDir){</p>
<p><strong>new</strong> Thread(<strong>new</strong> Runnable() {</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> run() {</p>
<p>FileDownloader downer = <strong>new</strong> FileDownloader(MulThreadDownloader.<strong>this</strong>, path, saveDir, 3);</p>
<p>progressBar.setMax(downer.getFileSize());//设置进度条的最大刻度</p>
<p><strong>try</strong> {</p>
<p>downer.download(<strong>new</strong> DownloadProgressListener(){</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> onDownloadSize(<strong>int</strong> size) {</p>
<p>Message msg = <strong>new</strong> Message();</p>
<p>msg.what = 1;</p>
<p>msg.getData().putInt("size", size);</p>
<p>handler.sendMessage(msg);//发送消息</p>
<p>}});</p>
<p>} <strong>catch</strong> (Exception e) {</p>
<p>Message msg = <strong>new</strong> Message();</p>
<p>msg.what = -1;</p>
<p>msg.getData().putString("error", "下载失败");</p>
<p>handler.sendMessage(msg);</p>
<p>}</p>
<p>}</p>
<p>}).start();</p>
<p>}</p>
<p>}</p>
<p><strong>6.FileDownload</strong></p>
<p><strong>package</strong> com.changcheng.net.download;</p>
<p><strong>import</strong> java.io.File;</p>
<p><strong>import</strong> java.io.RandomAccessFile;</p>
<p><strong>import</strong> java.net.HttpURLConnection;</p>
<p><strong>import</strong> java.net.URL;</p>
<p><strong>import</strong> java.util.LinkedHashMap;</p>
<p><strong>import</strong> java.util.Map;</p>
<p><strong>import</strong> java.util.UUID;</p>
<p><strong>import</strong> java.util.concurrent.ConcurrentHashMap;</p>
<p><strong>import</strong> java.util.regex.Matcher;</p>
<p><strong>import</strong> java.util.regex.Pattern;</p>
<p><strong>import</strong> com.changcheng.download.service.FileService;</p>
<p><strong>import</strong> android.content.Context;</p>
<p><strong>import</strong> android.util.Log;</p>
<p>/**</p>
<p>* 文件下载器</p>
<p>* <strong>@author</strong> <span style="text-decoration: underline;">lihuoming@sohu.com</span></p>
<p>*</p>
<p>*/</p>
<p><strong>public</strong> <strong>class</strong> FileDownloader {</p>
<p><strong>private</strong> Context <span style="text-decoration: underline;">context</span>;</p>
<p><strong>private</strong> FileService fileService;</p>
<p><strong>private</strong> <strong>static</strong> <strong>final</strong> String <em>TAG</em> = "FileDownloader";</p>
<p>/* 已下载文件大小 */</p>
<p><strong>private</strong> <strong>int</strong> downloadSize = 0;</p>
<p>/* 原始文件大小 */</p>
<p><strong>private</strong> <strong>int</strong> fileSize = 0;</p>
<p>/* 线程数 */</p>
<p><strong>private</strong> DownloadThread[] threads;</p>
<p>/* 下载路径 */</p>
<p><strong>private</strong> URL url;</p>
<p>/* 本地保存文件 */</p>
<p><strong>private</strong> File saveFile;</p>
<p>/* 下载记录文件 */</p>
<p><strong>private</strong> File <span style="text-decoration: underline;">logFile</span>;</p>
<p>/* 缓存各线程最后下载的位置*/</p>
<p><strong>private</strong> Map&lt;Integer, Integer&gt; data = <strong>new</strong> ConcurrentHashMap&lt;Integer, Integer&gt;();</p>
<p>/* 每条线程下载的大小 */</p>
<p><strong>private</strong> <strong>int</strong> block;</p>
<p><strong>private</strong> String downloadUrl;//下载路径</p>
<p>/**</p>
<p>* 获取线程数</p>
<p>*/</p>
<p><strong>public</strong> <strong>int</strong> getThreadSize() {</p>
<p><strong>return</strong> threads.length;</p>
<p>}</p>
<p>/**</p>
<p>* 获取文件大小</p>
<p>* <strong>@return</strong></p>
<p>*/</p>
<p><strong>public</strong> <strong>int</strong> getFileSize() {</p>
<p><strong>return</strong> fileSize;</p>
<p>}</p>
<p>/**</p>
<p>* 累计已下载大小</p>
<p>* <strong>@param</strong> size</p>
<p>*/</p>
<p><strong>protected</strong> <strong>synchronized</strong> <strong>void</strong> append(<strong>int</strong> size) {</p>
<p>downloadSize += size;</p>
<p>}</p>
<p>/**</p>
<p>* 更新指定线程最后下载的位置</p>
<p>* <strong>@param</strong> threadId 线程id</p>
<p>* <strong>@param</strong> pos 最后下载的位置</p>
<p>*/</p>
<p><strong>protected</strong> <strong>void</strong> update(<strong>int</strong> threadId, <strong>int</strong> pos) {</p>
<p><strong>this</strong>.data.put(threadId, pos);</p>
<p>}</p>
<p>/**</p>
<p>* 保存记录文件</p>
<p>*/</p>
<p><strong>protected</strong> <strong>synchronized</strong> <strong>void</strong> saveLogFile() {</p>
<p><strong>this</strong>.fileService.update(<strong>this</strong>.downloadUrl, <strong>this</strong>.data);</p>
<p>}</p>
<p>/**</p>
<p>* 构建文件下载器</p>
<p>* <strong>@param</strong> downloadUrl 下载路径</p>
<p>* <strong>@param</strong> fileSaveDir 文件保存目录</p>
<p>* <strong>@param</strong> threadNum 下载线程数</p>
<p>*/</p>
<p><strong>public</strong> FileDownloader(Context context, String downloadUrl, File fileSaveDir, <strong>int</strong> threadNum) {</p>
<p><strong>try</strong> {</p>
<p><strong>this</strong>.context = context;</p>
<p><strong>this</strong>.downloadUrl = downloadUrl;</p>
<p>fileService = <strong>new</strong> FileService(context);</p>
<p><strong>this</strong>.url = <strong>new</strong> URL(downloadUrl);</p>
<p><strong>if</strong>(!fileSaveDir.exists()) fileSaveDir.mkdirs();</p>
<p><strong>this</strong>.threads = <strong>new</strong> DownloadThread[threadNum];</p>
<p>HttpURLConnection conn = (HttpURLConnection) url.openConnection();</p>
<p>conn.setConnectTimeout(6*1000);</p>
<p>conn.setRequestMethod("GET");</p>
<p>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, */*");</p>
<p>conn.setRequestProperty("Accept-Language", "zh-CN");</p>
<p>conn.setRequestProperty("Referer", downloadUrl);</p>
<p>conn.setRequestProperty("Charset", "UTF-8");</p>
<p>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)");</p>
<p>conn.setRequestProperty("Connection", "Keep-Alive");</p>
<p>conn.connect();</p>
<p><em>printResponseHeader</em>(conn);</p>
<p><strong>if</strong> (conn.getResponseCode()==200) {</p>
<p><strong>this</strong>.fileSize = conn.getContentLength();//根据响应获取文件大小</p>
<p><strong>if</strong> (<strong>this</strong>.fileSize &lt;= 0) <strong>throw</strong> <strong>new</strong> RuntimeException("无法获知文件大小 ");</p>
<p>String filename = getFileName(conn);</p>
<p><strong>this</strong>.saveFile = <strong>new</strong> File(fileSaveDir, filename);/* 保存文件 */</p>
<p>Map&lt;Integer, Integer&gt; logdata = fileService.getData(downloadUrl);</p>
<p><strong>if</strong>(logdata.size()&gt;0){</p>
<p>data.putAll(logdata);</p>
<p>}</p>
<p><strong>this</strong>.block = <strong>this</strong>.fileSize / <strong>this</strong>.threads.length + 1;</p>
<p><strong>if</strong>(<strong>this</strong>.data.size()==<strong>this</strong>.threads.length){</p>
<p><strong>for</strong> (<strong>int</strong> i = 0; i &lt; <strong>this</strong>.threads.length; i++) {</p>
<p><strong>this</strong>.downloadSize += <strong>this</strong>.data.get(i+1)-(<strong>this</strong>.block * i);</p>
<p>}</p>
<p><em>print</em>("已经下载的长度"+ <strong>this</strong>.downloadSize);</p>
<p>}</p>
<p>}<strong>else</strong>{</p>
<p><strong>throw</strong> <strong>new</strong> RuntimeException("服务器响应错误 ");</p>
<p>}</p>
<p>} <strong>catch</strong> (Exception e) {</p>
<p><em>print</em>(e.toString());</p>
<p><strong>throw</strong> <strong>new</strong> RuntimeException("连接不到下载路径 ");</p>
<p>}</p>
<p>}</p>
<p>/**</p>
<p>* 获取文件名</p>
<p>*/</p>
<p><strong>private</strong> String getFileName(HttpURLConnection conn) {</p>
<p>String filename = <strong>this</strong>.url.toString().substring(<strong>this</strong>.url.toString().lastIndexOf('/') + 1);</p>
<p><strong>if</strong>(filename==<strong>null</strong> || "".equals(filename.trim())){//如果获取不到文件名称</p>
<p><strong>for</strong> (<strong>int</strong> i = 0;; i++) {</p>
<p>String mine = conn.getHeaderField(i);</p>
<p><strong>if</strong> (mine == <strong>null</strong>) <strong>break</strong>;</p>
<p><strong>if</strong>("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){</p>
<p>Matcher m = Pattern.<em>compile</em>(".*filename=(.*)").matcher(mine.toLowerCase());</p>
<p><strong>if</strong>(m.find()) <strong>return</strong> m.group(1);</p>
<p>}</p>
<p>}</p>
<p>filename = UUID.<em>randomUUID</em>()+ ".tmp";//默认取一个文件名</p>
<p>}</p>
<p><strong>return</strong> filename;</p>
<p>}</p>
<p>/**</p>
<p>* 开始下载文件</p>
<p>* <strong>@param</strong> listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null</p>
<p>* <strong>@return</strong> 已下载文件大小</p>
<p>* <strong>@throws</strong> Exception</p>
<p>*/</p>
<p><strong>public</strong> <strong>int</strong> download(DownloadProgressListener listener) <strong>throws</strong> Exception{</p>
<p><strong>try</strong> {</p>
<p><strong>if</strong>(<strong>this</strong>.data.size() != <strong>this</strong>.threads.length){</p>
<p><strong>this</strong>.data.clear();</p>
<p><strong>for</strong> (<strong>int</strong> i = 0; i &lt; <strong>this</strong>.threads.length; i++) {</p>
<p><strong>this</strong>.data.put(i+1, <strong>this</strong>.block * i);</p>
<p>}</p>
<p>}</p>
<p><strong>for</strong> (<strong>int</strong> i = 0; i &lt; <strong>this</strong>.threads.length; i++) {</p>
<p><strong>int</strong> downLength = <strong>this</strong>.data.get(i+1) - (<strong>this</strong>.block * i);</p>
<p><strong>if</strong>(downLength &lt; <strong>this</strong>.block &amp;&amp; <strong>this</strong>.data.get(i+1)&lt;<strong>this</strong>.fileSize){ //该线程未完成下载时,继续下载</p>
<p>RandomAccessFile randOut = <strong>new</strong> RandomAccessFile(<strong>this</strong>.saveFile, "rw");</p>
<p><strong>if</strong>(<strong>this</strong>.fileSize&gt;0) randOut.setLength(<strong>this</strong>.fileSize);</p>
<p>randOut.seek(<strong>this</strong>.data.get(i+1));</p>
<p><strong>this</strong>.threads[i] = <strong>new</strong> DownloadThread(<strong>this</strong>, <strong>this</strong>.url, randOut, <strong>this</strong>.block, <strong>this</strong>.data.get(i+1), i+1);</p>
<p><strong>this</strong>.threads[i].setPriority(7);</p>
<p><strong>this</strong>.threads[i].start();</p>
<p>}<strong>else</strong>{</p>
<p><strong>this</strong>.threads[i] = <strong>null</strong>;</p>
<p>}</p>
<p>}</p>
<p><strong>this</strong>.fileService.save(<strong>this</strong>.downloadUrl, <strong>this</strong>.data);</p>
<p><strong>boolean</strong> notFinish = <strong>true</strong>;//下载未完成</p>
<p><strong>while</strong> (notFinish) {// 循环判断是否下载完毕</p>
<p>Thread.<em>sleep</em>(900);</p>
<p>notFinish = <strong>false</strong>;//假定下载完成</p>
<p><strong>for</strong> (<strong>int</strong> i = 0; i &lt; <strong>this</strong>.threads.length; i++){</p>
<p><strong>if</strong> (<strong>this</strong>.threads[i] != <strong>null</strong> &amp;&amp; !<strong>this</strong>.threads[i].isFinish()) {</p>
<p>notFinish = <strong>true</strong>;//下载没有完成</p>
<p><strong>if</strong>(<strong>this</strong>.threads[i].getDownLength() == -1){//如果下载失败,再重新下载</p>
<p>RandomAccessFile randOut = <strong>new</strong> RandomAccessFile(<strong>this</strong>.saveFile, "rw");</p>
<p>randOut.seek(<strong>this</strong>.data.get(i+1));</p>
<p><strong>this</strong>.threads[i] = <strong>new</strong> DownloadThread(<strong>this</strong>, <strong>this</strong>.url, randOut, <strong>this</strong>.block, <strong>this</strong>.data.get(i+1), i+1);</p>
<p><strong>this</strong>.threads[i].setPriority(7);</p>
<p><strong>this</strong>.threads[i].start();</p>
<p>}</p>
<p>}</p>
<p>}</p>
<p><strong>if</strong>(listener!=<strong>null</strong>) listener.onDownloadSize(<strong>this</strong>.downloadSize);</p>
<p>}</p>
<p>fileService.delete(<strong>this</strong>.downloadUrl);</p>
<p>} <strong>catch</strong> (Exception e) {</p>
<p><em>print</em>(e.toString());</p>
<p><strong>throw</strong> <strong>new</strong> Exception("下载失败");</p>
<p>}</p>
<p><strong>return</strong> <strong>this</strong>.downloadSize;</p>
<p>}</p>
<p>/**</p>
<p>* 获取<span style="text-decoration: underline;">Http</span>响应头字段</p>
<p>* <strong>@param</strong> http</p>
<p>* <strong>@return</strong></p>
<p>*/</p>
<p><strong>public</strong> <strong>static</strong> Map&lt;String, String&gt; getHttpResponseHeader(HttpURLConnection http) {</p>
<p>Map&lt;String, String&gt; header = <strong>new</strong> LinkedHashMap&lt;String, String&gt;();</p>
<p><strong>for</strong> (<strong>int</strong> i = 0;; i++) {</p>
<p>String mine = http.getHeaderField(i);</p>
<p><strong>if</strong> (mine == <strong>null</strong>) <strong>break</strong>;</p>
<p>header.put(http.getHeaderFieldKey(i), mine);</p>
<p>}</p>
<p><strong>return</strong> header;</p>
<p>}</p>
<p>/**</p>
<p>* 打印<span style="text-decoration: underline;">Http</span>头字段</p>
<p>* <strong>@param</strong> http</p>
<p>*/</p>
<p><strong>public</strong> <strong>static</strong> <strong>void</strong> printResponseHeader(HttpURLConnection http){</p>
<p>Map&lt;String, String&gt; header = <em>getHttpResponseHeader</em>(http);</p>
<p><strong>for</strong>(Map.Entry&lt;String, String&gt; entry : header.entrySet()){</p>
<p>String key = entry.getKey()!=<strong>null</strong> ? entry.getKey()+ ":" : "";</p>
<p><em>print</em>(key+ entry.getValue());</p>
<p>}</p>
<p>}</p>
<p><strong>private</strong> <strong>static</strong> <strong>void</strong> print(String msg){</p>
<p>Log.<em>i</em>(<em>TAG</em>, msg);</p>
<p>}</p>
<p>}</p>
<p><strong>7.DownloadProgressListener</strong></p>
<p><strong>package</strong> com.changcheng.net.download;</p>
<p><strong>public</strong> <strong>interface</strong> DownloadProgressListener {</p>
<p><strong>public</strong> <strong>void</strong> onDownloadSize(<strong>int</strong> size);</p>
<p>}</p>
<p><strong>8.FileService</strong></p>
<p><strong>package</strong> com.changcheng.download.service;</p>
<p><strong>import</strong> java.util.HashMap;</p>
<p><strong>import</strong> java.util.Map;</p>
<p><strong>import</strong> android.content.Context;</p>
<p><strong>import</strong> android.database.Cursor;</p>
<p><strong>import</strong> android.database.sqlite.SQLiteDatabase;</p>
<p>/**</p>
<p>* 业务bean</p>
<p>*</p>
<p>*/</p>
<p><strong>public</strong> <strong>class</strong> FileService {</p>
<p><strong>private</strong> DBOpenHelper openHelper;</p>
<p><strong>public</strong> FileService(Context context) {</p>
<p>openHelper = <strong>new</strong> DBOpenHelper(context);</p>
<p>}</p>
<p>/**</p>
<p>* 获取线程最后下载位置</p>
<p>* <strong>@param</strong> path</p>
<p>* <strong>@return</strong></p>
<p>*/</p>
<p><strong>public</strong> Map&lt;Integer, Integer&gt; getData(String path){</p>
<p>SQLiteDatabase db = openHelper.getReadableDatabase();</p>
<p>Cursor cursor = db.rawQuery("select threadid, position from filedown where downpath=?", <strong>new</strong> String[]{path});</p>
<p>Map&lt;Integer, Integer&gt; data = <strong>new</strong> HashMap&lt;Integer, Integer&gt;();</p>
<p><strong>while</strong>(cursor.moveToNext()){</p>
<p>data.put(cursor.getInt(0), cursor.getInt(1));</p>
<p>}</p>
<p>cursor.close();</p>
<p>db.close();</p>
<p><strong>return</strong> data;</p>
<p>}</p>
<p>/**</p>
<p>* 保存下载线程初始位置</p>
<p>* <strong>@param</strong> path</p>
<p>* <strong>@param</strong> map</p>
<p>*/</p>
<p><strong>public</strong> <strong>void</strong> save(String path, Map&lt;Integer, Integer&gt; map){//<span style="text-decoration: underline;">int</span> <span style="text-decoration: underline;">threadid</span>, <span style="text-decoration: underline;">int</span> position</p>
<p>SQLiteDatabase db = openHelper.getWritableDatabase();</p>
<p>db.beginTransaction();</p>
<p><strong>try</strong>{</p>
<p><strong>for</strong>(Map.Entry&lt;Integer, Integer&gt; entry : map.entrySet()){</p>
<p>db.execSQL("insert into filedown(downpath, threadid, position) values(?,?,?)",</p>
<p><strong>new</strong> Object[]{path, entry.getKey(), entry.getValue()});</p>
<p>}</p>
<p>db.setTransactionSuccessful();</p>
<p>}<strong>finally</strong>{</p>
<p>db.endTransaction();</p>
<p>}</p>
<p>db.close();</p>
<p>}</p>
<p>/**</p>
<p>* 实时更新线程的最后下载位置</p>
<p>* <strong>@param</strong> path</p>
<p>* <strong>@param</strong> map</p>
<p>*/</p>
<p><strong>public</strong> <strong>void</strong> update(String path, Map&lt;Integer, Integer&gt; map){</p>
<p>SQLiteDatabase db = openHelper.getWritableDatabase();</p>
<p>db.beginTransaction();</p>
<p><strong>try</strong>{</p>
<p><strong>for</strong>(Map.Entry&lt;Integer, Integer&gt; entry : map.entrySet()){</p>
<p>db.execSQL("update filedown set position=? where downpath=? and threadid=?",</p>
<p><strong>new</strong> Object[]{entry.getValue(), path, entry.getKey()});</p>
<p>}</p>
<p>db.setTransactionSuccessful();</p>
<p>}<strong>finally</strong>{</p>
<p>db.endTransaction();</p>
<p>}</p>
<p>db.close();</p>
<p>}</p>
<p>/**</p>
<p>* 当文件下载完成后,清掉该文件对应的下载记录</p>
<p>* <strong>@param</strong> path</p>
<p>*/</p>
<p><strong>public</strong> <strong>void</strong> delete(String path){</p>
<p>SQLiteDatabase db = openHelper.getWritableDatabase();</p>
<p>db.execSQL("delete from filedown where downpath=?", <strong>new</strong> Object[]{path});</p>
<p>db.close();</p>
<p>}</p>
<p>}</p>
<p><strong>9.DownloadThread</strong></p>
<p><strong>package</strong> com.changcheng.net.download;</p>
<p><strong>import</strong> java.io.InputStream;</p>
<p><strong>import</strong> java.io.RandomAccessFile;</p>
<p><strong>import</strong> java.net.HttpURLConnection;</p>
<p><strong>import</strong> java.net.URL;</p>
<p><strong>import</strong> android.util.Log;</p>
<p><strong>public</strong> <strong>class</strong> DownloadThread <strong>extends</strong> Thread {</p>
<p><strong>private</strong> <strong>static</strong> <strong>final</strong> String <em>TAG</em> = "DownloadThread";</p>
<p><strong>private</strong> RandomAccessFile saveFile;</p>
<p><strong>private</strong> URL downUrl;</p>
<p><strong>private</strong> <strong>int</strong> block;</p>
<p>/* 下载开始位置 */</p>
<p><strong>private</strong> <strong>int</strong> threadId = -1;</p>
<p><strong>private</strong> <strong>int</strong> startPos;</p>
<p><strong>private</strong> <strong>int</strong> downLength;</p>
<p><strong>private</strong> <strong>boolean</strong> finish = <strong>false</strong>;</p>
<p><strong>private</strong> FileDownloader downloader;</p>
<p><strong>public</strong> DownloadThread(FileDownloader downloader, URL downUrl, RandomAccessFile saveFile, <strong>int</strong> block, <strong>int</strong> startPos, <strong>int</strong> threadId) {</p>
<p><strong>this</strong>.downUrl = downUrl;</p>
<p><strong>this</strong>.saveFile = saveFile;</p>
<p><strong>this</strong>.block = block;</p>
<p><strong>this</strong>.startPos = startPos;</p>
<p><strong>this</strong>.downloader = downloader;</p>
<p><strong>this</strong>.threadId = threadId;</p>
<p><strong>this</strong>.downLength = startPos - (block * (threadId - 1));</p>
<p>}</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> run() {</p>
<p><strong>if</strong>(downLength &lt; block){//未下载完成</p>
<p><strong>try</strong> {</p>
<p>HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();</p>
<p>http.setRequestMethod("GET");</p>
<p>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, */*");</p>
<p>http.setRequestProperty("Accept-Language", "zh-CN");</p>
<p>http.setRequestProperty("Referer", downUrl.toString());</p>
<p>http.setRequestProperty("Charset", "UTF-8");</p>
<p>http.setRequestProperty("Range", "bytes=" + <strong>this</strong>.startPos + "-");</p>
<p>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)");</p>
<p>http.setRequestProperty("Connection", "Keep-Alive");</p>
<p>InputStream inStream = http.getInputStream();</p>
<p><strong>int</strong> max = block&gt;1024 ? 1024 : (block&gt;10 ? 10 : 1);</p>
<p><strong>byte</strong>[] buffer = <strong>new</strong> <strong>byte</strong>[max];</p>
<p><strong>int</strong> offset = 0;</p>
<p><em>print</em>("线程 " + <strong>this</strong>.threadId + "从位置"+ <strong>this</strong>.startPos+ "开始下载 ");</p>
<p><strong>while</strong> (downLength &lt; block &amp;&amp; (offset = inStream.read(buffer, 0, max)) != -1) {</p>
<p>saveFile.write(buffer, 0, offset);</p>
<p>downLength += offset;</p>
<p>downloader.update(<strong>this</strong>.threadId, block * (threadId - 1) + downLength);</p>
<p>downloader.saveLogFile();</p>
<p>downloader.append(offset);</p>
<p><strong>int</strong> spare = block-downLength;//求剩下的字节数</p>
<p><strong>if</strong>(spare &lt; max) max = (<strong>int</strong>) spare;</p>
<p>}</p>
<p>saveFile.close();</p>
<p>inStream.close();</p>
<p><em>print</em>("线程 " + <strong>this</strong>.threadId + "完成下载 ");</p>
<p><strong>this</strong>.finish = <strong>true</strong>;</p>
<p><strong>this</strong>.interrupt();</p>
<p>} <strong>catch</strong> (Exception e) {</p>
<p><strong>this</strong>.downLength = -1;</p>
<p><em>print</em>("线程"+ <strong>this</strong>.threadId+ ":"+ e);</p>
<p>}</p>
<p>}</p>
<p>}</p>
<p><strong>private</strong> <strong>static</strong> <strong>void</strong> print(String msg){</p>
<p>Log.<em>i</em>(<em>TAG</em>, msg);</p>
<p>}</p>
<p>/**</p>
<p>* 下载是否完成</p>
<p>* <strong>@return</strong></p>
<p>*/</p>
<p><strong>public</strong> <strong>boolean</strong> isFinish() {</p>
<p><strong>return</strong> finish;</p>
<p>}</p>
<p>/**</p>
<p>* 已经下载的内容大小</p>
<p>* <strong>@return</strong> 如果返回值为-1,代表下载失败</p>
<p>*/</p>
<p><strong>public</strong> <strong>long</strong> getDownLength() {</p>
<p><strong>return</strong> downLength;</p>
<p>}</p>
<p>}</p>
<p><strong>11.DBOpenHelper</strong></p>
<p><strong>package</strong> com.changcheng.download.service;</p>
<p><strong>import</strong> android.content.Context;</p>
<p><strong>import</strong> android.database.sqlite.SQLiteDatabase;</p>
<p><strong>import</strong> android.database.sqlite.SQLiteOpenHelper;</p>
<p><strong>public</strong> <strong>class</strong> DBOpenHelper <strong>extends</strong> SQLiteOpenHelper {</p>
<p><strong>private</strong> <strong>static</strong> <strong>final</strong> String <em>DBNAME</em> = "download.db";</p>
<p><strong>private</strong> <strong>static</strong> <strong>final</strong> <strong>int</strong> <em>VERSION</em> = 2;</p>
<p><strong>public</strong> DBOpenHelper(Context context) {</p>
<p><strong>super</strong>(context, <em>DBNAME</em>, <strong>null</strong>, <em>VERSION</em>);</p>
<p>}</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> onCreate(SQLiteDatabase db) {</p>
<p>db.execSQL("CREATE TABLE IF NOT EXISTS filedown (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, position INTEGER)");</p>
<p>}</p>
<p>@Override</p>
<p><strong>public</strong> <strong>void</strong> onUpgrade(SQLiteDatabase db, <strong>int</strong> oldVersion, <strong>int</strong> newVersion) {</p>
<p>db.execSQL("DROP TABLE IF EXISTS filedown");</p>
<p>onCreate(db);</p>
<p>}</p>
<p>}</p>
<p>结束!</p>
分享到:
评论

相关推荐

    Android开发--多线程下载加断点续传

    1.多线程下载: 首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang - 1,注意最后一段的位置是到filesize - 1; ...

    Android多线程下载文件

    Android多线程下载文件,支持断点续传,这里用的数据库存储

    Android多线程下载

    功能包括多线程下载,progressbar更新,保存sd卡、多文件同名文件的命名方法以及检测存储空间等,博客地址为:http://blog.csdn.net/xutao3716/article/details/49357529

    多线程断点续传程序 java版本

    下载,支持多线程,支持断点续传 下载记录在数据库中 如果在android手机中可以存储在SQLite数据库里

    Android多线程断点续传小例子

    本例子是一个多线程支持断点续传下载文件和边缓冲边播放音乐的例子源码,亲测有效。点击下载以后会有一个下载进度条,如果想要美观一些可以自己给进度条使用一些比较好看的样式。点击暂停以后完全结束程序再重新打开...

    多线程断点续传

    基于文件存储写的一个多线程断点上传下载Demo

    Android入门:多线程断点下载详细介绍

    本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度。也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用...

    Android实现断点多线程下载

    断点多线程下载的几个关键点:①:得到要下载的文件大小后,均分给几个线程。②:使用RandomAccessFile类进行读写,可以指定开始写入的位置。③:数据库保存下载信息,下一次继续下载的时候从数据库取出数据,然后从...

    多线程断点续传+在线音乐缓冲播放

    多线程断点续传+在线音乐缓冲播放源码点击下载以后会有一个下载进度条,如果想要美观一些可以自己给进度条使用一些比较好看的样式。点击暂停以后完全结束程序再重新打开程序点击开始下载会在上次下载到的地方继续...

    PC版与Android手机版带断点续传的多线程下载

    一、多线程下载  多线程下载就是抢占服务器资源  原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源。  1、设置开启线程数,...

    Android 断点续传原理以及实现

    在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的setRequestProperty()方法可以告诉服务器,数据从哪里开始,到哪里结束。同时在本地的文件...

    黑马程序员 安卓学院 万元哥项目经理 分享220个代码实例

    |--利用FinalHttp实现多线程断点续传 |--加密之MD5 |--动画Animation详解 |--动画之view左右抖动 |--动画之移动动画 |--动画之组合动画 |--动画之缩放动画ScaleAnimation |--反序列化对象 |--发送短信 读天气 调音量...

    android开发秘籍

    1.8.6 android market 的候补之选 17 第2 章 应用程序基础知识:activity 和intent 18 2.1 android 应用程序预览 18 2.1.1 秘诀1:创建工程并新建activity 19 2.1.2 工程目录结构及自动生成内容 20 2.1.3 android...

    JAVA上百实例源码以及开源项目源代码

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    JAVA上百实例源码以及开源项目

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

Global site tag (gtag.js) - Google Analytics