Как да повишите разрешенията за приложения с помощта на библиотеката Shizuku

Руутването не е за всеки. Ето как можете да получите повишени разрешения на ниво обвивка във вашето приложение с помощта на библиотеката Shizuku.

Има много причини разрешенията, които обикновено се дават на вашето приложение, да не са достатъчни. Може би сте като мен и се наслаждавате на създаването на хакерски приложения, които злоупотребяват с Android API. Някои от API-тата, които използвам, са заключени зад специални разрешения. Понякога само потребителят на обвивката (ADB) или системата имат достъп до тях. Все пак има решение — Шизуку.

Shizuku ви позволява да извиквате системни API почти директно и изцяло в Java или Kotlin. Това ръководство ще ви покаже как да приложите и използвате Shizuku.

Какво е Шизуку?

Преди да започнем да използваме Shizuku, може да е полезно да знаем какво точно представлява. Ако сте запознати с Magisk, тогава Шизуку е подобен. Но вместо да управлява root достъпа, той управлява shell достъпа.

Shizuku изпълнява свой собствен процес с разрешения на ниво обвивка. Как потребителят активира този процес зависи от неговото устройство, версия на Android и избор. Shizuku може да се активира чрез ADB, чрез безжичен ADB на устройството (

на Android 11 и по-нови версии), или чрез root достъп. След това приложенията, внедряващи Shizuku, могат да поискат разрешение да използват този процес за извършване на операции с повишени права.

ШизукуРазработчик: Xingchen & Rikka

Цена: Безплатно.

4.1.

Изтегли

Защо Шизуку?

Въпреки че достъпът до системата на ниво обвивка не ви позволява да правите толкова много, колкото корен, то все още ви дава повече достъп, отколкото обикновено приложение. Освен това начинът, по който работи Shizuku, ви позволява да използвате API на Android почти нормално. Не е нужно да разчитате на команди на shell (въпреки че можете, ако искате).

Ако приложението ви се нуждае от специални разрешения, които могат да бъдат предоставени само чрез ADB (или с root), 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, нека поговорим за това да се уверим, че наистина е достъпно за използване.

Преди да проверите дали разрешението е дадено и преди да направите API извиквания през Shizuku, можете да се уверите, че тези проверки и извиквания ще успеят със следния метод:

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
}

Java:

boolean isGranted;
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
isGranted = checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED;
} else {
isGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED;
}

Искане

Ако трябва да поискате разрешение за използване на Shizuku, ето как.

The ШИЗУКУ_КОД променливата, използвана по-долу, трябва да бъде цяло число с постоянна стойност (статична променлива).

Котлин:

if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(arrayOf(ShizukuProvider.PERMISSION), SHIZUKU_CODE)
} else {
Shizuku.requestPermission(SHIZUKU_CODE)
}

Java:

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.

}
}

Java:

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 е настроен и разрешенията са дадени, можете да започнете действително да извиквате API с помощта на Shizuku. Процесът тук е малко по-различен от този, с който може би сте свикнали. Вместо да се обадя getSystemService() и кастинг към нещо подобно WindowManager, вместо това ще трябва да използвате вътрешните API за тях (напр. IWindowManager).

Shizuku включва байпас за скрития черен списък на API на Android, така че няма нужда да се притеснявате за това, когато го използвате. Ако сте любопитни как да заобиколите това сами, вижте предишния ми урок.

Ето бърз пример (с помощта на отражение), който ви показва как можете да получите екземпляр на 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)

Java:

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. Разгледайте GitHub хранилището на anggrayudi за модифицираните SDK и инструкции за инсталиране. С това инсталиране горният код (в Kotlin) става много по-прост.

val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)

Всички базирани на AIDL API могат да се използват с този метод от всяко място в приложението ви, стига Shizuku да работи и приложението ви има разрешение.

Потребителско обслужване

Въпреки че методът на Binder покрива повечето случаи на употреба, може да има моменти, в които имате нужда от API, който няма директен интерфейс на Binder. В такъв случай можете да използвате функцията за потребителски услуги в Shizuku.

Този метод работи подобно на нормална услуга в Android. Вие го „стартирате“, комуникирате чрез обвързване към него със ServiceConnection и изпълнявате вашата логика в сервизния клас. Разликата е, че не използвате услугата на Android и всичко вътре в услугата работи с ADB разрешения.

Сега има някои ограничения. Потребителската услуга работи в напълно отделен процес и потребител, така че не можете да взаимодействате с останалата част от приложението си, освен чрез вашите собствени AIDL обратни извиквания и Binders. Тъй като също така не работи в правилен процес на приложение, някои неща като обвързване на Android услуги може да не работят правилно.

Това също изисква Shizuku версия 10 или по-нова. Въпреки че в момента повечето източници за приложението имат версия 11, все пак трябва да включите проверка на версията, която ще бъде включена в примера.

Дефиниране на AIDL

За да започнете, ще трябва да създадете нов AIDL файл. Можете да направите това в Android Studio, като щракнете с десния бутон върху нещо в изгледа на файлове на Android, задържите курсора на мишката над опцията „Ново“ и изберете опцията „AIDL“. Въведете името на файла (напр. „IUserService“) и Android Studio ще създаде шаблонен файл за вас.

Ако не сте запознати с това как работят AIDLs, не забравяйте да проверите Документация на Google.

Премахнете шаблонните методи от AIDL и след това добавете a destroy() метод с правилния идентификатор за Shizuku. Това ще бъде извикано, когато 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.

Java:

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 за Android SDK е инсталиран.

Настройка на връзката към услугата

Сега, когато потребителската услуга е дефинирана и внедрена, е време да я настроите за използване. Първото нещо, което трябва да направите, е да дефинирате ServiceConnection, където искате да комуникирате с него (напр. от основната дейност във вашето приложение).

Java:

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;
}
}

The binder променливата е това, което ще използвате, за да комуникирате с потребителската услуга от вашето приложение. За да проверите дали е достъпно за използване, просто проверете дали не е null и това pingBinder() се завръща вярно, точно както в примера с код по-горе.

Създаване на аргументи на потребителската услуга

Преди да можете да контролирате потребителската услуга, ще трябва да дефинирате някои аргументи, които Shizuku да използва, когато я стартира и спира. Те включват неща като действително казване на Shizuku на името на класа на услугата, уточняване на суфикса на процеса, дали може да се отстраняват грешки или не и каква версия е.

Java:

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. Няма отделни методи за стартиране и обвързване или методи за спиране и развързване.

Ето как да стартиране и обвързване потребителската услуга.

Java:

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.
}

Ето как да спрете и развържете потребителската услуга.

Java:

if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}

Котлин:

if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}

Извикване на потребителската услуга

След като потребителската услуга е стартирана, можете да започнете да я използвате. Просто проверете дали binder променливата не е нула и може да се пингува, след което извикайте метода си.

Java:

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 е наличен, преди да опитате да го използвате.