天天看點

談談Android 6.0 的動态權限管理

Android适配系列:

  • Android 6.0 的動态權限管理
  • Android 7.0脫坑指南
  • Android 8.0适配指北
  • Android 9.0 适配指南

1.前言

大家都知道Android 6.0的新特性之一就是應用權限的管理。也就是說凡是涉及使用者隐私的權限,使用者可以自己去設定管理了。然而在6.0以前,我們安裝一款APP是預設同意此APP所需的所有權限(比如定位、通路通訊錄),不同意就不能安裝。當然,國内的一些手機廠商基于Android定制的系統中,可以實作在6.0以前關閉指定的權限。如下圖:

談談Android 6.0 的動态權限管理
談談Android 6.0 的動态權限管理

2.危險權限清單(Dangerous Permission)

Dangerous Permission一般都是涉及使用者隐私的權限。

談談Android 6.0 的動态權限管理

從上面的圖檔中可以看到,攝像頭、電話、定位等等都是我們平常開發中常用的權限。

3.可以在6.0不适配權限管理嗎?

答案是可以,但是不推薦。

首先說怎麼不适配,那就是設定targetSdkVersion小于23(Android 6.0系統預設為targetSdkVersion小于23的應用預設授予了所申請的所有權限,是以如果您APP設定的targetSdkVersion低于23,在運作時也不會崩潰。)

有人一看這不是挺好的嘛,解決問題。那麼我想告訴你,首先這不是長久之計,早晚都要面對的。你不可能永遠targetSdkVersion低于23。其次,它是有一個前提,那就是使用者自己不去操作權限。要知道如果使用者是6.0以上的手機或是國内部分6.0以前的手機,他可以自己在設定中關閉權限,那麼到時APP因為沒有權限擷取資料異常,導緻空指針的異常時,APP就會崩潰。

4.怎麼适配

首先Android Studio:

在build.gradle中聲明targetSdkVersion為23及以上。

Eclipse:

在AndroidManifest.xml中聲明targetSdkVersion為23及以上。

這裡引用高德定位Demo的CheckPermissionsActivity類,代碼如下:

/**
 * 繼承了Activity,實作Android6.0的運作時權限檢測
 * 需要進行運作時權限檢測的Activity可以繼承這個類
 * 
 * @建立時間:2016年5月27日 下午3:01:31 
 * @項目名稱: AMapLocationDemo
 * @author hongming.wang
 * @檔案名稱:PermissionsChecker.java
 * @類型名稱:PermissionsChecker
 * @since 2.5.0
 */
public class CheckPermissionsActivity extends Activity {
	/**
	 * 需要進行檢測的權限數組
	 */
	protected String[] needPermissions = {
			Manifest.permission.ACCESS_COARSE_LOCATION,
			Manifest.permission.ACCESS_FINE_LOCATION,
			Manifest.permission.WRITE_EXTERNAL_STORAGE,
			Manifest.permission.READ_EXTERNAL_STORAGE,
			Manifest.permission.READ_PHONE_STATE
			};
	
	private static final int PERMISSON_REQUESTCODE = 0;
	
	/**
	 * 判斷是否需要檢測,防止不停的彈框
	 */
	private boolean isNeedCheck = true;
	
	@Override
	protected void onResume() {
		super.onResume();
		if (Build.VERSION.SDK_INT >= 23
				&& getApplicationInfo().targetSdkVersion >= 23) {
			if (isNeedCheck) {
				checkPermissions(needPermissions);
			}
		}
	}
	
	/**
	 * 
	 * @param permissions
	 * @since 2.5.0
	 * requestPermissions方法是請求某一權限,
	 */
	private void checkPermissions(String... permissions) {
		try {
			if (Build.VERSION.SDK_INT >= 23
					&& getApplicationInfo().targetSdkVersion >= 23) {
				List<String> needRequestPermissonList = findDeniedPermissions(permissions);
				if (null != needRequestPermissonList
						&& needRequestPermissonList.size() > 0) {
					String[] array = needRequestPermissonList.toArray(new String[needRequestPermissonList.size()]);
					Method method = getClass().getMethod("requestPermissions", new Class[]{String[].class,
							int.class});

					method.invoke(this, array, PERMISSON_REQUESTCODE);
				}
			}
		} catch (Throwable e) {
		}
	}

	/**
	 * 擷取權限集中需要申請權限的清單
	 * 
	 * @param permissions
	 * @return
	 * @since 2.5.0
	 * checkSelfPermission方法是在用來判斷是否app已經擷取到某一個權限
     * shouldShowRequestPermissionRationale方法用來判斷是否
     * 顯示申請權限對話框,如果同意了或者不在詢問則傳回false
	 */
	private List<String> findDeniedPermissions(String[] permissions) {
		List<String> needRequestPermissonList = new ArrayList<String>();
		if (Build.VERSION.SDK_INT >= 23
				&& getApplicationInfo().targetSdkVersion >= 23){
			try {
				for (String perm : permissions) {
					Method checkSelfMethod = getClass().getMethod("checkSelfPermission", String.class);
					Method shouldShowRequestPermissionRationaleMethod = getClass().getMethod("shouldShowRequestPermissionRationale",
							String.class);
					if ((Integer)checkSelfMethod.invoke(this, perm) != PackageManager.PERMISSION_GRANTED
							|| (Boolean)shouldShowRequestPermissionRationaleMethod.invoke(this, perm)) {
						needRequestPermissonList.add(perm);
					}
				}
			} catch (Throwable e) {

			}
		}
		return needRequestPermissonList;
	}

	/**
	 * 檢測是否所有的權限都已經授權
	 * @param grantResults
	 * @return
	 * @since 2.5.0
	 *
	 */
	private boolean verifyPermissions(int[] grantResults) {
		for (int result : grantResults) {
			if (result != PackageManager.PERMISSION_GRANTED) {
				return false;
			}
		}
		return true;
	}
	
   /**
    * 申請權限結果的回調方法
    */
	@TargetApi(23)
	public void onRequestPermissionsResult(int requestCode,
			String[] permissions, int[] paramArrayOfInt) {
		if (requestCode == PERMISSON_REQUESTCODE) {
			if (!verifyPermissions(paramArrayOfInt)) {
				showMissingPermissionDialog();
				isNeedCheck = false;
			}
		}
	}

	/**
	 * 顯示提示資訊
	 * 
	 * @since 2.5.0
	 *
	 */
	private void showMissingPermissionDialog() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(R.string.notifyTitle);
		builder.setMessage(R.string.notifyMsg);

		// 拒絕, 退出應用
		builder.setNegativeButton(R.string.cancel,
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						finish();
					}
				});

		builder.setPositiveButton(R.string.setting,
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						startAppSettings();
					}
				});

		builder.setCancelable(false);

		builder.show();
	}

	/**
	 *  啟動應用的設定
	 * 
	 * @since 2.5.0
	 *
	 */
	private void startAppSettings() {
		Intent intent = new Intent(
				Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
		intent.setData(Uri.parse("package:" + getPackageName()));
		startActivity(intent);
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if(keyCode == KeyEvent.KEYCODE_BACK){
			this.finish();
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}
		
}

           

我在上面的類中,自己加入了一些注釋,大家仔細看就可以明白了。

補充:小米手機在動态權限這裡還需要一些相容,我們需要注意一下。當然對于國内部分6.0以前手機,隻能在需要權限去去捕獲異常來處理了。

當然不止上面一種實作方法,github上有許多大神開源的封裝庫,可以很友善的實作權限适配。我推薦兩個庫,大家根據需求選擇:

1. PermissionsDispatcher

2. 鴻洋大神的MPermissions

5.參考

1. Android M 新的運作時權限開發者需要知道的一切

2. 高德地圖定位API