Charger JNI sur Android & les mises à jour partielles/en direct
Aujourd’hui, nos téléphones intelligents contiennent des applications qui remplissent le rôle de programmes entiers, destinés à des ordinateurs. Ces applications sont plus complexes et occupent davantage d’espace qu’auparavant. Au cours des derniers mois, l’augmentation de la compatibilité pour les plateformes mobiles 64 bits a eu pour conséquence d’alourdir encore davantage les applications basées sur le C/C++. Pour plusieurs développeurs, la taille des applications devient un véritable casse-tête. Certains clients d’importance n’ont pas d’accès Wi-Fi et doivent se tourner vers des connexions mobiles (avec plan de données) lentes et onéreuses.
Ainsi, la taille des applications demeure un défi de taille pour les développeurs qui utilisent le C/C++, mais Android fournit les outils nécessaires à la réduction de la taille des apps, en plus d’offrir d’intéressantes solutions en matière de mises à jour.
La solution : charger les applications C/C++ à partir d’une bibliothèque JNI à leur démarrage.
Charger JNI sur Android
Le système Android est compatible avec sept plateformes (armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64). Cela signifie que si votre librairie C/C++ a une taille de 2 Mo, la bibliothèque embarquée de votre application représentera 14 Mo, desquels seulement 2 Mo seront réellement utiles pour votre appareil spécifique. Dans la mesure où votre bibliothèque reste modeste, vous n’y verrez aucun problème. C’est au moment d’utiliser une bibliothèque comme CrossWalk, dont le JNi pour une seule plateforme représente plus de 20 Mo, que les choses peuvent rapidement se corser.
Heureusement, rien ne vous force à embarquer votre bibliothèque JNI dans votre APK. Vous pouvez simplement procéder par téléchargement, en les chargeant au lancement.
D’abord, comment charge-t-on des bibliothèques JNI sur Android ?
Habituellement, les fichiers JNI sont chargés en ayant recours à un bloc de code statique dans le fichier Java, lequel contient vos déclarations de fonctions JNI, par exemple :
static { System.loadLibrary("my-jni-lib"); }
Ici, “my-jni-lib” est le nom de votre bibliothèque JNI. Le nom du fichier correspondant est “libmy-jni-lib.so”, dans le dossier correspondant à la plateforme visée.
Remarquez que votre appel “loadLibrary” peut être fait de manière statique ou non, indépendamment du fichier Java. Cependant, en faisant un appel statique à partir de la classe où est contenue votre déclaration de fonction JNI, vous garantissez que le chargement de votre bibliothèque JNI soit complété avant que ne soit déclenché un appel de fonction.
De plus, Java et Android permettent de charger des bibliothèques JNI en utilisant un chemin d’accès absolu. Pour ce faire, utilisez :
System.load("/path/to/libmy-jni-lib.so");
Contrairement à la méthode “loadLibrary”, vous devez fournir le chemin d’accès complet du fichier de la bibliothèque, incluant le nom complet du fichier. Vous devez également vous assurer que la bibliothèque que vous tentez de charger est compatible avec votre plateforme (compatible ABI).
Notons au passage que, tout comme “loadLibrary”, cet appel de fonction peut être statique ou non, à partir de n’importe quel fichier Java. Cependant, peu importe ce que vous faites, assurez-vous que la bibliothèque JNI est chargée avant de faire un appel de fonction.
Si nous ne voulons pas à embarquer tous les fichiers de la plateforme JNI dans l’APK, nous pouvons télécharger ce qui nous intéresse à partir d’Internet. Puis, il suffit de charger le tout au lancement de l’application.
Téléchargement et stockage de JNI pour Android
L’une des stratégies les plus simples pour télécharger un fichier KNI est d’avoir recours à une activité dédiée, qui sera l’activité du lanceur. Son rôle consiste à vérifier que la bibliothèque JNI est disponible sur l’appareil, de la télécharger si nécessaire, tout en informant l’utilisateur de ce qui se passe. Puis, cette activité démarrera la véritable application, qui chargera le fichier JNI et accomplira l’objet de votre application.
Télécharger la bibliothèque appropriée à votre plateforme
Télécharger le fichier JNI est très facile, peu importe le système que vous utilisez. Encore faut-il télécharger la bonne librairie pour l’architecture utilisée, ce qui suppose d’identifier votre plateforme ABI (Application Binary Interface).
Pour obtenir la chaîne de caractère qui correspond à l’ABI de votre appareil, vous pouvez utiliser cette constante :
android.os.Build.CPU_ABI;
Notez que la commande “CPU_ABI” est obsolète depuis API version 21. Pour les appareils avec API version 21 et plus, Google recommande :
android.os.Build.SUPPORTED_ABIS;
Contrairement à “CPU_ABI”, qui ne vous informe que sur un seul ABI, “SUPPORTED_ABIS” fournit une liste des ABIs pris en charge, ce qui vous laisse davantage de marge de manœuvre. Par exemple, un appareil avec “armeabi-v7a” comme “CPU_ABI peut généralement supporter des bibliothèques JNI pour “armeabi-v7a” et “armeabi”.
De toute façon, les chaînes de caractères fournies par “CPU_ABI” ou “SUPPORTED_ABIS” correspondent au nom du dossier où chaque JNI est copié lorsque vous programmez une application Android. Donc, à titre d’exemple, vous pouvez copier ces dossiers sur un serveur et utiliser l’information ABI pour trouver le dossier qui contient votre bibliothèque JNI.
Garder votre bibliothèque en lieux sûrs
Après avoir téléchargé votre fichier JNI, vous devez le garder à un endroit où il ne pourra pas être affecté par une autre app. Le meilleur endroit reste le dossier privé. Vous pouvez y créer un fichier intitulé “lib” et y copier votre fichier JNI. Au besoin, pour créer ce dossier et obtenir son chemin d’accès, utilisez :
File path = context.getDir("lib", Context.MODE_PRIVATE)
Ici, “context” vise le contexte actuel de votre application ou de votre activité. La méthode vous fournira un objet dans lequel vous trouverez le chemin de votre dossier.
Maintenant que vous connaissez le chemin d’accès de votre dossier, vous pouvez y copier votre bibliothèque et le charger en utilisant le code ci-dessus.
Notez que le fichier se situant dans votre dossier privé est à l’abri des modifications par d’autres applications, mais il est encore possible pour l’utilisateur de le supprimer, en utilisant “Clear Data” dans les propriétés, depuis les paramètres d’Android. Si vous voulez éviter que l’utilisateur efface votre bibliothèque JNI, Android fournit les outils appropriés.
Protéger votre bibliothèque JNI
Pour éviter que l’utilisateur n’efface votre bibliothèque JNI, vous devez vous charger de la fonction “Clear Data”. Vous devrez créer une Activité qui effacera tous les fichiers privés, à l’exception de votre bibliothèque. Faites-en mention, comme à l’habitude, dans votre fichier manifest. Dans le manifest, dans l’élément “application”, ajoutez l’attribut suivant :
android:manageSpaceActivity="com.example.myapp.MyClearAppDataActivity"
Ici, "com.example.myapp.MyClearAppDataActivity" est le chemin d’accès de votre classe d’activité.
Lorsque l’utilisateur utilisera le bouton “Clear Data”, votre activité se déclenchera et les fichiers privés s’effaceront en laissant votre bibliothèque JNI intacte.
Garantir l’intégrité de votre bibliothèque JNI
Puisque plusieurs utilisateurs root, leurs appareils et pour des questions de sécurité, il est recommandé de vous assurer que la version du fichier de votre bibliothèque JNI est bien celle que vous recherchez. Plusieurs solutions existent, selon le niveau de sécurité que vous désirez. Une solution simple consiste à valider le total de contrôle de votre fichier JNI dès le démarrage de l’application.
Mettre à jour le code C/C++, plutôt que l’APK
Puisque vous êtes maintenant capable de télécharger et d’exécuter une bibliothèque, vous êtes également capable de télécharger et d’utiliser une nouvelle version de cette bibliothèque, sans avoir à mettre l’entièreté de votre APK à jour.
Il s’agit d’un outil extrêmement puissant, puisqu’il vous sera possible de garder le contrôle sur vos mises à jour, voire même d’en forcer l’installation si nécessaire. Vous pourrez également utiliser une série de petites mises à jour, en envoyant seulement le manque à gagner entre la nouvelle bibliothèque JNI et la version déjà existante sur l’appareil de l’utilisateur.
Mettre à jour l’APK, plutôt que le code C/C++
D’un autre côté, un tel système vous permet de mettre à jour votre APK, sans avoir à toucher au JNI téléchargé. En effet, les données personnelles sont conservées lorsqu’une application est mise à jour. Pour les plus grosses bibliothèques du genre de CrossWalk, il vous sera alors possible de mettre à jour uniquement le code.
Note importante
Les notions traitées dans ce document sont d’une grande utilité et peuvent vous permettre de contrôler plus efficacement le processus d’installation et de mise à jour de vos applications.
Dans la mesure où vos applications sont installées et distribuées via votre propre plateforme, vous êtes totalement libre. Cependant, si vous distribuez vos applications à l’aide de Google Play, il est possible que Google ne permette pas de telles pratiques dans le futur. En effet, Google pourrait y voir un danger pour la sécurité de l’utilisateur, considérant que le code JNI, lequel n’est pas exécuté par l’appareil, n’est pas vérifié par le système anti-malware de Google Play.