Android internal os zygoteinit java

Некоторые “подводные камни” разработки под Android

Недавно наша команда завершила разработку приложения под Android. В процессе разработки и затем поддержки мы столкнулись с некоторыми проблемами технического характера. Часть из них — это наши баги, которых мы могли бы избежать, другая часть — это совсем неочевидные особенности Android, которые либо плохо описаны в документации, либо не описаны вообще.

В этой статье я бы хотел рассмотреть несколько реальных багов, которые возникли у наших пользователей и рассказать о путях их решения.

Статья не претендует на подробный анализ потенциальных проблем, это просто рассказ из жизни одно реального Android приложения.

RTFM (http://en.wikipedia.org/wiki/RTFM)

Так как при разработке под Android надо иметь в виду, что ваше приложение может работать на огромном количестве различных устройств, то надо думать о проблемах их совместимости.

Вот например одна из ошибок, которая возникала у наших пользователей:

А причина — проста, согласно документации класс android.util.Patterns доступен начиная с версии API 8 (Android 2.2.x), а у пользователя была версия 2.1. Решили мы это конечно оберткой этого кода в try/catch .

Вот еще одна подобная проблема вызванная невнимательным чтением документации:

Все дело в том, что strict mode (http://developer.android.com/reference/android/os/StrictMode.html) был включен по умолчанию в Android начиная с версии 3.0. Это значит, что ваше приложение не может обращаться к сети напрямую из основного UI потока, так как это может занимать некоторое время и при этом основной поток блокируется и не отвечает на другие события. Мы старались избегать подобного поведения, но в одном из мест все-таки остался простой сетевой вызов. Решена эта проблема была тем, что мы вынесли этот код в другой поток.

Поворот устройства — что может быть проще и привычнее для пользователя?

Казалось бы, для мобильных устройств смена ориентации экрана — это вещь настолько часто используемая и привычная, что это должно отразиться и в API. Т.е. эта ситуация должна обрабатываться очень просто. Но нет. Тут много нюансов.

Допустим, что нам необходимо сделать приложение, которое загружает список чего-либо и выводит его на экран. Т.е. при старте Activity (в методе onCreate() ) мы запускаем поток (чтобы не блокировать UI поток), который будет загружать данные. Этот поток отрабатывает некоторое время, поэтому мы будет отображать процесс загрузки в ProgressDialog . Все просто и замечательно работает.

Но, после загрузки, пользователь повернул устройство и тут мы обнаруживаем, что снова появился ProgressDialog и мы опять загружаем наши данные. Но ведь ничего не изменилось? Просто человеку удобнее смотреть на этот список повернув устройство.

А все дело в том, что метод onCreate() вызывается не только при создании Activity , но и при повороте экрана! Но мы не хотим засталять пользователя снова ждать загрузки. Все что нам надо — это снова показать уже загруженные данные.

Если мы поищем в интернете, мы найдем много ссылок на описание этой проблемы и также огромное количество примеров как решить эту проблему. И что самое плохое, что большинство таких ссылок предлагает неверное решение. Вот пример этого — http://codesex.org/articles/33-android-rotate

Не делайте обработку поворота экрана через onConfigurationChanged() ! Это неверно! Официальная документация ясно утверждает что “Using this attribute should be avoided and used only as a last-resort.“ http://developer.android.com/guide/topics/manifest/activity-element.html#config

Читайте также:  Хороший пешеходный навигатор для андроид

А вот правильный подход описан здесь — http://developer.android.com/guide/topics/resources/runtime-changes.html И как показывает практика — он не прост в имплементации.

Идея в том, что перед разворотом экрана Android вызовет метод onRetainNonConfigurationInstance() вашего Activity . И вы можете вернуть данные (например список загруженных объектов) из этого метода, которые и будут сохранены между 2 вызовами метода onCreate() вашего Activity . Затем при вызове onCreate() вы можете вызвать getLastNonConfigurationInstance() который и вернет вам сохраненные данные. Т.е. при создании Activity вы вызываете getLastNonConfigurationInstance() и если он вам вернул данные — то эти данные уже были загружены и надо только отобразить их. Если же не вернул данные — то запускаете загрузку.

Но вот на практике ситуация выглядит так. У нас может быть 2 варианта когда пользователь поворачивает устройство. Первый вариант — когда идет загрузка данных (работает наш поток который загружает данные и при этом отображается ProgressDialog ) или данные уже загружены и мы сохранили их в списке для отображения. Получается, что в первом случае мы при повороте должны сохранить ссылку на работающий поток, а во втором случае ссылку на уже загруженный список. Мы так и делаем.

Но это усложняет наш код и не кажется мне простым и интуитивным. Более того, когда идет смена ориентации экрана и мы сохранили поток загрузки данных, мы потом вынуждены при onCreate() опять восстанавливать ProgressDialog ! А если добавить сюда, что в нашем приложении пользователь может загружать данные из разных мест и у нас не один поток загрузки данных, а несколько — то количество кода которое обслуживает простой поворот экрана становится просто огромным.

Честно сказать, я не понимаю почему это было сделано так сложно.

Чуть более подробнее о потоках или использование AsyncTask.

Давайте рассмотрим загрузку данных в другом потоке чуть более подробно, так как и при этом нас ждали неожиданные “сюрпризы”.

Немного теории: для облегчения создания и работы в потоке отличном от основного UI потока был сделан специальный класс — AsyncTask (http://developer.android.com/reference/android/os/AsyncTask.html)

Суть его в том, что в нем уже есть готовые методы onPreExecute() и onPostExecute(Result) , которые выполняются в основном UI потоке и которые служат для отображение чего-либо и есть метод doInBackground(Params. ) внутри которого и происходит основная работа и он запускается в отдельном потоке автоматически. Вот примерный код как это выглядит:

Все просто и красиво.

Но теперь — немного практики. Вот Вам ошибка от реального пользователя:

А эта ошибка означает, что когда мы вызываем spinner.show() в методе onPreExecute() , то этот ProgressDialog созданный со ссылкой на MyActivity уже неактивен и его нет на экране! Ладно, я еще понимаю как такое может быть при вызове onPostExecute() . Т.е. например пока мы загружали данные пользователь нажал Back и наш ProgressDialog ушел с экрана. Но как такое может быть сразу при вызове загрузки, когда этот код стартует сразу при затуске Activity — это для меня неясно.

Но в любом случае, мы должны обрабатывать такие ситуации поэтому мы решили это обеткой методов spinner.show() и spinner.dismiss() в try/catch . Решение конечно не очень красивое, но в нашем случае — вполне функциональное.

Кстати, такой же код есть и например в Facebook SDK for Android, который был разработан другими опытными разрабочиками. И там тоже у нас были ситуации когда приложение вылетало при закрытии ProgressDialog . Нам пришлось добавить обработку и в их код. Так что проблема эта — не только в нас.

Читайте также:  Юизби кабель для андроид

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

И еще стоит вспомнить что метод doInBackground() выполняется в отдельном потоке и поэтому если при загрузке данных произошла ошибка, то нельзя выдать Alert прямо оттуда, так как это не UI поток. Надо сохранить ошибку, а затем после выхода из потока загрузки в методе onPostExecute(Void result) уже можно что-то показать.

Т.е. опять много вспомогательного кода и не все так просто…

AlarmManager

А еще есть моменты которые вообще не описаны в документации. Например, мы в своем приложении используем AlarmManager (http://developer.android.com/reference/android/app/AlarmManager.html) который помогает нам выдавать сообщения пользователю через некоторое время когда само наше приложение уже закрыто.

Этот AlarmManager — штука очень полезная, вот только проблема в том, что иногда он “теряет” созданные в нем нотификации! Мы потратили кучу времени чтобы понять как и почему это происходит, перерыли всю документацию и ничего не нашли. Совершенно случайно мы “набрели” на это обсуждение — http://stackoverflow.com/questions/9101818/how-to-create-a-persistent-alarmmanager.

Оказывается, что если приложение падает, или пользователь сам убивает приложение через task manager (что возможно и совершенно обычно), то ВСЕ нотификации для этого приложения в AlarmManager УДАЛЯЮТСЯ! Вот это — сюрприз! Особенно, мне понравился там один из комментариев: “Re Alarm canceled: Thanks for the clarification. I asked this on the Android team office hours g+ hangout and they even seemed confused about this behavior. Is it documented anywhere?

Так что теперь при старте приложения мы вынуждены пересоздавать настроенные нотификации, так как даже нет API в AlarmManager чтобы проверить есть ли такие нотификации или нет.

Бывает и такое.

Вот вам еще несколько ошибок которые мы зарегистрировали у наших пользователей. В своем приложении мы используем OAuth идентификацию для различных социальных сетей и поэтому вынуждены запускать штатный браузер в Android (через который и должен работать OAuth). При этом, он периодически “падает”

Заключение

Несмотря на все вешеописанное, мне понравилась заниматься разработкой под Android. В основном API вполне продуманный и удобный.

Вот только везде стоит использовать try/catch . Даже там, где это совсем неочевидно.

Источник

Русские Блоги

Zygote для Android

Что такое зигота

Zygote — это сервис-демон. SystemServer и все другие процессы виртуальной машины Dalvik разрабатываются Zygote.

Zygote — это самый важный процесс в Android и процесс Init. Процесс SystemServer — это три полюса, которые поддерживают мир Android. Процесс Zygote запускается как служба в процессе Init.

Zygote — это модель C / S. Процесс Zygote выступает в качестве сервера, а другие процессы выступают в качестве клиентов для отправки ему «инкубационных» запросов. Zygote «инкубирует» новый процесс после получения этого запроса. Как показано на рисунке, при щелчке по значку приложения в Launcher для запуска нового процесса приложения запрос достигает основной службы ActivityManagerService уровня платформы. Когда AMS получает этот запрос, он выполняет вызов, вызывая класс Process. «Зафиксируйте» запрос сокета дочернего процесса, и Zygote разветвляет новый процесс сразу после прослушивания этого запроса.

Процесс запуска Zygote

Основной файл, соответствующий процессу Zygote, это app_main.cpp. Когда он запускается процессом Init, он входит в функцию main () основного файла app_main.cpp. Затем его основная функция начинает анализ запуска процесса Zygote:

Наконец, функция main () вызывает функцию start () среды выполнения. Runtime — это объект AppRuntime. Взгляните на класс AppRuntime:

Читайте также:  Тест камер для андроид

Класс AppRuntime наследует класс AndroidRuntime, поэтому функция start () среды выполнения, вызванная ранее, войдет в функцию start () AndroidRuntime. Взгляните на функцию ниже:

Функция start () AndroidRuntime в основном выполняет следующие три функции:
1. Вызовите функцию startVM (), чтобы запустить виртуальную машину.
2. Вызовите функцию startReg (), чтобы зарегистрировать метод JNI.
3. Вызовите функцию main () класса com.android.internal.os.ZygoteInit (если это дочерний процесс, вызовите функцию main () com.android.internal.os.RuntimeInit Это в основном завершает инициализацию. Основная функция процесса Zygote имеет следующие 5 задач)

Далее описывается конкретная работа трех функций одна за другой:

Запустите виртуальную машину

Запустите виртуальную машину, вызвав функцию startVM ():

Зарегистрировать метод JNI

В Android мир Java использует механизм JNI для вызова функций в собственном мире, а механизм JNI также широко используется в системе Android.В системе Android для регистрации используется следующая функция startReg ():

Функция startReg () дополнительно регистрируется путем вызова функции register_jni_procs (). Переданное значение — массив gRegJNI. Версия Android 6.0 имеет более 130 массивов, как показано ниже:

JNI, перечисленные выше, регистрируются с помощью функции register_jni_procs ():

Зарегистрируйте все функции JNI через цикл. , ,

ZygoteInit отвечает за инициализацию Zygote. Вот основной метод класса ZygoteInit

1) Зарегистрируйте порт сокета в zygote для прослушивания

2) предварительная загрузка системных ресурсов;

3) Запустите процесс SystemServer;

4) войти в цикл прослушивания и получения сообщений;

Взгляните на эти четыре метода по очереди:

1) метод registerZygoteSocket ()

Создайте локальный сокет, а затем дождитесь вызова runSelectLoop (), чтобы войти в цикл, ожидающий сокет для ожидания соединения;

2) предварительная загрузка системных ресурсов, метод preload ();

Сначала посмотрите на карту общей памяти процесса приложения Android

Из приведенного выше рисунка легко понять, что после того, как процесс Zygote предварительно загружает системные ресурсы, он затем штрихует другие процессы виртуальной машины, а затем разделяет память виртуальной машины и ресурсы уровня структуры, что значительно повышает скорость запуска и запуска приложений. Теперь посмотрим на метод preload ();

В основном предварительная загрузка различных системных ресурсов, в основном см. 2.1) предварительная загрузка системных классов; 2.2) предварительная загрузка системных ресурсов; 2.3) предварительная загрузка, так что библиотека

Прочитайте файлы в файле PRELOADED_CLASSES, получите объект InputStream, преобразуйте его в BufferedReader, построчно прочитайте содержимое файла, передайте trim (), отфильтруйте пустые строки, а затем вызовите метод Class.forName () для загрузки информации о классе Java Вместо создания объекта;

2.2 preloadResources (); предварительная загрузка системных ресурсов

2.3 preloadSharedLibraries (); загрузка системных общих библиотек

3 startSystemServer () запускает процесс SystemServer

Было выполнено три основных действия: 3.1) Подготовить параметры для запуска процесса SystemServer. Вы можете видеть, что ProcessId и Group Id SystemServer оба равны 1000. Класс выполнения SystemServer — com.android.server.SystemServer; 3.2) Форкировать процесс SystemServer; 3.3) После того, как процесс SystemServer разветвлен, инициализируйте процесс SystemServer;
3.1 метод handleSystemServerProcess ()

4) метод runSelectLoop ()

Это делает две основные вещи:

4.1) Принять запрос на соединение, i = 0, это означает, что событие запроса на соединение наступило, вызвать acceptCommandPeer (), чтобы установить соединение с клиентом через сокет, а затем присоединиться к массиву прослушивания, ожидая прибытия команды на этом сокете;

4.2) Принять сообщение; i> 0 означает, что у уже подключенного сокета есть данные. После вызова метода runOnce () класса ZygoteConnection соединение с клиентом будет отключено и удалено из прослушивающего массива;

Источник

Оцените статью