学习service相关知识需要先学习java线程相关知识
概念
介绍
在Android中,Service是一种可以在后台执行长时间运行操作而不提供用户界面的组件。它主要用于执行长时间运行的任务,例如播放音乐、下载文件、处理网络请求等,而不会阻塞用户与应用程序的交互。Service可以在应用程序的后台运行,并且即使用户切换到其他应用程序,Service也可以继续执行。
作用
Service的主要作用包括:
- 执行长时间运行的操作:Service可以用于执行需要较长时间才能完成的任务,例如在后台下载文件、处理网络请求等。
- 提供后台音乐播放:通过Service,应用程序可以在后台播放音乐或者音频流,而不受用户切换到其他应用程序的影响。
- 处理后台网络请求:Service可以用于在后台执行网络请求,例如从服务器获取数据或者上传数据,而不会阻塞用户界面。
- 监听传感器数据:某些应用程序可能需要在后台持续监听传感器数据,例如位置信息、加速度计数据等,这时候可以使用Service来实现。
- 执行定时任务:Service可以用于执行定时任务,例如定期从服务器获取数据更新应用程序的内容。
- 后台处理广播事件:Service可以用于处理后台广播事件,例如接收系统广播或者自定义广播,并做出相应的处理。
总的来说,Service在Android应用程序中扮演着后台任务执行和长时间运行操作的角色,它使得应用程序可以在后台执行一些耗时的操作,同时保持用户界面的响应性。
与线程的不同
服务是主线程的一部分,也就是说他完全依赖与主线程,但是这个的前提是不在服务中创建线程,如果创建线程了就不是了.
Service的使用
前提(定义一个服务)
直接android studio创建一个service类
package com.example.servicetestapplication.service;
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 {
public MyService(){
}
/**
* 创建服务时调用
*/
@Override
public void onCreate() {
Log.i("service", "onCreate");
super.onCreate();
}
/**
* 启动服务时调用,用于一旦启动就调用某个动作
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.
* @param flags Additional data about this start request.
* @param startId A unique integer representing this specific request to
* start. Use with {@link #stopSelfResult(int)}.
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("service", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁服务时调用,用于回收不需要使用的资源
*/
@Override
public void onDestroy() {
Log.i("service", "onDestroy");
super.onDestroy();
}
}
创建服务时会自动在mainifest.xml文件中注册该service
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ServiceTestApplication"
tools:targetApi="31">
...
<service
android:name=".service.MyService"
android:enabled="true"
android:exported="true">
</service>
...
</application>
启动(停止)一个服务
在一个activity中使用初始化一个intent然后通过startService和stopService分别启动和停止服务
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ServiceTestActivity">
<Button
android:id="@+id/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开启服务" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止服务" />
<Button
android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="绑定服务" />
<Button
android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="解绑服务" />
</LinearLayout>
package com.example.servicetestapplication;
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 android.widget.Button;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.servicetestapplication.service.MyService;
public class ServiceTestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
....
Button startService = findViewById(R.id.start_service);
Button stopService = findViewById(R.id.stop_service);
startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ServiceTestActivity.this, MyService.class);
startService(intent);
}
});
stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ServiceTestActivity.this, MyService.class);
stopService(intent);
}
});
}
}
运行结果如下:
需要知道的是如果你不停止service的前提下,多次启动服务,onCreated只会执行一次,但是onStartCommand会执行多次.
绑定服务
绑定服务这边用到的是service生命周期中onBind方法,步骤如下
- 在service中创建一个继承了Binder类的内部类DownloadBinder,然后在service类中生命该对象,并在onBind方法中返回给对象.
public class MyService extends Service {
private DownloadBinder downloadBinder = new DownloadBinder();
public class DownloadBinder extends Binder {
public void startDownload() {
Log.i("service", "startDownload");
}
public int getProgress() {
Log.i("service", "getProgress");
return 0;
}
}
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return downloadBinder;
}
/**
* 创建服务时调用
*/
@Override
public void onCreate() {
Log.i("service", "onCreate");
super.onCreate();
}
/**
* 启动服务时调用
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.
* @param flags Additional data about this start request.
* @param startId A unique integer representing this specific request to
* start. Use with {@link #stopSelfResult(int)}.
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("service", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁服务时调用
*/
@Override
public void onDestroy() {
Log.i("service", "onDestroy");
super.onDestroy();
}
}
- 在activity中创建一个connection对象,并重写方法,onServiceConnected和onServiceDisconnected方法,分别是绑定服务和解除绑定时调用的
public class ServiceTestActivity extends AppCompatActivity {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder=(MyService.DownloadBinder)service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Button startService = findViewById(R.id.start_service);
Button stopService = findViewById(R.id.stop_service);
startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ServiceTestActivity.this, MyService.class);
startService(intent);
}
});
stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ServiceTestActivity.this, MyService.class);
stopService(intent);
}
});
Button bindService=findViewById(R.id.bind_service);
Button unbindService=findViewById(R.id.unbind_service);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ServiceTestActivity.this, MyService.class);
bindService(intent,connection,BIND_AUTO_CREATE);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ServiceTestActivity.this, MyService.class);
unbindService(connection);
}
});
}
}
实现效果如下:
绑定服务和启动服务,服务运行的生命周期是一样的,但是绑定服务只会运行一次,绑定完之后就不会再运行该服务的生命周期了.
但是如果在既绑定服务又启动服务的情况下,必须解除绑定服务和关闭服务都执行才能使得该服务停止运行.
查看该应用后台已启动服务
这边可以通过查看测试android手机的开发者选项中的后台服务中查看.
前台服务
介绍
在 Android 中,前台服务是一种在用户可见的情况下运行的服务。通常情况下,前台服务会在状态栏显示一个持续的通知,以提醒用户服务正在运行。前台服务通常用于执行与用户正在交互的任务,例如播放音乐、处理网络请求等。
这样的服务对于用户来说更加显眼,同时系统也会给予它们更高的优先级,以确保它们不容易被系统杀死以释放资源。
通常,前台服务需要通过startForeground()方法启动,并提供一个通知对象,以便将服务置于前台并在通知栏显示。这样做可以让用户清楚地知道当前应用正在执行一些任务,从而提高用户对应用的信任度。
需要注意的是,由于前台服务具有较高的优先级,因此在使用时需要谨慎,确保其使用场景合理,以避免给用户带来不必要的干扰。
相关方法:
- startForeground:把当前服务切换到前台运行。第一个参数表示通知的编号,第二个参数表示 Notification 对象,意味着切换到前台就是展示到通知栏。
- stopForeground:停止前台运行。参数为 true 表示清除通知,参数为 false 表示不清除。
使用步骤
- 在manifest.xml中定义前台服务类型,比如音频,相机之类的,因为这边只是个普通的通知所以就不需要了
<service
android:name=".service.MyService"
android:enabled="true"
android:foregroundServiceType=""
android:exported="true">
</service>
- 在manifest.xml中配置相应前台权限,下面那个权限是每个前台都需要配置的,但是如果你要启动一些其他前台服务如音频,相机,那么你就需要配置相应的前台相关权限了.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- 现在你就可以在原来MyService自定义服务类中作出修改使得其发送前台服务
public class MyService extends Service {
int serviceId=2;
public MyService() {
}
/**
* 创建服务时调用
*/
@SuppressLint("ForegroundServiceType")
@Override
public void onCreate() {
Log.i("onCreate","onCreate");
super.onCreate();
}
/**
* 启动服务时调用,用于一旦启动就调用某个动作
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.
* @param flags Additional data about this start request.
* @param startId A unique integer representing this specific request to
* start. Use with {@link #stopSelfResult(int)}.
* @return
*/
@SuppressLint("ForegroundServiceType")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("service", "onStartCommand");
Intent intents = new Intent(this, ServiceTestActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intents, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this, channelID)
.setSmallIcon(R.drawable.lock) //添加小图标,该图标通知必须透明
.setContentTitle("hjh的通知") //添加通知标题
.setContentText("This is hjh's notification") //添加通知内容
.setWhen(System.currentTimeMillis()) //添加通知事件
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ai)) //添加大图标
.setAutoCancel(true)
.setContentIntent(pendingIntent);
startForeground(serviceId,builder.build());
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁服务时调用,用于回收不需要使用的资源
*/
@SuppressLint("WrongConstant")
@Override
public void onDestroy() {
Log.i("service", "onDestroy");
// 关闭前台服务
stopForeground(serviceId);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
生命周期
相关方法介绍
- onCreate:创建服务。
- onStart:开始服务。Android 2.0 以下版本使用,已废弃。
- onStartCommand:开始服务。Android 2.0 及以上版本使用。
- onDestroy:销毁服务。
- onBind:绑定服务。
- onRebind:重新绑定。该方法只有当上次 onUnbind 返回 true 的时候才会被调用。
- onUnbind:解除绑定。默认为 false。
管理服务的生命周期
服务的生命周期比 activity 的生命周期简单得多。但是,密切关注如何创建和销毁服务更为重要,因为服务可以在用户不知情的情况下在后台运行。
服务生命周期(从创建到销毁)可以遵循以下两个路径之一:
- 启动的服务 该服务在其他组件调用 startService() 时创建。然后无限期运行,且必须通过调用 stopSelf() 自行停止运行。另一个组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。
- 绑定服务 当另一个组件(客户端)调用 bindService() 时,系统将创建该服务。然后,客户端通过 IBinder 接口与服务进行通信。客户端可通过调用 unbindService() 关闭连接。多个客户端可以绑定到同一服务,当所有客户端均取消绑定时,系统会销毁该服务。该服务不需要自行停止运行。
这两条路径并非完全独立。您可以绑定到已使用 startService() 启动的服务。例如,您可以启动后台音乐服务,方法是使用 Intent(用于标识要播放的音乐)调用 startService()。之后,可能当用户想要对播放器进行某种控制或获取有关当前歌曲的信息时,activity 可以通过调用 bindService() 绑定到该服务。在这种情况下,在所有客户端取消绑定之前,stopService() 或 stopSelf() 实际上不会停止服务。
实现生命周期回调
与 activity 一样,服务也有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并在适当的时间执行工作。以下框架服务演示了每种生命周期方法:
public class ExampleService extends Service {
int startMode; // indicates how to behave if the service is killed
IBinder binder; // interface for clients that bind
boolean allowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return startMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return allowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
注意:与 activity 生命周期回调方法不同,您不需要调用这些回调方法的父类实现。
图 2. 服务生命周期。左图显示了使用 startService() 创建服务的生命周期,右图显示了使用 bindService() 创建服务的生命周期。
图 2 说明了服务的典型回调方法。尽管下图将 startService() 创建的服务与 bindService() 创建的服务区分开来,但请注意,任何服务(无论以何种方式启动)都可能允许客户端与其绑定。最初使用 onStartCommand() 启动的服务(通过调用 startService() 的客户端)仍然可以接收对 onBind() 的调用(当客户端调用 bindService() 时)。
通过实现这些方法,您可以监控服务生命周期的以下两个嵌套循环:
- 服务的整个生命周期从调用 onCreate() 到 onDestroy() 返回这段时间内开始。与 activity 一样,服务也会在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。 注意:无论服务是通过 startService() 还是 bindService() 创建,都会针对所有服务调用 onCreate() 和 onDestroy() 方法。
- 服务的活跃生命周期从调用 onStartCommand() 或 onBind() 开始。每个方法都会获得传递给 startService() 或 bindService() 的 Intent。如果服务启动,有效生命周期与整个生命周期同时结束(即使在 onStartCommand() 返回后,服务仍处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。
注意:虽然已启动的服务是通过调用 stopSelf() 或 stopService() 来停止,但服务并无相应的回调(没有 onStop() 回调)。除非服务绑定到客户端,否则系统会在服务停止时将其销毁(onDestroy() 是接收到的唯一回调)。
如需详细了解如何创建提供绑定的服务,请参阅绑定服务文档,该文档详细介绍了管理绑定服务的生命周期部分中的 onRebind() 回调方法。
ALDL的使用
步骤
服务端
- 创建两个应用,并在它们的build.gradle文件中配置以下操作,使得我们可以创建aidl文件
android {
buildFeatures {
viewBinding true
aidl true
}
}
- 在提供服务的应用中创建aidl文件,aidl文件事实上类似于java里面的接口
// IMyAidlInterface.aidl
package com.example.androidtest;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
int a();
}
- 点击一下android-stuidio的build按键,然后在一个服务类中通过匿名内部类实现上面aidlbuidl后生成的接口,
然后在Service的onBind的生命周期方法中返回这个实现对象
public class MyService extends Service {
private final IMyAidlInterface.Stub binder=new IMyAidlInterface.Stub() {
private final String TAG = "RemoteService";
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
Log.d(TAG, "basicTypes anInt:" + anInt + ";aLong:" + aLong + ";aBoolean:" + aBoolean + ";aFloat:" + aFloat + ";aDouble:" + aDouble + ";aString:" + aString);
}
@Override
public int a() throws RemoteException {
return 11111;
}
};
/**
* 创建服务时调用
*/
@Override
public void onCreate() {
Log.i("onCreate","onCreate");
super.onCreate();
}
/**
* 启动服务时调用,用于一旦启动就调用某个动作
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.
* @param flags Additional data about this start request.
* @param startId A unique integer representing this specific request to
* start. Use with {@link #stopSelfResult(int)}.
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("onStart","onStart");
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁服务时调用,用于回收不需要使用的资源
*/
@Override
public void onDestroy() {
Log.i("service", "onDestroy");
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
- 在Manifest已经自动注册的服务中设置以下属性
<service
android:name=".service.MyService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="">
<intent-filter>
<action android:name="com.example.androidtest"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
客户端
- 将上面的aidl文件连它所在文件夹整个复制到客户端文件夹中,然后android-stuido按下build按钮,生成相关接口
- 在Manifest.xml中声明要访问的应用的包名如下
<queries>
<package android:name="com.example.androidtest" />
</queries>的好处
- 在activity中编写绑定服务逻辑
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bindService"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="绑定服务"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
public class MainActivity extends AppCompatActivity {
// aidl文件生成的接口,也就是要调用的服务
IMyAidlInterface aidlInterface;
ActivityMainBinding activityMainBinding;
private ServiceConnection mConnection = new ServiceConnection() {
// 绑定服务调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("A", "服务已连接");
aidlInterface = IMyAidlInterface.Stub.asInterface(service);
try {
int a = aidlInterface.a();
Log.d("a:", String.valueOf(a));
aidlInterface.basicTypes(12, 123, true, 123.4f, 123.45, "服务端你好,我是客户端");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
activityMainBinding.bindService.setText("取消绑定");
}
//断开服务调用
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("A", "服务已经断开");
aidlInterface = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
....
activityMainBinding.bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("com.example.androidtest");
intent.setPackage("com.example.androidtest");
boolean b = bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
Log.e("ifConnected", String.valueOf(b));
}
});
}
}