安卓平臺權(quán)限一直有被流氓應(yīng)用隨便利用詬病, android M的發(fā)布徹底解決了這一問題,取而代之的是,app不得不在運行時一個一個詢問用戶授予權(quán)限侣颂。
Android 6.0(api23)系統(tǒng)中,做了一些限制, 開發(fā)者在使用到每條權(quán)限時必須自己調(diào)用相關(guān)代碼請求.
如果沒有獲得某項權(quán)限,直接使用相關(guān)功能,則會導(dǎo)致自己程序crash.
可見6.0以后的系統(tǒng)開發(fā)者必須對權(quán)限適配,否則軟件隨時都可能奔潰,那么問題來了~
已經(jīng)發(fā)出去的版本或是targetSdkVersion小與23的apk怎么辦?
廢話,當(dāng)然會崩了!!!
只要在滿足在Android M上直接使用為授權(quán)的功能每庆,程序必須Crash. targetSdkVersion<23的應(yīng)用在安裝時系統(tǒng)會默認(rèn)全部授權(quán)應(yīng)用在manifest中申請的權(quán)限哪痰,
不要應(yīng)用這樣你的應(yīng)用就完事大全了.用戶可以在以下頁面或是其他應(yīng)用關(guān)閉相關(guān)權(quán)限泽论,然后…你的應(yīng)用就沒有然后了~
安卓系統(tǒng)把權(quán)限分為了三類:
Normal Permissions
Dangerous Permissions
Special Permissions
一般權(quán)限都是一些系統(tǒng)認(rèn)為比較權(quán)限的權(quán)限夺蛇,流氓應(yīng)用就是擁有這些權(quán)限也干不出多大壞事步做,Normal 權(quán)限會在應(yīng)用安裝是直接授權(quán)副渴,
官網(wǎng)解釋:權(quán)限如下:
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
FLASHLIGHT
GET_PACKAGE_SIZE
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT
UNINSTALL_SHORTCUT
Dangerous Permissions-危險權(quán)限
這些權(quán)限都是一些敏感性權(quán)限,一些廣告平臺或是流氓應(yīng)用會用這些權(quán)限干一些壞壞的事情全度,因此系統(tǒng)將這類權(quán)限分了幾個類別煮剧,
應(yīng)用每次都要檢測下是否有權(quán)限,沒有的化必須彈出對話框申請讼载,只要一個組別中的一個權(quán)限得到了授權(quán)轿秧,整個組的權(quán)限都會的到授權(quán).
這部分權(quán)限也是我們重點在M系統(tǒng)上關(guān)注和適配的部分.
官網(wǎng)權(quán)威說明, 具體相關(guān)權(quán)限見圖:
Special Permissions- 特殊權(quán)限
SYSTEM_ALERT_WINDOW and WRITE_SETTINGS, 這兩個權(quán)限比較特殊,不能通過代碼申請方式獲取咨堤,必須得用戶打開軟件設(shè)置頁手動打開菇篡,才能授權(quán).
There are a couple of permissions that don’t behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are particularly sensitive, so most apps should not use them. If an app needs one of these permissions, it must declare the permission in the manifest, and send an intent requesting the user’s authorization. The system responds to the intent by showing a detailed management screen to the user.
我們對相關(guān)申請方法封裝成了工具類,方便m系統(tǒng)適配隨時調(diào)用.
compileSdkVersion and targetSdkVersion 設(shè)置為 23開始
private void testAlertPermission() {
WindowManager mWindowManager = (WindowManager) getSystemService(
Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
mWindowManager.addView(new TextView(this), params);
}
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
requestPermissions方法調(diào)用時會彈出以下對話框.當(dāng)用戶點擊拒絕并且勾選了不再彈出后這個對話框?qū)⒉粫購棾鲆淮瑫苯泳芙^掉該權(quán)限:
shouldShowRequestPermissionRationale方法說明
用戶拒絕驱还,或是不在彈出,這個方法會返回false.
Activity activity = (Activity) cxt;
return ActivityCompat.checkSelfPermission(activity,
permission) == PackageManager.PERMISSION_GRANTED;
} else if (cxt instanceof Fragment) {
Fragment fragment = (Fragment) cxt;
return fragment.getActivity().checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
} else {
throw new RuntimeException("cxt is net a activity or fragment");
}
}
private static String[] checkSelfPermissionArray(Object cxt, String[] permission) {
ArrayList permiList = new ArrayList<>();
for (String p : permission) {
if (!checkSelfPermissionWrapper(cxt, p)) {
permiList.add(p);
}
}
return permiList.toArray(new String[permiList.size()]);
Activity和Fragment的申請方法不一樣凸克,所以我們對方法做了包裝如下:
@TargetApi(Build.VERSION_CODES.M)
public static boolean checkPermission(Object cxt, String permission, int requestCode) {
if (!checkSelfPermissionWrapper(cxt, permission)) {
if (!shouldShowRequestPermissionRationaleWrapper(cxt, permission)) {
requestPermissionsWrapper(cxt, new String[]{permission}, requestCode);
} else {
Log.d(TAG, "should show rational");
}
return false;
}
return true;
}
private static void requestPermissionsWrapper(Object cxt, String[] permission, int requestCode) {
if (cxt instanceof Activity) {
Activity activity = (Activity) cxt;
ActivityCompat.requestPermissions(activity, permission, requestCode);
} else if (cxt instanceof Fragment) {
Fragment fragment = (Fragment) cxt;
fragment.requestPermissions(permission, requestCode);
} else {
throw new RuntimeException("cxt is net a activity or fragment");
}
}
如圖一次可以申請多個權(quán)限议蟆,但是用戶還是一個一個授權(quán).我們對該請求也做了封裝:
@TargetApi(23)
private static boolean checkSelfPermissionWrapper(Object cxt, String permission) {
if (cxt instanceof Activity) {
Activity activity = (Activity) cxt;
return ActivityCompat.checkSelfPermission(activity,
permission) == PackageManager.PERMISSION_GRANTED;
} else if (cxt instanceof Fragment) {
Fragment fragment = (Fragment) cxt;
return fragment.getActivity().checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
} else {
throw new RuntimeException("cxt is net a activity or fragment");
}
}
private static String[] checkSelfPermissionArray(Object cxt, String[] permission) {
ArrayList permiList = new ArrayList<>();
for (String p : permission) {
if (!checkSelfPermissionWrapper(cxt, p)) {
permiList.add(p);
}
}
return permiList.toArray(new String[permiList.size()]);
}
在activity或fragment 中重寫onRequestPermissionsResult,用戶處理相關(guān)權(quán)限后會回調(diào)該方法萎战,當(dāng)活取到相關(guān)應(yīng)用后可以繼續(xù)原來的邏輯.
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PermissionUtils.PERMISSION_REQUEST_CODE:
if (PermissionUtils.verifyPermissions(grantResults)) {
// Permission Granted
// do you action
} else {
// Permission Denied
Toast.makeText(this, "WRITE_CONTACTS Denied", Toast.LENGTH_SHORT)
.show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public static boolean verifyPermissions(int[] grantResults) {
// At least one result must be checked.
if (grantResults.length < 1) {
return false;
}
// Verify that each required permission has been granted, otherwise return false.
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
以前特殊權(quán)限說明地方已經(jīng)支出咐容,該類權(quán)限需求intent到具體的設(shè)置頁面,讓用戶手動打開蚂维,才能授權(quán).
同樣重寫onActivityResult方法戳粒,返回該頁面時做回調(diào)處理.
系統(tǒng)彈出權(quán)限,相關(guān)代碼實例:
/**
* 檢測系統(tǒng)彈出權(quán)限
* @param cxt
* @param req
* @return
*/
@TargetApi(23)
public static boolean checkSettingAlertPermission(Object cxt, int req) {
if (cxt instanceof Activity) {
Activity activity = (Activity) cxt;
if (!Settings.canDrawOverlays(activity.getBaseContext())) {
Log.i(TAG, "Setting not permission");
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + activity.getPackageName()));
activity.startActivityForResult(intent, req);
return false;
}
} else if (cxt instanceof Fragment) {
Fragment fragment = (Fragment) cxt;
if (!Settings.canDrawOverlays(fragment.getActivity())) {
Log.i(TAG, "Setting not permission");
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + fragment.getActivity().getPackageName()));
fragment.startActivityForResult(intent, req);
return false;
}
} else {
throw new RuntimeException("cxt is net a activity or fragment");
}
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PermissionUtils.PERMISSION_SETTING_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
// do something
} else {
Toast.makeText(this, "not has setting permission", Toast.LENGTH_LONG).show();
finish();
}
}
}
}
Android 6.0系統(tǒng)權(quán)限管理是安卓系統(tǒng)的一大進(jìn)步虫啥,為安卓手機(jī)用戶提供了一個安全干凈系統(tǒng)前提蔚约,鑒于google對未授權(quán)應(yīng)用的奔潰方式處理,
安卓開發(fā)者應(yīng)當(dāng)盡早適配6.0系統(tǒng)涂籽,提示軟件體驗.
實戰(zhàn)整體代碼已提交到GitHub(https://github.com/CankingApp/PermissionDemo),歡迎下載交流學(xué)習(xí)~
博客:http://cankingapp.github.io/2016/03/18/android-permission/