现象
gms通过android.media.MediaRouteProviderService拉活应用宝的现象

日志
07-29 18:00:55.625 1442 2258 D IntentFirewall: service:Block = false intent = Intent { act=android.media.MediaRouteProviderService cmp=com.tencent.android.qqdownloader/com.tencent.assistant.syscomponent.MediaRouteProviderService } callerUid = 10152 callerPid = 2330 callerPackage = com.google.android.gms calledPackage = com.tencent.android.qqdownloader calledUid = 1026507-29 18:00:56.207 1442 1496 D IntentFirewall: provider:Block = false intent is NULL! callerUid = 10066 callerPid = -1 callerPackage = com.android.providers.blockednumber calledPackage = com.tencent.android.qqdownloader calledUid = 1026507-29 18:00:56.682 1442 2314 D IntentFirewall: provider:Block = false intent is NULL! callerUid = 10265 callerPid = -1 callerPackage = com.tencent.android.qqdownloader calledPackage = com.tencent.android.qqdownloader calledUid = 1026507-29 18:00:56.717 1442 1918 D IntentFirewall: provider:Block = false intent is NULL! callerUid = 10265 callerPid = -1 callerPackage = com.tencent.android.qqdownloader calledPackage = com.tencent.mm calledUid = 1028307-29 18:00:57.388 1442 2155 D IntentFirewall: service:Block = false intent = Intent { cmp=com.tencent.android.qqdownloader/com.tencent.assistant.daemon.CoreService } callerUid = 10265 callerPid = 5625 callerPackage = com.tencent.android.qqdownloader calledPackage = com.tencent.android.qqdownloader calledUid = 10265
系统自启动的拦截方案
方案1:动态拦截
原则是不影响兼容性或用户体验的条件下,对非可感知的后台启动行为可以进行拦截
判断启动方和被启动方的应用标签状态,如果都非可见且被启动方(应用宝)也没有被fork,但是由于caller是系统进程想要冷启动第三方App,还是需要慎重判断拦截(例如加一些灭屏条件或剔除gms类型拉起或Media路由非真实被使用等更精细化条件辅助判断是否拦截)

方案2:rule拦截
如下AOSP配置就行,可以简单包名也可以精确到called,caller,action和component,从配置文件我们也可以看出来,手机厂商拦截应用后台自启动,真的可以很随心所欲啊。第三方应用好不容易辛苦研究了一个保活技术,直接就被厂商配置文件给拦截了。这也就是后面app工程师,不怎么想研究保活技术的原因了哈。
<service block="true" called="com.tencent.android.qqdownloader" caller="com.google.android.gms" interaction="*" log="true"> <intent-filter> <action name="android.media.MediaRouteProviderService" /> </intent-filter> <component-filter name="com.tencent.android.qqdownloader/com.tencent.assistant.syscomponent.MediaRouteProviderService" /></service><service block="true" called="com.tencent.android.qqdownloader" caller="*" interaction="*" log="true"> <intent-filter> <action name="android.media.MediaRoute2ProviderService" /> </intent-filter> <component-filter name="com.tencent.android.qqdownloader/com.tencent.assistant.syscomponent.MediaRouteProviderService2" /></service>
这种配置文件拦截的,可能是一些大厂的作风为主。因为这样不破坏整体拦截策略稳定性。即为了交付进行拦截

应用宝保活思路研究
应用宝通过以下策略增强保活效果:
高频绑定:媒体路由发现是周期性行为(如设备网络变化时),触发多次绑定。
多进程守护::live 进程与主进程分离,避免全量被杀。
系统白名单:媒体路由服务属于系统功能依赖,绑定请求通常不受后台限制
- 应用宝如何使用SDK拉活自己呢
反编译一下应用宝原理就知道了哈

- 清单文件注册(AndroidManifest.xml)
应用宝需在清单文件中声明自定义的 MediaRouteProviderService/MediaRoute2ProviderService,并注册系统标准的媒体路由 Action:
<service android:name="com.tencent.assistant.syscomponent.MediaRoute2ProviderService" android:label="@string/media_route_service_name" android:process=":live" <!-- 使用独立进程保活 --> android:exported="true"> <!-- 允许系统绑定 --> <intent-filter> <action android:name="android.media.MediaRouteProviderService" /> </intent-filter></service><service android:name="com.tencent.assistant.syscomponent.MediaRouteProviderService2" android:enabled="true" android:exported="true" android:process=":live"> <intent-filter> <action android:name="android.media.MediaRoute2ProviderService"/> </intent-filter></service>
保活关键点:
1.android.media.MediaRouteProviderService 是系统媒体路由器框架的固定 Action,用于发现服务
2.:live 进程后缀用于隔离并提高优先级,避免主进程被杀时连带失效
- 服务实现类
应用宝其实也没做啥事情哈
package com.tencent.assistant.syscomponent;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.SystemClock;import androidx.annotation.Nullable;import com.tencent.assistant.Settings;import com.tencent.assistant.utils.SysComponentHelper;/* compiled from: ProGuard *//* loaded from: classes2.dex */public class MediaRouteProviderService2 extends BaseSysComponentService { @Override // com.tencent.assistant.syscomponent.BaseSysComponentService public SysComponentHelper.SysComponentType a() { return SysComponentHelper.SysComponentType.MediaRouteProviderService2; } @Override // com.tencent.assistant.syscomponent.BaseSysComponentService, android.app.Service @Nullable public IBinder onBind(Intent intent) { Settings.get().setAsync("key_live_media_service_on_bind", Long.valueOf(SystemClock.elapsedRealtime())); SysComponentHelper.e(SysComponentHelper.SysComponentType.MediaRouteProviderService2, "service_bind"); return new Binder(); } @Override // com.tencent.assistant.syscomponent.BaseSysComponentService, android.app.Service public void onCreate() { super.onCreate(); }}
Google Play 服务(com.google.android.gms)会主动绑定此服务以发现投屏设备,从而拉活应用宝进程。
- 系统侧的冷启动堆栈
本质还是bindService哈,MediaRoute2ProviderServiceProxy的bindService冷启动了应用宝
01-26 03:00:16.621 7461 7461 I ActivityManager: startProcessLocked com.tencent.android.qqdownloader:live01-26 03:00:16.621 7461 7461 I ActivityManager: java.lang.Throwable01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:1697)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:2418)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:2558)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:2858)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ActiveServices.bringUpServiceLocked(ActiveServices.java:4278)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ActiveServices.bindServiceLocked(ActiveServices.java:2956)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ActivityManagerService.bindServiceInstance(ActivityManagerService.java:12782)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.am.ActivityManagerService.bindServiceInstance(ActivityManagerService.java:12729)01-26 03:00:16.621 7461 7461 I ActivityManager: at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:2035)01-26 03:00:16.621 7461 7461 I ActivityManager: at android.app.ContextImpl.bindServiceAsUser(ContextImpl.java:1974)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.media.MediaRoute2ProviderServiceProxy.bind(MediaRoute2ProviderServiceProxy.java:243)01-26 03:00:16.621 7461 7461 I ActivityManager: at com.android.server.media.MediaRoute2ProviderServiceProxy.onBindingDied(MediaRoute2ProviderServiceProxy.java:312)01-26 03:00:16.621 7461 7461 I ActivityManager: at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:2184)01-26 03:00:16.621 7461 7461 I ActivityManager: at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:2221)
典型调用链:
MediaRouter2 → MediaRoute2ProviderServiceProxy.bind() → 目标服务进程启动 → getControllerName() 获取展示名称
/** * 媒体路由提供者服务的代理类,用于绑定和管理实际的 MediaRoute2ProviderService 服务。 * 通过 Binder 机制与系统媒体路由器框架交互,负责路由发现和会话控制。 * * @see MediaRoute2ProviderService 系统媒体路由服务的基类 */final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { // 系统定义的媒体路由服务接口 Action public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; /** * 绑定目标媒体路由提供者服务。 * 绑定成功后,通过 mServiceConnection 回调处理服务连接状态。 * 绑定优先级为前台服务(BIND_FOREGROUND_SERVICE),确保进程不被轻易回收。 * * @throws SecurityException 若缺少绑定权限或目标服务未声明导出 */ private void bind() { if (!mBound) { if (DEBUG) { Slog.d(TAG, this + ": Binding"); } // 构建指向目标服务的 Intent,使用系统预定义 Action Intent service = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE); service.setComponent(mComponentName); try { // 绑定服务并设置自动创建和前台优先级标志 mBound = mContext.bindServiceAsUser( service, mServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUserId)); if (!mBound && DEBUG) { Slog.d(TAG, this + ": Bind failed"); } } catch (SecurityException ex) { // 捕获权限异常(如目标服务未声明 exported=true) if (DEBUG) { Slog.d(TAG, this + ": Bind failed", ex); } } } }}/** * 媒体会话管理器,负责处理媒体控制器(MediaController)的元数据获取和展示逻辑。 * 包含从应用或服务中提取显示名称的功能。 */public class MediaSessions { /** * 获取媒体控制器的显示名称,优先从 MediaRouteProviderService 的服务标签中获取, * 若未找到则回退到应用标签,最后返回包名。 * * @param controller 目标媒体控制器实例 * @return 控制器友好名称(如"应用宝投屏"),默认返回包名 */ protected String getControllerName(MediaController controller) { final PackageManager pm = mContext.getPackageManager(); final String pkg = controller.getPackageName(); try { // 优先从 MediaRouteProviderService 的服务标签获取名称 if (USE_SERVICE_LABEL) { // 查询所有注册了 MediaRouteProviderService 的服务 final List<ResolveInfo> ris = pm.queryIntentServices( new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0); if (ris != null) { for (ResolveInfo ri : ris) { if (ri.serviceInfo == null) continue; // 确保服务属于当前包 if (pkg.equals(ri.serviceInfo.packageName)) { final String serviceLabel = Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim(); if (serviceLabel.length() > 0) { return serviceLabel; // 返回服务标签(如"应用宝投屏") } } } } } // 回退到应用标签 final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0); final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim(); if (appLabel.length() > 0) { return appLabel; // 返回应用名称(如"应用宝") } } catch (NameNotFoundException e) { // 忽略异常,返回包名 } return pkg; // 默认返回包名(如"com.tencent.android.qqdownloader") }}
MediaRoute2ProviderService:系统级服务,用于发布和管理媒体路由(如 Chromecast 设备),绑定后触发进程拉活。
进程保活:应用宝通过声明该服务并设置独立进程(:live),借助 Google Play 服务的绑定请求实现自启动
- 那么媒体路由保活方案有用吗?
答:肯定是有用的,不然应用宝也不会搞这个保活技术。那为啥有些手机又没用呢?因为啊华米OV等一些大厂都有自己的自启动模块或功耗开发工程师啊,很多年前啊,他们在大数据中或用户反馈中发现了这一后台启动行为,进行了拦截处理哈。但是啊,我们手机其实不止华米OV,总有一些非主流的牌子,不一定有对应拦截媒体路由保活的策略哈。反正看着选择就行哈。但是其实应用后台保活的趋势不强了,尽量还是按需来就行了。能不能常驻后台运行,用户决定就行了。当然这块我们程序员决定不了,看公司商业良心了。