1. 程式人生 > >Android-斷點續傳下載

Android-斷點續傳下載

工作找完了,玩也玩完了,該好好學習了,最近我把《Java併發程式設計的藝術》這本書給讀完了,對於併發程式設計以及執行緒池的使用還是不嫻熟,我就在imooc上找到一個專案“Android-Service系列之斷點續傳下載“,這是我對這個專案在編寫的時候記錄。

涉及知識點

  • UI介面編寫
  • 資料庫
  • Service
  • 廣播傳遞資料
  • 多執行緒以及Handler
  • 網路

這些應該是Android的基礎,我就不累述了,到時候在程式碼中遇到了再進行解釋。
這個專案主要的流程是:
流程圖
一切的操作的開始是基於Activity的,但是我們的下載任務肯定是不能在Activity中進行的,因為假如我們的Activity切換成後臺程序就有可能會被銷燬(程序的優先順序:前臺,可見,服務,後臺,空),所以我們將下載放在Service中是比較好的,但是Service和Activity一樣是主執行緒,是不能進行資料的操作的,所以我們要利用到Thread或者是執行緒池,如果我們要可見下載進度的話,我們就需要通過廣播的訊息傳遞來更新UI上的進度,對於斷點我們就需要實時將下載到的檔案位置儲存下來,所以我們利用資料庫(穩定)儲存進度。下載完成以後再將下載資訊刪除。

基礎佈局

這部分就不講了,特別簡單的一個佈局

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity"
>
<TextView android:id="@+id/tvFileName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> <ProgressBar android:id="@+id/pbProgress" style="?android:attr/progressBarStyleHorizontal
"
android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/tvFileName" android:layout_below="@+id/tvFileName"/>
<Button android:id="@+id/btStop" style="?android:buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/pbProgress" android:text="停止"/> <Button android:id="@+id/btStart" style="?android:buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/btStop" android:layout_below="@id/pbProgress" android:text="下載"/> </RelativeLayout>

實體類

在這個專案裡面我們需要定義兩個實體類來進操作,一個是檔案資訊的實體類,一個是執行緒資訊的實體類

檔案資訊

主要的檔案相關資訊

private int id;//檔案id
private String url;//檔案的下載url
private String fileName;//檔名
private int length;//檔案長度
private int finished;//檔案下載完成度
執行緒相關資訊
private int id;//檔案id
private String url;//檔案下載url
private int start;//執行緒從哪裡開始下載
private int end;//執行緒到哪裡結束下載
private int finished;//完成多少

因為我們需要將檔案資訊進行儲存以及傳遞,所以我們需要實現序列化介面

public class FileInfo implements Serializable

Activity

我們在Activity中需要對控制元件進行監聽,如何通過Intent將資訊進行傳遞。
當然這是最基礎的,在之後我們有多個下載任務或者是下載完成我們就需要使用ListView去完成這個效果。先暫時這樣寫。

package com.gin.xjh.download_demo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.gin.xjh.download_demo.entities.FileInfo;
import com.gin.xjh.download_demo.services.DownloadService;

public class MainActivity extends AppCompatActivity {

    private TextView mTvFileName;
    private ProgressBar mPbProgress;
    private Button mBtStop, mBtStart;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initEvent();
    }

    private void initEvent() {
        final FileInfo fileInfo = new FileInfo(0, "http://music.163.com/" +
                "song/media/outer/url?id=557581647.mp3", "一眼一生", 0, 0);
        mBtStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, DownloadService.class);
                intent.setAction(DownloadService.ACTION_START);
                intent.putExtra("fileInfo", fileInfo);
                startService(intent);
            }
        });
        mBtStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, DownloadService.class);
                intent.setAction(DownloadService.ACTION_STOP);
                intent.putExtra("fileInfo", fileInfo);
                startService(intent);
            }
        });
    }

    private void initView() {
        mTvFileName = findViewById(R.id.tvFileName);
        mPbProgress = findViewById(R.id.pbProgress);
        mBtStop = findViewById(R.id.btStop);
        mBtStart = findViewById(R.id.btStart);
    }
}


Service

當然我們將訊息傳遞到了Service中,並且我們是通過Start方法啟動的Service,所以我們需要在onStartCommand方法中對Intent傳遞的訊息進行判斷,我們就需要重寫onStartCommand方法,但是我們需要進行網路下載,所以我們需要新建一個Thread去完成這個耗時操作。

package com.gin.xjh.download_demo.services;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

import com.gin.xjh.download_demo.entities.FileInfo;

import java.net.HttpURLConnection;

public class DownloadService extends Service {

    public static final String DOWNLOAD_PATH =
            Environment.getExternalStorageDirectory().getAbsolutePath() +
                    "/downloads/";
    public static final String ACTION_START = "ACTION_START";
    public static final String ACTION_STOP = "ACTION_STOP";

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //從Activity中傳來的資料
        FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
        if (ACTION_START.equals(intent.getAction())) {
            Log.i("test", "Start:" + fileInfo.toString());
        } else if (ACTION_STOP.equals(intent.getAction())) {
            Log.i("test", "Stop:" + fileInfo.toString());
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    class InitThread extends Thread {
        private FileInfo mFileInfo = null;

        public InitThread(FileInfo mFileInfo) {
            this.mFileInfo = mFileInfo;
        }

        @Override
        public void run() {
            //網路下載操作
        }
    }
}

網路下載初始化

因為我們是最基礎的下載,所以我們使用的還是HttpURLConnection進行網路的相關操作,並且因為我們是從伺服器中下載檔案,所以我們選擇的是GET方法來獲取資料。
我們根據url獲取到檔案的相關資料,對長度進行初始化,以及建立下載檔案相關(檢查路徑是否存在,如果沒有則進行建立)
PS:網路連結,以及流檔案使用完後進行關閉,防止記憶體洩漏。

@Override
@Override
public void run() {
    HttpURLConnection conn = null;
    RandomAccessFile raf = null;
    try {
        //連線網路檔案
        URL url = new URL(mFileInfo.getUrl());
        conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(3000);
        conn.setRequestMethod("GET");
        int length = -1;
        if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
            //獲得檔案長度
            length = conn.getContentLength();
        }
        if (length <= 0) {
            return;
        }
        //在本地建立檔案
        File dir = new File(DOWNLOAD_PATH);//驗證下載地址
        if (!dir.exists()) {
            dir.mkdir();
        }
        File file = new File(dir, mFileInfo.getFileName());
        raf = new RandomAccessFile(file, "rwd");//r:讀許可權,w:寫許可權,d:刪除許可權
        //設定檔案長度
        raf.setLength(length);
        mFileInfo.setLength(length);
        mHandler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        conn.disconnect();
        try {
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

資料庫初始化

我們要將執行緒的相關資訊存入資料庫中,這樣我們才能做到斷點續傳,因為,沒有記錄的話,下一次的下載就不知道從哪裡開始了,所以我們需要使用資料庫(關於資料庫的基本操作可以看我的另一篇部落格:傳送門)。在這裡我們需要幾個操作:

定義資料庫幫助類

這裡我們繼承SQLiteOpenHelper,並且將資料庫的建立以及更新重寫好。

package com.gin.xjh.download_demo.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "download.db";
    private static final int VERSION = 1;
    private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement," +
            "thread_id integer,url text,start integer,ends integer,finished integer)";
    private static final String SQL_DROP = "drop table if exists thread_info";

    public DBHelper(Context context) {
        super(context, DB_NAME, null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int i, int i1) {
        db.execSQL(SQL_DROP);
        db.execSQL(SQL_CREATE);
    }
}

定義資料訪問介面

雖然我們直接在幫助類中寫增刪改查的操作也是可以的,但是使用介面的話就可以定義一個規範,並且在之後我們進行介面的實現也可以使得程式碼的耦合度降到最低。

package com.gin.xjh.download_demo.db;

import com.gin.xjh.download_demo.entities.ThreadInfo;

import java.util.List;

/**
 * 資料訪問介面
 */

public interface ThreadDAO {

    /**
     * 插入執行緒資訊
     */
    void insertThread(ThreadInfo threadInfo);

    /**
     * 刪除執行緒資訊
     */
    void deleteThread(String url, int thread_id);

    /**
     * 更新執行緒資訊
     */
    void updateThread(String url, int thread_id, int finished);

    /**
     * 查詢檔案的執行緒資訊
     */
    List<ThreadInfo> getThreads(String url);

    /**
     * 執行緒資訊是否存在
     */
    boolean isExists(String url, int thread_id);
}

實現資料訪問介面

就是簡單的增刪改查的操作

package com.gin.xjh.download_demo.db;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.gin.xjh.download_demo.entities.ThreadInfo;

import java.util.ArrayList;
import java.util.List;

/**
 * 資料訪問介面的實現
 */

public class ThreadDAOImpl implements ThreadDAO {

    private DBHelper mHelper = null;

    public ThreadDAOImpl(Context context) {
        mHelper = new DBHelper(context);
    }


    @Override
    public void insertThread(ThreadInfo threadInfo) {
        SQLiteDatabase db = mHelper.getWritableDatabase();
        db.execSQL(
                "insert into thread_info(thread_id,url,start,ends,finished) values(?,?,?,?,?)",
                new Object[]{threadInfo.getId(), threadInfo.getUrl(), threadInfo.getStart(),
                        threadInfo.getEnds(), threadInfo.getFinished()});
        db.close();
    }

    @Override
    public void deleteThread(String url, int thread_id) {
        SQLiteDatabase db = mHelper.getWritableDatabase();
        db.execSQL(
                "delete from thread_info where url = ? and thread_id = ?",
                new Object[]{url, thread_id});
        db.close();
    }

    @Override
    public void updateThread(String url, int thread_id, int finished) {
        SQLiteDatabase db = mHelper.getWritableDatabase();
        db.execSQL(
                "update thread_info set finished = ? where url = ? and thread_id = ?",
                new Object[]{finished, url, thread_id});
        db.close();
    }

    @Override
    public List<ThreadInfo> getThreads(String url) {
        List<ThreadInfo> list = new ArrayList<>();
        SQLiteDatabase db = mHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from thread_info where url = ?",
                new String[]{url});
        while (cursor.moveToNext()) {
            ThreadInfo thread = new ThreadInfo();
            thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
            thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));
            thread.setStart(cursor.getInt(cursor.getColumnIndex("start")));
            thread.setEnds(cursor.getInt(cursor.getColumnIndex(
            
           

相關推薦

Android-斷點下載

工作找完了,玩也玩完了,該好好學習了,最近我把《Java併發程式設計的藝術》這本書給讀完了,對於併發程式設計以及執行緒池的使用還是不嫻熟,我就在imooc上找到一個專案“Android-Service系列之斷點續傳下載“,這是我對這個專案在編寫的時候記錄。 涉及

Android網路程式設計 --斷點下載檔案

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android FTP 多執行緒斷點下載\上

最近在給我的開源下載框架Aria增加FTP斷點續傳下載和上傳功能,在此過程中,爬了FTP的不少坑,終於將功能實現了,在此把一些核心功能點記錄下載。 FTP下載原理 FTP單執行緒斷點續傳 FTP和傳統的HTTP協議有所不同,由於FTP沒有所謂的標頭

android 多執行緒斷點下載

今天跟大家一起分享下android開發中比較難的一個環節,可能很多人看到這個標題就會感覺頭很大,的確如果沒有良好的編碼能力和邏輯思維,這塊是很難搞明白的,前面2次總結中已經為大家分享過有關技術的一些基本要領,我們先一起簡單回顧下它的基本原理。什麼是多執行緒下載?多執行緒下載其

Android開發經驗】關於“多執行緒斷點下載”功能的一個簡單實現和講解

    上班第一天,在技術群裡面和大家閒扯,無意中談到了關於框架的使用,一個同學說為了用xUtils的斷線續傳下載功能,把整個庫引入到了專案中,在google的官方建議中,是非常不建議這種做法的,集合框架雖然把很多功能整合起來,但是程式碼越多,出現問題的可能越大,而且無形之中

Android實現網路多執行緒斷點下載

本示例介紹在Android平臺下通過HTTP協議實現斷點續傳下載。       我們編寫的是Andorid的HTTP協議多執行緒斷點下載應用程式。直接使用單執行緒下載HTTP檔案對我們來說是一件非常簡單的事。那麼,多執行緒斷點需要什麼功能?       1.多執行緒下載,

斷點/下載

在後臺專案中經常會要求有下載和上傳功能的實現,在大檔案傳輸的過程中可以實現斷點傳輸避免重複下載:現在我們來整理一下,也可以作為一個專案的亮點。 由於是本地測試,所以是將"D:/test/remote/file.txt"傳送到"D:/test/local/file.txt",如果是使用FT

installgithub-支援斷點下載GitHubDesktop離線安裝檔案

用GitHub賬號提交程式碼的都希望下載本地客戶端克隆倉庫 https://desktop.github.com/ 可是在天朝用GitHub.exe線上下載安裝這個客戶端實在是太難了 由於不支援斷點續傳 公司千M光纖外帶翻牆都無法成功下載這個玩意

ASP.NET WebAPi之斷點下載(下)

前言 上一篇我們穿插了C#的內容,本篇我們繼續來講講webapi中斷點續傳的其他情況以及利用webclient來實現斷點續傳,至此關於webapi斷點續傳下載以及上傳內容都已經全部完結,一直嚷嚷著把SQL Server和Oracle資料庫再重新過一遍,這篇過完,就要開始

java多執行緒實現斷點下載

public class DownloadThread extends Thread {private int id;private int startindex;private int endindex;private String path;static int threadfinishedcount=0

Python實現斷點下載檔案,大檔案下載還怕下載到一半就斷了嗎?不存在!

這篇部落格簡單介紹python斷點續傳下載檔案,並加入花哨的下載進度顯示方法,涉及Python檔案操作的技巧,和一些函式庫的使用。 環境 Python 3.6 requests模組 對應檔案的下載連結 (要下載的檔案必須支援斷點續傳) (是不是很少東西

OkHttp實現多執行緒斷點下載,單例模式下多工下載管理器,一起拋掉sp,sqlite的輔助吧

        最近專案需要使用到斷點下載功能,筆者比較喜歡折騰,想方設法拋棄SharedPreferences,尤其是sqlite作記錄輔助,改用臨時記錄檔案的形式記錄下載進度,本文以斷點下載為例。先看看demo執行效果圖:               斷點續傳:記

新技能 get —— Python 斷點下載檔案

from urllib.request import urlretrieve import sys import os prev_reported_download_percent = None #

C#實現http多執行緒斷點下載檔案

using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Net; using u8 = System.Byte; using

python實現斷點下載檔案

最近的任務裡有一個功能是要我從日誌伺服器實時跟新日誌到本地,日誌在不斷新增內容就需要我隔一段時間從上次下載的位置繼續下載,並寫入本地檔案上次寫完的位置後面。 headers = {'Range': 'bytes=%d-' % local_f

Java多執行緒斷點下載

package com.example.threadpool; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.RandomAc

iOS開發之網路程式設計--4、NSURLSessionDataTask實現檔案下載(離線斷點下載)

前言:根據前篇《iOS開發之網路程式設計--2、NSURLSessionDownloadTask檔案下載》或者《iOS開發之網路程式設計--3、NSURLSessionDataTask實現檔案下載(離線斷點續傳下載)》,都遺留了一個細節未處理的問題,那就是在離線斷點下載的過程中,當應用程式重新啟動之後,進度

利用Xutils框架進行斷點下載

前面有兩篇博文主要介紹瞭如何利用volley獲取所有cookie資訊和自定義一個request, 地址如下: 我們都知道volley是google官方推出的一款網路請求框架,它適合高併發但是資料量不大的網路請求操作,利用volley我們可以非常迅速的連線到伺服器拿到我們

輕量級多執行緒斷點下載框架

我又來了,一個月寫了三個小框架我也是屌屌的。 一般的小專案,遇到下載的問題時都是簡單的開一個執行緒然後通過流的方式來實現。少量的下載,檔案也比較小的的時候,這樣的方式都是OK的。但是如果真要做一款下載為主要功能的app的時候,或者專案中涉及大量下載任務的時候,

php實現大檔案斷點下載例項

require_once('download.class.php'); date_default_timezone_set('Asia/Shanghai'); error_reporting(E_STRICT); function errorHandler($errno, $errs