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