Hay muchas razones por las que los permisos que normalmente se otorgan a tu aplicación podrían no ser suficientes. Quizás seas como yo y disfrutes creando aplicaciones poco convencionales que abusan de la API de Android. Algunas de las API que uso están bloqueadas detrás de permisos especiales. A veces, solo el usuario de shell (ADB) o el sistema pueden acceder a ellas. Sin embargo, hay una solución: Shizuku.

Shizuku te permite llamar a las API del sistema casi directamente y completamente en Java o Kotlin. Esta guía te mostrará cómo implementar y usar Shizuku.

¿Qué es Shizuku?

Antes de empezar a usar Shizuku, puede que sea útil saber qué es exactamente. Si estás familiarizado conMagisk, Shizuku es similar. Pero en lugar de gestionar el acceso root, 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, la versión de Android y la 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 versiones posteriores) o a través del acceso raíz. Las aplicaciones que implementan Shizuku pueden solicitar permiso para usar ese proceso para realizar operaciones elevadas.

ShizukuDesarrollador:Xingchen y Rikka
Precio: Gratis
4.4
Descargar

¿Por qué Shizuku?

Si bien el acceso al sistema a nivel de shell no te permite hacer tanto comoroot, te brinda más acceso que una aplicación normal. Además, la forma en que funciona Shizuku te permite usar las API de Android casi como de costumbre. No tienes que depender de comandos de shell (aunque puedes hacerlo si lo deseas).

Si tu aplicación necesita permisos especiales que solo se pueden otorgar a través de ADB (o con root), Shizuku y Android 11 son una excelente combinación. Puedes usar Shizuku para otorgar permisos especiales en el dispositivo.

Incluso con dispositivos que no tengan Android 11, Shizuku puede resultar útil. La aplicación proporciona instrucciones y scripts a los usuarios para que tú no tengas que hacerlo.

Integración

Agregar Shizuku a tu aplicación no es lo más sencillo, pero tampoco es difícil. Lamentablemente, ladocumentación para desarrolladoresno está del todo completa, pero este artículo te ayudará. Aquí te mostramos cómo integrar Shizuku a tu aplicación.

Dependencias

El primer paso es agregar las dependencias de Shizuku. En el archivo 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. Abre AndroidManifest.xml y agrega lo siguiente dentro del bloque de la aplicación.

<provider
    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. Más adelante veremos cómo conceder ese permiso. Por ahora, agréguelo a AndroidManifest.xml dentro del bloque de manifiesto.

Ahora que se agregó todo eso, la integración básica está lista. Deje que Gradle sincronice el proyecto y continúe con el uso.


Uso

Comprobando disponibilidad

Antes de analizar cómo utilizar Shizuku, hablemos de asegurarnos de que esté realmente disponible para su uso.

Antes de verificar si se concede el permiso y antes de realizar llamadas API a través de Shizuku, puede asegurarse de que esas verificaciones y llamadas se realizarán correctamente con el siguiente método:

Shizuku.pingBinder()

Si Shizuku está instalado y en ejecución, devolverátrue. De lo contrario, devolverá false.

Concesión de permiso

Dado que Shizuku utiliza un permiso de tiempo de ejecución, se debe otorgar a la aplicación antes de poder hacer algo con el acceso al shell. También hay dos versiones de API en circulación, con diferentes formas de otorgarlo. Esta sección le mostrará cómo manejar ambas.

De cheques

Antes de solicitar el permiso, lo mejor es comprobar si ya lo tienes. Si es así, puedes continuar con lo que necesites. De lo contrario, tendrás que solicitarlo antes de continuar.

Para comprobar si tienes permiso para usar Shizuku, puedes usar lo siguiente. Este código supone que lo estás 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:

booleanisGranted;
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
    isGranted = checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED;
}else{
    isGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED;
}

Solicitando

Si necesitas solicitar permiso para usar Shizuku, aquí te explicamos cómo hacerlo.

La  variableSHIZUKU_CODEutilizada 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(new String[] { ShizukuProvider.PERMISSION }, SHIZUKU_CODE);
}else{
   Shizuku.requestPermissions(SHIZUKU_CODE);
}

Para escuchar el resultado, deberás anular el métodoonRequestPermissionsResult()de Activity . También deberás implementar Shizuku.OnRequestPermissionResultListener. El ejemplo que voy a mostrar supone que tu Activity implementa esa interfaz, pero puedes implementarla en una variable si lo deseas.

Kotlin:

class ExampleActivity:AppCompatActivity, Shizuku.OnRequestPermissionResultListener {
    ...

    overridevoidonRequestPermissionsResult(requestCode: Int,permissions:Array<String>,grantResults: IntArray) {
        permissions.forEachIndexed { index, permission ->
            if (permission == ShizukuProvider.PERMISSION) {
               onRequestPermissionResult(requestCode,grantResults[index])
            }
       }
    }

   overridevoid onRequestPermissionResult(requestCode: Int, grantResult: Int) {
        val isGranted = grantResult == PackageManager.PERMISSION_GRANTED
        //Do stuffbasedonthe result.

    }
}

Java:

public class ExampleActivity extends AppCompatActivity implements Shizuku.OnRequestPermissionResultListener {
    ...

   @Override
   public void onRequestPermissionsResult(intrequestCode, @NonNull String[] permissions, @NonNullint[] grantResults) {
       for(inti =0; i < permissions.length; i++) {
           Stringpermission = permissions[i];
           intresult = grantResults[i];

           if(permission.equals(ShizukuProvider.PERMISSION) {
                onRequestPermissionResult(requestCode, result);
            }
        }
    }

   @Override
   public void onRequestPermissionResult(intrequestCode,intgrantResult) {
       booleanisGranted = grantResult == PackageManager.PERMISSION_GRANTED;
        //Do stuffbasedonthe result.
    }
}

Uso de API

Ahora que Shizuku está configurado y se han otorgado los permisos, puedes comenzar a llamar a las API mediante Shizuku. El proceso aquí es un poco diferente al que estás acostumbrado. En lugar de llamargetSystemService()y enviar mensajes a algo comoWindowManager, tendrás que usar las API internas para esto (por ejemplo,IWindowManager).

Shizuku incluye una forma de eludir la lista negra de API ocultas de Android, por lo que no tendrás que preocuparte por ello cuando lo uses. Sin embargo, si tienes curiosidad por saber cómo eludir esa lista negra, consulta mi tutorial anterior.

Aquí hay un ejemplo rápido (usando reflexión) que le muestra cómo puede obtener una instanciaIPackageManagery usarla 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,newShizukuBinderWrapper(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 alasInterfacemétodo para la clase Stub. Obtenga las referencias a los métodos que desea de la propia clase. Luego, invoque laasInterfacereferencia del método que tiene, reemplazando"package"lo anterior con el servicio que necesite. 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.Consulta el repositorio de GitHub de anggrayudipara 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 usar con este método desde cualquier lugar de su aplicación, siempre que Shizuku se esté ejecutando y su aplicación tenga permiso.

Servicio al 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 Binder directa. En ese caso, puede usar la función de servicio de usuario en Shizuku.

Este método funciona de manera similar a un servicio normal en Android. Lo "inicias", te comunicas con él mediante un enlace de servicio y ejecutas tu lógica en la clase de servicio. La diferencia es que no estás usando el servicio de Android y todo lo que está dentro del servicio se ejecuta con permisos ADB.

Ahora bien, existen algunas limitaciones. El servicio de usuario se ejecuta en un proceso y un usuario completamente independientes, por lo que no puede interactuar con el resto de su aplicación excepto a través de sus propias devoluciones de llamadas AIDL y enlazadores. Dado que tampoco se ejecuta en un proceso de aplicación adecuado, es posible que algunas cosas, como la vinculación de servicios de Android, no funcionen correctamente.

Esto también requiere la versión 10 de Shizuku o posterior. Si bien la mayoría de las fuentes de la aplicación tienen actualmente la versión 11, debes incluir una verificación de versión, que se incluirá en el ejemplo.

Definición de AIDL

Para comenzar, deberá crear un nuevo archivo AIDL. Puede hacerlo en Android Studio haciendo clic derecho en cualquier elemento de la vista de archivos de Android, pasando 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 el funcionamiento de los AIDL, asegúrese de consultarla documentación de Google.

Elimine los métodos de plantilla de AIDL y luego agregue undestroy()método con el ID adecuado para Shizuku. Esto se llamará cuando Shizuku esté eliminando el servicio de usuario y se debe utilizar para limpiar cualquier referencia o lógica en curso que tenga.

Ejemplo AIDL:

package tk.zwander.shizukudemo;

interface IUserService {
   void destroy() =16777114;

   voidgrantPermission(StringpackageName,Stringpermission, int userId) =1;//You can specify your own method IDs in the AIDL. Check out the documentation for more details on this.
}

Implementando el AIDL

El siguiente paso es implementar realmente el AIDL.

Java:

public class UserService extends IUserService.Stub {
   //Make sure to include an empty constructor.
   public UserService() {
    }

   @Override
   public void destroy() {
       //Shizuku wants the service to be killed. Clean up and then exit.
        System.exit(0);
    }

   @Override
   public void grantPermission(String packageName, String permission,intuserId) {
       //No need to use ShizukuBinderWrapper.
       IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName,permission,userId);
    }
}

Kotlin:

class UserService:IUserService.Stub {
   //Include an empty constructor.
   constructor() {
    }

   override fundestroy() {
       //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 tiene instalado el SDKde API oculta de Android.

Configuración de la conexión de servicio

Ahora que el servicio de usuario está definido e implementado, es momento de configurarlo para su uso. Lo primero que debe hacer es definir una ServiceConnection con la que desea comunicarse (por ejemplo, desde la actividad principal de su aplicación).

Java:

privateIUserService binder =null;

private finalServiceConnection connection =newServiceConnection() {
   @Override
   public void onServiceConnected(ComponentName componentName, IBinder binder) {
       if(binder !=null&& binder.pingBinder()) {
            binder = IUserService.Stub.asInterface(binder);
        }
    }

   @Override
   public void onServiceDisconnected(ComponentName componentName) {
        binder =null;
    }
}

Kotlin:

private varbinder: IUserService? =null

privateval connection = object : ServiceConnection {
   override funonServiceConnected(componentName: ComponentName, binder: IBinder?) {
       if(binder !=null&& binder.pingBinder()) {
            binder = IUserService.Stub.asInterface(binder)
        }
    }

   override funonServiceDisconnected(componentName: ComponentName) {
        binder =null;
    }
}

Labindervariable es lo que usarás para comunicarte con el servicio de usuario desde tu aplicación. Para comprobar si está disponible para su uso, solo comprueba que no sea null y quepingBinder()devuelva true, tal como en el ejemplo de código anterior.

Creación de los argumentos del servicio de usuario

Antes de poder controlar el servicio de usuario, deberá definir algunos argumentos que Shizuku utilizará al iniciarlo y detenerlo. Estos incluyen cosas como indicarle a Shizuku el nombre de clase del servicio, especificar el sufijo del proceso, si se puede depurar o no y qué versión es.

Java:

private finalShizuku.UserServiceArgs serviceArgs =newShizuku.UserServiceArgs(
   new ComponentName(BuildConfig.APPLICATION_ID,UserService.class.getName()))
        .processNameSuffix("user_service")
       .debuggable(BuildConfig.DEBUG)
       .version(BuildConfig.VERSION_CODE);

Kotlin:

privateval 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 separados.

A continuación se explica cómoiniciar y vincularel 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.
}

A continuación se explica cómo detener y desvincularel servicio de usuario.

Java:

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

Kotlin:

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

Invocación del servicio de usuario

Una vez que se inicia el servicio de usuario, puede comenzar a usarlo. Simplemente verifique si labindervariable no es nula y se puede hacer ping, y luego realice 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 ha seguido todos estos pasos, ahora debería tener una integración de Shizuku en funcionamiento. Solo recuerde indicarles a sus usuarios que instalen Shizuku y que comprueben que esté disponible antes de intentar usarlo.