Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


¿Por qué algunos teléfonos Android hacen que nuestra aplicación lance un java.lang.UnsatisfiedLinkError?

Estamos experimentando un java.lang.UnsatisfiedLinkError en algunos de los teléfonos Android que están utilizando nuestra aplicación en el mercado.

Descripción del problema:

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

Cierra la aplicación en de las líneas System.loadLibrary() con un 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

Solución

Comenzamos a ejecutar algunos diagnósticos personalizados en todas nuestras instalaciones para comprobar si cada lib se desempaqueta en la carpeta /data/data/app_id/lib .

 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; } } } } 

En cada teléfono de prueba que tenemos numberOfLibFiles == 3 y subFilesLarger0 == true . Queríamos probar si todas las libs se descomprimieron correctamente y son más grandes que 0 byte. Además, estamos buscando en freeSpace para ver cuánto espacio en disco está disponible. freeSpace coincide con la cantidad de memoria que puede encontrar en Configuración -> Aplicaciones en la parte inferior de la pantalla. El pensamiento detrás de este enfoque fue que cuando no hay suficiente espacio en el disco disponible que el instalador podría tener problemas para desempaquetar el APK.

Escenario del mundo real

En cuanto a los diagnósticos, algunos de los dispositivos que hay no tienen todas las 3 libs en la carpeta /data/data/app_id/lib pero tienen un montón de espacio libre. Me pregunto por qué el mensaje de error está buscando /data/app-lib/app_id-2 . Todos nuestros teléfonos almacenan sus libs en /data/data/app_id/lib . También el System.loadLibrary() debe utilizar una ruta coherente a través de la instalación y la carga de las librerías? ¿Cómo puedo saber dónde está buscando el OS las librerías?

Pregunta

¿Alguien que tenga problemas con la instalación de libs nativas? ¿Qué trabajo ha sido exitoso? ¿Alguna experiencia con la descarga de bibliotecas nativas a través de Internet cuando no existen y almacenándolas manualmente? ¿Qué podría causar el problema en primer lugar?

EDITAR

Ahora también tengo un usuario que se ejecuta en este problema después de una actualización de la aplicación. La versión anterior funcionó bien en su teléfono, después de una actualización de las libs nativas parecen faltar. Copiar las libras manualmente parece causar problemas también. Está en android 4.x con un teléfono sin raíz sin ROM personalizada.

EDIT 2 – Solución

Después de 2 años de pasar tiempo en este problema. Hemos encontrado una solución que funciona bien para nosotros ahora. Abrimos la fuente: https://github.com/KeepSafe/ReLinker

  • Native Debug Android Studio
  • Creación de un paquete de biblioteca de Android que incluye un paquete externo (nativo o Java)
  • ¿Cómo empaquete los archivos .so en mi archivo .aar?
  • UnsatisfiedLinkError Android
  • Phonegap plugin androide nativo
  • Cómo almacenar en caché los mapas de bits en la memoria nativa
  • Android Matrix multiplyMM en detalle
  • Android 4.3: Native crash en unkown.unknown
  • 4 Solutions collect form web for “¿Por qué algunos teléfonos Android hacen que nuestra aplicación lance un java.lang.UnsatisfiedLinkError?”

    Tengo el mismo problema, y ​​el UnsatisfiedLinkErrors viene en todas las versiones de Android – en los últimos 6 meses, para una aplicación que actualmente tiene más de 90000 instalaciones activas, tuve:

     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% 

    Y los usuarios informan que normalmente ocurre justo después de la actualización de la aplicación. Esto es para una aplicación que recibe alrededor de 200 a 500 nuevos usuarios por día.

    Creo que se me ocurrió un trabajo más simple . Puedo averiguar dónde está el apk original de mi aplicación con esta simple llamada:

      String apkFileName = context.getApplicationInfo().sourceDir; 

    Esto devuelve algo como "/data/app/com.example.pkgname-3.apk", el nombre de archivo exacto del archivo APK de mi aplicación. Este archivo es un archivo ZIP regular y es legible sin root. Por lo tanto, si captura el java.lang.UnsatisfiedLinkError, puedo extraer y copiar mi biblioteca nativa, desde el interior de la carpeta .apk (zip) lib / armeabi-v7a (o cualquier arquitectura en la que estoy), a cualquier directorio donde Puedo leer / escribir / ejecutar, y cargarlo con System.load (full_path).

    Edit: Parece que funciona

    Actualización el 1 de julio de 2014 ya que la liberación de una versión de mi producto con el código similar al listado a continuación, el 23 de junio de 2014, no tenía ningún error de enlace no satisfecho de mi biblioteca nativa.

    Aquí está el código que usé:

     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(); } } } } 

    Desafortunadamente, si una actualización incorrecta de la aplicación deja una versión antigua de la biblioteca nativa, o una copia dañada de algún modo, que cargamos con System.loadLibrary ("MyNativeLibName"), no hay manera de descargarla. Al enterarse de la biblioteca defunctada remanente en la carpeta lib estándar de la aplicación estándar, por ejemplo, llamando a uno de nuestros métodos nativos y descubriendo que no está allí (UnsatisfiedLinkError de nuevo), podríamos almacenar una preferencia para evitar llamar al estándar System.loadLibrary ) En conjunto y confiando en nuestro propio código de extracción y carga en las próximas aplicaciones.

    Para completar, aquí está UnzipUtil clase, que he copiado y modificado de este CodeJava UnzipUtility artículo :

     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

    EDIT: Desde que recibí otro informe de accidente de ayer para una de mis aplicaciones me enterré un poco más en el asunto y encontré una tercera explicación muy probable para ese problema:

    La actualización parcial de APK de Google Play no funciona correctamente

    Para ser honesto, yo no sabía sobre esa característica. El sufijo de nombre de archivo APK "-2.apk" me hizo sospechar. Se menciona en el mensaje de colisión de esta pregunta aquí y yo también podría encontrar ese sufijo en el informe de accidente de ese cliente mío.

    Creo que el "-2.apk" sugiere una actualización parcial que probablemente ofrece un delta más pequeño a los dispositivos Android. Ese delta aparentemente no contiene bibliotecas nativas cuando no cambian desde la versión anterior.

    Por cualquier razón la función System.loadLibrary intenta buscar la biblioteca nativa de la actualización parcial (donde no existe). Es una falla tanto en Android como en Google Play.

    Aquí hay un informe de errores muy relevante con una interesante discusión sobre observaciones similares: https://code.google.com/p/android/issues/detail?id=35962

    Parece que Jelly Bean puede estar defectuoso en términos de instalación y carga de la biblioteca nativa (ambos informes de fallos que llegaron eran dispositivos Jelly Bean).

    Si esto es realmente el caso, sospecho que algún cambio forzado en el código de la biblioteca NDK puede arreglar el problema, como cambiar alguna variable dummy no utilizada para cada versión. Sin embargo, eso debe hacerse de una manera que el compilador conserva esa variable sin optimizarla.

    EDIT (12/19/13): La idea de cambiar el código de la biblioteca nativa para cada compilación por desgracia no funciona. Lo probé con una de mis aplicaciones y recibí un informe de fallo de "error de enlace insatisfecho" de un cliente que actualizó de todos modos.

    Instalación incompleta de APK

    Eso es, lamentablemente, sólo de mi memoria y no puedo encontrar un enlace ya. El año pasado leí un artículo de blog sobre ese problema de enlace insatisfecho. El autor dijo que esto es un error en la rutina de instalación de APK.

    Al copiar bibliotecas nativas en su directorio de destino falla por cualquier razón (el dispositivo se quedó sin espacio de almacenamiento, tal vez también desordenado los permisos de escritura de directorio …) el instalador todavía devuelve "éxito", como si las bibliotecas nativas fueran sólo "extensiones opcionales" Una aplicación.

    En este caso, la única solución sería reinstalar el APK, asegurándose de que hay suficiente espacio de almacenamiento para la aplicación.

    Sin embargo, no puedo encontrar ningún boleto de fallo de Android ni el artículo original del blog más y lo busqué por un buen rato. Así que esta explicación puede estar en los reinos de mitos y leyendas.

    El directorio "armeabi-v7a" tiene prioridad sobre el directorio "armeabi"

    Esta discusión de boletos de insectos sugiere un "problema de concurrencia" con las bibliotecas nativas instaladas, vea el boleto de fallo de Android # 9089 .

    Si existe un directorio "armeabi-v7a" con una sola biblioteca nativa, todo el directorio para esa arquitectura tiene prioridad sobre el directorio "armeabi".

    Si intenta cargar una biblioteca que sólo está presente en "armeabi" obtendrá una UnsatisfiedLinkException. Ese error ha sido marcado como "funciona como se pretende" por cierto.

    Posible solución

    En cualquier caso: He encontrado una respuesta interesante a una pregunta similar aquí en SO. Todo se reduce a empaquetar todas las bibliotecas nativas como recursos en bruto a su APK y copiar en la primera aplicación de iniciar los correctos para la arquitectura del procesador actual para el sistema de archivos (privado de la aplicación). Utilice System.load con las rutas de acceso completas del archivo para cargar estas bibliotecas.

    Sin embargo, esta solución tiene un defecto: ya que las bibliotecas nativas residirán como recursos en el APK Google Play no podrá encontrarlos y crear filtros de dispositivos para las arquitecturas de procesador obligatorias. Se podría trabajar alrededor de poner bibliotecas nativas "ficticias" en la carpeta lib para todas las arquitecturas de destino.

    En general, creo que este problema debe comunicarse adecuadamente a Google. Parece que tanto Jelly Bean como Google Play son defectuosos.

    Por lo general, ayuda a decirle al cliente con ese problema que vuelva a instalar la aplicación. Esta desafortunadamente no es una buena solución si la pérdida de datos de la aplicación es una preocupación.

    Android empezó a empaquetar sus bibliotecas en / data / app-lib // en algún momento alrededor de Ice Cream Sandwich o Jellybean, no puedo recordar cuál.

    ¿Está construyendo y distribuyendo tanto para "arm" como para "armv7a"? Mi mejor conjetura es que usted construyó solamente para una de las arquitecturas y usted está probando en el otro.

    Después de tener este problema yo mismo y haciendo un poco de investigación (aka googling), pude solucionar el problema apuntando a un SDK más bajo.

    Tuve compileSdkVersion conjunto a 20 (que trabajó en 4.4)

    y también

    MinSdkVersion 20 targetSdkVersion 20

    Una vez que los cambié a 18 (yo estaba en una posición para hacer eso sin mayores implicaciones) pude ejecutar la aplicación sin problemas en Lollipop.

    Espero que ayude – Me volvió loco durante días.

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.