Освой программирование играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Начиная с версии 19.1 библиотека Android support library включает себя аннотации, которые помогают улучшить код, уменьшая количество ошибок. Кстати, сама библиотека и многие другие классы системы уже используют новые аннотации в своём коде и вы их можете иногда видеть при наборе своего текста. Количество аннотаций увеличивается, поэтому сверяйтесь с документацией.
По умолчанию аннотации не включены, они идут как отдельная библиотека. Но она входит в состав библиотеки appcompat. Если вы не используете appcompat, то подключите библиотеку аннотаций самостоятельно.
dependencies {
compile 'com.android.support:support-annotations:22.2.0'
}
Так как на сегодняшний день проекты по умолчанию используют appcompat, то будем считать, что аннотации всегда готовы к употреблению.
Подсказки с аннотацией появляются при попытке ввести неверный тип параметра в методе, который предусмотрительно снабжён аннотацией.
Для первого знакомства приведу пример с использованием ресурсов. В коде мы часто используем ресурсы строк, изображений, идентификаторов и т.п. По сути ресурс в данном случае является числом типа int. Подготовим ресурсы для имён котов в res/strings.xml
<string name="cat_name">Marzik</string>
// и так далее
Напишем вспомогательный метод для генерации имени кота, используя строковый ресурс.
public String generateCatNameWrong(int resId){
String name = getString(resId);
return name.replace("a", "u");
}
Студия не замечает ошибок, метод написан правильно и даже работает.
public void onClick(View view) {
mResultTextView.setText(generateCatNameWrong(R.string.cat_name));
}
Но никто вам не помешает написать и такие варианты:
// ресурс изображения
mResultTextView.setText(generateCatNameWrong(R.mipmap.ic_launcher));
// просто число, взятое с потолка
mResultTextView.setText(generateCatNameWrong(451));
Два варианта используют корректные типы, но результат будет не тот, который мы хотели увидеть. В первом случае выведется путь к ресурсу (к счастью, программа продолжит работу), а во втором произойдёт ужасное - программа пойдёт к коту под хвост.
Как избежать этих ошибок, перепишем метод с добавлением аннотации:
public String generateCatName(@StringRes int resId){
String name = getString(resId);
return name.replace("a", "u");
}
Теперь студия будет подчёркивать неправильные параметры 451 или R.mipmap.ic_launcher и выводить предупреждение, что она ожидает строковые ресурсы, а не любые выдуманные вами числа.
Вы можете использовать аннотации @AnimatorRes, @AnimRes, @AnyRes, @ArrayRes, @AttrRes, @BoolRes, @ColorRes, @DimenRes, @DrawableRes, @FractionRes, @IdRes, @IntegerRes, @InterpolatorRes, @LayoutRes, @MenuRes, @PluralsRes, @RawRes, @StringRes, @StyleableRes, @StyleRes, @XmlRes и т.д. Если у вас есть свой тип ресурсов "foo", то можете использовать аннотацию FooRes.
Поняв базовый принцип, вы теперь можете использовать и другие аннотации. Например, указать, что параметр не может быть null или, наоборот, может принимать значение null.
public void onClick(View view) {
mResultTextView.setText(generateCatName(null));
}
public String generateCatName(@NonNull String name){
return name.replace("a", "u");
}
Студия выводит предупреждение, но позволит запустить программу. Нажатие на кнопку и опять ваш труд коту под хвост - крах приложения.
Метод может возвращать какое-то значение, которое нужно применить. А можно просто вызвать метод, только какой в этом смысл, если возвращаемое значение нигде не используется? Для тех, кто страдает склерозом - встречайте аннотацию @CheckResult.
public void onClick(View view) {
// результат никуда не передаётся
generateCatName("Marzik");
}
@CheckResult
public String generateCatName(@NonNull String name){
return name.replace("a", "u");
}
Студия укажет на ошибку и откажется запускать ваше приложение. Умница.
Аннотация для продвинутых. ProGuard удаляет неиспользуемые методы при компиляции. Если она делает это ошибочно или по каким-то другим причинам вам нужно оставить метод в приложении, то используйте @Keep:
public class Example {
@Keep
public void doSomething() {
// нужный метод
}
...
}
Существуют специальные аннотации для указания потоков.
Например, мы знаем, что методы класса AsyncTask могут работать только в определённых потоках.
@WorkerThread
protected abstract Result doInBackground(Params... params);
@MainThread
protected void onProgressUpdate(Progress... values) {
}
Если в методе doInBackground() обратиться к какому-нибудь компоненту, то студия предупредит, что так делать нельзя.
@UiThread и @MainThread практически совпадают, но в редких случаях могут различаться. Подробности в документации.
Вы можете задать цвет через ресурс, используя аннотацию @ColorRes. А если перед вами стоит противоположная задача - указать значение цвета через RGB/ARGB, то используйте аннотацию @ColorInt. В этом случае при использовании цветового ресурса студия покажет ошибку.
public void setTextColor(@ColorInt int color){
...
}
// Будет показана ошибка. Нужно указать цвет, а не ресурс
myView.setTextColor(R.color.black);
Можно указать диапазон значений для типов float/double через аннотацию @FloatRange:
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) { … }
В этом случае можно использовать значения от 0.0 до 1.0. Любая попытка ввести другое значение приведёт к предупреждению.
Аналогично работает для типов int/long через аннотацию @IntRange:
public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }
Для массивов, коллекций и строк можно использовать аннотацию @Size для установки ограничений в размерах или длине строк.
Если ваш метод использует функциональность, которая доступна через разрешения, то можете указать через аннотацию @RequiresPermission:
// Не забудьте установить разрешение на работу с обоями
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
Если имеются несколько подходящих разрешений, то можно использовать атрибут anyOf, чтобы выбрать один из них:
@RequiresPermission(anyOf = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION})
public abstract Location getLastKnownLocation(String provider);
А если нужно выбрать несколько разрешений, то используйте атрибут allOf:
@RequiresPermission(allOf = {
Manifest.permission.READ_HISTORY_BOOKMARKS,
Manifest.permission.WRITE_HISTORY_BOOKMARKS})
public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) { ...}
Разрешения для намерений
@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
"android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
Если есть разрешения на чтение или запись, то можно указать нужный режим через аннотации @Read или @Write:
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
Если метод должен использовать вызов суперкласса, то используем аннотацию @CallSuper:
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) { }
В документации вы можете узнать о других аннотациях: IntDef/StringDef, @VisibleForTesting и т.д.