這是一份提交給Google I/O寫作挑戰
每個人都離開Google I/O 2026後都在談論Compose First、Gemini整合以及Android 17的自適應佈局推動。公道——這些確實很重要。但最讓我無法停止思考的宣布卻幾乎沒有得到頭條新聞:那就是AppFunctions API。
我已經開發安卓應用程式十年了。我見過 API 的來來去去。這個感覺不一樣 — 不僅僅是因為它今天做了什麼,而是因為它所傳達的關於應用程式正在變成的訊號。
AppFunctions 其實是什麼
在核心上,AppFunctions 讓你的應用程式可以直接向 AI 助手暴露獨立、命名的動作。想想:
"Create expense report""Book appointment""Send daily update"
不是由使用者啟動您的應用程式,導航至螢幕,點擊流程 — 類似於 Gemini 的代理程式可以直接代為執行這些動作,而應用程式從未來到前景.
這是一個大致的概念,關於如何註冊 AppFunction 的.
@AppFunction
suspend fun createExpenseReport(
context: AppFunctionContext,
params: CreateExpenseParams
): ExpenseResult {
// your business logic here
return ExpenseResult(id = repo.create(params))
}
你定義函數。你定義參數。AI 決定何時以及如何呼叫它.
如何整合 AppFunctions:一個逐步範例
讓我們讓這個具體化。這裡是從零到可呼叫 Gemini 的完整整合.
步驟 1 — 添加相依性
// build.gradle.kts (module)
dependencies {
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha01")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha01")
}
第 2 步 — 定義您的輸入/輸出類型
AppFunctions 使用類型化的 Kotlin 資料類別。保持它們可序列化 — AI 代理需要從自然語言中構建它們.
@Serializable
data class CreateExpenseParams(
val title: String,
val amount: Double,
val category: String? = null // nullable — agent may not always provide this
)
@Serializable
data class ExpenseResult(
val id: String,
val success: Boolean,
val message: String
)
第 3 步 — 標註您的函數
使用@AppFunction 在一個暫停函數上。註解處理器在編譯時生成註冊模式 — 無需模板化的 XML。
class ExpenseAppFunctions(
private val repo: ExpenseRepository
) {
@AppFunction
suspend fun createExpenseReport(
context: AppFunctionContext,
params: CreateExpenseParams
): ExpenseResult {
val id = repo.create(params)
return ExpenseResult(
id = id,
success = true,
message = "Expense created: ${params.title}"
)
}
}
第 4 步 — 讓它對代理安全 (每個人都跳過的步驟)
代理可以在網絡失敗時重試。沒有幂等性,一個用戶的語音指令可能會創建重複的記錄。
@AppFunction
suspend fun createExpenseReport(
context: AppFunctionContext,
params: CreateExpenseParams
): ExpenseResult {
// Idempotency: return existing record if already created
val existing = repo.findByTitle(params.title)
if (existing != null) {
return ExpenseResult(
id = existing.id,
success = true,
message = "Already exists"
)
}
return ExpenseResult(id = repo.create(params), success = true, message = "Created")
}
注意: 若要進行更精細的控制,請使用AppFunctionContext.callerPackageName來為每個代理設定 idempotency keys.
第 5 步 — 在 AndroidManifest 中註冊
若沒有這一步,Gemini 就無法在執行時發現您的函數.
<!-- AndroidManifest.xml -->
<service
android:name=".ExpenseAppFunctions"
android:exported="true"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
<intent-filter>
<action android:name="androidx.appfunctions.AppFunctionService"/>
</intent-filter>
</service>
第 6 步 — Gemini 如何處理它
註冊後,當使用者輸入 "將 45 英鎊的計程車費用列為支出" 時,這項功能便在背景執行 — 不需介面、不需導航、不需點擊:
// Gemini constructs and executes this on-device
val result = appFunctionManager.executeAppFunction(
targetPackage = "com.yourapp",
functionId = "createExpenseReport",
params = CreateExpenseParams(
title = "Taxi",
amount = 45.0,
category = "Transport"
)
)
// result.message → "Expense created: Taxi"
那是完整的流程。從依賴到可由代理調用的行動有六個步驟——而實際上的大部分工作是在步驟 3 和 4,不是在 1 和
為何這比聽起來更重大
讓我直接說明這需要的思維轉變.
在過去十年中,Android 應用程式一直是 被動工具。用戶打開它們,與它們互動,關閉它們。應用程式等待。用戶回來了。這就是模式.
AppFunctions徹底打破這個模式.
注意: 你的應用程式不再僅僅是介面。它是一組可以被外部智能調用的功能——一個為用戶代為操作,跨越多個應用程式,而無需明確導航的智能。
這不是一個增量更新。這是一種不同的典範.
想想這對於我們永遠以來所持有的用戶體驗假設意味著什麼:
- 設計用來引導用戶的啟用流程?如果代理跳過它們,那麼相對來說就不那麼重要了.
- 導航架構?它仍然重要,但它不再是最終的唯一入口。
- 狀態管理?現在需要考慮在您的UI生命週期之外觸發的動作.
誰都不談的部分:這對開發者提出了什麼要求
我認為大多數報導都在這裡失分了.
所有人都將AppFunctions框架為一個要"添加"到您應用程式中的功能。加一些註解,暴露幾個動作,完畢。但這種框架忽略了架構上的影響。
您的應用程式邏輯必須符合代理程式的要求.
這表示:
-
單次執行的效果更為重要.如果發生網路暫時中斷,代理程式可能會呼叫
createExpenseReport兩次。您有處理這種情況嗎? - 錯誤訊息必須能被機器讀取.如果您的函式失敗,代理程式需要結構化的資訊來恢復或回報——不是簡單的提示訊息。
- 權限與認證需要明確. 代表使用者行動的代理仍然需要尊重範圍。你不能僅僅假設環境中的使用者認證.
這相對於「增加一個功能」更像是「重新思考你的服務層。」
我的誠實看法:令人興奮,但我們尚未準備好
我想要誠實地說——廣泛來說,Android開發者社群並未準備好這次轉變。
我們仍在遷移至 Compose。許多團隊仍在解開 MVVM 的麻煩。而我們現在卻被要求思考我們的應用程式作為代理相容的 API?
注意: 「Google 宣布 AppFunctions」與「大多數生產應用程式提供良好設計的 AppFunctions」之間的差距將以年計,而非月計。而這是可以接受的 — 但前提是我們開始思考架構目前.
將會走在這條曲線前面開發者,是那些現在就開始將他們的業務邏輯視為一個一等API的人 — 清潔的使用案例、清晰的輸入/輸出合約、正確的錯誤處理。不是因為還沒有代理調用它,而是因為良好的架構會為未來帶來回報.
我明天要告訴我的團隊
如果我明天去 standup 時,從 I/O 2026 帶走的一點收穫是這樣的:
開始審計你的應用場景,確保它們能夠應對 AI 前景。
不是為了在下一個 sprint 發布 AppFunctions — 而是問:如果一個 AI 需要無人鍵盤的情況下觸發這個動作,我們的程式碼能夠應付得了嗎?錯誤狀態會合理嗎?認證模型會有效嗎?
僅僅是這個問題就會浮現出許多值得處理的架構債務,無論是AppFunctions。
論隱私 — 因為總有人要說出來
我對AppFunctions充滿興奮。但如果我不指出這個問題開啟的隱私表面,我就對你做了不公。
當代理程式無需使用者明確點擊即可啟動您的應用程式時,同意模式就變得模糊不清。有幾件事值得深入思考:
- 代理人默默地行動。用戶曾經說過「添加計程車費用」一次 — 他們沒有檢查哪些數據離開了他們的設備或哪些系統被觸及。你的功能只需要做它說的確切的事情。
- 敏感資料需要明確的範圍界定。 若您的AppFunction觸及財務、健康或訊息,請將輸出視為公開API回應。回傳所需的最少數據,不多不少。
-
驗證callerPackageName。
AppFunctionContext可提供呼叫代理的套件。對於敏感操作,請白名單信任的呼叫者 — 不要假設所有代理都應得到同等信任。 - 記錄每一次呼叫。 使用者應該能夠看到他們的代理為他們做了什麼。從第一天開始就建立那條審計軌跡.
好消息是:Android的BIND_APP_FUNCTION_SERVICE權限意味著只有系統認可的代理如Gemini才能預設調用你的功能。平台的安全措施是合理的。但隨著代理生態系統的成長,攻擊面也在擴大.
注意: 每個@AppFunction 像一個公開的 API 端點 — 驗證輸入,範圍輸出,記錄呼叫。這個註解很簡單;它所承擔的責任卻不簡單.
你實際上暴露的是什麼
這個讓開發者感到驚訝:你並沒有暴露源代碼,但你 實際上暴露的是你的應用程式的功能表面 — 這同樣伴隨著風險。
AppFunctions 會向外界揭露什麼:
- 您的函數名稱如
createExpenseReport或deleteUserAccount作為可發現的公開模式 — 任何擁有正確工具的人都可以列舉您的應用程式註冊了什麼 - 您的參數類型揭露了您的內部數據模型。
CreateExpenseParams的形狀是可見的,不僅僅是它的名稱 - 您的商業邏輯邊界 — 如果
archiveAllRecords作為一個 AppFunction 存在,你甚至在發布它的 UI 之前就宣傳了這項功能
需要考慮的真正攻擊場景:
- 一個探測性應用程式列舉你的註冊函數,以逆向工程你的數據模型
- 一個沒有內部驗證的 admin 級別函數被不受信任的代理調用 — 僅僅依靠宣告權限是不夠的
- 競爭對手在您發布前會閱讀您的函數名稱,並且清楚知道即將推出什麼
該怎麼辦:
- 絕不註冊一個沒有明確設計為代理可調用的函數 — 您的儲存庫中不是每個東西都值得一個
@AppFunction - 在內部添加權限檢查 每個函數,無論是否具備顯示權限
- 對於敏感操作使用通用名稱,以避免函數名本身洩露意圖
- 在每次發布前審核
@AppFunction注釋,就像審核一個公開的 REST API 標準一樣
Google I/O 2026 有許多令人興奮的宣布。Compose First 已經過期,非常歡迎。AI 開發工具非常實用。Android Automotive 是一條值得探索的真實職業道路.
但 AppFunctions 才是改變我們所說「Android 應用程式」意味的關鍵。它是潛力股。五年後,我認為我們會回顧 I/O 2026 作為轉變開始的時刻。
如果您今天開始讓代理調用您的功能,您將如何處理當前服務層中的幂等性?在評論中分享您的架構「噩夢」場景——我非常想聽聽它們。












