Android 權限機制與適配經驗

Android 權限機制與適配經驗

一、概要

Android M已經發布一段時間了,市面上很多應用都已經適配Android M。權限機制,作爲Android M的一大特性,受到了很多開發者的關注。

本文主要分享了以下幾個知識點的內容:

  1. Android權限機制關鍵知識點;

  2. QQ音樂對於權限的適配經驗;

  3. 近段時間以來遇到的一些Android權限方面的問題。

OK,下面進入主題。

二、Android權限機制

已經瞭解過基本知識的,建議直接跳到第三點(QQ音樂的權限適配經驗)。

Android6.0以前,Android的權限機制比較簡單,開發者在AndroidManifest文件中聲明需要的權限,APP安裝時,系統提示用戶APP將獲取的權限,需要用戶同意授權才能繼續安裝,從此APP便永久的獲得了授權。然而,同期的iOS對於權限的處理會更加靈活,權限的授予並不是在安裝時,而是在APP運行時,用戶可以根據自身的需要,決定是否授予APP某一權限,同時,用戶也可以很方便回收授予的權限。顯然,動態權限管理的機制,對於用戶的隱私保護是更加適用的,Android過於簡單的權限機制也受到了不少人的吐槽。終於,Android6.0也發佈了動態權限的機制。

開始適配和如何兼容

APP要適配Android6.0非常簡單,只需要將targetSdkVersioncompileSdkVersion都升級到23及以上,同時加入權限檢查申請等代碼邏輯即可。這裏很多人會有一些疑惑,如果針對舊版本的APP在Android6.0機型上運行或者針對Android6.0適配了的APP在Android6.0以下機型上運行,會有什麼表現呢?是如何兼容的呢?

  1. 首先,舊版本APP(targetSdkVersion低於23),因爲沒有適配權限的申請相關邏輯,在Android6.0以上機型運行的時候,仍然採用安裝時授權的方案。

  2. 適配了Android6.0的APP,在低版本Android系統上運行的時候,仍然採用安裝時授權的方案,但是開發者需要注意的是,權限申請的代碼邏輯只應該在Android6.0及以上的機型被執行。

危險權限與普通權限

一開始,聽到要加入權限判斷和申請代碼邏輯的程序員內心可能是崩潰的:正常的一個有一定規模的APP,很容易就七七八八的聲明瞭很多權限,如果每個權限都申請豈不是非常麻煩?

好歹,Google還算比較明智,並不是所有的權限都需要運行時申請才能使用。Google對每個權限的隱私危害性進行了評估。將權限分爲了兩大類:普通權限和危險權限。舉個例子,控制手機震動的權限對於用戶並沒有什麼危害,只要開發者聲明瞭這個權限,安裝後就可以一直被授權,也不能被回收,但是,像讀取sd卡數據這類權限,很顯然就是危險權限了,APP必須向用戶申請這個權限。

Google還是很體貼我們開發者的,爲了進一步減少開發的工作量和申請權限對用戶的騷擾,對危險權限根據各自的屬性進行了分組。舉個例子,讀sd卡和寫sd卡,這兩個權限通常都是成對聲明和使用的,因此,它們被分爲一組,而且,只要我們獲取了這個權限組裏面的任意一個權限,就可以獲取整個權限組的權限。Google對於危險權限的定義和分組見下圖。

權限相關API說明

首先,在動態權限申請的流程中,開發者主要關注流程和API如下:

1、檢查權限是否授予。

//Activity.java

public int checkSelfPermission(permission)

2、申請權限。

//Activity.java

public final void requestPermissions( new String[permission1,permission2,...], requestCode)

這個時候,會彈出系統授權彈窗(授權彈窗是不支持自定義的,原因理所當然)。

3、權限回調。

用戶在系統彈窗裏面選擇後,結果會通過ActivityonRequestPermissionsResult方法回調APP。

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
    //繼續執行邏輯或者提示權限獲取失敗
}

4、權限說明。

用戶如果選擇了拒絕,下一次在需要聲明該權限的時候,Google建議APP開發者給予用戶更多的說明,因此提供了下面這個API,這個方法返回值在使用過程中會發現有點糾結(具體解析見下面代碼塊說明)

public boolean shouldShowRequestPermissionRationale(permission)
{
    1、APP沒有申請這個權限的話,返回false
    2、用戶拒絕時,勾選了不再提示的話,返回false
    3、用戶拒絕,但是沒有勾選不再提示的話,返回true
    因此如果想在第一次就給用戶提示,需要記錄權限是否申請過,沒有申請過的話,強制彈窗提示,而不能根據這個方法的返回值來。
}

三、QQ音樂的權限適配經驗

1、不同權限,申請的時機不同

QQ音樂作爲一個比較複雜的流媒體應用,也需要不少權限,但是究竟在什麼時候來申請這些權限就成了適配6.0時首當其衝問題。針對這個問題,我們也對需要的權限進行了思考,大致認爲申請權限需要分爲兩個時機。

用戶觸發:這個很好理解,有些和特性相關的權限,比如說聽歌識曲的錄音權限、自建歌單封面拍照權限等,這類權限平時APP運行時並不需要,那麼我們選擇在用戶觸發或者進入該功能的時候,進行授權受阻邏輯。

應用啓動時:我們在梳理的時候發現,有些權限(讀取設備信息,讀寫sd卡等)並不是由用戶或者特性觸發的,而是網絡免流,登錄安全,日誌系統這些底層邏輯無時不刻觸發的。對於這些權限,就比較糾結了。不過回過頭來看,這些權限通常是開發者或者APP不能妥協的權限,因爲如果用戶不授權的話,將會影響整個APP的功能和數據。所以,我們選擇比較暴力的方式,在應用啓動的時候,就受阻。這也是Google建議的一種方式。

但是需要注意的是,一開始就申請授權也不要冷冰冰地直接拉起系統彈窗授權,建議先用APP自己的彈窗向用戶禮貌地說明爲什麼需要這幾個權限,比如,讀取不到設備信息無法聯通免流,無法保證登錄安全,讀取不到SD卡無法播放歌曲等,避免太生硬引起用戶的反感。特別是,因爲本地化翻譯的原因,Google對於權限的彈窗說明很不local,例如我們申請讀取設備信息的權限時,系統的彈窗是“電話權限”,這裏很容易引起用戶的誤解,所以, 合理的引導和解釋是必不可少的

2、應用啓動授權,需要一個殼

剛剛已經說到了,很多隱形的權限和特性無關。那麼,如果我們直接啓動APP,用戶又還沒有授權的情況下,很多初始化邏輯很容易就因爲沒有權限crash了,即使沒有crash,後面也可能會有或多或少其他的問題。因此,我們需要在這些權限完全授予前,禁止這些邏輯的執行。

做過啓動相關的同學都知道,攔截一個APP正常的啓動後面再恢復,是很複雜的一件事情,往往我們需要一個外殼來把業務邏輯的內殼隔絕開。就QQ音樂而言,我們很容易的就想到了dex加載的殼,需求也很類似,dex加載也需要優先於業務來做。順着這個思路,很自然地,我們就選擇了在dex的殼裏面做權限的受阻邏輯,而且也很快很好的達到了預期的效果。相信現在大部分APP都是分dex的了,因此建議按照這個方式來做,可以節省很多的工作量。

四、Android權限機制“亂象”

這裏要說的亂象,其實是和Android嚴重的碎片化有一定的關係。隨着國產ROM越來越個性,很多ROM在嘗試建立自己的權限機制,有些甚至基於Android5.x就開放了原生的或者開發了自己的權限機制。而面對這些情況,我們往往能做的非常有限,舉幾個例子。

1、讀取運動數據權限

開發QQ音樂跑步電臺的過程中發現,在某國產ROM的一些機型上會提示“應用讀取運動數據權限”的系統彈窗。可是,反覆查閱相關API發現,我們使用的計步相關的Sensor並不需要申請什麼權限。可如果用戶選擇了拒絕,即使APP註冊了 Sensor,也收不到系統的回調。後來聯繫該廠商的相關人員後,給出的答覆是,第三方APP無法檢查和申請這個權限,這個權限本身也屬於該廠商ROM自己的權限機制。

類似的案例還有一個,就是在某廠商的手機管家,會一直提示QQ音樂嘗試讀取應用程序列表。其實,我們並沒有讀取應用程序列表,只是調用了PackageManager相關的一些API,就是觸發這個告警。

對於這類問題,我們懷疑,第三方ROM是在運行時檢測到了APP調用了相關的API後,進行權限阻斷。這裏開發同學需要注意的是,被阻斷的API不一定會導致crash,但是可能導致我們獲取不到正確的返回值或者收不到系統的一些消息回調。

2、無法添加快捷方式

本來<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>聲明後,我們就可以在桌面上創建快捷方式了,而且這個權限也不是危險權限。可是某些國產ROM,對於APP添加快捷方式限制的比較嚴,必須要用戶在設置裏面手動允許添加快捷方式後,APP才能最終成功的添加。這種情況,APP也不能知道是否能添加快捷方式,只能默默的添加失敗了。不過好在這裏受影響並不是主快捷方式,而且某些功能的快捷方式入口。

3、消失的桌面歌詞,懸浮窗權限

QQ音樂桌面歌詞采用了向WindowManager裏面添加 View的方式實現。可是很多國產ROM很早就具備了懸浮窗權限。一開始,我們將type改爲 LayoutParams.TYPE_TOAST同時聲明<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>這個普通權限,躲避了大多數系統的問題。可是,2016年底,隨着某ROM系統的升級,這一招也沒用了,大批用戶反饋爆發。

我們繼續嘗試檢測懸浮窗權限,發現checkPermission("android.permission.SYSTEM_ALERT_WINDOW")返回的結果永遠是 true,因此這條路也走不通。

最終,經過各種查閱,發現這個懸浮窗權限並不在Android6.0標準的權限機制內,而是AppOpsManager裏面已經被隱藏了的一個開關位,對應於第24個開關。需要注意的是, AppOpsManager這個類很早就有了,但是很多ROM隱藏了checkOp的方法,好在最後發現通過反射仍舊可以調用這個方法檢測權限是否打開。

AppOpsManager manager = (AppOpsManager) context.getSystemService("appops");
try {
    Object object = invokeMethod(manager, "checkOp", op, Binder.getCallingUid(), getPackageName(context));
    return AppOpsManager.MODE_ALLOWED == (Integer) object;
} catch (Exception e) {
    MLog.e(TAG, "CheckPermission " + e.toString());
}

不過,要打開懸浮窗權限,不同ROM的路徑還不一樣,有的是在設置裏面,有的是在系統自帶的管家裏面,最後我們只能根據不同的ROM,給予用戶不同的引導,終於將反饋量降了下去。

發佈了20 篇原創文章 · 獲贊 24 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章