Connect with us

Разработка

Будущее Android-приложений с AppFunctions

Это новый мост, который позволяет основной логике вашего приложения выйти за пределы пользовательского интерфейса и попасть в мир автономных агентов. 

Опубликовано

/

     
     

В настоящее время мы наблюдаем самый значительный сдвиг в мобильных вычислениях со времен открытия App Store. Мы официально вступили в Эру Агентного ИИ, где «строка поиска» заменяется «пузырьком чата».

В течение десяти лет мы одержимо оптимизировали наши Android-приложения для глубоких ссылок и SEO, чтобы сделать их доступными и открываемыми из поиска Google. Но в мире, где пользователи просят ИИ-агента «забронировать рейс» или «составить краткое изложение моих заметок» вместо того, чтобы открыть приложение, эти инструменты устаревают. Если ИИ-агент не может «видеть» внутреннее содержимое вашего приложения, вашего приложения не существует. Но как сделать функциональность вашего приложения доступной для интеллекта будущего? Ответ — Android AppFunctions. Это новый мост, который позволяет основной логике вашего приложения выйти за пределы пользовательского интерфейса и попасть в мир автономных агентов.

Что такое AppFunctions

Android AppFunctions позволяет приложениям обмениваться данными и функциональностью с агентами и ИИ-помощниками, предоставляя разработчикам возможность создавать самоописывающиеся функции, которые агентные приложения могут обнаруживать и выполнять, используя естественный язык. Это решение для Android-приложений на устройстве дает возможности, аналогичное возможностям бэкэнда, объявляемым через облачные MCP-серверы. Подробнее об этом можно прочитать в официальном блоге Google.

В прошлом году, когда я впервые столкнулся с этим API, я поделился своими мыслями, признавая огромное количество потенциальных вариантов его использования.

Будущее Android-приложений с AppFunctions

Согласно официальному блогу, хотя пока не для каждого сценария взаимодействия существует отдельная интеграция, компания Google разрабатывает фреймворк UI-автоматизации для AI-агентов и ассистентов. Он позволит им интеллектуально выполнять типовые задачи в установленных у пользователя приложениях, обеспечивая прозрачность и контроль со стороны пользователя. Если приложению не требуется предоставлять структурированный способ взаимодействия с более тонким уровнем контроля, внедрение AppFunctions является необязательным.

На момент написания этой статьи, он доступен только на Samsung S26 Ultra и Google Pixel 10. Пользователи этих устройств могут получить доступ к функциям приложений из установленных приложений через приложение Gemini.

Как интегрировать AppFunctions

AppFunctions входят в состав SDK Android 16, а также доступны через библиотеку Jetpack, что позволяет бесшовно интегрировать их в приложения без проблем с совместимостью. В основе лежит механизм межпроцессного взаимодействия, аналогичный AIDL, благодаря чему обеспечиваются меры безопасности — не любое приложение может вызывать функции другого приложения. Более подробные детали будут рассмотрены в следующем разделе.

Будущее Android-приложений с AppFunctions

Библиотека 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

При выполнении этой команды может появиться примерно такой результат.

Будущее Android-приложений с AppFunctions

Это позволяет увидеть полную информацию обо всех функциях, включая их идентификаторы, описания, типы и описания параметров, типы данных, возможность присвоения значения 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\"}'"

При выполнении этой команды в приложении будет создана заметка, которая может выглядеть следующим образом:

Будущее Android-приложений с AppFunctions

Вот так и выглядит тестирование.

Бонус — приложение агента

Я попытался имитировать приложение агента, чтобы понять его работу (хотя это не настоящий агент, а просто жестко закодированные действия, запускаемые определенными сообщениями), и оно выглядит следующим образом:

Вы можете найти это демонстрационное приложение здесь и поэкспериментировать с ним, если вам интересно.

Однако это не означает, что любое случайное приложение можно установить в качестве агента для вызова функций из любого приложения, которое их предоставляет. Если приложение попытается это сделать, возникнет исключение SecurityException, поскольку это ограниченный API, доступный только системным приложениям. В приведенной выше демонстрации я установил фиктивное приложение-агента как системное приложение в эмуляторе, что позволило мне поэкспериментировать с ним. Приложение-агент должно объявить разрешение в манифесте: <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" />. Поэтому только официальным приложениям, таким как приложения от Google/Gemini и OEM-производителей, разрешено вызывать эти функции приложения. Приложения-агенты могут использовать AppFunctionManager для запроса всех приложений и их доступных функций. Это работает следующим образом:

Будущее Android-приложений с AppFunctions

Что вы думаете об этой нововведении? Я искренне рад его потенциально широкому применению в приложениях. С развитием голосовой поддержки и ИИ-агентов это может стать выдающейся функцией в будущих тенденциях разработки Android. Представляю, как это можно использовать:

  • Повторите для меня последнее заказанное индийское блюдо на Zomato
  • Мне нужно испечь шоколадный торт на четверых; пожалуйста, закажите все необходимые ингредиенты в Swiggy Instamart
  • Закажите такси в аэропорт на завтра в 05:45 через Uber
  • Сочините милое стихотворение, чтобы поздравить моего друга X с днем ​​рождения, и отправьте его ему в WhatsApp
  • Отправьте X 500 за совместное использование такси через приложение UPI — приложение находит данные X в телефонной книге и просто запрашивает ввод пин-кода для оплаты, и всё!

и так далее…

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: