Product Manager di Google
Android berkembang menjadi sistem kecerdasan tempat agen seperti Gemini mengorkestrasi tugas di berbagai aplikasi. Untuk membuka pengalaman pengguna baru dan berpartisipasi dalam alur kerja agen ini, aplikasi Anda harus mengekspos kemampuan intinya.
Sesi ini adalah panduan praktis untuk mengintegrasikan aplikasi Anda dengan lapisan kecerdasan Android. Kami akan menyurvei jalur integrasi utama, termasuk MCP Android di perangkat, MCP sisi server, dan Otomatisasi Tugas, serta memberikan framework keputusan untuk memilih jalur yang tepat bagi kasus penggunaan Anda. Kami juga akan membahas pengalaman proaktif dengan Konteks Personal.
Anda akan mempelajari cara:
AppFunctionDeclaration, berikan deskripsi KDoc, dan hindari mengandalkan nilai default Kotlin.Transkrip
Oke.
Halo semuanya.
Saya Dan Galpin dari Developer Relations.
Dan saya Sarah Shamsie, product manager yang mengerjakan framework kecerdasan developer Android.
Kami sangat senang menyambut Anda di sesi hari ini.
Kita menyaksikan perubahan paradigma dalam cara pengguna berinteraksi dengan perangkat mereka.
Di Era Web, pengguna biasanya memulai perjalanan mereka dengan membuka browser web, lalu menggunakan fungsi penelusuran.
Kemudian di Era Seluler, kita melihat perubahan perilaku pengguna beralih ke penggunaan aplikasi native yang lebih khusus untuk menyelesaikan tindakan satu kali.
Berikut contohnya.
Pesan masuk dari teman yang mengundang saya untuk makan malam.
Untuk meresponsnya, saya perlu memeriksa kalender untuk melihat apakah saya ada waktu luang, menambahkan acara ke kalender, memesan transportasi jika saya perlu cara untuk pergi ke sana, lalu mengirim pesan balasan kepada teman saya bahwa saya bisa hadir.
Sebelumnya, hal ini memerlukan sesuatu yang disebut berpindah-pindah aplikasi, yang pada dasarnya mengharuskan saya beralih di antara berbagai aplikasi di perangkat untuk benar-benar menyelesaikan tindakan dan memenuhi perjalanan saya.
Namun, sekarang kita berada di Era Kecerdasan.
Di Era Kecerdasan, pengguna memulai perjalanan mereka dengan menyatakan niat dan sasaran tingkat tinggi, bukan tindakan spesifik yang perlu mereka lakukan.
Ada perubahan mendasar dalam cara mereka memandang perangkat, yaitu sebagai sesuatu yang secara proaktif mengantisipasi kebutuhan mereka dan membantu mereka mencapai tujuan, bukan sekadar sesuatu yang memungkinkan tindakan.
Kami juga melihat bahwa mereka mengharapkan framework kecerdasan ini berfungsi tidak hanya di perangkat seluler, tetapi juga di berbagai ekosistem faktor bentuk, termasuk smartwatch, mobil, TV, laptop, serta XR dan kacamata pintar.
Di Era Kecerdasan, satu perintah lintas aplikasi dapat memfasilitasi contoh undangan makan malam dari sebelumnya.
Hanya dengan satu perintah, agen di perangkat saya dapat membantu mengaktifkan semua tindakan yang saya perlukan, mulai dari memeriksa kalender, menambahkan acara, memesan tumpangan, dan mengirim pesan, semuanya sekaligus.
Hari ini, kami akan membagikan lebih banyak informasi tentang rencana Android di bidang ini dan cara Anda sebagai developer dapat membangun pengalaman terbaik.
Dan.
Terima kasih, Sarah.
Jadi, Android terus menambahkan kecerdasan pada intinya, dan hal itu menawarkan cara baru bagi aplikasi Anda untuk, Anda tahu, bersiap menghadapi Era Kecerdasan ini.
Dan pilar pertama dari dua pilar kecerdasan developer ini adalah kecerdasan dalam aplikasi, dan itu jelas tentang membuat aplikasi Anda lebih cerdas, menghadirkan kecerdasan ke dalam aplikasi Anda, dan Anda dapat menggunakan model di perangkat seperti Gemini Nano 4 melalui ML Kit untuk meringkas rangkaian pesan, dan menulis ulang balasan, dan memahami gambar, atau menggunakan bahasa alami untuk memanggil fungsi aplikasi Anda.
Anda bahkan dapat menggunakan model Gemini di cloud langsung di aplikasi Anda melalui Firebase AI Logic.
Namun, fokus kita hari ini adalah pada pilar kedua, yaitu memastikan kemampuan aplikasi Anda tersedia dalam sistem kecerdasan.
Lalu, apa itu sistem kecerdasan?
Jadi, selain, Anda tahu, berbicara dan menghosting pengalaman yang dipicu yang dibahas Sarah sebelumnya, Gemini adalah sistem yang memungkinkan integrasi mendalam untuk meningkatkan pengalaman di seluruh ekosistem aplikasi.
Jadi, perangkat yang dirancang untuk kecerdasan Gemini, misalnya, memanfaatkan titik integrasi ini untuk memberikan hal-hal seperti saran, ringkasan notifikasi, input suara yang ditingkatkan, dan pengisian formulir otomatis.
Sekarang, saran adalah bagian dari fitur yang disebut bantuan proaktif.
Sejauh ini, kita telah membahas bantuan yang diminta.
Kita semua sudah terbiasa dengan hal itu, yaitu pengguna memberi tahu agen untuk menyelesaikan satu atau beberapa tugas atas nama mereka.
Namun, bantuan proaktif sebenarnya adalah mengantisipasi tindakan berikutnya yang mungkin dilakukan pengguna.
Jadi, kembali ke contoh makan malam kita, agen mungkin secara proaktif memberikan saran kepada pengguna sebagai respons terhadap pesan yang dapat menambahkan undangan ke kalender mereka atau memesan transportasi online.
Namun, bagaimana cara aplikasi meminta pengguna?
Bantuan proaktif dapat menampilkan tindakan di tempat-tempat seperti layar kunci, notifikasi, keyboard virtual, dan bahkan dalam aplikasi yang dipilih.
Jadi, bagaimana cara membuat aplikasi Anda benar-benar muncul di chip proaktif ini untuk mengarahkan traffic ke aplikasi Anda?
Anda sudah melakukan hal-hal seperti mem-build dengan versi SDK terbaru dan mem-build dengan alat paling modern, tetapi hal terbaik yang dapat Anda lakukan sekarang adalah memastikan bahwa aplikasi Anda juga siap untuk bantuan yang diminta, yang akan dibahas Sarah selanjutnya.
Terima kasih, Dan.
Jadi, bantuan yang diminta melibatkan pengguna yang langsung membuka agen dan memintanya untuk memulai dan mengatur tugas, bahkan di berbagai aplikasi.
Hal ini dapat mengotomatiskan alur kerja yang sebelumnya dilakukan secara manual dan memakan waktu.
Untuk sistem kecerdasan kami di Android, kami terutama berfokus pada agen sistem seperti Gemini yang dapat memanfaatkan aplikasi di seluruh perangkat pengguna.
Ada beberapa cara aplikasi Anda dapat muncul dalam pengalaman ini dan berinteraksi dengan agen sistem.
Ada solusi Task Automation berbasis platform, dan ada integrasi API langsung ke agen sistem.
Jika memungkinkan, sebaiknya buat integrasi API langsung.
Hal ini dikontrol oleh developer, sehingga Anda dapat memutuskan dan mengontrol tampilan aplikasi dan merek Anda.
Aplikasi ini juga lebih cepat dan lebih andal, sehingga menawarkan pengalaman yang lebih premium kepada pengguna Anda.
Ada dua jalur API yang dapat Anda pertimbangkan saat ingin berintegrasi dengan agen.
Model Context Protocol, atau disingkat MCP, adalah praktik terbaik industri untuk pemanggilan fungsi sisi server.
Namun, untuk panggilan alat di perangkat, kami secara resmi mengumumkan Android MCP, framework panggilan fungsi terpadu di Android.
Android MCP didukung oleh library Jetpack AppFunctions kami, yang sangat selaras, baik secara teknis maupun konseptual, dengan MCP, sekaligus mendukung implementasi Kotlin yang familier dan idiomatis yang sudah dikenal oleh banyak developer Android.
Hal ini membuat penerapan API relatif intuitif dan mudah digunakan, dan Anda dapat memanfaatkan pembelajaran apa pun yang mungkin Anda miliki dari integrasi MCP sisi server yang sudah ada.
Banyak primitif MCP yang dipetakan satu per satu ke konstruksi AppFunctions Jetpack.
Mari kita pelajari beberapa contoh untuk mengilustrasikan hal ini.
Endpoint alat MCP dipetakan dengan baik ke fungsi Kotlin yang dianotasi dengan AppFunctionDeclaration.
Model pydantic Anda dipetakan ke class data Kotlin yang dianotasi dengan AppFunctionSerializable.
Namun, alih-alih transport I/O standar dan/atau HTTP yang dapat di-streaming, MCP Android berjalan di IPC Android Binder yang dioptimalkan.
Hari ini, dengan senang hati kami sampaikan bahwa library Jetpack, AppFunctions, kini dalam versi beta.
Oke, jadi Anda mungkin bertanya-tanya kapan sebaiknya menggunakan satu jalur API, dan kapan sebaiknya menggunakan Android MCP, khususnya.
Sebagai prinsip menyeluruh, Android MCP sangat berguna saat Anda ingin memanfaatkan konteks lokal atau di perangkat untuk memenuhi perjalanan pengguna.
Mari kita pelajari beberapa contoh keunggulan Android MCP.
Secara khusus, Anda harus mempertimbangkan Android MCP saat perlu mengakses data lokal yang dienkripsi.
Hal ini sangat penting untuk aplikasi pesan dan komunikasi, yang histori pesannya harus tetap ada di perangkat karena enkripsi end-to-end.
MCP Android juga berguna untuk tindakan yang sepenuhnya di perangkat dan offline, jika agen mendukungnya.
Partner Wear OS kami, Samsung Health, meluncurkan fungsi untuk memulai lari dan memulai latihan, yang diorkestrasi sepenuhnya di perangkat.
Pengguna sering kali ingin jenis tindakan ini dilakukan dengan sangat cepat, keandalan tinggi, latensi rendah, dan terkadang, bahkan di lingkungan dengan konektivitas rendah. Memastikan bahwa seluruh panggilan alat, perutean, dan perutean kueri dapat dilakukan di perangkat akan memastikan hal ini.
MCP Android juga merupakan jalur API yang direkomendasikan jika Anda ingin mengakses sensor perangkat atau berinteraksi langsung dengan hardware perangkat.
Misalnya, banyak aplikasi otomotif ingin memungkinkan pengguna membuka kunci pintu mobil mereka secara instan melalui konektivitas UWB dan sering kali secara handsfree.
Jadi, MCP Android dapat membantu mengatasi hal tersebut.
Terakhir, ini adalah solusi Anda jika tidak ada endpoint cloud yang tersedia untuk aplikasi atau API Anda.
Banyak aplikasi pihak pertama dari produsen perangkat kami yang hanya berjalan di sisi klien, sehingga mereka menggunakan Android MCP untuk alasan ini.
Misalnya, Gemini dapat digunakan untuk menelusuri dan mengambil foto dari aplikasi galeri yang hanya ada di sisi klien, seperti Xiaomi Gallery.
Di sini, saya dapat membuka Gemini, dan bertanya, "Temukan foto bot Android yang saya ambil minggu lalu di Galeri Xiaomi."
Xiaomi telah mengekspos fungsi aplikasi untuk menelusuri foto, yang kemudian dipanggil oleh Gemini, dan foto yang relevan ditampilkan dan muncul langsung di UI aplikasi Gemini, sehingga pengguna tidak perlu keluar.
Kami juga mengandalkan arsitektur yang sama persis untuk tindakan di perangkat di Android OS.
MCP Android mendukung fitur Bantuan Perangkat yang baru saja diluncurkan, yang merupakan integrasi Android OS langsung ke aplikasi Gemini.
Kemampuan ini telah diluncurkan di perangkat Pixel dan Lenovo, dan saat ini sedang diperluas ke ekosistem OEM Android yang lebih luas.
Pengguna dapat menggunakan Bantuan Perangkat dengan membuka Gemini dan menanyakan cara mengoptimalkan setelan, setelan perangkat, dan setelan sistem untuk meningkatkan pengalaman perangkat mereka.
Misalnya, daripada mencari di berbagai menu, pengguna yang buta warna dapat bertanya kepada Gemini, "Saya buta warna.
Bagaimana cara agar saya lebih mudah menggunakan ponsel ini?"
Gemini akan memanggil dan mengaktifkan fungsi aplikasi lokal, serta secara cerdas menganalisis permintaan, dan menyarankan cara aktual yang dapat dilakukan pengguna untuk mengubah setelan sistem mereka, serta cara spesifik yang dapat mereka lakukan untuk mengubah setelan perangkat mereka guna meningkatkan pengalaman.
Kemudian, kontrol dalam aplikasi ditampilkan langsung di UI aplikasi Gemini, sehingga pengguna dapat langsung mengontrolnya.
Misalnya, dalam memenuhi permintaan pengoptimalan untuk buta warna ini, Gemini telah menampilkan tombol koreksi warna ini, sehingga pengguna dapat melakukan perubahan dengan cepat.
Pengalaman MCP Android sudah tersedia di perangkat tertentu, dan kami bekerja sama dengan beberapa partner untuk menghadirkan lebih banyak kasus penggunaan ke ekosistem Android.
Meskipun deklarasi alat MCP Android eksplisit adalah standar emas kami, kami tahu akan ada situasi saat pengguna meminta agen untuk menyelesaikan kasus penggunaan yang kompleks atau kurang umum, dan di sinilah fitur Task Automation berbasis platform kami berperan.
Berdasarkan model ini, agen yang dimuat sebelumnya oleh OEM dapat menggunakan API kontrol komputer Android untuk menyimulasikan peristiwa sentuh di platform aplikasi.
Hal ini tidak memerlukan perubahan kode dari developer, dan mengharuskan agen untuk mengetuk UI seperti yang dilakukan pengguna, dengan menganalisis layar aplikasi.
Untuk memastikan kontrol dan keamanan pengguna, agen harus dijeda pada tindakan yang berpotensi sensitif seperti alur pembayaran dan meminta konfirmasi pengguna untuk melanjutkan.
Mari kita lihat contoh penerapannya.
Di sini, pengguna telah mengambil foto tur kopi dan cokelat, dan ingin meminta Expedia, atau ingin meminta Gemini untuk mencari di Expedia dan melihat apakah mereka dapat menemukan tur serupa, tetapi menyesuaikan jumlah orang menjadi enam peserta.
Tanpa memerlukan integrasi API langsung, Gemini menavigasi UI Expedia di latar belakang dengan cara yang sama seperti yang dilakukan pengguna.
Kemudian, Gemini akan menampilkan opsi relevan yang ditemukan dan mengalihkannya dengan lancar kepada pengguna saat tiba waktunya untuk konfirmasi akhir dan pemesanan.
Penting untuk dipahami bahwa ketiga jalur yang berbeda ini tidak saling terpisah, dan sebenarnya lebih baik jika digunakan bersama.
Pengguna dan developer dapat mengharapkan agen untuk dapat mengoordinasikan alat di seluruh implementasi server dan juga aplikasi Android lokal untuk menawarkan pengalaman yang paling komprehensif dan holistik kepada pengguna.
Oke.
Terima kasih, Sarah.
Kita akan membahas MCP Android lebih dalam.
Dan jika memungkinkan, saya ingin Anda tahu bahwa saya mengambil cuplikan kode MCP dari Jetpacker, yang merupakan aplikasi contoh perjalanan kami, yang sebenarnya dibuat dengan bantuan Antigravity.
Anda telah melihatnya dalam keynote.
Aplikasi ini dilengkapi dengan berbagai fitur cerdas yang keren, termasuk integrasi di perangkat dengan API pengenalan ucapan ML Kit dan AI generatif ML Kit.
Sekarang, MCP Android dan MCP berbasis cloud memiliki struktur yang serupa.
Keduanya memiliki klien dan server.
Dalam dunia MCP, server menyediakan alat, dan klien dapat menemukan serta memanggil alat ini.
Sekarang dengan MCP Android, aplikasi Anda adalah server, dan agen adalah klien.
Platform Android berperan sebagai registry alat.
Sekarang, aplikasi dimulai dengan mendaftarkan layanan untuk menyediakan server, dan kita akan memulai dengan menambahkan dependensi library Jetpack AppFunctions dalam contoh di sini.
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)Kemudian, kita akan menggunakan anotasi KSP untuk membantu membuat AppFunctionService.
Sekarang, layanan ini berarti fungsi aplikasi Anda dapat benar-benar mematuhi siklus proses Android dengan baik.
Kita dapat menggunakan injeksi dependensi dengan Hilt untuk menginjeksikan repositori data yang dapat digunakan oleh AppFunction kita.
@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
serviceName = "JetPackerAppFunctionService",
appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
@Inject internal lateinit var tripDao: TripDao
@Inject internal lateinit var expenseDao: ExpenseDao
...Maaf.
Kembali ke diagram kita.
Setelah menyiapkan layanan untuk server MCP Android, aplikasi akan mendaftarkan alat dalam bentuk AppFunctions, beserta model data terkait yang digunakan oleh alat tersebut.
Sekarang Anda dapat mendaftarkan model data menggunakan anotasi AppFunctionSerializable, dan KDoc di sini cukup keren.
/** Represents an event in a trip itinerary. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TimelineEventSerializable(
/** The event's unique identifier. */
val id: String,
/** The trip ID this event belongs to. */
val tripId: String,
/** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
val type: String,
/** The timestamp of the event. */
val timestamp: Long,
/** The title of the event. */
val title: String,
/** The location of the event. */
val location: String,
/** A description of the event. */
val description: String?,
/** The title of the trip this event belongs to. */
val tripTitle: String? = null,
/** Optional title of a matched itinerary event when added as a reminder. */
val matchedEventTitle: String? = null,
/** Execution status of the tool call (e.g., SUCCESS). */
val status: String? = "SUCCESS",
/** Human-readable completion message instructing the agent not to repeat the call. */
val message: String? = null,
)Deskripsi ini hanya akan menjadi metadata yang digunakan oleh agen untuk memahami cara menggunakan model.
Anda dapat menggunakan anotasi AppFunctionDeclaration untuk mendaftarkan fungsi sebagai alat.
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
fallback
}
events.map { it.toSerializable(tripTitle = trip?.title) }
}
}Dan dengan isDescribedByKDoc yang ditetapkan ke true, KDoc memberikan deskripsi kepada agen untuk memahami fungsi Anda, dan juga membantu manusia memahaminya.
/**
* Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
* trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
* specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
* visitor tips.
*
* @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
...Berikut beberapa praktik terbaik untuk desain parameter.
/**
* @param tripId Optional ID of the trip (e.g., '2026-1').
* Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
// resolveTrip defaults to the active trip if tripId == null
val trip = resolveTrip(tripId = tripId)Pertama-tama, Anda harus berfokus pada subset parameter minimal dengan nama yang jelas dan berbeda.
Buat parameter yang tidak penting menjadi nullable dan selesaikan default-nya dalam isi fungsi.
Ini bukan sekadar praktik terbaik.
Hal ini tidak akan berfungsi, karena nilai default Kotlin akan diabaikan karena library AppFunctions meneruskan null untuk parameter nullable yang dihilangkan.
Saya bahkan tidak akan menunjukkan contoh yang pertama ini, tetapi jangan gunakan parameter umum.
Hal-hal seperti konteks lengkap atau histori percakapan, semuanya buruk.
Hal ini dapat menyebabkan agen meneruskan data dalam jumlah besar dan menyebabkan pemindahan data yang tidak sah, dan ini merupakan praktik yang buruk.
Karena AppFunction dijalankan secara default di UI thread, Anda harus memastikan operasi yang berjalan lama terjadi di latar belakang dengan memilih dispatcher coroutine yang sesuai.
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
...Anda ingin menampilkan pengecualian AppFunction jika diperlukan dengan informasi mendetail agar agen dapat memahami cara menyelesaikan error.
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun send(
message: Message
): Result {
...
throw AppFunctionException(
errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
errorMessage = "To address has invalid syntax!"
)
}Baiklah, sekarang kita memiliki AppFunction, mari kita kembali ke diagram arsitektur di sini.
Aplikasi klien agen mendaftar ke platform untuk memanggil AppFunctions.
Jadi, ini adalah sesuatu seperti Gemini, misalnya.
Untuk melakukannya, mereka harus memiliki izin istimewa, dan agen terdaftar kemudian dapat mencari metadata AppFunction dari Platform Android dan memutuskan apakah mereka harus menjalankan satu atau beberapa AppFunction terdaftar untuk memenuhi permintaan pengguna.
Terkait satu atau beberapa AppFunction, bagaimana agen memutuskan?
Satu hal terakhir yang perlu Anda lakukan adalah menjelaskan kemampuan aplikasi Anda dalam file metadata aplikasi.
<AppFunctionAppMetadata
xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
appfn:description="This app manages trips and travel expenses.
Operational Patterns:
- Use 'getAllTrips' to find the correct 'tripId' before
adding expenses or managing trip details.
Constraints:
- Expenses must be associated with a valid trip ID."
appfn:displayDescription="@string/user_visible_description" />Dan agen memilih alat dengan terlebih dahulu membaca deskripsi ini dalam AppFunctionAppMetadata Anda, lalu membaca deskripsi dengan setiap AppFunction.
Jadi, Anda perlu memfokuskan bagian awal deskripsi dengan penjelasan singkat tentang domain utama aplikasi atau alat Anda.
Anda juga dapat menambahkan pola operasional untuk membantu agen memahami alur logis operasi, dan ini berguna untuk menguraikan prasyarat atau mengarahkan agen ke jalur optimal untuk menyelesaikan tugas.
Anda juga dapat menambahkan bagian tentang batasan yang menentukan batas logis jenis data input atau memperkuat pola operasional.
Jadi, itulah dasar-dasar MCP Android, tetapi ada beberapa hal lain yang dapat Anda lakukan.
Satu hal yang dapat Anda lakukan, yang cukup keren, adalah Anda dapat mengaktifkan dan menonaktifkan AppFunctions secara dinamis.
viewModelScope.launch {
val newState =
if (enabled) {
AppFunctionManager.APP_FUNCTION_STATE_ENABLED
} else {
AppFunctionManager.APP_FUNCTION_STATE_DISABLED
}
appFunctionManager?.setAppFunctionEnabled(id, newState)
}Hal ini penting, misalnya, jika Anda ingin AppFunction hanya tersedia jika pengguna login.
Hal lain yang dapat Anda lakukan adalah membuat AppFunction yang dicakupkan ke layar atau aktivitas tertentu di aplikasi Anda.
@AppFunctionSignature(
scope = AppFunctionMetadata.SCOPE_ACTIVITY,
appFunctionXmlFileName = "cart_functions"
)
fun interface AddCurrentItemToCart {
/** Adds the item currently shown on screen to the cart. Returns the new cart size. */
suspend fun addToCart(quantity: Int): Int
}Jadi, Anda mendeklarasikan AppFunctionSignature, bukan AppFunction.
Kemudian, Anda dapat mengimplementasikan pengendali untuk AppFunction di UI Anda.
val addToCartAppFunctionAdapter =
appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()
coroutineScope.launch {
appFunctionManager.handleAppFunction(
addToCartAppFunctionAdapter.adapt { quantity ->
val newCartSize = cart.addItem(quantity)
newCartSize
}
)
}Dan bagian menariknya adalah siklus proses pengendali secara otomatis terkait dengan siklus proses cakupan coroutine yang Anda gunakan.
Sekarang Anda perlu meng-cache adaptor permintaan AppFunctions ini, maaf, maksudnya adaptor permintaan AppFunctions handle, karena library menggunakan refleksi di balik layar untuk membuat instance-nya.
Ada juga fungsi untuk membuat AppFunction tercakup yang pendaftarannya tidak terkait langsung dengan siklus proses Android.
Sekarang, hal terakhir adalah kita memiliki hal yang disebut tingkat akses.
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
...Anda juga dapat menggunakan tingkat akses mandiri, sistem, atau tepercaya. Tingkat akses ini menunjukkan fungsi mana yang hanya dapat digunakan untuk agen dalam aplikasi Anda sendiri, seperti yang dibuat dengan ML Kit GenAI, fungsi mana yang hanya tersedia untuk sistem, dan fungsi mana yang tersedia untuk agen sistem tepercaya.
Untuk membantu Anda membangun agen dalam aplikasi Anda sendiri, kami telah mengumumkan bahwa ML Kit mendukung AppFunctions secara native sebagai bagian dari framework panggilan alat terpadunya.
Anda cukup mendefinisikannya sekali dan mendapatkan cakupan di seluruh agen tingkat sistem dan model dalam aplikasi lokal, sehingga Anda dapat menghindari deklarasi alat yang terfragmentasi.
Untuk mempelajari lebih lanjut, Anda dapat melihat diskusi ini, Membuka kemampuan agentic di aplikasi Anda dengan ML Kit GenAI, atau Anda dapat langsung melihat apa saja yang baru di Jetpack.
Cukup keren.
Baiklah, kembali ke Sarah.
Oke.
Dan baru saja menunjukkan cara kerja Android MCP di balik layar.
Mari kita terapkan.
Ingat, aplikasi Anda adalah server dan agen adalah klien, jadi tujuannya adalah memastikan bahwa aplikasi Anda adalah server yang baik untuk mereka.
Kita akan mengikuti Jetpacker, aplikasi contoh perjalanan kita, dari deklarasi alat pertama hingga panggilan fungsi yang sebenarnya melalui Gemini.
Anda dapat menulis AppFunction secara manual atau menggunakan agen coding untuk membantu Anda memulai.
Kami telah merilis kemampuan AppFunctions yang dapat membantu Anda memulai, mulai menulis fungsi, yang dapat Anda hubungkan ke Android Studio melalui Android CLI atau digunakan dengan agen coding pilihan Anda.
android skills add appfunctionsKeterampilan ini bekerja dalam tiga langkah.
Pertama, Gemini menganalisis codebase Anda untuk mengidentifikasi perjalanan pengguna inti yang akan menjadi alat yang berguna, seperti memutar media, membuat catatan, atau mengirim pesan.
Kemudian, Gemini akan membuat deklarasi AppFunction yang mengekspos perjalanan tersebut lengkap dengan implementasi dan dukungan platform yang diperlukan.
Misalnya, Gradle dan KSP, XML metadata aplikasi, dan injeksi dependensi.
Terakhir, Gemini akan menyempurnakan deskripsi KDoc Anda karena inilah yang digunakan agen untuk memutuskan kapan harus memanggil fungsi Anda dan caranya.
Berikut jenis hasil yang kita cari.
Ini adalah fungsi addExpense untuk Jetpacker.
/**
* Adds an expense to a trip.
*
* @param tripId ID of the trip. If null, the active trip is used.
* @param category Expense category. If null, it's inferred from the title.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addExpense(
title: String,
amount: Double,
currency: String,
tripId: String? = null,
category: String? = null,
): ExpenseSerializable = withContext(Dispatchers.IO) {
if (amount <= 0.0) {
throw AppFunctionInvalidArgumentException("amount must be greater than zero")
}
val trip = resolveTrip(tripId)
?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
val expense = Expense(trip.id, title, amount, currency,
category = category ?: inferExpenseCategory(title))
expenseDao.insertExpense(expense)
expense.toSerializable(remainingBudgetUsd(trip.id))
}Deskripsinya adalah kalimat satu baris yang dimulai dengan kata kerja dan resource: menambahkan pengeluaran ke perjalanan.
Kolom perjalanan dan kategori dapat bernilai null karena pengguna mungkin tidak selalu menentukannya, tetapi fungsi akan menyelesaikannya dalam isi.
Jika permintaan tidak dapat berhasil, alat ini akan menampilkan AppFunctionException sehingga agen mengetahui penyebabnya.
Sekarang, mari kita lihat pengujian.
Fungsi Anda kini menjadi layanan yang dapat digunakan agen.
Jadi, sebenarnya ada dua bagian yang perlu Anda uji: cara fungsi Anda berperilaku dan cara agen berperilaku.
Untuk fungsi Anda, kita akan mulai dengan pengujian unit yang mungkin sudah Anda tulis, tetapi ada beberapa hal yang perlu diingat saat agen yang benar-benar melakukan panggilan fungsi.
Pertama, nilai default Kotlin tidak berlaku di sini.
Parameter yang dihilangkan akan diterima sebagai null, false, atau 0.
Jadi, batas yang sebelumnya ditetapkan secara default ke 10 akan menjadi 0 di sini.
Panggilan juga dimulai di thread utama dan memanggil fungsi Anda dari pengujian mungkin tidak selalu terlihat jelas, jadi Anda harus menjalankan IO standar di dispatcher latar belakang.
Jika terjadi kegagalan, tampilkan AppFunctionException.
Hal lainnya akan menjangkau agen Anda sebagai error umum yang tidak diketahui dengan pesan pengecualian mentah yang dilampirkan.
Input Anda akan berasal dari model, jadi Anda harus menguji skenario kasus ekstrem seperti nilai kosong, sinonim untuk dolar seperti USD, dan nilai di luar rentang.
Untuk sisi agen, Anda perlu mengetahui apakah agen memilih alat Anda pada waktu yang tepat dan apakah agen menjalankan fungsi dengan benar.
Setiap agen berperilaku sedikit berbeda, jadi penting untuk menguji fungsi Anda dengan agen mana pun yang Anda harapkan akan menggunakannya.
Anda dapat menggunakan agen contoh open source kami untuk menguji atau Anda dapat menggunakan Gemini untuk menguji integrasi ini.
Jika Anda ingin melihat cara Gemini berinteraksi dengan AppFunctions atau ingin meluncurkan MCP Android di aplikasi Gemini, Anda dapat melakukannya dengan Platform Aplikasi Terhubung, yang kami luncurkan hari ini, dan saat ini dalam versi pratinjau.
Anda dapat mempelajari lebih lanjut dengan melihat diskusi kami sebelumnya tentang memperluas aplikasi Anda ke Gemini dan Penelusuran melalui Platform Aplikasi Terhubung.
Setelah Anda menjadi bagian dari program pratinjau, proses untuk menguji dengan Gemini hanya memiliki dua langkah.
Pertama, di Konsol Terhubung Google, kita akan membuat aplikasi terhubung yang dapat digunakan oleh Gemini.
Kedua, di Konsol Play, kita perlu mengupload app bundle Anda, sehingga kita dapat mengakses AppFunctions, dan Anda dapat memulai jalur pengujian internal, sehingga penguji dapat memvalidasi fungsi tersebut.
Jadi, mari kita mulai di Konsol Terhubung Google.
Di sini, Anda akan membuat aplikasi terhubung menggunakan tombol kanan atas, "Create an app".
Saat Anda membuat aplikasi yang terhubung, pilih Android MCP di layar ini.
Jika Anda membangun implementasi MCP sisi server, di sinilah Anda akan mengupload kode server Anda.
Saat Anda mengklik "Siapkan", semua aplikasi Google Play yang terkait dengan akun developer Anda akan muncul di sini dan siap ditautkan ke aplikasi yang terhubung.
Jika belum memiliki aplikasi Google Play, Anda harus membuatnya terlebih dahulu di Konsol Play.
Di sini, kita sudah memiliki Jetpacker, jadi kita akan menautkannya.
Selanjutnya, tambahkan detail yang akan dilihat pengguna di front end Gemini saat mereka mencoba berinteraksi dengan aplikasi Anda.
Hal ini mencakup hal-hal seperti ikon aplikasi, nama tampilan, kemampuan yang dimiliki aplikasi terhubung Anda, dan contoh perintah yang dapat digunakan pengguna untuk berinteraksi dengan aplikasi tersebut.
Teks ini harus deskriptif untuk membantu memandu pengguna dengan sebaik-baiknya.
Oke, itu tadi penyiapan aplikasi terhubung, tetapi Anda mungkin bertanya-tanya bagaimana aplikasi terhubung benar-benar mendapatkan akses ke fungsi.
Karena fungsi tersebut terdapat dalam kode aplikasi Anda, sekarang kita harus membuka Konsol Play dan mengupload app bundle Anda serta membuat jalur pengujian, sehingga kita dapat mengujinya dengan pengguna sungguhan.
Jadi, di layar ini, kita akan mengupload app bundle, lalu kita akan melihat di sini bahwa Google Play dapat mendeteksi AppFunctions, dan Anda dapat menautkannya langsung ke aplikasi terhubung Anda.
Sekarang, kita dapat melihat aplikasi terhubung Jetpack lengkap dengan fungsi Anda yang terintegrasi.
Selanjutnya, kita perlu menambahkan penguji, sehingga ada seseorang yang benar-benar siap untuk memvalidasi fungsi Anda dan memastikan bahwa fungsi tersebut berfungsi dengan baik.
Jadi, klik versi aplikasi yang terhubung di sini.
Di layar ini, Anda dapat memilih "Kelola penguji" dan mengonfigurasi daftar terbatas penguji yang akan dapat mengakses aplikasi terhubung Anda di Gemini setelah tersedia.
Anda dapat menambahkan penguji ke jalur pengujian ini dengan cara yang sama seperti yang Anda lakukan untuk rilis lainnya.
Pastikan Anda memiliki daftar penguji yang sama untuk aplikasi yang terhubung seperti yang Anda miliki untuk aplikasi Google Play.
Oke, sekarang mari kita uji integrasinya.
Berikut adalah aplikasi Jetpacker di ponsel pengguna, yang sudah memiliki perjalanan yang dicatat untuk konferensi ini.
Saya akan membuka Gemini untuk menghubungkan aplikasi.
Saya perlu memastikan bahwa saya telah menginstal versi aplikasi yang sama di perangkat saya dengan yang kami upload di Play sebelumnya, dan kami harus menggunakan akun untuk Gemini yang telah ditambahkan ke daftar penguji.
Jadi di sini, saya akan menyebutkan Jetpacker dan meminta nomor konfirmasi kamar hotel saya di Mountain View.
Gemini memanggil AppFunctions Jetpacker di perangkat dan menjawab, "RED11223 di Redwood Grove Hotel".
Selanjutnya, saya akan bertanya, "Berapa sisa anggaran saya untuk ADS?"
Dan panggilan fungsi akan menampilkan $1.850.
Sekarang, saya ingin melakukan perubahan, jadi saya akan meminta Gemini untuk menambahkan $45 untuk Uber bandara ke perjalanan ADS saya.
Gemini memanggil fungsi addExpense kita dari sebelumnya untuk menambahkan ini langsung ke itinerari saya.
Saat saya membuka Jetpacker, pengeluaran sudah ada di sana, dan anggaran berkurang sesuai dengan pengeluaran tersebut.
Jika Anda ingin mulai membangun aplikasi terhubung untuk Gemini sekarang, ajukan permohonan akses ke Platform Aplikasi Terhubung, yang saat ini dalam versi pratinjau.
Oke.
Jadi, mari kita selesaikan ini.
Jadi, kita berada di awal perubahan besar dalam komputasi seluler, dan inilah saatnya untuk memastikan bahwa aplikasi Anda siap.
Jadi, mulailah dengan mengidentifikasi perjalanan pengguna bernilai tinggi yang menjadi inti aplikasi Anda.
Ekspos backend cloud Anda menggunakan MCP sisi server, dan gunakan MCP Android untuk menangani data terenkripsi end-to-end, operasi lokal, sensor perangkat, dan hal-hal lain di perangkat.
Sekarang, untuk membuat prototipe, Anda dapat mendownload Android Studio dan Android CLI.
Anda dapat menambahkan dependensi AppFunctions AndroidX versi beta ke file build, dan menguji fungsi di perangkat pertama Anda secara lokal.
Untuk mendaftar agar dapat memulai lebih awal dalam menggunakan Gemini untuk produksi, Anda dapat mendaftar melalui kode QR ini di sini.
Dan tentu saja, pastikan untuk menyimak diskusi terkait tentang cara menggunakan Connected Apps Platform, serta cara menggunakan AppFunctions internal dengan ML Kit atau dengan agen Anda sendiri untuk menggerakkan agen dalam aplikasi Anda sendiri.
Terakhir, terima kasih banyak atas waktu Anda.
Kami tidak sabar melihat semua pengalaman luar biasa yang Anda bangun untuk Android.
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
serviceName = "JetPackerAppFunctionService",
appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
@Inject internal lateinit var tripDao: TripDao
@Inject internal lateinit var expenseDao: ExpenseDao
.../** Represents an event in a trip itinerary. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TimelineEventSerializable(
/** The event's unique identifier. */
val id: String,
/** The trip ID this event belongs to. */
val tripId: String,
/** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
val type: String,
/** The timestamp of the event. */
val timestamp: Long,
/** The title of the event. */
val title: String,
/** The location of the event. */
val location: String,
/** A description of the event. */
val description: String?,
/** The title of the trip this event belongs to. */
val tripTitle: String? = null,
/** Optional title of a matched itinerary event when added as a reminder. */
val matchedEventTitle: String? = null,
/** Execution status of the tool call (e.g., SUCCESS). */
val status: String? = "SUCCESS",
/** Human-readable completion message instructing the agent not to repeat the call. */
val message: String? = null,
)@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
fallback
}
events.map { it.toSerializable(tripTitle = trip?.title) }
}
}/**
* Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
* trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
* specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
* visitor tips.
*
* @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
.../**
* @param tripId Optional ID of the trip (e.g., '2026-1').
* Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
// resolveTrip defaults to the active trip if tripId == null
val trip = resolveTrip(tripId = tripId)@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
...@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun send(
message: Message
): Result {
...
throw AppFunctionException(
errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
errorMessage = "To address has invalid syntax!"
)
}<AppFunctionAppMetadata
xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
appfn:description="This app manages trips and travel expenses.
Operational Patterns:
- Use 'getAllTrips' to find the correct 'tripId' before
adding expenses or managing trip details.
Constraints:
- Expenses must be associated with a valid trip ID."
appfn:displayDescription="@string/user_visible_description" />viewModelScope.launch {
val newState =
if (enabled) {
AppFunctionManager.APP_FUNCTION_STATE_ENABLED
} else {
AppFunctionManager.APP_FUNCTION_STATE_DISABLED
}
appFunctionManager?.setAppFunctionEnabled(id, newState)
}@AppFunctionSignature(
scope = AppFunctionMetadata.SCOPE_ACTIVITY,
appFunctionXmlFileName = "cart_functions"
)
fun interface AddCurrentItemToCart {
/** Adds the item currently shown on screen to the cart. Returns the new cart size. */
suspend fun addToCart(quantity: Int): Int
}val addToCartAppFunctionAdapter =
appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()
coroutineScope.launch {
appFunctionManager.handleAppFunction(
addToCartAppFunctionAdapter.adapt { quantity ->
val newCartSize = cart.addItem(quantity)
newCartSize
}
)
}@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
...android skills add appfunctions/**
* Adds an expense to a trip.
*
* @param tripId ID of the trip. If null, the active trip is used.
* @param category Expense category. If null, it's inferred from the title.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addExpense(
title: String,
amount: Double,
currency: String,
tripId: String? = null,
category: String? = null,
): ExpenseSerializable = withContext(Dispatchers.IO) {
if (amount <= 0.0) {
throw AppFunctionInvalidArgumentException("amount must be greater than zero")
}
val trip = resolveTrip(tripId)
?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
val expense = Expense(trip.id, title, amount, currency,
category = category ?: inferExpenseCategory(title))
expenseDao.insertExpense(expense)
expense.toSerializable(remainingBudgetUsd(trip.id))
}