Monday, April 2, 2012

Секретные USSD коды Android

Внимание! Нижеприведенные ссылки работают только при запуске с телефона !


*#*#4636#*#* - Просмотр технической информации о телефоне, батарее, wifi, а также статистике использования.

*#*#225#*#* - Информация из календаря.

*#06# - IMEI телефона.

Thursday, February 9, 2012

Просмотр исходников Android в Eclipse

Всем известно что Android - это платформа с открытым кодом. Как скачать исходники из репозитория я уже писал в отдельной статье. Сегодня я хочу описать простой способ с помощью которого можно просматривать исходный код платформы в Eclipse IDE.

Зачем это нужно ? Во-превых, для того чтобы не открывать онлайн документацию в поисках описания классов и интрефейсов из SDK. Во-вторых, это позволит выполнять отладку по коду SDK и видеть что происходит внутри вызываемых методов.

Итак, если исходники ещё не подключены к среде разработки, то при попытке открыть описание любого класса из Android SDK (Open Declaration - F3)


вы увидите сообщение "Source not found" приблизительно вот такого содержания:

Самый простой способ подключить исходники - это установить плагин "Android Source" для Eclipse. Актуальную ссылку на апдейт сайт можно найти на Google Code страничке проекта. Обратите внимание, что вам нужен именно Android Sources плагин, поскольку там их несколько.

Теперь берём эту ссылку (сейчас она вот такая http://adt-addons.googlecode.com/svn/trunk/source/com.android.ide.eclipse.source.update/) и добавляем плагин. В Eclipse Indigo это можно сделать через Help > Install New Software.


После установки плагина мы с радостью видим исходный код Android SDK. На текущий момент поддерживаются все версии с 1.5 по 4.0.3. Пользуйтесь - это весьма удобно !

Friday, February 3, 2012

Установка Android 4 на Amazon Kindle Fire

Вчера приятель мне привёз Kindle Fire - бюджетный планшетник от Amazon. В штатах он стоит всего две сотни долларов. По умолчанию на устройстве установлена модифицированная сборка Android 2.3 и нет привычных мне Android Market, Gmail, Google Maps, Google Talk и других приложений от Google. Но, зато есть Amazon Store. На нём можно найти некоторые полезные приложения, но далеко не все. Например, там нет Skype. Также изменения коснулись стандартного интерфейса Android, который был переделан и теперь напоминает книжный шкаф.


Полюбовавшись прелестями нового планшета, я решил на него установить недавно появившуюся прошивку Android 4.0.3. Поискав на просторах интренета, я нашёл несколько подробных инструкций как это сделать. Вот основная статья, которая мне пригодилась. Всё достаточно подробно расписано, поэтому не буду повторяться. Буквально два слова. Установка выполняется с помощью утилиты Kindle Fire Utility v0.9.2, которая запускается на компе и скидывает на мобильное устройство приложение для перепрошивки. У текущей версии утилиты есть один недостаток - она не работает для обновления Kindle Fire 6.2.2.

Потратив некоторе время на изучение мат. части я выяснил причину. Она заключалась в том что не получалось зарутить (root) устройство. Для того чтобы это сделать утилита Kindle Fire использует приложение BurritoRoot. Видимо разработчики Kindle Fire Utility ещё не успели добавить последнюю версию этого приложения где была реализована поддержка Kindle Fire 6.2.2.
Поэтому, всё что мне потребовалось сделать - это установить вручную BurritoRoot3. Для этого нужно скачать последнюю версию .apk и выполнить несколько команд:

adb shell chmod 777 /data/local/tmp
adb install BurritoRoot3.apk
adb shell /data/local/tmp/BurritoRoot3.bin --root

После чего Kindle Fire Utility определит что устройство действительно зарутовано:

Остальные шаги установки я выполнил строго следуя инструкции и в течении 15 минут смог полюбоваться обновлённым интерфейсом Android 4.0.3 (CyanogenMod 9)


Люди пишут что эта прошивка ещё сыровата, однако особых проблем я пока не обнаружил. Может быть единственное - это не работает камера, но и то лишь потому у Kindle её вообще нет )

Friday, May 20, 2011

Импорт базы данных с сервера на Andoird

Эх, что-то давно я у себя в блоге ничего не писал...
Пора бы уже и поделиться чем-нибудь свеженьким.

В общем задача ( для меня свежая, для кого-то может и не очень ) :
а) есть сервер с базой данных
б) мобильное приложение, которое должно кэшировать часть серверной базы данных
в) доступ к данным возможен через веб сервис в виде csv / xml / json
Требуется реализовать импорт базы данных наиболее эфективным способом, поскольку данных достаточно много.

Итак, поехали ...

Первое с чего я решил начать, это выборка данных через веб сервис. В каком виде брать данные из веб сервиса ? Долго не думая над этим вопросо я решил пройти по протоптоной дорожке и вытаскивать данные в JSON. Почему ? Всё очень просто. Во-первых, Android SDK имеет встроенную поддержку JSON с помощью org.json библиотеки. Во-вторых, работать с JSON, как по мне, несколько удобнее чем с XML или CSV. И в третьих он компактнее чем XML, хотя и не настолько компактный как CSV.

В общем первая реализация не заставила себя долго ждать и получилось что-то вроде:
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(url.toString());
HttpResponse response = client.execute(request);

// Pull content stream from response
HttpEntity entity = response.getEntity();
InputStream inputStream = entity.getContent();

ByteArrayOutputStream content = new ByteArrayOutputStream();

// Read response into a buffered stream
int readBytes = 0;
byte[] buffer = new byte[BUFFER_SIZE];
while ((readBytes = inputStream.read(buffer)) != -1) {
  content.write(buffer, 0, readBytes);
}  

String plain = new String(content.toByteArray());
JSONArray data = new JSONArray(plain);
Отточеная до автоматизма тривиальность не предвещала никаких сложностей. Но увы, как это часто бывает всё не так просто как кажется на первый взгляд.

Пробный запуск и приложение вылетает с OutofMemoryError. Оказалось, что данных сервер выдаёт порядка 4,3Мб и при очередном копировании данных в памяти виртуальная машина выкидывает исключение. На каком именно копировании я не разбирался. Всего их получается три inputStream -> content, content -> plaint, plain -> data и в памяти в один момент могут держаться все четыре копии данных, что порядка 16Мб. Для меня всё же остаётся не совсем ясным почему возникает это исключение, поскольку на моём устройстве 576 Мб оперативки из которых 256 Мб обычно свободны. Ну да ладно. Зато это послужило хорошим поводом чтобы разобраться.

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

В итоге в приложение добавился цикл, а в url дополнительный параметр указывающий номер первой записи в наборе и размер страницы.

Попробовал - заработало. Ура !

Самое время заняться локальной базой данных и её наполнением. Вот примерный код:
for (int i = 0; i < response.length(); i++) {
  JSONObject item = response.getJSONObject(i).getJSONObject("item");
  Point point = Point.fromJson(item);
  ContentValues values = new ContentValues();
  values.put(KEY_POINT_ID, object.getId());
  values.put(KEY_POINT_NAME, object.getName());
  values.put(KEY_POINT_LAT, object.getLat());
  values.put(KEY_POINT_LON, object.getLon());
  values.put(KEY_POINT_TYPE, object.getType());
  values.put(KEY_POINT_UPDATE_TIME, object.getUpdateTime());
  db.insert(TABLE_POINT, null, values);
}
Запускаем - замечательно, всё работает как в сказке с первой попытки. Только вот как-то долго... Прошло минуты две, а обработаны меньше четверти данных... Вряд-ли у пользователей хватит терпения ждать 10 минут, пока загрузится приложение. Да и как-то не гуманно это что-ли, так что начинаем анализировать на что тратится так много времени. Для этого под Android есть специальная тулза traceview и Debug.startMethodTracing / Debug.stopMethodTracing в помощь. Важный момент - размер .trace - файла по умолчанию 8 Мб, что исчерпывается быстро. Поэтому я установил его побольше и ограничить импорт выборкой одной страницы в 582 записи. Оборачиваем интересующий нас код вот так:
Debug.startMethodTracing("myapp", 20 * 1024 * 1024);
// ... сдесь идёт код импорта данных ...
Debug.stopMethodTracing();
Снова запускаем и по завершению выполнения находим myapp.trace в корне /sdcard на телефоне. Теперь для анализа "сырых" данных из этого файла запускаем:
traceview <путь-к-папке>/myapp
Из таблички со статистикой видно что 77,5% времени "сжирает" операция вставки данных в таблицу. Затем 17,3% тратится на выборку данных с сервера из которых 14,4% от общего времени - это разборка JSON строк в объекты. Я решил начать оптимизировать с малого, а именно с разборки JSON. Первая мысль, которая мне пришла в голову - это использовать JSON Streaming. JSON Streaming - это аналог XML Pull парсера для JSON. Идея этого подхода заключается в том, что структура документа не хранится целиком в памяти, а вместо этого парсер сообщает о синтаксическом разборе каждого элемента в отдельности. Поскольку этот подход не поддерживается стандартной библиотекой org.json, я воспользовался альтернативой - Jackson JSON. Вот приблизительно как выглядел код после того как я его переписал:
do {
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(url.toString());
HttpResponse response = client.execute(request);

// Pull content stream from response
HttpEntity entity = response.getEntity();
InputStream inputStream = entity.getContent();

JsonFactory f = new JsonFactory();
JsonParser jp = f.createJsonParser(inputStream);
if (jp.nextToken() == JsonToken.VALUE_NULL) {
  break;
}

while (jp.nextToken() != JsonToken.END_ARRAY) {
  Point point = null;

  while (jp.nextToken() != JsonToken.END_OBJECT) {
    String fieldname = jp.getCurrentName();
    jp.nextToken(); // move to value, or
    // START_OBJECT/START_ARRAY
    if ("item".equals(fieldname)) { // contains an object
      point = pointFromJson(jp);
    } else {
      throw new IllegalStateException("Unrecognized field '" + fieldname + "'!");
    }
  }

  if (point != null) {
    db.getPointDao().create(point);
  }
}
jp.close();
А, вот как изменилась статистика:
После трёх замеров, получилось что разборка JSON ускорилась всего на 10-15%. Немного, но кроме этого данные не копируются в памяти несколько раз, поскольку пыборка происходит непосредственно из потока с http-ответом. Оптимизируем дальше. Почему JSON ? На самом деле в этой задаче нет особого смысла использовать JSON, поскольку данные "плоские" и не имеют никакой вложенности. Я решил ещё раз переписать вытягивание данных и на этот раз использовать CSV, поскольку он самый экономичный по размеру. А значит и по скорости приложение должно ощутимо выиграть, поскольку меньше данных будет скачиваться по сети. Итак, вместо 4,3Мб в CSV формате получилось 2,7 Мб что меншье на 37%. Код приводить не буду, он достаточно тривиальный. Скажу только что для работы с csv лучше всего использовать готовую библиотеку opencsv. Что же получилось теперь ? Прирост получился невелик - всего 5%-10%, но это потому что у меня слишком быстрый интернет. В реальных условиях работы через 3G импорт данных будет работать значительно быстрее после таких изменений. Ведь всё-таки почти на 40% меньше данных скачивается. Теперь, когда передача данных рабоатает оптимально, можно заняться оптимизацией вставки объектов в базу данных. Когда выполняется много однотипных операций вставки, то имеет смысл обернуть их в одну транзакцию. Таким образом будет во множество раз меньше обращений к файловой системе для физического сохранения данных, что сэкономит огромное количество времени. Это раз. И вторая, менее значительная оптимизация - использование откомпилированых запросов на вставку. Это два. Улучшенный код вставки данных:
// Prepare insert statement.
SQLiteStatement insert = db.compileStatement(INSERT_STATEMENT); 
db.beginTransaction();

try {
  while (scanner.hasNextLine()) {
    String line = scanner.nextLine();

    String[] columns = line.split(";");

    try {
      if (columns.length == 7) {
        insert.clearBindings();

        // Important ! Order of the fields in the statement
        // should be the same like in CSV input.
        for (int i = 0; i < 7; i++) {
          insert.bindString(i + 1, columns[i]);
        }

        insert.execute();
        result++;
      }
    } catch (Exception e) {
      Log.e("AppTrack", Log.getStackTraceString(e));
    }
  }

  db.setTransactionSuccessful();
} finally {
  db.endTransaction();
}

Итак, после всех-всех оптимизаций импорт данных занимает менее 1,5 мин вместо изначальных 10 мин. Кроме этого данные не копируются несколько раз в памяти, что существенно снижает нагрузку на сборщик мусора и виртуальную машину.

Вот такой небольой case study. Надеюсь кому-нибудь пригодится. Если что - пишите в коменты.

Monday, March 21, 2011

Нагрузочное тестирование веб сервиса с помощью soapUI

Есть такая полезная штука soapUI. Пользуюсь ей давно, но, в основном, только для того чтобы быстро проверить отдельные методы веб сервисов. Сегодня понадобилось провести нагрузочное тестирование веб сервиса авторизации, который я недавно написал.
Задача как бы тривиальная, но, вот самое сложное в такого рода задачах - это выбрать инструмент. Итак приступаем. После получаса общения с поисковой системой у меня насобирался списочек различного рода утилит. И первую, которую я решил попробовать – это младший брат soapUI, под названием loadUI. В общем, я эту штуку скачал, установил, запустил, немного подождал, увидел пользовательский интерфейс и понял что это не то что мне нужно. Интерфейс, не ужасный, очень даже красивый, и, наверное, даже слишком для такой утилиты, просто я его не понял. Половина экрана занимает раздел с устаревшими новостями и всё как-то уж очень медленно работает. Закрыв его подальше, я решил посмотреть нет ли в soapUI того что мне нужно, т.к. где-то краем глаза видел что эти две утилиты интегрируются. И мне повезло - оказывается есть ! И уже давно есть, просто раньше я не обращал внимания на то что когда импортируешь WSDL чудесной галочкой можно скачать чтобы soapUI сгенерировал набор тестов:


И следующим шагом указать настройки для генерации тестов. Для функциональных тестов мне подошёл один тест кейс в котором включены обращения к обоим методам моего веб сервиса.
Внизу самая полезная галочка, это генерация нагрузочного теста на основе функционального.


Вот как выглядит то что получилось в дереве проекта:


Теперь, когда шаблоны тестов созданы, можно приступить к эмуляции логики клиента.


В моём случае всё достаточно просто: клиент через определённые интервалы времени обращается к методу ApproveSession для подтверждения сессии и в конце работы принудительно закрывает свою сессию с помощью TerminateSession. Всё просто, но есть два нюанса:
1. Необходима поддержка HTTP сессии с помощью Cookies
2. Каждый клиент должен присылать свой уникальный ключ на сервер, который в идеале ещё и должен бы быть закриптован

Итак, оказалось, что первый пункт легко разрешается с помощью ещё одной волшебной галочки, а именно:


Разобравшись с первым вопросом я приступил ко второму. После получаса рысканья по просторам интернета, нашёл очень полезную статью, которая реально сэкономила мне немного времени, и теперь я с радостью потрачу его на блог :)
Итак, применительно к моему случаю, SOAP запрос на подтверждение сессии выглядит приблизительно следующим образом:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:ApproveSession>
         <tem:token>?</tem:token>
      </tem:ApproveSession>
   </soapenv:Body>
</soapenv:Envelope>

Чтобы обеспечить уникальнсть ключа, я буду использовать индекс потока в котором запускается клиент, выглядит это так:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:ApproveSession>
         <tem:token>token${=context.getProperty("ThreadIndex")+1}</tem:token>
      </tem:ApproveSession>
   </soapenv:Body>
</soapenv:Envelope>

Теперь, когда запросы готовы, можно добавить несколько повторов (к сожалению, не знаю как можно организовать цикл в SoapUI, если кто подскажет – буду признателен), задержки между ними и запустить нагрузочный тест:


Итак, в результате запуска теста, получен полезный отчёт. Каждый шаг был выполнен 501 раз (колонка cnt) на протяжении интервала тестирования в одну минуту. Этот интервал я установил перед запуском теста. Кроме того, среднее время отклика сервера в пределах 250 мс (колонка avg), при этом максимальное время отклика порядка 1 сек, а минимальное - 100 мс. Очень неплохо при параллельной работе 500 нагрузочных клиентов. Суммарный объём трафика для всех клиентов менее 1 мб, при этом не было ни одного отказа. В целом результат меня удовлетворил. К сожалению, не получилось сэмулировать большее количество клиентов. При попытке запустить 1000 проходит всего 50 первых запросов а дальше в лог возвращаются ошибки соединения и на короткое время пропадает доступ к интернету. Скорее всего исчерпываются свободные ресурсы на ноутбуке где я проводил тест. На сервер нареканий нет - в логах сервера всё чисто, а счётчики производительности показывают что все запросы были успешно обслужены. Но это уже детали.

Главное, что soapUI всё-таки полезная штука, и в который раз она меня выручает :)

Saturday, February 26, 2011

Я поэт

Есть у меня хорошая идея сделать онлайн бибиотеку поэзии. Вот моя небольшая презентация с Android Hackton, который проходил в Киеве в феврале.

Wednesday, February 23, 2011

Первое приложение на Android Market !

Скажу сразу что это было непросто, но в конце концов я это сделал! И теперь моё первое приложение можно увидеть по ссылке.
Надеюсь в ближайшее поделиться как это было :)