2026 Google I/O Extended · Hands-on

Android App Functions
내 앱을 에이전트에게
연결하기

@AppFunction 하나로, 내 앱의 기능을 AI Agent에게 제공할 수 있습니다.

류기민 · GDG Korea Android Organizer
Learning Goals

오늘 세션이 끝나면
이 세 가지를 할 수 있습니다

01 · Understand

App Function의
개념과 동작 원리

플랫폼이 앱의 기능을 어떻게 발견하고, 에이전트가 어떻게 호출하는지 전체 그림을 이해합니다.

02 · Build

내 프로젝트에
App Function 추가

의존성 추가부터 AppFunctionService 정의, 어노테이션 작성까지 직접 해봅니다.

03 · Verify

App Function을
테스트하는 방법

adb 직접 호출과 Testing Agent 두 가지 경로로 동작을 검증합니다.

2
Agenda

진행 순서

01

실습 환경 세팅

  • Android CLI 설치
  • 주요 명령어 둘러보기
  • SDK 36 에뮬레이터 생성
약 15분
02

App Function 개요

  • 정의와 사용 사례
  • 작동 방식 & 주요 어노테이션
  • 프로젝트에 추가하는 법
약 30분
03

직접 만들어보기

  • 공식 샘플로 호출 체험
  • 내 프로젝트에 구현
  • 발표 & 시상 🏆
나머지 시간
3
SECTION 01

실습 환경 세팅

Android Studio를 열기 전에, 터미널만으로 SDK와 에뮬레이터를 준비합니다.
새로 나온 Android CLI 하나면 충분합니다.
4
Section 01 · Setup

Android CLI 설치Android 개발 환경을 터미널에서 통째로 다루는 새 공식 도구

무엇을 대체하나요?

sdkmanager, avdmanager, emulator, adb를 몰라도 됩니다. SDK 설치 · 에뮬레이터 관리 · 프로젝트 생성 · 앱 배포까지 단일 진입점으로 묶여 있습니다.

왜 지금 이걸 쓰나요?
AI 에이전트가 Android 개발 환경을 다룰 수 있도록 설계된 CLI입니다. 그래서 사람이 쓰기에도 명령어 체계가 훨씬 단순합니다.

공식 문서

install
# macOS (Apple Silicon) / Linux — 설치 스크립트 한 줄 $ curl -fsSL https://dl.google.com/android/cli/latest/\ darwin_arm64/install.sh | bash # 설치 확인 $ android -V # 환경 정보 — SDK 위치, JDK, 연결된 디바이스 $ android info # CLI 자체 업데이트 $ android update
5
Section 01 · Setup

명령어 지도 android -h오늘 실제로 쓸 명령어는 파란색으로 표시했습니다.

android -h
$ android -h Usage: android [-hV] [--sdk=PARAM] [COMMAND] Commands: sdk // SDK 패키지 설치 · 목록 · 업데이트 ← sdkmanager 역할 emulator // AVD 생성 · 실행 · 중지 · 삭제 ← avdmanager + emulator 역할 create // 새 Android 프로젝트 생성 run // 앱 빌드 후 디바이스에 배포/실행 screen // 디바이스 화면 보기 / 캡처 layout // 실행 중인 앱의 레이아웃 트리 출력 ← 에이전트가 화면을 "읽는" 통로 describe // 프로젝트 구조·메타데이터 분석 skills // 스킬 관리 — App Functions 스킬도 여기서 (섹션 3에서 사용) init // 에이전트용 환경(스킬 등) 초기화 studio // Android Studio 연동 docs // 문서 검색 info // 환경 정보 출력 update // Android CLI 업데이트 # 모든 서브커맨드는 -h 로 도움말을 볼 수 있습니다 $ android emulator create -h
6
Section 01 · Setup

SDK 36 에뮬레이터 만들기 ① AI에게 맡기기App Functions는 API 36+ 에서만 동작합니다. 먼저 에뮬레이터부터 준비합니다.

PROMPT
android CLI를 이용하여 SDK 36 에뮬레이터를 생성해줘”

1. 환경 파악

에이전트가 android info, android sdk list로 현재 SDK 상태를 스스로 확인합니다.

2. 이미지 설치

API 36 시스템 이미지가 없으면 android sdk install로 내려받습니다. (수 GB, 시간이 걸립니다)

3. 생성 & 검증

android emulator createemulator list로 결과까지 확인해서 알려줍니다.

실습 팁 · 지금 다운로드를 걸어두고, 받는 동안 섹션 2 이론을 함께 보겠습니다.
7
Section 01 · Setup

SDK 36 에뮬레이터 만들기 ② 직접 호출AI가 실제로 무슨 일을 했는지 그대로 따라가 봅니다.

zsh — API 36 emulator
# 1. 환경 확인 (SDK 위치 등) $ android info # 2. 설치 가능한 패키지 확인 $ android sdk list # 3. API 36 시스템 이미지 설치 ← 가장 오래 걸리는 단계 $ android sdk install system-images/android-36/google_apis_playstore/arm64-v8a ↑ Intel/Windows/Linux 라면 x86_64 # 4. 사용 가능한 디바이스 프로필 확인 $ android emulator create --list-profiles small_phone medium_phone medium_tablet small/medium/large_desktop # 5. 에뮬레이터 생성 (설치된 이미지 중 최신 버전을 자동 선택 → 3번이 필수) $ android emulator create medium_phone # 6~9. 확인 / 실행(부팅 완료까지 대기 후 리턴) / 중지 / 삭제 $ android emulator list $ android emulator start medium_phone $ android emulator stop medium_phone $ android emulator remove medium_phone
8
SECTION 02

App Function 개요

앱의 기능에 어노테이션만 붙이면 시스템이 색인해두고,
AI 에이전트가 그것을 발견해서 대신 호출합니다.
9
10
Section 02 · Overview

App Function이란

앱의 핵심 기능을 시스템과 AI 에이전트가 호출할 수 있는 함수
공개하는 Android API.

선언은 Kotlin 함수로

별도 스키마 언어나 서버 등록이 없습니다. 기존 함수에 @AppFunction을 붙이면 끝입니다.

설명은 KDoc으로

isDescribedByKDoc = true — 여러분이 이미 쓰던 주석이 그대로 LLM을 위한 함수 설명이 됩니다.

실행은 내 앱 안에서

내 앱 프로세스 안에서, 내 권한으로 실행됩니다. 화면을 띄우지 않고 백그라운드 실행도 가능합니다.

핵심 전환 · 지금까지 앱은 사람이 보는 화면을 제공했습니다. App Functions는 여기에 에이전트가 부르는 API라는 두 번째 표면을 추가합니다.
11
Section 02 · Use Cases

어떤 앱에 어울릴까요“사용자가 이 앱에서 반복적으로 하는 한 문장짜리 행동”이 곧 App Function 후보입니다.

예시 01

할일 관리 &
생산성

할일 추가, 목록 조회, 완료 처리, 마감일 변경.

“내일 오전까지 발표자료 준비 할일로 추가해줘”
예시 02

미디어 &
엔터테인먼트

곡·에피소드 재생, 플레이리스트 추가, 재생목록 검색.

“운동할 때 듣던 플레이리스트 틀어줘”
예시 03

앱 간
워크플로

한 앱의 결과를 다른 앱의 입력으로. 에이전트가 두 함수를 이어 붙입니다.

“방금 찍은 사진 가족 그룹방에 보내줘”
예시 04

캘린더 &
일정

일정 생성·조회·변경, 참석자 추가, 빈 시간 찾기.

“다음 주 화요일 오후에 회의 잡아줘”
12
Section 02 · Use Cases

예시 ① 할일 관리 — 코드로 보기공식 문서 add-appfunctionscreateTask 예제. “할일로 추가해줘” 한 문장이 이 함수로 연결됩니다.

@RequiresApi(36) @AndroidEntryPoint @AppFunctionServiceEntryPoint( serviceName = "TaskAppFunctionService", appFunctionXmlFileName = "task_app_function_service", ) abstract class BaseTaskAppFunctionService : AppFunctionService() { @Inject internal lateinit var taskRepository: TaskRepository /** * Creates a task based on [createTaskParams]. * * @param createTaskParams The parameter to describe how to create the task. */ @AppFunction(isDescribedByKDoc = true) suspend fun createTask(createTaskParams: CreateTaskParams): Task = withContext(Dispatchers.IO) { if (createTaskParams.title == null && createTaskParams.content == null) throw AppFunctionInvalidArgumentException("Title or content should be non-null") val id = taskRepository.createTask(createTaskParams.title, createTaskParams.content) return@withContext taskRepository.getTask(id)?.toTask() ?: throw AppFunctionElementNotFoundException("Task not found for ID = $id") } }

KDoc = 함수 설명

isDescribedByKDoc = true“Creates a task…” 주석이 그대로 에이전트가 읽는 명세가 됩니다.

// 파라미터 타입 (개념 예시) @AppFunctionSerializable data class CreateTaskParams( /** Title of the task. */ val title: String? = null, /** Content body. */ val content: String? = null, )
실패도 대화로 · 둘 다 비면 AppFunctionInvalidArgumentException을 던져, 에이전트가 사용자에게 다시 물어보게 합니다.
13
Section 02 · How it works

App Functions 작동 방식

App Functions architecture

① 선언 & 등록 — 제공하는 쪽

내 앱이 @AppFunction으로 선언한 함수가, 설치 시점에 AppFunctionManager(플랫폼)에 메타데이터로 색인됩니다.

② Retrieve — 조회하는 쪽

에이전트 앱이 “지금 이 기기에서 부를 수 있는 함수 목록”을 플랫폼에 요청합니다. 앱 이름을 미리 알 필요가 없습니다.

③ Select & Execute

모델이 사용자의 말과 함수 설명을 매칭해 하나를 고르고, 플랫폼을 통해 내 앱 프로세스에서 실행합니다.

14
Section 02 · How it works

두 개의 역할, 두 개의 관문오늘 우리가 만드는 것은 오른쪽 제공하는 쪽입니다.

CONSUMER

조회하는 쪽 — 에이전트 · 시스템 앱

아무 앱이나 다른 앱의 기능을 마음대로 부를 수 있으면 안 되겠죠. 그래서 권한이 관문입니다.

<uses-permission android:name= "android.permission.EXECUTE_APP_FUNCTIONS" />
  • 이 권한이 있어야 함수 목록 조회 · 실행이 가능합니다
  • 일반 앱은 받을 수 없는 제한된 권한입니다 (Gemini 같은 시스템 에이전트용)
  • 실습에서는 공식 agent 앱을 run_privileged.sh로 권한을 부여해 실행합니다
PROVIDER · 오늘의 주제

제공하는 쪽 — 우리 앱의 서비스

androidx.appfunctions 라이브러리의 어노테이션만으로 기능을 공개합니다.

  • Kotlin 함수 + 어노테이션 → KSP가 컴파일 타임에 처리
  • 메타데이터 XML과 호출 디스패처 코드가 자동 생성됩니다
  • 리플렉션 없음 · 런타임 등록 없음 → 앱 설치만으로 시스템이 인지
잠시 뒤 어노테이션 프로세싱으로 생성되는 XML도 직접 열어봅니다.
15
Section 02 · Annotations

@AppFunctionServiceEntryPointApp Function들을 담을 서비스를 선언합니다. — ChatApp 샘플 실제 코드

@RequiresApi(36) @AndroidEntryPoint // Hilt 주입 사용 @AppFunctionServiceEntryPoint( serviceName = "ChatAppFunctionService", // 생성될 Service 클래스 이름 appFunctionXmlFileName = "chat_app_function_service", ) abstract class BaseChatAppFunctionService : AppFunctionService() { @Inject lateinit var messageRepository: MessageRepository @Inject lateinit var recipientsRepository: RecipientsRepository @Inject lateinit var callManager: CallManager // ... 여기에 @AppFunction 함수들이 들어갑니다 }

왜 abstract 인가요?

KSP가 이 클래스를 상속한 구현 서비스(ChatAppFunctionService)를 생성합니다. 우리는 비즈니스 로직만 씁니다.

DI를 그대로 쓸 수 있습니다

@AndroidEntryPoint + @Inject — 기존 Repository 계층을 그대로 재사용합니다. App Function을 위해 아키텍처를 바꿀 필요가 없습니다.

appFunctionXmlFileName

생성될 메타데이터 XML 파일명. Manifest의 android.app.appfunctions.v2 값과 짝을 맞춥니다.

16
Section 02 · Annotations

@AppFunction · isDescribedByKDocKDoc이 곧 LLM에게 전달되는 함수 명세가 됩니다.

/** * Send a text message with optional image attachments to an individual contact or group. * Required workflow: Call "searchContacts" first to obtain a valid endpointValue. * * @param endpointValue The unique identifier for the recipient or group. * @param messageBody The text content of the message. Cannot be empty or blank. * @param imageUris Optional list of image URIs to attach to the message. * @return A human-readable message indicating the error or confirmation. * @throws AppFunctionInvalidArgumentException If messageBody is blank. If thrown, * ask the user to provide the message content to send. * @throws AppFunctionElementNotFoundException If no contact matches endpointValue. * If thrown, call "searchContacts" to find the correct ID. */ @AppFunction(isDescribedByKDoc = true) suspend fun sendMessage( endpointValue: String, messageBody: String, imageUris: List<Uri>? = null, ): String { if (messageBody.isBlank()) throw AppFunctionInvalidArgumentException("Message body cannot be empty") // ... return "Message sent to: $displayName." }
주석을 LLM에게 쓴다고 생각하세요.
“이 함수를 언제 부르는지”, “실패하면 사용자에게 뭐라고 물어봐야 하는지”까지 적습니다.

워크플로를 명시

“Call searchContacts first” — 함수 간 호출 순서를 문장으로 알려주면 모델이 그 순서를 지킵니다.

예외도 복구 지시로

@throws“If thrown, ask the user to…”를 적으면, 실패가 대화의 다음 질문으로 이어집니다.

값 제약

@AppFunctionStringValueConstraint(enumValues = [...])로 허용 값을 못 박을 수 있습니다.

17
Section 02 · Annotations

@AppFunctionSerializable파라미터·반환 타입으로 쓸 커스텀 데이터 클래스를 등록합니다.

/** Represents a result from a contact or group search. */ @AppFunctionSerializable(isDescribedByKDoc = true) data class ContactSearchResult( /** The human-readable name of the contact or group. */ val contactDisplayName: String, /** The type of the found entity, either "INDIVIDUAL" or "GROUP". */ val contactType: String, /** The unique identifier of the endpoint. */ val endpointValue: String, /** The human-readable label/display name of the endpoint. */ val endpointDisplayName: String, ) /** Represents the search results for a specific chat endpoint. */ @AppFunctionSerializable(isDescribedByKDoc = true) data class MessagesSearchResult( val endpointValue: String, val messages: List<Message>, // 중첩도 가능 )

프로퍼티 KDoc도 읽습니다

각 필드 위 주석이 그대로 필드 설명으로 XML에 들어갑니다. 모델이 값을 잘못 채우는 걸 막아줍니다.

지원 타입

  • 기본형: String Int Long Double Float Boolean
  • List<T>, nullable, 기본값
  • Uri, PendingIntent 같은 Android 타입
  • 다른 @AppFunctionSerializable 중첩
PendingIntent 반환 · ChatApp의 makeCallPendingIntent를 돌려줍니다. 에이전트가 “여기서부터는 앱 화면에서 이어가세요”라고 넘길 수 있습니다.
18
Section 02 · Annotations

KSP가 만들어내는 것주석이 실제로 어떤 메타데이터가 되는지 확인해봅시다.

Kotlin 함수 + KDoc
KSP (appfunctions-compiler)
assets/chat_app_function_service.xml
AppFunctionManager 등록
shared/build/generated/ksp/debug/resources/assets/chat_app_function_service.xml <appfunctions> <appfunction> <id>com.example.chatapp.appfunctions.BaseChatAppFunctionService#searchContacts</id> <enabledByDefault>true</enabledByDefault> <description>Search for message recipients or chat groups by name or email. Required workflow: Call this before "send" or "makeCall" to obtain a valid endpointValue.</description> ← KDoc 본문 <parameters> <dataTypeMetadata><isNullable>false</isNullable><type>8</type></dataTypeMetadata> <name>query</name> <isRequired>true</isRequired> <description>Search string for contact name, email, or group name.</description> ← @param </parameters> <parameters> <dataTypeMetadata> <enumValues>INDIVIDUAL</enumValues><enumValues>GROUP</enumValues><enumValues>ANY</enumValues> ← @AppFunctionStringValueConstraint </dataTypeMetadata> <name>contactType</name> </parameters> <response> ... <dataTypeReference>com.example.chatapp.appfunctions.ContactSearchResult</dataTypeReference> ... </response> </appfunction> </appfunctions> // ChatApp 기준 총 374줄 · 5개 함수
19
Section 02 · Add to your app

내 프로젝트에 추가하기 ① 빌드 설정

전제 조건

compileSdk
36 이상
디바이스
Android 16 (API 36) 이상
KSP
Kotlin Symbol Processing 플러그인 필요
가장 흔한 실수
API 35 에뮬레이터에서 실행하면 앱은 설치되지만 함수가 아예 노출되지 않습니다. 오류도 나지 않아서 찾기 어렵습니다.

버전

세션 기준 1.0.0-alpha10 — alpha 단계라 API가 바뀔 수 있습니다. 샘플과 버전을 맞춰주세요.

// build.gradle.kts plugins { id("com.google.devtools.ksp") } android { compileSdk = 36 // 36 이상 필수 } dependencies { implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10") // Kotlin 소스를 쓴다면 KSP로 컴파일러를 추가 ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10") }

멀티 모듈이라면

함수를 여러 모듈에 나눠 두었다면, 앱 모듈에서 이를 합쳐주는 aggregate 단계가 필요합니다. ChatApp 샘플은 appfunctions.aggregateAppFunctions 컨벤션 플러그인으로 처리합니다.

20
Section 02 · Add to your app

내 프로젝트에 추가하기 ② 서비스 선언KSP가 생성한 Service를 Manifest에 등록해야 시스템이 발견합니다.

<!-- AndroidManifest.xml --> <service android:name="com.example.chatapp.appfunctions.ChatAppFunctionService" android:permission="android.permission.BIND_APP_FUNCTION_SERVICE" android:exported="true" tools:targetApi="36"> <property android:name="android.app.appfunctions.schema" android:value="app_functions_schema.xsd" /> <property android:name="android.app.appfunctions.v2" android:value="chat_app_function_service.xml" /> ← 생성된 XML <intent-filter> <action android:name="android.app.appfunctions.AppFunctionService" /> </intent-filter> </service>

exported = true 인데 안전한가요?

네. BIND_APP_FUNCTION_SERVICE 권한을 가진 시스템만 바인딩할 수 있습니다. 일반 앱은 접근할 수 없습니다.

android:name

@AppFunctionServiceEntryPoint(serviceName = …)에 적은 이름으로 클래스가 생성됩니다. 그 이름을 그대로 씁니다.

appfunctions.v2

appFunctionXmlFileName에 적은 파일명 + .xml. 두 값이 어긋나면 함수가 노출되지 않습니다.

21
Section 02 · Implementation tips

구현할 때 꼭 기억할 두 가지

TIP #1

suspend 함수를 쓸 수 있습니다

App Function은 기본적으로 UI 스레드에서 실행됩니다. 네트워크·DB 작업이 있다면 반드시 suspend로 선언하고 적절한 디스패처로 전환하세요.

@AppFunction(isDescribedByKDoc = true) suspend fun searchMessages(query: String): List<MessagesSearchResult> = withContext(Dispatchers.IO) { repository.search(query) // 무거운 작업은 여기서 }
주의 · 오래 걸리는 작업은 타임아웃될 수 있습니다. 즉시 PendingIntent를 돌려주고 나머지는 앱 화면에서 이어가는 패턴도 고려하세요.
TIP #2

노출 여부를 런타임에 제어할 수 있습니다

로그인하지 않은 사용자, 무료 플랜, 아직 준비 중인 기능 — 상황에 따라 특정 함수만 숨길 수 있습니다.

val manager = AppFunctionManager.getInstance(context) manager?.setAppFunctionEnabled( functionId = "…BaseChatAppFunctionService#makeCall", newEnabledState = AppFunctionManager.APP_FUNCTION_STATE_DISABLED, ) // STATE_DEFAULT(0) — XML의 enabledByDefault 값을 따름 // STATE_ENABLED(1) / STATE_DISABLED(2) — 명시적 재정의 val enabled = manager?.isAppFunctionEnabled(functionId)
기본값은 @AppFunction 선언 시점에 XML의 <enabledByDefault>로 굳고, 이 API는 그 위에 사용자/앱 상태별 재정의를 얹습니다.
22
Section 02 · Verify

통합 확인 adb shell cmd app_function앱을 설치한 뒤, 시스템이 내 함수를 실제로 부를 수 있는지 확인합니다.

adb
# 메시지 전송 함수를 시스템 에이전트처럼 직접 실행 $ adb shell "cmd app_function execute-app-function \ --package com.example.chatapp \ --function 'com.example.chatapp.appfunctions.\ BaseChatAppFunctionService#sendMessage' \ --parameters '{\"endpointValue\": \"1\", \"messageBody\": \"Hello Alice!\"}'" # 연락처 검색 $ adb shell "cmd app_function execute-app-function \ --package com.example.chatapp \ --function '…#searchContacts' \ --parameters '{\"query\": \"Alice\", \"contactType\": \"INDIVIDUAL\"}'" # 에이전트 허용목록 캐시 비우기 (함수가 안 보일 때) $ adb shell cmd app_function purge-allowlist-cache

이걸로 확인되는 것

  • 서비스가 Manifest에 제대로 등록됐는지
  • 파라미터 직렬화가 맞는지
  • 함수 본문이 기대대로 동작하는지

이걸로 확인되지 않는

이 명령은 내가 이미 스펙을 알고 파라미터를 직접 채워 넣는 방식입니다.

우리가 진짜 원하는 건 Gemini 같은 에이전트가 내 함수를 스스로 발견해서, 사용자의 한 문장으로 여러 앱을 넘나드는 워크플로를 수행하는 것입니다.

그래서 다음 단계 · 공식 agent 앱으로 “진짜 에이전트가 부르는” 경로를 확인합니다.
23
SECTION 03

App Functions 실습

이제 직접 만들어봅니다. 동작하지 않아도 괜찮습니다 —
무엇을 에이전트에게 열어줄지 설계해보는 게 오늘의 목표입니다.
24
Section 03 · Hands-on

실습 순서

STEP 1 · 따라하기

공식 샘플로
호출 체험하기

Agent 앱으로 ChatApp의 sendMessage App Function을 호출해봅니다.

github.com/android/appfunctions
  • ChatApp — 함수를 제공하는 앱
  • agent — 함수를 호출하는 앱
  • 두 프로젝트를 각각 열어 빌드
STEP 2 · 만들기

본인 프로젝트에
자유롭게 구현

내 앱에서 “사용자가 한 문장으로 시킬 만한 행동” 하나를 골라 App Function으로 열어봅니다.

팁! Android 공식 App Functions skill이 존재합니다. AI 도구에 물려두면 보일러플레이트를 대신 써줍니다.
STEP 3 · 나누기

희망자 발표
& 시상 🏆

동작하지 않아도 됩니다. 어떤 App Function을 설계했는지 보여주기만 해도 충분합니다.

발표자 중 추첨을 통해
《Jetpack Compose 메커니즘》 신간 도서를 드립니다.
25
Section 03 · Step 1

공식 샘플 실행하기ChatApp(제공) ↔ Testing Agent(호출) 두 앱을 함께 띄웁니다.

hands-on
# 1. 공식 샘플 저장소 클론 $ git clone https://github.com/android/appfunctions.git $ cd appfunctions # 2. 에뮬레이터 실행 (섹션 1에서 만든 것) $ android emulator start medium_phone # 3. ChatApp 설치 — Android Studio로 열어 Run 'app' $ cd ChatApp # 4. Testing Agent를 권한과 함께 실행 $ cd ../agent $ ./run_privileged.sh --build # Gemini로 자연어 테스트까지 하려면 $ ./run_privileged.sh --build --flavor retail --api-key $KEY

Agent 앱이 하는 일

플랫폼에 등록된 App Function 목록을 조회하고, 수동 입력 또는 LLM 에이전트 두 방식으로 실행해봅니다. 통합 문제 진단(troubleshoot) 기능도 있습니다.

왜 privileged 실행이 필요한가요?

EXECUTE_APP_FUNCTIONS는 일반 앱이 받을 수 없는 권한이라, 스크립트가 shell 신원으로 권한을 부여해 실행합니다.

체크리스트 · 함수가 목록에 안 보이면 ① API 36+ 인지 ② Manifest의 XML 파일명이 맞는지 ③ purge-allowlist-cache 순으로 확인하세요.
26
Section 03 · Step 2

내 앱에 하나 열어보기완성이 목적이 아닙니다. “무엇을 열어줄지” 고르는 게 절반입니다.

좋은 App Function 고르는 기준

  • 사용자가 한 문장으로 표현할 수 있는 행동인가
  • 파라미터가 말로 설명 가능한 값인가 (내부 ID만 요구하지 않는가)
  • 결과를 문장으로 돌려줄 수 있는가
  • 먼저 검색 함수를 만들었는가 — searchContacts처럼, ID를 얻는 함수가 대개 먼저 필요합니다
1
의존성 + compileSdk 36KSP 플러그인 함께 추가
2
AppFunctionService 상속 abstract 클래스@AppFunctionServiceEntryPoint 선언
3
함수 1개 + KDoc 작성@AppFunction(isDescribedByKDoc = true)
4
Manifest에 서비스 등록생성된 XML 파일명 확인
5
Agent 앱 또는 adb로 호출 확인
TIP · AI 활용

Android 공식 App Functions Skill

Android 팀이 배포하는 스킬입니다. AI 코딩 도구에 연결하면 어노테이션·Manifest·KDoc 보일러플레이트를 규칙에 맞게 작성해줍니다.

skills
# Android CLI로 스킬 환경 초기화 $ android init # 사용 가능한 스킬 확인 $ android skills # 이후 AI 도구에게 이렇게 시켜보세요 # "내 TodoRepository.addTodo 를 # App Function 으로 노출해줘"
27
Section 03 · Step 3

발표 & 시상

“동작 안 해도 됩니다.
어떤 App Function을 설계했는지만 보여주세요.”

무엇을 열었나요

어떤 기능을 에이전트에게 공개하기로 했는지

어떻게 설명했나요

KDoc을 어떻게 썼는지 — 가장 재미있는 부분입니다

무엇이 막혔나요

안 된 것도 공유해주세요. 다 같이 봅니다

🏆
발표자 중 추첨을 통해 《Jetpack Compose 메커니즘》 신간 도서를 드립니다.
28
Thank you

감사합니다
이제 여러분의 앱을
에이전트에게 열어주세요.

류기민 · GDG Korea Android Organizer · 2026 I/O Extended
29
Appendix

androidx.appfunctions 주요 Public API ①Annotation & Class

ANNOTATION
@AppFunctionServiceEntryPoint — 서비스 진입점 선언 (CLASS)
@AppFunction — 함수 공개 (FUNCTION) · isDescribedByKDoc
@AppFunctionSerializable — 커스텀 데이터 타입 (CLASS)
@AppFunctionSchemaDefinition — 표준 스키마 정의 (CLASS)
@AppFunctionSignature — 함수 시그니처 명세 (CLASS)
@AppFunctionInstruction — 모델용 추가 지시문
@AppFunctionStringValueConstraint — 문자열 허용값(enum) 제약
@AppFunctionIntValueConstraint — 정수 허용값 제약
CLASS · INTERFACE
AppFunctionService — 함수를 제공하는 서비스 base (API 36+)
AppFunctionManager — 조회 · 실행 · 상태 제어 진입점
AppFunctionContext — 호출자 패키지/신원 정보
AppFunctionData — 타입 안전 파라미터 컨테이너 (Builder)
AppFunctionSearchSpec — 함수 검색 조건
ExecuteAppFunctionRequest / Response (Success · Error)
AppFunctionState · ObserveAppFunctionsEvent — 상태 변화 관찰
AppFunctionConfiguration (Builder · Provider) — 앱 전역 설정
AppFunctionUriGrant · AppFunctionTextResource — 내장 직렬화 타입
metadata.* — AppFunctionMetadata, ParameterMetadata,
  ResponseMetadata, DataTypeMetadata 계층
30
Appendix

androidx.appfunctions 주요 Public API ②Method & Exception

METHOD · AppFunctionManager
getInstance(context) : AppFunctionManager?
searchAppFunctions(searchSpec) : List<AppFunctionMetadata>
  requires EXECUTE / DISCOVER_APP_FUNCTIONS
executeAppFunction(request) : ExecuteAppFunctionResponse
  requires EXECUTE_APP_FUNCTIONS
observeAppFunctions() : Flow<ObserveAppFunctionsEvent>
isAppFunctionEnabled(functionId) : Boolean
isAppFunctionEnabled(packageName, functionId) : Boolean
setAppFunctionEnabled(functionId, newEnabledState)
handleAppFunction(request) / handleAppFunctions(requests)
STATE_DEFAULT(0) · STATE_ENABLED(1) · STATE_DISABLED(2)
EXCEPTION
AppFunctionException (root)
AppFunctionRequestException — 호출이 잘못됨
│  ├ InvalidArgument · ElementNotFound · ElementAlreadyExists
│  └ Denied · Disabled · FunctionNotFound · LimitExceeded
AppFunctionAppException — 앱 쪽에서 처리 실패
│  └ AppUnknown · NotSupported · PermissionRequired
AppFunctionSystemException — 플랫폼 오류
│  └ Cancelled · SystemUnknown
AppFunctionUnknownException
설계 포인트 · 예외 종류가 곧 에이전트의 복구 전략입니다. Request 계열이면 “다시 물어보기”, App 계열이면 “나중에 재시도”로 이어집니다.
31
References

참고자료

32
← → 이동 · F 전체화면