Service实战:使用Service完成一个下载任务
来源:互联网 发布:邮政网络银行 编辑:程序博客网 时间:2024/06/05 23:00
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的解析算是比较全面了!
- Service实战:使用Service完成一个下载任务
- Service的实战:下载的实现
- 使用Service进行后台下载
- Service的使用:断点下载
- 写一个Windows Service定时任务
- 远程service下载,使用aidl,队列下载service端
- Service 入门——service调用执行顺序&利用service模拟完成一个后台服务
- 使用angular创建一个service
- 使用DownloadProvider来完成下载任务
- 使用DownloadProvider来完成下载任务
- 使用DownloadProvider来完成下载任务
- 使用DownloadProvider来完成下载任务
- Android 实现service后台多任务下载notification进度条更新
- 【No3.】实现使用Service下载APK文件
- Android 使用service实现多线程断点续传下载
- [待完成]Android Service的<service>标签
- 3.14 Web Service任务
- Service+AlarmManager 定时任务
- IOCTL函数的实现
- Android SDK 环境变量配置-Windows7-20170427
- Xutils笔记
- Apache server.xml详解
- st表求最值
- Service实战:使用Service完成一个下载任务
- 【PM】【需求】项目管理-需求:业务需求调研经验分享
- 抓取网易云音乐歌曲热门评论生成词云
- 闲聊
- 两块磁盘一起读写的设置方法
- 操作系统Unix、Windows、Mac OS、Linux的故事
- 求极值--三分
- IDEA创建工程
- 1008. Elevator (20)