본문으로 건너뛰기
5분 AI.
GITHUB TRENDING KOTLIN WEEKLY #8 딥다이브 · 2026-10-23 분석

Shizuku 딥다이브
— 앱과 시스템 사이, 권한을 잇는 다리

권한 중계 서비스

빠른 판단

무엇
ADB·root로 시작한 별도 권한 프로세스를 통해 승인된 Android 앱의 시스템 API 호출을 중계하는 도구
이럴 때
Shizuku 지원 앱이 사용자 승인 후 서버의 권한 범위에서 시스템 설정·기능을 호출하도록 연결할 때
도입 전 확인
앱 자체를 root로 바꾸는 방식이 아니라 별도 서버 권한으로 요청을 중계한다
일반 Android 앱이 ADB 또는 root 권한으로 시작한 별도 Java 프로세스를 통해 시스템 API를 호출하게 해주는 도구입니다. 화면 뒤에서 Binder 통신, 호출자 검증, 프로세스 시작, 권한 승인, 파일 저장이 어떻게 맞물리는지 따라갑니다. Kotlin으로 된 관리 화면과 Java 서버, C++ 실행기를 함께 읽으며 프로세스 경계를 설계하는 법을 배웁니다. RikkaApps/Shizuku · ⭐32,008 · 포크 3,410 · Apache-2.0 · 2026-10-23 GitHub 기준.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

강한 권한의 별도 프로세스에 일을 맡기는 Android 시스템 API 중계기

Shizuku는 일반 앱과 Android 시스템 서비스 사이에 들어가는 권한 중계 계층입니다. 사용자가 ADB 또는 root로 shizuku_server를 시작하면, Shizuku를 지원하는 앱이 사용자 승인을 거쳐 이 프로세스에 요청을 보냅니다. 서버는 자신이 가진 권한 범위에서 시스템 API 호출을 중계합니다.

앱 자체의 UID를 root로 바꾸는 것이 아니라, 다른 권한으로 실행 중인 프로세스가 일을 대신하게 만드는 구조입니다.

한 문장 비유

출입증을 복사해 주는 대신, 출입 자격이 있는 담당자에게 업무를 맡깁니다

일반 앱은 방문객, Shizuku 서버는 별도 출입 자격을 가진 담당자, Android 시스템 서비스는 시설 관리실입니다. 방문객이 관리실에 직접 요청하면 거절될 수 있지만, 승인된 업무는 담당자가 자기 자격으로 전달합니다.

담당자의 자격이 ADB 수준이면 그 범위까지만 처리할 수 있습니다. 방문객이 누구인지 확인하고, 사용자가 그 방문객을 허용했는지 검사하는 절차도 필요합니다.

1-1. 어디에 쓰는가

시스템 기능을 다루는 유틸리티가 매번 명령줄 프로그램의 출력 문자열을 해석하는 대신, Android의 시스템 서비스와 구조화된 통신을 하도록 돕습니다. README는 앱 구성요소를 켜고 끄는 pm enable/disable 같은 명령 기반 구현을 배경 사례로 듭니다. 실제 가능한 작업은 ADB와 root의 차이, OS 버전, 제조사 정책, 사용 앱의 구현에 따라 달라집니다.

Shizuku를 설치했다고 모든 앱이 자동으로 이 통로를 쓰지는 않습니다. 앱이 Shizuku API에 맞춰 연동해야 하며, 관리 화면은 연결된 앱의 허용 상태를 보여줍니다. 터미널 연동용 rish도 있지만, 그 역시 서버가 실행 중이어야 합니다.

용어부터
UID와 IPC
UID는 운영체제가 실행 주체를 구별하는 사용자 번호입니다. IPC는 서로 다른 프로세스가 대화하는 통신입니다. Android의 Binder는 이 통신과 함께 호출자의 UID·PID를 서버가 확인할 수 있도록 해줍니다.

1-2. 이 글을 읽고 설명할 수 있어야 할 세 가지

  1. 일반 앱, Shizuku 서버, system_server의 역할과 권한이 어떻게 다른가.
  2. 서버의 Binder를 전달받는 일과, 실제 사용 권한을 승인받는 일이 왜 다른가.
  3. 서버가 살아 있는지, 연결이 되는지, 필요한 권한이 있는지를 왜 각각 확인해야 하는가.
학습의 중심
Android를 소재로 배우는 시스템 설계

이 저장소의 가치는 API 목록보다 신뢰 경계와 수명 관리에 있습니다. 권한을 가진 프로세스를 분리하고, 그 통로를 배포하고, 승인 상태를 저장하고, 상대가 종료되면 연결을 정리하는 전체 흐름을 볼 수 있습니다.

2왜 주목받는가

트렌딩 순위와 별개로, 소스에서 확인할 수 있는 매력

2-1. 오래된 프로젝트가 계속 읽히는 이유

2026-10-23 GitHub Trending Kotlin Weekly #8에 오른 프로젝트이며, 생성일은 2017-05-24입니다. 같은 날짜 기준 스타는 32,008개, 포크는 3,410개입니다. 이 수치는 관심의 규모를 보여주지만, 특정 업데이트가 이번 순위를 만들었다는 인과관계까지 알려주지는 않습니다. 최근 push는 2025-06-18이므로, 순위만 보고 최근에 새 기능이 출시됐다고 읽으면 안 됩니다.

기술적으로 눈여겨볼 이유는 분명합니다. 루팅하지 않은 기기에도 ADB 기반 경로를 제공하고, 승인 화면과 앱 관리 기능을 공통으로 마련하며, 개발자가 시스템 API 호출을 재사용 가능한 연동으로 만들 수 있게 합니다. 이러한 장점은 코드 구조에 대한 평가이며, 트래픽이나 사용자 증가의 원인을 뜻하지는 않습니다.

2-2. 명령 문자열 대신 API 통신을 선택한다

README는 매번 su 셸에서 명령을 실행하는 방식에 프로세스 생성 비용, 출력 텍스트 해석의 불안정성, 명령이 노출한 기능에만 갇히는 한계가 있다고 설명합니다. 서버를 두고 Binder 요청을 중계하면 반복 업무에서 이 비용을 줄일 여지가 있습니다. 단, README는 구체적인 속도 배수나 성능 비교 결과를 제시하지 않습니다.

이 프로젝트가 바꾸는 것은 권한의 크기만이 아니라, 시스템 기능을 호출하는 인터페이스의 형태입니다.

방식·도구어떻게 접근하나Shizuku를 선택할 이유와 조건
일반 Android API앱 자신의 UID로 공개 API 호출충분하다면 가장 단순한 선택. Shizuku는 별도 실행 주체가 필요한 경우의 추가 계층
ADB 명령개발 도구에서 기기 셸에 명령 전달Shizuku는 지원 앱이 Binder 기반 통로를 재사용. 최초 서버 시작·권한 관리는 여전히 필요
root 셸 + libsu앱이 root 셸에서 명령 실행Shizuku는 API 중계와 공통 승인 UI를 제공. 자체 root 시작에는 libsu 6.0.0도 사용
Shizuku / ADBshell UID 2000 서버가 요청 처리루팅 없이 이용 가능한 경로. shell에 허용되지 않은 작업은 할 수 없음
Shizuku / rootUID 0 서버가 요청 처리더 넓은 권한이 필요하고 이미 root 환경인 경우. SELinux 등 시스템 정책도 확인해야 함

이 표는 실행 방식의 비교입니다. libsu는 root 연동 라이브러리이고, Shizuku는 자체 관리 앱과 서버, 연동 API를 함께 둡니다. 실제로 manager/build.gradle에는 libsu가 들어 있으므로 둘을 항상 서로 배타적인 경쟁 제품으로 볼 수는 없습니다.

2-3. 무선 디버깅을 제품 흐름으로 연결한다

Android 11 이상의 무선 디버깅 경로에서는 PC 없이 시작할 수 있습니다. AdbMdns가 서비스를 찾고, AdbPairingClient가 페어링하고, AdbClient.shellCommand()가 시작 명령을 보내는 과정이 관리 앱에 들어 있습니다. 초기 연결의 복잡함을 Kotlin·네이티브 코드·사용자 안내로 나누어 처리한 사례입니다. 시작 조건은 공식 사용자 가이드에서도 확인할 수 있습니다.

비교할 때 주의
ADB 경로를 완전한 root 대체재로 읽지 않기

사용자 승인은 Shizuku 통로의 이용 허가입니다. 시스템이 shell에 주지 않은 권한을 새로 만들어내지는 않습니다. 또한 숨겨진 API에 접근할 수 있는가와 그 작업을 실행할 권한이 있는가는 별개의 문제입니다.

3기술 스택 전체 지도

웹 서비스의 앞·뒤가 아니라, 기기 안의 관리 앱·서버·실행기

이 프로젝트의 “백엔드”는 인터넷 서버가 아니라 Android 기기 내부의 권한 프로세스입니다. 프론트엔드는 Kotlin·Java와 XML로 만든 네이티브 관리 화면입니다. 버전은 모두 커밋 b844bc4의 선언값이며, 현재 최신 라이브러리 버전을 뜻하지 않습니다.

Kotlin 저장소라는 분류만으로 읽으면, Java 서버와 C++ 실행기에 들어 있는 핵심을 놓치게 됩니다.

3-1. 백엔드: Binder 서버와 설정 저장

구성선언 버전·기술파일과 역할
주 서버Java, 일부 Kotlinserver/의 ShizukuService와 관리자들. Binder 연결·승인·앱 목록 처리
통신 계약Binder / AIDLsettings.gradle의 :aidl, :server-shared 참조. 실제 정의는 API 서브모듈에 분리
JSON 저장Gson 2.13.1server/build.gradle. 권한 설정을 ShizukuConfig로 직렬화
큰 목록 전달parcelablelist 2.0.1같은 파일의 API 의존성. 설치 앱 목록을 ParcelableListSlice로 전달
숨겨진 API 호환hidden compat / stub 4.4.0settings.gradle의 버전 카탈로그. 런타임 호환 계층과 컴파일용 선언 분리
타입 연결 도구Refine 4.4.0플러그인·runtime·annotation·processor가 같은 버전으로 선언됨

compileOnly libs.hidden.stub은 컴파일 시 타입 정보를 쓰겠다는 선언입니다. implementation libs.hidden.compat과 역할이 다릅니다. “컴파일이 된다”는 사실만으로 기기에서 해당 내부 API가 같은 모양으로 존재하거나 호출 권한이 보장되지는 않습니다.

3-2. 프론트엔드: 기존 View 체계와 상태 관찰

구성선언 버전소스에서 보이는 쓰임
Kotlin 플러그인2.1.21settings.gradle. 관리 앱·서버의 Kotlin 컴파일
코루틴core / android 1.10.2manager/build.gradle. 상태 조회·ADB 연결을 IO 작업으로 분리
AndroidX 화면core-ktx 1.16.0 / fragment-ktx 1.8.7기본 확장 함수와 다이얼로그 화면
목록·상태RecyclerView 1.4.0 / Lifecycle 2.9.0ViewModel·LiveData와 홈 카드 목록 구성
설정·브라우저Preference 1.2.1 / Browser 1.8.0설정 화면과 외부 도움말 열기
시각 구성Material 1.12.0 / rikkax material 2.7.2MaterialAlertDialogBuilder와 Material3 테마 생성 설정
앱 호환 계층rikkax appcompat 1.6.1일반 androidx.appcompat:appcompat은 설정에서 제외하고 별도 구현 사용
아이콘·내부 APIappiconloader 1.5.0 / hiddenapibypass 6.1앱 목록 아이콘과 Android 9 이상 관리 앱의 숨겨진 API 접근 처리

manager/build.gradle의 viewBinding true와 HomeActivityBinding.inflate(), manager/src/main/res/layout/home_activity.xml이 화면 구현의 단서입니다. 이 커밋의 관리 앱은 Compose 중심 구조가 아닙니다. HomeViewModel.reload()는 viewModelScope.launch(Dispatchers.IO)에서 상태를 읽어 LiveData에 보냅니다.

3-3. 인프라·빌드: Java, 네이티브, APK를 한 파이프라인으로

항목실제 설정확인 위치
Gradle / AGP8.14 / 8.10.1gradle/wrapper/gradle-wrapper.properties, settings.gradle
Java / JVMJava 21 / Kotlin jvmTarget 21build.gradle, manager/build.gradle. CI도 Temurin 21 사용
Android SDKcompileSdk 36 / targetSdk 36 / minSdk 24루트 build.gradle에서 Android 하위 모듈에 공통 적용
네이티브 도구Build Tools 36.0.0 / NDK 29.0.13113456루트 build.gradle
CMake / C++CMake 3.31.0+ / C++17manager/build.gradle, manager/src/main/jni/CMakeLists.txt
암호·C++ 런타임BoringSSL 20250114 / libcxx 27.0.12077973manager/build.gradle의 Prefab 의존성
인증서 처리Bouncy Castle 1.80같은 파일의 bcpkix-jdk18on 선언
리소스 생성autoresconfig 1.2.2 / materialthemebuilder 1.5.1settings.gradle. 언어 설정과 색상·테마 생성

네이티브 빌드의 흥미로운 점은 add_executable(libshizuku.so ...)입니다. 이름은 공유 라이브러리처럼 보이지만 대상은 실행 파일입니다. 별도로 add_library(adb SHARED ...)도 있어, 시작 실행기와 JNI 라이브러리가 서로 다른 산출물임을 보여줍니다.

# .github/workflows/app.yml의 빌드 흐름을 요약
checkout@v4: submodules=recursive, fetch-depth=0
setup-java@v4: distribution=temurin, java-version=21
gradle/actions/setup-gradle@v4
./gradlew :manager:assemble
actions/upload-artifact@v4

CI는 ubuntu-latest에서 실행하며 APK 출력 폴더를 아티팩트로 보관합니다. 릴리스 설정에는 코드 축소와 리소스 축소가 켜져 있습니다. 이 워크플로에는 별도 테스트 명령이 없고, 본체 트리에도 test/·androidTest/ 경로가 없습니다. 이것만으로 외부 테스트나 수동 검증의 유무까지 단정할 수는 없습니다.

용어부터
JNI와 Prefab
JNI는 Java·Kotlin 쪽 코드가 C/C++ 함수를 호출하는 연결 규약입니다. Prefab은 Android 의존성에 들어 있는 네이티브 헤더와 라이브러리를 CMake가 찾도록 돕습니다. 여기서는 무선 디버깅 페어링의 암호 연산이 이 경계를 통과합니다.

4아키텍처 심화 분석

시작 경로와 요청 경로를 나누면 복잡한 구조가 풀린다

4-1. 시스템 구조도: 프로세스 세 개를 구별하자

[최초 시작 경로] 사용자 선택 +-- root: libsu --------------------+ +-- ADB: PC 또는 앱 내부 AdbClient --+--> libshizuku.so 실행기 | fork / execvp v /system/bin/app_process | ShizukuService.main() v shizuku_server 프로세스 UID 0 또는 UID 2000 [연결과 승인 경로] Shizuku 서버 -- Binder 전달 --> 지원 앱의 ContentProvider ^ | +-- attachApplication() -------+ | +-- 권한 요청 --> 관리 앱 승인 화면 --> 승인 결과 회신 [실제 API 요청 경로: 개념도] 지원 앱 프로세스 Shizuku 서버 system_server 일반 앱 UID root 또는 shell UID 시스템 서비스 | | | +-- Binder 래퍼로 요청 ---->| | | +-- 시스템 Binder 호출 -->| | |<------- 결과 ----------+ |<-------- 결과 -----------+ |

관리 앱도 일반 앱 프로세스입니다. 관리 화면이 떠 있는 상태와 권한 서버가 실행 중인 상태는 다릅니다. 마지막 요청 경로는 README가 설명하는 중계 구조와 ShizukuSystemApis의 래퍼 사용을 합친 개념도입니다. 중계의 공통 구현과 내부 검사 흐름은 API 서브모듈의 코드로 이어집니다.

4-2. 시작 경로: C++에서 Java의 main까지

manager/src/main/java/moe/shizuku/manager/starter/Starter.kt는 설치된 앱의 nativeLibraryDir에 있는 libshizuku.so 경로로 시작 명령을 만듭니다. StarterActivity.kt의 startRoot()는 libsu를, startAdb()는 AdbClient를 통해 같은 내부 시작 명령을 실행합니다.

// manager/src/main/jni/starter.cpp: 시작 자격 검사 발췌
uid_t uid = getuid();
if (uid != 0 && uid != 2000) {
    // 원본은 진단 문구를 출력한 뒤 종료한다
    exit(EXIT_FATAL_UID);
}

// run_server()의 실행 환경 설정
setenv("CLASSPATH", dex_path, true);
// 인자에는 app_process, APK 경로, 서버 클래스가 들어간다
// execvp()가 현재 프로세스를 새 실행 이미지로 바꾼다

start_server()는 fork()로 자식을 만들고, 자식에서 setsid()와 표준 입출력 재지정을 거쳐 run_server()를 부릅니다. run_server()는 execvp()로 /system/bin/app_process를 실행합니다. Java 진입점은 server/src/main/java/rikka/shizuku/server/ShizukuService.java의 main()입니다.

// ShizukuService.java: main()의 이벤트 루프
Looper.prepareMainLooper();
new ShizukuService();
Looper.loop();

서버 생성자는 package·activity·user·appops 서비스가 준비될 때까지 기다립니다. waitSystemService()는 아직 준비되지 않았으면 1초 뒤 다시 확인합니다. 시작 로그의 성공과 실제 Binder 수신은 별도 단계여서, 관리 화면도 서버 연결을 추가로 기다립니다.

4-3. 패턴 ① 권한 중계자와 프록시·어댑터

manager/src/main/java/moe/shizuku/manager/utils/ShizukuSystemApis.kt의 초기화 블록을 봅시다. 시스템 서비스 Binder를 얻는 지점에 래퍼를 끼워 넣습니다. 이후의 PackageManagerApis.getInstalledPackages() 같은 호출은 이 연결 계층을 이용합니다.

// ShizukuSystemApis.kt의 실제 초기화 코드
init {
    SystemServiceBinder.setOnGetBinderListener {
        return@setOnGetBinderListener ShizukuBinderWrapper(it)
    }
}

프록시는 원래 대상처럼 보이는 창구에서 호출을 받아 다른 경로로 전달하는 방식입니다. 여기서 배울 점은 호출부마다 ADB 명령을 흩뿌리지 않고 통로를 얻는 경계에 변환을 모았다는 것입니다. README는 서버의 transactRemote와 래퍼를 설명하지만, 해당 공통 중계 구현은 본체의 ShizukuService.java에 직접 정의되어 있지 않습니다.

권한 설계는 함수 이름보다, 그 함수를 어느 프로세스의 신원으로 호출하느냐에서 시작합니다.

4-4. 패턴 ② 템플릿 메서드와 생성 훅

ShizukuService는 공통 Service를 상속하고, onCreateUserServiceManager()·onCreateClientManager()·onCreateConfigManager()를 재정의합니다. 공통 흐름에 필요한 부품을 하위 구현이 공급하는 형태로 읽을 수 있습니다. 내부 호출 순서는 API 서브모듈에 있는 기반 클래스의 구현을 함께 읽어야 알 수 있습니다.

// ShizukuService.java: 구체적인 설정 관리자를 공급
@Override
public ShizukuConfigManager onCreateConfigManager() {
    return new ShizukuConfigManager();
}

ShizukuClientManager.java는 생성자에서 super(configManager)를 호출하는 작은 클래스입니다. 반대로 ShizukuUserServiceManager.getUserServiceStartCmd()는 실행할 app_process 경로를 고르고 ServiceStarter.commandForUserService()에 위임합니다. 파일 크기보다 재정의한 훅이 어떤 책임을 추가하는지 읽는 연습이 중요합니다.

4-5. 패턴 ③ 관찰자와 연결 수명 관리

server/src/main/java/rikka/shizuku/server/BinderSender.java의 register()는 프로세스 관찰자를 등록하고, Android 8.0(API 26) 이상에서는 UID 관찰자도 등록합니다. 앱이 활성화되는 등의 이벤트를 받으면 sendBinder()가 실행됩니다. PID·UID 목록으로 같은 실행 상태에 대한 중복 전달을 줄이고, 종료 이벤트에서는 목록의 항목을 제거합니다.

실제 전달은 ShizukuService.sendBinderToUserApp()이 담당합니다. packageName + ".shizuku"라는 ContentProvider를 얻어 BinderContainer를 넣은 Bundle로 sendBinder 호출을 보냅니다. Provider 사용 뒤에는 finally에서 외부 참조를 반환합니다. 상대 Provider가 죽어 있으면 앱을 종료하고 1초 뒤 한 번 더 시도하는 복구 분기도 있습니다.

비유

서버 전화번호만 메모해 두는 것으로는 부족합니다. 앱이 새로 출근하면 연결 수단을 다시 건네고, 상대가 퇴근하면 이전 연결을 버려야 합니다. 이 저장소는 연결의 생성과 소멸을 모두 이벤트로 취급합니다.

starter/src/main/java/moe/shizuku/starter/ServiceStarter.java의 sendBinder()는 UserService를 연결한 뒤 서버 Binder에 linkToDeath()를 등록합니다. 서버가 사라지면 그 UserService 프로세스도 종료합니다. HomeActivity.onDestroy()에서는 화면이 등록했던 수신·종료 리스너를 해제합니다.

4-6. 호출자 확인: 전달받은 이름을 그대로 믿지 않는다

ShizukuService.attachApplication()은 요청 Bundle의 패키지 이름과 Binder가 알려준 호출자 UID를 대조합니다. 패키지 이름은 앱이 보낸 값이지만, 실제 UID는 Binder.getCallingUid()로 얻습니다. 그 UID에 속하는 패키지가 아니면 SecurityException을 던집니다.

// attachApplication()의 신원 대조 부분
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
List<String> packages =
        PackageManagerApis.getPackagesForUidNoThrow(callingUid);
if (!packages.contains(requestPackageName)) {
    // 원본은 로그를 남기고 SecurityException을 던진다
}

승인 결과를 받는 dispatchPermissionConfirmationResult()도 관리자 앱의 appId인지 먼저 검사합니다. 이후 같은 UID의 클라이언트 기록들에 allowed를 반영하고, 요청한 PID에는 결과 콜백을 보냅니다. onetime이 false인 결과만 설정에 저장합니다. 현재 승인 화면의 버튼은 지속 허용과 일회성 거절을 전달하므로, 프로토콜의 플래그만 보고 “일회성 허용 버튼도 있다”고 추정하면 안 됩니다.

v11 이상 서버에 대한 관리 앱의 AuthorizationManager.revoke()는 서버의 updateFlagsForUid()로 이어집니다. 이 경로는 허용 상태를 해제하고, 해당 UID의 연결된 클라이언트 기록마다 앱 강제 종료와 UserService 정리를 요청합니다. 권한 철회가 저장된 값 하나를 바꾸는 데서 끝나지 않는다는 점을 배울 수 있습니다.

4-7. 패턴 ④ 원자적 파일 저장과 쓰기 묶기

server/src/main/java/rikka/shizuku/server/ShizukuConfigManager.java는 설정을 /data/user_de/0/com.android.shell/shizuku.json에 저장합니다. AtomicFile.startWrite() 뒤에 성공하면 finishWrite(), 실패하면 failWrite()를 호출합니다. 파일을 쓰다 실패했을 때 이전 내용을 복구할 수 있는 저장 절차입니다.

// ShizukuConfigManager.java: 갱신할 비트만 교체
int newValue = (entry.flags & ~mask) | (mask & values);

// 저장은 즉시 하지 않고 10초 뒤로 예약
private static final long WRITE_DELAY = 10 * 1000;

scheduleWriteLocked()는 Android 10 이상에서 이미 예약된 쓰기가 있으면 더 예약하지 않습니다. 이전 버전에서는 기존 예약을 취소하고 다시 10초 뒤로 미룹니다. 두 분기의 차이는 각각 “첫 예약 이후 모아 쓰기”와 “마지막 갱신 뒤 기다리기”로 이해할 수 있습니다. 메모리의 허용 상태가 바뀐 시점과 디스크 저장 완료 시점 사이에는 간격이 있습니다.

생성자에서는 UID에 속한 패키지가 없거나, 기록된 패키지 중 현재 UID에 남아 있는 것이 하나도 없는 설정을 제거합니다. UID만 영구 식별자처럼 믿지 않고 패키지 목록을 함께 대조하는 이유입니다. ShizukuConfig.LATEST_VERSION은 2이며 설정 형식의 버전과 앱 릴리스 버전은 서로 다릅니다.

5디렉토리 구조 해부

화면·권한 서버·시작 코드·공통 API의 경계를 먼저 표시하자
Shizuku/ ├── settings.gradle 플러그인·의존성·모듈 연결 ├── build.gradle SDK·Java·버전 이름 공통 설정 ├── .gitmodules Shizuku-API 저장소 연결 ├── .github/workflows/app.yml APK 빌드 자동화 ├── manager/ │ ├── build.gradle 앱·의존성·네이티브 빌드 설정 │ └── src/main/ │ ├── AndroidManifest.xml 권한·화면·Provider 선언 │ ├── java/moe/shizuku/manager/ │ │ ├── home/ 홈 화면과 서버 상태 │ │ ├── adb/ 탐색·페어링·ADB 통신 │ │ ├── authorization/ 승인 화면과 허용 관리 │ │ ├── starter/ 시작 명령 생성·실행 UI │ │ └── shell/ 터미널 연결 처리 │ ├── jni/ C++ 실행기·페어링 암호 연산 │ ├── res/ 레이아웃·문자열·테마 │ └── assets/rish 터미널용 시작 스크립트 ├── server/src/main/java/rikka/shizuku/server/ │ ├── ShizukuService.java 서버 진입점·승인·Binder 배포 │ ├── BinderSender.java 앱 실행 이벤트 관찰 │ ├── ShizukuConfigManager.java 권한 설정 저장·정리 │ └── ShizukuUserServiceManager.java 별도 서비스 프로세스 관리 ├── starter/ UserService 시작·서버에 연결 ├── shell/ rish용 DEX 로더 ├── common/ OS·Android 버전 공통 유틸리티 └── api/ 별도 Git 서브모듈

api/는 단순한 빈 코드 폴더가 아닙니다. 본체 커밋은 Shizuku-API의 a27f6e4151ba7b39965ca47edb2bf0aeed7102e5를 가리킵니다. settings.gradle은 이 아래에서 aidl·rish·shared·api·provider·server-shared·hidden-api-stub을 연결합니다. 본체 트리만으로 공통 API 구현 전체를 읽을 수 없는 이유입니다.

5-1. 본체 코드 규모

언어·형식파일 수물리 줄 수
Kotlin544,819
Java302,919
C++ / 헤더7 / 6915 / 85
핵심 언어 합계978,738
XML1586,554
Gradle8455
YAML3126

집계 기준은 커밋 b844bc4 본체의 추적 파일 300개입니다. 확장자로 분류하고 빈 줄·주석을 포함한 물리 줄을 셌으며, 서브모듈 내용과 생성 산출물은 제외했습니다. 따라서 이 수치는 전체 Shizuku 생태계의 코드량이나 실행 가능한 문장 수가 아닙니다. XML에는 화면뿐 아니라 다국어 문자열과 리소스 정의도 포함됩니다.

Kotlin·Java·C++·헤더만 모듈별로 보면 manager 6,910줄, server 1,386줄, starter 196줄, shell 171줄, common 75줄입니다. 처음 읽을 때는 592줄인 ShizukuService.java 전체를 한 번에 이해하려 하기보다, 앞 장의 시작·전달·승인 흐름별로 나누어 읽는 편이 좋습니다.

6학습 포인트 (기술별)

좋은 구조와 함께, 그대로 복사하기 전에 검토할 부분도 배우기

6-1. Kotlin: 상태·수명·취소를 함께 보기

HomeViewModel.reload()는 IO 디스패처에서 조회하고 Resource.success/error를 LiveData로 전달합니다. HomeActivity는 상태를 관찰해 목록을 갱신합니다. UI가 통신 세부사항을 직접 처리하지 않게 만드는 책임 분리를 배울 수 있습니다.

한편 StarterActivity.kt의 시작 로직은 GlobalScope.launch(Dispatchers.IO)를 사용합니다. 화면 수명과 작업 수명을 어떻게 연결할지 검토할 학습 지점입니다. 시작 작업을 화면 밖에서도 유지해야 하는지부터 정한 뒤, 취소 가능한 범위와 정리 책임을 설계해 보세요. 현재 사용법을 모든 비동기 작업의 표준으로 일반화할 필요는 없습니다.

작은 실습
실행 중이라는 말의 뜻을 분리하기

ServiceStatus에는 UID·API 버전·SELinux 문맥·런타임 권한 부여에 필요한 GRANT_RUNTIME_PERMISSIONS 검사 결과가 따로 있습니다. “미연결”, “연결됨·해당 권한 없음”, “연결됨·해당 권한 있음”을 구분하는 화면 상태표를 만들어 보세요. boolean 하나로 합쳤을 때 어떤 정보가 사라지는지 설명하면 됩니다.

6-2. Binder와 Parcel: 쓴 순서대로 읽어야 한다

manager/src/main/java/moe/shizuku/manager/authorization/AuthorizationManager.kt의 getApplications()는 인터페이스 토큰, userId 순서로 Parcel에 기록합니다. 서버의 onTransact()는 토큰을 검사하고 정수를 읽은 뒤 목록을 반환합니다. 클라이언트가 reply.readException() 뒤에 목록을 읽는 순서까지 짝을 맞춰 보세요. finally의 recycle()도 통신 자원 관리의 일부입니다.

// 양쪽 소스의 기록·해석 순서를 학습용으로 요약
보내는 쪽: writeInterfaceToken → writeInt(userId) → transact
받는 쪽:   enforceInterface   → readInt          → 결과 기록
돌아온 쪽: readException      → 목록 역직렬화    → recycle

Android AIDL 공식 문서는 원격 호출이 Binder 스레드 풀에서 처리될 수 있으므로 동시성을 고려해야 한다고 설명합니다. IPC 메서드를 일반적인 UI 콜백과 같은 스레드에서 실행된다고 가정하지 않는 것이 출발점입니다. 인터페이스 토큰 검사는 통신 계약 확인이며, 그것만으로 모든 호출자 권한 검사가 끝나는 것도 아닙니다.

6-3. 바이너리 프로토콜: 길이·바이트 순서·검증

AdbMessage.kt의 HEADER_LENGTH는 24입니다. 명령·인자 둘·길이·체크섬·magic을 4바이트 정수 여섯 개로 기록하고, toByteArray()와 AdbClient.read()는 LITTLE_ENDIAN을 사용합니다. 반면 페어링 헤더는 AdbPairingClient.kt에서 6바이트, BIG_ENDIAN으로 다룹니다.

함수 이름도 검증 대상입니다. AdbMessage.crc32()는 이름과 달리 일반적인 CRC32 계산이 아니라 각 바이트를 0~255 값으로 보아 합산합니다. 페어링의 PairingPacketHeader.readFrom()는 버전·종류와 1~16,384바이트 길이 범위를 검사합니다. 반면 AdbClient.read()의 본문 버퍼 할당에는 같은 식의 명시적인 상한 검사가 보이지 않습니다. 두 구현을 비교하며 입력 검증 항목을 적는 연습을 할 수 있습니다.

주석과 함수 이름은 길잡이지만, 동작을 판정할 때는 실제 분기와 반환값까지 읽어야 합니다.

6-4. Java 동시성: 타임아웃은 예외인가, 반환값인가

RequestPermissionActivity.waitForBinder()는 CountDownLatch.await(5, TimeUnit.SECONDS) 뒤에 true를 반환하고, TimeoutException을 잡습니다. 하지만 이 await는 제한 시간이 지나면 false를 반환합니다. 그래서 타임아웃만으로는 코드의 해당 catch 분기에 들어가지 않고, 대기 결과를 성공으로 취급할 수 있습니다. 이는 Java API 계약과 대조해 확인할 수 있는 개선 지점입니다.

// 학습용 개선 예시. 원본 waitForBinder()의 전체 대체 코드는 아니다
val received = countDownLatch.await(5, TimeUnit.SECONDS)
if (!received) {
    return false
}
return true
// InterruptedException 처리와 리스너 해제도 별도로 설계해야 한다

6-5. C++·JNI: 한쪽의 생성은 다른 쪽의 정리와 짝이다

AdbPairingClient.kt의 PairingContext는 네이티브 포인터를 Long으로 보관하고 nativeEncrypt·nativeDecrypt·nativeDestroy를 호출합니다. manager/src/main/jni/adb_pairing.cpp는 SPAKE2, HKDF, AES-128-GCM 관련 BoringSSL 함수를 사용하고 JNI_OnLoad()에서 네이티브 메서드를 연결합니다. 생성 실패·중간 실패·정상 종료에서 누가 자원을 해제하는지 선으로 이어 보세요.

비유

JNI 포인터는 물건 자체가 아니라 보관소의 번호표입니다. Kotlin에서 번호를 잊었다고 C++ 물건이 자동으로 사라지지는 않습니다. 번호가 유효한 기간과 반납 담당자를 함께 정해야 합니다.

7하드웨어 / 시스템 요구사항

앱을 쓰는 조건, 코드를 읽는 조건, 빌드하는 조건은 다르다
상황필요한 조건근거·범위
앱 설치 대상이 커밋의 minSdk 24, 즉 Android 7.0 이상build.gradle. 모든 기기의 실제 동작 보장과는 다름
PC 없는 무선 시작Android 11 이상, 개발자 옵션·무선 디버깅·페어링 환경공식 가이드 및 AdbPairingClient의 API 30 제한
PC로 ADB 시작PC의 SDK Platform Tools, 기기의 USB 디버깅과 연결 승인공식 가이드의 비root 시작 경로. OS·기기 환경에 따라 연결 방식이 다름
root로 시작이미 root 권한을 쓸 수 있는 기기와 호환되는 su 환경실행기는 UID 0·2000만 허용하며 root 경로에서 SELinux Binder 접근도 검사
소스 독해텍스트 편집기와 Git 파일을 읽을 환경실제 기기·root·ADB 없이도 구조·비트 연산·프로토콜을 공부할 수 있음
소스 빌드JDK 21, SDK 36, Build Tools 36.0.0, 지정 NDK·CMake, API 서브모듈build.gradle·manager/build.gradle·CI 설정 기준
RAM·디스크공식 최소 수치가 저장소에 명시되어 있지 않음-Xmx2048m는 Gradle JVM 최대 힙 설정이며 PC 전체 메모리 최소 요구량이 아님

무선 디버깅은 Android 11부터 제공되는 플랫폼 기능입니다. 일반적인 ADB 구성과 PC 연결 조건은 Android의 ADB 공식 문서에서 확인할 수 있습니다. starter.cpp에는 arm·arm64·x86·x86_64 경로 분기가 있지만, 그것만으로 특정 릴리스 APK에 모든 ABI가 들어 있다고 확정할 수는 없습니다.

빌드에 필요한 Java 버전과 휴대전화가 지원해야 하는 Android 버전은 서로 다른 요구사항입니다.

7-1. 재부팅 이후: 앱의 일반 안내와 조건부 시작 코드

관리 앱의 manager/src/main/res/values/strings.xml은 PC를 통한 ADB 시작을 재부팅 때마다 반복하라고 안내합니다. 다만 커밋 b844bc4의 manager/src/main/java/moe/shizuku/manager/receiver/BootCompleteReceiver.kt에는 다음 조건부 분기가 있습니다. 일반적인 시작 안내와 조건부 자동 시작 경로를 함께 살펴보세요.

// BootCompleteReceiver.onReceive()의 조건을 학습용으로 요약
기본 사용자이고 서버가 아직 실행되지 않았는가?
  ├─ 마지막 시작 방식이 ROOT → rootStart()
  └─ Android 13 이상
       + WRITE_SECURE_SETTINGS 허용
       + 마지막 시작 방식이 ADB → adbStart()

ADB 자동 시작 경로는 무선 디버깅 설정을 쓰고, mDNS로 포트를 찾은 뒤, 127.0.0.1에 연결해 시작 명령을 보냅니다. 포트 탐색 대기는 3초입니다. 저장된 키·시스템 권한·부팅 브로드캐스트·제조사 정책까지 영향을 주므로, 코드가 있다는 사실을 자동 시작 보장으로 바꿔 읽으면 안 됩니다.

호환성의 경계
숨겨진 API와 실행 권한은 따로 검사한다

ShizukuApplication은 Android 9 이상에서 HiddenApiBypass를 설정합니다. 이는 내부 API 접근 문제를 다루는 코드이며, shell에 없는 시스템 권한을 부여하는 코드가 아닙니다. 제조사별 제한이나 Android 버전별 내부 API 변화는 별도로 확인해야 합니다.

8직접 해볼 수 있는 실습 과제

설치보다 먼저, 읽은 내용을 검증 가능한 결과물로 바꾸기

아래 다섯 과제는 소스 독해와 별도의 작은 연습 코드로 진행할 수 있습니다. 시스템 설정을 바꾸거나 실제 기기에서 높은 권한을 행사해야만 완성되는 과제가 아닙니다. 각 과제의 완료 기준을 만족했는지 스스로 설명해 보세요.

실습의 완료 기준은 코드를 읽었다는 느낌이 아니라, 정상 경로와 실패 경로를 함께 설명할 수 있다는 것입니다.

과제 1 · 입문

시작과 요청을 두 장의 흐름도로 그리기 30~45분

읽을 곳: Starter.kt, StarterActivity.kt, starter.cpp, ShizukuService.main().

할 일: “서버를 처음 띄우는 화살표”와 “시작된 서버에 기능을 요청하는 화살표”를 따로 그립니다. 각 상자에 언어·프로세스 이름·UID를 적고, fork()와 execvp()의 위치를 표시합니다.

완료 기준: 실행기가 UID 10000으로 시작되면 어디서 멈추는지, 실행기 부모 프로세스가 종료되어도 서버 자식이 계속 동작할 수 있는 이유를 설명합니다.

과제 2 · 입문

권한 비트 마스크를 손으로 계산하기 30분

읽을 곳: AuthorizationManager의 플래그와 ShizukuConfigManager.updateLocked().

할 일: ALLOWED=2, DENIED=4, MASK=6으로 두고 기존 값에 관계없는 비트 8을 섞습니다. 아래 식에서 허용과 거절을 번갈아 적용했을 때 남는 비트를 표로 만듭니다.

// 학습용 입력: 관계없는 비트 8은 유지되어야 한다
기존 flags = 8 | 2 = 10
mask = 2 | 4 = 6
values = 4
새 flags = (10 & ~6) | (6 & 4) = 12

완료 기준: 전체 flags를 values로 덮어쓰면 왜 비트 8이 사라지는지 설명하고, 같은 값으로 반복 갱신할 때 소스가 어떻게 조기 반환하는지 찾습니다.

과제 3 · 중급

작은 ADB 헤더 해석기 설계하기 1~2시간

읽을 곳: AdbMessage.toByteArray(), AdbClient.read(), PairingPacketHeader.readFrom().

할 일: 네트워크 연결 없이 바이트 배열만 입력받는 작은 연습 함수를 설계합니다. 24바이트 미만 헤더, 음수 길이, 자체 상한 초과, 본문 길이 불일치, 잘못된 magic을 각각 어떻게 거절할지 정합니다.

완료 기준: 정상 바이트 배열의 기록→해석 결과가 같고, 잘못된 길이에 대해 큰 메모리를 할당하기 전에 거절하도록 설계합니다. 페어링 헤더가 다른 바이트 순서를 쓰는 점과 crc32()의 실제 합산 방식도 명시합니다.

과제 4 · 중급

권한 요청과 타임아웃의 상태표 만들기 1~2시간

읽을 곳: attachApplication(), dispatchPermissionConfirmationResult(), waitForBinder().

할 일: 미연결·연결 중·미승인·허용·거절·연결 종료를 표로 정리합니다. 각 전이에 UID 검증, 관리자 검증, 콜백, 영속 저장 중 무엇이 필요한지 적습니다. Binder가 5초 안에 오지 않는 경우에는 await의 false 반환이 어느 상태로 이어져야 하는지도 표시합니다.

완료 기준: 패키지 이름 위조, 같은 UID의 여러 PID, 승인 직후 연결 종료, 제한 시간 경과라는 네 상황을 표에서 따라갈 수 있습니다. 일회성 결과와 저장되는 결과의 차이도 설명합니다.

과제 5 · 고급

독립적인 두 프로세스로 축소판 중계기 설계하기 반나절~하루

읽을 곳: BinderSender, ServiceStarter.sendBinder(), ShizukuConfigManager.

할 일: 별도 연습 앱에서 getCounter() 같은 무해한 AIDL 함수 하나를 설계합니다. 클라이언트와 서비스는 다른 프로세스에 두고, 연결·호출·해제·서버 종료 후 재연결 절차를 문서화합니다. root나 숨겨진 API를 추가하기 전에 이 수명 관리부터 완성합니다.

완료 기준: 연결이 끊겼을 때 낡은 Binder를 계속 사용하지 않고, 새 연결을 얻은 다음 다시 호출하도록 설계합니다. 추가 과제로 지연 저장 도중 종료될 때와 실제 저장 도중 실패할 때의 차이를 설명합니다.

9관련 기술 심화 학습 로드맵

화면을 읽는 단계에서, 통신 계약과 장애를 설계하는 단계까지

하루에 모두 이해하려 하기보다 매주 하나의 경계를 다루는 편이 좋습니다. 아래 로드맵은 Kotlin 기초 문법을 아는 독자를 기준으로 한 6주 학습안입니다. Android 경험이 없다면 1~2주차를 늘리고, 시스템 프로그래밍 경험이 있다면 후반 과제에 시간을 더 쓰세요.

주차핵심 주제읽을 코드남길 결과물
1주차Android 앱·UID·프로세스 기초manager/src/main/AndroidManifest.xml, Starter.kt, starter.cpp관리 앱·서버·시스템 서비스의 프로세스 지도
2주차ViewModel·LiveData·코루틴 수명HomeActivity, HomeViewModel, ServiceStatus연결 상태와 권한 상태를 구별하는 화면 상태표
3주차Binder·AIDL·Parcel과 호출자 검증attachApplication(), AuthorizationManager양쪽의 기록·해석 순서표와 권한 전이표
4주차바이너리 프로토콜·TLS·페어링AdbMessage, AdbClient, AdbPairingClient헤더 구조도와 잘못된 입력 사례 목록
5주차JNI·프로세스 시작·종료 관찰adb_pairing.cpp, ServiceStarter, ApkChangedObservers네이티브 자원과 Binder 참조의 생성·반납 지도
6주차저장 일관성·호환성·재현 가능한 빌드ShizukuConfigManager, build.gradle, CI지연 저장 실패 시나리오와 빌드 입력 목록

다음 단계로 넘어갈 기준은 파일을 다 읽었는지가 아니라, 그 경계에서 무엇이 실패할 수 있는지 설명할 수 있는지입니다.

9-1. 로드맵을 관통하는 확인 질문

확장 학습
API 서브모듈로 이어 읽기

본체의 연결 지도를 이해한 뒤에는 고정된 Shizuku-API 커밋으로 이어가세요. 먼저 Binder 계약과 클라이언트 래퍼, 다음으로 공통 서버의 검사·전달 흐름을 확인하면 좋습니다. 최신 브랜치의 API와 오래된 본체를 섞지 않는 것이 재현 가능한 독해의 출발점입니다.

10핵심 키워드 사전

낯선 용어를 이 프로젝트의 실제 역할에 연결하기
용어쉬운 설명
Shizuku 서버ADB 또는 root로 시작한 별도 프로세스. 관리 화면 자체와 구별해야 한다.
UID / PIDUID는 실행 주체를 나타내는 사용자 번호, PID는 실행 중인 프로세스 번호. 하나의 UID에 여러 PID가 연결될 수 있다.
ADB / shellADB는 기기와 통신하는 개발 도구 체계. 이 프로젝트의 ADB 시작 서버는 shell UID 2000으로 동작한다.
rootUID 0인 실행 주체. shell과 권한 범위가 다르며 시스템 정책까지 없어지는 것은 아니다.
system_server여러 Android 시스템 서비스가 실행되는 핵심 프로세스. Shizuku 서버와 별개다.
Binder / IBinderAndroid의 프로세스 간 통신 체계와 그 연결 수단을 다루는 인터페이스.
AIDL프로세스 사이에서 호출할 메서드의 계약을 적는 언어. 빌드 도구가 통신용 코드를 생성한다.
Parcel / BundleParcel은 통신 데이터를 순서대로 기록하는 컨테이너. Bundle은 이름 붙은 여러 값을 묶어 전달할 때 쓴다.
ContentProvider앱 간 호출 창구가 되는 Android 구성요소. 여기서는 Binder 연결 수단을 전달하는 데에도 쓰인다.
app_processAndroid 런타임에서 지정한 Java 진입 클래스를 실행하는 프로그램. Shizuku의 권한 서버 시작에 사용한다.
fork / execfork는 자식 프로세스를 만들고, exec는 현재 프로세스에서 실행할 프로그램을 교체한다.
Looper / HandlerLooper는 이벤트 처리를 이어가는 루프, Handler는 그 루프에 작업을 전달·예약하는 도구다.
ViewModel / LiveData화면이 쓸 상태와 작업을 모으는 객체, 그리고 상태 변화를 관찰하게 하는 데이터 도구.
프록시 / 어댑터호출을 대신 받는 창구와 인터페이스를 연결하는 계층. Binder 래퍼를 이해하는 데 유용한 관점이다.
DeathRecipientBinder의 상대 프로세스가 사라졌을 때 알림받는 장치. 낡은 연결을 정리하는 데 쓴다.
UserService연동 앱의 서비스 코드를 별도 권한 프로세스에서 실행하는 경로. Android의 일반 앱 Service와 같은 의미로 혼동하지 않는다.
SELinux 문맥프로세스와 자원에 붙는 보안 분류 정보. UID만으로 설명되지 않는 접근 제한을 이해하는 단서다.
숨겨진 API일반 앱용 공개 SDK 계약에 포함되지 않는 Android 내부 인터페이스. 버전별 변경과 접근 제한을 고려해야 한다.
mDNS / DNS-SD네트워크에서 서비스와 포트를 발견하는 기술. 관리 앱은 자신의 기기에 해당하는 ADB 서비스를 골라 쓴다.
TLS / SPAKE2TLS는 연결 보호에, SPAKE2는 짧은 페어링 코드를 이용한 키 합의에 쓰이는 기술. 이 저장소의 페어링 구현에서 만난다.
엔디언여러 바이트로 된 숫자를 저장하는 순서. ADB 메시지와 페어링 헤더의 순서가 서로 다르다.
JNI / NDK / ABIJNI는 Java와 네이티브 코드의 연결 규약, NDK는 빌드 도구 묶음, ABI는 CPU별 바이너리 호환 규약이다.
AtomicFile파일 저장 성공 시 확정하고 실패 시 이전 내용을 복구할 수 있게 돕는 Android 도구.
비트 마스크정수의 여러 비트에 담은 상태 중 원하는 부분만 골라 읽거나 바꾸는 방법.
Git 서브모듈다른 저장소의 특정 커밋을 현재 프로젝트의 일부로 연결하는 방식. 본체와 API 버전을 짝짓는다.
shallow clone일부 최근 이력만 가져오는 복제 방식. 그 복제본에서 센 커밋 수는 전체 역사와 다르다.

11참고 링크

공식 설명, 고정된 소스, 플랫폼 계약을 서로 대조하기

11-1. 공식 저장소·문서

11-2. 핵심 코드를 다시 찾는 링크

11-3. 관련 기술의 공식 학습 자료

현재 상태 (2026-10-23 확인)

항목상태읽을 때의 기준
선정GitHub Trending Kotlin Weekly #82026-10-23 분석의 선정 기준
스타 / 포크32,008 / 3,4102026-10-23 GitHub 기준
주 언어KotlinGitHub 분류. 본체 서버의 중심 언어는 Java
생성일2017-05-24GitHub 저장소 생성일
최신 릴리스v13.6.0 · 2025-05-252026-10-23 GitHub 기준
최근 push2025-06-182026-10-23 GitHub 기준
소스 기준 커밋b844bc4 · 2025-06-18
fixup! Clarify license
권리 표기 명확화에 관한 커밋 메시지
코드 규모Kotlin 4,819줄 / Java 2,919줄 / C++·헤더 1,000줄커밋 b844bc4의 본체 추적 파일. 공백·주석 포함, 서브모듈 제외
부가 리소스XML 6,554줄 / Gradle 455줄 / YAML 126줄리소스·빌드·자동화 설정을 별도 집계
API 연결Shizuku-API a27f6e4본체가 고정한 서브모듈 커밋
라이선스Apache-2.0LICENSE와 README의 이름·아이콘 관련 안내를 함께 확인
사용 전에 알아둘 한계
커밋별 구현과 실제 기기의 동작 조건

본문의 경로·설정값은 커밋 b844bc4 기준입니다. 공통 중계와 권한 검사 흐름은 API 서브모듈의 구현까지 함께 읽어야 합니다.

README의 일부 API 클래스 경로는 현재 본체의 import와 다릅니다. 예를 들어 래퍼는 본체에서 rikka.shizuku.ShizukuBinderWrapper로 참조합니다. 서버 시작 조건은 starter.cpp와 BootCompleteReceiver.kt의 분기에서 확인할 수 있습니다. 설치된 앱의 안내와 OS별 조건을 함께 확인해야 합니다.

루트 빌드는 git rev-list --count HEAD를 versionCode에 쓰므로, shallow clone처럼 일부 이력만 있으면 전체 이력 기준의 versionCode와 값이 달라집니다. CI는 fetch-depth: 0과 재귀 서브모듈을 지정합니다.

Apache-2.0이라는 코드 라이선스와 이름·아이콘 사용 안내를 혼동하지 마세요. README는 Shizuku 이름, 앱 ID moe.shizuku.privileged.api, 관리자 권한 이름, 런처 아이콘의 사용에 별도 주의를 명시합니다. 연습용 파생 앱을 배포할 계획이라면 해당 원문을 확인해야 합니다.