多線程下載與斷點續(xù)傳

多線程下載和斷點續(xù)傳用HttpUrlConnection和OkHttp都可以實現(xiàn)
這里說的是用OkHttp來實現(xiàn)的
以下是代碼,多線程下載,暫停開始,斷點續(xù)傳,進度條顯示

   public class testActivity extends Activity {
    private String name="aaa";
    private int pwd=123;
    private final String getUrl="http://139.129.110.99:8901/test?name="+name+"&pwd="+pwd;
    private final String postUrl="http://139.129.110.99:8901/posttest";
    private final String sourceUrl="http://api.jisuapi.com/news/get?channel=頭條&start=0&num=10&appkey=e06ac19faa18ef4a";
    public final String fieUrl="http://139.129.110.99:81/witraffic_app.rar";
    private ProgressBar progressBar,progressBar1,progressBar2,progressBar3;
    private OkHttpClient client=new OkHttpClient();
    private Call call;
    //設置線程的數(shù)量
    private final static  int threadCount=3;
    //正在下載的線程
    private int runningThread;
    //設置下載的文件的路徑
    public String filePath;
    //總的下載進度
    public int totalCount=0;
    //文件總大小
    public long total;
    //判斷是否正在下載
    public Boolean isRunning=false;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        progressBar=(ProgressBar)findViewById(R.id.progress);
        button=(Button)findViewById(R.id.btn);
        runningThread=threadCount;
        //創(chuàng)建下載的文件
        File file=new File(Environment.getExternalStorageDirectory(),"aaa");
        file.mkdirs();
        File file1=new File(file,"aaa.rar");
        try {
            file1.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //點擊這個按鈕的時候開始下載或者繼續(xù)下載,對應button的點擊事件
    public void click1(View v)
    {
        if(!isRunning) {
            //這次請求就為了獲取文件長度
            Request request = new Request.Builder()
                    .url(fieUrl)
                    .tag("a")
                    .build();
            call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.i("test", e.getMessage());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //1 獲取到文件的大小
                    total = response.body().contentLength();
                    //設置總大小
                    progressBar.setMax((int) total);
                    //2 創(chuàng)建和服務器大小一樣的文件,同時為他分配和服務器上文件大小一樣的空間
                    //支持對文件隨機讀寫的類,第一個參數(shù)傳入文件的地址,第二個參數(shù)是模式,rwd代表同步到底層設備
                    //隨機讀寫的意思是可以從任何的位置開始讀寫
                    filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/aaa/aaa.rar";
                    RandomAccessFile file = new RandomAccessFile(filePath, "rwd");
                    //設置文件的大小
                    file.setLength(total);
                    Log.i("test", "線程" + total);
                    //3 計算每個線程下載的大小和開始的長度
                /*
                計算方法說明:
                文件下載的起點是從0開始的,所以如果一個文件共10000bit,那么下載的位置就是0~9999
                首先用總大小除以線程數(shù)量,這兩者都是整型,做除法后得到的也是整型
                得到的就是處理最后一個線程以外每個線程需要下載的長度
                文件的地址是從0開始的,所以第一個線程下載的位置就是0*blockSize~1*blockSize-1
                第二個就是1*blockSize~2*blockSize-1
                ......
                第n個就是(n-1)*blockSize~n*blockSize-1
                .....
                最后一個線程下載區(qū)間就是,假設一共三個線程
                2*blockSize~文件末尾
                 */
                /*
                所以第一個線程的開始位置為0,結束位置就是1*blockSize-1
                第二個線程開始位置為1*blockSize,結束位置就是2*blockSize-1
                ....
                第n個線程開始位置就是(n-1)*blockSize,結束位置就是n*blockSize-1
                假設一共三個線程,最后一個線程開始位置就是,2*blockSize~(文件總長度-1)
                 */
                    //3.1 算出除了最后一個線程外每個線程下載的大小
                    long blockSize = total / threadCount;
                    Log.i("test", "" + blockSize);
                    //3.2 開多個線程進行下載
                    for (int i = 0; i < threadCount; i++) {
                        //每個線程的開始位置
                        long startIndex = i * blockSize;
                        //每個線程的結束位置
                        long endIndex = (i + 1) * blockSize - 1;
                        //如果是最后一個線程,那么結束位置是文件的末尾
                        if (i == (threadCount - 1)) {
                            endIndex = total - 1;
                        }
                        Log.i("test", "線程" + i + "開始位置" + startIndex + "結束位置:" + endIndex);
                        //開啟線程開始下載,單寫一個DownLoadThread類,直接傳入?yún)?shù)開始下載
                        new DownLoadThread(startIndex, endIndex, i).start();

                    }
                    isRunning = true;

                }
            });
            button.setText("暫停");

        }
        else
        {
            CancelTag("a");
            button.setText("開始");
            isRunning=false;
        }
    }
    //下載文件的線程的代碼
    public class DownLoadThread extends Thread
    {
        public long startIndex;
        public long endIndex;
        public long threadId;
        public DownLoadThread(long startIndex,long endIndex,long threadId)
        {
            this.startIndex=startIndex;
            this.endIndex=endIndex;
            this.threadId=threadId;
        }

        @Override
        public void run() {
            //創(chuàng)建存放開始位置的文件
            final File startIndexFile=new File(Environment.getExternalStorageDirectory()+"/aaa",threadId+".txt");
            if(!startIndexFile.exists())
            {
                try {
                    startIndexFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //判斷是第一次下載還是斷點后繼續(xù)下載
            try {
                FileInputStream inputStream=new FileInputStream(startIndexFile);
                BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
                String currentIndex=reader.readLine();
                if(currentIndex!=null)
                {
                    startIndex=Integer.parseInt(currentIndex);
                    Log.i("test","線程"+threadId+"開始位置:"+startIndex+"結束位置:"+endIndex);
                }
                inputStream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }catch (IOException e)
            {
                e.printStackTrace();
            }
            //這個判斷很重要,在結束的時候暫停很可能導致開始位置大于結束位置
            if(startIndex<=endIndex)
            {
                 //這里開始請求數(shù)據(jù),每個線程請求的都是部分數(shù)據(jù),所以要設置一個頭
                //這個頭的名為Range,參數(shù)是下載的起止位置,格式為"bytes=開始位置-結束位置"
                //這時返回的狀態(tài)碼為206,206代表請求部分數(shù)據(jù)
                //同時設置一個tag標簽,用于暫停下載時使用
                final Request request=new Request.Builder()
                        .url(fieUrl)
                        .tag("a")
                        .addHeader("Range","bytes="+startIndex+"-"+endIndex)
                        .build();
                Call call=client.newCall(request);
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {

                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        //這里開始處理接收文件的邏輯
                        //記錄該線程當前下載的大小
                        long currentCount=0;
                        //記錄當前的位置
                        long currentPosition=0;
                        //首先再拿到之前創(chuàng)建的空文件
                        RandomAccessFile file=new RandomAccessFile(filePath,"rwd");
                        //設置開始寫入的位置
                        file.seek(startIndex);
                        int len=-1;
                        InputStream inputStream=response.body().byteStream();
                        byte[] bytes=new byte[1024*1024];
                        while((len=inputStream.read(bytes))!=-1)
                        {
                            //RandomAccessFile類同樣有write方法,寫入文件,相當于輸出流,也需要關閉
                            file.write(bytes,0,len);
                            //當前線程下載的總大小
                            currentCount+=len;
                            //如果下載暫停了以后新的下載位置
                            currentPosition=startIndex+currentCount;
                            //用RandomAccessFile來記錄位置是因為它可以用rwd模式將數(shù)據(jù)同步到底層設備,
                            //避免數(shù)據(jù)同步不上
                            RandomAccessFile raf=new RandomAccessFile(startIndexFile,"rwd");
                            raf.write(String.valueOf(currentPosition).getBytes());
                            raf.close();
                            //設置下載進度,progress可以直接在子線程更新UI
                            synchronized (DownLoadThread.class)
                            {
                                totalCount+=len;
                                progressBar.setProgress(totalCount);
                                if(totalCount==total)
                                {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            Toast.makeText(testActivity.this,"下載完畢",Toast.LENGTH_SHORT).show();
                                        }
                                    });
                                }
                            }

                        }
                        //關閉RandomAccessFile
                        file.close();
                        Log.i("test","線程"+threadId+"下載完畢");
                        //每次下載完畢一個線程runningThread就減一,當runningThread為0時,代表所有線程都下載完畢
                        //為DownLoadThread在操作runningThread--時加鎖,在多個線程同時操作runningThread--時保證先后順序
                        synchronized (DownLoadThread.class)
                        {
                            runningThread--;
                            //當所有線程都下載完畢時,刪除存儲開始位置的文件
                            if(runningThread==0)
                            {
                                for(int i=0;i<threadCount;i++)
                                {
                                    File deleteFile=new File(Environment.getExternalStorageDirectory()+"/aaa",i+".txt");
                                    deleteFile.delete();
                                }
                            }
                        }

                    }
                });
            }



        }
    }
    //取消對應tag的call的方法,okhttp3.0以后沒有直接根據(jù)標簽取消的方法
    //只能間接的寫出這個方法
    public void CancelTag(Object o)
    {
        for(Call call:client.dispatcher().queuedCalls())
        {
            if(call.request().tag().equals(o))
            {
                call.cancel();
            }
        }
        for(Call call:client.dispatcher().runningCalls())
        {
            if(call.request().tag().equals(o))
            {
                call.cancel();
            }
        }
        isRunning=false;
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在需要的地方調(diào)用ActivityCollector.exit(),就可以退出所有的活動
        ActivityCollector.exit();
    }
}

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容