天天看點

安卓開發之多線程斷點下載下傳(三)

效果圖:

安卓開發之多線程斷點下載下傳(三)

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("表格删除", "表格删除");
                    }
                }

            }
        }
    }
}
           

有問題的不懂的歡迎評論