1. 程式人生 > >使用AsyncTask下載+Service+通知連Notification(控制下載暫停或取消)

使用AsyncTask下載+Service+通知連Notification(控制下載暫停或取消)

通過閱讀郭霖《第一行程式碼》第二版 服務最佳實踐啟發,寫的小案例

實現通知欄顯示下載進度,兩個按鈕:暫停下載,取消下載(刪除下載檔案)。

首先在建立工程之後,寫一個介面inControllerDownLoad用來回調暫停,取消,ProgressBar進度,下載成功,下載失敗,如下:

public interface inControllerDownLoad {
    public void onProgress(int progress);
    public void onCancle();
    public void onPaused();
    public void onSuccess();
    public void 
onFailed(); }

接著需要寫一個執行緒用來實現檔案下載,我們繼承AsysncTask命名為DownLoadAsysncTask

程式碼裡敘述AsysncTask使用,上程式碼:

package com.huida.notificationdemo;

import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Environment;

import java.io.File;
import java.io.IOException;
import 
java.io.InputStream; import java.io.RandomAccessFile; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * Created by Json on 2017/9/19. */ /** * 繼承AsyncTask發現有三個泛型引數 * 第一個String表示在執行此執行緒時候需要傳入一個字串引數 * 第二引數Integer表示用整型資料來作為進度顯示單位 * 第三個引數Integer表示使用整型資料來反饋執行結果 */ public class
DownLoadAsysncTask extends AsyncTask<String, Integer, Integer> { private final inControllerDownLoad inController; public static final int SUCCESS = 0; public static final int FAILED = 1; public static final int PAUSED = 2; public static final int CANCELED = 3; private final Context context; private boolean isCancled = false; private boolean isPaused = false; private int lastProgress; private String downloadUrl; private File file; private RandomAccessFile savedFile; public DownLoadAsysncTask(inControllerDownLoad inter, Context context) { this.context = context; this.inController = inter; } /** * 執行緒在此方法中執行 downLoadAsysncTask.execute("http://mpge.5nd.com/2016/2016-11-15/74847/1.mp3");這裡開啟執行緒 * 引數可以為多個,這裡只寫了一個引數 * * @param strings * @return */ @Override protected Integer doInBackground(String... strings) { InputStream is = null; long downloadLength = 0;//記錄已經下載檔案長度 downloadUrl = strings[0];//通過第一個引數拿到下載連結 String fileName = Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS).getPath() + "測試下載"; file = new File(fileName); if (file != null) { downloadLength = file.length();//獲取檔案所下載的位元組長度 } long countLength = 0; try { countLength = getContentLength(downloadUrl);//獲取需要下載檔案的總長度 if (downloadLength == countLength) { return SUCCESS;//如果一直 那麼下載成功 } if (countLength == 0) { return FAILED;//如果總長為0 表示請求失敗 } //這裡用到Okhttp 需要在app下的build.gradle 新增依賴 compile 'com.squareup.okhttp3:okhttp:3.8.1' Response response = new OkHttpClient().newCall(new Request.Builder() .addHeader("RANGE", "bytes=" + file.length() + "-").url(downloadUrl).build()) .execute(); //"RANGE","bytes=" + file.length() + "-",表示從伺服器請求file.length() 之後資料,注意格式bytes後有等於號 if (response.body() != null) { //獲取到file.length()位元組之後的流 is = response.body().byteStream(); //通過RandomAccessFile寫入 savedFile = new RandomAccessFile(file, "rw"); //將指標指向file.length() savedFile.seek(file.length()); byte[] buff = new byte[1024]; int len; while ((len = is.read(buff)) != -1) { //在下載向檔案中寫資料時候,如果使用者呼叫了pausedDownLoad(),和cancelDownLoad()方法會監聽到此 //isCancled,isPaused會改變值 以下進行判斷 if (isCancled) { return CANCELED; } else if (isPaused) { return PAUSED; } else { if (file.length() >= countLength) { break; } savedFile.write(buff, 0, len); int progress = (int) ((file.length()) * 100 / countLength); publishProgress(progress);//AsyscTask的方法表示更新Progress會呼叫下方onProgressUpdate()方法 } } response.body().close(); //到此表示流正常寫入 返回成功 return SUCCESS; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (savedFile != null) { savedFile.close(); } if (isCancled && file != null) { file.delete(); } } catch (Exception e) { e.printStackTrace(); } } return FAILED; } 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.body() != null && response.isSuccessful()) { response.body().close(); return response.body().contentLength(); } return 0; } /** * 表示線上程執行前的準備工作 */ @Override protected void onPreExecute() { super.onPreExecute(); } /** * 這裡執行緒執行完成之後呼叫,此方法中會在主執行緒執行 * 判斷 執行緒執行完的返回值,AsyncTask<String, Integer, Integer>第三個引數對應, * 也是doInBackground(String... strings) 的返回值 * * @param integer */ @Override protected void onPostExecute(Integer integer) { switch (integer) { case SUCCESS: inController.onSuccess(); break; case CANCELED: inController.onCancle(); //取消之後接著刪除該檔案 只有在下載過程中能監聽到 if (file != null) { file.delete(); } isCancled = false; break; case PAUSED: inController.onPaused(); break; case FAILED: inController.onFailed(); break; } } /** * 表示更新操作 也是在主執行緒中執行 * * @param values 可以為多個 我們在上方 publishProgress(progress);只傳入一個 */ @Override protected void onProgressUpdate(Integer... values) { int progress = values[0]; if (progress > lastProgress) { //這裡回撥介面的更新操作 inController.onProgress(progress); lastProgress = progress; } } /** * 暴露次方法用來暫停或繼續下載 * * @param isPaused true表示暫停,false表示繼續下載 */ public void pausedDownLoad(boolean isPaused) { this.isPaused = isPaused; if (!isPaused) { Intent intent = new Intent(context, DownLoadService.class); intent.putExtra("status", "begin"); context.startService(intent); } } /** * 暴露此方法用來取消下載(會刪除該下載檔案) */ public void cancelDownLoad() { isCancled = true; } }

執行緒已經準備好了,這裡我們打算在服務中開啟此執行緒,我們寫一個服務

DownLoadService 程式碼裡都有註釋

ackage com.huida.notificationdemo;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.widget.RemoteViews;
import android.widget.Toast;

import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;

/**
 * Created by Json on 2017/9/19.
 */
public class DownLoadService extends Service {
    private static boolean isPause = true;//用來判斷暫停還是繼續下載
private DownLoadAsysncTask downLoadAsysncTask;
    //實現該介面  實現的方法會在downLoadAsysncTask進行回撥
private inControllerDownLoad inControllerDownLoad = new inControllerDownLoad() {
        @Override
public void onProgress(int progress) {
            /**
             * getNotificationManager().notify();開啟通知欄
             * 兩個引數
             * int id:給我們的Notification 設定id 唯一標識
             * Notification:需要我們寫的通知欄
             */
getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @Override
public void onCancle() {

            stopForeground(true);//取消 通知關閉
getNotificationManager().notify(1, getNotification("取消", -1));
            getNotificationManager().cancel(1);
            downLoadAsysncTask=null;
        }

        @Override
public void onPaused() {
            getNotificationManager().notify(1, getNotification("暫停", -1));
            Toast.makeText(DownLoadService.this, "暫停", Toast.LENGTH_SHORT).show();
        }

        @Override
public void onSuccess() {
            getNotificationManager().notify(1, getNotification("下載完成", -1));
            Toast.makeText(DownLoadService.this, "DownLoadSuccess", Toast.LENGTH_SHORT).show();
            stopForeground(true);//下載成功 通知關閉
getNotificationManager().cancel(1);
            downLoadAsysncTask=null;
        }

        @Override
public void onFailed() {
            stopForeground(false);
            getNotificationManager().notify(1, getNotification("下載失敗", -1));
            Toast.makeText(DownLoadService.this, "DownLoadFailed", Toast.LENGTH_SHORT).show();
        }
    };

    /**
     *
     * @param s 通知欄顯示的資料
     * @param progress 通知欄上progress的進度
     * @return
*/
private Notification getNotification(String s, int progress) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        //使用RemoteViews進行自定義通知欄
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.download_contllor);

        //暫停按鈕的點選事件
Intent pauseIntent = new Intent(this, DownLoadService.class);
        pauseIntent.putExtra("status", "pause");
        /*PendingIntent 相當於Inteng進行封裝了一層 這個通過getService()得到,
         也可以通過getBroadcast(),與我們需要的意圖Intent第二引數型別相對應
         引數1 上下文
         引數2 int requestCode 該PendingIntent的唯一標識,不能重複,否則會判定為同一個PendingIntent 使用者自己定義
         引數3 PendingIntent.FLAG_UPDATE_CURRENT 表示 通過requestCode 判斷是否有該PendingIntent 如果有進行更新
        */
PendingIntent pausePendingIntent = PendingIntent.getService(this, 1, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        //給RemoteViews設定點選事件 表示子控制元件的點選事件
remoteViews.setOnClickPendingIntent(R.id.bt_pause, pausePendingIntent);


        //取消按鈕的點選事件
Intent goneIntent = new Intent(this, DownLoadService.class);
        goneIntent.putExtra("status", "gone");
        PendingIntent canclePendingIntent = PendingIntent.getService(this, 0, goneIntent, FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.bt_cancle, canclePendingIntent);
        //此通知的點選事件 隨意跳轉了 到MainActivity中
Intent goMainActivity=new Intent(this,MainActivity.class);
        remoteViews.setOnClickPendingIntent(R.id.remoteView
,PendingIntent.getActivity(this, 2, goMainActivity, FLAG_UPDATE_CURRENT));
        builder.setContent(remoteViews);
        remoteViews.setProgressBar(R.id.seek, 100, progress, false);
        remoteViews.setTextViewText(R.id.tv, s);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setWhen(SystemClock.currentThreadTimeMillis());

        return builder.build();
    }

    /**
     * 獲取到系統NotificationManager
     * @return
*/
private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

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

    @Override
public int onStartCommand(Intent intent, int flags, int startId) {
        String status = intent.getStringExtra("status");
        switch (status) {
            case "begin":
                startDownLoad();
                break;
            case "gone":
                if (downLoadAsysncTask != null) {
                    downLoadAsysncTask.cancelDownLoad();
                }
                break;
            case "pause":
                if (downLoadAsysncTask != null) {
                    downLoadAsysncTask.pausedDownLoad(isPause);
                    isPause = !isPause;
                }
                break;
        }
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 開啟DownLoadAsysncTask傳入我們實現的介面 和上下文
     */
private void startDownLoad() {
        downLoadAsysncTask = new DownLoadAsysncTask(inControllerDownLoad, this);
        downLoadAsysncTask.execute("http://mpge.5nd.com/2016/2016-11-15/74847/1.mp3");

    }
}

此時基本完事 別忘了去AndroidManifest.xml將服務註冊,在
MainActivity直接呼叫開啟服務

package com.huida.notificationdemo;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //進來我直接呼叫服務進行開啟下載執行緒
findViewById(R.id.bt_start_download).setOnClickListener(new View.OnClickListener() {
           @Override
public void onClick(View view) {
               Intent intent = new Intent(MainActivity.this, DownLoadService.class);
               intent.putExtra("status","begin");
               startService(intent);
           }
       });
    }
}


基本一個簡單的通知欄下載進度完成

思路可以學習一下,我也是自行寫的demo可能還有很多不完善的地方大家見諒