Android自发布第一个版本到 现在,已经十多个年头,android
O现都已经面世,对于不同的版本需要直到其中的差异性,方可做到广泛的向下兼容。
Android6.0之前,开发过程中使用的权限只要在manifest文件中声明即可;不过在Android6.0(SDK23)之后,谷歌引入动态权限机制,将权限分为敏感权限和一般权限,在涉及一些敏感权限时,不仅需要在配置文件中声明,还需要在程序运行过程中动态申请权限;
1、一般权限
一般权限包括如下部分,这些权限使用时不需要动态申请,因此可以将所有的权限放到配置文件中,因为这些权限不会涉及用户隐私,因此无需考虑安全问题。
<!-- Normal Permission -->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.REORDER_TASKS"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.SET_TIME_ZONE"/>
<uses-permission android:name="android.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS"/>
<uses-permission android:name="android.permission.TRANSMIT_IR"/>
<uses-permission android:name="android.permission.UNINSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.USE_PINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
2、敏感权限
与一般权限不同的是,如果软件需要敏感权限,比如读取联系人列表时,需要 动态的向用户申请 ,如果不进行申请而直接使用,则 会导致应用崩溃 。
<!-- Dangerous Permissions -->
<!-- group:android.permission-group.CONTACTS -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!-- group:android.permission-group.PHONE -->
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
<!-- group:android.permission-group.CALENDAR -->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!-- group:android.permission-group.CAMERA -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- group:android.permission-group.SENSORS -->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!-- group:android.permission-group.LOCATION -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- group:android.permission-group.STORAGE -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- group:android.permission-group.MICROPHONE -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- group:android.permission-group.SMS -->
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_CELL_BORADCASTS"/>
敏感权限被谷歌分为九组,默认 只要申请组内的一个权限,就可以拥有组内其他权限的使用权,这九组权限包括:联系人、电话、日历、照相机、传感器、地理位置、外部存储、录音机、短信
动态申请权限时需要借助Activity的方法:
//上下文对象
protected Activity baseActivity;
//请求所有需要的权限
private static final int PERMISSION_ALL = 0x00;
...
//api如果大于23,需要动态申请权限
if (Build.VERSION.SDK_INT >= 23) {
//校验是否已具有外部访问存储权限
//校验是否已具有访问联系人权限
//校验是否已具有电话权限
if (baseActivity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED || baseActivity.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED||baseActivity.checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
//如果没有相应权限,则动态进行申请
baseActivity.requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE,android.Manifest.permission.READ_CONTACTS,android.Manifest.permission.CALL_PHONE}, PERMISSION_ALL);
}else{
//如果有权限进行逻辑处理
}
}
然后覆盖Activity的onRequestPermissionsResult方法,检测动态权限是否申请成功:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[]
grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//请求读取联系人
if (requestCode == PERMISSION_ALL) {
for(int i=0;i<grantResults.length;i++){
if(grantResults[i]==PackageManager.PERMISSION_GRANTED){
//permissions[i]权限动态申请成功,得到相应授权,进行相应的逻辑处理
}
}
}
}
如果权限申请失败,可以让界面退出或者禁止进行相应操作,否则程序会崩溃;
动态申请权限如果在onResume中执行,则需要设置标志位,在用户第一次明确拒绝后,不应当重复的弹出授权框;
1、联系人权限、电话权限
如果动态权限申请成功,则可以读取系统的隐私信息或做一些敏感操作,如获取联系人列表:
Cursor cursor = baseActivity.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
ContactBean bean = new ContactBean();
bean.setName(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
bean.setPhone(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
} while (cursor.moveToNext());
}
根据phone字段还可以进入拨号界面:
/**
* 进入拨号界面
*/
private void dial(String phone) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel://" + phone));
startActivity(intent);
}
或者直接拨打电话:
/**
* 直接拨打电话
*/
private void call(String phone) {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel://" + phone));
startActivity(intent);
}
3、特殊权限
除了一般权限和敏感权限之外,还有两个特殊权限,这两个权限无法通过动态申请获得,必须通过系统的设置界面才可以启用。
<!--特殊权限-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>
1、android.permission.SYSTEM_ALERT_WINDOW
final WindowManager windowManager =(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
final WindowManager.LayoutParams parames = new WindowManager.LayoutParams();
parames.type = WindowManager.LayoutParams.TYPE_PHONE;
//floatView为需要添加的悬浮窗口视图
windowManager.addView(floatView, parames);
在自定义通过WindowManagerService的addView方法向界面中添加View时,需要设置type属性,如果设定的type超过了FIRST_SYSTEM_WINDOW,则表示弹出框为系统级别,此时就需要android.permission.SYSTEM_ALERT_WINDOW权限,表示“允许在应用上绘制图形”或者说是“允许显示悬浮窗”
检查悬浮窗权限是否打开
public static boolean checkFloatPermission(Context context, String permission) {
if (permission.equalsIgnoreCase(Manifest.permission.SYSTEM_ALERT_WINDOW) && getSDKVersion() >= 23) {
return Settings.canDrawOverlays(context);
}
return false;
}
如果权限 未开,则进行申请:
该权限申请方式与一般动态申请权限方法不同,一般默认不会弹出授权窗口,而是跳转到手机预置软件的设置界面,用户需要在系统的设置界面打开悬浮窗权限。
//开始 悬浮窗服务
private static final int PERMISSION_REQUEST_CODE_SYSTEM_ALERT_WINDOW = 1;
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, PERMISSION_REQUEST_CODE_SYSTEM_ALERT_WINDOW);
2、BIND_ACCESSIBILITY_SERVICE
该权限是自定义辅助功能需要的权限,这里辅助功能是指:一键安装等;
辅助功能是指通过服务的方法,配置xml文件,然后获取界面中的信息,可以自动去触发点击 事件,模拟用户的操作;
第一步,在minifest文件中进行注册
<service
android:name=".service.StaticInstallAPKService"
android:label="自动安装软件"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config"/>
</service>
第二步,定义xml文件
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
android:accessibilityEventTypes="typeWindowContentChanged"
android:description="@string/service_static_install_apk_description"
android:packageNames="com.android.packageinstaller,com.mokee.packageinstaller"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:accessibilityFlags="flagDefault"
android:settingsActivity="com.mnlin.hotchpotch.service.StaticInstallAPKService"
android:canRetrieveWindowContent="true"
android:canRequestTouchExplorationMode="true"
android:canRequestEnhancedWebAccessibility="true"
xmlns:android="http://schemas.android.com/apk/res/android"/>
第三步,自定义辅助service
public class StaticInstallAPKService extends AccessibilityService {
private static final String TAG = "StaticInstallAPKService";
private Map<Integer, Boolean> handleMap = new HashMap<>();
public StaticInstallAPKService() {
}
@Override
public void onInterrupt() {
Log.e(TAG, "onInterrupt: ");
}
@Override
protected void onServiceConnected() {
Log.e(TAG, "onServiceConnected: " + "检测到服务有响应");
super.onServiceConnected();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeInfo = event.getSource();
Log.e("onAccessibilityEvent", event.toString());
//获取根节点
while (nodeInfo.getParent() != null) {
nodeInfo = nodeInfo.getParent();
}
if (nodeInfo != null) {
int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
Log.e("根节点类型", nodeInfo.getWindowId() + " " + nodeInfo.getClassName());
iterateNodesAndHandle(nodeInfo);
}
}
}
private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) {
int childCount = nodeInfo.getChildCount();
if ("android.widget.Button".equals(nodeInfo.getClassName()) || "android.widget.TextView".equals(nodeInfo.getClassName())) {
String nodeContent = nodeInfo.getText().toString();
Log.e(TAG, "content is :" + nodeContent);
if (checkKeyWord(nodeContent)) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
}
}
for (int i = 0; i < childCount; i++) {
if (iterateNodesAndHandle(nodeInfo.getChild(i))) {
return true;
}
}
return false;
}
/**
* 检验关键字,如果按钮或文本中有该关键字,则返回true,表示需要主动触发点击事件
*/
private boolean checkKeyWord(String content) {
String[] keyWords = new String[]{"安装", "完成", "确定", "继续安装"};
for (String s : keyWords) {
if (s.trim().replaceAll("\\W", "").equalsIgnoreCase(content.trim().replaceAll("\\W", ""))) {
return true;
}
}
return false;
}
}
此时,辅助服务类编写完成,然后在activity中启动该service;
第四步、检测辅助服务是否开启
/**
* @return 检测某个辅助服务是否开启
*/
public static boolean isAccessibilitySettingsOn(Context context, Class clazz) {
try {
String service = context.getPackageName() + "/" + clazz.getCanonicalName();
int enable = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
if (enable == 1) {
String value = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
if (value != null) {
splitter.setString(value);
while (splitter.hasNext()) {
String temp = splitter.next();
Log.v("以下辅助服务已经开启:", temp);
if (temp.equalsIgnoreCase(service)) {
//有权限
return true;
}
}
}
}
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
return false;
}
该方法传入两个参数:Context上下文、服务类的class类型
如果辅助服务没有打开,则:
第五步,请求开启辅助服务
//如果没有辅助服务权限,则需要申请打开
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
该intent会使设备跳转到系统的设置界面,需要用户手动打开该辅助功能;
4、自定义权限
有时如果开发者需要将接口或服务适当暴露给其他应用,这时候可以自定义权限,只有申请了该权限的应用才可以远程绑定本应用的服务。
自定义权限需要先在manifest中声明:
<!-- 自定义permission -->
<permission
android:name="com.mnlin.hotchpotch.permission.ACCESS_SECRET_AIDL"
android:label="权限:访问secret服务"
android:protectionLevel="normal"/>
然后在service中验证远端是否具有该权限,如果没有权限,则拒绝bind方法:
@Override
public IBinder onBind(Intent intent) {
int checked = checkCallingOrSelfPermission("com.mnlin.hotchpotch.permission.ACCESS_SECRET_AIDL");
if (checked == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onBind: " + "绑定服务");
// TODO: 返回具体的binder对象
} else {
Log.d(TAG, "onBind: " + "没有绑定服务的权限");
return null;
}
}
如果某个应用A需要远程绑定该service,需要在应用A的manifest中声明:
<uses-permission android:name="com.mnlin.hotchpotch.permission.ACCESS_SECRET_AIDL"/>