Android實戰(zhàn):多線程多文件斷點續(xù)傳下載+通知欄控制

本文續(xù)接我上一篇文章《Android實戰(zhàn):簡易斷點續(xù)傳下載器實現(xiàn)》
鏈接地址:http://www.reibang.com/p/5b2e22c42467

本項目Github地址:
https://github.com/liaozhoubei/MultiDownload

說到多線程下載据忘,也許大家會覺得很迷惑穆刻,但多線程的原理實際上與單線程下載的原理并無區(qū)別耳标。

多線程下載只需要確定好下載一個文件需要多少個線程逢勾,一般來說最好為3條線程,因為線程過多會占用系統(tǒng)資源粉铐,而且線程間的相互競爭也會導致下載變慢疼约。

其次下載的時候?qū)⑽募指顬槿荩僭O(shè)用3條線程下載)下載,在java中就要用到上次提到的RandomAccessFile這個API蝙泼,它的開始結(jié)束為止用以下代碼確定:

conn.setRequestProperty("Range", "bytes=" + start + "-" + end)

最后就是斷點續(xù)傳了忆谓,只需要才程序停止下載的時候記錄下最后的下載位置就好了,當下次下載的時候從當前停止的位置開始下載踱承。

OK,那么現(xiàn)在就開始我們的多線程下載+通知欄控制的實戰(zhàn)之旅吧!

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

我們這次要做的并非簡單的多線程下載哨免,而是要做到多文件多線程的同時下載

重寫布局

這次下載需要展示多個下載的文件茎活,所以使用ListView控件,界面效果如下

下載界面.png

每個ListView的item都很簡單琢唾,基本上只需要將上次寫的下載界面搬過來就好了载荔。
新建一個Layout,命名為item采桃,將中的界面布局剪切過來懒熙,然后在中設(shè)置一個ListView空間。
activity_main.xml代碼如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"    >

<ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
</RelativeLayout>

至于Item的布局普办,為了省功夫工扎,就不寫了,大家可以去我的Github下載名為MultiDownload的項目來參考衔蹲。

建立FileAdapter類

布局寫好了肢娘,但是ListView總是要有個Adapter類來綁定視圖,填充布局的不是么舆驶,所以接下來就開始寫FileAdapter了橱健。
話說回來,ListView真的是個很重要的空間沙廉,不熟悉的小伙伴抓緊多看看怎么做吧拘荡。
創(chuàng)建一個繼承自BaseAdapter類的FileAdapter,里面擁有以下三個成員變量:

private Context mContext = null;
private List<FileInfo> mFilelist = null;
private LayoutInflater layoutInflater;

然后重寫構(gòu)造函數(shù):

    public FileAdapter(Context mContext, List<FileInfo> mFilelist) {
    this.mContext = mContext;
    this.mFilelist = mFilelist;
    layoutInflater = LayoutInflater.from(mContext);
}

再將繼承的getCount/getItem/getItemId三個方法的返回值寫好撬陵,用于ListView找到各自的Item珊皿。

接下來就是重頭戲网缝,重寫getView方法了!
我們先定義一個靜態(tài)的ViewHolder內(nèi)部類亮隙,這樣在ListView屬性的時候才不會重復創(chuàng)建對象途凫,減輕內(nèi)存壓力,這個谷歌官方推薦的哦溢吻!

static class ViewHolder {
    TextView textview;
    Button startButton;
    Button stopButton;
    ProgressBar progressBar;
}

然后在getView中綁定布局item中的各個控件维费,并且設(shè)置按鈕的點擊事件,getView代碼如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder = null;
    final FileInfo mFileInfo = mFilelist.get(position);
    if (convertView == null) {
        convertView = layoutInflater.inflate(R.layout.item, null);
        viewHolder = new ViewHolder();
        viewHolder.textview = (TextView) convertView.findViewById(R.id.file_textview);
        viewHolder.startButton = (Button) convertView.findViewById(R.id.start_button);
        viewHolder.stopButton = (Button) convertView.findViewById(R.id.stop_button);
        viewHolder.progressBar = (ProgressBar) convertView.findViewById(R.id.progressBar2);
        viewHolder.textview.setText(mFileInfo.getFileName());
        viewHolder.progressBar.setMax(100);
        viewHolder.startButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(mContext, DownloadService.class);                    intent.setAction(DownloadService.ACTION_START);
                intent.putExtra("fileInfo", mFileInfo);
                mContext.startService(intent);
            }
        });
        viewHolder.stopButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(mContext, DownloadService.class);
                intent.setAction(DownloadService.ACTION_STOP);
                intent.putExtra("fileInfo", mFileInfo);
                mContext.startService(intent);
            }
        });
        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }

    viewHolder.progressBar.setProgress(mFileInfo.getFinished());

    return convertView;
}

最后再新建一個更新進度條的方法促王,在獲得文件ID犀盟,和當前進度之后,直接更新進度條蝇狼,代碼如下:

public void updataProgress(int id, int progress) {
    FileInfo info = mFilelist.get(id);
    info.setFinished(progress);
    notifyDataSetChanged();
}

好了阅畴,整個FileAdapter類就這樣寫完了!下面我們來修改一下MainActivity中的代碼吧迅耘。

修改MainActivity代碼

由于我們在FileAdapter中已經(jīng)將布局寫好了贱枣,而且點擊事件和更新進度也是在FileAdapter中進行的,因此不需要在MainActivity中綁定按鍵了颤专,現(xiàn)在可以將有關(guān)Button和ProgressBar的代碼都刪掉纽哥。然后在配置好ListView控件就可以了,代碼如下:

private ListView listView;
private List<FileInfo> mFileList;
private FileAdapter mAdapter;
private String urlone = "http://www.imooc.com/mobile/imooc.apk";
private String urltwo = "http://www.imooc.com/download/Activator.exe";
private String urlthree = "http://s1.music.126.net/download/android/CloudMusic_3.4.1.133604_official.apk";
private String urlfour = "http://study.163.com/pub/study-android-official.apk";
private UIRecive mRecive;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);     
    // 初始化控件
    listView = (ListView) findViewById(R.id.list_view);
    mFileList = new ArrayList<FileInfo>();
    // 初始化文件對象
    FileInfo fileInfo1 = new FileInfo(0, urlone, getfileName(urlone), 0, 0);
    FileInfo fileInfo2 = new FileInfo(1, urltwo, getfileName(urltwo), 0, 0);
    FileInfo fileInfo3 = new FileInfo(2, urlthree, getfileName(urlthree), 0, 0);
    FileInfo fileInfo4 = new FileInfo(3, urlfour, getfileName(urlfour), 0, 0);
    mFileList.add(fileInfo1);
    mFileList.add(fileInfo2);
    mFileList.add(fileInfo3);
    mFileList.add(fileInfo4);
    mAdapter = new FileAdapter(this, mFileList);
    listView.setAdapter(mAdapter);      
    mRecive = new UIRecive();
    // 注冊廣播接收器
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(DownloadService.ACTION_UPDATE);
    intentFilter.addAction(DownloadService.ACTION_FINISHED);
    intentFilter.addAction(DownloadService.ACTION_START);
    registerReceiver(mRecive, intentFilter);
}

現(xiàn)在整個視圖終于搞好了栖秕,可以啟動應用春塌,看看視圖是否顯示正常了。當然啦簇捍,下載還沒吧下載的代碼改好只壳,現(xiàn)在我們就來修改下載代碼吧。

修改DownloadTask代碼

既然是多線程下載暑塑,那么我們便要在下載的時候設(shè)置好線程數(shù)吼句,首先添加一個int類型的threadCount的參數(shù)代碼代表線程數(shù),初始值為1事格。然后在DownloadTask的構(gòu)造函數(shù)中添加threadCount變量命辖,這樣在開始下載的時候就能夠確定需要多少個線程下載,代碼如下:

public DownloadTask(Context comtext, FileInfo fileInfo, int threadCount) {
    super();
    this.mThreadCount = threadCount;
    this.mComtext = comtext;
    this.mFileInfo = fileInfo;
    this.mDao = new ThreadDAOImple(mComtext);
}

然后我們要確認每個線程需要從文件的哪里開始下載分蓖。假設(shè)文件長度為10尔艇,分為3條線程下載,那么0-2是一份么鹤,3-5是一份终娃,6-8是一份(java中從0開始),那么多出的一份怎么辦蒸甜?當然是在計算時棠耕,如果最后多出來余佛,歸最后的拿份,也就是6-9了窍荧。
我們將每個線程需要下載多少長度的文件計算好辉巡,就可以讓每個文件開始自己的下載任務了,代碼如下:

public void download() {
    // 從數(shù)據(jù)庫中獲取下載的信息
    List<ThreadInfo> list = mDao.queryThreads(mFileInfo.getUrl());
    if (list.size() == 0) {
        int length = mFileInfo.getLength();
        int block = length / mThreadCount;
        for (int i = 0; i < mThreadCount; i++) {
            // 劃分每個線程開始下載和結(jié)束下載的位置
            int start = i * block;
            int end = (i + 1) * block - 1;
            if (i == mThreadCount - 1) {
                end = length - 1;
            }
            ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), start, end, 0);
            list.add(threadInfo);
        }
    }
    mThreadlist = new ArrayList<DownloadThread>();
    for (ThreadInfo info : list) {
        DownloadThread thread = new DownloadThread(info);
        // 使用線程池執(zhí)行下載任務
        DownloadTask.sExecutorService.execute(thread);
        mThreadlist.add(thread);
        // 如果數(shù)據(jù)庫不存在下載信息蕊退,添加下載信息
        mDao.insertThread(info);
    }
}

需要注意的是啟動下載線程的時候在這里沒有直接使用Thread.start()來啟動郊楣,而是使用了線程池,因為線程過多瓤荔,使用線程池便于管理净蚤。使用線程池非常簡單,只需要在開始的時候定義一個線程池的成員變量:

public static ExecutorService sExecutorService = Executors.newCachedThreadPool();

然后使用

  sExecutorService.execute(需要啟動的線程);

這樣就能夠啟動線程了输硝,是一種很簡單的用法今瀑。
然后我們還要定義一個同步方法忘晤,判斷一個文件的全部線程是否都下載完成又谋,如果下載完成就彈出Toast

public synchronized void checkAllFinished() {
    boolean allFinished = true;
    for (DownloadThread thread : mThreadlist) {
        if (!thread.isFinished) {
            allFinished = false;
            break;
        }
    }
    if (allFinished == true) {
        // 下載完成后,刪除數(shù)據(jù)庫信息
        mDao.deleteThread(mFileInfo.getUrl());
        // 通知UI哪個線程完成下載
        Intent intent = new Intent(DownloadService.ACTION_FINISHED);
        intent.putExtra("fileInfo", mFileInfo);
        mComtext.sendBroadcast(intent);
    }
}

最后修改一下run方法中的代碼矾削,前面我們保存斷點下載是整個文件的進度郎逃,現(xiàn)在保存下載是單個線程的進度砾医,同時我們還要判斷是否整個文件的所有線程是否完成的checkAllFinished方法添加進去,所以將部分代碼修改為:

                // 定義UI刷新時間
                long time = System.currentTimeMillis();
                while ((len = is.read(bt)) != -1) {
                    raf.write(bt, 0, len);
                    // 累計整個文件完成進度
                    mFinished += len;
                    // 累加每個線程完成的進度
                    threadInfo.setFinished(threadInfo.getFinished() + len);
                    // 設(shè)置爲500毫米更新一次
                    if (System.currentTimeMillis() - time > 1000) {
                        time = System.currentTimeMillis();
                            // 發(fā)送已完成多少
                            intent.putExtra("finished", mFinished * 100 / mFileInfo.getLength());
                            // 表示正在下載文件的id
                            intent.putExtra("id", mFileInfo.getId());
                            Log.i("test", mFinished * 100 / mFileInfo.getLength() + "");
                            // 發(fā)送廣播給Activity
                            mComtext.sendBroadcast(intent);
                    }
                    if (mIsPause) {
                        mDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
                        return;
                    }
                }
            }
            // 標識線程是否執(zhí)行完畢
            isFinished = true;
            // 判斷是否所有線程都執(zhí)行完畢
            checkAllFinished();

好了衣厘,這樣整個DownloadTask的代碼就修改完了,接下來我們開始修改DownloadService中的代碼了压恒。

DownloadService代碼修改

在之前的代碼中是使用單線程下載影暴,現(xiàn)成我們設(shè)置成可以定義多少條線程下載,因為在Handler中的啟動下載的時候需要添加線程數(shù)探赫。
同時我們要在DownloadService定義一個Map的集合型宙,用于管理下載線程,代碼如下:

private Map<Integer, DownloadTask> mTasks = new LinkedHashMap<Integer, DownloadTask>();

修改Handler代碼伦吠,在啟動下載線程時妆兑,添加進下載集合中,代碼如下:

        switch (msg.what) {
        case MSG_INIT:
            FileInfo fileInfo = (FileInfo) msg.obj;
            Log.i("test", "INIT:" + fileInfo.toString());
            // 獲取FileInfo對象毛仪,開始下載任務
            DownloadTask task = new DownloadTask(DownloadService.this, fileInfo, 3);
            task.download();
            // 把下載任務添加到集合中
            mTasks.put(fileInfo.getId(), task);         
            break;
        }

最后要修改onStartCommand代碼搁嗓,當我們點擊停止的時候,要停止一個文件中每一個正在運行中的線程箱靴,在點擊開始的時候要用線程池啟動下載腺逛,代碼如下:

    if (ACTION_START.equals(intent.getAction())) {
        FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
        Log.i("test", "START" + fileInfo.toString());
        InitThread initThread = new InitThread(fileInfo);
        DownloadTask.sExecutorService.execute(initThread);          
    } else if (ACTION_STOP.equals(intent.getAction())) {
        FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
        DownloadTask task = mTasks.get(fileInfo.getId());
        if (task != null) {
            // 停止下載任務
            task.mIsPause = true;
        }
    }

這樣DownloadService中的代碼也修改完了,只剩下最后修改MainActivity中的代碼了

修改MainActivity代碼

這次我們只需修改廣播接收者的代碼就可以了衡怀,但我們更新進度的時候不能按照單一文件的時候更新了棍矛,我們必須按照文件的id來更新進度安疗,這時我們可以調(diào)用FileAdapter中的updataProgress方法(前面自己定義的)便可以更新。同時我們還要在文件完成時彈出文件已完成的Toast够委,因此要給廣播增加Action荐类。
在onCreate中修改注冊廣播的代碼:

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(DownloadService.ACTION_UPDATE);
    intentFilter.addAction(DownloadService.ACTION_FINISHED);
    registerReceiver(mRecive, intentFilter);

修改廣播接收者的代碼:

    public void onReceive(Context context, Intent intent) {
        if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) {
            // 更新進度條的時候
            int finished = intent.getIntExtra("finished", 0);
            int id = intent.getIntExtra("id", 0);
            mAdapter.updataProgress(id, finished);              
        } else if (DownloadService.ACTION_FINISHED.equals(intent.getAction())){
            // 下載結(jié)束的時候
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            mAdapter.updataProgress(fileInfo.getId(), 0);
            Toast.makeText(MainActivity.this, mFileList.get(fileInfo.getId()).getFileName() + "下載完畢", Toast.LENGTH_SHORT).show();               
        }
}

大功告成!一個多線程多文件下載的項目就這樣解決了茁帽,滿滿的成就感對不_玉罐。
但是且慢,我們還有一個通知欄沒解決脐雪,等我們把通知欄做好再高興也不遲

Notification通知欄的使用

在低版本中厌小,Android使用通知欄是Notification這個API,但是在高版本中使用的是Notification.Builder這個API战秋,兩種區(qū)別不大璧亚,在這里使用的是低版本的Notification。

Notificaiton布局

使用通知欄必須要有個布局脂信,但我們下拉通知欄的時候癣蟋,如播放音樂,我們可以看到有上一首狰闪、下一首等按鍵疯搅。所以就像使用ListView一樣,我們首先要定義自己的通知欄布局埋泵,布局效果如下

Notification.png

這是一個很簡單的布局幔欧,一個TextView,一個ProgressBar丽声,兩個Button就解決了礁蔗。需要說明的是,寫這個布局與普通布局并無不同雁社,布局代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
    android:id="@+id/file_textview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >
    <ProgressBar
        android:id="@+id/progressBar2"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2" />
    <Button
        android:id="@+id/start_button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="start" />
    <Button
        android:id="@+id/stop_button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="stop" />
</LinearLayout>
</LinearLayout>

布局寫好了浴井,我們來定義如何操作這個視圖吧!

NotificationUtil工具類

在通知欄中我們要向Activity一樣找到這個布局霉撵,然后操縱它磺浙。但是不同的是通知欄使用的是RemoteViews遠程視圖這個API來控制視圖的。

另外在新建Notification對象的時候徒坡,要設(shè)置好幾個參數(shù)撕氧,這些參數(shù)是顯示在狀態(tài)欄中的。當QQ或者其他來通知的時候喇完,許多時候并不是直接彈出對話框的呵曹,而是在狀態(tài)欄中彈出一個提示,這就是Notification設(shè)置的參數(shù)。

好了Notification介紹到這里奄喂,下面就是NotificationUtil的完整代碼:

public class NotificationUtil {
private Context mContext;
private NotificationManager mNotificationManager = null;
private Map<Integer, Notification> mNotifications = null;   
public NotificationUtil(Context context) {
    this.mContext = context;
    // 獲得系統(tǒng)通知管理者
    mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    // 創(chuàng)建通知的集合
    mNotifications = new HashMap<Integer, Notification>();
}
/**
 * 顯示通知欄
 * @param fileInfo
 */
public void showNotification(FileInfo fileInfo) {
    // 判斷通知是否已經(jīng)顯示
    if(!mNotifications.containsKey(fileInfo.getId())){
        Notification notification = new Notification();
        notification.tickerText = fileInfo.getFileName() + "開始下載";
        notification.when = System.currentTimeMillis();
        notification.icon = R.drawable.ic_launcher;
        notification.flags = Notification.FLAG_AUTO_CANCEL;
        
        // 點擊通知之后的意圖
        Intent intent = new Intent(mContext, MainActivity.class);
        PendingIntent pd = PendingIntent.getActivity(mContext, 0, intent, 0);
        notification.contentIntent = pd;
        
        // 設(shè)置遠程試圖RemoteViews對象
        RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.notification);
        // 控制遠程試圖铐殃,設(shè)置開始點擊事件
        Intent intentStart = new Intent(mContext, DownloadService.class);
        intentStart.setAction(DownloadService.ACTION_START);
        intentStart.putExtra("fileInfo", fileInfo);
        PendingIntent piStart = PendingIntent.getService(mContext, 0, intentStart, 0);
        remoteViews.setOnClickPendingIntent(R.id.start_button, piStart);
        
        // 控制遠程試圖,設(shè)置結(jié)束點擊事件
        Intent intentStop = new Intent(mContext, DownloadService.class);
        intentStop.setAction(DownloadService.ACTION_STOP);
        intentStop.putExtra("fileInfo", fileInfo);
        PendingIntent piStop = PendingIntent.getService(mContext, 0, intentStop, 0);
        remoteViews.setOnClickPendingIntent(R.id.stop_button, piStop);
        // 設(shè)置TextView中文件的名字
        remoteViews.setTextViewText(R.id.file_textview, fileInfo.getFileName());
        // 設(shè)置Notification的視圖
        notification.contentView = remoteViews;
        // 發(fā)出Notification通知
        mNotificationManager.notify(fileInfo.getId(), notification);
        // 把Notification添加到集合中
        mNotifications.put(fileInfo.getId(), notification);
    }
}
/**
 * 取消通知欄通知
 */
public void cancelNotification(int id) {
    mNotificationManager.cancel(id);
    mNotifications.remove(id);
}
/**
 * 更新通知欄進度條
 * @param id 獲取Notification的id
 * @param progress 獲取的進度
 */
public void updataNotification(int id, int progress) {
    Notification notification = mNotifications.get(id);
    if (notification != null) {
        // 修改進度條進度
        notification.contentView.setProgressBar(R.id.progressBar2, 100, progress, false);
        mNotificationManager.notify(id, notification);
    }
}
}

通知欄的工具類已經(jīng)寫好了跨新,現(xiàn)在就是使用它的時候了富腊。我們要在Activity中點擊下載的時候就彈出通知欄,下面我們就來修改DownloadService和MainActivity中的代碼來啟動通知欄吧域帐。

修改DownloadService

要在點擊開始下載赘被,啟動下載任務的時候彈出通知欄,我們所要知道的是如何收到開始的信號肖揣。

1民假、當點擊開始下載的按鍵時,在FileAdapter的startButton傳出一個ACTION_START的信號龙优,并啟動服務羊异。

2、然后在DownloadService中的onStartCommand方法中接到信號彤断,然后啟動InitThread初始化線程野舶。

3、在InitThread啟動之后會獲得FileInfo的實例宰衙,里面包含所要下載的文件的長度平道,然后InitThread通過Message將FileInfo實例傳遞給Handler。

4供炼、在Hanlder中開啟DownloadTask下載線程任務一屋。

這個下載任務繞來繞去,還挺令人迷惑的袋哼,不過好在我們都知道它是走哪一條路了冀墨。所以在第4步,Handler開啟下載任務的時候先嬉,我們就發(fā)出一個通知,告訴大家:下載已經(jīng)開始啦楚堤!
代碼如下:

Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
        case MSG_INIT:
···
            // 發(fā)送啟動下載的通知
            Intent intent = new Intent(ACTION_START);
            intent.putExtra("fileInfo", fileInfo);
            sendBroadcast(intent);
            break;
        }
    };
};

分析了一堆疫蔓,我們終于獲得了開始下載的通知了,然后就能在MainActivity中的廣播接收器中接收到這條廣播身冬,然后彈出通知欄

MainActivity中開啟通知欄

首先我們要接收到開啟下載ACTION_START的這條廣播衅胀,但是之前注冊的廣播接收器并沒有包含這條廣播,因此要添加這條代碼:

        intentFilter.addAction(DownloadService.ACTION_START);

然后我們需要一個NotificationUtil成員對象酥筝,在onCreate中初始化它滚躯。
最后我們修改廣播接收者內(nèi)部類的代碼,代碼如下:

class UIRecive extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) {
            // 更新進度條的時候
            int finished = intent.getIntExtra("finished", 0);
            int id = intent.getIntExtra("id", 0);
            mAdapter.updataProgress(id, finished);
            mNotificationUtil.updataNotification(id, finished);
        } else if (DownloadService.ACTION_FINISHED.equals(intent.getAction())){
            // 下載結(jié)束的時候
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            mAdapter.updataProgress(fileInfo.getId(), 0);
            Toast.makeText(MainActivity.this, mFileList.get(fileInfo.getId()).getFileName() + "下載完畢", Toast.LENGTH_SHORT).show();
            // 下載結(jié)束后取消通知
            mNotificationUtil.cancelNotification(fileInfo.getId());
        } else if (DownloadService.ACTION_START.equals(intent.getAction())){
            // 下載開始的時候啟動通知欄
            mNotificationUtil.showNotification((FileInfo) intent.getSerializableExtra("fileInfo"));
            
        } 
    }

}

這是我們開始下載的時候就能彈出通知欄,來下載進行時能更新通知欄的進度掸掏,最后下載完成能夠自動取消通知欄茁影。
一個多線程多文件外加通知欄顯示的下載器終于完成了,可以直接測試了丧凤。

總結(jié)

一個小小的簡陋的項目終于完成了募闲!但是對于剛?cè)腴T的小伙伴們相信還是廢了不少的功夫。

在這個項目中愿待,我們運用的不再是單一的組件只是浩螺,而是將組件綜合運用起來,如何在listView中操作仍侥,數(shù)據(jù)庫如何增刪改查要出,Service如何與Activity通信,Notification通知欄又是怎樣顯示的····

這些組件我們都刷了一遍农渊,相信下次再次使用的時候就不會像剛開始一樣無從下手了患蹂。

這個項目看上去貌似不錯,但仔細思量仍是有種種的不足之處腿时,還擁有一些BUG待解決况脆。而且在Activity與Service之間的通信用BroadCast廣播,雖然會更簡單些批糟,但對于真正的項目而已可能不是這樣的格了。

因為廣播是系統(tǒng)組件,這樣大材小用是資源的浪費徽鼎,而且效率是偏低的盛末。在一個項目中的單線程多進程中,應該使用Handler加上Messenger進行通信的否淤,這有待于大家學習悄但。

好了,話就說到這里石抡,這個項目的Github地址是:
https://github.com/liaozhoubei/MultiDownload
歡迎大家下載檐嚣,如果發(fā)現(xiàn)有BUG,也可以通知我

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啰扛,一起剝皮案震驚了整個濱河市嚎京,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌隐解,老刑警劉巖鞍帝,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異煞茫,居然都是意外死亡帕涌,警方通過查閱死者的電腦和手機摄凡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚓曼,“玉大人亲澡,你說我怎么就攤上這事”脔铮” “怎么了谷扣?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捎琐。 經(jīng)常有香客問我会涎,道長,這世上最難降的妖魔是什么瑞凑? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任末秃,我火速辦了婚禮,結(jié)果婚禮上籽御,老公的妹妹穿的比我還像新娘练慕。我一直安慰自己,他們只是感情好技掏,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布铃将。 她就那樣靜靜地躺著,像睡著了一般哑梳。 火紅的嫁衣襯著肌膚如雪劲阎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天鸠真,我揣著相機與錄音悯仙,去河邊找鬼。 笑死吠卷,一個胖子當著我的面吹牛锡垄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祭隔,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼货岭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了疾渴?” 一聲冷哼從身側(cè)響起千贯,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎程奠,沒想到半個月后丈牢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祭钉,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡瞄沙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片距境。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡申尼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垫桂,到底是詐尸還是另有隱情师幕,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布诬滩,位于F島的核電站霹粥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疼鸟。R本人自食惡果不足惜后控,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望空镜。 院中可真熱鬧浩淘,春花似錦、人聲如沸吴攒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洼怔。三九已至署惯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茴厉,已是汗流浹背泽台。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矾缓,地道東北人怀酷。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像嗜闻,于是被迫代替她去往敵國和親蜕依。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容