В настоящее время мы наблюдаем самый значительный сдвиг в мобильных вычислениях со времен открытия App Store. Мы официально вступили в Эру Агентного ИИ, где «строка поиска» заменяется «пузырьком чата».
В течение десяти лет мы одержимо оптимизировали наши Android-приложения для глубоких ссылок и SEO, чтобы сделать их доступными и открываемыми из поиска Google. Но в мире, где пользователи просят ИИ-агента «забронировать рейс» или «составить краткое изложение моих заметок» вместо того, чтобы открыть приложение, эти инструменты устаревают. Если ИИ-агент не может «видеть» внутреннее содержимое вашего приложения, вашего приложения не существует. Но как сделать функциональность вашего приложения доступной для интеллекта будущего? Ответ — Android AppFunctions. Это новый мост, который позволяет основной логике вашего приложения выйти за пределы пользовательского интерфейса и попасть в мир автономных агентов.
Что такое AppFunctions
Android AppFunctions позволяет приложениям обмениваться данными и функциональностью с агентами и ИИ-помощниками, предоставляя разработчикам возможность создавать самоописывающиеся функции, которые агентные приложения могут обнаруживать и выполнять, используя естественный язык. Это решение для Android-приложений на устройстве дает возможности, аналогичное возможностям бэкэнда, объявляемым через облачные MCP-серверы. Подробнее об этом можно прочитать в официальном блоге Google.
В прошлом году, когда я впервые столкнулся с этим API, я поделился своими мыслями, признавая огромное количество потенциальных вариантов его использования.
Согласно официальному блогу, хотя пока не для каждого сценария взаимодействия существует отдельная интеграция, компания Google разрабатывает фреймворк UI-автоматизации для AI-агентов и ассистентов. Он позволит им интеллектуально выполнять типовые задачи в установленных у пользователя приложениях, обеспечивая прозрачность и контроль со стороны пользователя. Если приложению не требуется предоставлять структурированный способ взаимодействия с более тонким уровнем контроля, внедрение AppFunctions является необязательным.
На момент написания этой статьи, он доступен только на Samsung S26 Ultra и Google Pixel 10. Пользователи этих устройств могут получить доступ к функциям приложений из установленных приложений через приложение Gemini.
Как интегрировать AppFunctions
AppFunctions входят в состав SDK Android 16, а также доступны через библиотеку Jetpack, что позволяет бесшовно интегрировать их в приложения без проблем с совместимостью. В основе лежит механизм межпроцессного взаимодействия, аналогичный AIDL, благодаря чему обеспечиваются меры безопасности — не любое приложение может вызывать функции другого приложения. Более подробные детали будут рассмотрены в следующем разделе.
Библиотека Jetpack включает annotation processor, который упрощает создание типобезопасных схем и функций. Разработчику достаточно объявить, что делает функция, какие параметры ей нужны и что она возвращает. Этот annotation processor генерирует контракт, благодаря которому приложение-агент понимает, как вызывать функции приложения — включая необходимые параметры и их типы. По сути, это похоже на то, как LLM-агент понимает, как вызвать функцию при настроенном Model Context Protocol.
На текущий момент последняя версия AppFunctions — v1.0.0-alpha08, поэтому, если вы читаете это позже, стоит ожидать значительных изменений. У меня уже есть пример приложения для заметок на GitHub, поэтому я решил интегрировать это именно туда. Таким образом, приложениям необходимо добавить в проект следующие зависимости:
dependencies {
val appfunversion = "1.0.0-alpha08"
implementation("androidx.appfunctions:appfunctions-service:$appfunversion")
implementation("androidx.appfunctions:appfunctions:$appfunversion")
ksp("androidx.appfunctions:appfunctions-compiler:$appfunversion")
}
// Configure KSP
ksp {
arg("appfunctions:aggregateAppFunctions", "true")
arg("appfunctions:generateMetadataFromSchema", "false")
}
NotyKT — это простое приложение для создания заметок, поддерживающее следующие операции: создание, вставка, обновление и удаление.
// A model to expose via AppFunctions
@AppFunctionSerializable(isDescribedByKDoc = true)
data class Note(
/** The note's identifier */
val id: String,
/* The note's title */
val title: String,
/* The note's content */
val content: String,
)
// Function's entry point
class NotyAppFunctions @Inject constructor(
@LocalRepository private val repository: NotyNoteRepository
) {
/**
* Lists all available notes.
*
* @param appFunctionContext The context in which the AppFunction is executed.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun listNotes(appFunctionContext: AppFunctionContext): List<Note> =
repository.getAllNotes().map { Note(it.id, it.title, it.note) }
/**
* Adds a new note to the app.
*
* @param appFunctionContext The context in which the AppFunction is executed.
* @param title The title of the note.
* @param content The note's content.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun createNote(
appFunctionContext: AppFunctionContext,
title: String,
content: String,
): Note? = /* Implementation */
/**
* Edits a single note.
*
* @param appFunctionContext The context in which the AppFunction is executed.
* @param noteId The target note's ID.
* @param title The note's title if it should be updated.
* @param content The new content if it should be updated.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun editNote(
appFunctionContext: AppFunctionContext,
noteId: String,
title: String?,
content: String?,
): Note? = /* Implementation */
/**
* Deletes a single note.
*
* @param appFunctionContext The context in which the AppFunction is executed.
* @param noteId The target note's ID.
*
* @return Whether the note was deleted or not.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun deleteNote(
appFunctionContext: AppFunctionContext,
noteId: String,
): Boolean = /* Implementation */
}
Обратите внимание, что здесь всё определено вполне явно. Основную рутинную работу берёт на себя annotation processor. Аннотация @AppFunction(isDescribedByKDoc = true) генерирует структурированную схему: подтягивает описание функции из KDoc, документирует каждый параметр и описывает модель возвращаемого значения.
Но здесь возникает практический вопрос: если этой функции в качестве зависимости нужен репозиторий, как приложению создавать её экземпляр по требованию? В идеальном случае, если бы функция была объявлена без зависимостей, её инстанс можно было бы создавать автоматически. Однако при наличии зависимости необходимо предоставить фабрику. Так как большинство приложений используют DI-фреймворки, это обычно решается именно через них. В данном случае в классе Application приложения нужно реализовать новый интерфейс и переопределить его метод, как показано в примере ниже. В качестве DI-решения здесь используется Dagger Hilt.
// 1. Implement the interface
@HiltAndroidApp
class NotyApp : Application(), AppFunctionConfiguration.Provider {
@Inject lateinit var appFunctions: Provider<NotyAppFunctions>
/*other impl*/
override val appFunctionConfiguration: AppFunctionConfiguration
get() = AppFunctionConfiguration
.Builder()
.addEnclosingClassFactory(NotyAppFunctions::class.java) {
appFunctions.get()
}
.build()
}
Вот и всё! Так можно реализовать AppFunctions в приложении.
Как протестировать?
В настоящее время приложение Gemini не может напрямую вызывать функции, но утилита командной строки ADB может помочь в тестировании интеграции. Убедитесь, что у вас установлены platform-tools в SDK, и следующие команды ADB могут помочь.
1. Список функций
# Lists all the app functions exposed by all apps installed on the device. adb shell cmd app_function list-app-functions # Package specific adb shell cmd app_function list-app-functions --package dev.shreyaspatil.noty.composeapp
При выполнении этой команды может появиться примерно такой результат.
Это позволяет увидеть полную информацию обо всех функциях, включая их идентификаторы, описания, типы и описания параметров, типы данных, возможность присвоения значения NULL, а также ту же информацию о возвращаемых значениях.
2. Выполнение функций приложения
Команда для выполнения функций приложения:
adb shell cmd app_function execute-app-function \
--package <PACKAGE_NAME> \
--function <FUNCTION_ID> \
--parameters <PARAMS_IN_JSON>
Допустим, я хочу запустить функцию приложения для выполнения простой операции чтения — получение списка заметок в приложении:
adb shell cmd app_function execute-app-function \
--package dev.shreyaspatil.noty.composeapp \
--function dev.shreyaspatil.noty.appfunctions.NotyAppFunctions#listNotes \
--parameters {}
В данный момент при попытке запуска система заставляет меня передавать параметры, поэтому мне приходится указывать фигурные скобки {}.
В результате получается JSON-вывод следующего вида, соответствующий модели контракта, определенной в нашем приложении:
{
"androidAppfunctionsReturnValue": [
{
"id": ["TMP-053ffdbe-7997-4ba6-9187-3a4ef6be0833"],
"title": ["💡Ideas"],
"content": ["- Android AppFunctions demo\n- Android Foldable device APIs\n- Generative AI"]
},
{
"id": ["TMP-58832e3e-da0f-4542-aa55-68fbc9edea19"],
"title": ["👨🏻💻 Today's tasks"],
"content": ["Work on the open-source sample to demonstrate the use of Android AppFunctions"]
}
]
}
Допустим, мы хотим создать заметку. Команда для этого может выглядеть следующим образом:
adb shell "cmd app_function execute-app-function \
--package dev.shreyaspatil.noty.composeapp \
--function dev.shreyaspatil.noty.appfunctions.NotyAppFunctions#createNote \
--parameters '{\"title\":\"hello from shell\",\"content\":\"created from the terminal\"}'"
При выполнении этой команды в приложении будет создана заметка, которая может выглядеть следующим образом:
Вот так и выглядит тестирование.
Бонус — приложение агента
Я попытался имитировать приложение агента, чтобы понять его работу (хотя это не настоящий агент, а просто жестко закодированные действия, запускаемые определенными сообщениями), и оно выглядит следующим образом:
Вы можете найти это демонстрационное приложение здесь и поэкспериментировать с ним, если вам интересно.
Однако это не означает, что любое случайное приложение можно установить в качестве агента для вызова функций из любого приложения, которое их предоставляет. Если приложение попытается это сделать, возникнет исключение SecurityException, поскольку это ограниченный API, доступный только системным приложениям. В приведенной выше демонстрации я установил фиктивное приложение-агента как системное приложение в эмуляторе, что позволило мне поэкспериментировать с ним. Приложение-агент должно объявить разрешение в манифесте: <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" />. Поэтому только официальным приложениям, таким как приложения от Google/Gemini и OEM-производителей, разрешено вызывать эти функции приложения. Приложения-агенты могут использовать AppFunctionManager для запроса всех приложений и их доступных функций. Это работает следующим образом:
Что вы думаете об этой нововведении? Я искренне рад его потенциально широкому применению в приложениях. С развитием голосовой поддержки и ИИ-агентов это может стать выдающейся функцией в будущих тенденциях разработки Android. Представляю, как это можно использовать:
- Повторите для меня последнее заказанное индийское блюдо на Zomato
- Мне нужно испечь шоколадный торт на четверых; пожалуйста, закажите все необходимые ингредиенты в Swiggy Instamart
- Закажите такси в аэропорт на завтра в 05:45 через Uber
- Сочините милое стихотворение, чтобы поздравить моего друга X с днем рождения, и отправьте его ему в WhatsApp
- Отправьте X 500 за совместное использование такси через приложение UPI — приложение находит данные X в телефонной книге и просто запрашивает ввод пин-кода для оплаты, и всё!
и так далее…

