第一行代码中的下载服务最佳实践
第一行代码现在服务的最佳实践
废话不多说,直接开始
原理分析
在安卓手机中,如果需要从一个网站上下载一个文件,该如何实现。
1.首先考虑到从网络上下载文件由于各种原因会造成占用时间很长的问题,如果时间很久,会造成程序的主界面失去响应的问题,因此,一定要把下载的功能方法另一个线程中,下载线程主要任务就是下载文件与主线程互相工作,不至于造成界面失去响应的问题。
2.在下载的过程中,用户有可能等待的不耐烦,把程序界面关掉做其他事情去了,如果这个时候下载线程也停止工作,会造成用户体验效果不好,于是要即使用户关掉了界面,下载仍然要继续进行,这就需要使用到服务。
3.服务与主线程是一起工作的,所以在原始的服务中是不能放置占据长时间任务的工作,如果要放置长时间工作,需要手动在服务里开辟线程。
4.既然在服务里需要开辟线程,可以将下载的线程与服务结合,这就有了第一次的组合,下载线程与服务组合在一起
5.由于下载线程单独工作在另一个线程中,这个线程工作不受主线程的控制,下载线程进行到什么程度(下载成功?下载失败?下载暂停?)也没办法通知主线程,怎么办,只有使用回调了!
6主线程还想在下载的过程中,想控制一下下载的进度,比如暂停下载、取消下载,怎么办?绑定服务,在绑定服务的时候,服务里应该有一个Binder类,这个类生成的对象在Activity绑定服务的时候能够返回给Activity,这样Activity就可以调用Binder对象里的方法了。
7.Activity绑定服务的时候,怎么才能获取到Binder对象呢?Activity绑定服务的时候,传递进去一个ServiceConnection对象,当绑定成功的时候,会回调ServiceConnection对象的onServiceConnected方法,这个方法就会把Binder对象带回来!
8.最后一步,怎么把下载线程和服务放到一起?将下载线程作为服务类的私有成员变量就可以了。
总而言之,这个过程是比较繁琐的。
好了,上代码!
代码
1.接口定义,主要是下载线程进行到某个阶段回调的函数
public interface DownloadListener {
void onProgress(int progress);//正在下载
void onSucess();//下载成功
void onFailed();//下载失败
void onPaused();//下载暂停
void onCanceled();//下载取消
}
2.下载线程,采用异步任务类来实现
package cn.edu.yau.servicebestpractice;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ListIterator;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by Administrator on 2018-08-09.
*/
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_PAUSE=2;
public static final int TYPE_CANCELED=3;
private DownloadListener listener;
private boolean isCanceled=false;
private boolean isPaused=false;
private int lastProgress;
public DownloadTask(DownloadListener listener) {
this.listener=listener;
}
@Override
protected Integer doInBackground(String... params) {//这个会自动开线程并执行下载任务
Log.d("Download", "异步下载任务: "+Thread.currentThread().getId());
InputStream is=null;
RandomAccessFile saveFile=null;
File file=null;
try{
long downloadedLength=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())
{
downloadedLength=file.length();
}
long contentLength=getContentLength(downloadUrl);
if(contentLength==0)
return TYPE_FAILED;
if(contentLength==downloadedLength)
return TYPE_SUCCESS;
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder()
.url(downloadUrl)
.addHeader("RANGE","bytes="+downloadedLength+"-")
.build();
Response response=client.newCall(request).execute();
if(response!=null){
is=response.body().byteStream();
saveFile=new RandomAccessFile(file,"rw");
saveFile.seek(downloadedLength);
byte[] b=new byte[512];
int total=0;
int len;
while((len=is.read(b))!=-1){
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPaused) {
Log.d("Download", "下载暂停: "+Thread.currentThread().getId());
return TYPE_PAUSE;
} else {
total += len;
saveFile.write(b, 0, len);
int progress = (int) ((total + downloadedLength) * 100 / contentLength);
// 通知进度条更新
publishProgress(progress);
}
}
}
response.body().close();
return TYPE_SUCCESS;
}
catch (Exception e)
{
e.printStackTrace();
}
finally {
try {
if(is!=null)
is.close();
if(saveFile!=null)
saveFile.close();
if(isCanceled&&file!=null)
file.delete();
}catch (Exception e){
e.printStackTrace();
}
}
return TYPE_FAILED;
}
@Override
protected void onPostExecute(Integer status) {
switch (status)
{
case TYPE_SUCCESS:
listener.onSucess();
break;
case TYPE_FAILED:
listener.onFailed();
break;
case TYPE_CANCELED:
listener.onCanceled();
break;
case TYPE_PAUSE:
listener.onPaused();
break;
default:
break;
}
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
int progress=values[0];
if(progress>=lastProgress){
listener.onProgress(progress);
lastProgress=progress;
}
}
public void pauseDownload(){
isPaused=true;
}
public void cancelDownload(){
isCanceled=true;
}
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 contentLenght=response.body().contentLength();
response.close();
return contentLenght;
}
return 0;
}
}
3.构建服务,服务比较复杂,里面有下载线程,还有一个Binder类,Binder类中的startDownload方法可以启动下载线程,不过这个功能是返回给Activity,让Activity调用的
package cn.edu.yau.servicebestpractice;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
public class DownloadService extends Service {
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 onSucess() {
downloadTask=null;
stopForeground(true);
getNotificationManager().notify(1,getNotification("下载成功",-1));
Toast.makeText(DownloadService.this,"下载成功",Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed() {
downloadTask=null;
stopForeground(true);
getNotificationManager().notify(1,getNotification("下载失败",-1));
Toast.makeText(DownloadService.this,"下载失败",Toast.LENGTH_SHORT).show();
}
@Override
public void onPaused() {
downloadTask=null;
Toast.makeText(DownloadService.this,"下载暂停",Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled() {
downloadTask=null;
stopForeground(true);
Toast.makeText(DownloadService.this,"下载取消",Toast.LENGTH_SHORT).show();
}
};
class DownloadBinder extends Binder
{
public void startDownload(String url)
{
if(downloadTask==null)
{
downloadUrl=url;
downloadTask =new DownloadTask(listener);
downloadTask.execute(downloadUrl);
startForeground(1,getNotification("开始下载...",0));
Toast.makeText(DownloadService.this,"开始下载...",Toast.LENGTH_SHORT).show();
}
}
public void pauseDownload()
{
if(downloadTask!=null) {
downloadTask.pauseDownload();
Toast.makeText(DownloadService.this,"下载暂停",Toast.LENGTH_SHORT).show();
}
}
public void cancleDownload()
{
if(downloadTask!=null)
downloadTask.cancelDownload();
else
{
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,"下载取消",Toast.LENGTH_SHORT).show();
}
}
}
}
private DownloadBinder mBinder=new DownloadBinder();
private NotificationManager getNotificationManager()
{
return (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
}
private Notification getNotification(String title,int progress)
{
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
builder.setContentTitle(title);
builder.setContentIntent(pendingIntent);
if(progress>0){
builder.setContentText(progress+"%");
builder.setProgress(100,progress,false);
}
return builder.build();
}
@Override
public IBinder onBind(Intent intent) {
Log.d("Download", "onBind: "+Thread.currentThread().getId());
return mBinder;
}
}
4.构建界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始下载"/>
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂停下载"/>
<Button
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="取消下载"/>
</LinearLayout>
5.Acvitity的代码
package cn.edu.yau.servicebestpractice;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private DownloadService.DownloadBinder downloadBinder;
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
downloadBinder=(DownloadService.DownloadBinder)service;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_start=(Button)findViewById(R.id.start);
btn_start.setOnClickListener(this);
Button btn_pause=(Button)findViewById(R.id.pause);
btn_pause.setOnClickListener(this);
Button btn_cancel=(Button)findViewById(R.id.cancel);
btn_cancel.setOnClickListener(this);
Intent intent=new Intent(MainActivity.this,DownloadService.class);
startService(intent);
bindService(intent,connection,BIND_AUTO_CREATE);
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
Log.d("Download", "onCreate: "+Thread.currentThread().getId());
}
@Override
public void onClick(View view) {
switch (view.getId())
{
case R.id.start:
downloadBinder.startDownload("http://10.0.2.2/a.pdf");
break;
case R.id.pause:
downloadBinder.pauseDownload();
break;
case R.id.cancel:
downloadBinder.cancleDownload();
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(MainActivity.this,"读写SD卡权限被拒绝",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
由于在里面使用了Okhttp功能,所以需要添加依赖库
compile 'com.squareup.okhttp3:okhttp:3.11.0'
另外需要权限声明和服务声明,所以Manifest文件内容如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.edu.yau.servicebestpractice">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".DownloadService"
android:enabled="true"
android:exported="true"></service>
</application>
</manifest>
基本写好了,可以运行了。
在模拟器上运行,不知道为什么暂停功能不能使用,不知道是不是因为在模拟器里运行的结果还是代码有问题,百度了很久,找到很多类似的代码,可是几乎没人说暂停功能不能使用的,不知道为什么,如果你也实现了,而且暂停效果良好,欢迎留言指正!!!!
上一篇: C语言链表学习--学生信息管理系统
下一篇: 《第一行代码》——活动