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"/>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章