ANDROID 6.0+運行時權(quán)限簡文

2020年4月補充

一個簡單好用的權(quán)限庫 —— PermissionGrantor

—— https://github.com/dfqin/PermissionGrantor


一啄寡、運行時權(quán)限檢查 (Permissions at Run Time)

從 Android 6.0(API 級別 23)開始风秤,用戶開始在應用運行時向其授予權(quán)限,而不是在應用安裝時授予芳绩。

系統(tǒng)權(quán)限分為兩類:普通權(quán)限 (Normal Permissions),危險權(quán)限(Dangerous permissions

  • 敏感權(quán)限在運行時每次調(diào)用都需要進行檢查是否得到授權(quán)。
  • 用戶可以隨時進入應用的“Settings”屏幕決定授予任一申請的權(quán)限宰睡,就是用戶可以隨便開隨便關(guān)。
    比如气筋,用戶可以選擇為相機應用提供相機訪問權(quán)限拆内,而不提供設(shè)備位置的訪問權(quán)限。

二宠默、權(quán)限分類

系統(tǒng)權(quán)限分為兩類:普通權(quán)限 (Normal Permissions)麸恍,危險權(quán)限(Dangerous permissions

  • 正常權(quán)限:
    不會直接給用戶隱私權(quán)帶來風險。如果您的應用在其清單中列出了正常權(quán)限搀矫,系統(tǒng)將自動授予該權(quán)限抹沪。

  • 危險權(quán)限:
    會授予應用訪問用戶機密數(shù)據(jù)的權(quán)限。如果您的應用在其清單中列出了正常權(quán)限瓤球,系統(tǒng)將自動授予該權(quán)限融欧。如果您列出了危險權(quán)限,則用戶必須明確批準您的應用使用這些權(quán)限冰垄。


注意:不管你的權(quán)限是 正常權(quán)限 還是 危險權(quán)限蹬癌,都需要在清單列表將其列出來权她。不過虹茶,該聲明的影響因系統(tǒng)版本和應用的目標 SDK 級別的不同而有所差異:

—— 對于普通權(quán)限,即只要在 AndroidManifest 聲明隅要,用戶安裝了應用就會獲取到權(quán)限

—— 如果設(shè)備運行的是 Android 5.1 或更低版本蝴罪,或者應用的目標 SDK 為 22 或更低:如果您在清單中列出了危險權(quán)限,則用戶必須在安裝應用時授予此權(quán)限步清;如果他們不授予此權(quán)限要门,系統(tǒng)根本不會安裝應用虏肾。

—— 如果設(shè)備運行的是 Android 6.0 或更高版本,或者應用的目標 SDK 為 23 或更高:應用必須在清單中列出權(quán)限欢搜,并且它必須在運行時請求其需要的每項危險權(quán)限封豪。用戶可以授予或拒絕每項權(quán)限,且即使用戶拒絕權(quán)限請求炒瘟,應用仍可以繼續(xù)運行有限的功能吹埠。

.
.
.

運行時權(quán)限的具體使用

1、為什么危險權(quán)限每次調(diào)用都要進行申請疮装?

第一肯定因為他是敏感的缘琅。
第二就是因為隨時可以在設(shè)置里面關(guān)閉這個權(quán)限,所以每次都需要申請廓推。

比如拍照刷袍,這是危險權(quán)限,你昨天早上獲取了拍照的權(quán)限樊展,但是用戶可以在昨天下午就把這個權(quán)限給關(guān)掉呻纹,你不能在今天就想當然以為你擁有了拍照權(quán)限,所以每次運行都檢查是必要的专缠,

2居暖、使用

2.1、怎么申請運行時動態(tài)申請權(quán)限藤肢?

要檢查您是否具有某項權(quán)限太闺,請調(diào)用 [ContextCompat.checkSelfPermission()](https://developer.android.com/reference/android/support/v4/content/ContextCompat.html?hl=zh-cn#checkSelfPermission(android.content.Context, java.lang.String))
方法。例如嘁圈,以下代碼段顯示了如何檢查 Activity 是否具有撥號的權(quán)限:

if (ContextCompat.checkSelfPermission(CallActivity.this,
                        Manifest.permission.CALL_PHONE)
                        != PackageManager.PERMISSION_GRANTED) 

checkSelfPermission

checkSelfPermission會得到一個int類型的返回值

PackageManager.PERMISSION_GRANTED 常量表示已經(jīng)授權(quán)
PackageManager.PERMISSION_DENIED 常量表示 未申請省骂,此時可向用戶進行權(quán)限申請。

2.2最住、如何得知申請結(jié)果钞澳?

onRequestPermissionsResult回調(diào)方法的得知結(jié)果

  @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUESTCODE: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    callNum();
                } else {
                }
                return;
            }
        }
    }

.
.
.
到此為止,我們來利用上面涉及代碼做一個撥號的小demo

public class CallActivity extends AppCompatActivity {


    private static final int REQUESTCODE = 6001;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);

        findViewById(R.id.mTvCall).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 動態(tài)申請權(quán)限   ContextCompat.checkSelfPermission()
                if (ContextCompat.checkSelfPermission(CallActivity.this,
                        Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    // 沒有獲取權(quán)限涨缚,那么就申請權(quán)限
                    ActivityCompat.requestPermissions(CallActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE},
                            REQUESTCODE);

                } else {
                    // 已經(jīng)有權(quán)限轧粟,那么就撥打電話
                    callNum();
                }
            }
        });
    }

    private void callNum() {
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + "10010");
        intent.setData(data);
        try {
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUESTCODE: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    callNum();
                } else {
                }
                return;
            }
        }
    }
}

清單文件權(quán)限

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

代碼非常簡單,就是每次撥號都動態(tài)檢查是否具有撥號權(quán)限脓魏。

我們會發(fā)現(xiàn)情況如下

  • 1兰吟、當我們授予過權(quán)限,那么下次再撥號的時候茂翔,就不會再彈出是否授權(quán)的提示框
  • 2混蔼、當我們授予權(quán)限,然后又手動到 設(shè)置 把該app的撥號權(quán)限關(guān)閉珊燎,那么再次點擊撥號的時候會再次彈出 授權(quán)提示框 惭嚣。
  • 3遵湖、當我們我們點擊拒絕授予權(quán)限,下次再點擊撥號的時候晚吞,會多出一個“不在提示(Don‘t ask again)”的選框延旧,如果我們勾選“不在提示”,那么下次點擊撥號就不會彈出是否授權(quán)的提示框

gif如下

per.gif

2.3槽地、當被拒接授權(quán)之后的再次請求申請 shouldShowRequestPermissionRationale()

我們在運行時申請權(quán)限垄潮,比如撥打電話,用戶的選擇很簡單闷盔,要么同意弯洗,要么拒絕。

如果用戶同意了逢勾,那就萬事大吉牡整,但是如果用戶拒絕了,而且是“不再提示”的那種拒絕溺拱,那么問題就來了逃贝,你再點擊撥號就什么反應都沒有了。

對于這種情況迫摔,其實google也已經(jīng)幫我們想到了沐扳,通過shouldShowRequestPermissionRationale()方法來解決抡草。

shouldShowRequestPermissionRationale()袋毙。如果應用之前請求過此權(quán)限但用戶拒絕了請求,此方法將返回 true搂擦。根據(jù)這個特點纱烘,我們將代碼修改一下:

public class CallActivity extends AppCompatActivity {
    private static final int REQUESTCODE = 6001;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);

        findViewById(R.id.mTvCall).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 動態(tài)申請權(quán)限   ContextCompat.checkSelfPermission()
                if (ContextCompat.checkSelfPermission(CallActivity.this,
                        Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    // shouldShowRequestPermissionRational   解釋為什么需要這和權(quán)限
                    if (ActivityCompat.shouldShowRequestPermissionRationale(CallActivity.this,
                            Manifest.permission.CALL_PHONE)) {
                        // shouldShowRequestPermissionRationale 返回true杨拐,代表之前該權(quán)限已經(jīng)被拒絕,那么針對拒絕做處理擂啥,解釋為什么需要這個權(quán)限

                        new AlertDialog.Builder(CallActivity.this)
                                .setMessage("app需要開啟權(quán)限才能使用此功能")
                                .setPositiveButton("設(shè)置", new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialogInterface, int i) {
                                        // 跳轉(zhuǎn)到app設(shè)置
                                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                        intent.setData(Uri.parse("package:" + getPackageName()));
                                        startActivity(intent);
                                    }
                                })
                                .setNegativeButton("取消", null)
                                .create()
                                .show();
                    } else {
                        // 彈出權(quán)限申請框框
                        ActivityCompat.requestPermissions(CallActivity.this,
                                new String[]{Manifest.permission.CALL_PHONE},
                                REQUESTCODE);
                    }

                } else {
                    callNum();
                }
            }
        });
    }


    private void callNum() {
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + "10010");
        intent.setData(data);
        try {
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUESTCODE: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    callNum();
                } else {
                }
                return;
            }
        }
    }
}

運行g(shù)if如下


per2.gif

我們發(fā)現(xiàn)哄陶,當我們拒絕授權(quán)此權(quán)限之后,再次執(zhí)行到申請權(quán)限的代碼的時候哺壶,根據(jù)shouldShowRequestPermissionRationale()返回為true屋吨,我們彈出提示框解釋為什么需要這個權(quán)限,并且提供了跳轉(zhuǎn)到設(shè)置開始權(quán)限的地方山宾,這樣問題就得到了解決至扰。

3、注意

1塌碌、抽取的callNum方法執(zhí)行startActivity(intent)這句代碼的時候渊胸,會報
Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException這樣的編譯錯誤旬盯。
其實就是說這句帶嗎有可能因為未授權(quán)拋異常台妆,我們可以利用系統(tǒng)的提示的代碼處理一下翎猛,

 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        startActivity(intent);

但是很顯然,沒必要接剩,因為我們前面已經(jīng)處理好了切厘。直接try...catch一下就好

2、環(huán)境說明
機器
android 7.1.1

環(huán)境

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.amqr.permiss6"
        minSdkVersion 14
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

三懊缺、第三方庫 PermissionsDispatcher

hotchemi/PermissionsDispatcher
就此文時間當前最新版本的2.3.1

步驟1疫稿、

project 的gradle的dependencies配置
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

Paste_Image.png

步驟2、

主module配置如下

apply plugin: 'android-apt'

apt 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
compile 'com.github.hotchemi:permissionsdispatcher:2.3.1'

Paste_Image.png

步驟三

安裝PermissionsDispatcher插件(安裝方法類似普通插件安裝方法)

安裝后怎么使用鹃两?

清單文件寫上必要的權(quán)限遗座,比如
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

然后右鍵generate…

Paste_Image.png

選擇

Paste_Image.png

最后勾選對應權(quán)限,選擇需要的方法俊扳,寫上名字途蒋,即可自動生成代碼

Paste_Image.png

生成的代碼和簡單使用

@RuntimePermissions
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // storageNeedPer 原本涉及到純粹權(quán)限的代碼,我們放到storageNeedPer這個方法里面
        MainActivityPermissionsDispatcher.storageNeedPerWithCheck(MainActivity.this);


    }

    @NeedsPermission({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
    void storageNeedPer() {
        // 那些權(quán)限涉及到存儲權(quán)限的馋记,寫在這里
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
        //權(quán)限請求回調(diào)号坡,提示用戶之后,用戶點擊“允許”或者“拒絕”之后調(diào)用此方法 
    }

    @OnShowRationale({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
    void storageNeedShowRat(final PermissionRequest request) {
        // 解釋為什么需要這個權(quán)限
    }

    @OnPermissionDenied({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
    void storageDenied() {
        // 如果用戶不授予某權(quán)限時調(diào)用的方法,
    }

    @OnNeverAskAgain({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
    void storageAsk() {
        //如果用戶選擇了讓設(shè)備“不再詢問”梯醒,而調(diào)用的方法
    }
}

本文大概就到這里宽堆,github上關(guān)于權(quán)限的庫有很多,大家可以自行查找茸习,還有詳細的一些權(quán)限組啊之類的東西畜隶,權(quán)限的細節(jié)資料,大家也可以自行g(shù)oogle号胚。
本來權(quán)限就已經(jīng)被說了很久的事情代箭,也有很多優(yōu)秀的文章,此文是因為看到清單軟件一條信息誤標為完成涕刚,哈哈哈哈嗡综,只是為了完成清單。

四杜漠、注意點

  • 申請權(quán)限是app會進入后臺(界面上沒體現(xiàn))
    (彈出權(quán)限申請框時极景,其實這個這個時候程序會進入后臺,這個我們打印一下application的生命周期就知道了驾茴,如果程序涉及到手勢盼樟,需要根據(jù)這個特點可能需要調(diào)整下)

下載鏈接

.
.
.
參考:
google官方文章
Android 6.0+ 運行時權(quán)限探索

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锈至,隨后出現(xiàn)的幾起案子晨缴,更是在濱河造成了極大的恐慌,老刑警劉巖峡捡,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击碗,死亡現(xiàn)場離奇詭異筑悴,居然都是意外死亡,警方通過查閱死者的電腦和手機稍途,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門阁吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人械拍,你說我怎么就攤上這事突勇。” “怎么了坷虑?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵甲馋,是天一觀的道長。 經(jīng)常有香客問我迄损,道長摔刁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任海蔽,我火速辦了婚禮共屈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘党窜。我一直安慰自己拗引,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布幌衣。 她就那樣靜靜地躺著矾削,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豁护。 梳的紋絲不亂的頭發(fā)上哼凯,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音楚里,去河邊找鬼断部。 笑死,一個胖子當著我的面吹牛班缎,可吹牛的內(nèi)容都是我干的蝴光。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼达址,長吁一口氣:“原來是場噩夢啊……” “哼蔑祟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沉唠,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤疆虚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體径簿,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡罢屈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了牍帚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片儡遮。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡乳蛾,死狀恐怖暗赶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肃叶,我是刑警寧澤蹂随,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站因惭,受9級特大地震影響岳锁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹦魔,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一激率、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勿决,春花似錦乒躺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咆繁,卻和暖如春讳推,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玩般。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工银觅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坏为。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓设拟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親久脯。 傳聞我的和親對象是個殘疾皇子纳胧,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容