android M權限問題

Android6.0帶來了新的權限管理方式,本文主要來源於官方文檔,加入了自己的理解,目的是想總結Android6.0權限管理的新方式,其他部分可能主要是總結式的帶過,後續再詳細分析。

一.Security Architecture(安全體系結構)

Android安全體系結構的核心是:

默認情況下沒有任何應用有權限去執行對其他應用、操作系統、用戶有不利影響的操作。這是一個核心的設計理念。記住這句話對後面的權限管理可以很好的理解。

正式由於這樣的設計理念,默認情況下應用不能去讀寫用戶的私有數據(比如Email和Contacts),不能去讀寫其他App的文件,不能執行網絡訪問,不能保持設備始終喚醒等等。

因爲每一個Android 應用都是在一個進程沙盒中運行的,應用必須明確分享的資源和數據,通過申明需要的額外權限這種方式(這些額外權限不由基本沙河提供)。應用靜態的聲明這些權限(在Manifest裏面),然後Android系統會請求用戶同意這些權限。

Android應用沙盒不依賴於創建應用的技術(這句話不是很懂,懂的大神可以指一下),特別的是Dalvik虛擬機並不是安全邊界的,所有的應用都可以運行native code(比如參見NDK),所有類型的應用-Java,native,hybird,都是以同樣的方式封裝在沙盒內並且相互之間是同樣的安全等級。

二.Application signing(應用簽名)

所有的Apk文件都必須由他的開發者使用私有的簽名證書籤名,這個證書是開發者身份的唯一標識,這個證書是由開發者自己生成的開放式的證書,用於自己簽名應用。證書的目的是標識應用的身份,這樣可以讓系統知道是該允許還是拒絕應用訪問簽名級別的權限(signature-level permissions),以及允許還是拒絕應用所請求的給予相同Linux身份來作爲不同的應用。

三.User IDs and File Access(用戶ID和文件訪問)

在安裝一個app package的時候,android系統會給每一個package一個獨立的Linux user ID。這個User ID在這個應用在當前設備的生命週期內都是固定不變的,在不同的設備,相同的package的用戶ID可能各不相同,但可以確定的是在一臺設備上一個package的用戶ID是固定不變的。

因爲安全執行是發生在進程層面的,兩個不同的package不能運行在相同的進程中,他們會被作爲不同的Linux用戶來運行。

但是你可以在manifest中使用sharedUserId屬性來指定不同的package有相同的User ID,這樣這兩個不同的package將會被視爲相同的APP,會有相同的User ID和文件權限。

當然爲了保證安全,只有兩個APP簽名一致且申明瞭相同的sharedUserId纔會被給予相同的User ID。

四.Using Permission(使用權限)

一個新建的Android應用默認是沒有權限的,這意味着它不能執行任何可能對用戶體驗有不利影響的操作或者訪問設備數據。爲了使用受保護的功能,你必須包含一個或者多個標籤在你的app manifest中。

Android 6.0中權限分爲兩種,普通權限和危險權限(即運行時權限,下面統稱運行時權限)。

4.1普通權限

如果你的應用manifest中只申明瞭普通權限(也就是說,這些權限對於用戶隱私和設備操作不會造成太多危險),系統會自動授予這些權限。
4.2運行時權限

如果你的應用manifest中聲明瞭運行時權限(也就是說,這些權限可能會影響用戶隱私和設備的普通操作),系統會明確的讓用戶決定是否授予這些權限。系統請求用戶授予這些權限的方式是由當前應用運行的系統版本來決定的。
4.2.1 Android6.0及以上的系統

如果你的設備運行的是Android6.0(API level 23)及以上的系統,並且你的應用的targetSdkVersion也是23或者更高,那麼應用向用戶請求這些權限是實時的。這意味着用戶可以隨時取消這些運行時權限的授權。所以應用在每次需要用到這些運行時權限的時候都需要去檢查是否還有這些權限的授權。具體請求方式後面會講到。
4.2.2 Android 5.1及以下的系統

如果你的設備運行在Android5.1(API level 22)及以下的系統中,或者你的app的targetSdkVersion是22或者更低。系統會請求用戶在apk安裝的時候授予這些權限。

如果你的應用更新的時候添加了一個權限,系統會在用戶更新應用的時候請求用戶授予這個權限,一旦用戶安裝了這個應用,唯一可以取消授權的方式就是卸載掉這個應用。注意這句話的意思,想一下如果app的targetSdkVersion是22或者以下,但是運行在Android6.0及以上的設備中會有什麼問題?後面會分析這個問題。

常常來說一個授權失敗會拋出SecurityException,然而這並不是在所有情況下都會發生。比如,發送一個廣播去檢查授權(SendBroadcast(Intent)),數據會被髮送給所有接收者,但是當這個方法的請求返回的時候,你不會收到任何一個因爲授權失敗拋出的異常,其實在大多數情況下,授權失敗只會打印系統日誌。

任何應用也可以定義和執行他們自己的權限。

4.3自動權限調整

簡單的說,如果你的app targetSdkVersion是3,而你當前運行的系統版本是4,那麼在android version 4 中新添加的權限會自動添加到你的app中。

比如WRITE_EXTERNAL_STORAGE權限是在api 4的時候添加的,而你的應用的targetSdkVersion是3,那麼這個權限會自動添加到你的應用中。而且在官方商店上這個權限也會列出來(儘管可能你並不需要這個權限)。

所以建議經常更新你的targetSdkVersion到最新版本。

下面來回答上面的那個問題,如果app的targetSdkVersion是22或者以下,但是運行在android 6.0或以上版本的手機中,會發生什麼?

安裝過程中,會一起請求用戶授予所有權限,如果用戶拒絕,將不能安裝這個app,只有用戶全部同意這些授權,才能安裝這個應用,但是問題來了,安裝好了這個應用之後,android6.0以上的系統中,用戶是可以去設置中取消授權的,而且是隨時都可以取消,所以很多運行時權限可能也得不到,目前官方的做法是,如果用戶取消該項授權,那麼依賴該項授權的方法的返回值爲null,所以你的app可能會報空指針異常。以後是否會針對22以下的app做改變還不得而知,畢竟crash是很難讓人接受的,但是crash是由用戶造成的,用戶應該也可以理解。

五.普通權限和運行時權限

系統權限會被傳遞給兩種不同的保護級別,我們所知道這兩種最重要的保護級別就是普通權限和運行時權限。

5.1普通權限

普通權限的覆蓋區域是在你的app需要訪問沙盒以外的數據和資源的時候,但是對用戶隱私和其他app的操作只有很少的影響,比如開啓手電筒的權限。這個時候,系統會自動授權這些普通權限。

5.2運行時權限

運行時權限的覆蓋區域是你的app想要的數據和資源涉及用戶的隱私信息,或者是可能潛在的影響用戶的存儲數據或者其他app的操作。比如,請求獲取用戶聯繫人信息的權限。如果一個app申明瞭運行時權限,用戶必須明確的授權這些權限給app。

5.3權限組
所有的運行時權限都屬於對應的權限組,如果你的app運行在android6.0及以上系統,下面的規則都適用:
5.3.1

如果一個app在manifest中請求了一個運行時權限,而且app還沒有得到這個運行時權限所在的權限組中的任何一個運行時權限授權,那麼系統會彈出一個對話框,描述app想要訪問的運行時權限的權限組,這個對話框不會描述這個權限組中某一個特定的權限。

比如,你的app想要請求READ_CONTACT權限,對話框只會描述app想要請求設備的聯繫人,如果用戶授權通過,系統會授予app所請求的該項權限。
5.3.2

如果一個app在manifest中請求一個運行時權限,並且這個app已經在相同的權限組中有了另一個運行時權限的授權,那麼系統不會彈出對話框,而是會立即授予app該項運行時權限的授權。

比如,一個app之前請求過一個READ_CONTACT的權限並且被授權通過,之後又請求一個WRITE_CONTACT(在同一個權限組)權限,那麼系統會自動授予該權限。

其實所有權限(普通權限、運行時權限、用戶自定義權限)都屬於特定的權限組,但是隻有運行時權限的權限組纔會影響用戶體驗。

5.3.3運行時權限和權限組列表
Permission Group Permissions
CALENDAR

READ_CALENDAR

WRITE_CALENDAR

CAMERA

CAMERA

CONTACTS

READ_CONTACTS

WRITE_CONTACTS

GET_ACCOUNTS

LOCATION

ACCESS_FINE_LOCATION

ACCESS_COARSE_LOCATION

MICROPHONE

RECORD_AUDIO

PHONE

READ_PHONE_STATE

CALL_PHONE

READ_CALL_LOG

WRITE_CALL_LOG

ADD_VOICEMAIL

USE_SIP

PROCESS_OUTGOING_CALLS

SENSORS

BODY_SENSORS

SMS

SEND_SMS

RECEIVE_SMS

READ_SMS

RECEIVE_WAP_PUSH

RECEIVE_MMS

STORAGE

READ_EXTERNAL_STORAGE

WRITE_EXTERNAL_STORAGE

有兩個權限比較特殊,分別是SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS

都屬於比較敏感的權限,很多時候都永不到,如果需要這個權限,應該首先聲明,然後發送Intent去請求用戶授權,然後系統會顯示詳細的管理界面。

5.3.4運行時權限的申請

第一.判斷

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

第二.如果是android6.0以上的系統,則檢查是否獲取授權

int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);

如果返回值爲PackageManager.PERMISSION_GRANTED,則可以繼續之後的操作,如果返回值爲PERMISSION_DENIED,則代表沒有授權該權限。

第三.shouldShowRequestPermissionRationale()可以得到是否需要彈出一個解釋申請該權限的提示給用戶,如果爲true,則可以彈。

第四.請求該權限

示例如下:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
        Manifest.permission.READ_CONTACTS)) {

    // Show an expanation to the user *asynchronously* -- don't block
    // this thread waiting for the user's response! After the user
    // sees the explanation, try again to request the permission.

} else {

    // No explanation needed, we can request the permission.

    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.READ_CONTACTS},
            MY_PERMISSIONS_REQUEST_READ_CONTACTS);

    // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
    // app-defined int constant. The callback method gets the
    // result of the request.
}

} else {
執行獲取權限後的操作
}

第五.請求權限之後,在onRequestPermissionsResult()返回值中可以得到用戶是否授權,如果授權,就可以操作該運行時權限對應的方法

@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // permission was granted, yay! Do the
            // contacts-related task you need to do.

        } else {

            // permission denied, boo! Disable the
            // functionality that depends on this permission.
        }
        return;
    }

    // other 'case' lines to check for other
    // permissions this app might request
}

}

運行時權限的特點是,實時性,用戶可以隨時取消授權,所以每次調用運行時權限的方法都需要判斷或者請求一次運行時權限。

在執行運行時權限申請的同時想一下是否真的有必要,想一下使用Intent的方式啓動其他應用是否可以達到需求,比如ACTION_IMAGE_CAPTURE,是直接申明CAMERA的權限自己做一個照相機還是發送ACTION_IMAGE_CAPTURE請求讓別的應用處理並在onActivityResult()返回值更方便

如果設備運行在5.1或者以下的設備,或者targetSdkVersion在22或以下,系統會在安裝app的時候讓用戶授權權限。再說一遍,系統只會提示用戶app需要的權限組,而不會提示某一個特定的權限。

六.自定義權限

爲了執行自定義權限,你必須在你的manifest中聲明一個或多個標籤。

比如:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.me.app.myapp" >
    <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
        android:label="@string/permlab_deadlyActivity"
        android:description="@string/permdesc_deadlyActivity"
        android:permissionGroup="android.permission-group.COST_MONEY"
        android:protectionLevel="dangerous" />
    ...
</manifest>


<protectionLevel>屬性是必須的,告訴系統當app申請該權限的時候,要怎樣通知用戶。

<permissionGroup>屬性是可選的,可以幫助系統顯示自定義屬性屬於哪個權限組,當通知用戶彈出框的時候,當然你可以選擇某一個自定義權限屬於已知的權限組,也可以屬於某一個自定義權限組,建議屬於已知的權限組。

<android:label>相當於權限組的提示,要簡短

<android:description>是某一個特定權限的描述,規則是兩句話,第一句描述,第二句警告用戶如果授權會發生什麼後果。

比如,CALL_PHONE權限

<string name="permlab_callPhone">directly call phone numbers</string>
    <string name="permdesc_callPhone">Allows the application to call
        phone numbers without your intervention. Malicious applications may
        cause unexpected calls on your phone bill. Note that this does not
        allow the application to call emergency numbers.</string>

之後,你會在setting—>Application中看到該自定義權限。

一個例子:

private boolean mayRequestContacts() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }
        if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
            return true;
        }
        if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
            Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
                    .setAction(android.R.string.ok, new View.OnClickListener() {
                        @Override
                        @TargetApi(Build.VERSION_CODES.M)
                        public void onClick(View v) {
                            requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                        }
                    });
        } else {
            requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
        }
        return false;
    }

/**
     * Callback received when a permissions request has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == REQUEST_READ_CONTACTS) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                populateAutoComplete();
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章