RuntimePermission 데이터 구조
안드로이드 프레임워크에서 권한의 부여, 취소, 확인 등의 작업은 복잡하게 얽힌 데이터 구조를 통해 관리됩니다. 소스 코드를 효율적으로 이해하기 위해서는 아래와 같은 계층적인 데이터 매핑 구조를 파악하는 것이 중요합니다.
- 패키지 단위 관리: 최상위 레벨은 패키지 명(Package Name)을 키로 하고, 해당 앱의 권한 정보를 담은
Package객체를 값으로 하는ArrayMap구조입니다. 각Package객체 내부에는PermissionsState클래스가 존재합니다. - 권한 명칭 기반 매핑:
PermissionsState내부에는 권한 이름(String)을 키로 하고PermissionData를 값으로 갖는ArrayMap이 들어있습니다. - 멀티 유저 지원:
PermissionData클래스 안에는SparseArray<PermissionState>가 존재하며, 정수형 유저 ID(userId)를 키로 하여 해당 유저의 권한 상태인PermissionState(단수형에 유의)를 저장합니다. - 개별 권한 상태: 최종적으로
PermissionState객체는 구체적인 권한 이름, 부여 여부(granted), 그리고 관련 플래그(flag) 정보를 보유합니다.
권한 데이터의 영속성 저장
런타임 권한 정보는 기기를 재부팅해도 유지되어야 하므로 파일 시스템에 저장됩니다. 저장 경로는 보통 /data/system/users/[userId]/runtime-permissions.xml입니다. 예를 들어 기본 사용자의 경우 /data/system/users/0/ 경로에서 확인할 수 있으며, XML 내부에는 위 구조에서 살펴본 권한 이름, 부여 상태, 플래그 등이 기록됩니다.
앱 설치 시 권한 부여 프로세스
애플리케이션 설치 시 AndroidManifest.xml에 선언된 권한들은 유형에 따라 다르게 처리됩니다. 안드로이드 프레임워크의 PermissionManagerService 내 restorePermissionState()(안드로이드 10 기준, 이전 버전은 grantPermissions()) 메서드가 이 역할을 담당합니다.
특히 targetSdkVersion의 값이 23(Marshmallow) 이상인지 여부에 따라 권한 부여 동작이 크게 달라집니다.
- Target SDK >= 23:
INTERNET과 같은 일반(Normal) 권한은 즉시 부여되지만, 런타임 권한은 설치 시점에 허용되지 않으며 앱 실행 중에 요청해야 합니다. - Target SDK < 23: 레거시 호환성을 위해 런타임 권한과 시그니처 권한이 설치 시점에
GRANT_INSTALL로 간주되어 일괄 승인될 수 있습니다.
// PermissionManagerService 내부 권한 부여 로직의 간소화된 구조
private void processPackagePermissions(PackageParser.Package appPackage, boolean isUpdate, PermissionCallback internalCallback) {
// AndroidManifest.xml에 정의된 모든 권한 요청 순회
final int requestedCount = appPackage.requestedPermissions.size();
for (int i = 0; i < requestedCount; i++) {
final String permissionName = appPackage.requestedPermissions.get(i);
final BasePermission basePerm = mSettings.getPermission(permissionName);
final boolean isModernApp = appPackage.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
int grantResult = GRANT_DENIED;
if (basePerm.isNormal()) {
// 일반 권한은 항상 설치 시점에 승인
grantResult = GRANT_INSTALL;
} else if (basePerm.isRuntime()) {
// 마시멜로 미만 타겟 앱은 런타임 권한을 설치 권한으로 취급
if (!isModernApp) {
grantResult = GRANT_INSTALL;
} else if (hasPreviousInstallPermission(permissionName)) {
// 업그레이드 시 이전 권한 상태 유지
grantResult = GRANT_UPGRADE;
} else {
// 최신 앱은 런타임 요청 필요
grantResult = GRANT_RUNTIME;
}
} else if (basePerm.isSignature()) {
// 시그니처 일치 여부 확인 후 승인
if (evaluateSignaturePermission(permissionName, appPackage, basePerm)) {
grantResult = GRANT_INSTALL;
}
}
// 결정된 결과에 따라 권한 상태 업데이트
executeGrantOperation(grantResult, permissionName, appPackage);
}
}
시그니처 권한 판별 시에도 타겟 SDK 버전이 중요한 역할을 합니다. 아래는 시그니처 권한 부여 여부를 결정하는 보조 로직의 흐름입니다.
private boolean evaluateSignaturePermission(String permName, PackageParser.Package pkg, BasePermission bp) {
boolean isAllowed = checkSignatureMatch(pkg, bp);
// 시그니처가 일치하지 않더라도, 23 미만 타겟 앱이 과거 표준 권한을 사용하는 경우 예외적 허용
if (!isAllowed && bp.isPre23() && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
isAllowed = true;
}
return isAllowed;
}
ADB를 이용한 권한 상태 확인
개발 중 특정 앱의 권한 부여 상태를 상세히 파악하려면 다음 dumpsys 명령어를 활용할 수 있습니다.
adb shell dumpsys package [패키지_명칭]
출력 결과에서 주의 깊게 살펴봐야 할 섹션은 다음과 같습니다.
- requested permissions: Manifest에 선언된 전체 권한 목록입니다.
- install permissions: 설치 시점에 이미 승인된 일반(Normal) 및 시그니처(Signature) 권한들입니다.
- runtime permissions: 사용자의 동의가 필요한 위험(Dangerous) 권한들의 현재 허용 상태입니다.