El enraizamiento no es para todos. Así es como puedes obtener permisos elevados a nivel de shell en tu aplicación usando la biblioteca Shizuku.
Hay muchas razones por las que los permisos normalmente otorgados a su aplicación podrían no ser suficientes. Tal vez eres como yo y disfrutas creando aplicaciones hacky que abusan de la API de Android. Algunas de las API que uso están bloqueadas con permisos especiales. A veces, sólo el usuario shell (ADB) o el sistema pueden acceder a ellos. Sin embargo, hay una solución: Shizuku.
Shizuku le permite llamar a las API del sistema casi directamente y completamente en Java o Kotlin. Esta guía le mostrará cómo implementar y utilizar Shizuku.
¿Qué es Shizuku?
Antes de comenzar a utilizar Shizuku, puede resultar útil saber qué es exactamente. Si estás familiarizado con MagiscoEntonces Shizuku es similar. Pero en lugar de gestionar el acceso raíz, gestiona el acceso al shell.
Shizuku ejecuta su propio proceso con permisos a nivel de shell. La forma en que el usuario activa ese proceso depende de su dispositivo, versión de Android y elección. Shizuku se puede activar a través de ADB, a través de ADB inalámbrico en el dispositivo (
en Android 11 y posterior), o mediante acceso root. Las aplicaciones que implementan Shizuku pueden solicitar permiso para usar ese proceso para realizar operaciones elevadas.Precio: Gratis.
4.1.
¿Por qué Shizuku?
Si bien el acceso a nivel de shell al sistema no le permite hacer tanto como raíz, aún te brinda más acceso que una aplicación normal. Además de eso, la forma en que funciona Shizuku te permite usar las API de Android casi como de costumbre. No es necesario que dependa de los comandos del shell (aunque puede hacerlo si lo desea).
Si su aplicación necesita permisos especiales que solo se pueden otorgar a través de ADB (o con root), Shizuku y Android 11 forman una excelente combinación. Puedes usar Shizuku para otorgar permisos especiales completamente en el dispositivo.
Incluso con dispositivos que no tienen Android 11, Shizuku puede resultar útil. La aplicación proporciona instrucciones y guiones a los usuarios para que usted no tenga que hacerlo.
Integración
Agregar Shizuku a tu aplicación no es lo más simple, pero tampoco es difícil. Desafortunadamente, el documentación del desarrollador No está exactamente completo, pero este artículo lo cubre. A continuación te explicamos cómo integrar Shizuku en tu aplicación.
Dependencias
El primer paso es agregar las dependencias de Shizuku. En su build.gradle a nivel de módulo, agregue lo siguiente al bloque de dependencias.
def shizuku_version = '11.0.3'
implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"
Asegúrese de actualizar la versión si es necesario. 11.0.3 es la última versión al momento de escribir este artículo.
Proveedor
Para que Shizuku funcione, debes agregar un bloque de proveedor al manifiesto de tu aplicación. Abra AndroidManifest.xml y agregue lo siguiente dentro del bloque de la aplicación.
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" />
Permiso
Para la autorización, Shizuku utiliza un permiso de tiempo de ejecución. En un momento abordaremos la concesión de ese permiso. Por ahora, agréguelo a AndroidManifest.xml dentro del bloque de manifiesto.
Ahora que todo eso está agregado, la integración básica está hecha. Deje que Gradle sincronice el proyecto y continúe con Uso.
Uso
Comprobando disponibilidad
Antes de explicar cómo usar Shizuku, hablemos de asegurarnos de que esté realmente disponible para su uso.
Antes de verificar si se otorga el permiso y antes de realizar llamadas API a través de Shizuku, puede asegurarse de que esas comprobaciones y llamadas se realicen correctamente con el siguiente método:
Shizuku.pingBinder()
Si Shizuku está instalado y ejecutándose, esto volverá verdadero. De lo contrario, devolverá falso.
Conceder permiso
Dado que Shizuku usa un permiso de tiempo de ejecución, se debe otorgar a su aplicación antes de que pueda hacer algo con acceso al shell. También hay en circulación dos versiones de API, con diferentes formas de otorgarla. Esta sección le mostrará cómo manejar ambos.
Comprobación
Antes de solicitar el permiso, lo mejor es comprobar si ya lo tienes. Si lo haces, puedes continuar con lo que necesites hacer. De lo contrario, deberás solicitarlo antes de continuar.
Para verificar si tiene permiso para usar Shizuku, puede usar lo siguiente. Este código supone que lo está ejecutando dentro de una Actividad.
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;
}
Solicitando
Si necesita solicitar permiso para usar Shizuku, aquí le explicamos cómo hacerlo.
El SHIZUKU_CODE La variable utilizada a continuación debe ser un número entero con un valor estable (variable estática).
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);
}
Para escuchar el resultado, deberá anular la Actividad onRequestPermissionsResult() método. También necesitarás implementar Shizuku. OnRequestPermissionResultListener. El ejemplo que voy a mostrar supone que tu Actividad implementa esa interfaz, pero puedes implementarla en una variable si lo deseas.
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.
}
}
Usando API
Ahora que Shizuku está configurado y se han otorgado los permisos, puede comenzar a llamar a las API usando Shizuku. El proceso aquí es un poco diferente al que quizás esté acostumbrado. en lugar de llamar getSystemService()
y lanzando a algo como WindowManager
, tendrás que usar las API internas para estos en su lugar (por ejemplo, IWindowManager
).
Shizuku incluye una omisión de la lista negra de API oculta de Android, por lo que no tendrás que preocuparte por eso cuando la uses. Si tienes curiosidad por saber cómo evitarlo tú mismo, consulta mi tutorial anterior.
Aquí hay un ejemplo rápido (usando la reflexión) que le muestra cómo puede obtener una instancia de IPackageManager
y utilícelo para otorgarle a una aplicación un permiso de tiempo de ejecución.
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);
El proceso para otras API es similar. Obtenga las referencias a la clase y su subclase Stub. Obtenga la referencia al asInterface
método para la clase Stub. Obtenga las referencias a los métodos que desee de la propia clase. Luego, invoca el asInterface
referencia del método que tienes, reemplazando "package"
arriba con cualquier servicio que necesites. Luego, esa instancia se puede pasar para invocaciones de métodos.
Consejo profesional: Puedes evitar el uso de la reflexión por completo si instalas un SDK modificado. Consulte el repositorio GitHub de anggrayudi para ver los SDK modificados y las instrucciones de instalación. Con esto instalado, el código anterior (en Kotlin) se vuelve mucho más simple.
val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
Cualquier API basada en AIDL se puede utilizar con este método desde cualquier lugar de su aplicación, siempre que Shizuku se esté ejecutando y su aplicación tenga permiso.
Servicio de usuario
Si bien el método Binder cubre la mayoría de los casos de uso, puede haber ocasiones en las que necesite una API que no tenga una interfaz directa de Binder. En ese caso, puedes utilizar la función Servicio de usuario en Shizuku.
Este método funciona de manera similar a un Servicio normal en Android. Lo "inicia", se comunica vinculándolo con una ServiceConnection y ejecuta su lógica en la clase de servicio. La diferencia es que no está utilizando el servicio de Android y todo lo que esté dentro del servicio se ejecuta con permisos ADB.
Ahora hay algunas limitaciones. El Servicio de usuario se ejecuta en un proceso y un usuario completamente separados, por lo que no puede interactuar con el resto de su aplicación excepto a través de sus propias devoluciones de llamadas y carpetas AIDL. Dado que tampoco se ejecuta en un proceso de aplicación adecuado, es posible que algunas cosas, como vincular servicios de Android, no funcionen correctamente.
Esto también requiere Shizuku versión 10 o posterior. Si bien la mayoría de las fuentes de la aplicación tienen actualmente la versión 11, aun así debes incluir una verificación de versión, que se incluirá en el ejemplo.
Definiendo un AIDL
Para comenzar, deberá crear un nuevo archivo AIDL. Puede hacer esto en Android Studio haciendo clic derecho en cualquier cosa en la vista de archivos de Android, colocando el cursor sobre la opción "Nuevo" y eligiendo la opción "AIDL". Ingrese el nombre del archivo (por ejemplo, "IUserService") y Android Studio creará un archivo de plantilla para usted.
Si no está familiarizado con cómo funcionan los AIDL, asegúrese de consultar documentación de google.
Elimine los métodos de plantilla del AIDL y luego agregue un destroy()
método con la identificación adecuada para Shizuku. Esto se llamará cuando Shizuku esté eliminando el Servicio de usuario y debe usarse para limpiar cualquier referencia o lógica continua que tenga.
Ejemplo 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.
}
Implementación del AIDL
Lo siguiente que hay que hacer es implementar 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)
}
}
Los ejemplos anteriores suponen que usted tiene la API oculta de Android SDK instalado.
Configurar la conexión de servicio
Ahora que el Servicio de Usuario está definido e implementado, es hora de configurarlo para su uso. Lo primero que debe hacer es definir una conexión de servicio con la que desea comunicarse (por ejemplo, desde la actividad principal de su aplicación).
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;
}
}
El binder
La variable es lo que usará para comunicarse con el Servicio de usuario desde su aplicación. Para verificar si está disponible para su uso, simplemente verifique que no sea nulo y que pingBinder()
devoluciones verdadero, tal como en el ejemplo de código anterior.
Crear los argumentos del servicio de usuario
Antes de que puedas controlar el Servicio de Usuario, necesitarás definir algunos argumentos para que Shizuku los use al iniciarlo y detenerlo. Estos incluyen cosas como decirle a Shizuku el nombre de clase del servicio, especificar el sufijo del proceso, si es depurable o no y qué versión es.
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)
Iniciar, detener y vincular el servicio de usuario
Las acciones de inicio y vinculación y las acciones de detención y desvinculación están unificadas en Shizuku. No hay métodos de inicio y vinculación separados ni métodos de detención y desvinculación.
He aquí cómo empezar y unir el Servicio de Usuario.
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.
}
He aquí cómo detener y desatar el Servicio de Usuario.
Java:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}
Kotlin:
if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}
Invocar el servicio de usuario
Una vez iniciado el Servicio de Usuario, podrá empezar a utilizarlo. Simplemente compruebe si el binder
La variable no es nula y se puede hacer ping, y luego realiza la llamada al método.
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)
}
Conclusión
Si siguió todo eso, ahora debería tener una integración de Shizuku funcional. Sólo recuerde decirles a sus usuarios que instalen Shizuku y que verifiquen adecuadamente que Shizuku esté disponible antes de intentar usarlo.