編輯:關於Android編程
在6.0以前 權限都是在安裝時授權的,如果用戶不授權就無法安裝;
Android從6.0(API 23)開始 使用運行時權限,而不是像以前那樣安裝時授權。當你需要某些權限時,系統會向用戶去申請權限。用戶可以隨時取消授權給你的權限。
6.0中權限分為兩類 普通權限和危險權限,普通權限在AndroidManifest 文件中注冊就可以得到,對於能獲得用戶隱私的權限屬於危險權限。在使用的時候必須用戶授權才能使用。例如 拍照,錄音 sd卡的操作,危險權限被分為很多組,只要一組中的其中一項被授權 Android 就會將這一組的權限打包都授權給你app
危險權限被分為了9組
Permission Group Permissions
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
SENSORS ? BODY_SENSORS
SMS ? SEND_SMS
? RECEIVE_SMS
? READ_SMS
? RECEIVE_WAP_PUSH
? RECEIVE_MMS
STORAGE ? READ_EXTERNAL_STORAGE
? WRITE_EXTERNAL_STORAGE
普通權限
? ACCESS_LOCATION_EXTRA_COMMANDS
? ACCESS_NETWORK_STATE
? ACCESS_NOTIFICATION_POLICY
? ACCESS_WIFI_STATE
? BLUETOOTH
? BLUETOOTH_ADMIN
? BROADCAST_STICKY
? CHANGE_NETWORK_STATE
? CHANGE_WIFI_MULTICAST_STATE
? CHANGE_WIFI_STATE
? DISABLE_KEYGUARD
? EXPAND_STATUS_BAR
? GET_PACKAGE_SIZE
? INSTALL_SHORTCUT
? INTERNET
? KILL_BACKGROUND_PROCESSES
? MODIFY_AUDIO_SETTINGS
? NFC
? READ_SYNC_SETTINGS
? READ_SYNC_STATS
? RECEIVE_BOOT_COMPLETED
? REORDER_TASKS
? REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
? REQUEST_INSTALL_PACKAGES
? SET_ALARM
? SET_TIME_ZONE
? SET_WALLPAPER
? SET_WALLPAPER_HINTS
? TRANSMIT_IR
? UNINSTALL_SHORTCUT
? USE_FINGERPRINT
? VIBRATE
? WAKE_LOCK
? WRITE_SYNC_SETTINGS
請求權限
targetSdkVerion
申請權限之前必須先說一下tartgetSdkVersion ,目標sdk版本,一般定義在build.gradle文件中。
如果 targetSDKVersion 是22 安裝好之後 Android系統就知道這個App在系統API22一下都測試過了並且能正確運行的,假如這個App運行在了Android6.0系統上,Android就會對這個App很”照顧“,兼容它正確運行。6.0系統會把App申請的權限都默認給這個App。
但是 ,在6.0系統 ,用戶可隨時撤銷授權給app的權限 ,即使系統默認都授權給你,用戶也可以取消掉。這時就沒權限了。所以即使是targetSDKVersion < 23 也不是就萬事大吉了。Android為我們提供了android.support.v4.content.PermissionChecker 來檢測是否具有某些權限
判斷 targetSdkVersion
/**
* 檢查targetSDKVersion 是否在 23以上
* @return
*/
private boolean checkTargetSdkVersion(){
PackageInfo info= null;
try {
info = getPackageManager().getPackageInfo(getPackageName(),0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
int targetSdk= info.applicationInfo.targetSdkVersion;
log("TargetSdkVersion:"+targetSdk);
if (targetSdk>=Build.VERSION_CODES.M){
return true;
}
return false;
}
檢查權限
在去請求權限之前 應該先檢查一下系統 的版本 如果系統版本在6.0以上再去請求權限,如果不在就不去請求,直接使用
/**
* 檢查系統版本是否在6.0或者6.0以上
* @return
*/
private boolean checkVersion(){
// Build.VERSION.SDK_INT 當前系統版本
//Build.VERSION_CODES.M 6.0版本
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.M){
return true;
}
return false;
}
PermissionChecker.checkSelfPermission() 方法就是用於檢查App自身有沒有某一個權限 此方法適用於 targetSdkVersion < 23
context.checkSelfPermission() 適用於 targetSDKVersion >=23
返回結果有三種 狀態
- PermissionChecker.PERMISSION_GRANTED; //有權限
- PermissionChecker.PERMISSION_DENIED ; //無權限
- PermissionChecker.PERMISSION_DENIED_APP_OP;//無權限
PermissionChecker.PERMISSION_DENIED 和 PermissionChecker.PERMISSION_DENIED_APP_OP 的區別:
- targetSDKVersion 小於23沒有權限就返回 PermissionChecker.PERMISSION_DENIED_APP_OP
- targetSdkVersion23或者以上的返回 PermissionChecker.PERMISSION_DENIED
java
//檢測targetSDKVersion 是否在23以上
if (checkTargetSdkVersion()){
//targetSDKVersion >=23
//檢查是否具有讀取短信的權限
result = checkSelfPermission(permission);
}else{
//targetSDKVersion <23
//檢查是否具有讀取短信的權限
result= PermissionChecker.checkSelfPermission(this,permission);
}
請求權限
使用 requestPermissions() 方法去請求權限 參數有兩個 權限數組 和請求碼
requestPermissions(new String[]{"android.permission.READ_SMS"},10);
在請求權限之前最好是跟用戶解釋清楚為什麼要使用這個權限 ,用時候用戶並不清楚為什麼使用權限 就會被拒絕,如果一個權限被請求一次以上 在系統申請權限的Dialog會出現一個不再提醒的復選框 那怎麼判斷 用戶是否勾選了這個 不再提醒呢 ,Android提供了 shouldShowRequestPermissionRationale() 方法;
這個方法 在 第一次請求的時候 和 在用戶勾選了不再提醒時 返回false ,其他均返回true
// 第一次請求就返回false 拒絕過返回true 或者 用戶選擇不再提示返回false
boolean answer= shouldShowRequestPermissionRationale(permission);
log("shouldShowRequestPermissionRationale :"+answer);
if (!answer){
new AlertDialog.Builder(this).setTitle("權限說明")
.setMessage("此功能需要讀取短信的權限,沒有權限無法使用此功能。請在稍後授權後使用")
.setNegativeButton("確定", new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(new String[]{permission},SMS);
}
})
.setNeutralButton("取消",null)
.show();
}else{
requestPermissions(new String[]{permission},SMS);
}
處理用戶響應
重寫 activity的 onRequestPermissionsResult() 的方法 處理權限的響應
權限的申請是可以多個權限一塊申請的 ,所以 響應結果也是 數組和 請求的權限數組對應
/**
* 申請權限的響應
* @param requestCode 請求碼
* @param permissions 權限數組
* @param grantResults 結果數組
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case SMS:
LogCheckResult(grantResults[0]);
if (grantResults.length>0 && grantResults[0]==PermissionChecker.PERMISSION_GRANTED){
//TODO 讀取短信
Toast.makeText(this,"讀取短信授權成功",Toast.LENGTH_SHORT).show();
tv.setText(getSmsInPhone());
}else{
Toast.makeText(this,"讀取短信授權失敗",Toast.LENGTH_SHORT).show();
}
break;
}
}
完整的短信讀取權限申請 流程
請求權限
/**
* 請求短信權限
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private void requestSms() {
// 權限
final String permission = "android.permission.READ_SMS";
//檢查當前系統版本是否在6.0以上
if (checkVersion()){
int result =-1;
//檢測targetSDKVersion 是否在23以上
if (checkTargetSdkVersion()){
//targetSDKVersion >=23
//檢查是否具有讀取短信的權限
result = checkSelfPermission(permission);
}else{
//targetSDKVersion <23
//檢查是否具有讀取短信的權限
result= PermissionChecker.checkSelfPermission(this,permission);
}
LogCheckResult(result);
if(result==PermissionChecker.PERMISSION_GRANTED){
//已經有了權限
//TODO 讀取短信
Toast.makeText(this,"讀取短信授權成功",Toast.LENGTH_SHORT).show();
tv.setText(getSmsInPhone());
}else{
//沒有權限
//TODO 請求權限
// 第一次請求就返回false 拒絕過返回true 或者 用戶選擇不再提示返回false
boolean answer= shouldShowRequestPermissionRationale(permission);
log("shouldShowRequestPermissionRationale :"+answer);
if (!answer){
new AlertDialog.Builder(this).setTitle("權限說明")
.setMessage("此功能需要讀取短信的權限,沒有權限無法使用此功能。請在稍後授權後使用")
.setNegativeButton("確定", new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(new String[]{permission},SMS);
}
})
.setNeutralButton("取消",null)
.show();
}else{
requestPermissions(new String[]{permission},SMS);
}
}
}else{
//無需請求
Toast.makeText(this,"讀取短信授權成功",Toast.LENGTH_SHORT).show();
tv.setText(getSmsInPhone());
}
}
響應處理
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case SMS:
LogCheckResult(grantResults[0]);
if (grantResults.length>0 && grantResults[0]==PermissionChecker.PERMISSION_GRANTED){
//TODO 讀取短信
Toast.makeText(this,"讀取短信授權成功",Toast.LENGTH_SHORT).show();
tv.setText(getSmsInPhone());
}else{
Toast.makeText(this,"讀取短信授權失敗",Toast.LENGTH_SHORT).show();
}
break;
}
}
短信讀取 代碼
public String getSmsInPhone() {
log("開始讀取短信");
final String SMS_URI_ALL = "content://sms/";
final String SMS_URI_INBOX = "content://sms/inbox";
final String SMS_URI_SEND = "content://sms/sent";
final String SMS_URI_DRAFT = "content://sms/draft";
final String SMS_URI_OUTBOX = "content://sms/outbox";
final String SMS_URI_FAILED = "content://sms/failed";
final String SMS_URI_QUEUED = "content://sms/queued";
StringBuilder smsBuilder = new StringBuilder();
try {
Uri uri = Uri.parse(SMS_URI_ALL);
String[] projection = new String[] { "_id", "address", "person", "body", "date", "type" };
Cursor cur = getContentResolver().query(uri, projection, null, null, "date desc"); // 獲取手機內部短信
log("cursor:"+cur.getCount());
if (cur.moveToFirst()) {
int index_Address = cur.getColumnIndex("address");
int index_Person = cur.getColumnIndex("person");
int index_Body = cur.getColumnIndex("body");
int index_Date = cur.getColumnIndex("date");
int index_Type = cur.getColumnIndex("type");
do {
String strAddress = cur.getString(index_Address);
int intPerson = cur.getInt(index_Person);
String strbody = cur.getString(index_Body);
long longDate = cur.getLong(index_Date);
int intType = cur.getInt(index_Type);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date d = new Date(longDate);
String strDate = dateFormat.format(d);
String strType = "";
if (intType == 1) {
strType = "接收";
} else if (intType == 2) {
strType = "發送";
} else {
strType = "null";
}
smsBuilder.append("[ ");
smsBuilder.append(strAddress + ", ");
smsBuilder.append(intPerson + ", ");
smsBuilder.append(strbody + ", ");
smsBuilder.append(strDate + ", ");
smsBuilder.append(strType);
smsBuilder.append(" ]\n\n");
} while (cur.moveToNext());
if (!cur.isClosed()) {
cur.close();
cur = null;
}
} else {
smsBuilder.append("no result!");
} // end if
smsBuilder.append("getSmsInPhone has executed!");
} catch (SQLiteException ex) {
log("SQLiteException in getSmsInPhone");
}
return smsBuilder.toString();
}
關於這次的Demo,github 地址: https://github.com/sky-mxc/AndroidDemo/tree/master/permission
開源地址:https://github.com/SimonVT/android-menudrawer 簡介:menudrawer是跟sliderMenu差不多的一種框架,
code:package com.louisgeek.louiscustomviewstudy;import android.content.Context;import
我們生活中使用微信,如果看到的好東西,如小視頻,圖片,小段子等等,就會收藏起來,那麼微信收藏怎麼導出到電腦呢?不清楚的朋友可以來看看微信收藏導出到電腦方法流
衛星菜單可能網上已經有很多博文了,but,這裡僅記錄下自己的學習路程~剛看到自定義衛星菜單的時候真的是一臉懵逼,看完所有的源碼覺得還可以接受,自己寫難度較大,功力太薄嗚嗚