Android开发&权限机制

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);

在自定义通过WindowManagerServiceaddView方法向界面中添加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"/>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值