Service實戰:使用Service完成一個下載任務

1.新建一個項目,網絡方面使用okhttp來完成。在build.gradle中添加依賴:

    compile 'com.squareup.okhttp3:okhttp:3.7.0'

2..定義一個回調接口

    /**
     * 回調接口,對下載狀態進行監聽
     * Created by lmy on 2017/4/26.
     */
    public interface DownloadListener {
        void onProgress(int progress);//通知當前下載進度
        void onSuccess();//下載成功
        void onFaild();//失敗
        void onPaused();//暫停下載
        void onCancled();//取消下載
    }   

3.使用 AsyncTask來實現異步下載:

先簡要介紹下asynctask的泛型。

    AsyncTask<Params, Progress, Result>

三種泛型類型分別代表“啓動任務執行的輸入參數”、“後臺任務執行的進度”、“後臺計算結果的類型”。是不是聽着很拗口, 我一般會把它簡單的理解爲事件的起因,經過和結果,很好理解也好記。

在我公司的實際項目中,第一個就是我們網絡請求需要的傳給後臺的參數,第二個參數經常用的Void,第三個參數一般爲 List<>,存儲請求到的數據。

這裏我們泛型爲String,Integer,Integer,String表示要給後臺一個字符串url,即下載的地址,兩個Integer分別表示使用整型來顯示下載進度和反饋下載結果。(怎麼樣,很清晰吧)。

上代碼(註釋寫得那是相當的詳細):

/**
 * 異步下載任務
 * Created by lmy on 2017/4/26.
 */
public class DownLoadTask extends AsyncTask<String, Integer, Integer> {
    //四個常量表示下載狀態:分別爲成功,失敗,暫停,取消。
    public static final int TYPE_SUCCESS = 0;
    public static final int TYPE_FAILED = 1;
    public static final int TYPE_PAUSED = 2;
    public static final int TYPE_CANCLED = 3;

    private DownloadListener listener;
    private boolean isPaused = false;
    private boolean isCancled = false;
    private int lastProgress;

    //構造方法中傳入我們定義的接口,待會就可以把下載的結果通過這個參數進行回調
    public DownLoadTask(DownloadListener listener) {
        this.listener = listener;
    }

    /**
     * 後臺任務開始執行之前調用,用於進行一些界面上的初始化操作,如顯示進度條。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 後臺任務:
     * 子線程中執行耗時操作。任務完成可以用return語句來返回任務的結果。
     * 如果需要更新UI,可以調用 publishProgress();
     *
     * @param params 這裏的參數就是根據我們制指定的泛型來的
     * @return
     */
    @Override
    protected Integer doInBackground(String... params) {
        InputStream inputStream = null;
        RandomAccessFile savedFile = null;//RandomAccessFile 是隨機訪問文件(包括讀/寫)的類
        File file = null;
        try {
            long downloadLength = 0;//記錄已下載的文件的長度(默認爲0)
            String downloadUrl = params[0];
            //截取下載的URL的最後一個"/"後面的內容,作爲下載文件的文件名
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
            //將文件下載到sd卡的根目錄下
//          String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
            String directory = Environment.getExternalStorageDirectory().getAbsolutePath();
            file = new File(directory + fileName);

            if (file.exists()) {//判斷文件是否已經存在
                downloadLength = file.length();//如果文件已經存在,讀取文件的字節數。(這樣後面能開啓斷點續傳)
            }
            long contentLength = getContentLength(downloadUrl); //獲取待下載文件的總長度
            if (contentLength == 0) {
                return TYPE_FAILED;//待下載文件字節數爲0,說明文件有問題,直接返回下載失敗。
            }
            else if (downloadLength == contentLength) {
                return TYPE_SUCCESS;//待下載文件字節數=已下載文件字節數,說明文件已經下載過。
            }

            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    //斷點續傳,指定從哪個文件開始下載
                    .addHeader("RANGE", "bytes=" + downloadLength + "-")
                    .url(downloadUrl)
                    .build();

            Response response = client.newCall(request).execute();
            if (response != null) {//返回數據不爲空,則使用java文件流的方式,不斷把數據寫入到本地
                inputStream = response.body().byteStream();
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadLength);//斷點續傳--跳過已經下載的字節
                int total = 0;//記錄此次下載的字節數,方便計算下載進度
                byte[] b = new byte[1024];
                int len;
                while ((len = inputStream.read(b)) != -1) {
                    //下載是一個持續過程,用戶隨時可能暫停下載或取消下載
                    //所以把邏輯放在循環中,在整個下載過程中隨時進行判斷
                    if (isCancled) {
                        return TYPE_CANCLED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);
                        //計算已經下載到的百分比
                        int progress = (int) ((total + downloadLength) * 100 / contentLength);
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCancled && file != null) {
                    file.delete();//如果已經取消,並且文件不爲空,則刪掉下載的文件
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }

    /**
     * 當在後臺任務中調用了publishProgress()後,onProgressUpdate很快就會被執行。
     *
     * @param values 參數就是在後臺任務中傳過來的,這個方法中可以更新UI。
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }

    /**
     * 當後臺任務執行完畢並調用return返回時,這個方法很快會被調用。返回的數據會被作爲參數傳到這個方法中
     * 可根據返回數據更新UI。提醒任務結果,關閉進度條等。
     *
     * @param integer
     */
    @Override
    protected void onPostExecute(Integer integer) {
        //把下載結果通過接口回調傳出去
        switch (integer) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCLED:
                listener.onCanceled();
                break;
            default:
                break;

        }

    }

    //暫停下載
    public void pausedDownload() {
        isPaused = true;
    }

    //取消下載
    public void cancledDownload() {
        isCancled = true;
    }

    /**
     * 獲取待下載文件的字節數
     *
     * @param downloadUrl
     * @return
     * @throws IOException
     */
    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null && response.isSuccessful()) {
            long contentLength = response.body().contentLength();
            response.body().close();
            return contentLength;
        }
        return 0;
    }
}

這樣,下載的功能就已經實現了。下面爲了保證downloadTask能一直在後臺執行,我們創建一個用來下載的Service。新建一個DownloadService,代碼如下:

/**
 * 用於下載的Service
 * Created by lmy on 2017/4/27.
 */

public class DownloadService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private DownloadBinder mBinder=new DownloadBinder();
    private DownLoadTask downLoadTask;//要通過服務來下載,當然要在服務中創建下載任務並執行。
    private String downloadUrl;

    //創建一個下載的監聽
    private DownloadListener listener = new DownloadListener() {
        //通知進度
        @Override
        public void onProgress(int progress) {
            //下載過程中不停更新進度
            getNotificationManager().notify(1, getNotification("正在下載...", progress));
        }

        //下載成功
        @Override
        public void onSuccess() {
            downLoadTask = null;
            //下載成功時將前臺服務通知關閉,並創建一個下載成功的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("下載成功!", -1));
        }

        //下載失敗
        @Override
        public void onFailed() {
            downLoadTask = null;
            //下載失敗時將前臺服務通知關閉,並創建一個下載成功的通知
            getNotificationManager().notify(1, getNotification("下載失敗!", -1));
        }

        //暫停下載
        @Override
        public void onPaused() {
            downLoadTask=null;
        }

        //取消下載
        @Override
        public void onCanceled() {
            downLoadTask=null;
            stopForeground(true);
        }
    };

    /**
     * 代理對象:在這裏面添加三個方法:
     * 開始下載,暫停下載,取消下載
     * 就可以在Activity中綁定Service,並控制Service來實現下載功能
     */
    class DownloadBinder extends Binder {
        //開始下載,在Activity中提供下載的地址
        public void startDownload(String url) {
            if (downLoadTask == null) {
                downLoadTask = new DownLoadTask(listener);
                downloadUrl = url;
                downLoadTask.execute(downloadUrl);
                startForeground(1, getNotification("正在下載...", 0));//開啓前臺通知
            }
        }

        //暫停下載
        public void pausedDownload() {
            if (downLoadTask != null) {
                downLoadTask.pausedDownload();
            }
        }

        //取消下載
        public void cancledDownload() {
            if (downLoadTask != null) {
                downLoadTask.cancledDownload();
            } else {
                if (downloadUrl != null) {
                    //取消下載時需要將下載的文件刪除  並將通知關閉
                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                    String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getParent();
                    File file = new File(directory + fileName);
                    if (file.exists()) {
                        file.delete();
                    }
                    getNotificationManager().cancel(1);
                    stopForeground(true);
                }
            }
        }
    }

    private Notification getNotification(String title, int progress) {
        Intent intent = new Intent(this, DownloadActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.drawable.liuyifei);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.liuyifei));
        builder.setContentIntent(pendingIntent);
        builder.setContentTitle(title);
        if (progress >= 0) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);//最大進度。當前進度。是否使用模糊進度
        }
        return builder.build();
    }

    //獲取通知管理器
    private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }
}

然後是我們再Activity中調用startService和bindService來啓動並綁定服務。
startService保證我們的Service長期在後臺運行,bindService則能夠讓Activity和Service通信,就可以通過控制Service達到隨時暫停或開始或取消下載。

Activity的佈局很簡單,如下:


這裏寫圖片描述

Activity代碼如下:

/**
 * Created by lmy on 2017/4/27.
 */
public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {
    @InjectView(R.id.start_download)
    Button startDownload;
    @InjectView(R.id.paused_download)
    Button pausedDownload;
    @InjectView(R.id.cancel_download)
    Button cancelDownload;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        ButterKnife.inject(this);
        startDownload.setOnClickListener(this);
        pausedDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);
        Intent intent = new Intent(this, DownloadService.class);
        startService(intent);//啓動服務
        bindService(intent, connection, BIND_AUTO_CREATE);//綁定服務

        //運行時權限申請
        if (ContextCompat.checkSelfPermission(DownloadActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(DownloadActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (DownloadService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };


    @Override
    public void onClick(View v) {
        if (downloadBinder == null) {
            return;
        }
        switch (v.getId()) {
            case R.id.start_download:
                //這裏我們的下載地址是郭神提供的eclipse下載地址,致敬!
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                downloadBinder.startDownload(url);
                break;
            case R.id.paused_download:
                downloadBinder.pausedDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancledDownload();
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(DownloadActivity.this, "拒絕權限將無法使用程序!", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }

}
另外不要忘記添加權限:

<use-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

測試:點擊開始下載:


這裏寫圖片描述

我這個網速好慢啊!。。。
等等。。。
再等等。。。
快了。。。

這裏寫圖片描述

你可以通過點擊開始,暫停,取消,甚至斷網來測試這個程序的健壯性。最終下載完成會彈出一個下載“下載成功!”的通知。(對了,由於我這個測試機是android4.2版本的,所以下載的時候沒有提示運行時權限。)

終於下載好了:


這裏寫圖片描述

打開我的手機上面的文件管理:
可以看到我們下載的文件:


這裏寫圖片描述

以上,我們使用Service進行下載就大功告成了!結合前面我的兩篇文章,對Service的解析算是比較全面了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章