Permission 運行時權限 總結 翻譯 [MD]


博文地址

我的GitHub 我的博客 我的微信 我的郵箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

對運行時權限的一些理解

在舊的權限管理系統中,權限僅僅在App安裝時詢問用戶一次,用戶同意了這些權限App才能被安裝,App一旦安裝后就可以偷偷的做一些不為人知的事情了。

Android6.0開始,App可以直接安裝,App在運行時由開發者決定在任一適當的時機一個一個詢問用戶是否授予權限,系統會彈出一個對話框讓用戶選擇是否授權某個權限給App,這個Dialog是系統定義的,開發者不能定制,當App要求用戶授予不恰當的權限的時候,用戶可以拒絕(然而,很多APP可能會在請求權限失敗后直接退出,所以你往往無法拒絕),用戶即使授予或拒絕后也可以在設置頁面對每個App的權限進行重新管理。

新的權限策略將權限分為兩類,第一類是不涉及用戶隱私的,只需要在Manifest中聲明即可,比如網絡、藍牙、NFC等;第二類是涉及到用戶隱私信息的,需要用戶授權后才可使用,比如SD卡讀寫、聯系人、短信讀寫等。

不需要運行時申請的權限
此類權限都是正常保護的權限,只需要在AndroidManifest.xml中簡單聲明這些權限即可,安裝即授權,不需要每次使用時都檢查權限,而且用戶不能取消以上授權,除非用戶卸載App。

需要運行時申請的權限
所有危險的Android系統權限屬於權限組,如果APP運行在Android 6.0(API level 23)或者更高級別的設備中,並且targetSdkVersion>=23時,系統將會自動采用動態權限管理策略,如果你在涉及到特殊權限操作時沒有申請權限而直接調用了相關代碼,你的App可能就崩潰了。

綜上所述,你需要注意:

  • 此類權限也必須在Manifest中申明,否則申請時不提示用戶,直接回調開發者權限被拒絕。
  • 同一個權限組的任何一個權限被授權了,這個權限組的其他權限也自動被授權。例如一旦WRITE_CONTACTS被授權了,App也有READ_CONTACTS和GET_ACCOUNTS了。
  • 申請某一個權限的時候系統彈出的Dialog是對整個權限組的說明,而不是單個權限。例如我申請READ_EXTERNAL_STORAGE,系統會提示"允許xxx訪問設備上的照片、媒體內容和文件嗎?"。

其他情景:
1、targetSdkVersion小於等於22,但是設備系統版本大於等於6.0:

  • app使用舊的權限管理策略
  • 注冊文件列出的權限將會在安裝時要求用戶授予權限
  • 用戶可以在設置列表中編輯相關權限,這對app能否正常運行有很大影響

2、targetSdkVersion大於等於23,但是設備系統版本小於6.0:

  • app使用舊的權限管理策略
  • 注冊文件列出的權限將會在安裝時要求用戶授予權限

運行時權限使用案例

public class MainActivity extends ListActivity {
    
    private static final int REQUEST_CODE = 20094;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"完整的授權過程演示"};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, array));
        createFile();
    }
    
    private void createFile() {
        try {
            //如果將targetSdkVersion為22或以下,可以成功創建文件;如果改為23或以上,則失敗(會拋異常)
            File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".txt");
            Toast.makeText(this, "結果:" + file.createNewFile(), Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "異常了", Toast.LENGTH_SHORT).show();
        }
    }
    
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        requestPermissions();
    }
    
    private void requestPermissions() {
        String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
        int permissionState = ContextCompat.checkSelfPermission(this, permissions[0]); //檢查權限
        if (permissionState != PackageManager.PERMISSION_GRANTED) { //沒有權限,申請權限
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { //是否應該顯示請求權限的說明
                new AlertDialog.Builder(this)
                    .setTitle("通過彈一個自定義的對話框告訴用戶,我們為什么需要這個權限")
                    .setMessage("請求SD卡權限,作用是給你保存妹子圖片,點擊【好嘞】會重新嘗試請求用戶授權")
                    .setPositiveButton("好嘞", (dialog, which) -> ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE))
                    .create()
                    .show();
            } else {
                ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE); //請求用戶授權
            }
        } else {
            Toast.makeText(this, "有權限了", Toast.LENGTH_SHORT).show();
        }
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults.length > 0) {
            switch (requestCode) {
                case REQUEST_CODE: {
                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(this, "有權限了", Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(this, "你拒絕了權限,我們已經沒法愉快的玩耍了!", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        }
    }
}

開源庫:PermissionsDispatcher

PermissionsDispatcher provides a simple annotation-based API to handle runtime permissions.

This library lifts the burden 解除了負擔 that comes with writing a bunch of 一系列 check statements whether a permission has been granted or not from you, in order to keep your code clean and safe.

特性:

注解

@RuntimePermissions@NeedsPermission 是必須的,其余注解均為可選。

注意:被注解的方法不能是私有方法,因為這些方法會被自動生成的類調用。

Annotation Required Description
@RuntimePermissions Register an Activity or Fragment to handle permissions
@NeedsPermission Annotate a method which performs the action that requires one or more permissions 當用戶給予權限時會執行該方法
@OnShowRationale optional Annotate a method which explains why the permissions are needed.
@OnPermissionDenied optional Annotate a method which is invoked if the user doesn't grant the permissions
@OnNeverAskAgain optional Annotate a method which is invoked if the user chose to have the device "never ask again" about a permission

OnShowRationale 方法的細節
It passes in a PermissionRequest object which can be used to continue or abort the current permission request upon user input. If you don't specify any argument for the method compiler will generate process{方法名}ProcessRequest and cancel{方法名}ProcessRequest. You can use those methods in place of PermissionRequest(ex: with DialogFragment)

使用案例

使用步驟

1、聲明權限

<uses-permission android:name="android.permission.CAMERA" />

2、引入框架

//注意:要將targetSdkVersion設為23或以上,否則這個庫是無效的
//NOTE: 4.x only supports Jetpack. If you still use appcompat 3.x is the way to go.
implementation 'com.github.hotchemi:permissionsdispatcher:3.3.1'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'

3、添加注解

4、點擊 菜單欄 → Build → Make Project 重新編譯整個項目
編譯完成后,會在 app\build\intermediates\classes\debug 目錄下生成一個與被注解的Activity同一個包下的輔助類,名稱為被注解的Activity名稱+PermissionsDispatcher,例如MainActivityPermissionsDispatcher

測試代碼

@RuntimePermissions
public class MainActivity extends ListActivity {
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"開源庫 PermissionsDispatcher"};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, array));
    }
    
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        MainActivityPermissionsDispatcher.performActionWithCheck(this);
    }
    
    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void performAction() {
        Toast.makeText(this, "有權限了", Toast.LENGTH_SHORT).show();
    }
    
    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void doOnPermissionDenied() {
        Toast.makeText(this, "你拒絕了權限,我們已經沒法愉快的玩耍了!", Toast.LENGTH_SHORT).show();
    }
    
    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void doOnShowRationale(final PermissionRequest request) {
        new AlertDialog.Builder(this)
            .setTitle("通過彈一個自定義的對話框告訴用戶,我們為什么需要這個權限")
            .setMessage("請求SD卡權限,作用是給你保存妹子圖片,點擊【好嘞】會重新嘗試請求用戶授權")
            .setPositiveButton("我知道了,繼續吧", (dialog, which) -> request.proceed())
            .setNegativeButton("我不需要,不要再請求了", (dialog, which) -> request.cancel())
            .create()
            .show();
    }
    
    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void doOnNeverAskAgain() {
        Toast.makeText(this, "你拒絕了權限,並勾選了不再提醒,已經沒法愉快的玩耍了!", Toast.LENGTH_SHORT).show();
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }
}

自動生成的類

//final 類型的工具類
final class MainActivityPermissionsDispatcher {
    private static final int REQUEST_PERFORMACTION = 0; //請求碼
    private static final String[] PERMISSION_PERFORMACTION = new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}; //需要的權限
    
    private MainActivityPermissionsDispatcher() {} //私有構造方法
    
    //注解處理器會檢索所有帶 @NeedsPermission 的方法名,生成相應的名為【方法名+WithCheck】的工具方法
    //所有帶注解的方法都不能是私有方法,不然只能通過反射調用,而這個庫是100%不使用反射的
    static void performActionWithCheck(MainActivity target) {
        if (PermissionUtils.hasSelfPermissions(target, PERMISSION_PERFORMACTION)) { //檢查是否已經有權限
            target.performAction(); //有權限時執行
        } else {
            if (PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_PERFORMACTION)) {
                //判斷是否需要顯示提示,根據結果執行不同的回調
                target.doOnShowRationale(new PerformActionPermissionRequest(target));
            } else {
                //不需要顯示提示,所以開始通過系統API請求權限
                ActivityCompat.requestPermissions(target, PERMISSION_PERFORMACTION, REQUEST_PERFORMACTION);
            }
        }
    }
    
    static void onRequestPermissionsResult(MainActivity target, int requestCode, int[] grantResults) {
        switch (requestCode) { //處理授權結果
            case REQUEST_PERFORMACTION:
                if (PermissionUtils.verifyPermissions(grantResults)) { //授予了權限
                    target.performAction();
                } else { //拒絕了權限
                    if (!PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_PERFORMACTION)) {
                        target.doOnNeverAskAgain(); //拒絕了權限,並勾選了不再提醒
                    } else {
                        target.doOnPermissionDenied(); //用戶拒絕了權限
                    }
                }
                break;
            default:
                break;
        }
    }
    
    private static final class PerformActionPermissionRequest implements PermissionRequest {
        private final WeakReference<MainActivity> weakTarget; //弱引用,不影響GC
        
        private PerformActionPermissionRequest(MainActivity target) {
            this.weakTarget = new WeakReference<MainActivity>(target);
        }
        
        @Override
        public void proceed() {
            MainActivity target = weakTarget.get();
            if (target == null) return;
            //再次請求權限
            ActivityCompat.requestPermissions(target, PERMISSION_PERFORMACTION, REQUEST_PERFORMACTION);
        }
        
        @Override
        public void cancel() {
            MainActivity target = weakTarget.get();
            if (target == null) return;
            target.doOnPermissionDenied();//結束請求
        }
    }
}

官方文檔:請求權限

官方文檔:Request App Permissions

Every Android app runs in a limited-access sandbox. If an app needs to use resources or information outside of its own sandbox, the app has to request the appropriate 相應的 permission. You declare that your app needs a permission by listing the permission in the app manifest and then requesting that the user approve 批准 each permission at runtime (on Android 6.0 and higher).

This page describes how to use the Android Support Library to check for and request permissions. The Android framework provides similar methods as of Android 6.0 (API level 23), but using the support library makes it easier to provide compatibility with older versions of Android.

Add permissions to the manifest

On all versions of Android, to declare that your app needs a permission, put a <uses-permission> element in your app manifest, as a child of the top-level <manifest> element.

The system's behavior after you declare a permission depends on how sensitive 敏感程度 the permission is. Some permissions are considered "normal" so the system immediately grants them upon installation. Other permissions are considered "dangerous" so the user must explicitly 明確 grant your app access. For more information about the different kinds of permissions, see 保護級別.

Check for permissions

If your app needs a dangerous permission, you must check whether you have that permission every time you perform an operation that requires that permission. Beginning with Android 6.0 (API level 23), users can revoke 撤銷 permissions from any app at any time, even if the app targets a lower API level. So even if the app used the camera yesterday, it can't assume 確認 it still has that permission today.

To check if you have a permission, call the ContextCompat.checkSelfPermission() method. For example, this snippet shows how to check if the activity has permission to write to the calendar:

if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
    // Permission is not granted
}

If the app has the permission, the method returns PERMISSION_GRANTED, and the app can proceed 繼續 with the operation. If the app does not have the permission, the method returns PERMISSION_DENIED, and the app has to explicitly ask the user for permission.

Request permissions

When your app receives PERMISSION_DENIED from checkSelfPermission(), you need to prompt the user for that permission. Android provides several methods you can use to request a permission, such as requestPermissions(), as shown in the code snippet below. Calling these methods brings up 顯示 a standard Android dialog, which you cannot customize.

How this is displayed to the user depends on the device Android version as well as the target version of your application, as described in the 權限概覽.

Explain why the app needs permissions

In some circumstances, you want to help the user understand why your app needs a permission. For example, if a user launches a photography app, the user probably won't be surprised that the app asks for permission to use the camera, but the user might not understand why the app wants access to the user's location or contacts. Before your app requests a permission, you should consider providing an explanation to the user. Keep in mind that you don't want to overwhelm 不勝其煩 the user with explanations; if you provide too many explanations, the user might find the app frustrating 很麻煩 and remove it.

One approach 方法 you might use is to provide an explanation only if the user has already denied that permission request. Android provides a utility method, shouldShowRequestPermissionRationale(), that returns true if the user has previously 之前 denied the request, and returns false if a user has denied a permission and selected the Don't ask again option in the permission request dialog, or if a device policy 政策 prohibits 禁止 the permission.

If a user keeps trying to use functionality that requires a permission, but keeps denying the permission request, that probably means the user doesn't understand why the app needs the permission to provide that functionality. In a situation like that, it's probably a good idea to show an explanation 解釋.

More advice about how to create a good user experience when asking for permission is provided in 應用權限最佳做法.

Request the permissions you need

If your app doesn't already have the permission it needs, the app must call one of the requestPermissions() methods to request the appropriate 相應 permissions. Your app passes the permissions it wants and an integer request code that you specify 指定 to identify 識別 this permission request. This method functions asynchronously 異步. It returns right away, and after the user responds to the prompt 響應提示, the system calls the app's callback method with the results, passing the same request code that the app passed to requestPermissions().

The following code checks if the app has permission to read the user's contacts. If it does not have permission it checks if it should show an explanation for needing the permission, and if no explanation is needed, it requests the permission:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
    // Permission is not granted,Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
        // Show an explanation 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; request the permission
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE);
        // The callback method gets the result of the request.
    }
} else {
    // Permission has already been granted
}

The prompt 提示 shown by the system describes the 權限組 your app needs access to, not the specific permission.

Note: When your app calls requestPermissions(), the system shows a standard dialog box to the user. Your app cannot configure or alter that dialog box. If you need to provide any information or explanation to the user, you should do that before you call requestPermissions(), as described in Explain why the app needs permissions.

Handle the permissions request response

When the user responds to your app's permission request, the system invokes 調用 your app's onRequestPermissionsResult() method, passing it the user response. Your app has to override that method to find out whether the permission was granted. The callback is passed the same request code you passed to requestPermissions(). For example, if an app requests READ_CONTACTS access it might have the following callback method:

@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.
    }
}

The dialog box shown by the system describes the permission group your app needs access to; it does not list the specific permission. For example, if you request the READ_CONTACTS permission, the system dialog box just says your app needs access to the device's contacts. The user only needs to grant permission once for each permission group.

If your app requests any other permissions in that group (that are listed in your app manifest), the system automatically grants them. When you request the permission, the system calls your onRequestPermissionsResult() callback method and passes PERMISSION_GRANTED, the same way it would if the user had explicitly 明確 granted your request through the system dialog box.
如果您的應用請求該組中的其他任何權限(已在您的應用清單中列出),系統會自動授予這些權限。當您請求權限時,系統會調用您的 onRequestPermissionsResult() 回調方法並傳遞 PERMISSION_GRANTED,就像用戶通過系統對話框明確同意了您的請求時的處理方式一樣。

Note: Your app still needs to explicitly request every permission it needs, even if the user has already granted another permission in the same group. In addition 此外, the grouping of permissions into groups may change in future Android releases. Your code should not rely on the assumption 假設 that particular 特定 permissions are or are not in the same group.

For example, suppose you list both READ_CONTACTS and WRITE_CONTACTS in your app manifest. If you request READ_CONTACTS and the user grants the permission, and you then request WRITE_CONTACTS, the system immediately grants you that permission without interacting with 交互 the user.

If the user denies a permission request, your app should take appropriate action. For example, your app might show a dialog explaining why it could not perform the user's requested action that needs that permission.

When the system asks the user to grant a permission, the user has the option 選擇 of telling the system not to ask for that permission again. In that case, any time an app uses requestPermissions() to ask for that permission again, the system immediately denies the request. The system calls your onRequestPermissionsResult() callback method and passes PERMISSION_DENIED, the same way it would if the user had explicitly rejected your request again. The method also returns false if a device policy prohibits the app from having that permission. This means that when you call requestPermissions(), you cannot assume that any direct interaction with the user has taken place 發生.

To provide the best user experience when asking for app permissions, also see 應用權限最佳做法.

Declare permissions by API level

To declare a permission only on devices that support runtime permissions, that is, devices running Android 6.0 (API level 23) or higher, include the uses-permission-sdk-23 tag instead of the uses-permission tag.

When using either of these tags, you can set the maxSdkVersion attribute to specify that, on devices running a higher version, a particular permission isn't needed.

所有危險權限(組)

權限組:Manifest.permission_group
權限:Manifest.permission
中文解釋參考

使用以下 adb 命令可以查看所有需要授權的權限組:

adb shell pm list permissions -d -g

不同系統可能稍有差異,不過基本都一樣,以下為一個案例:

Dangerous Permissions:

group:com.google.android.gms.permission.CAR_INFORMATION    汽車資料
  permission:com.google.android.gms.permission.CAR_VENDOR_EXTENSION
  permission:com.google.android.gms.permission.CAR_MILEAGE
  permission:com.google.android.gms.permission.CAR_FUEL

group:android.permission-group.CONTACTS    聯系人
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE    【電話相關,高頻使用,經常會變】
  permission:android.permission.ANSWER_PHONE_CALLS    ---API26新增的
  permission:android.permission.READ_PHONE_NUMBERS    ---API26新增的
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.ACCEPT_HANDOVER    ---API28新增的
  permission:android.permission.USE_SIP
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALL_LOG【電話相關,API28中從PHONE權限組抽離出來的】
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.PROCESS_OUTGOING_CALLS

group:android.permission-group.CALENDAR    日歷
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA    相機
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS    傳感器
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION        【位置,高頻使用】
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:com.google.android.gms.permission.CAR_SPEED
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE    【存儲,高頻使用】
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE    麥克風
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS    短信
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

//省略了一小部分未分組的以及第三方應用(例如微博)定義的

2019-11-16


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM