產品最新消息

在 Android 裝置上新增及編輯聯絡人更可靠

閱讀時間:6 分鐘
查看 Roxanna Aliabadi Walker 的個人資料
Roxanna Aliabadi Walker Android 產品經理

異動內容

如果應用程式指定即將推出的 Android 版本,就無法再使用 WRITE_CONTACTS 權限直接寫入聯絡人資料。請改用標準聯絡人意圖 (ACTION_INSERT、ACTION_EDIT 和 ACTION_INSERT_OR_EDIT)。如需完整的參考實作,請參閱開發人員指南,瞭解如何使用意圖修改聯絡人資料。

在搭載即將推出的 Android 版本的裝置上,這三個聯絡人意圖的行為會保持一致,且完全不需要權限。如果應用程式指定即將推出的 Android 版本或更新版本,並嘗試直接寫入聯絡人,系統會拒絕這些寫入作業,並導致 SecurityException 或權限遭拒。Android 17 QPR2 (2026 年第 4 季) 將推出新的特殊應用程式存取權限 MANAGE_CONTACTS,適用於需要廣泛存取權來建立及修改聯絡人的少數應用程式。Google Play 今天發布了更新版聯絡人存取權指南,包括MANAGE_CONTACTS。

異動原因

目前,如果應用程式需要儲存聯絡人 (即使只有一個),您可能需要要求 WRITE_CONTACTS。但這項權限會授予廣泛的存取權,允許應用程式在背景對裝置上的聯絡人進行廣泛變更,即使使用者未互動也一樣。大多數應用程式不需要這麼廣泛的權限。

雖然有替代方案,但我們收到意見回饋,指出這些方案需要更標準化,才能在整個生態系統中運作。先前,聯絡人意圖在不同裝置上的行為有所不同。部分編輯器會捨棄您傳入的資料,例如聯絡人相片。在某些裝置上,返回按鈕會在使用者無意間儲存聯絡人,或「建立新聯絡人」選項難以找到。因此,要求權限是合理的選擇。我們將在即將推出的 Android 版本中修正所有這些問題,並要求搭載該版本的裝置遵循相同標準。

替代方案

新增或編輯聯絡人:使用聯絡人意圖

使用 ACTION_INSERT、ACTION_EDIT 和 ACTION_INSERT_OR_EDIT 意圖時,系統會處理繁重的工作,提供簡潔、標準化的聯絡人編輯器體驗。使用者可以開啟快速半頁表單,新增項目,或輕鬆搜尋並新增資訊至現有聯絡人。使用者可以順暢地將這個畫面展開為全頁編輯器,填寫更多詳細資料,而應用程式完全不需要直接寫入權限。請參閱下方的暫定使用者體驗模擬圖:

01_Figure_Quick_Insert_Half_Sheet_Prefilled.png
螢幕截圖 2 - 標題:建立新聯絡人的全螢幕模擬圖:Android 使用者介面暫定螢幕截圖,顯示全螢幕聯絡人編輯器,其中包含顯示圖片選取器、姓名、電話和電子郵件欄位。設計標示為「UX not Final」。

圖 1:建立新聯絡人  *使用者體驗非最終版本*

Figure02_01_Add_To_Existing_0_Search_State_Half_Sheet.png
螢幕截圖 2 - 標題:全螢幕模擬「將資訊新增至現有聯絡人」:Android 使用者介面暫定螢幕截圖,顯示全螢幕聯絡人編輯器,其中包含顯示圖片選取器、姓名、電話和電子郵件欄位。全版設計採用漸進式揭露方塊,用於顯示次要欄位。設計標示為「UX not Final」。

圖 2:將資訊新增至現有聯絡人 *使用者體驗尚未定案*


注意:不同裝置製造商可能會修改使用者體驗,以符合其 OS 風格,但行為會保持一致。


您可以在意圖中傳遞選用的資料酬載 (稱為額外資料),進一步簡化建立程序:

  • EXTRA_ACCOUNT 會建議聯絡人儲存到哪個帳戶,這在應用程式同步聯絡人時很有幫助。這項額外功能先前就已推出,但支援情況因裝置而異。從即將推出的 Android 版本開始,各裝置的支援功能將標準化。
  • EXTRA_PHOTO_URI 會傳遞相片,預填聯絡人相片。編輯器會為您處理 URI 讀取的權限。這是即將推出的 Android 版本新功能,整個生態系統都會支援。

之前:直接使用 WRITE_CONTACTS 寫入 (如果應用程式指定即將推出的 Android 版本,會導致 SecurityException 或權限遭拒。)

 

// Requires android.permission.WRITE_CONTACTS
fun saveContactDirectly(context: Context, name: String, phone: String, account: Account) {
    val ops = arrayListOf(
        ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
            .build(),
        ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
            .withValue(
                ContactsContract.Data.MIMETYPE,
                ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
            )
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
            .build(),
        ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
            .withValue(
                ContactsContract.Data.MIMETYPE,
                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
            )
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .build()
    )
    context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
}

 

 

之後:開啟聯絡人編輯器 (不需權限)

 

fun saveContactWithEditor(
    context: Context,
    name: String,
    phone: String,
    account: Account,
    photoUri: Uri
) {
    val intent = Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI).apply {
        putExtra(ContactsContract.Intents.Insert.NAME, name)
        putExtra(ContactsContract.Intents.Insert.PHONE, phone)
        // Suggest which account the contact is saved to
        putExtra(ContactsContract.Intents.Insert.EXTRA_ACCOUNT, account)
        // New in the upcoming Android release: pre-fill the contact photo
        putExtra(ContactsContract.Intents.Insert.EXTRA_PHOTO_URI, photoUri)
        clipData = ClipData.newRawUri("", photoUri)
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    context.startActivity(intent)
}

 

之前:直接更新現有聯絡人

 

// Requires android.permission.WRITE_CONTACTS
fun addPhoneDirectly(context: Context, rawContactId: Long, phone: String) {
    val values = ContentValues().apply {
        put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
        put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
        put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
    }
    context.contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
}

之後:讓使用者選擇要更新的聯絡人,或建立新聯絡人

fun addPhoneWithEditor(context: Context, phone: String) {
    val intent = Intent(Intent.ACTION_INSERT_OR_EDIT).apply {
        type = ContactsContract.Contacts.CONTENT_ITEM_TYPE
        putExtra(ContactsContract.Intents.Insert.PHONE, phone)
    }
    context.startActivity(intent)
}

如果您已知道要更新哪個聯絡人,請使用 ACTION_EDIT 搭配該聯絡人的 URI:

fun editKnownContact(context: Context, contactUri: Uri, phone: String) {
    val intent = Intent(Intent.ACTION_EDIT, contactUri).apply {
        putExtra(ContactsContract.Intents.Insert.PHONE, phone)
    }
    context.startActivity(intent)
}

 

挑選聯絡人:使用聯絡人選擇工具

如果只需要讀取使用者選擇的聯絡人,請使用 Android 17 起提供的聯絡人選擇工具。不需要任何權限。

通話和訊息應用程式:使用使用者目錄

部分應用程式會在聯絡人中加入「撥打電話給…」或「傳送訊息給…」動作,方便使用者透過「電話」或「聯絡人」應用程式撥打電話或發起即時通訊。如果您的應用程式目前會將額外項目寫入使用者的聯絡人,請改用 People Directory,這項功能將於 Android QPR2 (2026 年第 4 季) 推出。這是一個新的系統目錄,應用程式可在此發布使用者在應用程式中與他人聯絡的方式,而無需編輯使用者的聯絡人。

應用程式遷移範例

以下舉例說明不同類型的應用程式如何存取聯絡人,讓您瞭解這些替代方案的實際運作方式:

應用程式類型範例 (僅列舉部分)建議的遷移路徑
社群、P2P 付款和共乘應用程式如果應用程式可讓使用者尋找好友、分攤帳單或分享行程狀態,則完全不需要聯絡人權限。使用聯絡人選擇工具選取一次聯絡人,並在使用者輕觸個人資料中的「儲存至聯絡人」時,使用 EXTRA_PHOTO_URI 啟動 ACTION_INSERT。
名片掃描器和活動交流應用程式如果應用程式掃描識別證或 QR code 並儲存新聯絡人,或將公司電話號碼新增至現有同事的項目,請使用 ACTION_INSERT 或 ACTION_INSERT_OR_EDIT。標準化的半頁編輯器會預填剖析欄位 (姓名、公司、電話、電子郵件和相片),使用者只要輕觸一下即可驗證並儲存,不必授予背景權限。
VoIP 通話和訊息應用程式請不要要求 WRITE_CONTACTS 將自訂的「撥打電話給…」或「傳送訊息給…」資料列插入使用者的通訊錄,而是透過使用者目錄發布可連線中繼資料,並在使用者從即時通訊討論串新增聯絡人時使用 ACTION_INSERT。
企業客戶關係管理和雲端同步應用程式如果應用程式會持續與雲端或企業帳戶雙向同步處理背景聯絡人,且無法為每筆記錄向使用者顯示意圖 UI,則應用程式符合宣告 MANAGE_CONTACTS  的資格。

請檢查下列幾種變動

  • 編輯器不支援自訂資料類型。如果您傳遞的 Insert.DATA 資料列含有標準 ContactsContract.CommonDataKinds 類型以外的 MIME 類型,編輯器會忽略這些 MIME 類型,且不會顯示任何訊息。
  • SHOW_OR_CREATE_CONTACT 並非由系統編輯器處理。裝置隨附的「聯絡人」應用程式仍可處理這項操作。如要在各裝置上維持一致的行為,請使用 ACTION_INSERT_OR_EDIT。
  • 寫入作業預設會失敗。指定即將推出的 Android 版本後,任何不含 MANAGE_CONTACTS 的直接寫入都會導致 SecurityException or permission denial。進行測試。

我需要MANAGE_CONTACTS嗎?

別緊張。如果應用程式是應使用者要求儲存或編輯聯絡人,上述意圖就足以涵蓋這類行為,您不必取得權限,也不必在 Play 管理中心聲明。

MANAGE_CONTACTS 適用於核心用途為管理聯絡人,且意圖不足的應用程式。例如:

您的應用程式…需要MANAGE_CONTACTS嗎?
儲存即時通訊、商家名片或訂單中的聯絡人。否,使用 ACTION_INSERT
將電話號碼或電子郵件地址新增至現有聯絡人。否,請使用 ACTION_INSERT_OR_EDIT 或 ACTION_EDIT
讓使用者挑選要邀請、分享或付款的聯絡人。否,請使用聯絡人選擇工具
在聯絡人中新增通話或訊息動作。否,使用「使用者目錄」
有專屬的聯絡人編輯畫面。否,僅有自訂介面不符合資格
是專門用來管理聯絡人或取代撥號器的應用程式。可能可以
是來電顯示和騷擾/廣告來電阻擋應用程式,可大量更新通訊錄項目。可能可以
在背景將聯絡人與企業或雲端帳戶同步。可能可以
大量備份/還原聯絡人,或在裝置間轉移手機資料。可能可以
為個別聯絡人設定自訂鈴聲。可能可以

運作方式:

  • MANAGE_CONTACTS 是特殊的應用程式存取權限。這不是執行階段對話方塊。使用者可依序前往「設定」>「特殊應用程式存取權」>「管理聯絡人」,開啟這項功能。
// Opens Settings > Apps > Special app access > Manage contacts
val intent = Intent(Settings.ACTION_MANAGE_CONTACTS, Uri.fromParts("package", packageName, null))
context.startActivity(intent)
  • 包括讀取、新增、編輯及刪除聯絡人、帳戶和使用者目錄資料。您不需要同時要求 READ_CONTACTS。
  • 如要使用 MANAGE_CONTACTS 在 Google Play 發布應用程式,請在 Play 管理中心提交聲明,說明為何意圖無法滿足應用程式的核心功能需求。

遷移步驟

  • 尋找聯絡人寫入。在程式碼中搜尋 WRITE_CONTACTS、ContactsContract.RawContacts.CONTENT_URI、ContactsContract.Data.CONTENT_URI 和 applyBatch(ContactsContract.AUTHORITY, …)。
  • 將每個使用者導向的寫入作業替換為意圖: ACTION_INSERT 用於新聯絡人,ACTION_EDIT 或 ACTION_INSERT_OR_EDIT 用於更新。
  • 在 Insert.DATA 中僅使用標準資料類型。將通話或訊息動作移至「使用者目錄」。
  • 請根據上表,判斷是否確實需要 MANAGE_CONTACTS。如有,請規劃 Play 管理中心聲明。
  • 如果沒有程式碼路徑需要 WRITE_CONTACTS,請從資訊清單中移除 。

時間軸

When後續事宜
2026 年 10 月 21 日Google Play 宣布MANAGE_CONTACTS政策
2026 年 (Android 17 季度平台版本 2)MANAGE_CONTACTS權限可用
2027 (即將推出的 Android 版本)WRITE_CONTACTS 不再授予寫入權限給指定即將推出 Android 版本的應用程式。聯絡人意圖在所有裝置上都採用標準化
2027 年,不早於即將推出的 Android 版本Play 管理中心要求使用 MANAGE_CONTACTS 的應用程式提交聲明
2027 年,不早於即將推出的 Android 版本必須遵守 Play 政策

常見問題

  • 這項異動會影響所有應用程式嗎?
    • WRITE_CONTACTS 淘汰異動僅適用於指定即將推出的 Android 版本及後續版本的應用程式。指定舊版 SDK 的應用程式會維持現有行為,但無論目標 SDK 為何,所有在新 OS 上執行的應用程式都能使用新的標準化聯絡人編輯器。
  • 我的應用程式可以透過意圖刪除聯絡人嗎?
    • 沒有,目前沒有刪除聯絡人的意圖。使用者在「聯絡人」應用程式中刪除聯絡人。具有 MANAGE_CONTACTS 的應用程式可以直接刪除聯絡人。
  • 我已有自己的聯絡人編輯畫面,可以保留嗎?
    • 只有在應用程式符合 MANAGE_CONTACTS 資格時,想保留自訂設計,本身不足以構成申請這項權限的理由。
  • MANAGE_CONTACTS 是否會取代 READ_CONTACTS?
    • MANAGE_CONTACTS 包含讀取權限,因此您不需要同時要求這兩項權限,但許多應用程式符合 READ_CONTACTS 的資格,完全不需要修改聯絡人。這些應用程式可以繼續要求READ_CONTACTS。
  • 如果使用者使用舊版 Android,會受到什麼影響?
    • 聯絡人意圖也適用於舊版本,由裝置的「聯絡人」應用程式處理。大多數應用程式在使用這些意圖時,都能在所有裝置上正常運作。如果應用程式受到特定一致性問題影響,且該問題只能透過最新版本解決,您可以在資訊清單中保留 WRITE_CONTACTS 權限,並設定適當的 maxSdkVersion。
繼續閱讀