Android——Android 6.0 許可權申請機制處理
Android 6.0帶來了新的許可權管理方式,預設情況下沒有任何應用有許可權去執行對其他應用、作業系統、使用者有不利影響的操作,這涉及到android 6.0的 Security Architecture(安全體系結構),也是Android安全體系結構的核心,記住這句話後面可以對許可權管理有個很好的理解。
1.因為每一個Android 應用都是在一個程序沙盒中執行的,應用必須明確分享的資源和資料,通過申明需要的額外許可權這種方式(這些額外許可權不由基本沙河提供)。應用靜態的宣告這些許可權(在Manifest裡面),然後Android系統會請求使用者同意這些許可權。
2.Android應用沙盒不依賴於建立應用的技術
以上加粗的兩句話不是很懂,懂的大神指教一下。
·Using Permission(使用許可權)
一個新建的Android應用預設是沒有許可權的,這意味著它不能執行任何可能對使用者體驗有不利影響的操作或者訪問裝置資料。為了使用受保護的功能,你必須包含一個或者多個標籤在你的app manifest中。
·Android 6.0中許可權分為兩種,普通許可權(也稱一般許可權)和危險許可權(即執行時許可權,下面統稱執行時許可權)。
1.普通許可權(一般許可權)
如果你的應用manifest中只申明瞭普通許可權(也就是說,這些許可權對於使用者隱私和裝置操作不會造成太多危險),系統會自動授予這些許可權。
2.執行時許可權(危險許可權)
如果你的應用manifest中聲明瞭執行時許可權(也就是說,這些許可權可能會影響使用者隱私和裝置的普通操作),系統會明確的讓使用者決定是否授予這些許可權。系統請求使用者授予這些許可權的方式是由當前應用執行的系統版本來決定的。
普通許可權(一般許可權)和執行時許可權(危險許可權)的列表就不貼出來了,大家可以去這裡參考。
·Android6.0及以上的系統
1.如果你的裝置執行的是Android6.0(API level 23)及以上的系統,並且你的應用的targetSdkVersion也是23或者更高,那麼應用向用戶請求這些許可權是實時的。這意味著使用者可以隨時取消 這些執行時許可權的授權。所以應用在每次需要用到這些執行時許可權的時候都需要去檢查是否還有這些許可權的授權。
2.Android 5.1及以下的系統
如果你的裝置執行在Android5.1(API level 22)及以下的系統中,或者你的app的targetSdkVersion是22或者更低。系統會請求使用者在apk安裝的時候授予這些許可權。
3.如果你的應用更新的時候添加了一個許可權,系統會在使用者更新應用的時候請求使用者授予這個許可權,一旦使用者安裝了這個應用,唯一可以取消授權的方式就是解除安裝掉這個應用。注意這句話的意思,想一下如果app的targetSdkVersion是22或者以下,但是執行在Android6.0及以上的裝置中會有什麼問題?
這裡說一下:安裝過程中,會一起請求使用者授予所有許可權,如果使用者拒絕,將不能安裝這個app,只有使用者全部同意這些授權,才能安裝這個應用,但是問題來了,安裝好了這個應用之後,android6.0以上的系統中,使用者是可以去設定中取消授權的,而且是隨時都可以取消,所以很多執行時許可權可能也得不到,目前官方的做法是,如果使用者取消該項授權,那麼依賴該項授權的方法的返回值為null,所以你的app可能會報空指標異常。以後是否會針對22以下的app做改變還不得而知,畢竟crash是很難讓人接受的,但是crash是由使用者造成的,使用者應該也可以理解。
常常來說一個授權失敗會丟擲SecurityException,然而這並不是在 所有情況下都會發生。比如,傳送一個廣播去檢查授權(SendBroadcast(Intent)),資料會被髮送給所有接收者,但是當這個方法的請求返 回的時候,你不會收到任何一個因為授權失敗丟擲的異常,其實在大多數情況下,授權失敗只會列印系統日誌。
任何應用都可以去請求想要授權的許可權,以下參考應用demo:
先看下應用啟動檢測的流程圖:
有一個問題,檢測授權時,彈出對話方塊有一個複選框按鈕,如果使用者拒絕授權不再提示了,那麼問題來了,如果我退出第二次啟動的時候,應用就無法用了嗎?當然是否定的。看下流程圖:
多了一步判斷,授權失敗後,第二次啟動的時候就提示自定的對話方塊讓使用者選擇。
大家瞭解了這麼多,接下來看下demo的程式碼:
主介面MainActivity:
package com.lai.permissiondemo;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
/**
* Created by LaiYingtang on 2016/5/18.
* 主介面
*/
public class MainActivity extends Activity {
private static final int REQUEST_CODE = 0;//請求碼
private CheckPermission checkPermission;//檢測許可權器
//配置需要取的許可權
static final String[] PERMISSION = new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,// 寫入許可權
Manifest.permission.READ_EXTERNAL_STORAGE, //讀取許可權
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission = new CheckPermission(this);
}
@Override
protected void onResume() {
super.onResume();
//缺少許可權時,進入許可權設定頁面
if (checkPermission.permissionSet(PERMISSION)) {
startPermissionActivity();
}
}
//進入許可權設定頁面
private void startPermissionActivity() {
PermissionActivity.startActivityForResult(this, REQUEST_CODE, PERMISSION);
}
//返回結果回撥
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//拒絕時,沒有獲取到主要許可權,無法執行,關閉頁面
if (requestCode == REQUEST_CODE && resultCode == PermissionActivity.PERMISSION_DENIEG) {
finish();
}
}
}
啟動主Activity後去檢測android版本,檢查許可權的工具類:CheckPermission類:
package com.lai.permissiondemo;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.v4.content.ContextCompat;
/**
* Created by LaiYingtang on 2016/5/18.
* <p/>
* 檢查許可權的工具類
*/
public class CheckPermission {
private final Context context;
//構造器
public CheckPermission(Context context) {
this.context = context.getApplicationContext();
}
//檢查許可權時,判斷系統的許可權集合
public boolean permissionSet(String... permissions) {
for (String permission : permissions) {
if (isLackPermission(permission)) {//是否新增完全部許可權集合
return true;
}
}
return false;
}
//檢查系統許可權是,判斷當前是否缺少許可權(PERMISSION_DENIED:許可權是否足夠)
private boolean isLackPermission(String permission) {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED;
}
}
檢測SDKAPI如果是大於或等於23的時候,則彈出自定義的對話方塊提示使用者是否授權,拒絕的話提示幫助對話方塊去設定應用裡面去開啟許可權,否則退出。
package com.lai.permissiondemo;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
/**
* Created by LaiYingtang on 2016/5/18.
* 使用者許可權獲取頁面,許可權處理
*/
public class PermissionActivity extends Activity {
//首先宣告許可權授權
public static final int PERMISSION_GRANTED = 0;//標識許可權授權
public static final int PERMISSION_DENIEG = 1;//許可權不足,許可權被拒絕的時候
private static final int PERMISSION_REQUEST_CODE = 0;//系統授權管理頁面時的結果引數
private static final String EXTRA_PERMISSION = "com.lai.permissiondemo";//許可權引數
private static final String PACKAGE_URL_SCHEME = "package:";//許可權方案
private CheckPermission checkPermission;//檢測許可權類的許可權檢測器
private boolean isrequestCheck;//判斷是否需要系統許可權檢測。防止和系統提示框重疊
//啟動當前許可權頁面的公開介面
public static void startActivityForResult(Activity activity, int requestCode, String... permission) {
Intent intent = new Intent(activity, PermissionActivity.class);
intent.putExtra(EXTRA_PERMISSION, permission);
ActivityCompat.startActivityForResult(activity, intent, requestCode, null);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.permission_layout);
if (getIntent() == null || !getIntent().hasExtra(EXTRA_PERMISSION))//如果引數不等於配置的許可權引數時
{
throw new RuntimeException("當前Activity需要使用靜態的StartActivityForResult方法啟動");//異常提示
}
checkPermission = new CheckPermission(this);
isrequestCheck = true;//改變檢測狀態
}
//檢測完之後請求使用者授權
@Override
protected void onResume() {
super.onResume();
if (isrequestCheck) {
String[] permission = getPermissions();
if (checkPermission.permissionSet(permission)) {
requestPermissions(permission); //去請求許可權
} else {
allPermissionGranted();//獲取全部許可權
}
} else {
isrequestCheck = true;
}
}
//獲取全部許可權
private void allPermissionGranted() {
setResult(PERMISSION_GRANTED);
finish();
}
//請求許可權去相容版本
private void requestPermissions(String... permission) {
ActivityCompat.requestPermissions(this, permission, PERMISSION_REQUEST_CODE);
}
//返回傳遞過來的許可權引數
private String[] getPermissions() {
return getIntent().getStringArrayExtra(EXTRA_PERMISSION);
}
/**
* 用於許可權管理
* 如果全部授權的話,則直接通過進入
* 如果許可權拒絕,缺失許可權時,則使用dialog提示
*
* @param requestCode 請求程式碼
* @param permissions 許可權引數
* @param grantResults 結果
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (PERMISSION_REQUEST_CODE == requestCode && hasAllPermissionGranted(grantResults)) //判斷請求碼與請求結果是否一致
{
isrequestCheck = true;//需要檢測許可權,直接進入,否則提示對話方塊進行設定
allPermissionGranted(); //進入
} else { //提示對話方塊設定
isrequestCheck = false;
showMissingPermissionDialog();//dialog
}
}
//顯示對話方塊提示使用者缺少許可權
private void showMissingPermissionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(PermissionActivity.this);
builder.setTitle(R.string.help);//提示幫助
builder.setMessage(R.string.string_help_text);
//如果是拒絕授權,則退出應用
//退出
builder.setNegativeButton(R.string.quit, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setResult(PERMISSION_DENIEG);//許可權不足
finish();
}
});
//開啟設定,讓使用者選擇開啟許可權
builder.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startAppSettings();//開啟設定
}
});
builder.setCancelable(false);
builder.show();
}
//獲取全部許可權
private boolean hasAllPermissionGranted(int[] grantResults) {
for (int grantResult : grantResults) {
if (grantResult == PackageManager.PERMISSION_DENIED) {
return false;
}
}
return true;
}
//開啟系統應用設定(ACTION_APPLICATION_DETAILS_SETTINGS:系統設定許可權)
private void startAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName()));
startActivity(intent);
}
}
以上是android 6.0申請許可權的主要程式碼,註釋說明寫的很清楚,博友還感興趣的話可以到後面下載原始碼。
來看一下效果:
這裡我說下幾點要注意的問題:
1.在MainActivity中一定要寫入需要請求的許可權:
//配置需要取的許可權
static final String[] PERMISSION = new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,// 寫入許可權
Manifest.permission.READ_EXTERNAL_STORAGE, //讀取許可權
};
2.manifest清單檔案裡面也要配置需要的許可權:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.MainActivity中請求的結果回撥,請求碼與結果碼一定要一一對應,否則即使授權後無法回到主介面。
//返回結果回撥
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//拒絕時,沒有獲取到主要許可權,無法執行,關閉頁面
if (requestCode == REQUEST_CODE && resultCode == PermissionActivity.PERMISSION_DENIEG) {
finish();
}
}
用android 6.0編譯的時候會出現getSlotFromBufferLocked: unknown buffer錯誤,這是一個未知緩衝區的錯誤,有需要深入理解的話 請參考該文件。
最後附上demo的下載地址