目录
完整实例代码(模拟一个耗时操作 无限打印日志)此处使用DataBinding
服务还可以使用Android 接口定义语言 (AIDL)用于进程间通信但我个人认为其不应归纳于基础使用,有时间会另行总结。
服务简述
搬运官方文档的描述如下 服务概览 | Android 开发者 | Android Developers
Service
是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。
以下是三种不同的服务类型
前台服务
前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。
后台服务
后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。
绑定服务
当应用组件通过调用 bindService()
绑定到服务时,服务即处于绑定状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
虽然本文档分开概括讨论启动服务和绑定服务,但您的服务可同时以这两种方式运行,换言之,它既可以是启动服务(以无限期运行),亦支持绑定。唯一的问题在于您是否实现一组回调方法:onStartCommand()
(让组件启动服务)和 onBind()
(实现服务绑定)。
无论服务是处于启动状态还是绑定状态(或同时处于这两种状态),任何应用组件均可像使用 Activity 那样,通过调用 Intent
来使用服务(即使此服务来自另一应用)。不过,您可以通过清单文件将服务声明为私有服务,并阻止其他应用访问该服务。使用清单文件声明服务部分将对此做更详尽的阐述。
注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。通过使用单独的线程,您可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。
讲人话
就是不需要页面也能跑的代码,当你有些操作不需要进行交互但又要他干活就用服务。
Service的创建和注册
创建
新建一个类继承Service就可以,一般需要重写onCreate()、onStartCommand()、onDestroy()、onBind()
package com.example.servicrdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
private String TAG = "MyService";
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 服务创建时调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate:");
}
/**
* 每次服务启动时调用
*
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand:");
return super.onStartCommand(intent, flags, startId);
}
/**
* 服务销毁时调用
*/
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy:");
super.onDestroy();
}
}
注册
Android四大组建都是在清单文件里注册的
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
enabled属性:是指该服务是否能够被实例化。如果设置为true,则能够被实例化,否则不能被实例化。默认值是true。
exported属性:用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互(通常如果一个服务需要跨进程使用需要这么设置),设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。这个还是很重要的,当我们的服务或者其他组件不需要对外开放时请设置false。不然会存在一些安全隐患。
方法介绍(官方文档搬运)
onStartCommand
当另一个组件(如 Activity)请求启动服务时,系统会通过调用 startService()
来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。如果您实现此方法,则在服务工作完成后,您需负责通过调用 stopSelf()
或 stopService()
来停止服务。(如果您只想提供绑定,则无需实现此方法。)
onBind
当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 bindService()
来调用此方法。在此方法的实现中,您必须通过返回 IBinder
提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果您并不希望允许绑定,则应返回 null。
onCreate()
首次创建服务时,系统会(在调用 onStartCommand()
或 onBind()
之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。
onDestroy()
当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。
如果组件通过调用 startService()
启动服务(这会引起对 onStartCommand()
的调用),则服务会一直运行,直到其使用 stopSelf()
自行停止运行,或由其他组件通过调用 stopService()
将其停止为止。
如果组件通过调用 bindService()
来创建服务,且未调用 onStartCommand()
,则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。
只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务。如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止;如果将服务声明为在前台运行,则其几乎永远不会终止。如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升—如果服务是启动服务,则您必须将其设计为能够妥善处理系统执行的重启。如果系统终止服务,则其会在资源可用时立即重启服务,但这还取决于您从 onStartCommand()
返回的值。如需了解有关系统会在何时销毁服务的详细信息,请参阅进程和线程文档。
Service的两种启动模式
startService()
启动
Intent startIntent = new Intent(MainActivity.this,MyService.class);
startService(startIntent);
关闭
Intent stopintent = new Intent(MainActivity.this,MyService.class);
stopService(stopintent)
启动 onCreate--->onStartCommand
再次启动 onStartCommand
关闭 onDestroy
bindService()
既然是以绑定的方式启动服务那么活动便可以很方便的调用服务中的方法
绑定服务是客户端-服务器接口中的服务器。借助绑定服务,组件(例如 Activity)可以绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。
首先写一个BInder类用于假设后台的下载操作
class DownloadBinder extends Binder{
public void statrtDownload(){
Log.d(TAG, "statrtDownload: ");
}
public int getProgress(){
Log.d(TAG, "getProgress: ");
return 0;
}
}
实例化对象并在onBind方法中进行返回操作
private DownloadBinder binder = new DownloadBinder();
@Override
public IBinder onBind(Intent intent) {
return binder;
}
在活动中声明ServiceConnection
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.statrtDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
绑定服务
Intent bindIntent = new Intent(MainActivity.this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE); // 绑定服务
解绑服务
unbindService(connection); // 解绑服务
绑定onCreate---->onBind
解绑onUnbind onDestroy 没有同时启动服务的情况下并所有绑定都以解绑服务 会同时销毁服务
Service生命周期
public class ExampleService extends Service {
@Override
public void onCreate() {
// 服务被创建时调用
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 服务启动时调用
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
// 绑定服务时调用
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// 解绑服务时调用
return true;
}
@Override
public void onRebind(Intent intent) {
// 解绑服务后再次绑定时 注意此时不会走onBind方法 切需要onUnbind返回true
// 注意 是在服务没有被销毁时重复绑定才会走此方法 服务被销毁后 再次绑定走的是onBind
}
@Override
public void onDestroy() {
// 服务销毁时调用
}
}
完整实例代码(模拟一个耗时操作 无限打印日志)此处使用DataBinding
MyService
package com.example.servicrdemo;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
private String TAG = "MyService";
private Boolean flag;
private DownloadBinder binder = new DownloadBinder();
public MyService() {
}
class DownloadBinder extends Binder{
public void statrtDownload(){
Log.d(TAG, "statrtDownload: ");
}
public int getProgress(){
Log.d(TAG, "getProgress: ");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return binder;
}
/**
* 服务创建时调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate:");
}
/**
* 每次服务启动时调用
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand:");
flag = true;
new Thread(new Runnable() {
@Override
public void run() {
while (true){
Log.d(TAG, "执行");
try {
Thread.sleep(500);
if(!flag){
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 逻辑处理完成后停用服务 stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
/**
* 服务销毁时调用
*/
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy:");
flag = false;
super.onDestroy();
}
}
MainActivity
package com.example.servicrdemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import com.example.servicrdemo.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.statrtDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
setContentView(binding.getRoot());
initView();
}
private void initView(){
binding.startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent startIntent = new Intent(MainActivity.this,MyService.class);
startService(startIntent);
}
});
binding.stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent stopintent = new Intent(MainActivity.this,MyService.class);
stopService(stopintent);
}
});
binding.startBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent bindIntent = new Intent(MainActivity.this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE); // 绑定服务
}
});
binding.stopBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
unbindService(connection); // 解绑服务
}
});
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/start_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartService"
android:layout_marginTop="100dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/stop_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopService"
android:layout_marginTop="30dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_service" />
<Button
android:id="@+id/start_bind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartBind"
android:layout_marginTop="30dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_service" />
<Button
android:id="@+id/stop_bind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopBind"
android:layout_marginTop="30dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_bind" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
IntentService
ntentService是Android中的一个系统封装类,继承自四大组件之一的Service,主要用于处理异步请求,实现多线程,它有以下特点:
是一种特殊的Service,继承自Service并且本身就是一个抽象类。
用于在后台执行耗时的异步任务,当任务完成后会自动停止。
内部通过HandlerThread和Handler实现异步操作。
创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作。
优点,省的自己创建线程和关服务了
官方描述
由于大多数启动服务无需同时处理多个请求(实际上,这种多线程情况可能很危险),因此最佳选择是利用 IntentService
类实现服务。
IntentService
类会执行以下操作:
- 创建默认的工作线程,用于在应用的主线程外执行传递给
onStartCommand()
的所有 Intent。 - 创建工作队列,用于将 Intent 逐一传递给
onHandleIntent()
实现,这样您就永远不必担心多线程问题。 - 在处理完所有启动请求后停止服务,因此您永远不必调用
stopSelf()
。 - 提供
onBind()
的默认实现(返回 null)。 - 提供
onStartCommand()
的默认实现,可将 Intent 依次发送到工作队列和onHandleIntent()
实现。
如要完成客户端提供的工作,请实现 onHandleIntent()
。不过,您还需为服务提供小型构造函数。
使用也简单 直接继承IntentService 耗时操作直接写到onHandleIntent里剩下的就不用管了
package com.example.servicrdemo;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyIntentService extends IntentService {
private String TAG = "MyIntentService";
/**
* @deprecated
*/
public MyIntentService() {
super("MyService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d(TAG,"onHandleIntent"+Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
启动
Intent intentService = new Intent(MainActivity.this,MyIntentService.class);
startService(intentService);
前台服务
官方描述
前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。
注意:请限制应用使用前台服务。
只有当应用执行的任务需供用户查看(即使该任务未直接与应用交互)时,您才应使用前台服务。因此,前台服务必须显示优先级为 PRIORITY_LOW
或更高的状态栏通知,这有助于确保用户知道应用正在执行的任务。如果某操作不是特别重要,因而您希望使用最低优先级通知,则可能不适合使用服务;相反,您可以考虑使用计划作业。
每个运行服务的应用都会给系统带来额外负担,从而消耗系统资源。如果应用尝试使用低优先级通知隐藏其服务,则可能会降低用户正在主动交互的应用的性能。因此,如果某个应用尝试运行拥有最低优先级通知的服务,则系统会在抽屉式通知栏的底部调用出该应用的行为。
其使用也并不复杂
调用 startForeground()
即可。
此方法采用两个参数:唯一标识通知的整型数和用于状态栏的 Notification
。
需要注意的是从Android9开始使用前台服务需要在清单文件声明如下权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Android8.0 开始要求设置通知渠道Notification Channel 具体使用请看代码
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate:");
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivities(this,0, new Intent[]{intent},0);
NotificationCompat.Builder notification = null;
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
// ID,名字,重要度(IMPORTANCE_HIGH 开启通知,会弹出,发出提示音,状态栏中显示) 其他重要度用时查询
NotificationChannel channel = new NotificationChannel("com.example.servicrdemo","Channel One", NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
manager.createNotificationChannel(channel);
// 设置通知的ID
notification = new NotificationCompat.Builder(MyService.this).setChannelId("com.example.servicrdemo");
} else {
notification = new NotificationCompat.Builder(this);
}
if(notification != null){
notification.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setContentIntent(pi);
startForeground(1,notification.build());
}
}
正常启动即可 此时在通知栏就有了一个前台的服务
小示例(第一行代码第二版的下载器)
接口
public interface IDownloadListener {
void onProgress(int progress); // 下载进度
void onSuccess(); // 下载成功
void onFailed(); // 下载失败
void onPaused(); // 下载暂停
void onCanceled(); // 下载取消
}
任务栈
package com.example.downloaddemo.task;
import android.os.AsyncTask;
import android.os.Environment;
import android.webkit.DownloadListener;
import com.example.downloaddemo.inter.IDownloadListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* String 执行时所需传入的对象
* Integer 进度
* Integer 结果
*/
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_CANCELED = 3;
private IDownloadListener listener;
private boolean isCanceled = false;
private boolean isPaused = false;
private int lastProgress;
public DownloadTask(IDownloadListener listener) {
this.listener = listener;
}
@Override
protected Integer doInBackground(String... strings) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try {
long downloadedLength = 0L;
String downloadUrl = strings[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;
} else if (contentLength == downloadedLength) {
// 已下载字节和文件总字节数相等 表明文件已经下载完成
return TYPE_SUCCESS;
}
// 开始下载 并设置断点传输
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
// 断点下载,指定所需要下载的位置
.addHeader("RANGE", "bytes=" + downloadedLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != 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 = 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;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
listener.onProgress(progress);
lastProgress = progress;
}
}
@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 canceDownload() {
isCanceled = 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;
}
}
服务
package com.example.downloaddemo.service;
import android.app.Notification;
import android.app.NotificationChannel;
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.Build;
import android.os.Environment;
import android.os.IBinder;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.example.downloaddemo.MainActivity;
import com.example.downloaddemo.R;
import com.example.downloaddemo.inter.IDownloadListener;
import com.example.downloaddemo.task.DownloadTask;
import java.io.File;
public class DownloadService extends Service {
private DownloadTask mDownloadTask;
private String mDownloadUrl;
private IDownloadListener listener = new IDownloadListener() {
@Override
public void onProgress(int progress) {
getNotificationManager().notify(1,getNotification("Downloading...",progress));
}
@Override
public void onSuccess() {
mDownloadTask = null;
// 下载成功时将前台服务通知关闭,并创建一个下载成功的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Success",-1));
Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_LONG).show();
}
@Override
public void onFailed() {
mDownloadTask = null;
// 下载失败关闭前台服务,创建一个下载失败的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Failed",-1));
Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_LONG).show();
}
@Override
public void onPaused() {
mDownloadTask = null;
Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_LONG).show();
}
@Override
public void onCanceled() {
mDownloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_LONG).show();
}
};
private DownloadBinder mBinder = new DownloadBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class DownloadBinder extends Binder {
public void startDownload(String url){
if(mDownloadTask == null){
mDownloadUrl = url;
mDownloadTask = new DownloadTask(listener);
mDownloadTask.execute(mDownloadUrl);
startForeground(1,getNotification("Downloading...",0));
Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_LONG).show();
}
}
public void pauseDownload(){
if(mDownloadTask != null ){
mDownloadTask.pauseDownload();
}
}
public void cancelDownload(){
if(mDownloadTask != null){
mDownloadTask.canceDownload();
}else {
if(mDownloadUrl != null){
// 取消下载删除已经下载的文件换粗
String fileName = mDownloadUrl.substring(mDownloadUrl.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,"Downloading...",Toast.LENGTH_LONG).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.getActivities(this,0, new Intent[]{intent},0);
NotificationCompat.Builder notification = null;
NotificationManager manager = getNotificationManager();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
NotificationChannel channel = new NotificationChannel("com.example.downloaddemo","Channel One",NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
manager.createNotificationChannel(channel);
notification = new NotificationCompat.Builder(this).setChannelId("com.example.downloaddemo");
} else {
notification = new NotificationCompat.Builder(this);
}
if(notification != null){
notification.setContentTitle(title)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setContentIntent(pi);
if(progress >= 0){
notification.setContentText(progress+"%");
notification.setProgress(100,progress,false);
}
startForeground(1,notification.build());
}
return notification.build();
}
}
MainActivity
package com.example.downloaddemo;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Toast;
import com.example.downloaddemo.databinding.ActivityMainBinding;
import com.example.downloaddemo.service.DownloadService;
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
private DownloadService.DownloadBinder mDownloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mDownloadBinder = (DownloadService.DownloadBinder) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
setContentView(binding.getRoot());
Intent intent = new Intent(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);
}
initView();
}
private void initView() {
binding.startdownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String url = "下载地址";
mDownloadBinder.startDownload(url);
}
});
binding.pausedownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mDownloadBinder.pauseDownload();
}
});
binding.canceldownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mDownloadBinder.cancelDownload();
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,"没得权限",Toast.LENGTH_LONG).show();
finish();
}
break;
default:
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/startdownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Download"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="150dp"
/>
<Button
android:id="@+id/pausedownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause Download"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/startdownload"
android:layout_marginTop="150dp"
/>
<Button
android:id="@+id/canceldownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel Download"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/pausedownload"
android:layout_marginTop="150dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
清单
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.downloaddemo">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<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/Theme.DownloadDemo"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
>
<service
android:name=".service.DownloadService"
android:enabled="true"
android:exported="true"></service>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>