效果圖:

Log:
網上關于講解挺多的,我這裡不講解了,不懂的可以評論留言,從問題中解決問題
我可以說一下我解決問題的方式,将複雜問題劃分成多個簡單的問題
多線程下載下傳一:請點選這裡
多線程下載下傳二:請點選這裡
權限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
布局:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:hint="URL"
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:hint="線程數"
android:id="@+id/et_thread"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.TextInputLayout>
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:indeterminate="false"/>
<TextView
android:id="@+id/tv_progress"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:onClick="startDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="開始下載下傳"
android:id="@+id/button"/>
<Button
android:onClick="stopDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暫停"
android:id="@+id/button2"
/>
<Button
android:onClick="restartDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重新下載下傳"
android:id="@+id/button3"
/>
</LinearLayout>
DBHelp:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* 用于存儲下載下傳記錄的資料庫
*/
public class DBHelp extends SQLiteOpenHelper {
public DBHelp(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER,alldown INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
}
}
DownlaodSqlTool:
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
/**
*
* 資料庫操作工具類
*/
public class DownlaodSqlTool {
DBHelp dbHelper;
public DownlaodSqlTool(Context context) {
dbHelper = new DBHelp(context,"down",null,);
}
/**
* 建立下載下傳的具體資訊
*/
public void insertInfos(String downPath,int threadID,int downLength) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
String sql = "insert into filedownlog(downpath,threadid, downlength) values (?,?,?)";
Object[] bindArgs = {downPath,threadID,downLength};
database.execSQL(sql, bindArgs);
}
/**
* 根據線程和URL得到已經下載下傳的長度
*/
public int getInfo(String urlstr,String threadID) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
String sql = "select downlength from filedownlog where downpath=? and threadid=?";
String[] bindArgs = { urlstr,threadID };
Cursor cursor = database.rawQuery(sql, bindArgs);
if (cursor.getCount()>){
while (cursor.moveToNext()) {
int downlength=cursor.getInt();
return downlength;
}
}
cursor.close();
return -;
}
/**
* 用于判斷表中是否有資料
* 傳回1表示有資料
* 0表示沒有資料
*/
public int getInfo(String urlstr) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
String sql = "select * from filedownlog where downpath=?";
String[] bindArgs = { urlstr};
Cursor cursor = database.rawQuery(sql, bindArgs);
Log.i("DownloadSqoTool",cursor.getCount()+"Count");
if (cursor.getCount()>){
return ;
}
cursor.close();
return ;
}
/**
* 關閉資料庫
*/
public void closeDb() {
dbHelper.close();
}
/**
* 更新資料庫中的每個線程的下載下傳資訊
* @param threadId
* @param compeleteSize
* @param urlstr
*/
public void updataInfos( int threadId, int compeleteSize, String urlstr) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
String sql = "update filedownlog set downlength=? where threadid=? and downpath=?";
Object[] bindArgs = { compeleteSize ,threadId, urlstr};
database.execSQL(sql, bindArgs);
}
/**
* 下載下傳完成後删除資料庫中的資料
*/
public void delete(String url) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.delete("filedownlog", "downpath=?", new String[] { url });
}
}
MainActivity:
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class MainActivity extends AppCompatActivity {
DownlaodSqlTool sqlTool = new DownlaodSqlTool(MainActivity.this);
Object object = new Object();
Button button, button2,button3;
TextView tv_progress;
EditText textInputEditText, textInputEditText1;
ProgressBar progressBar;
//SD卡路徑
String SDpath = Environment.getExternalStorageDirectory() + "";
//SD卡不存在錯誤
final int SDPATH_ERROR = ;
//伺服器錯誤
final int SERVICE_ERROR = ;
//URL錯誤
final int URL_ERROR = ;
//Thread錯誤
final int THREAD_ERROR = ;
//已經下載下傳的長度
final int HASLENGTH = ;
//重新下載下傳
final int RESTART=;
//初始化進度
final int UPDATE_DATA = ;
//判斷線程是否下載下傳完成
int finish = ;
//計算多個線程累計下載下傳的進度
int allDown = ;
int hasAllDown = ;
//是否暫停
boolean isPause = false;
//是否重新下載下傳
boolean isRestart=false;
//URL 2: 13817637 3:26018378 1:1638400
// String path="http://ddd1.pc6.com/soft/explorer.exe";
//http://66dx.pc6.com/lzz3/360compkill32w.zip
//http://down.360safe.com/yunpan/360wangpan_setup_6.6.0.1307.exe
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
button2 = (Button) findViewById(R.id.button2);
button3= (Button) findViewById(R.id.button3);
button2.setClickable(false);
button3.setClickable(false);
textInputEditText = (EditText) findViewById(R.id.et_name);
textInputEditText1 = (EditText) findViewById(R.id.et_thread);
tv_progress = (TextView) findViewById(R.id.tv_progress);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setProgress();
}
/**
* 按鈕的點選事件
* 點選建立檔案,開始下載下傳
*
* @param view
*/
public void startDownload(View view) {
isPause = false;
isRestart=false;
button.setClickable(false);
button2.setClickable(true);
button3.setClickable(true);
//首先建立一個檔案,然後下載下傳
new CreateSameFileSize().start();
}
public void stopDownload(View view) {
button2.setClickable(false);
button.setClickable(true);
button3.setClickable(true);
isPause = true;
}
public void restartDownload(View view){
button2.setClickable(true);
button.setClickable(false);
button3.setClickable(false);
isRestart=true;
}
/**
* 切割URL得到下載下傳應用的名字
*/
public String splitURL(String url) {
//得到 / 最後出現的位置
int index = url.lastIndexOf("/");
Log.i("splitURL", index + "");
//截取 / 後面的字元串
String appName = url.substring(index + , url.length());
Log.i("splitURL", appName);
return appName;
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SDPATH_ERROR:
Toast.makeText(MainActivity.this, "SD卡路徑錯誤", Toast.LENGTH_SHORT).show();
break;
case SERVICE_ERROR:
Toast.makeText(MainActivity.this, "伺服器錯誤", Toast.LENGTH_SHORT).show();
break;
case URL_ERROR:
Toast.makeText(MainActivity.this, "URL為空", Toast.LENGTH_SHORT).show();
break;
case THREAD_ERROR:
Toast.makeText(MainActivity.this, "線程數不規範", Toast.LENGTH_SHORT).show();
break;
case HASLENGTH:
int progressSize = msg.getData().getInt("size");
Log.i("progress:", "progress:" + progressSize);
progressBar.setProgress(progressSize);
float temp = (float) progressBar.getProgress() / (float) progressBar.getMax();
int progress = (int) (temp * );
if (progress == ) {
Toast.makeText(MainActivity.this, "下載下傳完成!", Toast.LENGTH_SHORT).show();
}
tv_progress.setText("下載下傳進度:" + progress + " %");
break;
case RESTART:
int newprogressSize = msg.getData().getInt("newsize");
Log.i("progress:", "progress:" + newprogressSize);
progressBar.setProgress(newprogressSize);
float newtemp = (float) progressBar.getProgress() / (float) progressBar.getMax();
int newprogress = (int) (newtemp * );
if (newprogress == ) {
Toast.makeText(MainActivity.this, "下載下傳完成!", Toast.LENGTH_SHORT).show();
}
tv_progress.setText("下載下傳進度:" + newprogress + " %");
break;
}
}
};
/**
* 建立一個空的下載下傳檔案
* synchronized保證下載下傳之前必定會先建立檔案
* 給線程配置設定開始下載下傳位置 結束下載下傳位置
*/
public class CreateSameFileSize extends Thread {
@Override
public void run() {
if (Environment.getExternalStorageState() != null) {
try {
//得到下載下傳的應用名稱
String inputPath = textInputEditText.getEditableText().toString();
if (inputPath == null || inputPath.equals("")) {
handler.obtainMessage(URL_ERROR);
} else {
String appName = splitURL(inputPath);
URL url = new URL(inputPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout();
//200表示ok
if (conn.getResponseCode() == ) {
//得到檔案的長度
int length = conn.getContentLength();
progressBar.setMax(length);
Log.i("檔案大小:", "檔案大小:" + length);
int isData = sqlTool.getInfo(inputPath);
if (isData == ) {
//在SDpath目錄下聲明一個appName的檔案,此時還沒有建立檔案
File file = new File(SDpath, appName);
if (file.exists()) {
file.delete();
}
//建立檔案,rwd表示檔案可讀可寫,一旦更新立即寫入
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
Log.i("raf", "建立檔案完成");
}
//擷取線程數
String threadCount = textInputEditText1.getEditableText().toString();
//正規表達式,判斷是不是數字
Pattern p = Pattern.compile("[0-9]*");
Matcher m = p.matcher(threadCount);
if (threadCount == null || threadCount.equals("") || (!m.matches())) {
handler.obtainMessage(THREAD_ERROR);
} else {
int threadCount1 = Integer.parseInt(threadCount);
finish = threadCount1;
//平均每個線程下載下傳的長度
int blockSize = length / threadCount1;
//計算每個線程下載下傳的起始位置和結束位置
for (int threadID = ; threadID <= threadCount1; threadID++) {
//通過公式計算出起始位置
int startIndex = (threadID - ) * blockSize;
//通過公式計算出結束位置
int endIndex = threadID * blockSize - ;
if (threadCount1 == threadID) {
endIndex = length;
}
Log.i("下載下傳位置:", threadID + ":" + startIndex + "--->" + endIndex);
//開始下載下傳
Log.i("開始下載下傳", threadID + "開始");
new DownloadThread(threadID, startIndex, endIndex, inputPath).start();
}
//完成後斷開連接配接
conn.disconnect();
}
} else {
handler.obtainMessage(SERVICE_ERROR);
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
handler.obtainMessage(SERVICE_ERROR);
} catch (IOException e) {
e.printStackTrace();
}
} else {
handler.obtainMessage(SDPATH_ERROR);
}
}
}
/**
* 下載下傳類
*/
public class DownloadThread extends Thread {
int threadId;//線程ID
int startIndex;//下載下傳起始位置
int endIndex;//下載下傳結束位置
String path;//下載下傳路徑
public DownloadThread(int threadId, int startIndex, int endIndex, String path) {
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.path = path;
}
@Override
public void run() {
//得到下載下傳的應用名稱
URL url = null;
InputStream is = null;
RandomAccessFile raf = null;
try {
//記錄已經下載下傳的長度
String newthreadID = Integer.toString(threadId);
int hasDownloadLength = ;
/**
* 每次下載下傳去查詢表中是否有資料,有資料的話就
* 将資料取出來作為下載下傳的開始位置
*/
int haslength = sqlTool.getInfo(path, newthreadID);
if (haslength != -) {
allDown = haslength - startIndex;
hasAllDown += allDown;
Log.i("更新", "更新");
Log.i("已經存儲", haslength + "..." + threadId);
hasDownloadLength = haslength;
startIndex = haslength;
} else {
Log.i("插入", "插入");
hasDownloadLength += startIndex;
sqlTool.insertInfos(path, threadId, hasDownloadLength);
}
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//重要:請求伺服器下載下傳指定位置的檔案
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
Log.i("真實下載下傳位置:", threadId + ":" + startIndex + "--->" + endIndex);
conn.setConnectTimeout();
//200 ok 下載下傳全部資源
//206 ok 下載下傳部分資源
int code = conn.getResponseCode();
Log.i("code", "code:" + code);
//已經設定了指定請求,是以這裡是指定位置的輸出流
is = conn.getInputStream();
//得到下載下傳的應用名稱
String inputPath = textInputEditText.getEditableText().toString();
String appName = splitURL(inputPath);
File file = new File(SDpath, appName);
//随機寫檔案的時候,從哪個位置開始寫
raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
int length = ;
byte[] buf = new byte[];
int i = ;
while ((length = is.read(buf)) != -) {
synchronized (MainActivity.this) {
if (isPause) {
return;
}
if (isRestart){
Message message = new Message();
message.what = RESTART;
message.getData().putInt("newsize", );
handler.sendMessage(message);
return;
}
Log.i("has", threadId + "之前:" + hasDownloadLength);
hasAllDown += length;
hasDownloadLength += length;
Log.i("allDownload", "總共下載下傳" + hasAllDown);
sqlTool.updataInfos(threadId, hasDownloadLength, path);
Log.i("has", threadId + "之後:" + hasDownloadLength);
raf.write(buf, , length);
Message message = new Message();
message.what = HASLENGTH;
message.getData().putInt("size", hasAllDown);
handler.sendMessage(message);
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isPause) {
allDown = ;
hasAllDown=;
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.i("stop", threadId +"暫停下載下傳");
} else {
if (isRestart){
Log.i("重新下載下傳", "重新下載下傳");
}else{
Log.i("下載下傳結束", threadId + "下載下傳結束");
}
finish--;
if (finish == ) {
allDown = ;
hasAllDown=;
button.setClickable(true);
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
sqlTool.delete(path);
sqlTool.closeDb();
Log.i("表格删除", "表格删除");
}
}
}
}
}
}
有問題的不懂的歡迎評論