应用预测框架-基于Android S

总括

本文基于Android S 应用预测框架,导入了一个tensorflow模型并运行成功,意在调研下Android系统的应用预测流程。

背景介绍

Android 12的应用预测功能是其核心创新之一,旨在通过智能化技术优化用户交互体验。以下是基于官方技术逻辑的详细说明:

一、功能核心机制

  1. 数据收集与行为建模

    Android 12通过机器学习模型持续分析用户行为数据,包括应用使用频率、时间段(如早晨查看新闻、通勤时使用娱乐应用)、场景(工作或休闲)等,构建动态的用户行为画像

    • 数据类型:涵盖应用启动顺序、地理位置、设备使用模式等维度。
    • 隐私保护:数据仅在本地处理,避免上传云端,符合Android隐私沙盒规范。
  2. 智能预测算法

    系统采用多维度关联分析算法,不仅预测单个应用的启动概率,还识别应用间的关联性(例如打开邮件后可能启动日历或文档编辑器)

    • 实时行为整合:结合用户当前操作(如插耳机可能触发音乐应用预加载)进行动态调整。
  3. 后台预加载技术

    预测到用户可能使用的应用后,系统会在后台提前加载其核心组件(如主界面、常用功能模块),但不会完全启动应用进程以节省资源

    • 资源调度优化:通过智能优先级管理,确保预加载不影响前台应用的流畅度或电池续航。

二、实际应用场景(官方示例)

  1. 高频场景预判

    • 早晨例行操作:系统识别用户起床后常打开社交媒体或新闻应用,提前预加载,实现"点击即启动"
    • 通勤娱乐需求:检测到用户连接蓝牙耳机或进入地铁区域时,自动预加载音乐、视频类应用。
  2. 工作流效率提升

    • 在用户启动会议软件后,系统可能预加载文档协作工具(如Google Docs)或邮件客户端,减少多任务切换的等待时间

三、用户体验优化效果

  1. 启动速度提升

    • 官方测试显示,预加载技术可将高频应用的启动时间缩短50%以上,部分场景下实现"瞬时响应"。
  2. 资源与能效平衡

    • 通过动态资源分配,预加载仅占用闲置内存,并在检测到电量不足时自动降低预加载强度,确保续航不受显著影响。

代码实现

关于这部分网上的资料很少,未能找到相关的资料,本着按图索骥的原则,下载一套android 12源码分析一下,以下的分析基于android-12 r3.

一、AppPredictionManagerService

想要实现应用预测的功能,必须有一个系统服务来承担数据采集和预测启动的能力,本着这个思路去检索,发现了"AppPredictionManagerService"

  • AppPredictionManagerService 在SystemServer的startOtherServices函数中启动
  • 需要配置AppPredictionManagerService 绑定的客户端,默认无配置,则不会启动改服务
less 复制代码
 private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
   // 省略部分代码
   // App prediction manager service
   if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
       t.traceBegin("StartAppPredictionService");
       mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
       t.traceEnd();
   } else {
       Slog.d(TAG, "AppPredictionService not defined by OEM");
  }
 }
xml 复制代码
    <!-- The package name for the system's app prediction service.
         This service must be trusted, as it can be activated without explicit consent of the user.
         Example: "com.android.intelligence/.AppPredictionService"
    -->
    <string name="config_defaultAppPredictionService" translatable="false"></string>

同样通过按图索骥的方式,检索到了客户端,模块名为AppPredictionService(com.android.apppredictionservice),这个模块默认不会编译到镜像里面,需要手动添加,代码修改如下:

bash 复制代码
/code/Android12/build$ vi make/target/product/handheld_system_ext.mk +25
PRODUCT_PACKAGES += \
    Launcher3QuickStep \
    Provision \
    Settings \
    StorageManager \
    SystemUI \
    WallpaperCropper \
    AppPredictionService \
xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.apppredictionservice">

    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

    <application>
        <service
            android:name=".PredictionService"
            android:exported="true">
            <intent-filter>
                <!-- This constant must match AppPredictionService.SERVICE_INTERFACE -->
                <action android:name="android.service.appprediction.AppPredictionService" />
            </intent-filter>
        </service>

    </application>

</manifest>

将config_defaultAppPredictionService配置成对应的名字:

bash 复制代码
<string name="config_defaultAppPredictionService" translatable="false">com.android.apppredictionservice/.PredictionService</string>

好了所有的条件已经准备就绪,我们直接看下AppPredictionManagerService启动的时序图:

  • 服务启动只是把我们配置的客户端service名字记了下来并没有发起连接动作,这里我们需要继续调查服务是怎么绑定上的

二、Launcher3

通过分析log发现,应用和AppPredictionManagerService建立session连接的时候才会触发服务绑定,通过分析代码和增加trace,创建session 这个动作是在Launcher3中作的,AOSP Launcher3代码量很大,这里我们只涉及到感兴趣的部分,研究Launcher3不是目的。

  • 系统启动Launcher3时,Launcher3的onCreate方法中会调用到createPredictor
  • 创建Predictor时,会创建PredictorSession,这个方法的实现在AppPredictionManagerService中,这其中涉及到RemoteService(我想它就是"AppPredictionService"在fwk中的代号)
  • RemoteService会调用到binderservice最终拉起AppPredictionService(com.android.apppredictionservice)

三、AppPredictionService(com.android.apppredictionservice)

AppPredictionService模块就一个类,重点是这个框架,谷歌设计这个的本意是OEM可以自己扩展,目前基于AOSP释放的代码来分析。

  • PredictionService onCreate方法中默认DEFAULT_PACKAGES赋值了五个应用:日历、相册、map、email、浏览器;
  • 这五个应用就是预测应用的默认值,赋值完成后保存到本地sharedPreferences中;
xml 复制代码
emulator_x86_64:/data/data/com.android.apppredictionservice # cd shared_prefs/                                                                                                                                                                                                    
emulator_x86_64:/data/data/com.android.apppredictionservice/shared_prefs # cat mypref.xml                                                                                                                                                                                         
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="third">com.android.camera2/com.android.camera.CameraLauncher</string>
    <string name="fifth">com.android.deskclock/com.android.deskclock.DeskClock</string>
    <string name="fourth">com.android.contacts/com.android.contacts.activities.PeopleActivity</string>
    <string name="first">com.android.settings/com.android.settings.Settings</string>
    <string name="second">com.android.quicksearchbox/com.android.quicksearchbox.SearchActivity</string>
</map>
  • 当Laucnher3监控到有app动作时,会触发回调到系统中,系统会调用到AppPredictionService模块来更新target,更新完之后,AppPredictionService将packagelist返回给client,最终经过系统在传回到Launcher3中;

注:此图是简化版本工作流程,实际代码流程复杂。

  • Launcher3会将更新的预测应用展示出来,并保存到本地sharedPreferences,如下图,第一行5个应用就是预测应用;

现有算法

AOSP释放出来的就是一个空壳子,这部分涉及到OEM定制不开源,所以自带的算法基本上就是个demo:

  • 当触发感兴趣的事件时,更新预测list,当前list如果包含点击的应用则返回
  • 如果不包含,直接删除最后一个,将该次的应用添加到第一个
ini 复制代码
        // Check if packageName already exists in existing list of appNames
        for (String packageName:appNames) {
            if (packageName.contains(target.getPackageName())) {
                found = true;
                break;
            }
        }

        if (!found) {
            appNames.remove(appNames.size() - 1);
            appNames.add(0, mostRecentComponent);

            for (int i = 0; i < appNameKeys.length; i++) {
                editor.putString(appNameKeys[i], appNames.get(i));
            }
            editor.apply();

            predictionList.remove(predictionList.size() - 1);
            predictionList.add(0, event.getTarget());

            Log.d(TAG, "onAppTargetEvent:: update predictions");
            postPredictionUpdateToAllClients();
        }

一、Try TensorFlow

why TensorFlow

AOSP 从2017年开始逐步支持tensorflow,到现在越来越完善。

  • 2017年Google I/O大会首次宣布TensorFlow Lite作为轻量级解决方案,并于2017年11月15日正式发布开发者预览版
  • 2022年,TensorFlow Lite通过Google Play服务集成,成为Android官方ML推理引擎,提供稳定的跨版本API支持

TensorFlow在Android上的核心优势

轻量化与高效性能
  • TensorFlow Lite专为移动端优化,模型体积小(通过量化、剪枝等技术压缩),推理速度快,内存占用低
  • 支持硬件加速(如Android Neural Networks API),在GPU或NPU上运行时性能提升显著
跨平台兼容性
  • 模型可无缝部署到Android、iOS及嵌入式设备,且支持多种格式
  • 通过Google Play服务内置二进制文件,减少APK体积,并保持与不同Android版本的兼容性
  • 提供简洁的Java/Kotlin API和丰富的文档,降低开发门
  • 支持异步加载模型,避免主线程阻塞,提升应用流畅度
隐私与离线能力
  • 本地化推理无需依赖云端服务器,保护用户隐私,且在网络不佳时仍可运行
  • 支持设备端训练(如通过Model Maker库),实现个性化模型微调
生态与工具链完善
  • 提供模型转换工具(TFLite Converter)、调试工具及预训练模型库(如MobileNet),简化开发流程
  • 与TensorFlow生态系统(如TF Hub、TF.js)深度集成,便于模型复用与扩展

二、导入尝试

我们本地通过tensorflow做一个模型,尝试将它部署到AppPredictionService模块中:

行为模式模拟与预测
  • 通过生成合成数据(当前应用+时间),预测用户在不同时间段可能打开的下一个应用
  • 支持预测的包名包括日历、相机、通讯录等10个常见Android系统应用。
模型训练与部署
  • 使用TensorFlow构建双输入神经网络,结合嵌入层处理应用名称的类别特征和时间数值特征。
  • 最终将模型转换为TFLite格式(app_predictor.tflite),便于移动端低资源环境部。
导入模型

在APP bp文件中增加对tensorflow的依赖,将模型文件放到assert路径下,并配置依赖

css 复制代码
android_app {
    name: "AppPredictionService",

    defaults: ["apppredictionservice_defaults"],

    manifest: "AndroidManifest.xml",

    srcs: [
        ":AppPredictionService-srcs",
    ],

    static_libs: [
        "tensorflowlite_java",
    ],

    jni_libs: ["libtensorflowlite_jni"],
    aaptflags: ["-0 .tflite"],
    asset_dirs: ["assets"],

    resource_dirs: [
    ],
}

在APP 中加载模型

ini 复制代码
    private static final String MODEL_FILE = "models/app_predictor.tflite";
    try {

            encoder = LabelEncoder.loadFromAssets(getAssets(), "models/label_encoder.txt");
            String[] files = getAssets().list("models");
            Log.d(TAG, "模型目录文件: " + Arrays.toString(files));
            // 从 assets 加载模型
            AssetFileDescriptor fileDescriptor = getAssets().openFd(MODEL_FILE);
            FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
            FileChannel fileChannel = inputStream.getChannel();
            long startOffset = fileDescriptor.getStartOffset();
            long declaredLength = fileDescriptor.getDeclaredLength();
            MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);

            // 初始化解释器
            Interpreter.Options options = new Interpreter.Options();
            options.setUseNNAPI(false);  // 禁用神经网络API
            options.setUseXNNPACK(false); //禁用XNNPACK优化
            tflite = new Interpreter(buffer, options);
            validateModelInputs();
        } catch (IOException e) {
            Log.e(TAG, "Error loading model", e);
        }

当launcher发生点击事件时,触发预测动作:

ini 复制代码
    private void predictNextApp(String currentApp, int hour) {
        try {
            // 1. 编码并验证输入
            int encodedApp = encoder.encode(currentApp);
            if (encodedApp < 0 || encodedApp >= encoder.getClassCount()) {
                Log.e(TAG, "编码值越界: " + encodedApp);
                return;
            }
            float normalizedHour = hour / 23.0f;

            // 2. 创建输入缓冲区(假设模型期望形状为 [1, 1])
            ByteBuffer appBuffer = createInt32Buffer(encodedApp);  // 形状 [1, 1]
            ByteBuffer hourBuffer = createFloat32Buffer(normalizedHour);  // 形状 [1, 1]

            // 3. 输入包装为 Object[]
            Object[] inputs = new Object[]{appBuffer, hourBuffer};

            // 4. 运行推理
            float[][] output = new float[1][encoder.getClassCount()];
            Map<Integer, Object> outputs = new HashMap<>();
            outputs.put(0, output);
            tflite.runForMultipleInputsOutputs(inputs, outputs);

            // 6. 解析结果
            int predictedId = argmax(output[0]);
            String predictedApp = encoder.decode(predictedId);
            Log.d(TAG, "预测结果: " + predictedApp);
        } catch (Exception e) {
            Log.e(TAG, "推理异常: " + e.getMessage(), e);
        }
    }

模拟器验验证可用:

yaml 复制代码
04-01 22:43:12.112  1507  1507 D PredictionService: onAppTargetEvent com.android.dialer
04-01 22:43:12.112  1507  1507 D PredictionService: 预测结果: com.android.deskclock

代码路径

本文涉及到的源码基于Android 12, 涉及到的源码路径如下:

  • App:

" packages/apps/OnDeviceAppPrediction "

" packages/apps/Launcher3 "

  • Framework:

" frameworks/base/core/java/android/service/appprediction/ "

" frameworks/base/services/appprediction/java/com/android/server/appprediction/ "

" frameworks/base/core/java/android/app/prediction/ "

  • Tensorflow

" external/tensorflow "

相关推荐
用户685453759776939 分钟前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo1 小时前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM971 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack1 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo1 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊2 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说2 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者2 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端
苏三说技术3 小时前
Spring AI 和 LangChain4j ,哪个更好?
后端