Rootiranje nije za svakoga. Evo kako možete dobiti povišena dopuštenja na razini ljuske u svojoj aplikaciji pomoću biblioteke Shizuku.
Mnogo je razloga zašto dopuštenja koja se inače daju vašoj aplikaciji možda nisu dovoljna. Možda ste poput mene i uživate u stvaranju hakiranih aplikacija koje zlorabe Android API. Neki od API-ja koje koristim zaključani su iza posebnih dozvola. Ponekad im samo korisnik ljuske (ADB) ili sustav mogu pristupiti. Ipak postoji rješenje — Shizuku.
Shizuku vam omogućuje pozivanje sistemskih API-ja gotovo izravno, i to u potpunosti u Javi ili Kotlinu. Ovaj vodič će vam pokazati kako implementirati i koristiti Shizuku.
Što je Shizuku?
Prije nego počnemo koristiti Shizuku, moglo bi biti korisno znati što je to točno. Ako ste upoznati s Magisk, onda je Shizuku sličan. Ali umjesto upravljanja root pristupom, upravlja pristupom ljuske.
Shizuku pokreće vlastiti proces s dopuštenjima na razini ljuske. Kako korisnik aktivira taj proces ovisi o njegovom uređaju, verziji Androida i izboru. Shizuku se može aktivirati putem ADB-a, putem bežičnog ADB-a na uređaju (
na Androidu 11 i novijim), ili putem root pristupa. Aplikacije koje implementiraju Shizuku tada mogu zatražiti dopuštenje za korištenje tog procesa za izvođenje povišenih operacija.Cijena: besplatno.
4.1.
Zašto Shizuku?
Dok vam pristup sustavu na razini ljuske ne dopušta da učinite toliko koliko korijen, i dalje vam daje veći pristup nego normalna aplikacija. Povrh toga, način na koji Shizuku radi omogućuje vam korištenje Android API-ja gotovo kao i obično. Ne morate se oslanjati na naredbe ljuske (iako možete ako želite).
Ako su vašoj aplikaciji potrebna posebna dopuštenja koja se mogu dodijeliti samo putem ADB-a (ili s rootom), Shizuku i Android 11 sjajan su par. Možete samo koristiti Shizuku za dodjelu posebnih dopuštenja u potpunosti na uređaju.
Čak i s uređajima koji nemaju Android 11, Shizuku može biti koristan. Aplikacija pruža upute i skripte za korisnike tako da vi ne morate.
Integracija
Dodavanje Shizukua u vašu aplikaciju nije najjednostavnije, ali nije ni teško. Nažalost, dokumentacija programera nije baš potpun, ali ovaj članak vas pokriva. Evo kako integrirati Shizuku u svoju aplikaciju.
Ovisnosti
Prvi korak je dodavanje Shizuku ovisnosti. U build.gradle na razini modula dodajte sljedeće u blok ovisnosti.
def shizuku_version = '11.0.3'
implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"
Obavezno ažurirajte verziju ako je potrebno. 11.0.3 najnovija je u vrijeme pisanja.
Davatelj
Kako bi Shizuku radio, trebate dodati blok pružatelja u manifest svoje aplikacije. Otvorite AndroidManifest.xml i dodajte sljedeće unutar bloka aplikacije.
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" />
Dopuštenje
Za autorizaciju Shizuku koristi dopuštenje za vrijeme izvođenja. Uskoro ćemo pristupiti davanju tog dopuštenja. Za sada ga dodajte u AndroidManifest.xml unutar bloka manifesta.
Sada kada je sve to dodano, osnovna integracija je gotova. Neka Gradle izvrši sinkronizaciju projekta i nastavite na Korištenje.
Korištenje
Provjera dostupnosti
Prije nego što krenemo u korištenje Shizukua, razgovarajmo o tome da budemo sigurni da je stvarno dostupan za korištenje.
Prije provjere je li dopuštenje dodijeljeno i prije upućivanja API poziva putem Shizukua, možete provjeriti hoće li te provjere i pozivi uspjeti pomoću sljedeće metode:
Shizuku.pingBinder()
Ako je Shizuku instaliran i pokrenut, ovo će se vratiti pravi. U suprotnom, vratit će false.
Davanje dopuštenja
Budući da Shizuku koristi dopuštenje za vrijeme izvođenja, ono mora biti odobreno vašoj aplikaciji prije nego što možete učiniti bilo što s pristupom ljusci. U opticaju su i dvije verzije API-ja, s različitim načinima dodjele. Ovaj odjeljak će vam pokazati kako postupati s oboje.
Provjeravanje
Prije nego zatražite dopuštenje, najbolje je provjeriti imate li ga već. Ako to učinite, možete nastaviti sa svime što trebate učiniti. U suprotnom ćete ga morati zatražiti prije nastavka.
Kako biste provjerili imate li dopuštenje za korištenje Shizukua, možete koristiti sljedeće. Ovaj kod pretpostavlja da ga pokrećete unutar aktivnosti.
Kotlin:
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;
}
Zahtijevanje
Ako trebate zatražiti dopuštenje za korištenje Shizukua, evo kako.
The ŠIFRA_SHIZUKU varijabla korištena u nastavku treba biti cijeli broj sa stalnom vrijednošću (statička varijabla).
Kotlin:
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);
}
Da biste čuli rezultat, morat ćete nadjačati aktivnost onRequestPermissionsResult() metoda. Također ćete morati implementirati Shizuku. OnRequestPermissionResultListener. Primjer koji ću pokazati pretpostavlja da vaša aktivnost implementira to sučelje, ali ga možete implementirati u varijabli ako želite.
Kotlin:
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.
}
}
Korištenje API-ja
Sada kada je Shizuku postavljen i dopuštenja su odobrena, možete zapravo početi pozivati API-je koristeći Shizuku. Proces je ovdje malo drugačiji nego što ste možda navikli. Umjesto poziva getSystemService()
i lijevanje na nešto poput WindowManager
, umjesto toga morat ćete koristiti interne API-je (npr. IWindowManager
).
Shizuku uključuje premosnicu za Androidov skriveni API crni popis, tako da nećete morati brinuti o tome kada ga koristite. Ako vas ipak zanima kako to sami zaobići, pogledajte moj prethodni vodič.
Evo kratkog primjera (koristeći odraz) koji vam pokazuje kako možete dobiti instancu IPackageManager
i upotrijebite ga za dodjeljivanje dopuštenja za vrijeme izvođenja aplikaciji.
Kotlin:
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);
Postupak za druge API-je je sličan. Dobijte reference za klasu i njenu podklasu Stub. Dobijte referencu na asInterface
metoda za klasu Stub. Uzmite reference na metode koje želite iz same klase. Zatim pozovite asInterface
referenca metode koju imate, zamjena "package"
iznad s bilo kojom uslugom koja vam je potrebna. Ta se instanca zatim može proslijediti za pozivanje metode.
Stručni savjet: možete u potpunosti izbjeći korištenje refleksije ako instalirate modificirani SDK. Provjerite GitHub repozitorij anggrayudija za modificirane SDK-ove i upute za instalaciju. S ovim instaliranim, gornji kod (u Kotlinu) postaje puno jednostavniji.
val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
Bilo koji API-ji koji se temelje na AIDL-u mogu se koristiti ovom metodom s bilo kojeg mjesta u vašoj aplikaciji, sve dok je Shizuku pokrenut i vaša aplikacija ima dozvolu.
Korisnički servis
Dok Binder metoda pokriva većinu slučajeva upotrebe, ponekad će vam trebati API koji nema izravno Binder sučelje. U tom slučaju možete koristiti značajku korisničke usluge u Shizukuu.
Ova metoda radi slično normalnoj usluzi u Androidu. Vi ga "pokrenete", komunicirate vežući se za njega pomoću ServiceConnection i pokrećete svoju logiku u klasi usluge. Razlika je u tome što ne koristite Androidovu uslugu i sve unutar usluge radi s ADB dopuštenjima.
Sada postoje neka ograničenja. Korisnička usluga radi u potpuno odvojenom procesu i korisniku, tako da ne možete komunicirati s ostatkom svoje aplikacije osim putem vlastitih AIDL povratnih poziva i povezivača. Budući da također ne radi u odgovarajućem procesu aplikacije, neke stvari poput povezivanja Androidovih usluga možda neće ispravno raditi.
Ovo također zahtijeva Shizuku verziju 10 ili noviju. Iako većina izvora za aplikaciju trenutno ima verziju 11, ipak biste trebali uključiti provjeru verzije, koja će biti uključena u primjer.
Definiranje AIDL-a
Za početak morate izraditi novu AIDL datoteku. To možete učiniti u Android Studiju tako da desnom tipkom miša kliknete bilo što u Android prikazu datoteka, zadržite pokazivač iznad opcije "Novo" i odaberete opciju "AIDL". Unesite naziv datoteke (npr. "IUserService") i Android Studio će za vas stvoriti datoteku predloška.
Ako niste upoznati s načinom rada AIDL-a, svakako provjerite Googleova dokumentacija.
Uklonite metode predloška iz AIDL-a i zatim dodajte a destroy()
metoda s ispravnim ID-om za Shizuku. Ovo će se pozvati kada Shizuku ubije korisničku uslugu i trebalo bi se koristiti za čišćenje svih referenci ili tekuće logike koju imate.
Primjer AIDL-a:
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.
}
Implementacija AIDL-a
Sljedeće što treba učiniti je zapravo implementirati 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);
}
}
Kotlin:
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)
}
}
Gornji primjeri pretpostavljaju da imate Android skriveni API SDK instaliran.
Postavljanje servisne veze
Sada kada je korisnička usluga definirana i implementirana, vrijeme je da je postavite za korištenje. Prva stvar koju biste trebali učiniti je definirati ServiceConnection gdje želite komunicirati s njim (npr. iz glavne aktivnosti u vašoj aplikaciji).
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;
}
}
Kotlin:
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
varijabla je ono što ćete koristiti za komunikaciju s korisničkom uslugom iz svoje aplikacije. Da biste provjerili je li dostupan za upotrebu, samo provjerite da nije null i to pingBinder()
vraća pravi, baš kao u gornjem primjeru koda.
Stvaranje argumenata korisničke usluge
Prije nego što možete kontrolirati korisničku uslugu, morat ćete definirati neke argumente koje će Shizuku koristiti prilikom pokretanja i zaustavljanja. To uključuje stvari kao što je stvarno govorenje Shizukuu o nazivu klase usluge, određivanje sufiksa procesa, može li ili ne ispravljati pogreške i koja je verzija.
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);
Kotlin:
private val serviceArgs = Shizuku.UserServiceArgs(
ComponentName(BuildConfig.APPLICATION_ID, UserService.class::java.getName()))
.processNameSuffix("user_service")
.debuggable(BuildCOnfig.DEBUG)
.version(BuildConfig.VERSION_CODE)
Pokretanje, zaustavljanje i vezanje korisničke usluge
Radnje pokretanja i vezanja te radnje zaustavljanja i odvezivanja objedinjene su u Shizukuu. Ne postoje odvojene metode pokretanja i vezanja ili metode zaustavljanja i poništavanja vezanja.
Evo kako započeti i vezati korisničku uslugu.
Java:
if (Shizuku.getVersion >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection);
} else {
//Tell the user to upgrade Shizuku.
}
Kotlin:
if (Shizuku.getVersion() >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection)
} else {
//Tell the user to upgrade Shizuku.
}
Evo kako zaustaviti se i odvezati korisničku uslugu.
Java:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}
Kotlin:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}
Pozivanje korisničke usluge
Nakon što se korisnička usluga pokrene, možete je početi koristiti. Jednostavno provjerite je li binder
varijabla nije null i može se pingati, a zatim pozovite svoju metodu.
Java:
if (binder != null && binder.pingBinder()) {
binder.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);
}
Kotlin:
if (binder?.pingBinder() == true) {
binder?.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
}
Zaključak
Ako ste sve to pratili, sada biste trebali imati Shizuku integraciju koja radi. Samo ne zaboravite reći svojim korisnicima da instaliraju Shizuku i da pravilno provjere je li Shizuku dostupan prije nego što ga pokušaju koristiti.