權(quán)限請求可以保護(hù)設(shè)備中的敏感信息,并且僅當(dāng)應(yīng)用正常工作需要訪問相關(guān)信息時(shí)才能使用這些信息。利用本文檔提供的技巧,您無需請求訪問此類信息即可實(shí)現(xiàn)相同(或更好的)功能;但本文不會詳細(xì)討論權(quán)限在 Android 操作系統(tǒng)中的工作方式。
要更籠統(tǒng)地了解 Android 權(quán)限,請參閱權(quán)限概述。要詳細(xì)了解如何在代碼中使用權(quán)限,請參閱請求應(yīng)用權(quán)限。
使用 Android 權(quán)限的原則
使用 Android 權(quán)限時(shí),我們建議遵循以下原則:
#1:僅使用應(yīng)用正常工作所需的權(quán)限。根據(jù)您使用權(quán)限的方式,您可以通過其他方式執(zhí)行所需的操作(系統(tǒng) intent、標(biāo)識符、電話的后臺處理),而無需依賴于訪問敏感信息。
#2:注意庫所需的權(quán)限。 添加某個(gè)庫時(shí),您也會繼承它的權(quán)限要求。您應(yīng)了解正在添加的庫、它們需要的權(quán)限以及這些權(quán)限的用途。
#3:公開透明。 請求權(quán)限時(shí),請清晰說明您要訪問的內(nèi)容以及訪問原因,以便用戶可以做出明智的決策。在請求權(quán)限時(shí)(包括安裝、運(yùn)行時(shí)或更新權(quán)限對話框)列出這些信息。
#4:讓系統(tǒng)以顯式方式訪問。 在訪問敏感功能(例如,攝像頭或麥克風(fēng))時(shí)提供連續(xù)指示,讓用戶知道您在收集數(shù)據(jù),避免讓他們認(rèn)為您在偷偷地收集數(shù)據(jù)。
本指南剩下的部分將以開發(fā) Android 應(yīng)用為背景詳細(xì)介紹這些規(guī)則。
Android 6.0+ 中的權(quán)限
Android 6.0 Marshmallow 引入了一個(gè)新的權(quán)限模式,讓應(yīng)用可以在運(yùn)行時(shí)而不是在安裝之前向用戶請求權(quán)限。支持這個(gè)新模式的應(yīng)用會在應(yīng)用確實(shí)需要相關(guān)服務(wù)或這些服務(wù)保護(hù)的數(shù)據(jù)時(shí)請求權(quán)限。盡管這不會(不一定會)改變整體應(yīng)用行為,但會給敏感用戶數(shù)據(jù)的處理方式帶來一些變化:
增加了情境上下文:系統(tǒng)會在運(yùn)行時(shí)在應(yīng)用的上下文中提示用戶提供訪問相關(guān)權(quán)限組涵蓋的功能所需的權(quán)限。用戶對請求權(quán)限的上下文更加敏感,如果您請求的權(quán)限與應(yīng)用的用途不匹配,則一定要向用戶詳細(xì)解釋您為什么請求此權(quán)限;您應(yīng)盡可能在請求時(shí)以及后續(xù)對話框中(如果用戶拒絕請求)解釋您的請求。
在授予權(quán)限時(shí)更加靈活:用戶可以在收到請求時(shí)以及在設(shè)置中拒絕訪問各個(gè)權(quán)限,但是當(dāng)功能因此而中斷時(shí),他們可能仍會感到驚訝。最好監(jiān)控有多少用戶拒絕權(quán)限請求(例如,使用 Google Analytics(分析)),以便重構(gòu)應(yīng)用以避免依賴該權(quán)限,或更好地解釋應(yīng)用需要此權(quán)限才能正常工作的原因。還應(yīng)確保應(yīng)用可以處理當(dāng)用戶拒絕權(quán)限請求或在設(shè)置中關(guān)閉權(quán)限時(shí)產(chǎn)生的異常。
增加了事務(wù)負(fù)擔(dān):系統(tǒng)將要求用戶單獨(dú)授予權(quán)限組的訪問權(quán)限,而不是以集合的形式授予。這樣一來,最大程度降低請求的權(quán)限數(shù)量就變得非常重要,因?yàn)閿?shù)量多會增加用戶授予權(quán)限的負(fù)擔(dān),并且會增大至少有一個(gè)請求被拒絕的概率。
避免請求不必要的權(quán)限
每次您請求某個(gè)權(quán)限時(shí),都是在強(qiáng)迫用戶做出決定。應(yīng)盡量減少提出這些請求的次數(shù)。如果用戶運(yùn)行的是 Android 6.0(API 級別 23)或更高版本,則每次用戶嘗試一些請求權(quán)限的新應(yīng)用功能時(shí),應(yīng)用都必須中斷用戶的操作而發(fā)起權(quán)限請求。如果用戶運(yùn)行的是較低版本的 Android,則在安裝應(yīng)用時(shí)必須授予應(yīng)用每一種權(quán)限;如果列表過長或看起來不合適,用戶可能會決定根本不安裝應(yīng)用。因此,應(yīng)盡量減少應(yīng)用需要的權(quán)限數(shù)量。
本部分提供了常見用例的替代方法,有助于限制您提出權(quán)限請求的次數(shù)。由于向用戶請求的權(quán)限數(shù)量和類型會影響下載量(與其他請求較少權(quán)限的類似應(yīng)用相比),因此最好避免為不必要的功能請求權(quán)限。
改用 intent
在許多情況下,要讓應(yīng)用執(zhí)行某項(xiàng)任務(wù),有兩種方法供您選擇。應(yīng)用可以要求提供權(quán)限來自行執(zhí)行該任務(wù),也可以使用 intent 讓其他應(yīng)用執(zhí)行該任務(wù)。
例如,假設(shè)應(yīng)用需要使用設(shè)備攝像頭才能夠拍攝照片。應(yīng)用可以請求 CAMERA 權(quán)限,以便允許應(yīng)用直接訪問攝像頭。然后,應(yīng)用將使用攝像頭 API 控制攝像頭并拍攝照片。此方法使應(yīng)用能夠完全控制拍攝過程,并且您可以將攝像頭界面整合到應(yīng)用中。
不過,如果您很少需要訪問用戶數(shù)據(jù),換句話說,每次您需要訪問數(shù)據(jù)時(shí)都向用戶顯示運(yùn)行時(shí)對話框,這種中斷操作并非不可接受,那么您可以使用基于 intent 的請求。Android 提供了一些系統(tǒng) intent,借助這些 intent,應(yīng)用無需請求權(quán)限,因?yàn)樵诎l(fā)出基于 intent 的請求時(shí)用戶會選擇與應(yīng)用共享的內(nèi)容(如果有)。
例如,您可以使用 intent 操作類型 MediaStore.ACTION_IMAGE_CAPTURE 或 MediaStore.ACTION_VIDEO_CAPTURE 來拍攝圖像或視頻,而無需直接使用 Camera 對象(或請求權(quán)限)。在這種情況下,每次拍攝圖像時(shí),系統(tǒng) intent 都會代表您請求用戶提供權(quán)限。
同樣,如果您需要撥打電話、訪問用戶的聯(lián)系人或執(zhí)行其他操作,您可以通過創(chuàng)建適當(dāng)?shù)?intent 來完成,也可以直接請求權(quán)限并訪問相應(yīng)的對象。每種方法各有優(yōu)缺點(diǎn)。
如果使用權(quán)限:
- 當(dāng)您執(zhí)行操作時(shí),您的應(yīng)用可以完全控制用戶體驗(yàn)。不過,如此廣泛的控制會增加代碼的復(fù)雜性,因?yàn)槟枰O(shè)計(jì)適當(dāng)?shù)慕缑妗?/li>
- 系統(tǒng)會在運(yùn)行時(shí)或安裝時(shí)(具體取決于用戶的 Android 版本)提示用戶授予權(quán)限一次。之后,應(yīng)用即可執(zhí)行操作,不再需要用戶進(jìn)行其他互動。不過,如果用戶未授予權(quán)限(或之后撤消權(quán)限),則應(yīng)用將根本無法執(zhí)行操作。
如果使用 intent:
- 您不必為操作設(shè)計(jì)界面。處理 intent 的應(yīng)用將提供界面。
- 用戶可以使用他們首選的應(yīng)用執(zhí)行任務(wù)。例如,用戶可以選擇用他們喜愛的照片應(yīng)用拍照。
- 如果用戶沒有適用于操作的默認(rèn)應(yīng)用,則系統(tǒng)會提示用戶選擇一款應(yīng)用。如果用戶未指定默認(rèn)處理程序,則他們每次執(zhí)行此操作時(shí)都可能必須處理一個(gè)額外的對話框。
不要讓用戶感到無所適從
如果用戶運(yùn)行的是 Android 6.0(API 級別 23)或更高版本,則用戶必須在運(yùn)行應(yīng)用時(shí)為其授予權(quán)限。如果您讓用戶一次面對大量的權(quán)限請求,可能會讓用戶感到無所適從,導(dǎo)致他們退出您的應(yīng)用。您應(yīng)根據(jù)需要請求權(quán)限。
在某些情況下,一項(xiàng)或多項(xiàng)權(quán)限可能對您的應(yīng)用來說必不可少。在這種情況下,合理的做法是,在應(yīng)用啟動之后立即請求提供所有這些權(quán)限。例如,如果您創(chuàng)建的是攝影應(yīng)用,則該應(yīng)用將需要訪問設(shè)備的攝像頭。當(dāng)用戶首次啟動該應(yīng)用時(shí),系統(tǒng)會要求他們提供攝像頭使用權(quán)限,他們不會對此感到驚訝。但是,如果同一應(yīng)用還具備與用戶的聯(lián)系人分享照片的功能,那么您或許不應(yīng)在應(yīng)用首次啟動時(shí)請求用戶提供 READ_CONTACTS 權(quán)限,而應(yīng)等到用戶嘗試使用“分享”功能之后再請求該權(quán)限。
如果應(yīng)用提供教程,則合理的做法是,在教程結(jié)束時(shí)請求提供應(yīng)用的必要權(quán)限。
失去音頻焦點(diǎn)后暫停媒體
在這種情況下,當(dāng)用戶接電話時(shí),您的應(yīng)用需要轉(zhuǎn)入后臺,只有在通話停止后才會重新獲得焦點(diǎn)。
出現(xiàn)此類情況(例如,媒體播放器在通話期間靜音或暫停)時(shí),通常采用的方法是使用 PhoneStateListener 或監(jiān)聽 android.intent.action.PHONE_STATE 的廣播,以監(jiān)聽通話狀態(tài)有無變化。這種解決方法的問題是它需要 READ_PHONE_STATE 權(quán)限,這將強(qiáng)制用戶授予對廣泛的敏感數(shù)據(jù)(如用戶的設(shè)備和 SIM 硬件 ID 以及來電的電話號碼)的訪問權(quán)限。
您可以通過為應(yīng)用請求 AudioFocus,在沒有 READ_PHONE_STATE 或 MODIFY_PHONE_STATE 權(quán)限的情況下檢測用戶是否在通話中,這么做不需要顯式權(quán)限,因?yàn)樗辉L問敏感信息。只需將對音頻放入后臺所需的代碼放入onAudioFocusChange() 事件處理程序,當(dāng)操作系統(tǒng)轉(zhuǎn)換其音頻焦點(diǎn)時(shí),它將自動運(yùn)行。要詳細(xì)了解如何執(zhí)行此操作,請參閱此文檔。
確定正在運(yùn)行實(shí)例的設(shè)備
在這種情況下,您需要一個(gè)唯一標(biāo)識符來確定您的應(yīng)用實(shí)例正在哪個(gè)設(shè)備上運(yùn)行。
應(yīng)用可能具有設(shè)備特定的偏好設(shè)置或消息(例如,在云端為用戶保存設(shè)備特定的播放列表,以便他們在車上和家里可以有不同的播放列表)。常見的解決方案是利用設(shè)備標(biāo)識符(如 Device IMEI),但這需要 Device ID and call information 權(quán)限組(M+ 中為 PHONE)。它還使用一個(gè)無法重置且在所有應(yīng)用之間共享的標(biāo)識符。
下面兩種方法可以替代這些類型的標(biāo)識符:
- 使用
com.google.android.gms.iidInstanceID API。getInstance(Context context).getID()將為您的應(yīng)用實(shí)例返回一個(gè)唯一設(shè)備標(biāo)識符。結(jié)果是一個(gè)應(yīng)用實(shí)例作用域標(biāo)識符,在存儲有關(guān)應(yīng)用的信息時(shí),該標(biāo)識符可用作鍵,如果用戶重新安裝應(yīng)用,該標(biāo)識符會重置。 - 使用
randomUUID()之類的基本系統(tǒng)函數(shù)創(chuàng)建您自己的標(biāo)識符,其作用域限定為應(yīng)用的存儲空間。
為廣告或用戶分析創(chuàng)建唯一標(biāo)識符
在這種情況下,您需要一個(gè)唯一標(biāo)識符來為沒有登錄您應(yīng)用的用戶構(gòu)建配置文件(例如,用于廣告定位或衡量轉(zhuǎn)化率)。
為廣告和用戶分析構(gòu)建配置文件有時(shí)需要一個(gè)在其他應(yīng)用之間共享的標(biāo)識符。此問題的常見解決方案需要利用設(shè)備標(biāo)識符(如 Device IMEI),這需要 Device ID and call information 權(quán)限組(API 級別 23+ 中為 PHONE),并且無法由用戶重置。無論是上述哪種情況,除了使用不可重置的標(biāo)識符并請求用戶可能認(rèn)為不尋常的權(quán)限外,還會違反 Play 開發(fā)者計(jì)劃政策。
遺憾的是,在這些情況下,使用 com.google.android.gms.iid InstanceID API 或系統(tǒng)函數(shù)創(chuàng)建應(yīng)用作用域 ID 并不是適當(dāng)?shù)慕鉀Q方案,因?yàn)榭赡苄枰趹?yīng)用之間共享該 ID。一種替代解決方案是使用通過 getId() 方法從AdvertisingIdClient.Info 類中獲取的 Advertising Identifier。您可以使用 getAdvertisingIdInfo(Context)方法創(chuàng)建一個(gè) AdvertisingIdClient.Info 對象,并調(diào)用 getId() 方法來使用該標(biāo)識符。請注意,此方法會產(chǎn)生阻塞,因此,您不應(yīng)從主線程調(diào)用它;有關(guān)此方法的詳細(xì)說明,請點(diǎn)擊此處。
了解您正在使用的庫
有時(shí),您在應(yīng)用中使用的庫需要一些權(quán)限。例如,廣告和分析庫可能需要訪問 Location 或 Identity 權(quán)限組以實(shí)現(xiàn)必需的功能。但從用戶的角度來看,權(quán)限請求來自于您的應(yīng)用,而不是庫。
就像用戶會選擇使用較少權(quán)限即可實(shí)現(xiàn)相同功能的應(yīng)用一樣,開發(fā)者也應(yīng)檢查他們的庫,并選擇不會使用非必要權(quán)限的第三方 SDK。例如,設(shè)法避免使用需要 Identity 權(quán)限組的庫,除非可以清楚地向用戶解釋應(yīng)用為什么需要這些權(quán)限。尤其是對于提供位置功能的庫,請確保您不需要請求 FINE_LOCATION 權(quán)限,除非您正在使用基于位置的定位功能。
解釋為何需要權(quán)限
系統(tǒng)在您調(diào)用 requestPermissions()時(shí)顯示的權(quán)限對話框?qū)⒄f明應(yīng)用需要哪些權(quán)限,但不會解釋為何需要這些權(quán)限。在某些情況下,用戶可能會感到困惑。最好在調(diào)用 requestPermissions() 之前向用戶解釋應(yīng)用需要相應(yīng)權(quán)限的原因。
研究表明,如果用戶知道應(yīng)用需要相應(yīng)權(quán)限的原因,他們會更容易接受權(quán)限請求。用戶研究表明:
…用戶是否愿意為某個(gè)移動應(yīng)用授予給定權(quán)限,在很大程度上受此類權(quán)限關(guān)聯(lián)用途的影響。例如,用戶是否愿意授予訪問其位置的權(quán)限取決于該權(quán)限請求是否為支持應(yīng)用的核心功能所必需,或者應(yīng)用是否將與廣告網(wǎng)絡(luò)或分析公司分享此信息。1
卡內(nèi)基梅隆大學(xué) (CMU) 的 Jason Hong 教授根據(jù)他所帶領(lǐng)的小組的研究成果得出一個(gè)一般結(jié)論:
…與只是告訴用戶應(yīng)用正在使用其位置相比,如果用戶知道應(yīng)用為什么使用像他們的位置這樣敏感的信息(例如,用于定向廣告),那么用戶會更容易接受。1
因此,如果您僅使用歸入權(quán)限組的一小部分 API 調(diào)用,明確列出您使用哪些權(quán)限以及使用原因會非常有用。例如:
- 如果您僅使用粗略位置,請?jiān)趹?yīng)用說明或應(yīng)用的幫助文檔中告知用戶。
- 如果您需要訪問短信以接收身份驗(yàn)證碼,從而防止用戶被欺詐,請?jiān)趹?yīng)用說明和/或首次訪問數(shù)據(jù)時(shí)告知用戶。
注意:如果應(yīng)用面向 Android 8.0(API 級別 26)或更高版本,請不要在驗(yàn)證用戶憑據(jù)過程中請求 READ_SMS 權(quán)限,而應(yīng)使用
createAppSpecificSmsToken()生成應(yīng)用特定的令牌,然后將此令牌傳遞給可以發(fā)送驗(yàn)證短信的其他應(yīng)用或服務(wù)。
在特定條件下,讓用戶實(shí)時(shí)了解應(yīng)用在訪問敏感數(shù)據(jù)也是非常有益的。例如,如果應(yīng)用在訪問攝像頭或麥克風(fēng),通常最好在應(yīng)用中的某個(gè)位置或在通知托盤中(如果應(yīng)用正在后臺運(yùn)行)使用通知圖標(biāo)告知用戶,這樣不會讓您看起來像是在偷偷地收集數(shù)據(jù)。
最后,如果您需要請求權(quán)限以便在應(yīng)用中運(yùn)行某項(xiàng)功能,但用戶不清楚原因,則需要找到一種方法讓用戶知道您為什么需要最敏感的權(quán)限。
測試兩種權(quán)限模式
自 Android 6.0(API 級別 23)起,用戶是在運(yùn)行時(shí)(而不是在安裝應(yīng)用時(shí))授予和撤消應(yīng)用權(quán)限。因此,您必須在多種不同條件下測試應(yīng)用。在低于 Android 6.0 的版本中,您可以合理地認(rèn)為,如果應(yīng)用能運(yùn)行,它就已經(jīng)獲得在應(yīng)用清單中聲明的全部權(quán)限。自 Android 6.0 起,用戶可以開啟或關(guān)閉任何應(yīng)用的權(quán)限,即使面向 API 級別 22 或更低級別的應(yīng)用也是如此。您應(yīng)測試以確保您的應(yīng)用能正常運(yùn)行,無論它是否具有任何權(quán)限。
以下提示可幫助您在運(yùn)行 API 級別 23 或更高級別的設(shè)備上找出與權(quán)限有關(guān)的代碼問題:
- 確定應(yīng)用的當(dāng)前權(quán)限和相關(guān)的代碼路徑。
- 在各種受權(quán)限保護(hù)的服務(wù)和數(shù)據(jù)中測試用戶流。
- 使用授予或撤消權(quán)限的各種組合進(jìn)行測試。例如,相機(jī)應(yīng)用可能會在其清單中列出 CAMERA、READ_CONTACTS和 ACCESS_FINE_LOCATION。您應(yīng)在測試應(yīng)用時(shí)逐一開啟和關(guān)閉這些權(quán)限,確保應(yīng)用可以妥善處理所有權(quán)限配置。
- 使用 adb 工具從命令行管理權(quán)限:
- 按組列出權(quán)限和狀態(tài):
$ adb shell pm list permissions -d -g
- 授予或撤消一項(xiàng)或多項(xiàng)權(quán)限:
$ adb shell pm [grant|revoke] <permission-name> ...
- 針對使用權(quán)限的服務(wù)對應(yīng)用進(jìn)行分析。
其他資源
- Android 權(quán)限的 Material Design 準(zhǔn)則
- Android Marshmallow 6.0:請求權(quán)限 - 此視頻介紹了 Android 運(yùn)行時(shí)權(quán)限模式以及請求用戶提供權(quán)限的正確方法。
- 解釋應(yīng)用為什么需要權(quán)限
- 唯一標(biāo)識符最佳做法