之前在Android(java)学习笔记215中,我们从JavaSE的角度去实现了多线程断点下载,下面从Android角度实现这个断点下载:
1. 新建一个Android工程:
(1)其中我们先实现布局文件activity_main.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 tools:context=".MainActivity" > 7 8 <EditText 9 android:id="@+id/et_path" 10 android:layout_width="match_parent" 11 android:layout_height="wrap_content" 12 android:text="http://192.168.1.100:8080/" /> 13 14 <EditText 15 android:inputType="number" 16 android:id="@+id/et_count" 17 android:layout_width="match_parent" 18 android:layout_height="wrap_content" 19 android:hint="请设置下载线程的数量,默认3" /> 20 21 <Button 22 android:layout_width="match_parent" 23 android:layout_height="wrap_content" 24 android:onClick="download" 25 android:text="下载" /> 26 27 <LinearLayout 28 android:paddingLeft="5dip" 29 android:paddingRight="5dip" 30 android:id="@+id/ll_container" 31 android:layout_width="match_parent" 32 android:layout_height="match_parent" 33 android:orientation="vertical" > 34 </LinearLayout> 35 36 </LinearLayout>
布局效果如下:
(2)其中的进度条样式pb.xml如下:
<?xml version="1.0" encoding="utf-8"?> <ProgressBar xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_marginLeft="5dip" android:layout_marginRight="5dip" android:layout_height="wrap_content" />
2. MainActivity.java:
1 package com.itheima.mutiledownloader; 2 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.FileNotFoundException; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.io.InputStreamReader; 10 import java.io.RandomAccessFile; 11 import java.net.HttpURLConnection; 12 import java.net.MalformedURLException; 13 import java.net.ProtocolException; 14 import java.net.URL; 15 16 import android.app.Activity; 17 import android.os.Bundle; 18 import android.os.Environment; 19 import android.text.TextUtils; 20 import android.view.View; 21 import android.widget.EditText; 22 import android.widget.LinearLayout; 23 import android.widget.ProgressBar; 24 import android.widget.Toast; 25 26 public class MainActivity extends Activity { 27 private EditText et_path; 28 private EditText et_count; 29 private LinearLayout ll_container; 30 private int threadCount = 3; 31 private String path; 32 protected int runningThreadCount; 33 34 @Override 35 protected void onCreate(Bundle savedInstanceState) { 36 super.onCreate(savedInstanceState); 37 setContentView(R.layout.activity_main); 38 et_count = (EditText) findViewById(R.id.et_count); 39 et_path = (EditText) findViewById(R.id.et_path); 40 ll_container = (LinearLayout) findViewById(R.id.ll_container); 41 } 42 43 public void download(View view){ 44 String str_count = et_count.getText().toString().trim(); 45 path = et_path.getText().toString().trim(); 46 if(TextUtils.isEmpty(path)&&!path.startsWith("http://")){ 47 Toast.makeText(this, "下载路径不合法", 0).show(); 48 return; 49 } 50 if(!TextUtils.isEmpty(str_count)){ 51 threadCount = Integer.parseInt(str_count); 52 } 53 ll_container.removeAllViews(); 54 for(int i=0;i<threadCount;i++){ 55 ProgressBar pb = (ProgressBar) View.inflate(this, R.layout.pb,null); 56 ll_container.addView(pb); 57 } 58 new Thread(){ 59 public void run() { 60 try { 61 URL url = new URL(path); 62 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 63 conn.setRequestMethod("GET"); 64 int code = conn.getResponseCode(); 65 if(code == 200){ 66 int length = conn.getContentLength(); 67 System.out.println("服务器文件的大小为:"+length); 68 //创建一个空白文件文件的大小和服务器资源一样 69 RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getPath()+"/"+getFileName(path), "rw"); 70 raf.setLength(length); 71 raf.close(); 72 //每个线程下载的平均区块大小 73 int blocksize = length / threadCount; 74 runningThreadCount = threadCount; 75 for(int threadId = 0;threadId<threadCount;threadId++){ 76 int startIndex = threadId*blocksize; 77 int endIndex = (threadId+1)*blocksize-1; 78 if(threadId==(threadCount-1)){//最后一个线程的修正 79 endIndex = length - 1; 80 } 81 new DownloadThread(startIndex, endIndex, threadId).start(); 82 } 83 } 84 } catch (Exception e) { 85 e.printStackTrace(); 86 showToast("下载失败"); 87 } 88 89 }; 90 }.start(); 91 92 } 93 class DownloadThread extends Thread{ 94 /** 95 * 理论上第一次开始的位置 96 */ 97 int firstStartIndex; 98 int startIndex; 99 int endIndex; 100 /** 101 * 当前线程下载到文件的位置 102 */ 103 int filePosition; 104 int threadId; 105 /** 106 * 当前线程需要下载的总文件大小 107 */ 108 int threadTotal; 109 ProgressBar pb;//当前线程对应的进度条 110 /** 111 * 112 * @param startIndex 开始位置 113 * @param endIndex 结束位置 114 * @param threadId 线程id 115 */ 116 public DownloadThread(int startIndex, int endIndex, int threadId) { 117 this.startIndex = startIndex; 118 this.firstStartIndex = startIndex; 119 this.endIndex = endIndex; 120 threadTotal = endIndex - startIndex; 121 this.threadId = threadId; 122 pb = (ProgressBar) ll_container.getChildAt(threadId); 123 pb.setMax(threadTotal); 124 filePosition = startIndex; 125 } 126 127 @Override 128 public void run() { 129 //System.out.println("线程:"+threadId+"理论下载的位置:"+startIndex+"~~~"+endIndex); 130 //读取,看看有没有下载的历史数据,读下载的进度 131 try { 132 File file = new File(Environment.getExternalStorageDirectory().getPath()+"/"+threadId+getFileName(path)+".txt");//用一个文本记录当前线程下载的进程 133 if(file.exists()&&file.length()>0){ 134 FileInputStream fis = new FileInputStream(file); 135 BufferedReader br = new BufferedReader(new InputStreamReader(fis)); 136 filePosition = Integer.parseInt(br.readLine());//上一次下载到文件的哪个位子。 137 startIndex = filePosition; 138 fis.close(); 139 } 140 System.out.println("线程:"+threadId+"实际上下载的位置:"+startIndex+"~~~"+endIndex); 141 URL url = new URL(path); 142 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 143 conn.setRequestMethod("GET"); 144 conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); 145 int code = conn.getResponseCode();//2XX 成功 3XX重定向 4XX资源找不到 5XX服务器异常 146 if(code == 206){ 147 InputStream is = conn.getInputStream(); 148 RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getPath()+"/"+getFileName(path), "rwd"); 149 //一定要记得定位文件写的位置 150 raf.seek(startIndex); 151 byte[] buffer = new byte[1024*4];//缓冲区的大小 152 int len = -1; 153 while((len = is.read(buffer))!=-1){ 154 raf.write(buffer, 0, len); 155 filePosition+=len;//记录写入数据的进度 156 int process = filePosition - firstStartIndex; 157 pb.setProgress(process); 158 RandomAccessFile rafinfo = new RandomAccessFile(file, "rwd"); 159 rafinfo.write(String.valueOf(filePosition).getBytes()); 160 rafinfo.close(); 161 } 162 raf.close(); 163 is.close(); 164 System.out.println("线程:"+threadId+"下载完毕了。"); 165 synchronized (MainActivity.this) {//加锁,同步运行,保证下面一段代码在同一个时间片运行,原子性执行 166 runningThreadCount--; 167 if(runningThreadCount==0){ 168 System.out.println("所有的线程都下载完毕了"); 169 showToast("下载完毕了"); 170 for(int i =0;i<threadCount;i++){ 171 File f = new File(Environment.getExternalStorageDirectory().getPath()+"/"+i+getFileName(path)+".txt"); 172 System.out.println(f.delete()); 173 } 174 } 175 } 176 177 } 178 } catch (Exception e) { 179 e.printStackTrace(); 180 } 181 } 182 } 183 /** 184 * 获取路径对应的文件名 185 * @param path 186 * @return 187 */ 188 private static String getFileName(String path){ 189 int beginIndex = path.lastIndexOf("/")+1; 190 return path.substring(beginIndex); 191 } 192 193 194 private void showToast(final String text){ 195 runOnUiThread(new Runnable() { 196 @Override 197 public void run() { 198 Toast.makeText(MainActivity.this, text, 0).show(); 199 } 200 }); 201 } 202 }
3. 其中AndroidMainfest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.itheima.mutiledownloader" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.itheima.mutiledownloader.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>