r17b r16 r13 ndk64 ndk android android-ndk jni native unsatisfiedlinkerror

r16 - ¿Por qué algunos teléfonos con Android hacen que nuestra aplicación arroje java.lang.UnsatisfiedLinkError?



ndk r13 (4)

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

Descripción del problema:

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

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

Enfoque de la solución

Comenzamos a ejecutar algunos diagnósticos personalizados en todas nuestras instalaciones para verificar 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 están desempaquetadas correctamente y son más grandes que 0 byte. Además, estamos buscando en freeSpace para ver cuánto espacio de disco hay 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, el instalador podría tener problemas para desempaquetar el APK.

Escenario del mundo real

En cuanto a los diagnósticos, algunos de los dispositivos no tienen las 3 librerías en la carpeta /data/data/app_id/lib pero tienen mucho 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 . Además, System.loadLibrary() debería usar una ruta consistente en la instalación y cargar las librerías? ¿Cómo puedo saber dónde está buscando el sistema operativo las libs?

Pregunta

¿Alguien tiene problemas con la instalación de libs nativas? ¿Qué soluciones han sido exitosas? ¿Alguna experiencia con solo descargar libs nativos a través de internet cuando no existen y almacenarlos manualmente? ¿Qué podría causar el problema en primer lugar?

EDITAR

Ahora también tengo un usuario que se encuentra con este problema después de una actualización de la aplicación. La versión anterior funcionaba bien en su teléfono, y después de una actualización las bibliotecas nativas parecen haber desaparecido. Copiar las libs de forma manual parece causar problemas también. Está en Android 4.x con un teléfono no rooteado sin una ROM personalizada.

EDIT 2 - Solución

Después de 2 años de pasar tiempo con este problema. Se nos ocurrió una solución que funciona bien para nosotros ahora. Lo abrimos desde el origen: https://github.com/KeepSafe/ReLinker


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

¿Estás construyendo y distribuyendo tanto para "arm" como para "armv7a"? Mi mejor suposición es que solo construiste para una de las arquitecturas y estás probando por la otra.


Después de tener este problema yo mismo y hacer un poco de investigación (también conocido como Google), pude solucionar el problema al apuntar a un SDK más bajo.

Tenía compileSdkVersion establecido en 20 (que funcionó en 4.4)

y también

minSdkVersion 20 targetSdkVersion 20

Una vez que los cambié a 18 (estaba en condiciones de hacerlo sin mayores implicaciones) pude ejecutar la aplicación sin problemas en Lollipop.

Espero que ayude. Me volvía loco por días.


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

La actualización del APK parcial de Google Play va mal

Para ser honesto, no sabía acerca de esa característica. El sufijo del nombre del archivo APK "-2.apk" me hizo sospechar. Se menciona en el mensaje de error de esta pregunta aquí y también pude encontrar ese sufijo en el informe de fallas de ese cliente mío.

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

Por alguna razón, la función System.loadLibrary intenta buscar la biblioteca nativa desde la actualización parcial (donde no existe). Es a la vez un defecto en Android y en Google Play.

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

Parece que Jelly Bean puede tener fallas en términos de instalación y carga de la biblioteca nativa (ambos informes de fallos que entraron fueron dispositivos Jelly Bean).

Si este es realmente el caso, sospecho que algún cambio forzoso en el código de la biblioteca NDK puede solucionar el problema, como cambiar alguna variable ficticia no utilizada para cada lanzamiento. Sin embargo, eso debe hacerse de forma que el compilador conserve esa variable sin optimizarla.

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

Instalación APK incompleta

Desafortunadamente, eso está fuera de mi memoria y ya no puedo encontrar un enlace. 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.

Cuando se produce un error al copiar las bibliotecas nativas en el directorio de destino por cualquier motivo (el dispositivo se quedó sin espacio de almacenamiento, tal vez también se dañaron los permisos de escritura del directorio ...) el instalador aún devuelve "éxito", como si las bibliotecas nativas fueran solo "extensiones opcionales" Una aplicación.

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

Sin embargo, no puedo encontrar ningún ticket de error de Android ni el artículo original del blog y lo busqué un poco. Entonces 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 sobre el aviso de error alude a un "problema de concurrencia" con las bibliotecas nativas instaladas, consulte el ticket de error de Android # 9089 .

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

Si intenta cargar una biblioteca que solo está presente en "armeabi", obtendrá una UnsatisfiedLinkException. Por cierto, ese error se ha marcado como "funciona como se pretende".

Posible solución

En cualquier caso: encontré una respuesta interesante a una pregunta similar aquí en SO. Todo se reduce a empaquetar todas las bibliotecas nativas como recursos sin formato para su APK y copiar en la primera aplicación para iniciar las correctas para la arquitectura del procesador actual en el sistema de archivos (privado de la aplicación). Use System.load con las rutas de archivos completas para cargar estas bibliotecas.

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

En general, creo que este problema debería comunicarse correctamente a Google. Parece que tanto Jelly Bean como Google Play tienen fallas.

Por lo general, ayuda decirle al cliente con ese problema que reinstale la aplicación. Desafortunadamente, esta no es una buena solución si la pérdida de datos de la aplicación es una preocupación.


Tengo el mismo problema, y ​​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 generalmente ocurre justo después de la actualización de la aplicación. Esto es para una aplicación que recibe entre 200 y 500 usuarios nuevos por día.

Creo que se me ocurrió una solución más simple . Puedo averiguar dónde está la aplicación original de mi aplicación con esta simple llamada:

String apkFileName = context.getApplicationInfo().sourceDir;

esto arroja algo así 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 normal y es legible sin root. Por lo tanto, si capturo java.lang.UnsatisfiedLinkError, puedo extraer y copiar mi biblioteca nativa, desde el interior de la carpeta .apk (zip) lib / armeabi-v7a (o de la arquitectura en la que estoy), a cualquier directorio donde Puedo leer / escribir / ejecutar y cargarlo con System.load (full_path).

Editar: Parece que funciona

Actualizo el 1 de julio de 2014 desde que lancé una versión de mi producto con el código similar al listado a continuación, el 23 de junio de 2014, no tuve ningún error de enlace insatisfecho de mi biblioteca nativa.

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

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 mala actualización de la aplicación deja una versión anterior de la biblioteca nativa, o una copia dañada de alguna manera, que cargamos con System.loadLibrary ("MyNativeLibName"), no hay forma de descargarla. Al enterarnos de la existencia de esa biblioteca extinta diferida en la carpeta de lib nativa de la aplicación estándar, por ejemplo, llamando a uno de nuestros métodos nativos y descubriendo que no está allí (UnsatisfiedLinkError nuevamente), podríamos almacenar una preferencia para evitar llamar al System.loadLibrary estándar ( ) en conjunto y confiando en nuestro propio código de extracción y carga en los próximos inicios de la aplicación.

Para completar, aquí está la clase UnzipUtil, que copié y modifiqué de este artículo de CodeJava UnzipUtility :

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