Rootowanie nie jest dla każdego. Oto, jak uzyskać podwyższone uprawnienia na poziomie powłoki w swojej aplikacji, korzystając z biblioteki Shizuku.
Istnieje wiele powodów, dla których uprawnienia zwykle przyznawane Twojej aplikacji mogą nie wystarczyć. Może jesteś podobny do mnie i lubisz tworzyć hackerskie aplikacje, które wykorzystują interfejs API Androida. Niektóre interfejsy API, których używam, są zablokowane za specjalnymi uprawnieniami. Czasami dostęp do nich może uzyskać tylko użytkownik powłoki (ADB) lub system. Jest jednak rozwiązanie – Shizuku.
Shizuku pozwala na niemal bezpośrednie wywoływanie systemowych API, w całości w Javie lub Kotlinie. Ten przewodnik pokaże Ci, jak wdrożyć i używać Shizuku.
Co to jest Shizuku?
Zanim zaczniemy używać Shizuku, pomocne może być poznanie, co to dokładnie jest. Jeśli znasz Magisk, to Shizuku jest podobny. Ale zamiast zarządzać dostępem roota, zarządza dostępem do powłoki.
Shizuku uruchamia własny proces z uprawnieniami na poziomie powłoki. Sposób, w jaki użytkownik aktywuje ten proces, zależy od jego urządzenia, wersji Androida i wyboru. Shizuku można aktywować poprzez ADB, poprzez bezprzewodowe ADB na urządzeniu (
na Androidzie 11 i nowszych wersjach) lub poprzez dostęp do konta root. Aplikacje wdrażające Shizuku mogą następnie poprosić o pozwolenie na użycie tego procesu do wykonywania operacji z podwyższonym poziomem uprawnień.Cena: za darmo.
4.1.
Dlaczego Shizuku?
Chociaż dostęp do systemu na poziomie powłoki nie pozwala na tyle, co źródło, nadal zapewnia większy dostęp niż zwykła aplikacja. Co więcej, sposób działania Shizuku pozwala korzystać z interfejsów API Androida prawie jak zwykle. Nie musisz polegać na poleceniach powłoki (chociaż możesz, jeśli chcesz).
Jeśli Twoja aplikacja wymaga specjalnych uprawnień, które można przyznać tylko za pośrednictwem ADB (lub roota), Shizuku i Android 11 stanowią świetną parę. Możesz po prostu użyć Shizuku, aby przyznać specjalne uprawnienia w pełni na urządzeniu.
Nawet w przypadku urządzeń, które nie mają Androida 11, Shizuku może się przydać. Aplikacja udostępnia użytkownikom instrukcje i skrypty, dzięki czemu Ty nie musisz tego robić.
Integracja
Dodanie Shizuku do aplikacji nie jest najprostsze, ale też nie jest trudne. Niestety dokumentacja deweloperska nie jest dokładnie kompletny, ale w tym artykule wszystko zostało omówione. Oto jak zintegrować Shizuku ze swoją aplikacją.
Zależności
Pierwszym krokiem jest dodanie zależności Shizuku. W pliku build.gradle na poziomie modułu dodaj następujące elementy do bloku zależności.
def shizuku_version = '11.0.3'
implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"
W razie potrzeby zaktualizuj wersję. Wersja 11.0.3 jest najnowszą wersją w chwili pisania tego tekstu.
Dostawca
Aby Shizuku działało, musisz dodać blok dostawcy do manifestu swojej aplikacji. Otwórz plik AndroidManifest.xml i dodaj następujący tekst w bloku aplikacji.
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" />
Pozwolenie
Do autoryzacji Shizuku używa uprawnień wykonawczych. Za chwilę zajmiemy się udzielaniem tego pozwolenia. Na razie dodaj go do pliku AndroidManifest.xml w bloku manifestu.
Teraz, gdy to wszystko zostało dodane, podstawowa integracja została zakończona. Pozwól Gradle na synchronizację projektu i przejdź do Użycie.
Stosowanie
Sprawdzanie dostępności
Zanim przejdziemy do korzystania z Shizuku, porozmawiajmy o upewnieniu się, że jest on rzeczywiście dostępny do użycia.
Przed sprawdzeniem, czy zezwolenie zostało przyznane i przed wykonaniem wywołań API za pośrednictwem Shizuku, możesz upewnić się, że te kontrole i wywołania powiodą się, korzystając z następującej metody:
Shizuku.pingBinder()
Jeśli Shizuku jest zainstalowane i uruchomione, komunikat powróci PRAWDA. W przeciwnym razie zwróci wartość false.
Udzielenie pozwolenia
Ponieważ Shizuku korzysta z uprawnień wykonawczych, należy je przyznać aplikacji, zanim będzie można cokolwiek zrobić z dostępem do powłoki. W obiegu są także dwie wersje API, różniące się sposobami jego przyznawania. W tej sekcji dowiesz się, jak sobie poradzić z obydwoma.
Kontrola
Zanim poprosisz o pozwolenie, najlepiej jest sprawdzić, czy już je posiadasz. Jeśli tak, możesz kontynuować wszystko, co musisz zrobić. W przeciwnym razie musisz o to poprosić, zanim będziesz kontynuować.
Aby sprawdzić, czy masz pozwolenie na używanie Shizuku, możesz skorzystać z poniższych opcji. Ten kod zakłada, że uruchamiasz go wewnątrz działania.
Kotlina:
val isGranted = if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED
} else {
Shizuku.checkSelfPermission() = PackageManager.PERMISSION_GRANTED
}
Jawa:
boolean isGranted;
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
isGranted = checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED;
} else {
isGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED;
}
Prośba
Jeśli chcesz poprosić o pozwolenie na używanie Shizuku, oto jak to zrobić.
The KOD SHIZUKU zmienna używana poniżej powinna być liczbą całkowitą o stałej wartości (zmienna statyczna).
Kotlina:
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(arrayOf(ShizukuProvider.PERMISSION), SHIZUKU_CODE)
} else {
Shizuku.requestPermission(SHIZUKU_CODE)
}
Jawa:
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(newString[] { ShizukuProvider.PERMISSION }, SHIZUKU_CODE);
} else {
Shizuku.requestPermissions(SHIZUKU_CODE);
}
Aby nasłuchiwać wyniku, musisz zastąpić Activity onRequestPermissionsResult() metoda. Będziesz także musiał wdrożyć Shizuku. OnRequestPermissionResultListener. Przykład, który pokażę, zakłada, że Twoja aktywność implementuje ten interfejs, ale jeśli chcesz, możesz zaimplementować go w zmiennej.
Kotlina:
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.
}
}
Jawa:
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.
}
}
Korzystanie z API
Teraz, gdy Shizuku jest skonfigurowany i uprawnienia zostały przyznane, możesz zacząć wywoływać interfejsy API za pomocą Shizuku. Proces ten różni się nieco od tego, do czego jesteś przyzwyczajony. Zamiast dzwonić getSystemService()
i rzutowanie na coś takiego WindowManager
, zamiast tego będziesz musiał użyć do nich wewnętrznych interfejsów API (np. IWindowManager
).
Shizuku zawiera obejście ukrytej czarnej listy API Androida, więc nie musisz się tym martwić podczas korzystania z niej. Jeśli jednak ciekawi Cię, jak samodzielnie to ominąć, zapoznaj się z moim poprzednim tutorialem.
Oto krótki przykład (przy użyciu refleksji), pokazujący, jak można uzyskać instancję IPackageManager
i użyj go, aby przyznać aplikacji uprawnienia wykonawcze.
Kotlina:
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)
Jawa:
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);
Proces w przypadku innych interfejsów API jest podobny. Uzyskaj odniesienia do klasy i jej podklasy Stub. Uzyskaj odniesienie do asInterface
metoda dla klasy Stub. Uzyskaj odniesienia do żądanych metod z samej klasy. Następnie wywołaj asInterface
odwołanie do metody, które posiadasz, zastępując "package"
powyżej z dowolną usługą, której potrzebujesz. To wystąpienie można następnie przekazać do wywołań metod.
Wskazówka dla profesjonalistów: możesz całkowicie uniknąć odbicia, jeśli zainstalujesz zmodyfikowany zestaw SDK. Sprawdź repozytorium GitHub firmy anggrayudi dla zmodyfikowanych zestawów SDK i instrukcji instalacji. Po zainstalowaniu tego powyższy kod (w Kotlinie) staje się o wiele prostszy.
val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
Z tą metodą można używać dowolnych interfejsów API opartych na AIDL z dowolnego miejsca w aplikacji, o ile działa Shizuku i aplikacja ma uprawnienia.
Obsługa Użytkownika
Chociaż metoda Binder obejmuje większość przypadków użycia, może się zdarzyć, że będziesz potrzebować interfejsu API, który nie ma bezpośredniego interfejsu Bindera. W takim przypadku możesz skorzystać z funkcji Obsługi użytkownika w Shizuku.
Ta metoda działa podobnie do zwykłej usługi w systemie Android. „Uruchamiasz” go, komunikujesz się poprzez powiązanie z nim za pomocą ServiceConnection i uruchamiasz logikę w klasie usług. Różnica polega na tym, że nie korzystasz z usługi Androida, a wszystko w tej usłudze działa z uprawnieniami ADB.
Teraz istnieją pewne ograniczenia. Usługa użytkownika działa w ramach całkowicie oddzielnego procesu i użytkownika, więc nie możesz wchodzić w interakcję z resztą aplikacji, chyba że za pośrednictwem własnych wywołań zwrotnych AIDL i segregatorów. Ponieważ nie działa on również w odpowiednim procesie aplikacji, niektóre rzeczy, takie jak wiązanie usług Androida, mogą nie działać poprawnie.
Wymaga to również wersji Shizuku 10 lub nowszej. Chociaż większość źródeł aplikacji ma obecnie wersję 11, nadal powinieneś uwzględnić kontrolę wersji, co zostanie uwzględnione w przykładzie.
Definicja AIDL
Aby rozpocząć, musisz utworzyć nowy plik AIDL. Możesz to zrobić w Android Studio, klikając prawym przyciskiem myszy dowolny element w widoku plików Androida, najeżdżając kursorem na opcję „Nowy” i wybierając opcję „AIDL”. Wpisz nazwę pliku (np. „IUserService”), a Android Studio utworzy dla Ciebie plik szablonu.
Jeśli nie wiesz, jak działają AIDL, koniecznie sprawdź Dokumentacja Google.
Usuń metody szablonowe z AIDL, a następnie dodaj plik destroy()
metodę z właściwym identyfikatorem dla Shizuku. Zostanie to wywołane, gdy Shizuku zabije usługę użytkownika i powinno zostać użyte do oczyszczenia wszelkich odniesień lub bieżącej logiki, którą posiadasz.
Przykład 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.
}
Wdrażanie AIDL
Następną rzeczą do zrobienia jest faktyczne wdrożenie AIDL.
Jawa:
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);
}
}
Kotlina:
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)
}
}
W powyższych przykładach założono, że masz Ukryte API Androida Zainstalowano SDK.
Konfiguracja połączenia serwisowego
Teraz, gdy usługa użytkownika jest zdefiniowana i zaimplementowana, nadszedł czas, aby ją skonfigurować do użytku. Pierwszą rzeczą, którą powinieneś zrobić, to zdefiniować ServiceConnection, gdzie chcesz się z nim komunikować (np. z głównego działania w Twojej aplikacji).
Jawa:
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;
}
}
Kotlina:
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
zmienna jest tym, czego będziesz używać do komunikowania się z usługą użytkownika ze swojej aplikacji. Aby sprawdzić, czy jest dostępny do użycia, po prostu sprawdź, czy nie ma wartości null i tyle pingBinder()
zwroty PRAWDA, podobnie jak w powyższym przykładzie kodu.
Tworzenie argumentów usługi użytkownika
Zanim będziesz mógł kontrolować usługę użytkownika, musisz zdefiniować pewne argumenty, których Shizuku będzie używał podczas jej uruchamiania i zatrzymywania. Należą do nich takie rzeczy, jak faktyczne podanie Shizuku nazwy klasy usługi, określenie przyrostka procesu, tego, czy można go debugować i jaka to jest wersja.
Jawa:
privatefinal Shizuku.UserServiceArgs serviceArgs = new Shizuku.UserServiceArgs(
newComponentName(BuildConfig.APPLICATION_ID, UserService.class.getName()))
.processNameSuffix("user_service")
.debuggable(BuildConfig.DEBUG)
.version(BuildConfig.VERSION_CODE);
Kotlina:
private val serviceArgs = Shizuku.UserServiceArgs(
ComponentName(BuildConfig.APPLICATION_ID, UserService.class::java.getName()))
.processNameSuffix("user_service")
.debuggable(BuildCOnfig.DEBUG)
.version(BuildConfig.VERSION_CODE)
Uruchamianie, zatrzymywanie i wiązanie usługi użytkownika
Akcje uruchamiania i wiązania oraz akcje zatrzymywania i rozłączania są w Shizuku ujednolicone. Nie ma oddzielnych metod uruchamiania i wiązania ani metod zatrzymywania i unbind.
Oto jak to zrobić zacznij i zwiąż Serwisu Użytkownika.
Jawa:
if (Shizuku.getVersion >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection);
} else {
//Tell the user to upgrade Shizuku.
}
Kotlina:
if (Shizuku.getVersion() >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection)
} else {
//Tell the user to upgrade Shizuku.
}
Oto jak to zrobić zatrzymaj się i rozwiąż Serwisu Użytkownika.
Jawa:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}
Kotlina:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}
Wywołanie Usługi Użytkownika
Po uruchomieniu Usługi Użytkownika możesz zacząć z niej korzystać. Po prostu sprawdź, czy binder
zmienna nie ma wartości null i można ją pingować, a następnie wywołaj metodę.
Jawa:
if (binder != null && binder.pingBinder()) {
binder.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);
}
Kotlina:
if (binder?.pingBinder() == true) {
binder?.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
}
Wniosek
Jeśli wykonałeś to wszystko, powinieneś mieć teraz działającą integrację Shizuku. Pamiętaj tylko, aby powiedzieć użytkownikom, aby zainstalowali Shizuku i odpowiednio sprawdzili, czy Shizuku jest dostępne, zanim spróbujesz go użyć.