官术网_书友最值得收藏!

上篇 UML

UML是面向?qū)ο蠓治雠c設(shè)計(jì)時(shí)的行業(yè)標(biāo)準(zhǔn),談面向?qū)ο蟮姆治觥⒃O(shè)計(jì)時(shí)就不能不談UML。

對(duì)軟件工程師而言,使用UML規(guī)范整個(gè)程序的開(kāi)發(fā),是最佳的選擇。

第1章 項(xiàng)目分析

1.1 為什么要選擇Android多線(xiàn)程斷點(diǎn)續(xù)傳下載器作為本書(shū)講解UML時(shí)的項(xiàng)目

Android多線(xiàn)程斷點(diǎn)續(xù)傳下載器涉及了Android應(yīng)用開(kāi)發(fā)大部分的核心知識(shí)點(diǎn)和難點(diǎn):

(1)Android中主線(xiàn)程和非主線(xiàn)通信機(jī)制:Handler、Looper、Message、MessageQueue。

(2)多線(xiàn)程的編程、管理。

(3)Android網(wǎng)絡(luò)編程。

(4)IoC技術(shù),自己動(dòng)手實(shí)現(xiàn)設(shè)計(jì)模式中的Listener模式。

(5)Activity、Service、數(shù)據(jù)庫(kù)編程等。

(6)文件系統(tǒng)。

(7)緩存。

本章用一個(gè)項(xiàng)目去貫穿整個(gè)Android項(xiàng)目的學(xué)習(xí),理論和實(shí)踐相結(jié)合,設(shè)計(jì)和編碼并舉。

1.2 細(xì)致剖析Android多線(xiàn)程斷點(diǎn)續(xù)傳下載器

首先看一下多線(xiàn)程斷點(diǎn)續(xù)傳下載器的運(yùn)行效果圖,如下圖所示。

其實(shí)上面運(yùn)行效果的基本原理如下圖所示。

1.Android多線(xiàn)程的實(shí)現(xiàn)思想

(1)可以根據(jù)記錄當(dāng)前的下載位置,實(shí)現(xiàn)斷點(diǎn)下載。

如果現(xiàn)在需要下載一個(gè)大小為29MB的文件,當(dāng)下載到5MB時(shí),臨時(shí)有事情,關(guān)閉之后普通的下載器不能幫助我們繼續(xù)下載,而是必須重新開(kāi)始,而多線(xiàn)程下載器(如迅雷)可以幫助我們記錄下上次下載的位置,當(dāng)再次下載時(shí)可以從記錄的位置繼續(xù)下載。

(2)下載速度快。

使用多線(xiàn)程下載文件可以更快地完成文件的下載,多線(xiàn)程下載文件之所以快,是因?yàn)槠鋼屨嫉姆?wù)器資源多。假設(shè)服務(wù)器同時(shí)最多服務(wù)100個(gè)用戶(hù),在服務(wù)器中一條線(xiàn)程對(duì)應(yīng)一個(gè)用戶(hù),100條線(xiàn)程在計(jì)算機(jī)中并非并發(fā)執(zhí)行,而是由CPU劃分時(shí)間片輪流執(zhí)行,如果A應(yīng)用使用了99條線(xiàn)程下載文件,那么相當(dāng)于占用了99個(gè)用戶(hù)的資源。假設(shè)一秒內(nèi)CPU分配給每條線(xiàn)程的平均執(zhí)行時(shí)間是10ms, A應(yīng)用在服務(wù)器中一秒內(nèi)就得到了990ms的執(zhí)行時(shí)間,而其他應(yīng)用在一秒內(nèi)只有10ms的執(zhí)行時(shí)間。就如同一個(gè)水龍頭,在每秒出水量相等的情況下,放990ms的水肯定比放10ms的水要多。

實(shí)現(xiàn)多線(xiàn)程操作的可以分為:

● 取得網(wǎng)絡(luò)連接;

● 初始化多線(xiàn)程下載信息;

● “開(kāi)辟”硬盤(pán)空間;

● 將網(wǎng)絡(luò)數(shù)據(jù)放入已申請(qǐng)的空間中;

● 關(guān)閉資源。

通過(guò)下圖來(lái)加深對(duì)多線(xiàn)程的理解。

此文件的大小是6MB,共有三條線(xiàn)程同時(shí)進(jìn)行下載,實(shí)現(xiàn)過(guò)程如下所示。

(1)首先要根據(jù)要訪(fǎng)問(wèn)的URL路徑去調(diào)用openConnection()方法,得到HttpUrlConnection對(duì)象。HttpUrlConnection調(diào)用它的方法得到下載文件的長(zhǎng)度,然后設(shè)置本地文件的長(zhǎng)度。

    Int  filesize = HttpURLConnection.getContentLength();
    RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe", "rw");

可以使用RandomAccessFile隨機(jī)訪(fǎng)問(wèn)類(lèi)。

RandomAccessFile和File的區(qū)別:

RandomAccessFile將FileInputStream和FileOutputStream整合到一起,而且支持將從文件的任意字節(jié)處讀或?qū)憯?shù)據(jù),F(xiàn)ile類(lèi)只是將文件作為整體來(lái)處理文件的,不能讀寫(xiě)文件。

    file.setLength(filesize);

調(diào)用setLength(filesize)方法設(shè)置文件的長(zhǎng)度,file可以達(dá)到下載文件的長(zhǎng)度,但是它的內(nèi)部不存在我們要下載文件的的數(shù)據(jù),而是File類(lèi)的特有的一些初始化的數(shù)據(jù)。

(2)根據(jù)文件長(zhǎng)度和線(xiàn)程數(shù)計(jì)算每條線(xiàn)程下載的數(shù)據(jù)長(zhǎng)度和下載位置。例如,文件的長(zhǎng)度為6MB,線(xiàn)程數(shù)為3,那么,每條線(xiàn)程下載的數(shù)據(jù)長(zhǎng)度為2MB,每條線(xiàn)程開(kāi)始下載的位置如上圖所示。

需要計(jì)算每一條線(xiàn)程需要下載的長(zhǎng)度、每條線(xiàn)程下載的開(kāi)始位置。例如,如果每一條線(xiàn)程是2MB,那么第一條線(xiàn)程就是從0開(kāi)始,第二條就是從2開(kāi)始,以此類(lèi)推。

但是這樣就引出了一個(gè)問(wèn)題:在下載時(shí),怎樣去指定這些線(xiàn)程開(kāi)始下載的位置呢?

HTTP協(xié)議已經(jīng)為我們解決了這個(gè)問(wèn)題,它可以給我們提供一個(gè)Range頭。

(3)使用HTTP的Range頭字段指定每條線(xiàn)程從文件的什么位置開(kāi)始下載,例如,指定從文件的2MB位置開(kāi)始下載文件,代碼如下所示。

    HttpURLConnection.setRequestProperty("Range", "bytes=2097152-");

我們?cè)O(shè)置的請(qǐng)求頭Range字段,就是bytes=2097152,2MB的字節(jié),比如說(shuō)指定了上圖的線(xiàn)程2,在下載的過(guò)程中只要設(shè)置了這個(gè)頭,那么它就會(huì)從文件的A→B開(kāi)始下載。

每條線(xiàn)程在各自的文件下載完之后,需要將下載完的文件保存到一定的位置。這樣就引入了RandomAccessFile類(lèi)。

(4)保存文件,使用RandomAccessFile類(lèi)指定每條線(xiàn)程從本地文件的一定位置開(kāi)始寫(xiě)入數(shù)據(jù)。

下面的代碼就可以指定從文件的什么位置開(kāi)始寫(xiě)入數(shù)據(jù)。

    RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ", "rw");
    threadfile.seek(2097152);

用時(shí)序圖演示在此工程中實(shí)現(xiàn)多線(xiàn)程、斷點(diǎn)下載的思路,每條線(xiàn)程負(fù)責(zé)寫(xiě)文件的某一段的數(shù)據(jù),如下圖所示。

整個(gè)工程的結(jié)構(gòu)圖如下圖所示。

多線(xiàn)程、斷點(diǎn)實(shí)現(xiàn)過(guò)程如下:

(1)首先設(shè)計(jì)main.xml頁(yè)面,當(dāng)在DownLoadActivity.java中單擊下載按鈕時(shí),就會(huì)觸發(fā)其單擊事件,在單擊事件內(nèi)部中調(diào)用download()方法用于實(shí)現(xiàn)下載功能。

(2)編寫(xiě)實(shí)現(xiàn)下載的download()方法,并在方法內(nèi)開(kāi)啟一個(gè)線(xiàn)程,在其run()方法中“new”FileDownloader類(lèi)。返回下載文件的大小和已經(jīng)下載的數(shù)量。

● 在FileDownloader類(lèi)中構(gòu)造線(xiàn)程下載器。

● 在download方法中調(diào)用FileService類(lèi)中操作線(xiàn)程的下載記錄業(yè)務(wù)方法,得到各個(gè)線(xiàn)程的最后下載位置。

● 將DownloadProgressListener接口以對(duì)象的形式當(dāng)做參數(shù)傳入download方法中。

(3)在FileDownloader類(lèi)中的download方法中“new”DownloadThread類(lèi)實(shí)現(xiàn)斷點(diǎn)多線(xiàn)程下載,并存儲(chǔ)在指定的文件中。DownloadThread線(xiàn)程返回文件是否下載完成。

(4)在FileDownloader類(lèi)中的download方法中完成未下載完的補(bǔ)救方案。

(5)如果已經(jīng)下載完成,則刪除數(shù)據(jù)庫(kù)中的文件,并通過(guò)DownloadProgressListener接口中的onDownloadSize方法得到已經(jīng)下載文件的數(shù)量。

(6)將下載文件的大小和已經(jīng)下載的數(shù)量已經(jīng)返回給新開(kāi)啟的線(xiàn)程中,通過(guò)Handle異步通信實(shí)現(xiàn)頁(yè)面重繪。將文件的下載進(jìn)度顯示在UI界面上。

完成此功能需要解決的技術(shù)要點(diǎn):

● 完成頁(yè)面UI和布局文件;

● 數(shù)據(jù)庫(kù)中記錄各線(xiàn)程已經(jīng)下載的信息,對(duì)各個(gè)線(xiàn)程的下載記錄進(jìn)行操作;

● 構(gòu)造下載器;

● 實(shí)現(xiàn)下載功能,并同時(shí)可以得到實(shí)時(shí)的各個(gè)線(xiàn)程的下載數(shù)量;

● 完成下載的進(jìn)度的實(shí)時(shí)更新;

● 得到下載文件的名字;

● 完成頁(yè)面的實(shí)時(shí)更新。

因?yàn)槎嗑€(xiàn)程文件下載涉及Web服務(wù)端和Android客戶(hù)端,所以需要分別建立這兩部分工程。

2.Android多線(xiàn)程斷點(diǎn)續(xù)傳下載之服務(wù)器端

服務(wù)端的核心功能是提供一個(gè)相對(duì)比較大的文件以供Android客戶(hù)端下載使用,具體建立過(guò)程如下所示。

(1)建立一個(gè)動(dòng)態(tài)的Web工程“ServerForMultipleThreadDownloader”,如下圖所示。

一切采用默認(rèn)設(shè)置,單擊“Finish”按鈕完成工程的創(chuàng)建。此時(shí)的工程視圖如下圖所示。

(2)把一個(gè)相對(duì)比較大的音頻文件加入到WebContent的根目錄下,例如,筆者這里加入的是筆者自己的CNN錄音文件,文件名為“CNNRecordingFromWangjialin.mp3”,加入后的工程視圖如下圖所示。

(3)發(fā)布服務(wù)端Web工程,如下圖所示。

發(fā)布后的Eclipse內(nèi)置的瀏覽器顯示如下圖所示。

此時(shí)在該瀏覽器中輸入http://localhost:8080/ServerForMultipleThreadDownloader/CNNReco-rdingFromWangjialin.mp3,出現(xiàn)如下圖所示的頁(yè)面。

因?yàn)榘惭b了瀏覽器的播放器的緣故,此時(shí)筆者的計(jì)算機(jī)上正在播放自己模仿的CNN新聞播音。

至此,Web服務(wù)器端實(shí)現(xiàn)并發(fā)布成功。

3.Android多線(xiàn)程斷點(diǎn)續(xù)傳下載之Android客戶(hù)端

(1)新建Android工程,工程的名字為“MultipleThreadContinuableDownloaderForAndroid4”,如下圖所示。

單擊“Next”按鈕,選擇默認(rèn)的Android 4.0平臺(tái),如下圖所示。

單擊“Next”按鈕,把包名設(shè)為“com.wangjialin.internet.multipleThreadContinuableDown-loaderForAndroid4”,單擊“Finish”按鈕完成。此時(shí)的工程視圖如下圖所示。

(2)完成主界面main.xml,其具體內(nèi)容如下所示。

    <? xml version="1.0" encoding="utf-8"? >
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <! -- 下載路徑提示文字 -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/path"
            />
        <! -- 下載路徑輸入框,此處為了方便測(cè)試,設(shè)置了默認(rèn)的路徑,可以根據(jù)需要在用戶(hù)界面處修改 -->
        <EditText
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="http://192.168.1.100:8080/ServerForMultipleThread Downloader/CNNRecordingFromWangjialin.mp3"
            android:id="@+id/path"
            />
        <! -- 水平LinearLayout布局,包括下載按鈕和暫停按鈕 -->
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            >
            <! -- 下載按鈕,用于觸發(fā)下載事件 -->
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/button"
                android:id="@+id/downloadbutton"
                />
            <! -- 暫停按鈕,在初始狀態(tài)下為不可用 -->
            <Button
            android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/stopbutton"
                android:enabled="false"
                android:id="@+id/stopbutton"
                />
        </LinearLayout>
 
        <! -- 水平進(jìn)度條,用圖形化的方式實(shí)時(shí)顯示進(jìn)步信息 -->
        <ProgressBar
            android:layout_width="fill_parent"
            android:layout_height="18dp"
            style="? android:attr/progressBarStyleHorizontal"
            android:id="@+id/progressBar"
            />
        <! -- 文本框,用于顯示實(shí)時(shí)下載的百分比 -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:id="@+id/resultView"
            />
 
    </LinearLayout>

在main.xml中我們用到了水平進(jìn)度條ProgressBar,這是Android所定義的,我們?nèi)绻L(fǎng)問(wèn)Android系統(tǒng)中定義的樣式,就必須加一個(gè)“? ”,也就是說(shuō)要引用Android系統(tǒng)中的樣式。

style="? android:attr/progressBarStyleHorizontal"這里面的樣式和以前接觸過(guò)的css樣式很相似,主要定義頁(yè)面顯示的一個(gè)風(fēng)格。我們?cè)谙螺d的過(guò)程中,還用到了百分率,我們用textview來(lái)表示,用到了Andorid的居中顯示android:gravity="center",內(nèi)容可以居中對(duì)齊。

此時(shí)打開(kāi)main.xml的Graphical Layout視圖,如下圖所示。

從上圖中可以看到,需要定義path、button、stopbutton等幾個(gè)字符串資源,具體的strings.xml內(nèi)容如下所示。

    <? xml version="1.0" encoding="utf-8"? >
    <resources>
 
        <string name="hello">Hello World, MultipleThreadContinuableDownloader ForAndroid4Activity! </string>
        <string name="app_name">MultipleThreadContinuableDownloaderFor Android4</string>
        <string name="path">Download URL</string>
        <string name="button">Start Downloading</string>
        <string name="success">Downloading Completed Successfully</string>
        <string name="error">Downloading Error</string>
        <string name="stopbutton">Pause Downloading</string>
        <string name="sdcarderror">There is no SDCard or it is not allowed to write</string>
 
    </resources>

此時(shí)再看main.xml的Graphical Layout視圖就不會(huì)有顯示問(wèn)題了,如下圖所示。

(3)完成數(shù)據(jù)庫(kù)的設(shè)計(jì)、實(shí)現(xiàn)以及對(duì)數(shù)據(jù)庫(kù)的操作。

數(shù)據(jù)庫(kù)中表的字段有id, downpath, threadid, downlength。

● id:代表數(shù)據(jù)記錄的主鍵。

● threadid:代表線(xiàn)程的id。

● downlength:代表線(xiàn)程下載的最后位置。

● downpath:代表當(dāng)前線(xiàn)程下載的資源,因?yàn)橐粋€(gè)下載器可能會(huì)同時(shí)下載很多資源。

此時(shí)建立自己的數(shù)據(jù)庫(kù)管理類(lèi),負(fù)責(zé)數(shù)據(jù)庫(kù)和數(shù)據(jù)表的創(chuàng)建、升級(jí)、初始化等工作。

創(chuàng)建數(shù)據(jù)庫(kù)管理類(lèi)DBOpenHelper,該類(lèi)需要繼承“android.database.sqlite.SQLiteOpen Helper”,如下圖所示。

DBOpenHelper的具體內(nèi)容如下所示。

    package com.wangjialin.internet.service;
 
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
 
    /**
     * SQLite管理器,實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫(kù)和表,但版本變化時(shí)實(shí)現(xiàn)對(duì)表的數(shù)據(jù)庫(kù)表的操作
     * @author think
     *
     */
    public class DBOpenHelper extends SQLiteOpenHelper {
        private static final String DBNAME = "eric.db";    //設(shè)置數(shù)據(jù)庫(kù)的名稱(chēng)
        private static final int VERSION = 1;              //設(shè)置數(shù)據(jù)庫(kù)的版本
 
        /**
        * 通過(guò)構(gòu)造方法
        * @param context應(yīng)用程序的上、下文對(duì)象
        */
        public DBOpenHelper(Context context){
            super(context, DBNAME, null, VERSION);
        }
 
        @Override
        public void onCreate(SQLiteDatabase db){  //建立數(shù)據(jù)表
            db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog(id integer
            primary key autoincrement, downpath varchar(100), threadid INTEGER,
            downlength INTEGER)");
        }
 
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
        {   //當(dāng)版本變化時(shí)系統(tǒng)會(huì)調(diào)用該回調(diào)方法
            db.execSQL("DROP TABLE IF EXISTS filedownlog"); //此處是刪除數(shù)據(jù)表,在實(shí)際的業(yè)務(wù)中一般是需要數(shù)據(jù)備份的
            onCreate(db);   //調(diào)用onCreate方法重新創(chuàng)建數(shù)據(jù)表,也可以自己根據(jù)業(yè)務(wù)需onCreate(db);   //調(diào)用onCreate方法重新創(chuàng)建數(shù)據(jù)表,也可以自己根據(jù)業(yè)務(wù)需要?jiǎng)?chuàng)建新的的數(shù)據(jù)表
        }
 
    }

接下來(lái)建立數(shù)據(jù)庫(kù)業(yè)務(wù)操作類(lèi),如下圖所示。

單擊“Finish”按鈕完成創(chuàng)建,F(xiàn)ileService的具體內(nèi)容如下所示。

    package com.wangjialin.internet.service;
 
    import java.util.HashMap;
    import java.util.Map;
 
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
 
    /**
     * 業(yè)務(wù)Bean,實(shí)現(xiàn)對(duì)數(shù)據(jù)的操作
     * @author Wang Jialin
     *
     */
    public class FileService {
        private DBOpenHelper openHelper;   //聲明數(shù)據(jù)庫(kù)管理器
 
        public FileService(Context context){
            openHelper = new DBOpenHelper(context); //根據(jù)上、下文對(duì)象實(shí)例化數(shù)據(jù)庫(kù)管理器
        }
        /**
        * 獲取特定URI的每條線(xiàn)程已經(jīng)下載的文件長(zhǎng)度
        * @param path
        * @return
        */
        public Map<Integer, Integer> getData(String path){Cursor cursor = db.rawQuery("select threadid, downlength from SQLiteDatabase db = openHelper.getReadableDatabase();  //獲取可讀SQLiteDatabase db = openHelper.getReadableDatabase();  //獲取可讀的數(shù)據(jù)庫(kù)句柄,一般情況下在該操作的內(nèi)部實(shí)現(xiàn)中,其返回的其實(shí)是可寫(xiě)的數(shù)據(jù)庫(kù)句柄filedownlog where downpath=? ", new String[]{path}); //根據(jù)下載路徑查詢(xún)所有線(xiàn)程下載數(shù)據(jù),返回的Cursor指向第一條記錄之前Map<Integer, Integer> data = new HashMap<Integer, Integer>();//建立一個(gè)哈希表用于存放每條線(xiàn)程的已經(jīng)下載的文件長(zhǎng)度while(cursor.moveToNext()){//從第一條記錄開(kāi)始開(kāi)始遍歷Cursor對(duì)象
                data.put(cursor.getInt(0), cursor.getInt(1));  //把線(xiàn)程ID和該線(xiàn)程已下載的長(zhǎng)度設(shè)置進(jìn)data哈希表中
                data.put(cursor.getInt(cursor.getColumnIndexOrThrow
               ("threadid")), cursor.getInt(cursor.getColumnIndexOrThrow
               ("downlength")));
            }
            cursor.close();     //關(guān)閉cursor,釋放資源
            db.close();         //關(guān)閉數(shù)據(jù)庫(kù)
            return data;        //返回獲得的每條線(xiàn)程和每條線(xiàn)程的下載長(zhǎng)度
        }
        /**
        * 保存每條線(xiàn)程已經(jīng)下載的文件長(zhǎng)度
        * @param path  下載的路徑
        * @param map現(xiàn)在的ID和已經(jīng)下載的長(zhǎng)度的集合
        */
       public void save(String path,  Map<Integer, Integer> map){
            SQLiteDatabase db = openHelper.getWritableDatabase();  //獲取可寫(xiě)的數(shù)據(jù)庫(kù)句柄
            db.beginTransaction();  //開(kāi)始事務(wù),因?yàn)榇颂幰迦攵嗯鷶?shù)據(jù)
            try{
                for(Map.Entry<Integer, Integer> entry : map.entrySet()){
                //采用For-Each的方式遍歷數(shù)據(jù)集合
                    db.execSQL("insert into filedownlog(downpath, threadid,
                    downlength)values(? , ? , ?)",
                            new Object[]{path, entry.getKey(), entry.getValue
                           ()});   //插入特定下載路徑,特定線(xiàn)程ID,已經(jīng)下載的數(shù)據(jù)
                }
                db.setTransactionSuccessful(); //設(shè)置事務(wù)執(zhí)行的標(biāo)志為成功
            }finally{   //此部分的代碼肯定是被執(zhí)行的,如果不殺死虛擬機(jī)的話(huà)
                db.endTransaction(); //結(jié)束一個(gè)事務(wù),如果事務(wù)設(shè)立了成功標(biāo)志,則提交事務(wù),否則回滾事務(wù)}
            db.close(); //關(guān)閉數(shù)據(jù)庫(kù),釋放相關(guān)資源
        }
        /**
         * 實(shí)時(shí)更新每條線(xiàn)程已經(jīng)下載的文件長(zhǎng)度
         * @param path
         * @param map
         */
        public void update(String path, int threadId, int pos){
            SQLiteDatabase db = openHelper.getWritableDatabase();  //獲取可寫(xiě)
            的數(shù)據(jù)庫(kù)句柄
            db.execSQL("update filedownlog set downlength=? where downpath=?
            and threadid=? ",
                    new Object[]{pos, path, threadId});//更新特定下載路徑,特定線(xiàn)程,已經(jīng)下載的文件長(zhǎng)度
            db.close(); //關(guān)閉數(shù)據(jù)庫(kù),釋放相關(guān)的資源
        }
        /**
         * 當(dāng)文件下載完成后,刪除對(duì)應(yīng)的下載記錄
         * @param path
         */
        public void delete(String path){
            SQLiteDatabase db = openHelper.getWritableDatabase();  //獲取可寫(xiě)的數(shù)據(jù)庫(kù)句柄
            db.execSQL("delete from filedownlog where downpath=? ", new Object[]
            {path});    //刪除特定下載路徑的所有線(xiàn)程記錄
            db.close(); //關(guān)閉數(shù)據(jù)庫(kù),釋放資源
        }
 
    }

(4)實(shí)現(xiàn)文件下載類(lèi)FileDownloader、具體線(xiàn)程DownloadThread以及進(jìn)度監(jiān)聽(tīng)器接口DownloadProgressListener。

FileDownloader的建立如下圖所示。

FileDownloaderd的具體內(nèi)容如下所示。

    package com.wangjialin.internet.service.downloader;
 
    import java.io.File;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.UUID;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
 
    import android.content.Context;
    import android.util.Log;
 
    import com.wangjialin.internet.service.FileService;
 
    public class FileDownloader {
    private static final String TAG = "FileDownloader"; //設(shè)置標(biāo)簽,方便Logcat日志記錄
    private static final int RESPONSEOK = 200; //響應(yīng)碼為200,即訪(fǎng)問(wèn)成功
    private Context context;                //應(yīng)用程序的上、下文對(duì)象
    private FileService fileService;       //獲取本地?cái)?shù)據(jù)庫(kù)的業(yè)務(wù)Bean
    private boolean exited;                 //停止下載標(biāo)志
    private int downloadedSize = 0;        //已下載文件長(zhǎng)度
    private int fileSize = 0;               //原始文件長(zhǎng)度
    private DownloadThread[] threads;      //根據(jù)線(xiàn)程數(shù)設(shè)置下載線(xiàn)程池
    private File saveFile;                  //數(shù)據(jù)保存到的本地文件
    private Map<Integer, Integer> data = new ConcurrentHashMap<Integer,
    Integer>();                             //緩存各線(xiàn)程下載的長(zhǎng)度
    private int block;                      //每條線(xiàn)程下載的長(zhǎng)度
    private String downloadUrl;            //下載路徑
 
    /**
     * 獲取線(xiàn)程數(shù)
     */
    public int getThreadSize(){
        return threads.length;              //根據(jù)數(shù)組長(zhǎng)度返回線(xiàn)程數(shù)
    }
 
    /**
     * 退出下載
     */
    public void exit(){
        this.exited = true;                 //設(shè)置退出標(biāo)志為true
    }
    public boolean getExited(){
        return this.exited;
    }
    /**
     * 獲取文件大小
     * @return
     */
    public int getFileSize(){
        return fileSize;                    //從類(lèi)成員變量中獲取下載文件的大小
    }
 
    /**
     * 累計(jì)已下載大小
     * @param size
     */
    protected synchronized void append(int size){ //使用同步關(guān)鍵字解決并發(fā)訪(fǎng)問(wèn)問(wèn)題
        downloadedSize += size;         //把實(shí)時(shí)下載的長(zhǎng)度加入到總下載長(zhǎng)度中
    }
    /**
     * 更新指定線(xiàn)程最后下載的位置
     * @param threadId線(xiàn)程ID
     * @param pos最后下載的位置
     */
    protected synchronized void update(int threadId, int pos){
        this.data.put(threadId, pos);  //把制定線(xiàn)程ID的線(xiàn)程賦予最新的下載長(zhǎng)度,以前的值會(huì)被覆蓋掉
        this.fileService.update(this.downloadUrl, threadId, pos);
                                            //更新數(shù)據(jù)庫(kù)中指定線(xiàn)程的下載長(zhǎng)度
    }
    /**
     * 構(gòu)建文件下載器
     * @param downloadUrl下載路徑
     * @param fileSaveDir文件保存目錄
     * @param threadNum下載線(xiàn)程數(shù)
     */
    public FileDownloader(Context context, String downloadUrl, File
    fileSaveDir, int threadNum){
        try {
            this.context = context; //對(duì)上、下文對(duì)象賦值
            this.downloadUrl = downloadUrl; //對(duì)下載的路徑賦值
            fileService = new FileService(this.context);   //實(shí)例化數(shù)據(jù)操
                作業(yè)務(wù)Bean,此處需要使用Context,因?yàn)榇颂幍臄?shù)據(jù)庫(kù)是應(yīng)用程序私有
            URL url = new URL(this.downloadUrl);   //根據(jù)下載路徑實(shí)例化URL
            if(! fileSaveDir.exists())fileSaveDir.mkdirs(); //如果指定的文
            件不存在,則創(chuàng)建目錄,此處可以創(chuàng)建多層目錄
            this.threads = new DownloadThread[threadNum];  //根據(jù)下載的線(xiàn)
            程數(shù)創(chuàng)建下載線(xiàn)程池
            HttpURLConnection conn =(HttpURLConnection)url.
            openConnection();   //建立一個(gè)遠(yuǎn)程連接句柄,此時(shí)尚未真正連接
            conn.setConnectTimeout(5*1000); //設(shè)置連接超時(shí)時(shí)間為5秒
            conn.setRequestMethod("GET");  //設(shè)置請(qǐng)求方式為GET
            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, */*"); //設(shè)置客戶(hù)端可以接受的媒體類(lèi)型
            conn.setRequestProperty("Accept-Language", "zh-CN");
            //設(shè)置客戶(hù)端語(yǔ)言
            conn.setRequestProperty("Referer", downloadUrl);   //設(shè)置請(qǐng)求
            的來(lái)源頁(yè)面,便于服務(wù)端進(jìn)行來(lái)源統(tǒng)計(jì)
            conn.setRequestProperty("Charset", "UTF-8"); //設(shè)置客戶(hù)端編碼
            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)");  //設(shè)置用戶(hù)代理
        conn.setRequestProperty("Connection", "Keep-Alive");
        //設(shè)置Connection的方式
        conn.connect(); //和遠(yuǎn)程資源建立真正的連接,但尚無(wú)返回的數(shù)據(jù)流
        printResponseHeader(conn); //答應(yīng)返回的HTTP頭字段集合
        if(conn.getResponseCode()==RESPONSEOK){  //此處的請(qǐng)求會(huì)打開(kāi)
        返回流并獲取返回的狀態(tài)碼,用于檢查是否請(qǐng)求成功,當(dāng)返回碼為200時(shí)執(zhí)行下面
        的代碼
            this.fileSize = conn.getContentLength(); //根據(jù)響應(yīng)獲取文件
            大小
            if(this.fileSize <= 0)throw new RuntimeException("Unkown
            file size ");   //當(dāng)文件大小為小于等于零時(shí)拋出運(yùn)行時(shí)異常
 
            String filename = getFileName(conn); //獲取文件名稱(chēng)
            this.saveFile = new File(fileSaveDir, filename); //根據(jù)文件
            保存目錄和文件名構(gòu)建保存文件
            Map<Integer, Integer> logdata = fileService.getData
           (downloadUrl); //獲取下載記錄
 
            if(logdata.size()>0){//如果存在下載記錄
                for(Map.Entry<Integer, Integer> entry : logdata.
                entrySet())//遍歷集合中的數(shù)據(jù)
                    data.put(entry.getKey(), entry.getValue()); //把各
                    條線(xiàn)程已經(jīng)下載的數(shù)據(jù)長(zhǎng)度放入data中
            }
 
            if(this.data.size()==this.threads.length){//如果已經(jīng)下載的
            數(shù)據(jù)的線(xiàn)程數(shù)和現(xiàn)在設(shè)置的線(xiàn)程數(shù)相同時(shí)則計(jì)算所有線(xiàn)程已經(jīng)下載的數(shù)據(jù)總
            長(zhǎng)度
                for(int i = 0; i < this.threads.length; i++){
                //遍歷每條線(xiàn)程已經(jīng)下載的數(shù)據(jù)
                    this.downloadedSize += this.data.get(i+1);
                    //計(jì)算已經(jīng)下載的數(shù)據(jù)之和
                }
                print("已經(jīng)下載的長(zhǎng)度"+ this.downloadedSize + "個(gè)字節(jié)");
                //打印出已經(jīng)下載的數(shù)據(jù)總和
            }
 
            this.block =(this.fileSize % this.threads.length)==0?
            this.fileSize / this.threads.length : this.fileSize /
            this.threads.length + 1;    //計(jì)算每條線(xiàn)程下載的數(shù)據(jù)長(zhǎng)度
        }else{
            print("服務(wù)器響應(yīng)錯(cuò)誤:" + conn.getResponseCode()+ conn.
            getResponseMessage());  //打印錯(cuò)誤
            throw new RuntimeException("server response error ");
            //拋出運(yùn)行時(shí)服務(wù)器返回異常
        }
    } catch(Exception e){
        print(e.toString());    //打印錯(cuò)誤
            throw new RuntimeException("Can't connection this url");
            //拋出運(yùn)行時(shí)無(wú)法連接的異常
}
    }
    /**
     * 獲取文件名
     */
    private String getFileName(HttpURLConnection conn){
        String filename = this.downloadUrl.substring(this.downloadUrl.
        lastIndexOf('/')+ 1);  //從下載路徑的字符串中獲取文件名稱(chēng)
 
        if(filename==null || "".equals(filename.trim())){//如果獲取不到文件名稱(chēng)
            for(int i = 0; ; i++){//無(wú)限循環(huán)遍歷
                String mine = conn.getHeaderField(i);  //從返回的流中獲取特定索引的頭字段值
                if(mine == null)break;  //如果遍歷到了返回頭末尾處,退出循環(huán)
                if("content-disposition".equals(conn.getHeaderField
                Key(i).toLowerCase())){//獲取content-disposition返回頭字段,里面可能會(huì)包含文件名
                    Matcher m = Pattern.compile(".*filename=(.*)").matcher
                   (mine.toLowerCase());   //使用正則表達(dá)式查詢(xún)文件名
                    if(m.find())return m.group(1); //如果有符合正則表達(dá)規(guī)則的字符串
                }
            }
            filename = UUID.randomUUID()+ ".tmp"; //由網(wǎng)卡上的標(biāo)識(shí)數(shù)字(每個(gè)網(wǎng)卡都有唯一的標(biāo)識(shí)號(hào))及CPU時(shí)鐘的唯一數(shù)字生成的一個(gè)16字節(jié)的二進(jìn)制數(shù)作為文件名
        }
        return filename;
    }
 
    /**
     *  開(kāi)始下載文件
     * @param listener監(jiān)聽(tīng)下載數(shù)量的變化,如果不需要了解實(shí)時(shí)下載的數(shù)量,可以設(shè)置為
    null
     * @return已下載文件大小
     * @throws Exception
     */
    public int download(DownloadProgressListener listener)throws
    Exception{  //進(jìn)行下載,并拋出異常給調(diào)用者,如果有異常的話(huà)
        try {
            RandomAccessFile randOut = new RandomAccessFile(this.saveFile,
            "rwd"); //The file is opened for reading and writing. Every
            change of the file's content must be written synchronously to
            the target device.
            if(this.fileSize>0)randOut.setLength(this.fileSize);
            //設(shè)置文件的大小
            randOut.close();    //關(guān)閉該文件,使設(shè)置生效
            URL url = new URL(this.downloadUrl);   //A URL instance
            specifies the location of a resource on the internet as specified
            by RFC 1738
            if(this.data.size()! = this.threads.length){   //如果原先未曾下載或者原先的下載線(xiàn)程數(shù)與現(xiàn)在的線(xiàn)程數(shù)不一致
                this.data.clear();  //Removes all elements from this Map,
                leaving it empty.
                for(int i = 0; i < this.threads.length; i++){//遍歷線(xiàn)程池
                    this.data.put(i+1, 0); //初始化每條線(xiàn)程已經(jīng)下載的數(shù)據(jù)長(zhǎng)度為0
                }
                this.downloadedSize = 0;    //設(shè)置已經(jīng)下載的長(zhǎng)度為0
            }
            for(int i = 0; i < this.threads.length; i++){//開(kāi)啟線(xiàn)程進(jìn)行下載
                int downloadedLength = this.data.get(i+1); //通過(guò)特定的線(xiàn)程ID獲取該線(xiàn)程已經(jīng)下載的數(shù)據(jù)長(zhǎng)度
                if(downloadedLength < this.block && this.downloadedSize <
                this.fileSize){//判斷線(xiàn)程是否已經(jīng)完成下載,否則繼續(xù)下載
                    this.threads[i] = new DownloadThread(this, url,
                    this.saveFile, this.block, this.data.get(i+1), i+1);
                    //初始化特定ID的線(xiàn)程
                    this.threads[i].setPriority(7); //設(shè)置線(xiàn)程的優(yōu)先級(jí),
                    Thread.NORM_PRIORITY = 5 Thread.MIN_PRIORITY = 1 Thread.
                MAX_PRIORITY = 10
                    this.threads[i].start();    //啟動(dòng)線(xiàn)程
                }else{
                    this.threads[i] = null; //表明在線(xiàn)程已經(jīng)完成下載任務(wù)
                }
            }
            fileService.delete(this.downloadUrl);  //如果存在下載記錄,刪除它們,然后重新添加
            fileService.save(this.downloadUrl, this.data); //把已經(jīng)下載的實(shí)時(shí)數(shù)據(jù)寫(xiě)入數(shù)據(jù)庫(kù)
            boolean notFinished = true; //下載未完成
            while(notFinished){// 循環(huán)判斷所有線(xiàn)程是否完成下載
                Thread.sleep(900);
                notFinished = false; //假定全部線(xiàn)程下載完成
                for(int i = 0; i < this.threads.length; i++){
                    if(this.threads[i] ! = null && ! this.threads[i].
                    isFinished()){//如果發(fā)現(xiàn)線(xiàn)程未完成下載
                        notFinished = true; //設(shè)置標(biāo)志為下載沒(méi)有完成
                        if(this.threads[i].getDownloadedLength()== -1){
                        //如果下載失敗,再重新在已經(jīng)下載的數(shù)據(jù)長(zhǎng)度的基礎(chǔ)上下載
                            this.threads[i] = new DownloadThread(this, url,
                            this.saveFile, this.block, this.data.get(i+1),
                            i+1);   //重新開(kāi)辟下載線(xiàn)程
                            this.threads[i].setPriority(7); //設(shè)置下載的優(yōu)
                            先級(jí)
                            this.threads[i].start();    //開(kāi)始下載線(xiàn)程
                        }
                    }
                }
                if(listener! =null)listener.onDownloadSize(this.
                downloadedSize); //通知目前已經(jīng)下載完成的數(shù)據(jù)長(zhǎng)度
            }
            if(downloadedSize == this.fileSize)fileService.delete
           (this.downloadUrl); //下載完成刪除記錄
        } catch(Exception e){
            print(e.toString());    //打印錯(cuò)誤
            throw new Exception("File downloads error"); //拋出文件下載異常
        }
        return this.downloadedSize;
    }
    /**
    * 獲取HTTP響應(yīng)頭字段
    * @param http  HttpURLConnection對(duì)象
    * @return  返回頭字段的LinkedHashMap
    */
   public static Map<String, String> getHttpResponseHeader(HttpURL
   Connection http){
        Map<String, String> header = new LinkedHashMap<String, String>();
        //使用LinkedHashMap保證寫(xiě)入和遍歷的時(shí)候的順序相同,而且允許空值存在
        for(int i = 0; ; i++){//此處為無(wú)限循環(huán),因?yàn)椴恢李^字段的數(shù)量
            String fieldValue = http.getHeaderField(i);
            //getHeaderField(int n)用于返回第n個(gè)頭字段的值。
 
            if(fieldValue == null)break; //如果第i個(gè)字段沒(méi)有值了,則表明頭字段部分已經(jīng)循環(huán)完畢,此處使用Break退出循環(huán)
            header.put(http.getHeaderFieldKey(i), fieldValue);
            //getHeaderFieldKey(int n)用于返回第n個(gè)頭字段的鍵。
        }
        return header;
   }
   /**
    * 打印HTTP頭字段
    * @param http HttpURLConnection對(duì)象
    */
   public static void printResponseHeader(HttpURLConnection http){
        Map<String, String> header = getHttpResponseHeader(http);
        //獲取HTTP響應(yīng)頭字段
        for(Map.Entry<String, String> entry : header.entrySet()){
        //使用For-Each循環(huán)的方式遍歷獲取的頭字段的值,此時(shí)遍歷的循序和輸入的順序相同
            String key = entry.getKey()! =null ? entry.getKey()+ ":" : "";
        //當(dāng)有鍵的時(shí)候截獲取鍵,如果沒(méi)有則為空字符串
            print(key+ entry.getValue());  //答應(yīng)鍵和值的組合
            }
        }
 
        /**
        * 打印信息
        * @param msg   信息字符串
        */
        private static void print(String msg){
            Log.i(TAG, msg);    //使用LogCat的Information方式打印信息
        }
    }

可以看出FileDownloader是使用了DownloadThread來(lái)執(zhí)行具體的下載工作的,Download Thread的建立如下圖所示。

單擊“Finish”按鈕完成創(chuàng)建,DownloadThread的具體內(nèi)容如下所示。

    package com.wangjialin.internet.service.downloader;
 
    import java.io.File;
    import java.io.InputStream;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.URL;
 
    import android.util.Log;
 
    /**
    * 下載線(xiàn)程,根據(jù)具體下載地址、數(shù)據(jù)保存到的文件、下載塊的大小、已經(jīng)下載的數(shù)據(jù)大小等信息進(jìn)行下載
    * @author Wang Jialin
    *
    */
   public class DownloadThread extends Thread {
        private static final String TAG = "DownloadThread";    //定義TAG,方便日期的打印輸出
        private File saveFile;          //下載的數(shù)據(jù)保存到的文件
        private URL downUrl;            //下載的URL
        private int block;              //每條線(xiàn)程下載的大小
        private int threadId = -1;      //初始化線(xiàn)程ID設(shè)置
        private int downloadedLength;  //該線(xiàn)程已經(jīng)下載的數(shù)據(jù)長(zhǎng)度
        private boolean finished = false;  //該線(xiàn)程是否完成下載的標(biāo)志
        private FileDownloader downloader; //文件下載器
 
        public DownloadThread(FileDownloader downloader, URL downUrl, File
        saveFile, int block, int downloadedLength, int threadId){
            this.downUrl = downUrl;
            this.saveFile = saveFile;
            this.block = block;
            this.downloader = downloader;
            this.threadId = threadId;
            this.downloadedLength = downloadedLength;
        }
 
        @Override
        public void run(){
            if(downloadedLength < block){//未下載完成
                try {
                    HttpURLConnection http =(HttpURLConnection)downUrl.
                    openConnection();   //開(kāi)啟HttpURLConnection連接
                    http.setConnectTimeout(5 * 1000); //設(shè)置連接超時(shí)時(shí)間為5秒鐘
                    http.setRequestMethod("GET");  //設(shè)置請(qǐng)求的方法為GET
                    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, */*"); //設(shè)置客戶(hù)端可以接受的返回?cái)?shù)據(jù)類(lèi)型
                    http.setRequestProperty("Accept-Language", "zh-CN");
                    //設(shè)置客戶(hù)端使用的語(yǔ)言為中文
                    http.setRequestProperty("Referer", downUrl.toString());
                    //設(shè)置請(qǐng)求的來(lái)源,便于對(duì)訪(fǎng)問(wèn)來(lái)源進(jìn)行統(tǒng)計(jì)
                    http.setRequestProperty("Charset", "UTF-8");   //設(shè)置通信編碼為UTF-8
                    int startPos = block *(threadId -1)+ downloadedLength;
                    //開(kāi)始位置
                    int endPos = block * threadId -1; //結(jié)束位置
                    http.setRequestProperty("Range", "bytes=" + startPos +
                    "-"+ endPos); //設(shè)置獲取實(shí)體數(shù)據(jù)的范圍,如果超過(guò)了實(shí)體數(shù)據(jù)的大小會(huì)自動(dòng)返回實(shí)際的數(shù)據(jù)大小
                    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)");
    //客戶(hù)端用戶(hù)代理
        http.setRequestProperty("Connection", "Keep-Alive");
        //使用長(zhǎng)連接
 
        InputStream inStream = http.getInputStream();  //獲取遠(yuǎn)程連接的輸入流
        byte[] buffer = new byte[1024]; //設(shè)置本地?cái)?shù)據(jù)緩存的大小為1MB
        int offset = 0; //設(shè)置每次讀取的數(shù)據(jù)量
        print("Thread " + this.threadId + " starts to download from
        position "+ startPos);  //打印該線(xiàn)程開(kāi)始下載的位置
        RandomAccessFile threadFile = new RandomAccessFile
       (this.saveFile, "rwd"); //If the file does not already
        exist then an attempt will be made to create it and it require
        that every update to the file's content be written
        synchronously to the underlying storage device.
        threadFile.seek(startPos); //文件指針指向開(kāi)始下載的位置
        while(! downloader.getExited()&&(offset = inStream.
        read(buffer, 0, 1024))! = -1){//但用戶(hù)沒(méi)有要求停止下載,同時(shí)沒(méi)有到達(dá)請(qǐng)求數(shù)據(jù)的末尾時(shí)候會(huì)一直循環(huán)讀取數(shù)據(jù)
            threadFile.write(buffer, 0, offset);   //直接把數(shù)據(jù)寫(xiě)到文件中
            downloadedLength += offset; //把新下載的已經(jīng)寫(xiě)到文件中的數(shù)據(jù)加入到下載長(zhǎng)度中
            downloader.update(this.threadId, downloadedLength);
            //把該線(xiàn)程已經(jīng)下載的數(shù)據(jù)長(zhǎng)度更新到數(shù)據(jù)庫(kù)和內(nèi)存哈希表中
            downloader.append(offset); //把新下載的數(shù)據(jù)長(zhǎng)度加入到已經(jīng)下載的數(shù)據(jù)總長(zhǎng)度中
        }//該線(xiàn)程下載數(shù)據(jù)完畢或者下載被用戶(hù)停止
        threadFile.close(); //Closes this random access file stream
        and releases any system resources associated with the stream.
        inStream.close();   //Concrete implementations of this
        class should free any resources during close
        if(downloader.getExited())
        {
            print("Thread " + this.threadId + " has been paused");
        }
        else
        {
            print("Thread " + this.threadId + " download finish");
        }
 
        this.finished = true;   //設(shè)置完成標(biāo)志為true,無(wú)論是下載完成還是用戶(hù)主動(dòng)中斷下載
    } catch(Exception e){//出現(xiàn)異常
        this.downloadedLength = -1; //設(shè)置該線(xiàn)程已經(jīng)下載的長(zhǎng)度為-1
        print("Thread "+ this.threadId+ ":"+ e);  //打印出異常信息
    }
}
}
/**
        * 打印信息
        * @param msg   信息
        */
       private static void print(String msg){
            Log.i(TAG, msg);    //使用Logcat的Information方式打印信息
       }
 
       /**
        * 下載是否完成
        * @return
        */
       public boolean isFinished(){
            return finished;
       }
 
       /**
        * 已經(jīng)下載的內(nèi)容大小
        * @return如果返回值為-1,代表下載失敗
        */
       public long getDownloadedLength(){
            return downloadedLength;
       }
    }

同時(shí)在FileDownloader中需要使用DownloadProgressListener進(jìn)行進(jìn)度監(jiān)聽(tīng),這是一個(gè)接口,如下圖所示。

DownloadProgressListener的具體內(nèi)容如下所示。

    package com.wangjialin.internet.service.downloader;
 
    /**
     * 下載進(jìn)度監(jiān)聽(tīng)器
     * @author Wang Jialin
     *
     */
    public interface DownloadProgressListener {
        /**
        * 下載進(jìn)度監(jiān)聽(tīng)方法,獲取和處理下載點(diǎn)數(shù)據(jù)的大小
        * @param size數(shù)據(jù)大小
        */
        public void onDownloadSize(int size);
    }

(5)實(shí)現(xiàn)主Activity類(lèi)。

MultipleThreadContinuableDownloaderForAndroid4Activity具體的內(nèi)容如下所示。

    package com.wangjialin.internet.multipleThreadContinuableDownloaderFor
    Android4;
    import java.io.File;
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.Message;
    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 com.eric.net.download.DownloadProgressListener;
    import com.eric.net.download.FileDownloader;
    /**
     * 主界面,負(fù)責(zé)下載界面的顯示、與用戶(hù)交互、響應(yīng)用戶(hù)事件等
     * @author Wang Jialin
     *
     */
    public class MultipleThreadContinuableDownloaderForAndroid4Activity
    extends Activity {
        private static final int PROCESSING = 1;   //正在下載實(shí)時(shí)數(shù)據(jù)傳輸Message標(biāo)志
        private static final int FAILURE = -1;     //下載失敗時(shí)的Message標(biāo)志
 
        private EditText pathText;                  //下載輸入文本框
        private TextView resultView;                //現(xiàn)在進(jìn)度顯示百分比文本框
    private Button downloadButton;     //下載按鈕,可以觸發(fā)下載事件
    private Button stopbutton;         //停止按鈕,可以停止下載
    private ProgressBar progressBar;   //下載進(jìn)度條,實(shí)時(shí)圖形化的顯示進(jìn)度信息
    //hanlder對(duì)象的作用是向創(chuàng)建Hander對(duì)象所在的線(xiàn)程所綁定的消息隊(duì)列發(fā)送消息并處理消息
    private Handler handler = new UIHander();
 
    private final class UIHander extends Handler{
     /**
      * 系統(tǒng)會(huì)自動(dòng)調(diào)用的回調(diào)方法,用于處理消息事件
      * Mesaage一般會(huì)包含消息的標(biāo)志和消息的內(nèi)容以及消息的處理器Handler
      */
        public void handleMessage(Message msg){
            switch(msg.what){
            case PROCESSING:        //下載時(shí)
                int size = msg.getData().getInt("size");   //從消息中獲取已經(jīng)下載的數(shù)據(jù)長(zhǎng)度
                progressBar.setProgress(size); //設(shè)置進(jìn)度條的進(jìn)度
                float num =(float)progressBar.getProgress()/(float)
                progressBar.getMax();   //計(jì)算已經(jīng)下載的百分比,此處需要轉(zhuǎn)換為浮點(diǎn)數(shù)計(jì)算
                int result =(int)(num * 100); //把獲取的浮點(diǎn)數(shù)計(jì)算結(jié)構(gòu)轉(zhuǎn)化為整數(shù)
                resultView.setText(result+ "%");   //把下載的百分比顯示在界面顯示控件上
                if(progressBar.getProgress()== progressBar.getMax()){
                //當(dāng)下載完成時(shí)
                    Toast.makeText(getApplicationContext(), R.string.
                    success, Toast.LENGTH_LONG).show(); //使用Toast技術(shù),提示用戶(hù)下載完成
                }
                break;
 
            case -1:    //下載失敗時(shí)
                Toast.makeText(getApplicationContext(), R.string.error,
                Toast.LENGTH_LONG).show(); //提示用戶(hù)下載失敗
                break;
            }
        }
    }
 
    @Override
    public void onCreate(Bundle savedInstanceState){  //應(yīng)用程序啟動(dòng)時(shí)會(huì)首先調(diào)用且在應(yīng)用程序整個(gè)生命周期中只會(huì)調(diào)用一次,適合于初始化工作
        super.onCreate(savedInstanceState); //使用父類(lèi)的onCreate用做屏幕主界面的底層和基本繪制工作
        setContentView(R.layout.main);  //根據(jù)XML界面文件設(shè)置主界面
 
        pathText =(EditText)this.findViewById(R.id.path); //獲取下載URL
        的文本輸入框?qū)ο?
        resultView =(TextView)this.findViewById(R.id.resultView);
        //獲取顯示下載百分比文本控件對(duì)象
        downloadButton =(Button)this.findViewById(R.id.downloadbutton);
        //獲取下載按鈕對(duì)象
        stopbutton =(Button)this.findViewById(R.id.stopbutton);
        //獲取停止下載按鈕對(duì)象
        progressBar =(ProgressBar)this.findViewById(R.id.progressBar);
        //獲取進(jìn)度條對(duì)象
        ButtonClickListener listener = new ButtonClickListener();
        //聲明并定義按鈕監(jiān)聽(tīng)器對(duì)象
        downloadButton.setOnClickListener(listener); //設(shè)置下載按鈕的監(jiān)聽(tīng)器對(duì)象
        stopbutton.setOnClickListener(listener); //設(shè)置停止下載按鈕的監(jiān)聽(tīng)器對(duì)象
    }
    /**
    * 按鈕監(jiān)聽(tīng)器實(shí)現(xiàn)類(lèi)
    * @author Wang Jialin
    *
    */
    private final class ButtonClickListener implements View.
    OnClickListener{
        public void onClick(View v){  //該方法在注冊(cè)了該按鈕監(jiān)聽(tīng)器的對(duì)象被單
        擊時(shí)會(huì)自動(dòng)調(diào)用,用于響應(yīng)單擊事件
            switch(v.getId()){    //獲取單擊對(duì)象的ID
            case R.id.downloadbutton:  //當(dāng)單擊下載按鈕時(shí)
                String path = pathText.getText().toString(); //獲取下載路徑
                if(Environment.getExternalStorageState().equals
               (Environment.MEDIA_MOUNTED)){  //獲取SDCard是否存在,當(dāng)SDCard存在時(shí)
                    File saveDir = Environment.getExternalStorage
                    Directory();    //獲取SDCard根目錄文件
                    File saveDir = Environment.getExternalStoragePublic
                    Directory(
                            Environment.DIRECTORY_MOVIES);
                    /*File saveDir = Environment.getExternalStoragePublic
                    Directory(
                            Environment.DIRECTORY_MUSIC); */
 
                    File saveDir =  getApplicationContext().getExternal
                    FilesDir(Environment.DIRECTORY_MOVIES);
                    download(path, saveDir);    //下載文件
                }else{  //當(dāng)SDCard不存在時(shí)
                    Toast.makeText(getApplicationContext(), R.string.
                    sdcarderror, Toast.LENGTH_LONG).show(); //提示用戶(hù)SDCard
                    不存在
                }
                downloadButton.setEnabled(false);  //設(shè)置下載按鈕不可用
                stopbutton.setEnabled(true);   //設(shè)置停止下載按鈕可用
                break;
            case R.id.stopbutton:   //當(dāng)單擊停止下載按鈕時(shí)
                exit(); //停止下載
                downloadButton.setEnabled(true);   //設(shè)置下載按鈕可用
                stopbutton.setEnabled(false);  //設(shè)置停止按鈕不可用
                break;
            }
        }
 
        ///////////////////////////////////////////////////////////////
        //由于用戶(hù)的輸入事件(單擊button,觸摸屏幕…)是由主線(xiàn)程負(fù)責(zé)處理的,如果主線(xiàn)程
        處于工作狀態(tài)
        //此時(shí)用戶(hù)產(chǎn)生的輸入事件如果沒(méi)能在5秒內(nèi)得到處理,系統(tǒng)就會(huì)報(bào)"應(yīng)用無(wú)響應(yīng)"錯(cuò)誤
        //所以在主線(xiàn)程里不能執(zhí)行一件比較耗時(shí)的工作,否則會(huì)因主線(xiàn)程阻塞而無(wú)法處理用戶(hù)
        的輸入事件
        //導(dǎo)致"應(yīng)用無(wú)響應(yīng)"錯(cuò)誤的出現(xiàn),耗時(shí)的工作應(yīng)該在子線(xiàn)程里執(zhí)行
        ///////////////////////////////////////////////////////////////
 
        private DownloadTask task; //聲明下載執(zhí)行者
        /**
        * 退出下載
        */
        public void exit(){
            if(task! =null)task.exit(); //如果有下載對(duì)象時(shí),退出下載
        }
        /**
        * 下載資源,生命下載執(zhí)行者并開(kāi)辟線(xiàn)程開(kāi)始現(xiàn)在
        * @param path  下載的路徑
        * @param saveDir   保存文件
        */
        private void download(String path, File saveDir){//此方法運(yùn)行在主線(xiàn)程
            task = new DownloadTask(path, saveDir); //實(shí)例化下載任務(wù)
            new Thread(task).start();  //開(kāi)始下載
        }
 
        /*
        * UI控件畫(huà)面的重繪(更新)是由主線(xiàn)程負(fù)責(zé)處理的,如果在子線(xiàn)程中更新UI控件的
        值,更新后的值不會(huì)重繪到屏幕上
        * 一定要在主線(xiàn)程里更新UI控件的值,這樣才能在屏幕上顯示出來(lái),不能在子線(xiàn)程中
        更新UI控件的值
        */
        private final class DownloadTask implements Runnable{
            private String path;    //下載路徑
            private File saveDir;   //下載到保存到的文件
            private FileDownloader loader; //文件下載器(下載線(xiàn)程的容器)
            /**
            * 構(gòu)造方法,實(shí)現(xiàn)變量初始化
            * @param path  下載路徑
            * @param saveDir   下載要保存到的文件
            */
            public DownloadTask(String path, File saveDir){
                this.path = path;
                this.saveDir = saveDir;
            }
 
            /**
             * 退出下載
             */
            public void exit(){
                if(loader! =null)loader.exit(); //如果下載器存在的話(huà)則退出下載
            }
            DownloadProgressListener downloadProgressListener = new
            DownloadProgressListener(){   //開(kāi)始下載,并設(shè)置下載的監(jiān)聽(tīng)器
V
                /**
                * 下載的文件長(zhǎng)度會(huì)不斷的被傳入該回調(diào)方法
                */
                public void onDownloadSize(int size){
                    Message msg = new Message(); //新建立一個(gè)Message對(duì)象
                    msg.what = PROCESSING;      //設(shè)置ID為1;
                    msg.getData().putInt("size", size); //把文件下載的size設(shè)置進(jìn)Message對(duì)象
                    handler.sendMessage(msg); //通過(guò)handler發(fā)送消息到消息隊(duì)列
                }
            };
            /**
             * 下載線(xiàn)程的執(zhí)行方法,會(huì)被系統(tǒng)自動(dòng)調(diào)用
             */
            public void run(){
                try {
                    loader = new FileDownloader(getApplicationContext(),
                    path, saveDir, 3);  //初始化下載
                    progressBar.setMax(loader.getFileSize());  //設(shè)置進(jìn)度條的最大刻度
                    loader.download(downloadProgressListener);
 
                } catch(Exception e){
                    e.printStackTrace();
                    handler.sendMessage(handler.obtainMessage(FAILURE));
                    //下載失敗時(shí)向消息隊(duì)列發(fā)送消息
                    /*Message message = handler.obtainMessage();
                    message.what = FAILURE; */
                }
            }
        }
    }
 
  }

(6)因?yàn)橐L(fǎng)問(wèn)網(wǎng)絡(luò),同時(shí)要在SDCard中創(chuàng)建文件,所示要在AndroidManifest.xml中加入如下權(quán)限信息。

    <! -- 訪(fǎng)問(wèn)internet權(quán)限 -->
        <uses-permission android:name="android.permission.INTERNET"/>
 
        <! -- 在SDCard中創(chuàng)建與刪除文件權(quán)限 -->
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_
        FILESYSTEMS"/>
        <! -- 往SDCard寫(xiě)入數(shù)據(jù)權(quán)限 -->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_
        STORAGE"/>

此時(shí)的AndroidManifest.xml的具體內(nèi)容如下所示。

    <? xml version="1.0" encoding="utf-8"? >
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.wangjialin.internet.multipleThreadContinuableDownloader
        ForAndroid4"
        android:versionCode="1"
        android:versionName="1.0" >
 
        <uses-sdk android:minSdkVersion="8" />
V
        <application
          android:icon="@drawable/ic_launcher"
          android:label="@string/app_name" >
          <! -- 主Acitivity,提供用戶(hù)操作的界面和接口 -->
          <activity
              android:label="@string/app_name"
              android:name=".MultipleThreadContinuableDownloaderForAndroid4
              Activity" >
              <intent-filter >
                  <! -- 應(yīng)用啟動(dòng)時(shí)啟動(dòng)入口Activity -->
                  <action android:name="android.intent.action.MAIN" />
                  <! -- 應(yīng)用顯示在應(yīng)用程序列表-->
                  <category android:name="android.intent.category.LAUNCHER" />
              </intent-filter>
          </activity>
        </application>
 
        <! -- 訪(fǎng)問(wèn)internet權(quán)限 -->
        <uses-permission android:name="android.permission.INTERNET"/>
 
        <! -- 在SDCard中創(chuàng)建與刪除文件權(quán)限 -->
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_
        FILESYSTEMS"/>
        <! -- 往SDCard寫(xiě)入數(shù)據(jù)權(quán)限 -->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_
        STORAGE"/>
 
    </manifest>

(7)安裝并運(yùn)行Android多線(xiàn)程斷點(diǎn)續(xù)傳下載程序,安裝并運(yùn)行后的初始界面如下圖(左)所示。單擊“Start Downloading”開(kāi)始進(jìn)行下載,此時(shí)的狀態(tài)如下圖(右)所示。

當(dāng)下載完成時(shí),出現(xiàn)如下圖所示的視圖。

至此,Android多線(xiàn)程斷點(diǎn)續(xù)傳下載項(xiàng)目完成。

主站蜘蛛池模板: 惠来县| 同江市| 屏东市| 图们市| 大名县| 台湾省| 恩施市| 南漳县| 炎陵县| 古蔺县| 云浮市| 靖江市| 金山区| 利辛县| 江永县| 会宁县| 青海省| 绿春县| 武川县| 清新县| 庆元县| 革吉县| 郸城县| 阿图什市| 凤阳县| 湘阴县| SHOW| 读书| 宜丰县| 乐业县| 那坡县| 宜良县| 夹江县| 博爱县| 临朐县| 金秀| 师宗县| 东莞市| 凤台县| 图木舒克市| 九江市|