Hur man höjer appbehörigheter med Shizuku-biblioteket

click fraud protection

Rooting är inte för alla. Så här kan du få förhöjda behörigheter på skalnivå i din app med hjälp av Shizuku-biblioteket.

Det finns många anledningar till att de behörigheter som normalt ges till din app kanske inte räcker. Kanske är du som jag och tycker om att skapa hackiga appar som missbrukar Android API. Vissa av de API: er jag använder är låsta bakom speciella behörigheter. Ibland kan bara skalanvändaren (ADB) eller systemet komma åt dem. Det finns dock en lösning - Shizuku.

Shizuku låter dig anropa system-API: er nästan direkt, och helt i Java eller Kotlin. Den här guiden visar dig hur du implementerar och använder Shizuku.

Vad är Shizuku?

Innan vi börjar använda Shizuku kan det vara bra att veta exakt vad det är. Om du är bekant med Magisk, då är Shizuku liknande. Men istället för att hantera root-åtkomst, hanterar den skalåtkomst.

Shizuku kör sin egen process med behörigheter på skalnivå. Hur användaren aktiverar den processen beror på deras enhet, Android-version och val. Shizuku kan aktiveras via ADB, via trådlös ADB på enheten (

på Android 11 och senare), eller via root-åtkomst. Appar som implementerar Shizuku kan sedan begära tillstånd att använda den processen för att utföra förhöjda operationer.

ShizukuUtvecklare: Xingchen & Rikka

Pris: Gratis.

4.1.

Ladda ner

Varför Shizuku?

Även om åtkomst på skalnivå till systemet inte låter dig göra så mycket som rot, det ger dig fortfarande mer åtkomst än en vanlig app skulle göra. Utöver det låter Shizuku hur du fungerar att använda Android API: er nästan som vanligt. Du behöver inte förlita dig på skalkommandon (även om du kan om du vill).

Om din app behöver speciella behörigheter som endast kan beviljas via ADB (eller med root), är Shizuku och Android 11 en bra ihopkoppling. Du kan bara använda Shizuku för att ge speciella behörigheter helt på enheten.

Även med enheter som inte är på Android 11 kan Shizuku vara användbar. Appen tillhandahåller instruktioner och skript för användare så att du inte behöver.

Integration

Att lägga till Shizuku i din app är inte det enklaste, men det är inte svårt heller. Tyvärr, den utvecklardokumentation är inte helt komplett, men den här artikeln täcker dig. Så här integrerar du Shizuku i din app.

Beroenden

Det första steget är att lägga till Shizuku-beroenden. I din build.gradle på modulnivå, lägg till följande till beroendeblocket.

def shizuku_version = '11.0.3'

implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"

Se till att uppdatera versionen om det behövs. 11.0.3 är den senaste i skrivande stund.

Leverantör

För att Shizuku ska fungera måste du lägga till ett leverantörsblock i din app manifest. Öppna AndroidManifest.xml och lägg till följande i applikationsblocket.

 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" />

Lov

För auktorisering använder Shizuku en körtidsbehörighet. Vi kommer in på att ge det tillståndet om en stund. För nu, lägg till det i AndroidManifest.xml i manifestblocket.

Nu när allt detta har lagts till är den grundläggande integrationen klar. Låt Gradle göra en projektsynkronisering och fortsätt till Usage.


Användande

Kollar tillgänglighet

Innan vi går in på hur man använder Shizuku, låt oss prata om att se till att den faktiskt är tillgänglig att använda.

Innan du kontrollerar om behörigheten är beviljad och innan du gör API-anrop via Shizuku, kan du se till att dessa kontroller och anrop kommer att lyckas med följande metod:

Shizuku.pingBinder()

Om Shizuku är installerat och körs kommer detta att återkomma Sann. Annars kommer det att returnera falskt.

Bevilja tillstånd

Eftersom Shizuku använder en runtime-behörighet måste den ges till din app innan du kan göra något med skalåtkomst. Det finns också två API-versioner i omlopp, med olika sätt att bevilja det. Det här avsnittet visar hur du hanterar båda.

Kontroll

Innan du begär tillstånd är det bästa du kan göra att kontrollera om du redan har det. Om du gör det kan du fortsätta med vad du än behöver göra. Annars måste du begära det innan du fortsätter.

För att kontrollera om du har behörighet att använda Shizuku kan du använda följande. Den här koden förutsätter att du kör den i en aktivitet.

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

Begärande

Om du behöver begära tillstånd att använda Shizuku, så här.

De SHIZUKU_CODE variabel som används nedan ska vara ett heltal med ett konstant värde (statisk variabel).

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

För att lyssna efter resultatet måste du åsidosätta aktivitetens onRequestPermissionsResult() metod. Du måste också implementera Shizuku. OnRequestPermissionResultListener. Exemplet jag ska visa förutsätter att din aktivitet implementerar det gränssnittet, men du kan implementera det i en variabel om du vill.

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

Använder API: er

Nu när Shizuku har konfigurerats och behörigheter beviljats ​​kan du faktiskt börja anropa API: er med Shizuku. Processen här är lite annorlunda än du kanske är van vid. Istället för att ringa getSystemService() och casting till något liknande WindowManagermåste du använda de interna API: erna för dessa istället (t.ex. IWindowManager).

Shizuku inkluderar en bypass för Androids dolda API-svarta lista, så du behöver inte oroa dig för det när du använder den. Om du är nyfiken på hur du kan kringgå det själv, kolla in min tidigare handledning.

Här är ett snabbt exempel (med reflektion) som visar hur du kan få en instans av IPackageManager och använda den för att ge en app körningsbehörighet.

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

Processen för andra API: er är liknande. Få referenserna till klassen och dess Stub-underklass. Få referensen till asInterface metod för Stub-klassen. Få referenserna till de metoder du vill ha från själva klassen. Anropa sedan asInterface metodreferens du har, ersätter "package" ovan med vilken service du än behöver. Den instansen kan sedan skickas för metodanrop.

Proffstips: du kan undvika att använda reflektion helt och hållet om du installerar en modifierad SDK. Kolla in anggrayudis GitHub-förråd för de modifierade SDK: erna och installationsinstruktionerna. Med detta installerat blir ovanstående kod (i Kotlin) mycket enklare.

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

Alla AIDL-baserade API: er kan användas med den här metoden var som helst i din app, så länge Shizuku körs och din app har behörighet.

Användarservice

Även om Binder-metoden täcker de flesta användningsfall, kan det finnas tillfällen då du behöver ett API som inte har ett direkt Binder-gränssnitt. I så fall kan du använda funktionen Användarservice i Shizuku.

Denna metod fungerar på samma sätt som en normal tjänst i Android. Du "startar" den, kommunicerar genom att binda till den med en ServiceConnection och kör din logik i serviceklassen. Skillnaden är att du inte använder Androids tjänst, och allt i tjänsten körs med ADB-behörigheter.

Nu finns det några begränsningar. Användartjänsten körs i en helt separat process och användare, så du kan inte interagera med resten av din app förutom genom dina egna AIDL-återuppringningar och bindare. Eftersom det inte heller körs i en korrekt appprocess kanske vissa saker som att binda Android-tjänster inte fungerar korrekt.

Detta kräver också Shizuku version 10 eller senare. Även om de flesta källor för appen har version 11 för närvarande, bör du fortfarande inkludera en versionskontroll, som kommer att inkluderas i exemplet.

Definiera en AIDL

För att komma igång måste du skapa en ny AIDL-fil. Du kan göra detta i Android Studio genom att högerklicka på vad som helst i Android-filvyn, föra muspekaren över alternativet "Ny" och välja alternativet "AIDL". Ange namnet på filen (t.ex. "IUserService"), så skapar Android Studio en mallfil åt dig.

Om du inte är bekant med hur AIDL fungerar, se till att kolla in Googles dokumentation.

Ta bort mallmetoderna från AIDL och lägg sedan till en destroy() metod med rätt ID för Shizuku. Detta kommer att kallas när Shizuku dödar användartjänsten och bör användas för att rensa upp alla referenser eller pågående logik du har.

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

Implementering av AIDL

Nästa sak att göra är att faktiskt implementera 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)
}
}

Ovanstående exempel förutsätter att du har Android Hidden API SDK installerat.

Konfigurera serviceanslutningen

Nu när användartjänsten är definierad och implementerad är det dags att ställa in den för användning. Det första du bör göra är att definiera en ServiceConnection där du vill kommunicera med den (t.ex. från huvudaktiviteten i din app).

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

De binder variabel är vad du kommer att använda för att kommunicera med användartjänsten från din app. För att kontrollera om den är tillgänglig för användning, kontrollera bara att den inte är null och så pingBinder() returnerar Sann, precis som i kodexemplet ovan.

Skapar användartjänstargumenten

Innan du kan styra användartjänsten måste du definiera några argument som Shizuku ska använda när du startar och stoppar den. Dessa inkluderar saker som att faktiskt berätta för Shizuku klassens namn på tjänsten, specificera processsuffixet, om det är felsökningsbart eller inte och vilken version det är.

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)

Starta, stoppa och binda användartjänsten

Åtgärderna för start och bindning och stopp och avbindning är förenade i Shizuku. Det finns inga separata start- och bindningsmetoder eller stoppa och avbindningsmetoder.

Så här gör du starta och binda användartjänsten.

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

Så här gör du stoppa och lossa användartjänsten.

Java:

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

Kotlin:

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

Anropar användartjänsten

När användartjänsten har startat kan du börja använda den. Kontrollera helt enkelt om binder variabeln är icke-null och pingbar, och gör sedan ditt metodanrop.

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

Slutsats

Om du följde allt detta borde du nu ha en fungerande Shizuku-integration. Kom bara ihåg att berätta för dina användare att installera Shizuku och kontrollera att Shizuku är tillgängligt innan du försöker använda det.