42 votes

Pourquoi certains téléphones Android font-ils que notre application jette une erreur java.lang.UnsatisfiedLinkError?

Nous vivons à une java.lang.UnsatisfiedLinkError sur certains téléphones Android, qui sont à l'aide de notre application dans le marché.

Description du problème:

static
{
    System.loadLibrary("stlport_shared"); // C++ STL        
    System.loadLibrary("lib2"); 
    System.loadLibrary("lib3"); 
}

Bloque l'application sur de l' System.loadLibrary() lignes java.lang.UnsatisfiedLinkError. java.lang.UnsatisfiedLinkError: Couldn't load stlport_shared from loader dalvik.system.PathClassLoader[dexPath=/data/app/app_id-2.apk,libraryPath=/data/app-lib/app_id-2]: findLibrary returned null

Approche de la Solution

Nous avons commencé l'exécution de certains diagnostics personnalisés sur tous nos installe pour vérifier si tous les lib est décompressé dans l' /data/data/app_id/lib le dossier.

PackageManager m = context.getPackageManager();
String s = context.getPackageName();
PackageInfo p;
p = m.getPackageInfo(s, 0);
s = p.applicationInfo.dataDir;

File appDir = new File(s);
long freeSpace = appDir.getFreeSpace();

File[] appDirList = appDir.listFiles();
int numberOfLibFiles = 0;
boolean subFilesLarger0 = true;
for (int i = 0; i < appDirList.length; i++) {

    if(appDirList[i].getName().startsWith("lib")) {
        File[] subFile = appDirList[i].listFiles(FileFilters.FilterDirs);   
        numberOfLibFiles = subFile.length;
        for (int j = 0; j < subFile.length; j++) {
            if(subFile[j].length() <= 0) {
                subFilesLarger0 = false;
                break;
            }
        }
    }
}

Sur chaque téléphone de test que nous avons numberOfLibFiles == 3 et subFilesLarger0 == true. Nous avons voulu tester si toutes les libs installés correctement et qu'ils sont supérieurs à 0 octet. En outre, nous sommes à la recherche à l' freeSpace pour voir combien d'espace disque est disponible. freeSpace correspond à la quantité de mémoire que vous pouvez trouver dans Paramètres --> Applications au bas de l'écran. L'idée derrière cette approche est que lorsqu'il n'y a pas assez d'espace sur le disque disponible que le programme d'installation peut avoir des problèmes déballage de l'APK.

Scénario réel

En regardant les diagnostics, certains appareils ne PAS avoir toutes les 3 libs dans l' /data/data/app_id/lib le dossier mais ont beaucoup d'espace libre. Je me demandais pourquoi le message d'erreur est à la recherche d' /data/app-lib/app_id-2. Tous nos téléphones stocker leurs libs en /data/data/app_id/lib. Aussi l' System.loadLibrary() doit être constante chemin à travers l'installation et le chargement de la libs? Comment puis-je savoir d'où l'OS est à la recherche pour les libs?

Question

Toute personne éprouvant des problèmes avec l'installation natif libs? Quelles solutions ont été couronnés de succès? Une expérience avec juste de le télécharger native libs sur internet lorsqu'ils ne sont pas inexistants et de les stocker manuellement? Ce qui pourrait causer le problème en premier lieu?

MODIFIER

J'ai maintenant aussi un utilisateur qui exécute ce problème après une mise à jour d'application. La version précédente a bien fonctionné sur son téléphone, après une mise à jour de la native libs semblent manquants. La copie de la libs manuellement semblent causer des problèmes ainsi. Il est sur android 4.x avec un non enraciné de téléphone sans ROM personnalisé.

20voto

Nobu Games Points 2760

EDIT: Depuis que j'ai reçu un autre rapport de crash hier pour une de mes applications, j'ai creusé un peu plus profondément dans la question et trouvé une troisième explication la plus probable de ce problème:

Google Play Partielle APK mise à Jour se Passe Mal

Pour être honnête, je ne connaissais pas cette fonctionnalité. Le fichier APK de suffixe de nom de "-2.apk" m'a fait suspect. Il est mentionné dans le crash du message de cette question ici, et j'ai pu également constater que le suffixe dans le rapport de crash du client de la mine.

Je crois que le "-2.apk" fait allusion à une mise à jour partielle qui est probablement à l'offre d'un petit delta pour les appareils Android. Delta apparemment ne contient pas de bibliothèques natives quand ils n'ont pas changé depuis la version précédente.

Pour quelque raison que ce soit le Système.fonction loadLibrary essaie de rechercher la bibliothèque native de la mise à jour partielle (où il n'existe pas). C'est à la fois une faille dans Android et Google Play.

Voici un très pertinentes rapport de bogue avec une discussion intéressante au sujet des observations similaires: https://code.google.com/p/android/issues/detail?id=35962

Il ressemble à Jelly Bean peut être déficient en termes de la bibliothèque native de l'installation et de chargement (à la fois les rapports de plantage qui étaient Jelly Bean périphériques).

Si c'est vraiment le cas, je soupçonne que certains forcé le changement dans le NDK code de la bibliothèque ne peut résoudre le problème, tel que la modification de certains inutilisés variable indicatrice pour chaque version. Toutefois, cela devrait être fait d'une manière que le compilateur conserve cette variable sans l'optimisation de loin.

EDIT (12/19/13): L'idée de l'évolution de la bibliothèque native code pour chaque version ne fonctionne malheureusement pas. Je l'ai essayé avec une de mes applications, et a obtenu un "insatisfaits erreur de lien" rapport de crash d'un client qui a mis à jour de toute façon.

Incomplète APK installation

C'est malheureusement juste hors de ma mémoire et je ne peux pas trouver un lien de plus. L'année dernière j'ai lu un article de blog à ce sujet non satisfaites problème du lien. L'auteur dit que c'est un bug dans l'APK routine d'installation.

Lors de la copie de bibliothèques natives de leur répertoire cible échoue pour quelque raison que ce soit (appareil couru hors de l'espace de stockage, peut-être aussi foiré répertoire des autorisations d'écriture...), l'installateur renvoie toujours "succès", comme si bibliothèques natives étaient juste "extensions facultatives" à une application.

Dans ce cas, la seule solution serait de réinstaller le APK, tout en s'assurant il ya assez d'espace de stockage pour l'application.

Cependant, je ne trouve aucune Android bug billet ni de l'original article du blog de plus et je l'ai cherché pendant tout à fait un peu. Donc, cette explication peut être dans les domaines de mythes et de légendes.

"armeabi-v7a" répertoire prend la priorité sur "armeabi" répertoire

Ce bug billet discussion allusion à un "problème de concurrence" sont installées des bibliothèques natives, voir Android bug ticket #9089.

Si il y a un "armeabi-v7a" dossier présent avec une seule bibliothèque native, l'ensemble du répertoire pour que l'architecture prend la priorité sur le "armeabi" répertoire.

Si vous essayez de charger une bibliothèque qui est seulement présent dans "armeabi" vous aurez une UnsatisfiedLinkException. Ce bug a été signalé comme "fonctionne comme prévu" par le chemin.

Solution de contournement Possible

Dans les deux cas: j'ai trouvé une réponse intéressante à une question similaire ici sur DONC. Tout se résume à l'emballage de toutes les bibliothèques natives comme matières premières pour votre APK et les copier sur la première application de commencer les bonnes pour l'actuelle architecture de processeur à l'app (privé) du système de fichiers. L'Utilisation Du Système.charge avec le chemin complet des fichiers à charger ces bibliothèques.

Cependant, cette solution a un défaut: depuis les librairies natives de résider en tant que ressources dans l'APK de Google Play ne sera pas en mesure de les trouver et de créer des filtres de périphérique pour la déclaration obligatoire des architectures processeur plus. Il pourrait être contourné en mettant "factice" bibliothèques natives dans le dossier lib pour toutes les architectures cibles.

Dans l'ensemble, je crois que cette question devrait être communiquée de manière appropriée à Google. Il me semble que si les deux Jelly Bean et Google Play sont imparfaits.

Il est généralement utile de dire au client ce problème pour réinstaller l'application. Ce n'est malheureusement pas une bonne solution en cas d'application de la perte de données est un sujet de préoccupation.

14voto

gregko Points 1272

J'ai le même problème, et la UnsatisfiedLinkErrors vient sur toutes les versions d'Android sur les 6 derniers mois, pour une application qui a actuellement le plus de 90000 active installe, j'ai eu:

Android 4.2     36  57.1%
Android 4.1     11  17.5%
Android 4.3     8   12.7%
Android 2.3.x   6   9.5%
Android 4.4     1   1.6%
Android 4.0.x   1   1.6%

et les utilisateurs rapportent qu'il se produit généralement juste après l'application de mise à jour. C'est une application qui obtient autour de 200 - 500 les nouveaux utilisateurs par jour.

Je pense que je suis venu avec un simple travail. Je peux savoir où est l'original apk de mon appli avec cette simple appel:

    String apkFileName = context.getApplicationInfo().sourceDir;

cela renvoie à quelque chose comme "/data/app/com.exemple.pkgname-3.apk", le nom exact du fichier de mon application fichier APK. Ce fichier est régulièrement fichier ZIP et il est lisible sans racine. Donc, si je prends la java.lang.UnsatisfiedLinkError, je peux extraire et de copier ma bibliothèque native, à partir de l'intérieur de .apk (zip) lib/armeabi-v7a dossier (ou quel que soit l'architecture, je suis sur), dans le répertoire où je peux lire/écrire/exécuter, et de le charger avec le Système.charge(full_path).

Edit: Il semble que cela fonctionne

Mise à jour le 1er juillet 2014 depuis la sortie d'une version de mon produit avec le code similaire à la liste ci-dessous, le 23 juin 2014, n'ont aucun Insatisfaits des Erreurs de Lien de ma bibliothèque native.

Voici le code que j'ai utilisé:

public static void initNativeLib(Context context) {
    try {
        // Try loading our native lib, see if it works...
        System.loadLibrary("MyNativeLibName");
    } catch (UnsatisfiedLinkError er) {
        ApplicationInfo appInfo = context.getApplicationInfo();
        String libName = "libMyNativeLibName.so";
        String destPath = context.getFilesDir().toString();
        try {
            String soName = destPath + File.separator + libName;
            new File(soName).delete();
            UnzipUtil.extractFile(appInfo.sourceDir, "lib/" + Build.CPU_ABI + "/" + libName, destPath);
            System.load(soName);
        } catch (IOException e) {
            // extractFile to app files dir did not work. Not enough space? Try elsewhere...
            destPath = context.getExternalCacheDir().toString();
            // Note: location on external memory is not secure, everyone can read/write it...
            // However we extract from a "secure" place (our apk) and instantly load it,
            // on each start of the app, this should make it safer.
            String soName = destPath + File.separator + libName;
            new File(soName).delete(); // this copy could be old, or altered by an attack
            try {
                UnzipUtil.extractFile(appInfo.sourceDir, "lib/" + Build.CPU_ABI + "/" + libName, destPath);
                System.load(soName);
            } catch (IOException e2) {
                Log.e(TAG "Exception in InstallInfo.init(): " + e);
                e.printStackTrace();
            }
        }
    }
}

Malheureusement, si une mauvaise application de mise à jour des feuilles d'une ancienne version de la bibliothèque native, ou d'une copie en quelque sorte endommagé, que nous avons chargé avec le Système.loadLibrary("MyNativeLibName"), il n'y a aucun moyen de le décharger. Lors de la découverte de ces restes disparu de la bibliothèque en attente dans la norme app native dossier lib, par exemple, en appelant l'un de nos natif de méthodes et de savoir qu'il n'est pas là (UnsatisfiedLinkError encore), nous avons pu enregistrer une préférence pour éviter d'appeler le Système standard.loadLibrary() en tout et en s'appuyant sur nos propres extraction et de chargement de code lors de la prochaine application de startups.

Pour être complet, voici UnzipUtil classe, que j'ai copié et modifié à partir de cette CodeJava UnzipUtility article:

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class UnzipUtil {
    /**
     * Size of the buffer to read/write data
     */

    private static final int BUFFER_SIZE = 4096;
    /**
     * Extracts a zip file specified by the zipFilePath to a directory specified by
     * destDirectory (will be created if does not exists)
     * @param zipFilePath
     * @param destDirectory
     * @throws java.io.IOException
     */
    public static void unzip(String zipFilePath, String destDirectory) throws IOException {
        File destDir = new File(destDirectory);
        if (!destDir.exists()) {
            destDir.mkdir();
        }
        ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
        ZipEntry entry = zipIn.getNextEntry();
        // iterates over entries in the zip file
        while (entry != null) {
            String filePath = destDirectory + File.separator + entry.getName();
            if (!entry.isDirectory()) {
                // if the entry is a file, extracts it
                extractFile(zipIn, filePath);
            } else {
                // if the entry is a directory, make the directory
                File dir = new File(filePath);
                dir.mkdir();
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
        zipIn.close();
    }

    /**
     * Extracts a file from a zip to specified destination directory.
     * The path of the file inside the zip is discarded, the file is
     * copied directly to the destDirectory.
     * @param zipFilePath - path and file name of a zip file
     * @param inZipFilePath - path and file name inside the zip
     * @param destDirectory - directory to which the file from zip should be extracted, the path part is discarded.
     * @throws java.io.IOException
     */
    public static void extractFile(String zipFilePath, String inZipFilePath, String destDirectory) throws IOException  {
        ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
        ZipEntry entry = zipIn.getNextEntry();
        // iterates over entries in the zip file
        while (entry != null) {
            if (!entry.isDirectory() && inZipFilePath.equals(entry.getName())) {
                String filePath = entry.getName();
                int separatorIndex = filePath.lastIndexOf(File.separator);
                if (separatorIndex > -1)
                    filePath = filePath.substring(separatorIndex + 1, filePath.length());
                filePath = destDirectory + File.separator + filePath;
                extractFile(zipIn, filePath);
                break;
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
        zipIn.close();
    }

    /**
     * Extracts a zip entry (file entry)
     * @param zipIn
     * @param filePath
     * @throws java.io.IOException
     */
    private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[BUFFER_SIZE];
        int read = 0;
        while ((read = zipIn.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }
        bos.close();
    }
}

Greg

0voto

David Points 273

Android a commencé à empaqueter ses bibliothèques dans / data / app-lib //, parfois autour de Ice Cream Sandwich ou Jellybean, je ne me souviens plus lequel.

Construisez-vous et distribuez-vous à la fois pour "arm" et "armv7a"? Ma meilleure hypothèse est que vous n'avez construit que pour l'une des architectures et que vous testez sur l'autre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X