1.StartService可以跨进程吗通信?如果可以的话,它是如何做到的?
首先来看下这段代码, 进程A中点击某个按钮后,启动进程B中的服务:
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.example.进程B","com.example.进程B.MyService");
intent.setComponent(componentName);
startService(intent);
要启动 Service ,我们会调用 startService 方法,它在 ContextImpl.java 中实现,代码如下:
ContextImpl.java 客户端
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
// 通过 IActivityManager 的接口调用 AMS
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
getOpPackageName(), user.getUserId());
if (cn == null) {
return null;
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- ActivityManager.getService() 获取一个 IActivityManager 接口的实例,该接口是与 AMS 通信的桥梁。
- mMainThread.getApplicationThread() 获取客户端应用程序的 IApplicationThread 对象,用于与 AMS 进行回调。
- ActivityManager.getService().startService(…) 这是一个跨进程调用,它将请求传递给 AMS。
** ActivityManagerService.java (服务端)😗*
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId) {
// ... 权限检查 ...
synchronized(this) {
// ... 创建 ServiceRecord ...
// 如果服务不在同一个进程中,则启动新的进程
if (app == null) {
// 启动新的进程
startProcessLocked(appInfo.processName, appInfo, false, 0,
"service", component.flattenToShortString(), false, isolated, userId,
callerPackage, false, fgServiceTypes);
}
// ... 将启动服务的请求放入队列 ...
bringUpServiceLocked(r, service.getFlags(), false, false, false);
}
return r.name;
}
private void bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean oomAdjusted, boolean isRestart) throws TransactionTooLargeException {
// ...
realStartServiceLocked(r, app, execInFg);
}
private void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg)
throws RemoteException {
// ...
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.getCompatInfoForPackageLocked(r.serviceInfo.packageName,
r.userId), app.repProcState);
// ...
}
- startService 方法接收来自客户端的请求。
- startProcessLocked 方法用于启动新的进程(如果需要)。
- app.thread.scheduleCreateService 方法是一个跨进程调用,它将创建服务的请求发送到目标进程。 app.thread 是一个 IApplicationThread 对象,用于与客户端进程进行通信。
ActivityThread.java (服务端):
public final class ActivityThread {
// ...
private class ApplicationThread extends IApplicationThread.Stub {
// ...
@Override
public final void scheduleCreateService(ServiceRecord token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
// ...
sendMessage(H.CREATE_SERVICE, data);
}
}
}
- ApplicationThread 是运行在应用程序进程中的一个类,它实现了 IApplicationThread 接口。
- scheduleCreateService 方法接收来自 AMS 的创建服务的请求,并将请求放入消息队列中。
工作流程
- 客户端(Client):
客户端应用程序通过 Context.startService(Intent) 发起启动服务的请求。
客户端提供的 Intent 包含了目标服务的组件名(ComponentName),或者可以解析为目标服务的组件名。
- 系统服务(ActivityManagerService, AMS):
客户端的 startService 调用最终会传递给 ActivityManagerService (AMS),这是一个运行在 system_server 进程中的系统服务,负责管理应用程序的生命周期。
AMS 接收到请求后,会验证 Intent 和客户端的权限。
AMS 查找与 Intent 匹配的服务。 如果服务尚未运行,AMS 会创建一个新的服务进程(如果需要),并在该进程中启动服务。
如果服务已经运行在另一个进程中,AMS 会向该进程发送一个命令,告知它需要启动服务。
- 服务端(Service):
服务端进程(包含目标服务的进程)接收到 AMS 发送的命令。
Android 运行时环境会创建一个 Service 实例,并调用它的 onCreate() 和 onStartCommand() 方法。
- 跨进程调用(IPC):
即使 startService 方法返回了,客户端和服务端之间的通信并没有结束。 如果客户端需要与运行在服务端进程中的 Service 进行进一步的交互(例如,调用服务的方法),它通常会使用 bindService 方法绑定到服务。 绑定服务会创建一个 ServiceConnection 对象,该对象允许客户端接收来自服务端的 Binder 对象。
Binder 对象是一个实现了 IBinder 接口的类,它充当了客户端和服务端之间的桥梁。 通过 Binder 对象,客户端可以调用服务端的方法,并且可以将数据传递给服务端。
因此,startService可以跨进程,也就是说我们可以在进程B启动进程A中的Service。启动请求会传递给AMS,AMS查找与 Intent 匹配的服务。 如果服务尚未运行,AMS 会创建一个新的服务进程(如果需要),并在该进程中启动服务。如果服务已经运行在另一个进程中,AMS 会向该进程发送一个命令,告知它需要启动服务。
startService跨进程的缺点
1)没有好的机制立即返回执行结果,往往Service完成任务后,还需要其他方式向Client端反馈。
2)Service端无法识别Client端是谁,只知道有启动命令,但无法知道是谁下的命令。
3)在已经创建好Service情况下,每次调用startService,就会执行onStartCommand()生命周期方法,相比于bindService,效率低下。
4)如果Client端忘记调用stopService()了,那么该Service会一直运行下去,这也是一个隐患。
所以,针对上述缺点,往往建议startService()方式用于同一个App内
2.在Service所在进程未启动的情况下, bindService可以成功绑定吗?
首先来看下这段代码, 进程A中点击某个按钮后,绑定进程B中的服务:
// 进程 A
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.appB", "com.example.appB.MyService"));
boolean success = bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
要绑定 Service ,我们会调用 bindService 方法,它在 ContextImpl.java 中实现,代码如下:
ContextImpl.java 客户端
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
return bindServiceCommon(service, conn, flags, mUser);
}
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
try {
// 通过 IActivityManager 接口调用 AMS
int res = ActivityManager.getService().bindService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
conn, flags, getOpPackageName(), user.getUserId());
if (res < 0) {
return false;
}
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
与 startService 类似,bindService 也通过 ActivityManager.getService() 获取 IActivityManager 接口的实例,用于与 AMS 通信。
ActivityManagerService.java (服务端):
@Override
public int bindService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String callingPackage,
int userId) {
synchronized(this) {
// ...
ServiceRecord s = getServiceRecordLocked(service, callingPackage, userId, false, true);
if (s == null) {
// 找不到 ServiceRecord,可能是 Service 尚未启动
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
// BIND_AUTO_CREATE 标志被设置,需要启动 Service
bringUpServiceLocked(s, service.getFlags(), false, false, false); //启动service
} else {
// 没有 BIND_AUTO_CREATE 标志,无法绑定
return 0; //或其它错误码
}
}
//关联service和client,并调用client端的onServiceConnected
...
}
}
private void bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean oomAdjusted, boolean isRestart) throws TransactionTooLargeException {
// ...
realStartServiceLocked(r, app, execInFg);
}
private void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg)
throws RemoteException {
if (!app.hasStarted) { //启动进程A
//startProcessLocked(appInfo.processName, appInfo, false, 0,
// "service", component.flattenToShortString(), false, isolated, userId,
// callerPackage, false, fgServiceTypes);
}
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.getCompatInfoForPackageLocked(r.serviceInfo.packageName,
r.userId), app.repProcState); //创建service
...
}
bindService 方法接收绑定请求。
关键在于 (flags&Context.BIND_AUTO_CREATE) != 0 的判断。 如果设置了 BIND_AUTO_CREATE 标志,并且 Service 尚未启动,则 AMS 会负责启动 Service。
如果 Service 之前没有运行,startProcessLocked()会尝试启动进程A,然后app.thread.scheduleCreateService会被调用,创建一个Service实例,并调用onCreate和onBind。
工作流程和依赖的条件:
-
进程 A 未启动: 进程 A 没有正在运行的任何组件(Activity、Service、BroadcastReceiver 等)。
-
进程 B 尝试绑定: 进程 B 调用 bindService(Intent, ServiceConnection, int),其中 Intent 指向进程 A 中的 Service。
-
AMS 的处理:
-
ActivityManagerService (AMS) 接收到绑定请求。
-
由于进程 A 尚未运行,AMS 会启动进程 A。
-
在进程 A 中,Android 运行时环境会创建 Application 实例(如果尚未创建),并初始化该进程。
-
然后,AMS 会尝试创建 Service 的实例,并调用它的 onCreate() 方法。
关键点: 如果绑定标志包含 BIND_AUTO_CREATE (或者使用默认值),则在进程 A 中的 Service 未启动时,系统会先创建 Service 的实例。如果没有BIND_AUTO_CREATE, 且Service未启动,那么绑定会失败。
-
-
Service 的 onBind 方法:
-
在 Service 的 onCreate() 方法之后,系统会调用 Service 的 onBind(Intent) 方法。 onBind() 方法返回一个 IBinder 对象,该对象是进程 A 和进程 B 之间进行通信的桥梁。
-
如果 onBind() 返回 null,则绑定将失败。
-
-
ServiceConnection 的回调:
-
进程 B 中的 ServiceConnection 对象会收到回调:
-
如果绑定成功,则会调用 onServiceConnected(ComponentName, IBinder) 方法,并将 onBind() 方法返回的 IBinder 对象传递给进程 B。
-
如果绑定失败,则会调用 onServiceDisconnected(ComponentName) 方法。
-
-
-
后续通信: 进程 B 可以使用 IBinder 对象来调用进程 A 中的 Service 的方法。
3.bindService跨进程通信所遇到的一些问题
02-08 18:24:10.867 W/OplusAppStartupManager( 1745): isAllowStartFromStartService: prevent start com.seres.smartscene5 , intent = Intent { act=android.intent.action.STANDARD_SERVICE cmp=com.seres.smartscene5/cn.seres.alps.service.BluetoothService } by cn.seres.starryskypanel
02-08 18:24:10.869 W/ALPS.SERVICE(10222): startConnect: [bindService] result false
OplusAppStartupManager 阻止了应用 cn.seres.starryskypanel 启动 com.seres.smartscene5 应用中的 BluetoothService。 这很可能是因为 Oppo 手机上限制了后台服务的启动,以节省电量或提高性能。
解决方案
进入 “设置” → “应用管理” → “权限管理” → “自启动管理”
允许 com.seres.smartscene5 和 cn.seres.starryskypanel 自启动
允许 “后台运行权限”
Done dumping
02-08 19:31:46.948 E/ActivityManager( 1745): ANR in com.seres.smartscene5
02-08 19:31:46.948 E/ActivityManager( 1745): PID: 1067
02-08 19:31:46.948 E/ActivityManager( 1745): Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{bae1474 u0 com.seres.smartscene5/cn.seres.alps.service.BluetoothService}
02-08 19:31:46.948 E/ActivityManager( 1745): Frozen: false
从 Android 8.0 (API level 26) 开始,对后台服务启动进行了更严格的限制。 如果一个服务需要在后台长时间运行,并且需要优先级别,那么它必须声明为前台服务 (Foreground Service)。
解决方案:
public class BluetoothService extends Service {
private static final String CHANNEL_ID = "BluetoothServiceChannel";
private static final int NOTIFICATION_ID = 1;
@Override
public void onCreate() {
super.onCreate();
// 创建通知渠道 (Android 8.0 及以上)
createNotificationChannel();
// 创建通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("蓝牙服务")
.setContentText("正在后台运行...")
.setSmallIcon(android.R.drawable.ic_menu_manage) // 替换为你的应用图标
.build();
// 启动前台服务
startForeground(NOTIFICATION_ID, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 这里执行你的蓝牙服务逻辑
return START_STICKY; // 根据你的需求选择合适的返回值
}
@Override
public IBinder onBind(Intent intent) {
return null; // 如果不需要绑定,返回 null
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Bluetooth Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}
AndroidManifes.xml:
Service所在的进程A中:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 添加此权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<service
android:name="cn.seres.alps.service.BluetoothService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="connectedDevice|location">
<intent-filter>
<action android:name="android.intent.action.STANDARD_SERVICE" />
</intent-filter><!-- 允许其他应用绑定 -->
</service>
android:foregroundServiceType="connectedDevice|location|bluetooth":重要! 从 Android 10 (API level 29) 开始,如果你的前台服务用于特定目的,你必须在android:foregroundServiceType属性中声明这些目的。 可用的类型包括:
- connectedDevice: 与蓝牙、USB 设备连接或通信。
- location: 访问设备位置信息。
- microphone: 访问设备麦克风。
- camera: 访问设备摄像头。
- health: 执行与健康相关的任务。
- remoteMessaging: 处理远程消息传递。
- bluetooth: 执行常规的蓝牙操作。
- dataSync: 同步数据。
- mediaPlayback: 播放媒体。
- mediaProjection: 屏幕投影。
添加了connectedDevice和location,需要添加权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
进程B中:
<queries>
<package android:name="com.seres.smartscene5"/>
<intent>
<action android:name="android.intent.action.STANDARD_SERVICE" />
</intent><!-- 允许其他应用绑定 -->
</queries>
18万+

被折叠的 条评论
为什么被折叠?



