使用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可能還有很多不完善的地方大家見諒




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