02 ноября 2011

Безопасности в Android нет.

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

Что делает пользователь iOS для запуска нового приложения на устройстве? Покупает приложение, запускает. Что делает пользователь Android? Покупает неизвестно откуда взявшийся непроверенный кусок кода, смотрит на диалог "а вот тут надо какие-то разрешения", кликает OK, запускает. Всё, больше ничего не надо. Зашиваем в malware какой-нибудь universal androot и делаем с девайсом всё, что хотим.

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

01 октября 2011

Time machine on SMB (Windows) shares

Итак, благодаря некоторым событиям, я перешёл в категорию людей, которые делают бэкапы. Чем делать вопрос практически не вставал - Time machine. Она есть, она надёжна, самостоятельна и проста (а ещё "путешествие во времени" красиво выглядит). А вот куда... Каждый раз втыкать внешний USB винт надоело быстро, это был верный способ перестать бэкапиться вообще. Так что по сети, а ввиду отсутсвия Time Capsule - на SMB сетевой диск. И вот для этого придётся немного пошаманить.
Для начала берём панель настроек Secrets и разрешаем в ней показывать в Time Mаchine неподдерживаемые диски (Show unsupported network volumes in Time Machine). Монтируем диск. Качаем скрипт, и если файловая система не case sensitive (эй! 21 век!), то оригинал. Запускаем:

sh ./makeImage.sh 600 /Volumes/backup
где число - максимальный размер файла с бэкапом, а путь - его будущее расположение.
После этого выбираем диск в Time Machine и бэкапимся. Без проводов.

Есть ещё lifehack как использовать несколько мест для бэкапа, например, на работе и дома на разные диски. Time Machine хранит свои настройки в /Library/Preferences/com.apple.TimeMachine.plist. Настроив бэкап дома, копируем этот файл куда-нибудь с суффиксом .home, настроив на работе - с суффиксом .work. Правим простой скриптик:
#!/bin/sh
profile=`basename $0`
defaults write com.apple.TimeMachine AutoBackup -bool false
sudo cp /Users/phoenix/cfg/com.apple.TimeMachine.plist.$profile /Library/Preferences/com.apple.TimeMachine.plist
defaults write com.apple.TimeMachine AutoBackup -bool true

под свои нужды, делаем на него симлинки:
ln -s timeswitch.sh home
ln -s timeswitch.sh work
и настраиваем Marco Polo(кстати, а есть ли альтернативы? программа более не поддерживается) запускать их в нужных местах.
Всё, теперь ноут бэкапится сам, всегда и везде.

30 сентября 2011

Remotix video

Вышло видео про Remotix, мне очень понравилось.


26 сентября 2011

Remotix и новости россыпью


Ну вот и зарелизили Remotix (и для iOS тоже). В духе эппол уличшились стабилити, компатибилити и секурити, ну и на планшетках он теперь совсем как живой смотрится. И вообще он теперь "как надо".

Кроме того переехал на ssd, ssd оказался говно, так что у меня теперь 3 бэкапа и восстанавливаю я из них свой мак за пару часов практически не отвлекаясь. Time machine - отдельная басня в стиле эппол - просто работает. Но если тебе надо, чтобы работало не просто, а как-то затейливо... то это непросто.

Переехал на другой mbp (тоже с ssd). 13" это, конечно, мало. SSD - это просто a must, без него совершенно невозможно - мысль сто раз успеет прокрутиться по кругу и уйти в неведомые дали, пока что-то там запускается на традиционном HDD. Трекпады эппол - лучшие трекпады в мире, но мне пока мышка удобнее. Дизайн mbp немного неудачен - все нужные порты толпятся на одном торце, и рудиментарный dvd привод на другом.

Съездил в Питер, с собой были только Acer Iconia Tab A500 & Acer Liquid. Икония очень так даже ничего, со своими задачами (навигация и почитать в метро в основном) справилась. Liquid за год сдал и разряжается непростительно быстро и иногда внезапно.

Жду Deus Ex: Human Revolution под мак. Такими темпами как раз до середины доберусь к его выходу. Свет очей цифровой дистрибуции Steam в 21 веке спустя год со своего выпуска не научился работать на case sensitive файловой системе. Что за болячка такая?

Так и живём.

24 сентября 2011

Опять курощение iTunes

После какого-то очердного обновления iTunes отказался работать, и тебя мативировал, и меня мативировал, примерно вот так:

При этом запустить его предложенным способом невозможно - указанной настройки тупо нет.

Но подкастов-то надо! (Предпочитаю не использовать этого монстра ещё для чего-то, кроме как на ipod заливать подкасты.) И лайона ставить рано пока. Так что идём в сам iTunes и правим файл /Applications/iTunes.app/Contents/Info.plist, заменяя


 <key>LSMinimumSystemVersionByArchitecture</key>
<dict>
<key>i386</key>
<string>10.5.0</string>
<key>ppc</key>
<string>10.5.0</string>
<key>x86_64</key>
<string>10.7.0</string>
</dict>


на

 <key>LSMinimumSystemVersionByArchitecture</key>
<dict>
<key>i386</key>
<string>10.5.0</string>
<key>ppc</key>
<string>10.5.0</string>
<key>x86_64</key>
<string>10.6.7</string>
</dict>

ну или как-то так, смотря какая у вас там версия ирбиса стоит. Ну и первый запуск после колдунства должен быть непосредственно iTunes.app, а не через скрипт, который я уже предлагал, то есть
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
ему таки временно надо сделать.

27 мая 2011

Flexiglass

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

Вчера окончательно и бесповоротно стал патриотом и зарегистрировал свою копию программы для управления окнами для мака под названием Flexiglass, после того как туда добавилась критичная для меня возможность по хоткею перекинуть окно на второй монитор. Но не хоткеями едиными, есть там и улучшения для жестов, вроде так не хватавшей после Linux'овых DE возможности потаскать окно за любую его часть. И это с мышкой, трекпадом и графическими планшетами. И много чего ещё, вы лучше видео посмотрите. В общем, хороша, чертовка, умеет всё то, ради чего у меня стояли программы от конкурентов, и даже без учёта действующей сейчас скидки стоит дешевле. Среди первых в Top Paid категории Productivity и в общем Top Paid в Mac App Store.

Это то, чего Apple не доложила в оконную среду. Удобство.

PS.: любопытно, что финальным вариантом названия в ходе обсуждения стало моё предложение. Так что и мои 5 копеек там есть.

08 апреля 2011

Android: logout from google account

По работе приходится иметь дело со множеством девайсов на андроиде, меняться ими с коллегами, нужна возможность отвязать девайс от гугль аккаунта без factory reset (мало удовольствия вбивать пароль от офисного wi-fi, например, как и отдавать свой gmail кому-то).
Официально (через API) основной аккаунт удалить нельзя. На рутованом аппарате это достигается удалением файла /data/system/accounts.db и перезагрузкой.

01 апреля 2011

SMO Hate

Вот пишет человек блог, и мнит себя великим сеошником-смошником, упоминает какую-то неведомую тебе технологию, а ссылка ведёт на тег в его блоге, по которому находится только тот пост, в котором ты на эту ссылку и кликнул. Смысл? Чмошники.

08 марта 2011

Rails 3, virtual attr validations

Давненько собирался написать про валидацию виртуальных атрибутов в моделях Rails, а тут по случаю опробовал Rails 3, наступил на грабли в данном вопросе, так что повод появился.
Зачем могут понадобиться виртуальные атрибуты модели? Затем, чтобы опять же отделить внутренний способ хранения данных от их отображения, например в базе дата хранится как соответствующий тип Date, а на клиенте должна быть представлена в американском формате mm/dd/yy. Вводить её будут так же. Нужно проверять и только потом конвертировать в свой внутренний формат.
Немного о граблях. Были они вызваны тем, что в коде сеттера для такого виртуального атрибута я делал проверку valid? у модели. Модель пропускалась через валидации, некоторые поля модели к тому времени установлены не были (nil), соответственно валидаторы (в частности, один мой собственный) записывали в массив ошибок сообщение, что поле с нулевым значением неверно. Когда приходило время настоящей валидации, эта ошибка сохранялась, и, несмотря на то, что модель уже валидна, она считалась неверной.
А теперь как надо.

class SomeModel < ActiveRecord::Base
validates :v_field, :presence => true, :custom_format => true
before_save :set_attr

def set_attr
self.field = @v_field.convert_to_internal
end

def v_field
@v_field || field.convert_to_formatted
end

def v_field=(val)
@v_field = val
end
end

class CustomFormatValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
real_attr = attribute.to_s.gsub /v_/, ''
begin
if( value.length > 0 )
return
else
object.errors[real_attr.to_sym] << (options[:message] || "is not valid")
end
rescue Exception => e
Rails.logger.add(0, e.to_s )
object.errors[real_attr.to_sym] << (options[:message] || "is not valid")
end
end
end

Теперь всё идёт как надо - форматированное значение лежит в отдельном поле, валидация происходит 1 раз после того, как модель полностью установлена, после валидации - конвертация во внутренний формат.

18 декабря 2010

Git svn & Mac OS X

The world's most advanced operating system под названием Snow Leopard она же Mac OS 10.6, выпущенная в 2009 году, сегодня преподнесла мне сюрприз. Сюрприз был в виде стоического отказа git svn делать rebase, под предлогом того, что "The following untracked working tree files would be overwritten by checkout..." и далее путь к некоему файлу в репозитории, над которым работал коллега. Некоторое гугление и эксперименты показали, что коллега файл просто переименовал, обозвав в другом регистре, самая продвинутая операционная система макось в 21 веке имеет нечувствительную к регистру файловую систему по умолчанию, выполняя checkout на которой git видит странное - он этот файл и так, и этак, а он никак... Пришлось его немного сломать:
sed -i '' 's/checkout/checkout -f/g' /usr/local/Cellar/git/1.7.3.1/libexec/git-core/git-rebase
Аналогичный приём может применяться и к другим ситуациям с такими же симптомами. Просто нужно знать, где ударить и, главное, где забрать свои 100 рублей.

05 декабря 2010

Android tricks

Не так давно возникла потребность узнать, запущено ли приложение в прямом смысле этого слова, а не в том, как его понимает андроид. То есть был ли это запуск из лончера, иконки на рабочем столе, нотификации после установки или же просто приложение было показано при переходе к нему по стеку истории. Определяет это вот такой код, помещённый в onCreate() главной Activity:

protected boolean isLaunch()
{
Intent i = getIntent();
return i != null &&
((i.getCategories()!= null && i.getCategories().contains(Intent.CATEGORY_LAUNCHER)) || // from launcher
Intent.ACTION_MAIN.equalsIgnoreCase(i.getAction())) && // or first launch from notification panel
getLastNonConfigurationInstance() == null; // orientation change
}


Другой небольшой трюк - как дать возможность послать что-то в Gmail, SMS, Twitter и пр. Пользователи Андроида знают это меню "Отправить". А отправить вот как:
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "title");
sendIntent.putExtra(Intent.EXTRA_TEXT, "description");
Intent intent = Intent.createChooser(sendIntent,
getString(R.string.AndroidProgramItemActionShare));
startActivity(intent);

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

23 ноября 2010

Про Андроид 2

Стравив в прошлый раз яд, я, видимо, должен сейчас сочиться миррой и благовониями, посмотрим.

Для начала я снова отошлю вас почитать три неплохие статьи на тему.

Первое, за что андроид может заслужить фавора - это открытость. Под этим я понимаю много разных вещей. Например, крайне широкие возможности для интеграции как с системой, так и с другими приложениями. Вот я сделал фото камерой и могу его теперь отправить в Gmail, Picasa, Twitter и кучу других приложений. Если я объявлю в своём приложении, что оно умеет принимать изображения - оно тоже окажется в этом списке. И обратно - чтобы реализовать кнопку "Share" мне не нужно волочить с собой библиотеку для Facebook, Twitter и прочего Вконтакта. Достаточно сделать один вызов к OS - она выберет те приложения, которые могут обработать то или иное содержимое. И, заметьте, только те приложения, которые установлены, которые пользователь сам себе поставил и, вероятно, где надо уже авторизован. При этом границы приложения пересекает только та информация, которой это приложение готово поделиться, никакого нарушения изоляции, sandboxing там не менее строгий. На том же механизме, например, реализована система плагинов к популярному приложению Locale - просто и красиво. Или интеграция в поиск по телефону. Хотите, чтобы ваши задачи из вашей новой ToDo'шечки отображались в результатах поиска - вы можете реализовать поддержку этого. Или возможность всё заменить - приложение для контактов, телефона, почтовый клиент, плеер, камеру, рабочий стол... тысячи их (замен стандартным вариантам).

Другое проявление открытости - доступность исходников. Это отдельная тема священных войн, открыт ли андроид в смысле отрытого ПО, но факт остаётся собой - исходники можно получить и пощупать, можно даже почитать. Увы, делать это иногда даже необходимо (про документацию см. пред. пост). Однако не стоит забывать о том, что в случае конкретных прошивок исходники могут не соответствовать реальности (см. пред. пост).

Неплохим моментом можно считать и Java в качестве основного языка разработки под платформу. Конечно, это выливается в некоторые неудобства типа внезапного как понос GC (и я бы не сказал, что следить за памятью приходится меньше, чем с Objective-C), однако есть и преимущества. Для всего существует библиотека на Java - почти аксиома. Да и сама Java тут вполне нормальная, и SDK работать должен на всех основных платформах (RIM, тут вам нужно начать икать втрое интенсивнее). И да, декларировать пять раз свойство не приходится - объявил поле и автоматически сгенерировал методы доступа (это в тему того, что язык хоть и не обладает такими красивыми концепциями как передача сообщений или необязательные методы в интерфейсе/протоколе, но не выглядит таким динозавром. Подробнее - по одной из ссылок). Опять же мощная поддержка со стороны IDE: десятки рефакторингов, сотни инспекций (в реальном времени, а не после компиляции), тысячи плагинов, навигация, документация и автодополнение - XCode даже не разгибается (что они сделали с документацией XCode?! раньше хоть как-то можно было пользоваться). Удобнее отладчик, можно подключиться к уже запущенному приложению, это отчасти компенсирует медлительность приложения с подключённым отладчиком - быстро дошли до исследуемого места и подключили. Логи лучше - уровни (от Debug до WTF), фильтры, лог сохраняется даже тогда, когда устройство не подключено к IDE и можно посмотреть его постфактум (впрочем, коллеги начали прикручивать отправку исключений в Redmine из iOS приложений по моим стопам , частично устранив этот недостаток iOS).

Интерфейс. Действительно очень мощная поддержка различных разрешений и диагоналей, что с одной стороны необходимость при том многообразии устройств, на которых андроид должен запускаться, с другой стороны никто так здорово это не сделал. Таких гибких менеджеров размещения у Apple я не видел, остальные меры для поддержания различных экранов (iPhone, iPad, iPhone 4) либо провальные либо неудобные, а RIM уже должна помереть от икоты. Достаточно гибкие встроенные виджеты, которые к тому же при некоторой подготовке так легко видоизменять и комбинировать в более сложные компоненты (NB. об этом тоже стоит написать). В общем, пока вы играете на этом поле - возможности очень широки.

Многозадачность. Она, конечно же, не такая, как мы привыкли на ПК, OS будет ограничивать (16 Мб! 21 век!) и следить за вами и отрубит голову при первом же подозрении. Но если играть по правилам - вы можете работать на переднем плане, в фоне, получать системные события и реагировать на них. Не возникнет той абсурдной ситуации, когда я хочу открыть почту, когда синхронизируется RSS читалка, и вынужден ждать. А ещё это многозадачность пользователя - он может переключаться с задачи на задачу, вызывая именно те окна различных приложений, которые нужны ему для продолжения его рабочего процесса (workflow). Об этом и кнопке "Share" в частности рассказано в этом видео с Google IO 09 с 10 минуты.
Потерпите этого чувака 4 минуты, он это покажет лучше, чем я. Ну и виджеты, которые могут дать вам моментальный обзор необходимой информации без жонглирования приложениями. Всё это грамотно организовано и работает одновременно, не мешая друг другу.

Распространение софта свободно и дико, как нравы в племени мумбо-юмбо. Можно в Android маркет выложить (приложение на устройстве будет доступно через минуту! даже через Jabber я услышал, как упала челюсть шефа, до этого несколько лет воевавшего с AppStore), можно в сторонние магазины, можно на своём сайте, да хоть в Dropbox - знай только ссылку давай. Маркет, конечно, предпочтительней по некоторым причинам - он самый крупный, он предустановлен на большинстве аппаратов, он позволяет чётко разделять приложения по используемому железу и версии прошивки, так что вы не увидите игру для акселерометра на планшете без него. Однако даже если пользователь поставил софт почтовыми голубями, то вы можете мягко направить его в маркет за обновлениями, и приложение "Маркет" начнёт само следить за обновлениями. И каждый раз перед обновлением не нужно будет вводить пароль (умри, Appstore!)

Есть Flash (с 2.2). Кому-то надо. Нет, не тормозит (там, где у меня есть 2.2, но это весьма шустрый аппарат, так что не показательно).

Закончу, пожалуй, действительно радостной новостью - приложение, работа над которым дала почву для этих постов, стало Today hot #1 на appbrain.com, в чём огромная заслуга команды и мой небольшой вклад.

Хорошего начала рабочей недели!

20 ноября 2010

Про Андроид

Я давно копил яд и тщательно сцеживал в баночку. Но сегодня есть повод наконец-то высказаться. Поводом стало интерью с Максом Хауэллом (Max Howell) оригинал, одним из разработчиков TweetDeck (маководам-гикам он также может быть известен как автор Homebrew) и некоторых других полезных проектов.

Практически со всем, что пишет Макс, я могу согласиться. "Debug cycles on Android take half a minute at least." Да, чёрт побери, с запуском приложения всё плохо, очень плохо. Варианта, как водится, два: эмулятор (а не симулятор, как для iPhone) и реальное устройство. Первый вариант отпадает сразу, потому что эмулятор жестоко медленный (отдельный пункт для злословия - зачем делать эмулятор, который по идее должен досконально воспроизводить работу эмулируемой сущности, если он этого не делает? А тормозит так, будто целый кластер эмулирует. Вот симулятор iPhone - тоже часть функционала можно протестировать только на устройстве, но то, что можно на симуляторе - можно очень быстро). Какое там быстродействие, достаточное, чтобы UI плавно следовал за пальцем! Шевелится - и ладно. В мусорку, годится только на первый день, пока не завезут девайс. Да и тогда между сохранением исходников и запуском программы стоят неповоротливые компилятор, отправка бинарника на устройство, пакетный менеджер, который установит программу. Потом она запустится, и ведь наверняка у ней есть некий этап инициализации (да хотя бы логин в твиттер или что там у них). Полминуты? Да, что-то вроде того. Купите кресло и мешок кофе.

Второй безальтернативный вариант - реальное устройство. Девайс лучше брать мощный. А лучше пять, разной степени говнистости, но один мощный. У меня их 4, 2 из них (худший и лучший) - постоянно со мной, 2 лежат в офисе на случай, если команде срочно потребуется альтернативный девайс для тестов. У моего коллеги 2 (ну и он всегда может взять с моего стола те 2 которые я не ношу с собой). Вы подумали только что, что мы все совсем тут еб^W с ума сошли? Нет, отнюдь. Например для тестирования iOS приложений у меня ровно 1 девайс. Просто это единственный способ бороться с другой проблемой платформы Android - "фрагментацией". Про это я могу под пиво задвинуть немалую тираду, но постараюсь тут и кратко.

О, все так полюбили это слово с подачи Джобса, но никто нихрена не понимает, что это значит по отношению к Андроиду. И Макс тоже не догоняет, считая эту проблему надуманной. Вот он рассказывает про адаптацию интерфейса, тут же признаваясь, что с офигенной системой менеджеров размещения Андроида это вообще не проблема для таких приложений как твиттер-клиент (но в то же время я с ужасом жду момента, когда придётся переносить с iOS приложение не использующее стандартный UI, для которого эта система будет бессмысленна). Да не в этом дело, при чём тут разные экраны и разрешения? Ребята из Google тут всё сделали за вас (против ребят из Apple, у которых например функция загрузки изображения соответствующего разрешения вообще не работает, хотя описана в документации). А дело в том, что Андроид есть только один - в git репозитариях Google и на Nexus One. Всё остальное - это прошивка от HTC, прошивка от LG, прошивка от Motorola, взгромождённые на железо от HTC, LG и Motorola соотв. И в эту прошивку, что характерно, этот производитель залез руками (да, чтобы был хоть какой-то added value, который выделял бы его телефончик на фоне сотен других моделей) и, вероятно, непоправимо там наследил. Один из телефонов, к примеру, появился у нас вследствие того, что производитель переписал стандартный класс из Android SDK так, что при его использовании приложение гарантированно падало с исключением. Пришлось брать этот класс из исходников андроида и адаптировать, заменяя своим вариантом как сломанный вариант у того производителя так и стандартный вариант у всех других и лишаясь потенциальных будущих его обновлений. Другой телефон гарантированно уходил в ребут в одном месте приложения. Без какой-либо причины, которую хоть как-то можно было бы установить. После пришедшего обновления перестал. Зато вдруг стал разряжаться за полдня даже в режиме ожидания. Вот что такое фрагментация Андроида - никогда невозможно гарантировать, что у пользователя будет хоть что-то работать. Будет работать у меня на 4 аппаратах, ещё на 2 у коллеги, на 2 у шефа - не будет у 100500 пользователей на каком-нибудь китайском куске говна. Кто-то из них непременно придёт в маркет и насрёт в карму. Видели наверное - у приложения >250 000 загрузок, 5 звёзд и куча истеричных комментариев. Энди Рубин даёт нам определение открытости как возможность собрать андроид и поставить на свой телефон целиком из исходников, я смеюсь над ним - пусть проделает этот трюк с произвольным телефоном из магазина. Или это должен сделать разработчик, который лишь хочет продать свою программу?

Железо разное, очень разное. Казалось бы, везде какой-то ARM, но один аппарат тормозит как тот эмулятор, а другой, раза в 2 дешевле с резистивным говно-экраном на 16 цветов - летает. Парадокс! Или вы думаете, что Rovio от хорошей жизни объявили, что их злые птицы не поддерживаются на куче устройств? Посмотрите на список - это устройства дешевле $500 (в России, очень грубая прикидка на глаз). Такие вот кирпичи. В кирпичи превращаются устройства, как только вы их купите. Они морально устарели, когда добрались до России, и производитель не будет выпускать для них новую прошивку. Купить что-то стоящее на Андроид - сложно, обычно это означает накинуть $50-$150 сверх того, что задумывалось, тщательно выбрать производителя по отзывам (и вероятности обновления прошивок), и магазин, в который можно без проблем вернуть устройство (или поменять). Но это так, к слову, потребительская сторона вопроса меня не особо касается, просто хочется подчеркнуть, что придётся тщательно выбирать. В случае с Apple придётся выбирать между 16 & 32 Gb к примеру, и только. И я не знаю, что лучше или хуже, но париться по поводу 16 и 32 обычно придётся гораздо меньше, чем с тысячей аспектов андроид телефона.

Что-то я отклонился. Далее Макс говорит о жизненном цикле приложения. И вот тут происходит очередной разрыв шаблона, потому что приложение на Android работает совсем по-другому, нежели привычное десктоп или даже iOS приложение. Как вам, например, тот факт, что приложение нельзя закрыть? Это не нужно в той парадигме, которую предлагает Google. Приложение, будучи запущенным, вероятно понадобится пользователю в дальнейшем. Ну так и не надо его закрывать, пусть оно будет доступно как можно быстрее, когда вы захотите к нему вернуться! А андроид сам позаботится об использовании памяти, процессора и батареи. Но это же просто невозможно! и пользователь требует кнопку "Закрыть". А единственный способ приложению закрыться - это послать себе kill -9, да и это не гарантирует, что удивлённый таким ходом событий андроид не перезапустит его тотчас же. Другие способы уже требуют пользователя - удалить приложение или перезагрузить телефон. Вот тогда оно точно закроется. Знаете, я видел одно приложение, которое предлагало себя закрыть. Как вы думаете, что оно делало? Скрывало все свои 'окна' и засыпало, как любое другое добропорядочное андроид приложение.

Тут разрыв шаблона случается и у разработчиков (у некоторой части), потому что чтобы стать "good citizen" нужно хотя бы пару раз хорошенько прочитать документацию по теме (по этой теме она весьма подробна) и приложить голову. И если хороший разработчик "в итоге будет писать гораздо более надёжные приложения, чем раньше (особенно после отладки)", то остальные "говнякают код по-быстрому", чтобы завалить маркет бесплатными поделиями. И это тоже большая, просто огромная проблема - отсутствие не только контроля, но и каких бы то ни было гайдлайнов - как для разработчиков софта, так и для производителей аппаратов. Поэтому в маркете такое количество шлака, о чём так любят упоминать противники платформы, забывая при этом, сколько шлака в Apple AppStore, а на прилавках такое количество неудобных аппаратов, то не имеющих D-pad в каком-либо виде, то с самой часто используемой на андроиде кнопкой "назад", засунутой в самое труднодоступное место, то страдающих чем-то ещё из широкого арсенала недоработок юзабелистов. Отсюда возникает самый популярный класс программ на андроиде - тасккиллеры, и холиворы - нужны они или нет. Пока я увидел в такой свободе только один положительный момент - отсутствие задержки между публикацией и доступностью приложения, за что все яростно клянут AppStore (упущенные возможности, конкуренты догоняют и пр.).

Далее идёт речь о самом SDK. Документация местами хороша, местами просто отсутствует, средства странны, не образуют что-то цельное и удобное (даже если заменить Eclipse на Idea, хоть последняя мне лично гораздо приятнее) и также неполны (ну какой смысл в отслеживании выделений памяти в куче, если большая её часть выделяется вне её и не отображается стандартными средствами? Собственные велосипеды дают лишь примерную картину) что в купе с драконовскими ограничениями типа слабых устройств на рынке, непредсказуемостью GC и домокловым мечом OutOfMemory (16 Mb! 21 век на дворе! и из этого треть откусывает runtime) создаёт картину безысходности. Нет, бывают вещи гораздо, гораздо хуже (RIM, вам икается?), но тут действительно непаханое поле для улучшений.

Такую же безрадостную картину может составить себе читатель статьи Android vs. iPhone: the good, the bad and the ugly нашего соотечественника Олега Андреева, но с ней я не во всём согласен, большей частью она была вызвана плохим аппаратом и непониманием. Согласен с тем, что делать вещи на Java гораздо сложнее, и только упрощением сборки мусора (которая только добавляет проблем на самом-то деле) нельзя это оправдать. Java даёт средства для постройки чего угодно, но даже простые вещи делаются сложно, и без хорошего опыта это ещё сложнее. Поэтому, например, перед началом изучения андроида я настоятельно порекомендую Java Concurrency in Practice. Это достаточно подробная, толстая и дорогая книга про многопоточность в Java. Это почти обязательно для разработки приложения сложнее фоточки кота и мяукания (это шпилька в адрес App Inventor. Где он кстати?).

Вопреки всему вышеизлитому яду я, как и Макс, считаю, что у Андроида всё впереди. Но для этого придётся пойти на решительнейшие шаги. Отказаться от любой поддержки старых прошивок (например ниже 2.1), остальные объявить устаревающими (deprecated) и только после этого выпускать следующую версию, подняв аппаратные требования, чтобы убрать наконец те ограничения, в которых вынуждена запускаться андроид программа. Это оставит на обочине большую часть ныне существующих аппаратов, но иначе детские болезни погубят платформу. Необходима работа над документацией (к примеру, в документации Microsoft встречаются недостаточно полно освещённые моменты, в документации Apple их меньше, в документации Google же зияют дыры); над гайдлайнами, в первую очередь HIG; средствами разработки, мониторинга и отладки; возможно даже над какой-либо системой принятия приложения в магазин.

Хороших выходных. Ответные посты коллег, подкреплённые опытом, приветствуются.

18 ноября 2010

немного стиля Android виджетам!

Вчера коллега спросил, как бы поменять "эти ужасные зелёные ползунки у SeekBar'а в оболочке HTC Sense"? Сегодня я над этим немного поразмыслил, благо недавно как раз заглядывал в эту тему.
Начать, пожалуй, стоит с ознакомления со стилями Android'а. Увы, в редкой книге поднимается эта тема, так что можно почитать про стили в документации и обратить внимание на встроенные стили. Во встроенных есть, например, такой фрагмент:

<style name="Widget.SeekBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
<item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:thumb">@android:drawable/seek_thumb</item>
<item name="android:thumbOffset">8px</item>
<item name="android:focusable">true</item>
</style>

В нём и задаётся стиль для SeekBar'а. Значит, точно так же мы можем его переопределить. Как показывает вскрытие, @android:drawable/progress_horizontal есть фигура, описанная в xml (исходник, за ленью, выковыривать не стал), сделаем свою:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="1px" android:color="#FFF" />
<corners android:radius="9dip"/>

<gradient
android:angle="270"
android:endColor="#CCE2001A"
android:startColor="#CCA10013"
android:type="linear"/>
</shape>

Красный градиент в белой рамке.
Создадим стиль для виджета:
    <style name="MySeekBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@drawable/seekbar</item>
<item name="android:indeterminateDrawable">@drawable/seekbar</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:thumbOffset">8px</item>
<item name="android:focusable">true</item>
</style>

и применим его на живом экземпляре:
<SeekBar
style="@style/MySeekBar"
android:id="@+id/screenControlVolume"
android:layout_width="150dip"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:layout_marginBottom="10dip"/>

Итог мучений (внизу - стандартный, не HTC):

Ну и, надо полагать, очевидно, что можно расширить и углубить мысль и применять этот подход почти ко всему.
UPD:
Как говорил мой завкафедры, показывая пальцем на голову, "Вы никогда не сможете перестать работать". И уже улёгшись спать, я вспомнил, что полоска-то в моём SeekBar'е одинакового цвета что слева от указателя позиции, что справа. Непорядок, который я сегодня исправляю. Итак, всё-таки расковыряв исходник android-варианта фигуры этой полоски, переписал свою версию:
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<stroke android:width="1px" android:color="@color/widgetBorder" />
<corners android:radius="9dip"/>

<gradient
android:angle="270"
android:endColor="@color/widgetBorder"
android:startColor="@color/widgetBorder"
android:type="linear"/>
</shape>
</item>

<item android:id="@android:id/progress">
<clip>
<shape>
<stroke android:width="1px" android:color="@color/widgetBorder" />
<corners android:radius="9dip"/>

<gradient
android:angle="270"
android:endColor="@color/gradientWidgetEndColor"
android:startColor="@color/gradientWidgetStartColor"
android:type="linear"/>
</shape>
</clip>
</item>
</layer-list>

тут уже список слоёв с 2 (в оригинале 3, зачем третья можете подумать на досуге), в которых могут быть разные фигуры, в моём случае - всё те же скруглённые градиентные прямоугольники, верхний слой обрезан - это левая часть. Ну и вот как это выглядит в моём проекте:

14 ноября 2010

Ну и опять про браузеры. И андроид.

Просто для поднятия настроения, раз уж я тут картинки разбираю. Документация Google Android в Google Chrome и Opera (версии последниие). И не говорите мне после этого ничего, и не осуждайте выбор. На самом android'е, кстати, встроенный браузер тоже слажал немного, не показав нижнюю левую панель, Opera Mobile справилась (хотя и были неточности).

13 ноября 2010

Gradients in Android

Построение интерфейса Android'а через xml - это сплошные преимущества, куда не посмотри. Только привыкнуть и научиться надо (конечно, на это надо время, которого никогда нет, поэтому этот рабочий пост написан в выходной).

Вот пример - описав на xml радиальный градиент, используемый в качестве фона приложения, сократил размер выходного пакета почти в 3 раза за счёт того, что выкинул картинки с фоном. Осталось только с дизайнером сесть и цвета поправить.

Ну и чтоб два раза не вставать, вот так можно ескейпить код в HTML:
alias -g E='| ruby -rcgi -e "ARGF.each{|line| puts CGI.escapeHTML line}"'
это на zsh.

XML:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:centerX="0.5"
android:centerY="0.5"
android:endColor="#023F8F"
android:gradientRadius="300"
android:startColor="#1A96CA"
android:type="radial"
/>
</shape>


До и после:

24 октября 2010

Линковка в Mac OS X

Симптомы

ld: in lib_foo.a, archive has no table of contents
collect2: ld returned 1 exit status

Лечение
export COMMAND_MODE=unix2003

ещё можно почитать man compat и убедиться, что врут, негодяи:
if it is unset or set to something other than legacy or unix2003 it behaves as if it were set to unix2003

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

22 октября 2010

уходи, iTunes, уходи!

Чтобы угомонить вконец обнаглевшего уродливого толстяка iTunes, возомнившего себя единственным на свете плеером и намертво присосавшегося к аппаратной кнопке Play/Pause моего макбука после обновления оного до сноулео, достаточно сделать так:

sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes

а для редких случаев, когда iTunes всё-таки нужен, сделать в Automator приложение с простым шелл-скриптом:
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
/Applications/iTunes.app/Contents/MacOS/iTunes &
sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes
Это я после очередного апдейта вспомнил, записываю чтоб не забыть.
UPD:
После того, как в очередной раз пришлось курощать iTunes, скрипт перестал работать, то есть работал, но iTunes продолжала сетовать на отсутствие лайона. Рабочий вариант ниже:
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
open -a iTunes &
sleep 5
sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes

20 октября 2010

Yet another Vim blog post (YAVBP)

За последние пару недель как-то много вокруг меня стало инфоповодов на тему Vim. Вот например прекрасная статья о том, почему его надо использовать (очень толково, просто, есть про настройку с описанием).
Так что я усиленно постился^Wпилил конфиг, запрягал Vim работать в паре с Elipse для работы над Android проектом (всё на удивление просто - последний Elipse, последний MacVim или GVim и Eclim), собрался с духом, засунул наконец все конфиги в git и отправил на GitHub. Конфигурация получилась лёгкой, из неё было выброшены все вековые наслоения конфигов, ненужных плагинов и прочей чуши. Ещё там есть маленький враппер для открывания файла в новой вкладке существующего Vim'а а не в новом экземпляре Vim'а. Так что полюбопытствуйте.
Впрочем, скорее всего, разработку под андроид вскоре можно будет отдать на откуп IntelliJ Idea X EAP, в которой есть и поддержка оной и плагин-эмулятор Vim'а. Попробовал, повертел - за час не возникло серьёзных претензий (да и вообще продукты JetBrains - это что-то, как и их поддержка), разве что не сразу понятно куда там коней^WSDK запрягать. Во всяком случае решение лучше Eclipse.

27 сентября 2010

Как заставить работать Force Close Dialog на себя

... или Stacktrace or GTFO.

Задача.
Итак, первое приложение для Android написано и его бета достигла девайсов пользователей. Задача - при получении исчерпывающего сообщения от пользователя "Ничего не работает" (ну ладно, ладно, более конкретного - "Вылетает") знать, где и что не работает. Вообще это должно быть первым, что будет написано в приложении. Далее будет показано, как гарантированно поймать исключение и послать отчёт о нём в Redmine.

Примечание: большая часть информации относится скорее к Java, чем к Андроиду, однако повод и место применения намекают.

Стратегия.
Приложение совершило критический промах и укусило себя в глаз. Частный и самый распространённый случай этого - исключение (есть и другие варианты, я сталкивался с перезагрузкой телефона), которое принято отлавливать и что-то делать по факту. Если оно не отловлено - пользователь увидит т.н. Force Close Dialog. Начиная с версии 2.2 ещё и предложение собрать данные об ошибке и поспамить ими девелопера, НО надо понимать, что
1. специфика некоторых ошибок привязана к определённым моделям девайсов, на которых вполне определённые версии Андроида, и это не 2.2
2. специфика некоторых рынков в популярности определённых моделей девайсов, см. выше
так что на этот способ не рассчитываем (как на основной).

Необработанное исключение нужно прежде всего отловить. Для этого в Java есть обработчик исключений потока, Thread.UncaughtExceptionHandler, реализация Андроида как раз и показывает диалог и пишет стектрейс в LogCat. С отловленным исключением нужно что-то сделать (узнать о самом факте его возникновения) и... в случае с UI Thread Андроида отпустить дальше - передать умолчальной реализации обработчика, потому что в противном случае, если исключение утаить, получим вместо одного диалога об ошибке другой (Application Not Responding). Кроме того, пользователю тоже особо ничего сказать не получится - UI Thread-то уже загнулся.



public class CrashReporter implements UncaughtExceptionHandler {
private UncaughtExceptionHandler defaultHandler;
public CrashReporter(UncaughtExceptionHandler defaultHandler)
{
this.defaultHandler = defaultHandler;
}

public void uncaughtException(Thread thread, Throwable e) {
Log.e("CrashReporter", "Caught " + e.toString() + " in "
+ thread.toString() + "\n" + Log.getStackTraceString(e));
// allow system to kill us
if(defaultHandler != null)
defaultHandler.uncaughtException(thread, e);
}
}



На основе этого бесполезного пока (потом будет интереснее) класса и будет строится обработка ошибок.

Тактика. Андроид.

Поскольку UI Thread в Андроиде один на всех, то получить глобальную обработку ошибок очень просто, в главной Activity достаточно при создании указать этот класс как обработчик:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(
new CrashReporter(Thread.currentThread().getUncaughtExceptionHandler()));
}


Тактика. Продвинутые техники.
Однако не всё делается в UI потоке Андроида, тяжёлые вычисления требуется вынести за его пределы. В Андроиде есть AsyncTask, который мне не подошёл (да и вообще на мой взгляд подходит только для простейших вещей), я использую ThreadPoolExecutor. Чтобы создаваемые для него потоки имели нужный класс обработчика ошибок, придётся создать свою фабрику (да, вот он, Java way) и передавать её в конструктор ThreadPoolExecutor'а:


public class CrashReportingThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r) {
Thread newThread = Executors.defaultThreadFactory().newThread(r);
newThread.setUncaughtExceptionHandler(
new CrashReporter(newThread.getUncaughtExceptionHandler()));
return newThread;
}
}


public class CrashExecutor extends ThreadPoolExecutor {
private CrashExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
new CrashReportingThreadFactory());
}


Но это полдела. Теперь хлопок другой ладонью - надо заставить Executor обрабатывать исключения, возникшие в задачах, в т.ч. отправленных на выполнение вызовом submit и/или обёрнутые в класс типа FutureTask (за объяснениями - в доки к ThreadPoolExecutor.afterExecute и литературу):



protected void afterExecute (Runnable r, Throwable t)
{
super.afterExecute(r, t);
// задача отправлена на выполнение через submit или изначально является Future
if (t == null && r instanceof Future) {
try {
// принудительный вызов исключения
((Future<?>) r).get();
} catch (CancellationException ce) {
// отмена задачи - нормальное событие
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
// прерывание потока извне - передача исключения выше
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null) {
Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
}
}

Так задача гарантированно будет вызывать исключение (из-за вызова get()) а исключения от Runnable не будут проглатываться.

Тактика. Redmine.

Всё, исключение есть, пора настроить Redmine:
1. Создать роль типа Crash reporter, имеющую право только создавать issue
2. учётную запись с этой ролью
3. набор дополнительных полей для Issue - например номер билда, тип девайса, на котором произошло падение
4. добавить учётку репортера в проект
5. разрешить REST web service
6. дополнительно можно завести под это свой тип трекера, чтобы отделять автоматически созданные issue

теперь можно постить:


// post reports only from wild (end-user) devices
if(!ApplicationInfo.getInstance().isAppDebuggable()){
try {
StringBuilder postBody = new StringBuilder()
.append("<issue><project_id>20</project_id><subject>")
.append(e.toString())
.append("</subject><tracker_id>4</tracker_id><priority_id>10</priority_id><custom_field_values><1>")
.append(Uri.decode(Settings.deviceInfo))
.append("</1><2>")
.append(ApplicationInfo.getInstance().deviceuid)
.append("</2><3>")
.append(String.valueOf(ApplicationInfo.getInstance().getAppBuild()))
.append("</3></custom_field_values><description><![CDATA[")
.append(Log.getStackTraceString(e).replaceAll("]]>","]]>]]><![CDATA["))
.append("]]></description></issue>");
post("http://redmine.company.com/issues.xml", postBody.toString());
} catch (Exception e1) {/*silently die*/}
}

public static void post(String url, String postData) throws IOException {
HttpClient client = new DefaultHttpClient();
HttpPost request = new HttpPost(url);
request.addHeader(BasicScheme.authenticate(
new UsernamePasswordCredentials("crash", "password"), "latin1", false));
request.addHeader("Content-Type", "application/xml");
StringEntity se = new StringEntity(postData);
request.setEntity(se);
HttpResponse response = client.execute(request);
}


ApplicationInfo - собственный класс, который собирает основную инфу об устройстве. Упоминаемые в xml ID можно узнать если посмотреть на ссылки соответствующих вещей - трекера, дополнительных полей, проекта.

Можно собирать релизный билд. Главное - не забыть убрать логирование и возможность отладки (удивительно, как много приложений, в которыx это не сделано).