当前位置: 首页 > article >正文

Android启动第三方App的服务

背景

在高版本中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";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Service created");
    }

    @Override
    public 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() {
            @Override
            public 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();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Service destroyed");
    }
}

这个服务的代码可以直接复制去跑,没有依赖任何其他第三方库。
服务中打印了启动服务之后接受了intent中的key=my_key的信息。这个是用来测试自己启动服务,或者第三方启动服务的时候传的参数。还有打印了模拟的耗时计算,服务销毁的生命周期。

以上就是服务的代码。除了编写服务的代码,还需要在AndroidManifest中配置服务属性。

<service
    android: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>

刚开始配置服务时候其实代码只有这样:

<service
    android: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是没有被杀死,它自己启动服务也正常工作的。

<service
    android:name=".MyBackgroundService">
</service>

这是服务的配置,服务在高版本中需要主动声明对外可访问。我把对外访问的配置补充完整:

```xml
<service
    android: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" />
        <service
            android: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或ComponentName
    serviceIntent.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>
    <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.ChipDemo"
        tools:targetApi="31">
        <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>

也就是调用服务的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: 7106
    android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service Intent { pkg=com.cat.tv cmp=com.cat.tv/.MyBackgroundService (has extras) }: app is in background uid null
        at 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,操作更方便哦


http://www.kler.cn/news/350828.html

相关文章:

  • Java 多线程(八)—— 锁策略,synchronized 的优化,JVM 与编译器的锁优化,ReentrantLock,CAS
  • Openpyxl--学习记录
  • 【AI服务器】全国产PCIe 5.0 Switch SerDes 测试和分析,以11槽PCIe GPU底板(PCIe 4.0/5.0)为例(二)
  • vue3移动端可同时上传照片和视频的组件
  • LabVIEW伺服压机是如何实现压力位移的精度?
  • Docker快速安装Grafana
  • HDFS单元测试
  • 曲线的弧长与曲率
  • 1.3.ReactOS系统宏函数ASSERT的实现
  • 【SAM模型应用于遥感影像|论文解读3】突破边界与一致性:SAM模型革新遥感影像语义分割
  • 大模型入门到精通!大模型应用开发极简入门(含PDF)
  • 信息安全工程师(52)网络安全审计系统组成与类型
  • 第3篇:传输层协议
  • Spark高级用法-数据源的读取与写入
  • Centos 7.5上配置mailx发送邮件
  • 《C++开发 AR 游戏:开启未来娱乐新潮流》
  • 六、IPD 方法论框架(IPD的核心流程)
  • UPDATE 更新数据
  • 【FP60】林业害虫数据集——目标检测、图像分类
  • 微软十月补丁星期二发现了 118 个漏洞
  • windows性能调优--基本性能优化
  • 传感器应用注意事项
  • PDF-XChange PRO v10.4.2.390 x64 已授权中文特别版
  • C++面试速通宝典——29
  • java代码生成器集成dubbo,springcloud详解以及微服务遐想
  • 【Golang】Go语言Web开发之模板渲染