Rootingul nu este pentru toată lumea. Iată cum puteți obține permisiuni ridicate la nivel de shell în aplicația dvs. folosind biblioteca Shizuku.
Există o mulțime de motive pentru care permisiunile acordate în mod normal aplicației dvs. ar putea să nu fie suficiente. Poate ești ca mine și îți place să creezi aplicații hacker care abuzează de API-ul Android. Unele dintre API-urile pe care le folosesc sunt blocate în spatele permisiunilor speciale. Uneori, doar utilizatorul shell (ADB) sau sistemul le poate accesa. Există totuși o soluție - Shizuku.
Shizuku vă permite să apelați API-urile de sistem aproape direct și în întregime în Java sau Kotlin. Acest ghid vă va arăta cum să implementați și să utilizați Shizuku.
Ce este Shizuku?
Înainte de a folosi Shizuku, ar putea fi util să știm ce este exact. Dacă ești familiarizat cu Magisk, atunci Shizuku este similar. Dar, în loc să gestioneze accesul la rădăcină, gestionează accesul la shell.
Shizuku rulează propriul proces cu permisiuni la nivel de shell. Modul în care utilizatorul activează acest proces depinde de dispozitivul său, de versiunea Android și de alegere. Shizuku poate fi activat prin ADB, prin ADB wireless pe dispozitiv (
pe Android 11 și versiuni ulterioare), sau prin acces root. Aplicațiile care implementează Shizuku pot solicita apoi permisiunea de a utiliza acel proces pentru a efectua operațiuni cu nivel ridicat.Pret: Gratuit.
4.1.
De ce Shizuku?
În timp ce accesul la nivel de shell la sistem nu vă permite să faceți atât de mult ca rădăcină, încă vă oferă mai mult acces decât o aplicație normală. În plus, modul în care funcționează Shizuku vă permite să utilizați API-urile Android aproape ca de obicei. Nu trebuie să vă bazați pe comenzile shell (deși puteți dacă doriți).
Dacă aplicația dvs. are nevoie de permisiuni speciale care pot fi acordate numai prin ADB (sau cu root), Shizuku și Android 11 fac o pereche excelentă. Puteți folosi Shizuku pentru a acorda permisiuni speciale complet pe dispozitiv.
Chiar și cu dispozitive care nu sunt pe Android 11, Shizuku poate fi util. Aplicația oferă instrucțiuni și scripturi pentru utilizatori, astfel încât să nu fie nevoie.
Integrare
Adăugarea lui Shizuku la aplicația ta nu este cea mai simplă, dar nici nu este greu. Din păcate, documentația dezvoltatorului nu este complet complet, dar acest articol vă acoperă. Iată cum să integrezi Shizuku în aplicația ta.
Dependente
Primul pas este să adăugați dependențele Shizuku. În build.gradle la nivel de modul, adăugați următoarele la blocul de dependențe.
def shizuku_version = '11.0.3'
implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"
Asigurați-vă că actualizați versiunea dacă este necesar. 11.0.3 este cel mai recent la momentul scrierii.
Furnizor
Pentru ca Shizuku să funcționeze, trebuie să adăugați un bloc de furnizor la manifestul aplicației dvs. Deschideți AndroidManifest.xml și adăugați următoarele în blocul aplicației.
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" />
Permisiune
Pentru autorizare, Shizuku folosește o permisiune de rulare. Vom începe să acordăm această permisiune în curând. Pentru moment, adăugați-l la AndroidManifest.xml în interiorul blocului manifest.
Acum că toate acestea sunt adăugate, integrarea de bază este făcută. Lăsați-l pe Gradle să sincronizeze un proiect și continuați la Utilizare.
Utilizare
Verifica disponibilitatea
Înainte de a înțelege cum să folosiți Shizuku, să vorbim despre a ne asigura că este efectiv disponibil pentru utilizare.
Înainte de a verifica dacă permisiunea este acordată și înainte de a efectua apeluri API prin Shizuku, vă puteți asigura că acele verificări și apeluri vor avea succes cu următoarea metodă:
Shizuku.pingBinder()
Dacă Shizuku este instalat și rulează, acesta va reveni Adevărat. În caz contrar, va returna false.
Acordarea permisiunii
Deoarece Shizuku folosește o permisiune de rulare, aceasta trebuie să fie acordată aplicației dvs. înainte de a putea face ceva cu acces shell. Există, de asemenea, două versiuni API în circulație, cu modalități diferite de acordare. Această secțiune vă va arăta cum să le gestionați pe ambele.
Control
Înainte de a solicita permisiunea, cel mai bine este să verificați dacă o aveți deja. Dacă o faci, poți continua cu tot ce trebuie să faci. În caz contrar, va trebui să îl solicitați înainte de a continua.
Pentru a verifica dacă aveți permisiunea de a folosi Shizuku, puteți utiliza următoarele. Acest cod presupune că îl rulați într-o activitate.
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;
}
Solicitarea
Dacă trebuie să solicitați permisiunea de a folosi Shizuku, iată cum.
The SHIZUKU_CODE variabila utilizată mai jos ar trebui să fie un număr întreg cu o valoare constantă (variabilă statică).
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);
}
Pentru a asculta rezultatul, va trebui să înlocuiți activitățile onRequestPermissionsResult() metodă. De asemenea, va trebui să implementați Shizuku. OnRequestPermissionResultListener. Exemplul pe care îl voi arăta presupune că Activitatea dvs. implementează acea interfață, dar o puteți implementa într-o variabilă dacă doriți.
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.
}
}
Utilizarea API-urilor
Acum că Shizuku este configurat și permisiunile sunt acordate, puteți începe să apelați efectiv API-uri folosind Shizuku. Procesul de aici este puțin diferit de cel cu care ați fi obișnuit. În loc să sune getSystemService()
și casting la ceva de genul WindowManager
, va trebui să utilizați API-urile interne pentru acestea (de ex., IWindowManager
).
Shizuku include un bypass pentru lista neagră ascunsă a API-ului Android, așa că nu va trebui să vă faceți griji pentru asta atunci când îl utilizați. Dacă sunteți curios cum să ocoliți singur, consultați tutorialul meu anterior.
Iată un exemplu rapid (folosind reflecția) care vă arată cum puteți obține o instanță IPackageManager
și folosiți-l pentru a acorda unei aplicații permisiunea de rulare.
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);
Procesul pentru alte API-uri este similar. Obțineți referințele la clasă și subclasa Stub. Obțineți referința la asInterface
metoda pentru clasa Stub. Obțineți referințele la metodele dorite din clasa în sine. Apoi, invocați asInterface
referință de metodă pe care o aveți, înlocuind "package"
mai sus cu orice serviciu de care ai nevoie. Acea instanță poate fi apoi transmisă pentru invocări de metode.
Sfat pro: puteți evita cu totul utilizarea reflectării dacă instalați un SDK modificat. Consultați depozitul GitHub al anggrayudi pentru SDK-urile modificate și instrucțiunile de instalare. Cu acesta instalat, codul de mai sus (în Kotlin) devine mult mai simplu.
val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
Orice API-uri bazate pe AIDL pot fi folosite cu această metodă de oriunde în aplicația dvs., atâta timp cât Shizuku rulează și aplicația dvs. are permisiunea.
Serviciul pentru utilizatori
În timp ce metoda Binder acoperă majoritatea cazurilor de utilizare, pot exista momente în care aveți nevoie de un API care nu are o interfață directă Binder. În acest caz, puteți utiliza funcția User Service din Shizuku.
Această metodă funcționează similar cu un serviciu normal din Android. Îl „porniți”, comunicați legându-vă de el cu un ServiceConnection și rulați logica în clasa de serviciu. Diferența este că nu utilizați serviciul Android și orice din interiorul serviciului rulează cu permisiuni ADB.
Acum există câteva limitări. Serviciul pentru utilizatori rulează într-un proces și utilizator complet separat, așa că nu puteți interacționa cu restul aplicației, decât prin propriile apeluri AIDL și Binders. Deoarece nici nu rulează într-un proces adecvat de aplicație, este posibil ca unele lucruri precum legarea serviciilor Android să nu funcționeze corect.
Acest lucru necesită și versiunea Shizuku 10 sau o versiune ulterioară. Deși majoritatea surselor pentru aplicație au versiunea 11 în prezent, ar trebui să includeți în continuare o verificare a versiunii, care va fi inclusă în exemplu.
Definirea unui AIDL
Pentru a începe, va trebui să creați un nou fișier AIDL. Puteți face acest lucru în Android Studio făcând clic dreapta pe orice din vizualizarea fișierului Android, trecând cu mouse-ul peste opțiunea „Nou” și alegând opțiunea „AIDL”. Introduceți numele fișierului (de exemplu, „IUserService”) și Android Studio va crea un fișier șablon pentru dvs.
Dacă nu sunteți familiarizat cu modul în care funcționează AIDL-urile, asigurați-vă că verificați documentația Google.
Eliminați metodele șablon din AIDL și apoi adăugați a destroy()
metoda cu ID-ul adecvat pentru Shizuku. Acesta va fi apelat atunci când Shizuku oprește Serviciul pentru utilizatori și ar trebui să fie folosit pentru a curăța orice referințe sau logica continuă pe care o aveți.
Exemplu 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.
}
Implementarea AIDL
Următorul lucru de făcut este de fapt implementarea 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)
}
}
Exemplele de mai sus presupun că aveți Android Hidden API SDK instalat.
Configurarea conexiunii de serviciu
Acum că Serviciul pentru utilizatori este definit și implementat, este timpul să îl configurați pentru utilizare. Primul lucru pe care ar trebui să-l faceți este să definiți o conexiune de serviciu în care doriți să comunicați cu aceasta (de exemplu, din activitatea principală din aplicația dvs.).
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
variabila este ceea ce veți folosi pentru a comunica cu Serviciul pentru utilizatori din aplicația dvs. Pentru a verifica dacă este disponibil pentru utilizare, verificați doar că nu este nul și asta pingBinder()
se intoarce Adevărat, la fel ca în exemplul de cod de mai sus.
Crearea argumentelor User Service
Înainte de a putea controla Serviciul pentru utilizatori, va trebui să definiți câteva argumente pentru ca Shizuku să le folosească la pornirea și oprirea acestuia. Acestea includ lucruri precum spunerea lui Shizuku a numelui de clasă al serviciului, specificarea sufixului procesului, dacă este sau nu depanabil și ce versiune este.
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)
Pornirea, oprirea și legarea serviciului utilizator
Acțiunile de pornire și de legare și acțiunile de oprire și dezlegare sunt unificate în Shizuku. Nu există metode separate de pornire și legare sau metode de oprire și dezlegare.
Iată cum să porniți și legați Serviciul pentru utilizatori.
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.
}
Iată cum să opriți și dezlegați Serviciul pentru utilizatori.
Java:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}
Kotlin:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}
Invocarea Serviciului pentru utilizatori
Odată pornit Serviciul pentru utilizatori, puteți începe să îl utilizați. Pur și simplu verificați dacă binder
variabila nu este nulă și se poate face ping și apoi efectuați apelul de metodă.
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)
}
Concluzie
Dacă ați urmat toate acestea, ar trebui să aveți acum o integrare Shizuku funcțională. Nu uitați să spuneți utilizatorilor să instaleze Shizuku și să verificați în mod corespunzător dacă Shizuku este disponibil înainte de a încerca să-l folosească.