Android6.0中設定許可權時候的SecurityException異常的處理
說來也巧了,今天碰到了一個特別奇怪的問題。上午寫了一個小demo其中有一個功能是獲取通訊的,大家肯定知道這時候肯定要新增一個許可權:
<!-- 讀聯絡人許可權 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
新增完後就開始運行了,竟然報錯了,
Caused by: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2 from ProcessRecord{7ed1706 32137:com.fighting.together.oomtest/u0a166} (pid=32137, uid=10166) requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
這種獲取聯絡人簡單的東西,以前不知道做了多少遍,而且許可權的申明確實已經在manifest裡面,但是還是報了這種錯誤,真是讓我很尷尬啊~
之後就開始找問題,這種奇怪的問題百度肯定是不會有結果的,我就開始想想這個專案是哪裡和以前不一樣了。沒錯,昨天剛更新了Android Studio,更新到了2.0版本了;然後!!重點來了,在build.gradle中的一個屬性他也幫我改了:
targetSdkVersion 23
沒錯,之前都是22的,現在變成了API23,後來查閱發現,google在API23以及之後的版本對危險許可權的申請要求變的很嚴格了,需要多一步操作了,下文會詳解。
但是你以為僅僅是這個原因導致的crash嗎??no!還有一個非常重要的原因是因為我是真機除錯,並且我用的nexus6,並且在前段時間剛剛通過OTA升級到了Android6.0的系統。也就說如果你的手機或者模擬器的版本是android6.0的系統,才會導致這個錯誤;
總結一下,如果你同時滿足這三個條件你就會出現上述的crash:
1.targetSdkVersion設定為23或者更高
2.手機的作業系統為6.0或者以上
3.請求的是google定義的dangerous permissions
從Android6.0(API23)開始,app的需要的許可權的授權過程不是在安裝的過程中了,而是在app的執行中。因為使用者不需要在安裝或更新時候授權,所有有效的簡化了app的安裝過程。這樣也可以給使用者更多的自由去支配自己app的功能。舉個例子,使用者使用一個拍照軟體,他會同意給這個app相機使用的許可權,但是當app請求你的位置的時候,你可以選擇不給他獲取位置的許可權。
系統許可權主要分為了兩種,普通許可權以及危險許可權:
普通許可權:不會直接獲取使用者的隱私,如果manifest中申明瞭這些許可權,系統會自動授予app這些普通許可權。
危險許可權:顧名思義,會獲取使用者隱私,如果你確實需要這些危險許可權,使用者必須在app中,通過點選同意才能獲取。(相機、位置、通訊錄、簡訊等許可權)
當用戶手機的android系統為5.1及以下的系統,或者專案設定的targetSDK為22或者以下,那麼所有的許可權(普通和危險)都是在安裝時候授權的,不會出現本部落格所提出的問題。
但是如果你的android系統為6.0及以上並且targetSDK為23及以上,每一個危險的許可權都必須在app執行時候逐一讓使用者點選同意或者拒絕。
原理簡單的介紹完了,下面開始具體操作:
第一.你需要匯入最新的v4包
compile 'com.android.support:support-v4:23.1.1'
第二.檢測使用者時候已經授權同意過該危險許可權
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED)
上述等式返回true說明還沒有授權或者已經拒絕,返回false說明使用者已經同意了該危險許可權,可以繼續執行需要該許可權的操作了。
第三.接著第二步返回true的情況,進行使用者未授權或拒絕情況的處理
ActivityCompat.shouldShowRequestPermissionRationale(context,Manifest.permission.READ_CONTACTS)
上述方法返回是一個boolean值,返回true表明使用者之前已經選擇過拒絕授權了;若是返回false,則表明我們還有戲,使用者還沒有選擇過是否授權~畢竟主動權是掌握在使用者手中的,此時我們就可以通過彈窗來詢問使用者時候同意授權了,進入第四步
第四.通過呼叫系統自帶的彈窗詢問使用者是否授權
ActivityCompat.requestPermissions(context,new String[]{Manifest.permission.READ_CONTACTS},TAG_PERMISSION);
這時一個非同步的方法,TAG_PERMISSION是我們自己定義的一個int型別常量(requestCode),用於標記我們當前的方法。呼叫此方法,系統會彈出一個dialog,dialog的樣式我們是無法修改的。如下圖:
當我們選擇deny或者allow都會直接回調到某一個方法中,由此我們進入第五步
第五.重寫請求授權的回撥方法
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case TAG_PERMISSION: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(MainActivity.this, "allow", Toast.LENGTH_SHORT).show();
getContact();
} else {
Toast.makeText(MainActivity.this, "deny", Toast.LENGTH_SHORT).show();
}
return;
}
}
}
這時候我們的TAG_PERMISSION就派上用處了,如果使用者點選了授權,那麼非常好!我們就可以去執行我們需要的操作了;如果使用者拒絕的那麼我們也只能執行被限制的操作了。
好了,最後貼上完整的程式碼:
package com.fighting.together.oomtest;
import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final String[] SQL_COLUMN = new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,};
private static final int TAG_PERMISSION = 1023;
private TextView mContactTV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContactTV = (TextView) findViewById(R.id.tv_contact);
// getContact();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS)) {
Toast.makeText(MainActivity.this, "deny for what???", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "show the request popupwindow", Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},
TAG_PERMISSION);
}
} else {
Toast.makeText(MainActivity.this, "agreed", Toast.LENGTH_SHORT).show();
getContact();
}
}
private void getContact() {
ContentResolver resolver = getContentResolver();
Cursor phoneCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, SQL_COLUMN, null, null, null);
if (phoneCursor != null) {
while (phoneCursor.moveToNext()) {
//得到手機號碼
String phoneNumber = phoneCursor.getString(0);
String contactName = phoneCursor.getString(1);
mContactTV.append("\nnumber:" + phoneNumber + ",name:" + contactName);
}
phoneCursor.close();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case TAG_PERMISSION: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(MainActivity.this, "allow", Toast.LENGTH_SHORT).show();
getContact();
} else {
Toast.makeText(MainActivity.this, "deny", Toast.LENGTH_SHORT).show();
}
return;
}
}
}
}