【记录】使用Service下载文件,并通过Notification显示
简单记录,便于以后自己查看
整个案例使用到AsyncTask、Service、ForegroundService、OkHttp,下面看代码
首先定义一个接口类,监听下载过程中的各种状态,有注释,就不解释了
public interface DownloadListener { /** 下载进度 * @param progress */ void onProgress(int progress); /**下载成功*/ void onSuccess(); /**下载失败*/ void onFailed(); /**下载暂停*/ void onPaused(); void onCanceled(); }
在创建一个继承AsyncTask的DownloadTask类,该类负责下载的主要任务,然后通过接口的方式告知下载情况。该类在下载文件的时候,文件名是以最后一个斜杠/后面的内容为名。下载过程是通过OkHttp完成的,其中RequestUtil.getOkHttpClient()可以直接new 一个OkHttpClient,较为简单就不贴出来了。
在下载之前主要是通过添加了一个header来完成的断点续传功能,然后就是Java的IO操作
public class DownloadTask extends AsyncTask<String, Integer, Integer> { private DownloadListener downloadListener; // 四种状态 private static final int TYPE_SUCCESS = 0; private static final int TYPE_FAILED = 1; private static final int TYPE_PAUSED = 2; private static final int TYPE_CANCELED = 3; // 下载控制标记 private boolean isCanceled = false; private boolean isPaused = false; // 最后的下载进度 private int lastProgress; public DownloadTask(DownloadListener downloadListener) { this.downloadListener = downloadListener; } @Override protected Integer doInBackground(String... params) { RandomAccessFile saveFile = null; InputStream input = null; File file = null; try { long downloadLength = 0;//用于记录下载的文件长度 String downloadUrl = params[0]; String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); file = new File(directory + fileName); if (file.exists()) {//如果有文件就记录下载的文件长度 downloadLength = file.length(); } long contentLength = getContentLength(downloadUrl); if (contentLength == 0) { return TYPE_CANCELED; } else if (contentLength == downloadLength) { // 已下载字节跟文件总字节相等,说明已经下载完毕了 return TYPE_SUCCESS; } OkHttpClient okHttpClient = RequestUtil.getOkHttpClient(); Request request = new Request.Builder() // 断点下载,指定从哪个字节开始下载 .addHeader("RANGE", "bytes=" + downloadLength + "-") .url(downloadUrl) .build(); Response response = okHttpClient.newCall(request).execute(); if (request != null) { input = response.body().byteStream(); saveFile = new RandomAccessFile(file, "rw"); saveFile.seek(downloadLength);// 跳过已下载的字节 byte[] buffer = new byte[1024]; int total = 0; int len; while ((len = input.read(buffer)) != -1) { if (isCanceled) { return TYPE_CANCELED; } else if (isPaused) { return TYPE_PAUSED; } else { total += len; saveFile.write(buffer, 0, len); //计算已下载百分比 int progress = (int) ((total + downloadLength) * 100 / contentLength); publishProgress(progress); } } response.body().close(); return TYPE_SUCCESS; } } catch (IOException e) { e.printStackTrace(); } finally { try { if (input != null) { input.close(); } if (saveFile != null) { saveFile.close(); } if (isCanceled) { file.delete(); } } catch (Exception e) { e.printStackTrace(); } } return TYPE_FAILED; } @Override protected void onProgressUpdate(Integer... values) { int progress = values[0]; if (progress > lastProgress) { downloadListener.onProgress(progress); lastProgress = progress; } } @Override protected void onPostExecute(Integer status) { switch (status) { case TYPE_SUCCESS: downloadListener.onSuccess(); break; case TYPE_PAUSED: downloadListener.onPaused(); break; case TYPE_FAILED: downloadListener.onFailed(); break; case TYPE_CANCELED: downloadListener.onCanceled(); break; default: break; } } /** * 获取下载文件长度 * @param downloadUrl 文件下载地址 */ private long getContentLength(String downloadUrl) throws IOException { OkHttpClient okHttpClient = RequestUtil.getOkHttpClient(); Request request = new Request.Builder() .url(downloadUrl) .build(); Response response = okHttpClient.newCall(request).execute(); if (request != null && response.isSuccessful()) { return response.body().contentLength(); } return 0; } /**暂停下载*/ public void pauseDownload() { isPaused = true; } /**取消下载*/ public void cancelDownload() { isCanceled = true; } }
再看DownloadService,整个状态的处理都是在DownloadService中完成的,除了进度回调,其它回调都把DownloadTask置为null,这样在下次点击下载的时候都会重新创建一个DownloadTask。每个回调都使用Notification来显示下载情况。不用担心重复下载,因为有断点续传功能的。随后在DownloadService中定义了一个Binder类,并提供三个方法,分别的开始下载、暂停下载、取消下载。取消下载会把没有下载完的文件给删除掉。
public class DownloadService extends Service { private DownloadTask downloadTask; private String downloadUrl; private DownloadListener downloadListener = new DownloadListener() { @Override public void onProgress(int progress) { getNotificationManager().notify(1, getNotification("Downloading...", progress)); } @Override public void onSuccess() { downloadTask = null; // 下载完成时将前台服务通知关闭,并创建一个下载成功通知 stopForeground(true); getNotificationManager().notify(1, getNotification("Download Success", -1)); Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show(); } @Override public void onFailed() { downloadTask = null; // 下载失败时将前台服务通知关闭,并创建一个下载成功通知 stopForeground(true); getNotificationManager().notify(1, getNotification("Download Failed", -1)); Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show(); } @Override public void onPaused() { downloadTask = null; Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_SHORT).show(); } @Override public void onCanceled() { downloadTask = null; stopForeground(true); Toast.makeText(DownloadService.this, "Download Canceled", Toast.LENGTH_SHORT).show(); } }; private DownloadBinder mBinder = new DownloadBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } class DownloadBinder extends Binder { public void startDownload(String url) { if (downloadTask == null) { downloadUrl = url; downloadTask = new DownloadTask(downloadListener); downloadTask.execute(downloadUrl); // 使用startForeground方法,变成前台Service startForeground(1, getNotification("Downloading...", 0)); Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show(); } } public void pauseDownload() { if (downloadTask != null) { downloadTask.pauseDownload(); } } public void cancelDownload() { if (downloadTask != null) { downloadTask.cancelDownload(); } if (downloadUrl != null) { // 取消下载文件时需将文件删除,将通知关闭 String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); File file = new File(directory + fileName); if (file.exists()) { file.delete(); } getNotificationManager().cancel(1); stopForeground(true); Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show(); } } } private NotificationManager getNotificationManager() { return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } private Notification getNotification(String title, int progress) { Intent intent = new Intent(this, PhotoAlbumActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setContentTitle(title); builder.setContentIntent(pendingIntent); builder.setSmallIcon(R.mipmap.ic_launcher); if (progress >= 0) { // 当progress 大于0时才需要显示下载进度 builder.setContentText(progress + "%"); builder.setProgress(100, progress, false); } return builder.build(); } }下面就可以通过绑定Service得到DownloadBinder来下载文件了,看Activity代码。Activity中比较简单,一个下载地址输入框,三个按钮分别是下载、暂停、取消。通过绑定Service得到DownloadBinder,在点击事件里调用DownloadBinder的方法完成下载操作
public class ServiceDownloadActivity extends AppCompatActivity implements View.OnClickListener { private DownloadService.DownloadBinder downloadBinder; private EditText downloadEdit; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder = (DownloadService.DownloadBinder) service; } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle("ServiceDownload"); setContentView(R.layout.activity_service_download); downloadEdit = (EditText) findViewById(R.id.service_download_edit); findViewById(R.id.service_download_start).setOnClickListener(this); findViewById(R.id.service_download_pause).setOnClickListener(this); findViewById(R.id.service_download_cancel).setOnClickListener(this); Intent intent = new Intent(this, DownloadService.class); bindService(intent, serviceConnection, BIND_AUTO_CREATE); // 绑定服务 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 111); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case 111: if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "权限被拒绝,无法使用 下载功能", Toast.LENGTH_SHORT).show(); } break; } } @Override public void onClick(View view) { switch (view.getId()) { case R.id.service_download_start: String downloadUrl = downloadEdit.getText().toString().trim(); if (!TextUtils.isEmpty(downloadUrl)) { downloadBinder.startDownload(downloadUrl); } else { Toast.makeText(this, "下载地址不能为空", Toast.LENGTH_SHORT).show(); } break; case R.id.service_download_pause: downloadBinder.pauseDownload(); break; case R.id.service_download_cancel: downloadBinder.cancelDownload(); break; default: break; } } }
Activity布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp"> <EditText android:id="@+id/service_download_edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="input download url" android:maxLines="5" android:text="https://res9.d.cn/m/yxzx.apk" /> <Button android:id="@+id/service_download_start" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="start download" android:textAllCaps="false"/> <Button android:id="@+id/service_download_pause" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="pause download" android:textAllCaps="false"/> <Button android:id="@+id/service_download_cancel" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="cancel download" android:textAllCaps="false"/> </LinearLayout>
至此,整个下载案例就完成了。简单记录,便于自己以后查看。
出自《第一行代码 第二版》