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的解析算是比較全面了!