背景
在高版本中App如何启动第三方App中的某个服务,启动服务会遇到那些问题。
这里高版本我用的环境是:Android12,当然更高版本也适合。
普通服务的demo
先编写一个简单App,在里面配置一个服务。
package com.cat.tv;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;/*** Create by os on 2024/10/16* Desc : 服务*/
public class MyBackgroundService extends Service {private static final String TAG = "MyBackgroundService";@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "Service created");}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d(TAG, "Service onStartCommand " + flags);if (intent != null) {String myKey = intent.getStringExtra("my_key");Log.i(TAG, "onStartCommand: my_key = " + myKey);}// 在这里执行计算逻辑new Thread(new Runnable() {@Overridepublic void run() {performCalculations();}}).start();return START_NOT_STICKY;}private void performCalculations() {// 在这里执行一些计算逻辑Log.d(TAG, "Performing calculations...");// 模拟计算过程for (int i = 0; i < 5; i++) {Log.d(TAG, "Calculation step: " + i);try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}}Log.d(TAG, "Calculations completed 完成后停止服务");// 完成后停止服务stopSelf();}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "Service destroyed");}
}
这个服务的代码可以直接复制去跑,没有依赖任何其他第三方库。
服务中打印了启动服务之后接受了intent中的key=my_key的信息。这个是用来测试自己启动服务,或者第三方启动服务的时候传的参数。还有打印了模拟的耗时计算,服务销毁的生命周期。
以上就是服务的代码。除了编写服务的代码,还需要在AndroidManifest中配置服务属性。
<serviceandroid:name=".MyBackgroundService"android:exported="true"android:enabled="true"android:permission="com.cat.tv.MY_PERMISSION"><intent-filter><action android:name="com.cat.tv.server" /><category android:name="android.intent.category.DEFAULT" /></intent-filter>
</service>
刚开始配置服务时候其实代码只有这样:
<serviceandroid:name=".MyBackgroundService">
</service>
这样的代码App自己启动服务是可以的。也就是这个服务是自己用的,不允许外部的App使用。服务配置好之后,App内部自己启动服务做测试。下面是启动服务的代码:
private void startMyService() {Intent serviceIntent = new Intent(this, MyBackgroundService.class);startService(serviceIntent);
}
在按钮点击的时候,调用这个函数,启动服务。可以得到日志:
D/MyBackgroundService: Service created
D/MyBackgroundService: Service onStartCommand 0
I/MyBackgroundService: onStartCommand: my_key = null
D/MyBackgroundService: Performing calculations...
D/MyBackgroundService: Calculation step: 0
D/MyBackgroundService: Calculation step: 1
D/MyBackgroundService: Calculation step: 2
D/MyBackgroundService: Calculation step: 3
D/MyBackgroundService: Calculation step: 4
D/MyBackgroundService: Calculations completed 完成后停止服务
D/MyBackgroundService: Service destroyed
服务启动成功,因为启动服务的intent中没有传递参数,my_key是空的,也属于正常。接下里尝试开发其他App启动这个服务。
启动其他App的服务
以下测试基于Android12,理论上更高版本也适用。
角色分配:
1、CommonDemo这个App中有一个服务:MyBackgroundService
2、ChipDemo是一个普通App,它要启动CommonDemo中的MyBackgroundService,传递参数过去。
下面是2个App编译参数:
1、CommonDemo:
applicationId "com.cat.tv"
minSdk 19
targetSdk 31
versionCode 1
versionName "1.0"服务:com.cat.tv.MyBackgroundService
2、ChipDemo:
applicationId "com.cat.chipdemo"
minSdk 29
targetSdk 32
versionCode 1
versionName "1.0
启动第三方App服务
ChipDemo启动第三方服务
private void test() {Intent serviceIntent = new Intent();serviceIntent.setPackage("com.cat.tv");serviceIntent.setClassName("com.cat.tv", "com.cat.tv.MyBackgroundService");serviceIntent.putExtra("my_key", "我是另一个App,hello~");try {Log.i(TAG, "startMyService: 启动");startService(serviceIntent);} catch (SecurityException e) {// 处理没有权限的情况e.printStackTrace();}
}
错误日志:
ActivityManager: Unable to start service Intent { pkg=com.cat.tv cmp=com.cat.tv/.MyBackgroundService (has extras) } U=0: not found
这里提示找不到,这个时候CommonDemo是没有被杀死,它自己启动服务也正常工作的。
<serviceandroid:name=".MyBackgroundService">
</service>
这是服务的配置,服务在高版本中需要主动声明对外可访问。我把对外访问的配置补充完整:
```xml
<serviceandroid:name=".MyBackgroundService"android:exported="true"android:enabled="true"android:permission="com.cat.tv.MY_PERMISSION"><intent-filter><action android:name="com.cat.tv.server" /><category android:name="android.intent.category.DEFAULT" /></intent-filter>
</service>
当设置exported=true之后as提示需要permission,然后在配置文件中增加了自定义权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.cat.tv"><!-- 声明一个自定义权限 --><permission android:name="com.cat.tv.MY_PERMISSION" android:protectionLevel="normal" /><serviceandroid:name=".MyBackgroundService"android:exported="true"android:enabled="true"android:permission="com.cat.tv.MY_PERMISSION"><intent-filter><action android:name="com.cat.tv.server" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></service></application></manifest>
这里把权限设置normal,也就是其他App想要启动这个服务,就直接把权限复制过去就行了。
ChipDemo配置:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.cat.chipdemo"><uses-permission android:name="com.cat.tv.MY_PERMISSION" /></manifest>
尝试使用ChipDemo启动服务
private void startMyService() {Intent serviceIntent = new Intent("com.cat.tv.server");// 确保使用正确的Action或ComponentNameserviceIntent.addCategory(Intent.CATEGORY_DEFAULT);serviceIntent.setPackage("com.cat.tv");serviceIntent.setAction("com.cat.tv.server");// 指向服务的包名和类名serviceIntent.setClassName("com.cat.tv", "com.cat.tv.MyBackgroundService");serviceIntent.putExtra("my_key", "我是另一个App,hello~");try {Log.i(TAG, "startMyService: 启动");startService(serviceIntent);} catch (SecurityException e) {// 处理没有权限的情况e.printStackTrace();}
遇到的错误一样的,not found。
经过查询Google在Android11的时候有新的特性:
如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台,在默认情况下,系统会自动让部分应用对您的应用可见,但会隐藏其他应用。通过让部分应用在默认情况下不可见,系统可以了解应向您的应用显示哪些其他应用,这样有助于鼓励最小权限原则,还可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。
ActivityManager找不到服务是这个特性禁止了访问。
如何让服务被第三方App发现?
在大于等于Android11的设备,target 30+的App需要增加白名单。
配置文件中这样配置:
<queries><package android:name="com.cat.chipdemo" />......<package android:name="com.aaa" />
</queries>
你想让谁发现你就要把对面的包名写到配置文件中.
回到我们的服务,CommonDemo配置修改如下:
<!-- 声明一个自定义权限 -->
<permission android:name="com.cat.tv.MY_PERMISSION" android:protectionLevel="normal" />
<queries><package android:name="com.cat.chipdemo" /><intent><action android:name="com.cat.tv.server" /></intent>
</queries>
让chipdemo发现我们服务的App,顺便给一个action可以被对方调用。
配置完成后继续调用,还是not found,经过我的测试,这个包可见性需要双方一起配置。
也就是chipdemo的配置文件也需要配置queries。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.cat.chipdemo"><uses-permission android:name="com.cat.tv.MY_PERMISSION" /><queries><package android:name="com.cat.tv" /></queries><applicationandroid: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.ChipDemo"tools:targetApi="31"><activityandroid: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>
也就是调用服务的App也需要配置附带服务的App的包名。
D/MyBackgroundService: Service created
D/MyBackgroundService: Service onStartCommand 0
I/MyBackgroundService: onStartCommand: my_key = 我是另一个App,hello~
D/MyBackgroundService: Performing calculations...
D/MyBackgroundService: Calculation step: 0
D/MyBackgroundService: Calculation step: 1
D/MyBackgroundService: Calculation step: 2
D/MyBackgroundService: Calculation step: 3
D/MyBackgroundService: Calculation step: 4
D/MyBackgroundService: Calculations completed 完成后停止服务
D/MyBackgroundService: Service destroyed
这样ChipDemo >>> CommonDemo跑通了。
ChipDemo启动了CommonDemo的MyBackgroundService且通过intent传递了my_key的信息。
如果带服务的App没有启动?
当我主动杀死CommonDemo,使用ChipDemo去启动服务:
Process: com.cat.chipdemo, PID: 7106android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service Intent { pkg=com.cat.tv cmp=com.cat.tv/.MyBackgroundService (has extras) }: app is in background uid nullat android.app.ContextImpl.startServiceCommon(ContextImpl.java:1861)at android.app.ContextImpl.startService(ContextImpl.java:1817)at android.content.ContextWrapper.startService(ContextWrapper.java:774)at android.content.ContextWrapper.startService(ContextWrapper.java:774)at com.cat.chipdemo.MainActivity.test(MainActivity.java:37)at com.cat.chipdemo.MainActivity.access$000(MainActivity.java:11)at com.cat.chipdemo.MainActivity$1.onClick(MainActivity.java:25)
如果带服务的App是死的,系统是不允许去启动它,如果允许,就变成以前那种手拉手循环拉活的环境了。
测试apk
如果安装apk失败,可以改adb install -t path
apk下载地址:
链接: https://pan.baidu.com/s/1aGDkoMaboOp36MhrEZJZPw?pwd=2ghw 提取码: 2ghw 复制这段内容后打开百度网盘手机App,操作更方便哦