суббота, 5 апреля 2014 г.

Android Studio — два способа изготовить apk-файл

Введение

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

В этом смысле в царстве Android нет достаточно комфортных сред разработки, как это, впрочем, и полагается программам в мире бесплатных операционных систем. Ещё до недавнего времени центральным средством разработки считался Eclipse со специализированными плагинами. Однако летом 2013 года Google представил на суд общественности новую IDE — Android Studio, основанную на давнишнем конкуренте Eclipse — системе IntelliJ IDEA. Надо сказать, что несмотря на раннюю версию системы, не вошедшую ещё даже в стадию Beta, она уже превосходит удобством Eclipse.

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

После такого большого вступления, я всего лишь расскажу, как в Android Studio можно генерировать конечные apk-файлы, готовые к распространению, ибо есть нюансы. Надеюсь, моя статья хоть как-то облегчит жизнь начинающим пользователям этой, пожалуй, лучшей на сегодняшний день IDE для программирования под Android.


Ручной способ
  1. В левом нижнем углу Android Studio есть маленькая кнопочка, управляющая отображением специальных ярлыков у краёв экрана. Ярлыки открывают различные вспомогательные окна. Кроме того, при простом наведении на эту кнопочку указателя мыши, появляется список всех этих окон для быстрого открытия нужного. Откроем окно Build Variants и напротив нашего модуля в поле Build Variant переключим режим сборки с debug на release.
  2. В основном меню открываем Build Generate Signed APK. Возникает сообщение, которое, немного перефразируя, можно перевести примерно так: «Для проектов, собирающихся утилитой Gradle, информация о подписи и порядок подписывания apk-файлов должны быть прописаны в специальном сценарии. Настройте сценарий, как это описано в руководстве пользователя: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Signing-Configurations. Затем запустите "Gradle assembleRelease", и получите сгенерированный apk-файл в папке build/apk/» Это сообщение как раз настоятельно рекомендует нам использовать второй способ создания apk-файла. Принимаем информацию к сведению, но продолжаем ручной способ, нажав OK.
  3. Открывается окно помощника генерации подписи для apk-файла. Мы уже знаем, что все распространяемые приложения для Android должны иметь цифровую подпись автора, иначе приложение не установится на устройства пользователей. Сгенерированная подпись должна храниться в специальном файле-хранилище, расположенном на вашем компьютере, откуда потом она будет извлекаться для подписания apk-файлов. При этом одно хранилище может хранить несколько подписей для разных apk-файлов. Кроме того, одна и та же подпись может использоваться для подписания разных apk-файлов. Итак, для создания хранилища нажимаем кнопку Create New… и заполняем открывшиеся в окне поля:

    1. Путь для размещения файла хранилища
    2. Пароль и подтверждение для доступа к хранилищу
    3. Имя подписи, по которому она будет вызываться
    4. Пароль и подтверждение для доступа к подписи
    5. Срок действия подписи (по умолчанию 25 лет, оставляем без изменений)
    6. Хотя бы одно из полей сертификата. Обычно заполняют имя и фамилию, город и страну (RU).
       
  4. Нажимаем OK. Окно закрывается, и мы видим, что все поля в предыдущем окне автоматически заполнились введёнными нами данными. Ставим галочку в поле Remember Password, чтобы каждый раз не набирать пароль, и нажимаем OK.
  5. В следующем окне контролируем путь и имя apk-файла. По умолчанию оно равно имени модуля. Включаем галочку Run ProGuard, чтобы наш файл был оптимизирован, и можем даже поменять файл конфигурации proguard-android.txt на proguard-android-optimize.txt для более жёсткой оптимизации (хотя это может быть чревато для некоторых экзотичных смартфонов). Нажимаем Finish и ждём, посматривая в строку состояния.
  6. Когда сборка проекта закончится, отобразится окошко с предложением открыть папку с полученным apk-файлом. Открываем её и видим наш файл.

Скажу сразу, что несмотря на то, что я ставлю галочку в поле Run ProGuard, он у меня почему-то не отрабатывает, и apk-файл остаётся неоптимизированным. Возможно, это пережитки ранней версии Android Studio (у меня версия 0.5.4). Но выйти из положения достаточно просто, включив эту галочку непосредственно в файле сценария утилиты ProGuard. Найдите в окне проекта внутри нашего модуля файл build.gradle. В секции android buildTypes release, отвечающей за генерацию релиза, поменяйте параметр runProguard с false на true.

Не забудьте после редактирования файла сценария синхронизировать с ним ваш проект. Для этого в панели инструментов нажмите кнопку Sync Project with Gradle Files (стрелочка вниз из зелёного кружка) или ссылку Sync Now во всплывшей в верхней части исходника жёлтой полосе сообщения. После синхронизации можно снова попробовать собрать apk-файл, начиная со второго пункта нашей инструкции. Только в этот раз вместо генерации нового хранилища используем уже созданное. Все наши пароли и настройки сохранились, поэтому нам только остаётся нажимать во всплывающих окнах OK-Next-Finish. Обратите внимание, что вновь сгенерированный apk-файл стал немного меньше, чем в прошлый раз.


Автоматический способ

Автоматический способ позволяет генерировать apk-файл без ввода паролей при каждом запуске приложения на выполнение, а также командой assembleRelease, добавленной в список конфигураций и выполняющейся той же кнопкой Run.

Для включения автоматического режима надо внести в уже знакомый нам файл сценария build.gradle новую секцию, содержащую в том числе информацию о подписи:

signingConfigs {
    release {
        storeFile file("C:\\Users\\ИмяПользователя\\KEYSTORE.jks")
        storePassword "ПарольХранилища"
        keyAlias "ИмяПодписи"
        keyPassword "ПарольПодписи"
    }

    buildTypes {
        release {
            minifyEnabled true 
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
       }
    }
}

Вместо файла настроек "proguard-android.txt" можно вписать файл ещё более оптимизированных настроек "proguard-android-optimize.txt". Это позволит ещё немного сократить объём apk-файла.

Для внесения этой секции в настройки откроем файл build.gradle, расположенный внутри нашего модуля и закомментируем секцию buildTypes. Вместо неё вставим вышеприведённую секцию. Не забудьте поменять путь к вашему файлу ключей, который вы создали в первой части статьи при ручном создании apk-файла, а также вписать правильное имя подписи и оба пароля.

После внесения изменений в файл нажимаем в панели инструментов кнопку Sync Project with Gradle Files (стрелочка вниз из зелёного кружка) или ссылку Sync Now во всплывшей вверху жёлтой информационной полосе, чтобы синхронизировать изменённые настройки с проектом. Всё, автоматический режим настроен!

Теперь, чтобы иметь возможность генерировать apk-файл без запуска приложения, добавим отдельную команду запуска генерации apk-файла в список конфигураций (комбо-бокс в панели инструментов). Откроем окошко Gradle Tasks, нажав на ярлык Gradle у правого края экрана, или выбрав его во всплывающем списке кнопки, расположенной в левом нижнем углу Android Studio. В секции All tasks раскрываем список задач для нашего модуля и правой кнопкой щёлкаем по задаче assembleRelease. В появившемся контекстном меню выбираем пункт Create… и выбранная задача добавится в список конфигураций.

Теперь мы можем генерировать релиз и без запуска программы на выполнение, просто выбрав в списке конфигураций команду assembleRelease и нажав кнопку Run. Результат автоматического создания apk-файла будет находится в папке build/apk/. Там будут два файла: ИмяМодуля-release-unaligned.apk и ИмяМодуля-release.apk. Первый файл — это полуфабрикат. Он уже подписан но ещё не выровнен. Второй файл — уже выровненный. Это и есть наш конечный apk-файл, который мы уже можем переименовать и свободно распространять.

Замечу, что выравнивание файла гарантирует, что все несжатые данные будут начинаться с позиции, кратной 4 байтам по отношению к началу файла. Это обеспечит оптимизацию производительности программы на устройстве. Когда файл выровнен, Android способен читать информацию из файла по мере необходимости, а не загружать в память весь пакет целиком. В итоге уменьшается объем оперативной памяти, потребляемой запущенным приложением.

четверг, 3 апреля 2014 г.

Округление чисел в Java

Вступление

При выводе результатов числовых расчётов часто бывает ситуация, когда вместо десятичной дроби, например, 3,7, отображается число 3,6999999999999997. Это происходит из-за того, что далеко не все десятичные дроби можно точно представить в двоичном коде. В случае невозможности записать десятичную дробь точно, используется самое близкое к ней значение, которое можно выразить двоичным числом. При обратном переводе в десятичный формат это значение и даёт такие непонятные длинные дроби.


Перевод десятичных дробей в двоичную систему счисления

Невозможность записать некоторые десятичные дроби в двоичной системе (нулями и единицами) можно привести на примере обычного небольшого десятичного числа 20.1. Целая часть десятичных дробей переводится в двоичный вид точно так же, как целые числа, то есть складыванием положительных степеней двойки так, чтобы в итоге получить искомую сумму:



Получаем, что 2010 = 000101002

Как мы видим по таблице, справа налево вес каждого разряда двоичного числа увеличивается вдвое (положительные степени двойки). Набираем число 20, суммируя результаты этих степеней и отмечая единичками те степени, которые мы использовали. Вариант у нас только один – использовать степени 2 и 4, то есть числа 4 и 16, в сумме как раз дающие 20. Отметив их единичками, получаем двоичное число 00010100 или, если отбросить незначащие нули, 10100.

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



Эту таблицу мы можем продолжать вправо до бесконечности, но никогда не наберём полные 0.1. В итоге получаем, что 0,110 ≈ 00011001100112 = 0,099975585937510.

Таким образом, 20,110 ≈ 10100,00011001100112.


Хранение чисел с плавающей точкой в компьютере

В компьютере числа двойной точности (в Java тип Double) хранятся в экспоненциальном (т.н. научном, инженерном) формате. Этот формат, например, записывает десятичное число 20,1 как 2,01*101, или сокращённо, как 2,01E+1. То есть, число после буквы E указывает, на сколько знаков следует сдвинуть запятую вправо (или влево, если число отрицательное) в числе, стоящем перед буквой

Переведённое в двоичную систему число 20,1, выглядящее как 10100,0001100110011 в экспоненциальной форме будет выглядеть как 1,01000001100110011 * 2100, или сокращённо 1,01000001100110011E+100 (двоичное 100 — это десятичное 4, то есть сдвигать запятую в числе 1,01000001100110011 следует на 4 знака вправо).

Для хранения этих чисел в компьютере отводится 64 бита (8 байт). Биты распределяются следующим образом. Для знака числа (плюс или минус) отводится 1 бит, для записи порядка (в нашем случае 100) — 11 бит, а для записи мантиссы (в нашем случае 1,01000001100110011) — 52 бита.

Схематично это выглядит так:



Чтобы иметь возможность записывать отрицательные порядки, за ноль в поле порядка принято число 1023 (в двоичном коде — 01111111111), что делит весь возможный диапазон порядков примерно пополам (от -1023 до 0 и от 0 до 1024). Поэтому наш порядок 100 фактически выглядит в битах, как 10000000011, ведь 1023 + 4 = 1027.

Кроме того, по причинам экономии на аппаратных ресурсах, самого левого бита мантиссы «в железе» не существует, но программно он всегда подразумевается. Этот бит принимается равным 1 во всех случаях, кроме случая нахождения в поле порядка специального числа -1023 (значения битов 00000000000). В нашем случае в поле порядка занесено число 4, а значит, левый бит признаётся единичным, и поэтому у нас в мантиссе фактически лежит лишь дробная часть, то есть число 01000001100110011.

Исходя из вышесказанного, для нашего числа 10100,0001100110011 значения всех 64 реальных битов и 1 подразумеваемого таковы:



Несколько слов про магическое число -1023 (значение битов 00000000000) в поле порядка. Как я уже говорил, это число просто устанавливает подразумеваемый бит целой части мантиссы в ноль. Если все остальные биты мантиссы также будут нулевыми, то хранимое таким образом в компьютере число, очевидно, будет являться нулём.

В случае же наличия в мантиссе ненулевых битов при том же числе -1023 в поле порядка, подразумеваемый бит целой части мантиссы также принимается равным нулю, но значение порядка подразумевается равным -1022. Грубо говоря, смена чисел в поле порядка с -1022 на -1023 и обратно влияют только на снятие или установку единичного бита целой части мантиссы, тогда как и то, и другое значение порядка подразумевается равным -1022.

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

Это легко понять, представив, как восстанавливается число из экспоненциального формата. Основание системы счисления (2) возводится в степень порядка и умножается на мантиссу. Самое маленькое число типа Double, которое мы можем так выразить, равно 1 * 2-1022. Ведь подразумеваемый бит равен единице, а значит, мантисса не может быть меньше единицы. Постепенно увеличивая мантиссу с 1 до почти 2 мы можем равномерно заполнить числами весь диапазон до предыдущего порядка 1 * 2-1021. Но у нас всегда остаётся незаполненный диапазон с другой стороны — в сторону нуля. И вот его то мы можем целиком заполнить только делая мантиссу меньше единицы. А для этого надо просто переключить подразумеваемый бит в ноль, что мы и делаем!

Существует ещё одно магическое число в поле порядка — 1024 (значение битов 11111111111). В зависимости от содержимого мантиссы, оно означает либо бесконечность (1 / 0 = ∞) либо неопределённость (неупорядоченное число, не число) (0 / 0 = NaN). Если мантисса нулевая — перед нами бесконечность, иначе — неопределённость.


Правильное округление в Java

Конечно, в Java есть функции, которые умеют округлять числа до нужного знака после запятой. Но иногда может встретится число, которое в десятичном виде округляется в большую сторону, а будучи записано в двоичном виде становится чуть меньше, и уже по правилам должно округляться в меньшую сторону. В качестве примера такого числа можно привести число 13,5465. Очевидно, что оно должно округляться в большую сторону до 13,547. Но при записи в двоичный код число становится равным 13,5464999…, и оно уже должно округляться в меньшую сторону до 13,546.

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

public BigDecimal roundUp(double value, int digits){
    return new BigDecimal(""+value).setScale(digits, BigDecimal.ROUND_HALF_UP);
} 

Теперь мы можем применить этот метод, например, если программируем под Андроид, при выводе результата result в поле EditText активности:

EditText.setText(String.valueOf(roundUp(result,3)));

Рассмотрим, как же это работает. Метод roundUp принимает подлежащее округлению число value типа double и желаемое количество знаков после запятой digits типа int. Возвращает метод результат в виде числа типа BigDecimal. Тип BigDecimal хранит данные не в виде двоичного представления числа с плавающей запятой, о котором я рассказывал выше, а в виде числа с фиксированной запятой. Число с фиксированной запятой содержит в себе два отдельных целых числа. Это исходное число, но без запятой, и число, содержащее количество знаков после запятой. А целые числа всегда можно записать в двоичный формат абсолютно точно. Так что на выходе мы потенциально можем получить любую точную десятичную дробь.

Внутри нашего метода создаётся объект BigDecimal, который принимает на вход число с плавающей запятой value типа double, и методом setScale округляет его. Но при обычном использовании этого метода число округляется всё по тем же правилам, что приводит к неправильному округлению. В нашем же методе есть одна хитрость. Чтобы округление всегда происходило в нужную сторону, значение на вход метода BigDecimal мы передаём не в виде числа, а в виде строки. На этапе преобразования числа в строку, язык Java сам автоматически округляет число с достаточным количеством знаков, до степени, имеющей смысл (округляя весь хвост, начинающийся с последовательности девяток или нулей, подозревая в этом погрешность). Таким образом, мы подаём на вход BigDecimal строку с уже исправленной погрешностью.

Чтобы перевести число в строку, мы использовали трюк ""+value. При сложении числа со строкой (даже пустой) результат окажется тоже строкой.

Сравним результаты округления без предварительного перевода числа в строку и с переводом.


Вариант 1:

value = 13.5465
digits = 3
return new BigDecimal(value).setScale(digits, BigDecimal.ROUND_HALF_UP);

Результат: 13,546 — неправильно!


Вариант 2:

value = 13.5465
digits = 3
return new BigDecimal(""+value).setScale(digits, BigDecimal.ROUND_HALF_UP);

Результат: 13,547 — правильно.


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

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

вторник, 1 апреля 2014 г.

Контекст - что это?

Введение

Практика показывает, что смысл контекста в Android не всегда прозрачен. Определения контекста слишком абстрактны, разнятся от источника к источнику, и не дают возможности чётко осмыслить, что же это за сущность такая. Я постарался собрать разрозненную информацию о контексте и привести её к единому виду.

Определение контекста

Контекст (Context) – это базовый абстрактный класс, реализация которого обеспечивается системой Android. Этот класс имеет методы для доступа к специфичным для конкретного приложения ресурсам и классам и служит для выполнения операций на уровне приложения, таких, как запуск активностей, отправка широковещательных сообщений, получение намерений и прочее. От класса Context наследуются такие крупные и важные классы, как Application, Activity и Service, поэтому все его методы доступны из этих классов.

Методы получения контекста и их различие

Получить контекст внутри кода можно одним из следующих методов:
  • getBaseContext (получить ссылку на базовый контекст)
  • getApplicationContext (получить ссылку на объект приложения)
  • getContext (внутри активности или сервиса получить ссылку на этот объект)
  • this (то же, что и getContext)
  • MainActivity.this (внутри вложенного класса или метода получить ссылку на объект MainActivity)
  • getActivity (внутри фрагмента получить ссылку на объект родительской активности)
Все эти способы дадут нам возможность получить ссылку на объект, содержащий методы класса Context.

Как было сказано выше, контекст является базовым классом для классов Application, Activity и Service, а значит его методы входят в их состав. Именно поэтому для передачи контекста в качестве параметра можно использовать как ссылку на сам контекст (getBaseContext), так и ссылки на наследуемые классы (getApplicationContext, getContext, this, MainActivity.this, getActivity).

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

Например, если вызвать сообщение с помощью Toast, используя разные context, то:

Сообщение умрёт вместе с активностью:
Toast.makeText(this, "Text", Toast.LENGTH_SHORT).show();

Сообщение умрёт вместе с приложением:
Toast.makeText(getApplicationContext(), "Text ", Toast.LENGTH_SHORT).show();

Будет видно даже после завершения приложения:
Toast.makeText(getBaseContext(), "Text ", Toast.LENGTH_SHORT).show();

То есть, мы видим, что хотя контекст одинаков у разных объектов, сами эти объекты могут жить разное время. 

Заключение

Итак, контекст можно представить, как набор функций для работы на уровне приложения, вошедший в состав таких крупных классов, как Application, разных видов Activity, Service и пр.