Intereting Posts
Реализация приложения-приложения на Android Использование MultiDexApplication приводит к тому, что тест Robolectric для класса приложения прерывается Как увеличить растровое изображение с высоким разрешением? Как улучшить качество отладочных данных, когда мое приложение аварийно завершает работу? Проект с открытым исходным кодом для редактирования видео Android Google SignIn не работает в режиме отладки: GoogleSignInResult является ложным Установите будильник из моего приложения Почему runnable callbacks уничтожает активность автоматически? Android: метод WebView goBack () показывает пустую страницу Ускорение системы координат устройства в абсолютную систему координат Как я могу получить правильные DisplayMetrics из AppWidget в Android? Автоматическое масштабирование на Google Картах в java? (В зависимости от разрешения экрана Android) Как изменить изображения по умолчанию CheckBox Выберите элемент из AutoCompleteTextView в uiautomator Создать программные кнопки для Android с использованием XML-макета в качестве шаблона

Почему некоторые Android-телефоны заставляют наше приложение бросать java.lang.UnsatisfiedLinkError?

Мы испытываем java.lang.UnsatisfiedLinkError на некоторых телефонах Android, которые используют наше приложение на рынке.

Описание проблемы:

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

Сбой приложения в строках System.loadLibrary() с помощью 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

Решение

На всех наших установках мы запустили специальную диагностику, чтобы проверить, не распакована ли каждая /data/data/app_id/lib папке /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; } } } } 

На каждом тестовом телефоне у нас есть numberOfLibFiles == 3 и subFilesLarger0 == true . Мы хотели проверить, все ли библиотеки распакованы правильно и они больше, чем 0 байт. Кроме того, мы смотрим на freeSpace чтобы узнать, сколько места на диске доступно. freeSpace соответствует объему памяти, который вы можете найти в настройках -> Приложения в нижней части экрана. Мысль за этим подходом заключалась в том, что, когда на диске недостаточно свободного места, у установщика могут возникнуть проблемы с распаковкой APK.

Сценарий реального мира

Глядя на диагностику, некоторые из устройств там НЕ имеют всех 3 libs в папке /data/data/app_id/lib но имеют много свободного места. Мне интересно, почему сообщение об ошибке ищет /data/app-lib/app_id-2 . Все наши телефоны хранят свои библиотеки в /data/data/app_id/lib . Также System.loadLibrary() должен использовать согласованный путь для установки и загрузки libs? Как я могу узнать, где ОС ищет библиотеки?

Вопрос

Кто-нибудь испытывает проблемы с установкой родных libs? Какая работа прошла успешно? Какой-либо опыт загрузки загружаемых файлов через Интернет, если они не существуют и хранятся вручную? Что может вызвать проблему в первую очередь?

РЕДАКТИРОВАТЬ

У меня теперь также есть пользователь, который сталкивается с этой проблемой после обновления приложения. Предыдущая версия отлично работала на его телефоне, после обновления, по-видимому, отсутствуют собственные библиотеки. Копирование файлов вручную вручную также вызывает проблемы. Он находится на android 4.x с неуправляемым телефоном без специального ПЗУ.

EDIT 2 – Решение

После 2 лет тратить время на эту проблему. Мы придумали решение, которое теперь хорошо работает для нас. Мы открываем его: https://github.com/KeepSafe/ReLinker

Solutions Collecting From Web of "Почему некоторые Android-телефоны заставляют наше приложение бросать java.lang.UnsatisfiedLinkError?"

У меня такая же проблема, и UnsatisfiedLinkErrors поставляется на всех версиях Android – за последние 6 месяцев для приложения, которое в настоящее время имеет более 90000 активных установок, у меня было:

 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% 

И пользователи сообщают, что это обычно происходит сразу после обновления приложения. Это приложение, которое получает около 200 – 500 новых пользователей в день.

Думаю, я придумал более простой подход . Я могу узнать, где оригинальный apk моего приложения с помощью этого простого вызова:

  String apkFileName = context.getApplicationInfo().sourceDir; 

Это возвращает что-то вроде «/data/app/com.example.pkgname-3.apk», точное имя файла APK моего приложения. Этот файл является обычным ZIP-файлом, и он читается без использования root. Поэтому, если я поймаю java.lang.UnsatisfiedLinkError, я могу извлечь и скопировать мою собственную библиотеку из папки .apk (zip) lib / armeabi-v7a (или любой другой архитектуры, в которой я включен), в любой каталог, где Я могу читать / писать / выполнять и загружать его с помощью System.load (full_path).

Изменить: похоже, работает

Обновление 1 июля 2014 года с момента выпуска версии моего продукта с кодом, аналогичным приведенному ниже, 23 июня 2014 года, не имело ошибок, которые не удалось устранить из моей родной библиотеки.

Вот код, который я использовал:

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

К сожалению, если плохое обновление приложения оставляет старую версию родной библиотеки или как-то поврежденную копию, которую мы загрузили с помощью System.loadLibrary («MyNativeLibName»), нет возможности ее выгрузить. Узнав о такой оставшейся не существующей библиотеке, задерживающейся в обычной папке lib, например, вызывая один из наших собственных методов и выясняя, что ее нет (UnsatisfiedLinkError снова), мы могли бы сохранить предпочтение, чтобы избежать вызова стандартной System.loadLibrary ( ) В целом и опираясь на наш собственный код извлечения и загрузки при следующих запусках приложений.

Для полноты, вот класс UnzipUtil, который я скопировал и модифицировал из этой статьи 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

EDIT: Поскольку вчера я получил еще один отчет о сбоях в одном из моих приложений, я углубился в этот вопрос и нашел третье очень вероятное объяснение этой проблемы:

Частичное обновление APK от Google Play идет не так

Честно говоря, я не знал об этой функции. Суффикс имени файла APK «-2.apk» сделал меня подозрительным. Он упоминается в сообщении об аварии этого вопроса здесь, и я также могу найти этот суффикс в отчете о сбоях моего клиента.

Я считаю, что «-2.apk» намекает на частичное обновление, которое, вероятно, обеспечивает меньшую дельта для Android-устройств. Эта дельта, по-видимому, не содержит родные библиотеки, когда они не менялись со времени предыдущей версии.

По какой-либо причине функция System.loadLibrary пытается найти собственную библиотеку из частичного обновления (там, где она не существует). Это и недостаток в Android, и в Google Play.

Вот очень актуальный отчет об ошибках с интересным обсуждением подобных наблюдений: https://code.google.com/p/android/issues/detail?id=35962

Похоже, что Jelly Bean может быть испорчен с точки зрения установки и загрузки собственной библиотеки (оба отчета о сбоях, которые приходили, были устройствами Jelly Bean).

Если это действительно так, я подозреваю, что некоторые принудительные изменения в библиотечном коде NDK могут решить проблему, например, изменить некоторую неиспользованную фиктивную переменную для каждой версии. Однако это должно быть сделано таким образом, чтобы компилятор сохранял эту переменную без ее оптимизации.

EDIT (12/19/13): Идея изменить исходный код библиотеки для каждой сборки, к сожалению, не работает. Я попробовал это с одним из моих приложений и получил сообщение о сбое «неудовлетворенной ссылки» от клиента, который все равно обновил.

Неполная установка APK

Это, к сожалению, просто из моей памяти, и я больше не могу найти ссылку. В прошлом году я прочитал статью в блоге об этой неудовлетворенной проблеме ссылок. Автор сказал, что это ошибка в процедуре установки APK.

Когда копирование родных библиотек в их целевой каталог сбой по какой-либо причине (у устройства закончилось место для хранения, возможно, также испорчено разрешение на запись в каталог …), установщик по-прежнему возвращает «успех», как если бы родные библиотеки были просто «дополнительными расширениями» для приложение.

В этом случае единственным обходным решением будет переустановка APK, при этом убедитесь, что для приложения достаточно места для хранения.

Тем не менее, я больше не могу найти ни одного бита Android-бонуса, ни оригинальную статью в блоге, и я искал его совсем немного. Таким образом, это объяснение может быть в сферах мифов и легенд.

Каталог «armeabi-v7a» имеет приоритет над каталогом «armeabi»

В этом обсуждении с ошибкой намекает на «проблему параллелизма» с установленными родными библиотеками, см. Биг-код Android # 9089 .

Если есть каталог «armeabi-v7a», содержащий только одну родную библиотеку, весь каталог для этой архитектуры имеет приоритет над каталогом «armeabi».

Если вы попытаетесь загрузить библиотеку, которая присутствует только в «armeabi», вы получите UnsatisfiedLinkException. Кстати, эта ошибка отмечена как «работает по назначению».

Возможное обходное решение

В любом случае: я нашел интересный ответ на аналогичный вопрос здесь, на SO. Все сводится к упаковыванию всех родных библиотек в качестве исходных ресурсов для вашего APK, и копирование на первом приложении запускает правильные для текущей архитектуры процессора (приватную) файловую систему. Используйте System.load с полными файловыми путями для загрузки этих библиотек.

Однако это обходное решение имеет недостаток: поскольку родные библиотеки будут находиться в качестве ресурсов в APK, Google Play не сможет их найти и создать фильтры устройств для обязательных архитектур процессоров. Его можно было бы обойти, поместив «манекены» родных библиотек в папку lib для всех целевых архитектур.

В целом я считаю, что эта проблема должна быть надлежащим образом передана Google. Кажется, что оба Jelly Bean и Google Play испорчены.

Обычно это помогает сообщить клиенту, что проблема заключается в переустановке приложения. Это, к сожалению, не очень хорошее решение, если потеря данных приложения является проблемой.

Android начал упаковывать свои библиотеки в / data / app-lib // когда-то вокруг Ice Cream Sandwich или Jellybean, я не помню, какой.

Вы строите и распространяете как «руку», так и «armv7a»? Мое лучшее предположение, что вы строились только для одной из архитектур, и вы тестируете другую.

После того, как я сам это сделал и немного изучил (ака-роуглинг), я смог решить проблему, настроив более низкий SDK.

У меня был compileSdkVersion установлен на 20 (который работал в 4.4)

а также

MinSdkVersion 20 targetSdkVersion 20

Как только я изменил их на 18 (я был в состоянии сделать это без каких-либо больших последствий), я смог запустить приложение без проблем на Lollipop.

Надеюсь, что это поможет – это заставляло меня остывать в течение нескольких дней.