Решение проблем при разработке под Android
Unity не удаётся установить ваше приложение на ваше устройство
- Убедитесь, что ваш компьютер видит ваше устройство и может взаимодействовать с ним. Для деталей см. Публикация сборок.
- Проверьте консоль Unity на наличие сообщений об ошибках. Это часто помогает в определении проблемы.
Если у вас появляется ошибка “Unable to install APK, protocol failure” во время сборки, то это значит, что ваше устройство подключено через USB-порт с низким питанием (возможно порт на клавиатуре или ещё на какой-нибудь периферии). Если такое случается, то попробуйте подсоединить устройство в USB порт на самом компьютере.
Ваше приложение падает сразу после запуска.
- Убедитесь, что вы не пытаетесь использовать NativeActivity с устройствами, которые это не поддерживают.
- Попробуйте убрать все нативные плагины, что у вас есть.
- Попробуйте отключить stripping.
- Используйте adb logcat чтобы получить отчёт о крахе с вашего устройства.
Building DEX Failed
Это ошибка, которая выдаёт сообщение, вроде следующего:-
Building DEX Failed! G:\Unity\JavaPluginSample\Temp/StagingArea> java -Xmx1024M -Djava.ext.dirs="G:/AndroidSDK/android-sdk_r09-windows\platform-tools/lib/" -jar "G:/AndroidSDK/android-sdk_r09-windows\platform-tools/lib/dx.jar" --dex --verbose --output=bin/classes.dex bin/classes.jar plugins Error occurred during initialization of VM Could not reserve enough space for object heap Could not create the Java virtual machine.
Обычно это вызвано неверной версией Java на вашем компьютере. Обновление Java до последней версии обычно решает проблему.
Приложение падает через несколько секунд после начала проигрывания видео.
Убедитесь, что Settings->Developer Options->Don’t keep activities не включено на вашем телефоне. Проигрыватель видео — это отдельное приложение и поэтому обычное игровое приложение будет закрыто, если проигрыватель видео включён.
Моя игра закрывается, когда я жму кнопку сна
Измените тег activity в файле AndroidManifest.xml так , чтобы он содержал тег android:configChanges , сделайте это таким образом, как описано здесь.
Пример тега activity может выглядеть, например, вот так:-
Do not keep activities
«Не сохранять операции» — именно таким странным образом переведена фраза «Do not keep activities» в настройках Android. А описание «Удалять все операции сразу после их завершения пользователем» не добавляет ясности. Включается она в меню «Параметры разработчика» (Developer Options), находится в самом низу.
Работает эта настройка очень просто, когда она включена, все неактивные активити умирают. Т.е. после перехода из активити A в активити B, активити A уничтожается. Таким образом можно проверить насколько вписывается ваше приложение в activity lifecycle.
Не хочется вдаваться в подробности lifecycle, но, в двух словах, система убивает фоновую активити когда захочет. В реальности это происходит не так редко, так что не стоит этим пренебрегать. Например, вам кто-то позвонил. Во время вашего разговора система может убить активити приложения, которым вы только что пользовались.
Или, самый простой способ убить активити — повернуть экран (если не android:configChanges=«keyboardHidden|orientation|screenSize»). Но не все приложения поддерживают поворот экрана. Да и те, которые поддерживают, встречаются с ошибками lifecycle на этапе разбработки, так что у них как раз проблем и не должно быть. Второй простой способ — сменить язык устройства.
Ну и разумеется можно просто включить «Do not keep activities».
Я потратил несколько дней на исправление ошибок, после того как прошелся по своему приложению с «Do not keep activities». Теперь, думаю, стоит всегда включать эту настройку на время разработки. Главной моей проблемой было корректное подключение социальных сетей в приложении.
Почти не возникло проблем с Вконтакте, с их SDK можно написать все правильно, но все таки есть у них небольшая проблема. А именно, после авторизации через приложение, результат возвращается в onActivityResult. По документации, нужно вызвать
VKUIHelper.onActivityResult(requestCode, resultCode, data);
однако, в случае уничтожения активити нашего приложение, вылетит NPE, поэтому сначала нужно вызвать
VKUIHelper.onResume(this);
Не совсем очевидно, да и незадокументировано. Завел issue, на всякий.
Были проблемы с Facebook, потому как, по глупости своей, я решил использовать android-simple-facebook, а ее автор, как я понял, не особо парился по поводу android lifecycle. Пришлось полностью от него отказаться и переделывать все на официальный SDK Facebook и все проблемы ушли. Хотя, наверное, можно было и так все поправить одной строчкой, но пути назад уже не было.
И совсем не было проблем с Twitter, потому что все работает просто, как топор. У них просто нет своего SDK и авторизации через приложение. Приходится делать все по старинке, через WebView. Вообще у них самая ужасная авторизация из всех.
Ну да это так, лирическое отступление.
Есть проблемы с PayPal SDK, оно просто валится везде где только можно. Благо кто-то уже завел issue до меня и наверняка они в скором будущем это поправят.
Другие примеры
У меня не много приложений на телефоне, да и смотреть их все мне, если честно, лень, но я не мог пройти мимо недавно вышедшего приложения хабра. Начнем, конечно же, с него. Ну и добавлю ошибку Payoneer, которую нашел случайно:)
Хабрахабр
Я потыкался по разным экранам и уже было расстроился, неужели все написано правильно и один лишь я делаю столько ошибок. Но нет, я таки добрался до страницы About:
Caused by: java.lang.NullPointerException at ru.habrahabr.activity.about.AboutFragment.onAttach(AboutFragment.java:44)
Payoneer
Много времени не понадобилось. Экран логина, ушел в keepass за паролем, вернулся — гипс крэш:
Caused by: java.lang.NullPointerException at com.payoneer.android.ui.fragment.LoginFragment.initializeOnCreateData(LoginFragment.java:379) at com.payoneer.android.ui.fragment.LoginFragment.onCreate(LoginFragment.java:152)
Ещё есть некоторые приложения без крэшэй, но с неправильным поведением, например, не сохраняется открытый фрагмент, а вместо него, после пересоздания, снова показывается главный экран приложения.
P.S.
Используйте настройку «Do not keep activities», надеюсь для кого-то эта информация окажется новой и поможет избежать ошибок поведения и крэшэй.
Думаю тоже смог бы избежать кучи репортов в Google Play, если бы сразу знал об этой настройке.
Ах да, репорты отправил.
- android development
- android
- appgranula
Как отключить опцию «Do not keep activities» в смвртфоне на базе Андроид?
На смартфоне НТС выскакивает окно с такой надписью: The option «Do not keep activities» (also called «immediately destroy activities») is set ON in Android Developer Options. The option «Do not keep activities» is a developer option and should not be set under normal user-conditions. Not run with this option set ON.
Телефон ребёнка. Может он загрузил чего не то или накрутил не там. Я не нашёл где она отключается.
Голосование за лучший ответ
настройки — функции для разработчиков — сними все галочки — перезагрузи девайс
В большинстве случаев сериализация в Андроиде не нужна
TL;DR: В большинстве приложений имеет смысл принять явное осознанное архитектурное решение, что в случае смерти процесса приложение просто перезапускается с нуля, не пытаясь восстанавливать состояние. И в этом случае Serializable , Parcelable и прочие Bundle не нужны.
Если хотя бы одна активность приложения находится между onStart() и onStop() , то гарантируется, что активность, а следовательно, и процесс, в котором активность живёт, находятся в безопасности. В остальных случаях операционная система может в любой момент убить процесс приложения.
Мне не приходилось реализовывать прозрачную (то есть чтобы было незаметно для пользователя) обработку смерти процесса в реальном приложении. Но вроде бы это вполне реализуемо, набросал рабочий пример: https://github.com/mychka/resurrection.
Идея состоит в том, чтобы в каждой активности в onSaveInstanceState() сохранять всё состояние приложения, а в onCreate() , если процесс был убит, восстанавливать:
abstract class BaseActivity : AppCompatActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) ResurrectionApp.ensureState(savedInstanceState) >override fun onSaveInstanceState(outState: Bundle) < super.onSaveInstanceState(outState) ResurrectionApp.STATE.save(outState) >>
Для того, чтобы сэмулировать убийство процесса, можно свернуть приложение и использовать команду
adb shell am kill org.resurrection
Если решаем обрабатывать смерть процесса, то можно отметить следующие накладные расходы.
- Усложнение кода.
- Если не заботимся о смерти, то можно в любом месте воткнуть статическое поле, и хранить там состояние приложения, ни о чём не беспокоясь. Если заботимся, то нужно аккуратно следить за состоянием, чтобы не забыть что-нибудь сохранить. Но в чём-то это даже плюс: дисциплинирует, может положительно повлиять на архитектуру, помочь избежать утечки памяти.
- Всё состояние должно быть честно Serializable / Parcelable .
- Восстановление состояния — это не только десериализация, но и приведение к консистентному виду. Например, до смерти процесса был флажок loading == true и запущенный поток. Так как после смерти процесса поток умер, нужно либо этот поток перезапустить, либо сбросить loading в false . То же самое с открытыми TCP-соединениями, которые после смерти процесса закрываются.
- Нужно следить за кодом, который вызывается до Activity#onCreate() — например, за статикой и инициализацией полей — так как в этот момент глобальное состояние может быть ешё не восстановлено.
В целом, мне кажется, описанные выше проблемы вполне решаемы так, чтобы полностью оградить основной код. Нужно немного дисциплины, а современные инструменты позволят всё спрятать в базовые классы и аннотации, в крайнем случае помогут рефлексия, манипуляция байт-кодом и кодогенерация.
Отдельного исследования заслуживает вопрос о том, насколько вообще вероятно убийство процесса приложения в реальной жизни. Как вариант, чтобы снизить вероятность, можно запускать foreground сервис. Но не думаю, что это правильно.
По моему мнению (с которым многие в комментариях не согласились) прозрачно обработать смерть процесса технически возможно, но в большинстве приложений это нецелесообразно, и имеет смысл сознательно и явно принять решение не поддерживать прозрачную обработку смерти процесса.
Для интереса проверил несколько приложений. На сегодня (02.03.2020) прозрачно обрабатывают смерть процесса: Facebook, Twitter, WhatsApp, Chrome, Gmail, Yandex.Maps. Перезапускают приложение: Yandex.Navigator, YouTube, мобильный банкинг от Сбербанка и от Альфа-Банка, Skype.
Итак, допустим, мы решаем не заморачиваться с обработкой смерти процесса. Если процесс убивают, и пользователь возвращается в приложение, то нас устраивает перезапуск с нуля. В этом случае возникает трудность: Андроид пытается восстанавливать стек активностей, что может приводить к непредсказуемым последствиям.
В качестве иллюстрации создал https://github.com/mychka/life-from-scratch
Закомментируем код BaseActivity и запустим приложение. Открывается LoginActivity . Нажимаем кнопку «NEXT». Поверх открывается DashboardActivity . Сворачиваем приложение. Для эмуляции убийства процесса вызываем
adb shell am kill org.lifefromscratch
Возвращаемся в приложение. Приложение крашится, так как DashboardActivity обращается к полю LoginActivity#loginShownAt , которое в случае смерти процесса оказывается непроинициализированным.
Красивого и универсального решения проблемы я не знаю. Предлагаю в статическом блоке базового класса активностей делать проверку, не было ли перезапуска приложения. Если обнаруживаем перезапуск процесса, то отправляем интент на перезапуск приложения с нуля и самоубиваемся:
abstract class BaseActivity : AppCompatActivity() < companion object < init < if (!appStartedNormally) < APP.startActivity( APP.getPackageManager().getLaunchIntentForPackage( APP.getPackageName() ) ); System.exit(0) >> > >
Решение кривое. Но оно вроде бы достаточно надёжное, проверено годами в серьёзном интернет-банкинге.
Теперь пришла пора пожинать плоды. Коль скоро мы всегда остаёмся в рамках одного процесса, то и заморачиваться с сериализацией нет резона. Создаём класс
class BinderReference(val value: T?) : Binder()
И гоняем через Parcel любые объекты по ссылке. Например,
class MyNonSerializableData(val os: OutputStream) val parcel: Parcel = Parcel.obtain() val obj = MyNonSerializableData(ByteArrayOutputStream()) parcel.writeStrongBinder(BinderReference(obj)) parcel.setDataPosition(0) val obj2 = (parcel.readStrongBinder() as BinderReference).value assert(obj === obj2)
Темы использования android.os.Binder в качестве транспорта объектов я касался в статье https://habr.com/ru/post/274635/
Такой подход имеет большой потенциал. Можно красиво обернуть и использовать во многих местах. Например, для передачи произвольных аргументов во фрагменты. Добавим несколько утилит:
const val DEFAULT_BUNDLE_KEY = "com.example.DEFAULT_BUNDLE_KEY.cr5?Yq+&Jr@rnH5j" val Any?.bundle: Bundle? get() = if (this == null) null else Bundle().also < it.putBinder(DEFAULT_BUNDLE_KEY, BinderReference(this)) >inline fun Bundle?.value(): T = this?.getBinder(DEFAULT_BUNDLE_KEY)?.let < if (it is BinderReference) it.value as T else null > as T inline fun Fragment.defaultArg() = lazy(LazyThreadSafetyMode.NONE)
И наслаждаемся комфортом. Запуск фрагмента:
findNavController(R.id.nav_host_fragment).navigate( R.id.bbbFragment, MyNonSerializableData(ByteArrayOutputStream()).bundle )
Во фрагменте добавляем поле
val data: MyNonSerializableData by defaultArg()
Другой пример — androidx.lifecycle.ViewModel . Этот класс бесполезен чуть менее, чем полностью, так как не переживает destroy активности, а обрабатывает только configuration change, являясь обёрткой над https://developer.android.com/reference/androidx/activity/ComponentActivity.html#onRetainCustomNonConfigurationInstance()
Допустим, что одна активность приложения открывается поверх другой, или пользователь кратковременно переключается на другое приложение, и операционная система решает уничтожить активность. В этом случае ViewModel умирает.
Используя BinderReference , несложно сделать аналог androidx.lifecycle.ViewModel , основывающийся на обычном механизме сохранения состояния onSaveInstanceState(outState) / onCreate(savedInstanceState) . Такому view model не страшно уничтожение активности.
UPD: В комментариях kamer и Dimezis меня поправили, что активности уничтожались без удаления активитирекорда из бэкстека только в старых версиях Андроида, в районе версии 2.3 (API 9). Затем это убрали. Теперь активность может быть уничтожена либо насовсем (например, как результат вызова finish() ), либо в процессе configuration change. Комментарии по теме от разработчиков Андроида:
https://stackoverflow.com/questions/7536988/android-app-out-of-memory-issues-tried-everything-and-still-at-a-loss/7576275#7576275
https://stackoverflow.com/questions/11616575/android-not-killing-activities-from-stack-when-memory-is-low/11616829#11616829
Тикет на улучшение документации:
https://issuetracker.google.com/issues/36934944
Чтобы сказать наверняка, нужно погружаться в исходники Андроида. Раз есть опция «Don’t keep activities», значит есть и код в ядре Андроида, умеющий «нежно» уничтожать активности. С большой вероятностью можно придумать хитрый сценарий, когда этот код будет вызван, и, соответственно, androidx.lifecycle.ViewModel приведёт к крашу, в отличие от onSaveInstanceState() . Но в реальной жизни, видимо, всё же androidx.lifecycle.ViewModel можно смело использовать.