10探究服务-下载实例
2018-04-02 本文已影响24人
何惧l
编写一个完整版的下载实例
- 在项目中添加使用的依赖库,编辑build.gradle文件
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
}
- 只需添加这个OkHttp依赖就可以了
- 定义回调接口,用于对下载过程中的各种状态进行监听和回调,建一个接口DownloadListener
// 自定义一个回调接口
public interface DownloadListener {
// 用于通知当前下载的进度
void onProgress(int progress);
// 用于通知下载成功事件
void onSuccess();
// 下载失败事件
void onFailed();
//下载暂停事件
void onPaused();
// 下载取消事件
void onCanceled();
}
- 编写下载功能,使用AsyncTask来进行实现,DownloadTask继承自AsyncTask
// 编写下载功能,第一个参数表示执行这个的时候要传入一个参数给后台
//第二个参数表示使用整型来作为进度条单位
// 第三个参数用整型表示返回的结果类型
public class DownloadTask extends AsyncTask<String,Integer,Integer> {
// 成功
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 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... strings) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try{
// 记录已经下载的文件长度
long downloadedLength = 0;
// 获取下载的URL地址
String downloadUrl = strings[0];
// 根据下载的URL地址解析出下载的文件名
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
// 指定将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是sd卡的Download目录下
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;
}else if (contentLength == downloadedLength){
// 已下载的字节和文件的总字节相等,说明已经下载成功了
return TYPE_SUCCESS;
}
// 这里使用的是OkHttp发送一个网络请求,
// 注意这里在请求中添加了一个header,用于告诉服务器想要从那个字节开始读取
// 因为下载获得部分就不用再下载了
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.addHeader("RANGE","bytes="+downloadedLength+"-")
.url(downloadUrl)
.build();
//Request对象就是服务器返回的数据
Response response = client.newCall(request).execute();
//进行判断
// 读取服务器响应的数据,并使用java流的方法,不断的读取,不断的写入,
// 在这个过程中要判断用户是否点击了暂停或者取消,要做出响应的判断
// 如果一切顺利的话,则实时计算当前的下载进度,调用 publishProgress()方法进行通知
if (request != null){
// 获取返回的数据
is = response.body().byteStream();
savedFile = new RandomAccessFile(file,"rw");
// 这里跳过已经下载的字节
savedFile.seek(downloadedLength);
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1){
if (isCanceled){
// 取消
return TYPE_CANCELED;
}else if (isPaused){
// 暂停
return TYPE_PAUSED;
}else {
total += len;
savedFile.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 (savedFile != null){
savedFile.close();
}
if (isCanceled && file != null){
file.delete();
}
}catch (Exception e){
e.printStackTrace();
}
}
return TYPE_FAILED;
}
//当后台任务中调用了publishProgress(Progress...)方法之后
// 用于更新当前下载的进度,首先获取到当前的下载速度和上次保存的下载速度进行对比
// 如果有变化的时候,就调用DownloadListener的onProgress()方法用于通知当前下载进度
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress){
// 接口中的方法,用于通知下载进度
listener.onProgress(progress);
lastProgress = progress;
}
}
// 这个方法中的参数就是doInBackground()方法中return返回的数据
// 通知最终下载的结果
@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_CANCELED:
listener.onCanceled();
break;
default:
break;
}
}
// 暂停
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 contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
}
- 具体的下载就是这样,为了让这个下载可以在后台一直运行,需要创建一个服务,
public class DownloadService extends Service {
private DownloadTask downloadTask;
// 下载的地址
private String downloadUrl;
// 首先创建一个DownloadListener匿名类实例,在这个匿名类中实现这五个方法
private DownloadListener listener = new DownloadListener() {
// 用于通知当前下载的进度
@Override
public void onProgress(int progress) {
// getNotificationManager().notify()方法,用于触发通知
// 这样就可以在下拉状态栏中实时看到当前的下载的进度
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;
Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show();
}
};
//创建一个下载的对象
private DownloadBinder mBinder = new DownloadBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// 借助这个可以让服务和活动之间进行通信
class DownloadBinder extends Binder{
// 开始下载
public void startDownload(String url){
if (downloadTask == null){
// 获取连接
downloadUrl = url;
// 创建一个DownloadTask的实例,把刚才的DownloadListener的实例传入进去
downloadTask = new DownloadTask(listener);
// 调用execute()方法开始下载,把下载地址传入
downloadTask.execute(downloadUrl);
// 为了让这个下载的服务成为一个前台的服务,调用这个startForeground()方法
// 这样就在系统的状态栏中创建了以一个持续运行的通知了
startForeground(1,getNotification("Downloadign...",0));
Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
}
}
// 暂停下载
public void pauseDownload(){
if (downloadTask != null){
// 简单的调用了DownloadTask的pauseDownload()方法
downloadTask.pauseDownload();
}
}
// 下载取消
public void cancelDownload(){
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,"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,MainActivity.class);
PendingIntent pi = 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.setContentIntent(pi);
builder.setContentTitle(title);
if (progress > 0){
builder.setContentText(progress + "%");
// 这个是设置进度条的,第一个参数是总长度,第二个是当前的长度,第三个是是否使用模糊进度条
builder.setProgress(100,progress,false);
}
return builder.build();
}
}
- 现在下载的服务也就成功的实现了,开始写前端页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/start_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start Download"/>
<Button
android:id="@+id/pause_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="pause Download"/>
<Button
android:id="@+id/cancel_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="cancel Download"/>
</LinearLayout>
- 这里就3个按钮,用于开始,暂停,取消
- 最后就是修改MainActivity中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private DownloadService.DownloadBinder downloadBinder;
//创建一个匿名类进行活动和事件的绑定
private ServiceConnection connection = new ServiceConnection() {
// 绑定成功的话
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
// 通过向下转型获取到了DownloadBinder的实例
// 有了这个实例就可以在活动中调用服务提供的各种方法了
downloadBinder = (DownloadService.DownloadBinder)iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startDownload = (Button)findViewById(R.id.start_download);
Button spauseDownload = (Button)findViewById(R.id.pause_download);
Button cancelDownload = (Button)findViewById(R.id.cancel_download);
startDownload.setOnClickListener(this);
spauseDownload.setOnClickListener(this);
cancelDownload.setOnClickListener(this);
Intent intent = new Intent(this,DownloadService.class);
// 启动服务
startService(intent);
// 绑定服务,第二个参数是前面创建ServiceConnection的实例
bindService(intent,connection,BIND_AUTO_CREATE);
// 运行时申请权限 WRITE_EXTERNAL_STORAGE
// 文件要下载到SD卡,所以要申请权限
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);
}
}
// 根据点击不同的按钮,调用服务中的不同方法
@Override
public void onClick(View view) {
if (downloadBinder == null){
return;
}
switch (view.getId()){
case R.id.start_download:
//String url = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
String url = "https://dl.hdslb.com/mobile/latest/iBiliPlayer-bili.apk";
downloadBinder.startDownload(url);
break;
case R.id.pause_download:
downloadBinder.pauseDownload();
break;
case R.id.cancel_download:
downloadBinder.cancelDownload();
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(this,"拒接权限将无法使用程序",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
// 当活动被销毁的时候,对服务进行解绑,不然会造成内存的泄漏
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
- 当然了,在AndroidManifest.xml文件中还要声明使用到的权限,以及服务
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.md.servertest">
<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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".DownloadService"
android:enabled="true"
android:exported="true" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
-
这个时候运行程序就可以了,同意权限,点击开始下载按钮,
下载主页面.png
-
然后下拉通知栏就可以看到
正在下载.png -
完成后就在文件管理器中找到这个文件
b站.png - 这个时候就可以点击安装了
安装.png
这个实例把前面的都复习了一遍