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"/>