Permission 使用詳解


極力推薦文章:歡迎收藏
Android 干貨分享

閱讀五分鍾,每日十點,和您一起終身學習,這里是程序員Android

本篇文章主要介紹 Android 開發中的部分知識點,通過閱讀本篇文章,您將收獲以下內容:

  1. 安全架構設計
  2. 應用簽名
  3. 用戶ID 和文件訪問
  4. 使用權限
  5. 正常權限和危險權限
  6. 自定義權限
  7. 動態申請權限案例

Android 是一個權限分隔的操作系統,其中每個應用都有其獨特的系統標識(Linux 用戶 ID 和組 ID)。系統各部分也分隔為不同的標識。Linux 據此將不同的應用以及應用與系統分隔開來。

其他更詳細的安全功能通過“權限”機制提供,此機制會限制特定進程可以執行的具體操作,並且根據URI權限授權臨時訪問特定的數據段。

Permission 簡介

Permission 繼承關系

java.lang.Object
   ↳	android.Manifest.permission

1. 安全架構設計

Android 安全架構的中心設計點是:
在默認情況下任何應用都沒有權限執行對其他應用、操作系統或用戶有不利影響的任何操作。這包括讀取或寫入用戶的私有數據(例如聯系人或電子郵件)、讀取或寫入其他應用程序的文件、執行網絡訪問、使設備保持喚醒狀態等。

由於每個 Android 應用都是在進程沙盒中運行,因此應用必須顯式共享資源和數據。它們的方法是聲明需要哪些權限來獲取基本沙盒未提供的額外功能。應用以靜態方式聲明它們需要的權限,然后 Android 系統提示用戶同意。

2. 應用簽名

所有 APK(.apk 文件)都必須使用證書簽署,其私鑰由開發者持有。此證書用於識別應用的作者。證書不需要由證書頒發機構簽署;Android 應用在理想情況下可以而且通常也是使用自簽名證書。證書在 Android 中的作用是識別應用的作者。這允許系統授予或拒絕應用對簽名級權限的訪問,以及授予或拒絕應用獲得與另一應用相同的 Linux 身份的請求。

比如:
聲明一個安全權限,可用於限制對此或其他應用程序的特定組件或功能的訪問。

權限聲明

3. 用戶ID 和文件訪問

在安裝時,Android 為每個軟件包提供唯一的 Linux 用戶 ID。此 ID 在軟件包在該設備上的使用壽命期間保持不變。在不同設備上,相同軟件包可能有不同的 UID;重要的是每個軟件包在指定設備上的UID是唯一的。

由於在進程級實施安全性,因此任何兩個軟件包的代碼通常都不能在同一進程中運行,因為它們需要作為不同的 Linux 用戶運行。您可以在每個軟件包的 AndroidManifest.xmlmanifest 標記中使用 sharedUserId 屬性,為它們分配相同的用戶 ID。這樣做以后,出於安全目的,兩個軟件包將被視為同一個應用,具有相同的用戶 ID 和文件權限。

為保持安全性,只有兩個簽署了相同簽名(並且請求相同的 sharedUserId)的應用才被分配同一用戶 ID

應用存儲的任何數據都會被分配該應用的用戶 ID,並且其他軟件包通常無法訪問這些數據。使用 getSharedPreferences(String, int)、openFileOutput(String, int) openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory) 創建新文件時,可以使用 MODE_WORLD_READABLE MODE_WORLD_WRITEABLE 標記允許任何其他軟件包讀取/寫入文件。設置這些標記時,文件仍歸您的應用所有,但其全局讀取和/或寫入權限已適當設置,使任何其他應用都可看見它。

4. 使用權限

基本 Android 應用默認情況下未關聯權限,這意味着它無法執行對用戶體驗或設備上任何數據產生不利影響的任何操作。要利用受保護的設備功能,必須在應用清單中包含一個或多個 <uses-permission> 標記。

例如,需要監控傳入的短信的應用要指定:

接收短信權限

權限分類:

  • 正常權限

不會對用戶隱私或設備操作造成很大風險的權限,系統會自動授予這些權限。

  • 危險權限

可能影響用戶隱私或設備正常操作的權限,系統會要求用戶明確授予這些權限,否則默認不授予。

注意:
Android 6.0 之后(targetSdkVersion SDK 23之上)的版本,處於對手機用戶更安全的機制考慮,在應用使用可能影響用戶隱私的危險權限時,需要動態申請權限,必須用戶手動授予權限才可以,僅在Androidmainfest.xml 中申請權限是不夠的。

高版本Android系統中,如不動態申請權限,則會報SecurityException的錯誤,但不能保證每個地方都是這樣。例如,sendBroadcast(Intent) 方法在數據傳遞到每個接收者時會檢查權限,在方法調用返回后,即使權限失效,您也不會收到異常。但在幾乎所有情況下,權限失效會記入系統日志。

特定使用權限場景

  • 在調用系統時,防止應用執行某些功能。
  • 在啟動 Activity 時,防止應用啟動其他應用的 Activity
  • 在發送和接收廣播時,控制誰可以接收您的廣播,誰可以向您發送廣播。
  • 在訪問和操作內容提供程序時。
  • 綁定至服務或啟動服務。

5. 正常權限和危險權限

    1. 正常權限涵蓋應用需要訪問其沙盒外部數據或資源,但對用戶隱私或其他應用操作風險很小的區域。例如,設置時區的權限就是正常權限。如果應用聲明其需要正常權限,系統會自動向應用授予該權限。
    1. 危險權限涵蓋應用需要涉及用戶隱私信息的數據或資源,或者可能對用戶存儲的數據或其他應用的操作產生影響的區域。例如,能夠讀取用戶的聯系人屬於危險權限。如果應用聲明其需要危險權限,則用戶必須明確向應用授予該權限。
  • 權限組

所有危險的 Android 系統權限都屬於權限組。

  • 如果應用請求其清單中列出的危險權限,而應用目前在權限組中沒有任何權限,則系統會向用戶顯示一個對話框,描述應用要訪問的權限組。對話框不描述該組內的具體權限。例如,如果應用請求 READ_CONTACTS 權限,系統對話框只說明該應用需要訪問設備的聯系信息。如果用戶批准,系統將向應用授予其請求的權限。

  • 如果應用請求其清單中列出的危險權限,而應用在同一權限組中已有另一項危險權限,則系統會立即授予該權限,而無需與用戶進行任何交互。例如,如果某應用已經請求並且被授予了 READ_CONTACTS 權限,然后它又請求 WRITE_CONTACTS,系統將立即授予該權限。

任何權限都可屬於一個權限組,包括正常權限和應用定義的權限。但權限組僅當權限危險時才影響用戶體驗。可以忽略正常權限的權限組。

危險權限和權限組

要實施您自己的權限,必須先使用一個或多個 <permission> 元素在 AndroidManifest.xml 中聲明它們。 例如,想要控制誰可以開始其中一個 Activity 的應用可如下所示聲明此操作的權限:

自定義權限

6. 自定義權限

  • 如果要設計一套向彼此顯示功能的應用,請盡可能將應用設計為每個權限只定義一次。如果所有應用並非使用同一證書簽署,則必須這樣做。即使所有應用使用同一證書簽署,最佳做法也是每個權限只定義一次。

  • 如果功能僅適用於使用與提供應用相同的簽名所簽署的應用,您可能可以使用簽名檢查避免定義自定義權限。當一個應用向另一個應用發出請求時,第二個應用可在遵從該請求之前驗證這兩個應用是否使用同一證書簽署。

  • 如果您要開發一套只在您自己的設備上運行的應用,則應開發並安裝管理該套件中所有應用權限的軟件包。此軟件包本身無需提供任何服務。它只是聲明所有權限,然后套件中的其他應用通過<uses-permission>元素請求這些權限。

  • 在 AndroidManifest.xml 中實施權限

您可以通過AndroidManifest.xml應用高級權限,限制訪問系統或應用的全部組件。要執行此操作,在所需的組件上包含 android:permission 屬性,為用於控制訪問它的權限命名。

    1. Activity 權限

(應用於<activity>標記)限制誰可以啟動相關的 Activity。在 Context.startActivity() Activity.startActivityForResult() 時會檢查權限;如果調用方沒有所需的權限,則調用會拋出 SecurityException

  • 2 . Service 權限

(應用於 <service> 標記)限制誰可以啟動或綁定到相關的服務。在 Context.startService()、Context.stopService() Context.bindService() 時會檢查權限;如果調用方沒有所需的權限,則調用會拋出 SecurityException

  • 3 .BroadcastReceiver 權限

(應用於 <receiver> 標記)限制誰可以發送廣播給相關的接收方。在 Context.sendBroadcast() 返回后檢查權限,因為系統會嘗試將提交的廣播傳遞到指定的接收方。因此,權限失效不會導致向調用方拋回異常;只是不會傳遞該 intent

同樣,可以向 Context.registerReceiver() 提供權限來控制誰可以廣播到以編程方式注冊的接收方。另一方面,可以在調用 Context.sendBroadcast() 時提供權限來限制允許哪些 BroadcastReceiver 對象接收廣播。

  • 4 .ContentProvider 權限

(應用於<provider>標記)限制誰可以訪問ContentProvider中的數據。(內容提供程序有重要的附加安全工具可用,稱為 URI 權限,將在后面介紹。)與其他組件不同,您可以設置兩個單獨的權限屬性:android:readPermission 限制誰可以讀取提供程序,android:writePermission 限制誰可以寫入提供程序。

請注意,如果提供程序有讀取和寫入權限保護,僅擁有寫入權限並不表示您可以讀取提供程序。第一次檢索提供程序時將會檢查權限(如果沒有任何權限,將會拋出 SecurityException),對提供程序執行操作時也會檢查權限。使用 ContentResolver.query() 需要擁有讀取權限;使用 ContentResolver.insert()、ContentResolver.update()、ContentResolver.delete() 需要寫入權限。在所有這些情況下,沒有所需的權限將導致調用拋出 SecurityException

7. 動態申請權限Demo

  • 實現效果如下:

動態申請權限

引導用戶進入Settings 中進行手動管理權限

Settings 中權限管理

  • 實現代碼如下:
public class PermissionActivity extends Activity {

	private static final int PERMISSION_REQUEST_CODE = 10000;

	String[] mPermissionsArrays = { Manifest.permission.READ_CONTACTS,
			Manifest.permission.READ_PHONE_STATE,
			Manifest.permission.WRITE_EXTERNAL_STORAGE };

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_permission);
	}

	@SuppressLint("NewApi")
	public void PermissionOnClick(View view) {

		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
			// Android 6.0 之下無須動態申請權限 ,讓用戶手動授權
			Toast.makeText(getApplicationContext(),
					"Android 6.0 以下版本不需要用戶手動授權", 0).show();
		} else {

			boolean isAllGranted = checkPermissionAllGranted(mPermissionsArrays);
			// 如果已經授權,繼續執行其他任務
			if (isAllGranted) {
				//
				Toast.makeText(getApplicationContext(), "已經授權", 0).show();

			} else {
				// 沒有授權則開啟申請授權流程
				requestPermissions(mPermissionsArrays, PERMISSION_REQUEST_CODE);
			}
		}

	}

	/**
	 * 檢查是否擁有指定的所有權限
	 */
	@SuppressLint("NewApi")
	private boolean checkPermissionAllGranted(String[] permissions) {

		for (String permission : permissions) {
			if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
				// 只要有一個權限沒有被授予, 則直接返回 false
				return false;
			}
		}
		return true;
	}

	/***
	 * 在授權權限返回結果時候 處理
	 * 
	 * **/
	@Override
	public void onRequestPermissionsResult(int requestCode,
			String[] permissions, int[] grantResults) {

		if (requestCode == PERMISSION_REQUEST_CODE) {
			boolean isAllGranted = true;

			for (int grant : grantResults) {
				if (grant != PackageManager.PERMISSION_GRANTED) {
					isAllGranted = false;
					break;
				}
			}
			if (isAllGranted) {
				// 如果所有的權限都授予了, 則執行備份代碼
				Toast.makeText(getApplicationContext(), "已經授權", 0).show();
			} else {

				// 彈出對話框告訴用戶需要權限的原因, 並引導用戶去應用權限管理中手動打開權限按鈕
				openPermissionsDialog();
			}
		}
	}

	private void openPermissionsDialog() {

		AlertDialog.Builder builder = new AlertDialog.Builder(this,
				android.R.style.Theme_Material_Light_Dialog_Alert);
		builder.setMessage("需要獲取 聯系人、 SD卡、手機狀態權限 ,請允許授權,否則會影響您的使用。如需關閉,請到 設置-應用信息 -> 權限 中關閉!");
		builder.setPositiveButton("去手動授權",
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {

						Intent intent = new Intent();
						intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
						intent.addCategory(Intent.CATEGORY_DEFAULT);
						intent.setData(Uri.parse("package:" + getPackageName()));
						intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
						intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
						intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);

						startActivity(intent);
					}
				});
		builder.setNegativeButton("取消", null);
		builder.show();
	}
}

注意:需要在AndroidMainfest.xml 中申請權限

AndroidMainfest.xml 中聲明權限
`

至此,本篇已結束,如有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

微信關注公眾號:  程序員Android,領福利


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM