루팅은 모든 사람을 위한 것이 아닙니다. Shizuku 라이브러리를 사용하여 앱에서 높은 셸 수준 권한을 얻는 방법은 다음과 같습니다.
일반적으로 앱에 부여되는 권한이 충분하지 않은 데는 여러 가지 이유가 있습니다. 어쩌면 당신도 나처럼 Android API를 남용하는 해킹된 앱을 만드는 것을 좋아할 수도 있습니다. 내가 사용하는 API 중 일부는 특별한 권한으로 잠겨 있습니다. 때로는 셸 사용자(ADB) 또는 시스템만 액세스할 수 있습니다. 하지만 해결책이 있습니다 - Shizuku.
Shizuku를 사용하면 시스템 API를 거의 직접적으로 호출할 수 있으며 완전히 Java 또는 Kotlin으로 호출할 수 있습니다. 이 가이드에서는 Shizuku를 구현하고 사용하는 방법을 보여줍니다.
시즈쿠란?
Shizuku를 사용하기 전에 그것이 정확히 무엇인지 아는 것이 도움이 될 수 있습니다. 당신이 익숙하다면 마기스크, 그러면 시즈쿠도 비슷합니다. 그러나 루트 액세스를 관리하는 대신 셸 액세스를 관리합니다.
Shizuku는 셸 수준 권한으로 자체 프로세스를 실행합니다. 사용자가 해당 프로세스를 활성화하는 방법은 기기, Android 버전 및 선택에 따라 다릅니다. Shizuku는 ADB, 온디바이스 무선 ADB(Android 11 이상) 또는 루트 액세스를 통해. 그런 다음 Shizuku를 구현하는 앱은 해당 프로세스를 사용하여 향상된 작업을 수행할 수 있는 권한을 요청할 수 있습니다.
가격: 무료.
4.1.
왜 시즈쿠인가?
시스템에 대한 셸 수준 액세스로는 다음과 같은 작업을 수행할 수 없습니다. 뿌리, 일반 앱보다 더 많은 액세스 권한을 제공합니다. 게다가 Shizuku의 작동 방식을 통해 Android API를 거의 평소처럼 사용할 수 있습니다. 쉘 명령에 의존할 필요는 없습니다(원하는 경우에는 가능하지만).
앱에 ADB(또는 루트)를 통해서만 부여할 수 있는 특별한 권한이 필요한 경우 Shizuku와 Android 11은 훌륭한 조합을 이룹니다. Shizuku를 사용하여 기기 전체에서 특별한 권한을 부여할 수 있습니다.
Android 11이 탑재되지 않은 기기에서도 Shizuku는 유용할 수 있습니다. 앱은 사용자를 위한 지침과 스크립트를 제공하므로 귀하는 그럴 필요가 없습니다.
완성
앱에 Shizuku를 추가하는 것은 가장 간단하지는 않지만 어렵지도 않습니다. 불행히도, 개발자 문서 완전히 완료되지는 않았지만 이 문서에서 다뤘습니다. Shizuku를 앱에 통합하는 방법은 다음과 같습니다.
종속성
첫 번째 단계는 Shizuku 종속성을 추가하는 것입니다. 모듈 수준 build.gradle에서 종속성 블록에 다음을 추가합니다.
def shizuku_version = '11.0.3'
implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"
필요한 경우 버전을 업데이트하세요. 글을 쓰는 시점에서는 11.0.3이 최신 버전입니다.
공급자
Shizuku가 작동하려면 앱 매니페스트에 공급자 블록을 추가해야 합니다. AndroidManifest.xml을 열고 애플리케이션 블록 내부에 다음을 추가합니다.
android: name="rikka.shizuku.ShizukuProvider"
android: authorities="${applicationId}.shizuku"
android: multiprocess="false"
android: enabled="true"
android: exported="true"
android: permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
허가
인증을 위해 Shizuku는 런타임 권한을 사용합니다. 잠시 후 해당 권한을 부여하는 방법을 살펴보겠습니다. 지금은 매니페스트 블록 내부의 AndroidManifest.xml에 추가하세요.
이제 모든 것이 추가되었으므로 기본 통합이 완료되었습니다. Gradle이 프로젝트 동기화를 수행하도록 하고 계속해서 사용법을 진행합니다.
용법
가능 여부 확인
Shizuku를 사용하는 방법을 알아보기 전에 실제로 사용할 수 있는지 확인하는 방법에 대해 이야기해 보겠습니다.
권한이 부여되었는지 확인하기 전, 그리고 Shizuku를 통해 API 호출을 하기 전에 다음 방법을 사용하여 해당 확인 및 호출이 성공하는지 확인할 수 있습니다.
Shizuku.pingBinder()
Shizuku가 설치되어 실행 중이면 다음이 반환됩니다. 진실. 그렇지 않으면 false를 반환합니다.
권한 부여
Shizuku는 런타임 권한을 사용하므로 셸 액세스로 작업을 수행하려면 먼저 앱에 이 권한을 부여해야 합니다. 또한 부여 방법이 다른 두 가지 API 버전이 유통되고 있습니다. 이 섹션에서는 두 가지를 모두 처리하는 방법을 보여줍니다.
확인 중
권한을 요청하기 전에 가장 좋은 방법은 권한이 이미 있는지 확인하는 것입니다. 그렇게 하면 해야 할 일을 계속할 수 있습니다. 그렇지 않으면 계속하기 전에 요청해야 합니다.
Shizuku 사용 권한이 있는지 확인하려면 다음을 사용하세요. 이 코드는 활동 내에서 실행된다고 가정합니다.
코틀린:
val isGranted = if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED
} else {
Shizuku.checkSelfPermission() = PackageManager.PERMISSION_GRANTED
}
자바:
boolean isGranted;
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
isGranted = checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED;
} else {
isGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED;
}
요청 중
Shizuku 사용 권한을 요청해야 하는 경우 방법은 다음과 같습니다.
그만큼 SHIZUKU_CODE 아래에 사용되는 변수는 일정한 값을 갖는 정수(정적 변수)여야 합니다.
코틀린:
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(arrayOf(ShizukuProvider.PERMISSION), SHIZUKU_CODE)
} else {
Shizuku.requestPermission(SHIZUKU_CODE)
}
자바:
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(newString[] { ShizukuProvider.PERMISSION }, SHIZUKU_CODE);
} else {
Shizuku.requestPermissions(SHIZUKU_CODE);
}
결과를 수신하려면 활동을 재정의해야 합니다. onRequestPermissionsResult() 방법. 또한 다음을 구현해야 합니다. 시즈쿠. OnRequestPermissionResultListener. 제가 보여드릴 예에서는 활동이 해당 인터페이스를 구현한다고 가정하지만 원하는 경우 이를 변수에 구현할 수 있습니다.
코틀린:
classExampleActivity : AppCompatActivity, Shizuku.OnRequestPermissionResultListener {
...override void onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
permissions.forEachIndexed { index, permission ->
if (permission == ShizukuProvider.PERMISSION) {
onRequestPermissionResult(requestCode, grantResults[index])
}
}
}override voidonRequestPermissionResult(requestCode: Int, grantResult: Int){
val isGranted = grantResult == PackageManager.PERMISSION_GRANTED
//Dostuff based on the result.
}
}
자바:
publicclassExampleActivityextendsAppCompatActivityimplementsShizuku.OnRequestPermissionResultListener{
...@Override
publicvoidonRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int result = grantResults[i];if (permission.equals(ShizukuProvider.PERMISSION) {
onRequestPermissionResult(requestCode, result);
}
}
}
@Override
publicvoidonRequestPermissionResult(int requestCode, int grantResult){
boolean isGranted = grantResult == PackageManager.PERMISSION_GRANTED;
//Dostuff based on the result.
}
}
API 사용
이제 Shizuku가 설정되고 권한이 부여되었으므로 Shizuku를 사용하여 실제로 API 호출을 시작할 수 있습니다. 여기의 프로세스는 이전에 익숙했던 프로세스와 약간 다릅니다. 전화하는 대신 getSystemService()
그리고 다음과 같은 것으로 캐스팅 WindowManager
, 대신 내부 API를 사용해야 합니다(예: IWindowManager
).
Shizuku에는 Android의 숨겨진 API 블랙리스트에 대한 우회 기능이 포함되어 있으므로 사용할 때 이에 대해 걱정할 필요가 없습니다. 하지만 이를 직접 우회하는 방법이 궁금하다면 이전 튜토리얼을 확인하세요.
다음은 인스턴스를 얻는 방법을 보여주는 간단한 예(리플렉션 사용)입니다. IPackageManager
이를 사용하여 앱에 런타임 권한을 부여합니다.
코틀린:
val iPmClass = Class.forName("android.content.pm.IPackageManager")
val iPmStub = Class.forName("android.content.pm.IPackageManager\$Stub")
val asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder::class.java)
val grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String:: class.java /* package name */, String:: class.java /* permission name */, Int:: class.java /* user ID */)val iPmInstance = asInterfaceMethod.invoke(null, ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")))
grantRuntimePermissionMethod.invoke(iPmInstance, "com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
자바:
Class< ?>> iPmClass = Class.forName("android.content.pm.IPackageManager");
Class< ?>> iPmStub = Class.forName("android.content.pm.IPackageManager$Stub");
Method asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder.class);
Method grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String.class, String.class, int.class);val iPmInstance = asInterfaceMethod.invoke(null, new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")));
grantRuntimePermissionMethod.invoke(iPmInstance, "com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);
다른 API의 프로세스도 비슷합니다. 클래스 및 해당 Stub 하위 클래스에 대한 참조를 가져옵니다. 에 대한 참조를 얻으십시오. asInterface
Stub 클래스의 메서드입니다. 클래스 자체에서 원하는 메서드에 대한 참조를 가져옵니다. 그런 다음 asInterface
가지고 있는 메서드 참조, 교체 "package"
필요한 서비스가 무엇이든 위에 있습니다. 그러면 해당 인스턴스가 메서드 호출을 위해 전달될 수 있습니다.
전문가 팁: 수정된 SDK를 설치하면 리플렉션 사용을 완전히 피할 수 있습니다. anggrayudi의 GitHub 저장소를 확인하세요. 수정된 SDK 및 설치 지침을 확인하세요. 이를 설치하면 위의 코드(Kotlin)가 훨씬 더 간단해집니다.
val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
Shizuku가 실행 중이고 앱에 권한이 있는 한 모든 AIDL 기반 API는 앱의 어느 곳에서나 이 메서드와 함께 사용할 수 있습니다.
사용자 서비스
Binder 메서드는 대부분의 사용 사례를 다루지만 직접 Binder 인터페이스가 없는 API가 필요한 경우가 있을 수 있습니다. 이 경우 Shizuku의 사용자 서비스 기능을 사용할 수 있습니다.
이 방법은 Android의 일반 서비스와 유사하게 작동합니다. 이를 "시작"하고 ServiceConnection을 사용하여 바인딩하여 통신하고 서비스 클래스에서 논리를 실행합니다. 차이점은 Android 서비스를 사용하지 않고 서비스 내부의 모든 항목이 ADB 권한으로 실행된다는 것입니다.
이제 몇 가지 제한 사항이 있습니다. 사용자 서비스는 완전히 별도의 프로세스와 사용자에서 실행되므로 자체 AIDL 콜백 및 바인더를 통하지 않고는 앱의 나머지 부분과 상호작용할 수 없습니다. 적절한 앱 프로세스에서도 실행되지 않기 때문에 Android 서비스 바인딩과 같은 일부 작업이 제대로 작동하지 않을 수 있습니다.
이를 위해서는 Shizuku 버전 10 이상이 필요합니다. 현재 대부분의 앱 소스에는 버전 11이 있지만 예제에 포함될 버전 확인도 포함해야 합니다.
AIDL 정의
시작하려면 새 AIDL 파일을 만들어야 합니다. Android 파일 보기에서 아무 항목이나 마우스 오른쪽 버튼으로 클릭하고 '새로 만들기' 옵션 위에 마우스를 놓고 'AIDL' 옵션을 선택하면 Android 스튜디오에서 이 작업을 수행할 수 있습니다. 파일 이름(예: "IUserService")을 입력하면 Android Studio가 템플릿 파일을 생성합니다.
AIDL 작동 방식에 익숙하지 않다면 꼭 확인해 보세요. Google 문서.
AIDL에서 템플릿 메소드를 제거한 다음 destroy()
Shizuku에 대한 적절한 ID를 사용하는 메서드입니다. 이는 Shizuku가 사용자 서비스를 종료할 때 호출되며, 참조나 진행 중인 논리를 정리하는 데 사용해야 합니다.
AIDL 예:
packagetk.zwander.shizukudemo;
interfaceIUserService{
voiddestroy()= 16777114;
void grantPermission(String packageName, String permission, int userId) = 1; //You can specify your own method IDs in the AIDL. Check out the documentation for more details on this.
}
AIDL 구현
다음으로 할 일은 실제로 AIDL을 구현하는 것입니다.
자바:
publicclassUserServiceextendsIUserService.Stub{
//Make sure to include an empty constructor.
publicUserService(){
}@Override
publicvoiddestroy(){
//Shizuku wants the service to be killed. Clean up and then exit.
System.exit(0);
}
@Override
publicvoidgrantPermission(String packageName, String permission, int userId){
//No need to use ShizukuBinderWrapper.
IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName, permission, userId);
}
}
코틀린:
classUserService : IUserService.Stub {
//Include an empty constructor.
constructor() {
}override fun destroy(){
//Shizuku wants the service to be killed. Clean up and exit.
System.exit(0)
}
override fun grantPermission(packageName: String, permission: String, userId: Int) {
//No need for ShizukuBinderWrapper.
IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName, permission, userId)
}
}
위의 예에서는 다음을 가지고 있다고 가정합니다. 안드로이드 숨겨진 API SDK가 설치되었습니다.
서비스 연결 설정
이제 사용자 서비스가 정의되고 구현되었으므로 사용할 수 있도록 설정해야 합니다. 가장 먼저 해야 할 일은 통신하려는 ServiceConnection을 정의하는 것입니다(예: 앱의 기본 활동에서).
자바:
private IUserService binder = null;
privatefinal ServiceConnection connection = new ServiceConnection() {
@Override
publicvoidonServiceConnected(ComponentName componentName, IBinder binder){
if (binder != null && binder.pingBinder()) {
binder = IUserService.Stub.asInterface(binder);
}
}
@Override
publicvoidonServiceDisconnected(ComponentName componentName){
binder = null;
}
}
코틀린:
privatevar binder: IUserService? = null
private val connection = object: ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, binder: IBinder?){
if (binder != null && binder.pingBinder()) {
binder = IUserService.Stub.asInterface(binder)
}
}
override fun onServiceDisconnected(componentName: ComponentName){
binder = null;
}
}
그만큼 binder
변수는 앱에서 사용자 서비스와 통신하는 데 사용할 것입니다. 사용할 수 있는지 확인하려면 null이 아닌지 확인하세요. pingBinder()
보고 진실, 위의 코드 예시와 같습니다.
사용자 서비스 인수 만들기
사용자 서비스를 제어하기 전에 Shizuku가 시작하고 중지할 때 사용할 몇 가지 인수를 정의해야 합니다. 여기에는 실제로 Shizuku에 서비스의 클래스 이름을 알려주는 것, 프로세스 접미사를 지정하는 것, 디버그 가능한지 여부, 버전이 무엇인지 등이 포함됩니다.
자바:
privatefinal Shizuku.UserServiceArgs serviceArgs = new Shizuku.UserServiceArgs(
newComponentName(BuildConfig.APPLICATION_ID, UserService.class.getName()))
.processNameSuffix("user_service")
.debuggable(BuildConfig.DEBUG)
.version(BuildConfig.VERSION_CODE);
코틀린:
private val serviceArgs = Shizuku.UserServiceArgs(
ComponentName(BuildConfig.APPLICATION_ID, UserService.class::java.getName()))
.processNameSuffix("user_service")
.debuggable(BuildCOnfig.DEBUG)
.version(BuildConfig.VERSION_CODE)
사용자 서비스 시작, 중지 및 바인딩
시작 및 바인딩 작업과 중지 및 바인딩 해제 작업이 Shizuku에서 통합되었습니다. 별도의 시작 및 바인딩 방법이나 중지 및 바인딩 해제 방법이 없습니다.
방법은 다음과 같습니다. 시작 및 바인딩 사용자 서비스.
자바:
if (Shizuku.getVersion >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection);
} else {
//Tell the user to upgrade Shizuku.
}
코틀린:
if (Shizuku.getVersion() >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection)
} else {
//Tell the user to upgrade Shizuku.
}
방법은 다음과 같습니다. 중지하고 바인딩 해제 사용자 서비스.
자바:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}
코틀린:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}
사용자 서비스 호출
사용자 서비스가 시작되면 사용을 시작할 수 있습니다. 간단하게 binder
변수는 null이 아니고 ping이 가능합니다. 그런 다음 메서드를 호출하세요.
자바:
if (binder != null && binder.pingBinder()) {
binder.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);
}
코틀린:
if (binder?.pingBinder() == true) {
binder?.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
}
결론
이 모든 작업을 수행했다면 이제 작동 중인 Shizuku 통합이 완료되었을 것입니다. 사용자에게 Shizuku를 설치하라고 알리고 Shizuku를 사용하기 전에 Shizuku를 사용할 수 있는지 제대로 확인하는 것을 잊지 마십시오.