Android運行時權限

CharleyLitt 9年前發布 | 8K 次閱讀 安卓開發 Android開發 移動開發

1. Android權限機制

我們知道,為了要使開發出來的APP能夠從網絡上獲取數據,就要申請接入網絡的權限,只要在AndroidManifest.xml中添加這樣一句權限聲明即可:

<manifest package="com.example.jaydragon.logindemo"
      xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET"/>
    ...
</manifest>

其實權限機制的設計思路非常簡單,就是用戶如果認可你所申請的權限,那么就會安裝你的程序,如果不認可,那么就拒絕安裝。

但是理想是美好的,現實卻很殘酷,因為很多我們離不開的常用軟件普遍存在著濫用權限的情況,不管用不用得到,反正先申請了再說。

Android開發團隊當然也意識到了這個問題,于是在Android6.0系統加入了運行時權限功能。 也就是說,用戶不需要在安裝軟件的時候一次性授權所有申請的權限,而是可以在軟件使用的過程中再對某一項權限申請進行授權。 比如說一款相機應用在運行時申請了地理位置定位權限,就算我拒絕了這個權限,但是我仍然可以繼續使用這個軟件的其他功能,而不是像之前那樣直接無法安裝它。

當然,并不是所有權限都需要在運行時申請。Android現在將權限歸成了兩類,一類是普通權限,一類是危險權限。對于普通權限,系統會自動幫我們進行授權,而危險權限就需要用戶動手點擊授權才可以,否則程序就無法使用相應的功能。

但是Android中一共有上百種權限,我們怎么從中區分哪些是普通權限,哪些是危險權限呢?不用慌,因為危險權限就那么幾個,我們記住(或熟悉)它們,剩下的舊都是普通權限了。

下表列出了Android中所有的危險權限,一共9組24個危險權限。

權限名稱 權限名
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
SENSOPS BODY_SENSORS
SMS SEND_SMS、RECEIVE_SMS、READ_SMS、RECEIVE_WAP_PUSH、RECEIVE_SMS
STORAGE READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE

這里的權限你可能有的都沒見過,不過沒關系,你不需要了解表格中的每個權限的作用,只要把它當成一個參照表來查看就行了。每當要使用權限時,可以先到這張表來查看一下,如果是這張表里的權限,那么就需要進行運行時權限處理,如果不在這張表里,那么只需要在AndroidManifest.xml文件中添加一下權限聲明就可以了。

注意,表格中的每個權限都屬于一個權限組,我們在進行運行時權限處理時使用的是權限名,但是用戶一旦同意授權了,那么該權限所隊形的權限組中所有其他權限也會同時被授權。

好了,關于Android權限機制的內容就這么多。 那么到底如何在程序運行的時候申請權限呢?

2. 在程序運行時申請權限

這里我們就使用CALL_PHONE這個權限來展示一下如何在程序運行的時候進行運行時權限的處理。

CALL_PHONE這個權限是編寫打電話功能的時候需要聲明的,因為撥打電話會涉及用戶手機的資費問題,因而被列為了危險權限。這里我就直接定位在Android6.0系統及以上了,Android6.0系統以下的處理方法就是在AndroidManifest.xml中聲明如下權限就OK了:

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

然后,具體的處理方法,就放在onClick()方法中就行了。

那么,在Android6.0系統及以上系統到底怎樣處理危險權限呢?

睜大眼睛,Show Time!

首先這里有一個makeCall按鈕,我們點擊這個按鈕就會觸發申請CALL_PHONE權限了。直接上代碼,然后具體分析一下:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button makeCall = (Button) findViewById(R.id.make_call);
        makeCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1);
                }else{
                    call();
                }
            }
        });
    }
    public void call(){
        try {
            Intent intent=new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode){
        case 1:
            if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                call();
            }else{
                Toast.makeText(this, "權限被您殘忍地拒絕了", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}</code></pre> 

上面的代碼將運行時權限的完整流程都覆蓋了,下面我們來分析一下。

說白了,運行時權限的核心就是在程序運行過程中由用戶授權去執行某些危險操作,程序是不可以擅自做主去執行這些危險操作的。因此,第一步就是要先判斷用戶是不是已經給應用程序授過該權限了,借助的是ComtextCompat.checkSelfPermission()方法。該方法接受兩個參數,第一個參數是Context,這個沒什么好說的,第二個參數是具體的權限名,比如打電話的權限名就是Manifest.permission.CALL_PHONE,然后我們使用方法的返回值和PackageManager.PERMISSION_GRANTED做比較,相等就說明用戶已經授權,不等就表示用戶沒有授權。

如果已經授權的話,直接去執行撥打電話的邏輯操作就可以了,這里,我們把撥打電話的邏輯封裝到了call()方法當中。在call()方法中,我們構建了一個隱式Intent,Intent的action指定為 Intent.ACTION_CALL ,這是一個系統內置的打電話的動作,然后在data部分指定了協議是 tel ,號碼是10086。如果沒有授權的話,則需要調用ActivityCompat.requestPermissions()方法來向用戶申請授權,requestPermissions()方法接受3個參數,第一個參數要求是Activity的實例,第二個參數是一個String數組,我們把要申請的權限名放在數組中即可,第三個參數是請求碼,只要是唯一值就可以了,這里傳入了1。

調用完了requestPermissions()方法之后,系統就會彈出一個權限申請的對話框,然后用戶選擇同意或拒絕我們的權限申請,不論是哪種結果,最終都會回調到onRequestPermissionsResult()方法中,而授權的結果則會封裝到grantResults參數當中。這里,我們只要判斷一下最后的授權結果,如果用戶同意的話就調用call()方法來撥打電話,如果用戶拒絕的話我們只能放棄操作,并彈出一條失敗提示。

現在運行一下程序,并點擊Make Call按鈕,效果如下:

申請電話權限對話框.png

由于用戶還沒授權過撥打電話的權限,因此第一次會彈出這樣一個申請權限的對話框,用戶可以選擇同意或拒絕,加入這里點擊了DENY,效果如下:

用戶拒絕了權限申請.png

由于用戶沒有同意授權,我們只能彈出一個操作失敗的提示。下面我們再次點擊Make Call按鈕,仍然會彈出申請權限的對話框,這次點擊ALLOW,結果如圖:

撥打電話界面.png

可以看到,這次我們就成功進入到了撥打電話的界面了,以后再次點擊Make Call按鈕,就可以直接撥打電話了,因為用戶已經授權過了。

好了,到這里,我們就已經將運行時權限搞得差不多了。可是你發現沒有,有時我們的程序會一次性申請多個危險權限,這時候我們怎么處理呢?來來來,跟著我繼續套路一下。

假如,這次我們要申請以下4個危險權限:ACCESS_COARSE_LOCATION、ACCESS_FINE_LOCATION、READ_PHONE_STATE、WRITE_EXTERNAL_STORAGE。

這里由于ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION屬于同一個權限組,因此兩者只要申請其一就可以了。那么到底怎么才能在運行時一次性申請3個權限呢?我先將代碼放出來,然后再來分析一下。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        List<String> permissionList=new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE)!=PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String[] permissions=permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
        }else{
            requestLocation();
        }

    }
    private void requestLocation(){
        initLocation();
        mLocationClient.start();
    }
    private void initLocation(){
        LocationClientOption option=new LocationClientOption();
        option.setScanSpan(5000);
        option.setIsNeedAddress(true);
        mLocationClient.setLocOption(option);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length>0){
                    for (int result:grantResults) {
                        if (result!=PackageManager.PERMISSION_GRANTED){
                            Toast.makeText(this, "必須同意所有權限才能使用本程序", Toast.LENGTH_SHORT).show();
                            finish();
                            return;
                        }
                    }
                    requestLocation();
                }else{
                    Toast.makeText(this, "發生未知錯誤", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

首先創建了一個List集合,然后一次判斷這3個權限有沒有被授權,如果沒被授權就添加到List集合中,最后將List集合轉換成數組,在調用ActivityCompat.requestPermission()方法一次性申請。除此之外,onRequestPermissionsResult()方法中對權限結果的邏輯處理也和之前有所不同,這次我們通過一個循環將申請的每個權限都進行了判斷,如果有任何一個權限被拒絕了,那么就直接調用finish()方法關閉當前程序。

以上代碼是關于百度地圖定位的,以后會另起一篇記錄一下,如何調用百度地圖SDK的。

怎么樣,是不是很精髓,可惜我這是拿來主義,并不是現在的我可以獨立想出來的。

 

來自:http://www.jianshu.com/p/d529c51deb33

 

 本文由用戶 CharleyLitt 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!