天天看點

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

運作時權限(Runtime Permission)是Android 6.0( 代号為 Marshmallow,API版本為 23)及以上版本新增的功能,相比于以往版本,這是一個較大變化。本文将介紹如何在代碼中加入并配置運作時權限功能。

如需閱讀英文原文,請您點選這個連結:《Everything every Android Developer must know about new Android’s Runtime Permission》。

如需閱讀官方運作時權限的相關介紹,請您點選這個連結:《Working with System Permissions》

運作時權限介紹

一直以來,為了保證最大的安全性,安裝Android應用時,系統總是讓使用者選擇是否同意該應用所需的所有權限。一旦安裝應用,就意味着該應用所需的所有權限均已獲得。若在使用某個功能時用到了某個權限,系統将不會提醒使用者該權限正在被擷取(比如微信需要使用攝像頭拍照,在Android 6.0以前的裝置上,使用者将不會被系統告知正在使用“使用系統攝像頭”的權限)。

這在安全性上是個隐患:在不經使用者同意的情況下,一些應用在背景可以自由地收集使用者隐私資訊而不被使用者察覺。

從Android 6.0版本開始,這個隐患終于被消除了:在安裝應用時,該應用無法取得任何權限!相反,在使用應用的過程中,若某個功能需要擷取某個權限,系統會彈出一個對話框,顯式地由使用者決定是否将該權限賦予應用,隻有得到了使用者的許可,該功能才可以被使用。

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

需要注意的是,在上述的右圖中,對話框并不會自動彈出,而需要由開發者手動調用。若程式調用的某個方法需要使用者賦予相應權限,而此時該權限并未被賦予時,那麼程式就會抛出異常并崩潰(Crash),如下圖所示。

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

除此之外,使用者還可以在任何時候撤銷賦予過的權限。

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

運作時權限無疑提升了安全性,有效地保護了使用者的隐私,這對于使用者來說确實是個好消息,但對于開發者來說簡直就是噩夢:因為這需要開發者在調用方法時,檢查該方法使用了什麼系統權限——這仿佛颠覆了傳統的程式設計的邏輯——開發者編寫每一句代碼時都得小心翼翼,否則應用可能随時崩潰。

在程式中,設定目标SDK版本(targetSDKVersion)為23及以上時(這意味着程式可以在Android 6.0及以上的版本中運作),将應用安裝在Android 6.0及以上機型中,運作時權限功能才能生效;若将其安裝在Android 6.0以前的機型中,權限檢查仍将僅僅發生在安裝應用時。

運作時權限與各版本間的相容性問題

假如将一個早期版本的應用安裝在Android 6.0版本的機型上,應用是不會崩潰的,因為這隻有兩種情況:1)該應用的targetSDKVersion < 23,在這種情況下,權限檢查仍是早期的形式(僅在安裝時賦予權限,使用時将不被提醒);2)該應用的targetSDKVersion ≥ 23時,則将使用新的運作時權限規則。

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

是以,這個早期版本的應用将運作如常。不過,将該應用安裝在Android 6.0上,且targetSDKVersion ≥ 23時,使用者仍然可以随時手動撤銷權限,當然這種做法不被官方推薦。

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

不被推薦的原因是,這種做法容易導緻應用崩潰。若targetSDKVersion < 23,當然不會出問題;若早期應用的targetSDKVersion ≥ 23,在使用應用時手動撤消了某個權限,那麼程式在調用了需要這個權限才能執行的方法時,應用什麼也不做,若該方法還有傳回值,那麼會根據實際情況傳回 0 或者 null。如下圖所示。

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

若上述調用的方法沒有崩潰,那麼這個方法被其他方法調用時也會因為傳回值是 0 或者 null 而崩潰。

不過好消息是,使用者幾乎不會手動撤銷已經賦予給應用的權限。

說了這麼多,在避免應用崩潰的前提下,适配新的運作時權限功能才是王道:對于那些在代碼中并未支援運作時權限的應用,請将targetSDKVersion設定為 < 23,否則應用有崩潰隐患;若代碼中支援了運作時權限,再将targetSDKVersion設定為 ≥ 23。

請注意:在Android Studio中建立Project時,會自動賦予targetSDKVersion為最新版本,若您的應用還暫時無法完全支援運作時權限功能,建議首先将targetSDKVersion手動設定為22。

自動賦予應用的權限

以下羅列了在安裝應用時,自動賦予應用的權限,這些權限無法在安裝後手動撤銷。我們稱其為基本權限(Normal Permission):

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
           

開發者僅需要在

AndroidManifest.xml

中聲明這些權限,應用就能自動擷取無需使用者授權。

為應用适配新的運作時權限

為了設配新的運作時權限,首先需要将

compileSdkVersion

targetSdkVersion

設定為23:

android {
    compileSdkVersion 
    ...

    defaultConfig {
        ...
        targetSdkVersion 
        ...
    }
           

下面示範了一個增加聯系人的方法,該方法是需使用

WRITE_CONTACTS

的權限:

private static final String TAG = "Contacts";
private void insertDummyContact() {
    // Two operations are needed to insert a new contact.
    ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

    // 、設定一個新的聯系人
    ContentProviderOperation.Builder op =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
    operations.add(op.build());

    // 、為聯系人設定姓名
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, )
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                    "__DUMMY CONTACT from runtime permissions sample");
    operations.add(op.build());

    // 、使用ContentResolver添加該聯系人
    ContentResolver resolver = getContentResolver();
    try {
        resolver.applyBatch(ContactsContract.AUTHORITY, operations);
    } catch (RemoteException e) {
        Log.d(TAG, "Could not add a new contact: " + e.getMessage());
    } catch (OperationApplicationException e) {
        Log.d(TAG, "Could not add a new contact: " + e.getMessage());
    }
}
           

調用這個方法需要配置

WRITE_CONTACTS

權限,否則應用将崩潰:在

AndroidManifest.xml

中配置如下權限:

接着,我們需要建立一個方法用于判斷

WRITE_CONTACTS

權限是否确實被賦予;若方法為建立,那麼可以彈出一個對話框向使用者申請該權限。待權限被賦予後,方可建立聯系人。

權限被歸類成權限組(Permission Group),如下表所示:

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

若應用被賦予了某個權限組中的一個權限(比如

READ_CONTACTS

權限被賦予),那麼該組中的其他權限将被自動擷取(

WRITE_CONTACTS

GET_ACCOUNTS

權限被自動擷取)。

檢查和申請權限的方法分别是

Activity.checkSelfPermission()

Activity.requestPermissions

,這兩個方法是在 API 23 中新增的。

final private int REQUEST_CODE_ASK_PERMISSIONS = ;

private void insertDummyContactWrapper() {
    //檢查AndroidManiFest中是否配置了WRITE_CONTACTS權限
    int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
    //若未配置該權限
    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
        //申請配置該權限
        requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                REQUEST_CODE_ASK_PERMISSIONS);
        //直接傳回,不執行insertDummyContact()方法
        return;
    }
    //若配置了該權限,才能調用方法
    insertDummyContact();
}
           

若程式賦予了權限,

insertDummyContact()

方法将被調用;否則,

requestPermissions()

方法将彈出一個對話框申請權限,如下所示:

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

無論您選擇的是“DENY”還是“ALLOW”,程式都将回調

Activity.onRequestPermissionsResult()

方法,并将選擇的結果傳到方法的第三個參數中:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE_ASK_PERMISSIONS:
            if (grantResults[] == PackageManager.PERMISSION_GRANTED) {
                // 使用者選擇了“ALLOW”,擷取權限,調用方法
                insertDummyContact();
            } else {
                // 使用者選擇了“DENY”,未擷取權限
                Toast.makeText(MainActivity.this, "WRITE_CONTACTS Denied", Toast.LENGTH_SHORT)
                        .show();
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}
           

這就是Android 6.0的全新運作時權限機制,為了提高安全性,增加代碼量在所難免:為了比對運作時權限機制,必須把處理方法的所有情況考慮在内。

處理 “不再詢問”(“Never Ask Again”)

每當系統申請權限時,彈出的對話框會有一個“不再詢問”(“Never Ask Again”)的勾選項。

若使用者打了勾,并選擇拒絕(“DENY”),那麼下次程式調用

Activity。requestPermissions()

方法時,将不會彈出對話框,權限也不會被賦予。

這種沒有回報的互動并不是一個好的使用者體驗(User Experience)。是以,下次啟動時,程式應彈出一個對話框,提示使用者“您已經拒絕了使用該功能所需要的權限,若需要使用該功能,請手動開啟權限”,應調用

Activity.shouldShowRequestPermissionRationale()

方法:

final private int REQUEST_CODE_ASK_PERMISSIONS = ;

private void insertDummyContactWrapper() {
    int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
                showMessageOKCancel("You need to allow access to Contacts",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                                        REQUEST_CODE_ASK_PERMISSIONS);
                            }
                        });
                return;
            }
        requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                REQUEST_CODE_ASK_PERMISSIONS);
        return;
    }
    insertDummyContact();
}

private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
    new AlertDialog.Builder(MainActivity.this)
            .setMessage(message)
            .setPositiveButton("OK", okListener)
            .setNegativeButton("Cancel", null)
            .create()
            .show();
}
           

效果如下:

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

上述對話框應在兩種情形下彈出:

1)應用第一次申請權限時;

2)使用者勾選了“不再詢問”複選框。

對于第二種情況,

Activity.onRequestPermissionsResult()

方法将被回調,并回傳參數

PERMISSION_DENIED

,該對話框将不再彈出。

一次性申請多個權限

有些功能需要申請多個權限,仍然可以像上述方式一樣編寫代碼:

final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = ;

private void insertDummyContactWrapper() {
    //提示使用者需要手動開啟的權限集合
    List<String> permissionsNeeded = new ArrayList<String>();

    //功能所需權限的集合
    final List<String> permissionsList = new ArrayList<String>();
    //若使用者拒絕了該權限申請,則将該申請的提示添加到“使用者需要手動開啟的權限集合”中
    if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
        permissionsNeeded.add("GPS");
    if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS))
        permissionsNeeded.add("Read Contacts");
    if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS))
        permissionsNeeded.add("Write Contacts");

    //若在AndroidManiFest中配置了所有所需權限,則讓使用者逐一賦予應用權限,若權限都被賦予,則執行方法并傳回
    if (permissionsList.size() > ) {
        //若使用者賦予了一部分權限,則需要提示使用者開啟其餘權限并傳回,該功能将無法執行
        if (permissionsNeeded.size() > ) {
            // Need Rationale
            String message = "You need to grant access to " + permissionsNeeded.get();
            for (int i = ; i < permissionsNeeded.size(); i++)
                message = message + ", " + permissionsNeeded.get(i);
            //彈出對話框,提示使用者需要手動開啟的權限
            showMessageOKCancel(message,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                    REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                        }
                    });
            return;
        }
        requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
        return;
    }

    insertDummyContact();
}
//判斷使用者是否授予了所需權限 
private boolean addPermission(List<String> permissionsList, String permission) {
    //若配置了該權限,傳回true
    if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
        //若未配置該權限,将其添加到所需權限的集合,傳回true
        permissionsList.add(permission);
        // 若使用者勾選了“永不詢問”複選框,并拒絕了權限,則傳回false
        if (!shouldShowRequestPermissionRationale(permission))
            return false;
    }
    return true;
}
           

當使用者設定了每個權限是否可被賦予後,Activity.onRequestPermissionsResult()方法被回調,并傳入第三個參數:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
            {
            //初始化Map集合,其中Key存放所需權限,Value存放該權限是否被賦予
            Map<String, Integer> perms = new HashMap<String, Integer>();
            // 向Map集合中加入元素,初始時所有權限均設定為被賦予(PackageManager.PERMISSION_GRANTED)
            perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
            perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
            perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
            // 将第二個參數回傳的所需權限及第三個參數回傳的權限結果放入Map集合中,由于Map集合要求Key值不能重複,是以實際的權限結果将覆寫初始值
            for (int i = ; i < permissions.length; i++)
                perms.put(permissions[i], grantResults[i]);
            // 若所有權限均被賦予,則執行方法
            if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
                    && perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
                    && perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
                // All Permissions Granted
                insertDummyContact();
            } 
            //否則彈出toast,告知使用者需手動賦予權限
            else {
                // Permission Denied
                Toast.makeText(MainActivity.this, "Some Permission is Denied", Toast.LENGTH_SHORT)
                        .show();
            }
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}
           

使用支援庫(Support Library)提高程式的相容性

盡管上述代碼在Android 6.0版本的裝置上能夠正常運作,但運作在早前版本的裝置上,程式将崩潰。

簡單直接的方式是事先進行版本判斷:

if (Build.VERSION.SDK_INT >= ) {
    // Marshmallow+
} else {
    // Pre-Marshmallow
}
           

但這樣會使程式變得臃腫。

比較好的解決方式是使用

Support Library v4

支援庫中的方法替換原來的方法,這将省去為不同版本的裝置分别提供代碼的麻煩:

// 将Activity.checkSelfPermission()方法替換為如下方法
ContextCompat.checkSelfPermission()
           
// 将Activity.requestPermissions()方法替換為如下方法
ActivityCompat.requestPermissions()
           
//将Activity.shouldShowRequestPermissionRationale()方法替換為如下方法,在早期版本中,該方法直接傳回false
ActivityCompat.shouldShowRequestPermissionRationale() 
           

無論哪個版本,調用上面的三個方法都需要Content或Activity參數。

以下是使用

Support Library v4

支援庫中的方法替換原代碼中相應方法後的程式:

private void insertDummyContactWrapper() {
    int hasWriteContactsPermission = ContextCompat.checkSelfPermission(MainActivity.this,
            Manifest.permission.WRITE_CONTACTS);
    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
        if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_CONTACTS)) {
            showMessageOKCancel("You need to allow access to Contacts",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            ActivityCompat.requestPermissions(MainActivity.this,
                                    new String[] {Manifest.permission.WRITE_CONTACTS},
                                    REQUEST_CODE_ASK_PERMISSIONS);
                        }
                    });
            return;
        }
        ActivityCompat.requestPermissions(MainActivity.this,
                new String[] {Manifest.permission.WRITE_CONTACTS},
                REQUEST_CODE_ASK_PERMISSIONS);
        return;
    }
    insertDummyContact();
}
           

需要注意的是,若程式中用到了Fragment,也最好使用android.support.v4.app.Fragment,這樣可以相容更低的版本,使應用适配更多裝置。

使用第三方開源庫(3rd Party Library)簡化代碼

為了是代碼更加簡潔,推薦一個第三方架構。該架構可以友善地內建運作時權限機制并有效相容新舊版本。

在應用打開時撤銷權限所帶來的問題

如上所述,使用者可以随時撤銷賦予應用的權限,若某個應用正在運作時,使用者撤消了其某些權限,應用所在程序會立刻終止(application’s process is suddenly terminated),是以盡量不要在應用運作時,改變其權限規則。

總結與建議

總結:

運作時權限機制大大提高了應用的安全性,不過開發者需要為此修改代碼以比對新的版本,不過好消息是,大部分常用的權限都被自動賦予了,是以,隻有很小一部分代碼需要修改。

建議:

  1. 使用運作時機制時應該以版本的相容作為前提。
  2. 不要将未适配運作時機制的程式的targetSdkVersion設定為 23 及以上。

感謝

特别感謝原創作者的付出,下面是作者的介紹資訊:

Android 6.0及以上版本的運作時權限介紹運作時權限介紹運作時權限與各版本間的相容性問題自動賦予應用的權限為應用适配新的運作時權限處理 “不再詢問”(“Never Ask Again”)一次性申請多個權限使用支援庫(Support Library)提高程式的相容性使用第三方開源庫(3rd Party Library)簡化代碼在應用打開時撤銷權限所帶來的問題總結與建議感謝

繼續閱讀