AndroidAnnotations — упрощаем и укорачиваем код без вреда для здоровья проекта (I часть)

  • Tutorial

Уже несколько лет существует и совершенствуется открытая библиотека для Android — Android Annotations
Она похожа на RoboJuice по возможностям, но если изучить ее тщательнее, то станет ясно — она гораздо обильнее по возможностям и реализована более удобным для использования в проекте способом.
Об этой библиотеке уже писали на Хабре, но кратко, да и она сама обновилась.
Что ж, пройдемся по AndroidAnnotations подробно, тем более она вошла в джентельменский набор разработки под Android.

Преимущества


Первое — это сокращение кода, вам больше не нужно искать по id вьюшки и отдельно определять для них слушателей.
Второе — это реализованная, как в самых эротических снах разработчиков под Android, работа с потоками.
Третье — удобное и лаконичное храние состояния активити/фрагмента
Четвертое — простое обращение с адаптерами ListView.
И еще много разных вкусностей...

Что внутри и как работает


Работает это великолепно! Вам не нужно наследоваться от RoboGreendroidOrmliteActionBarFragmentActivity и добавлять в проект тяжеловесные библиотеки. Аннотации обрабатываются на одном из шагов во время компиляции, в собранный проект добавляется лишь 50кб jar, остальное лежит и не лезет в собранный apk.
После аннотации Activity, во время компиляции AndroidAnnotations создаст наследника от него, в котором будут реализованы стандартные методы Android. А остальной код, который мы написали будет вызываться из предка. Из-за этого нам нужно в манифесе декларировать, a MyActivity_ — класс наследник MyActivity, автоматически генерируемый AndroidAnnotations.
Звучит страшно, но самом деле благодаря такому подходу мы как разработчики только выигрываем.
Производительность не падает — используются стандартные методы. А удобство написания растет во много раз.
И еще одна важная особенность: нам не нужно переписывать весь проект — достаточно аннотировать наши Activity/Fragment/View и использовать внутри только, те аннотации, которые нам нужны. А остальное писать, как мы привыкли это делать ранее, и как нам говорит официальная документация. К примеру, нужны в проекте только аннотации для потоков — их и аннотацию Activity/Fragment добавляем, а остальное пишем через стандартные методы.

Имлементация в проект


Здесь и далее всё описано для версии 2.6. Об изменениях в следующих версиях читайте чейнжлог

Настройки проекта

  • Скачиваем архив с библиотекой
  • androidannotations-2.6-api.jar — помещаем в папку «libs» нашего проекта.
  • androidannotations-2.6.jar — поместим в новую папку внутри проекта с названием «ext-libs».
  • Затем зайдем в «Project/Properties/Java compiler/Annotation Processing».
  • Ставим галочки «Enable project specific settings», «Enable annotations processing», «Enable processing in editor».
  • Заходим в подвкладку «Factory path»: ставим галочку «Enable project specific settings» и жмем «Add jar». Добавляем из папки «ext-libs» «androidannotations-2.6.jar». Жмем «apply» и «yes».
  • Во вкладке «Java Compiler» убеждаемся, что у нас «1.6» версия компилятора.
  • Во вкладке «Java Build Path/Source» жмем «Add Folder» и добавляем новую папку ".apt_generated".
  • Жмем ОК


Самое нудное закончилось, дальше начинается песня.

Простая Activity

Для начала аннотируем MainActivity — в манифесте:
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

заменим .MainActivity на .MainActivity_
Аннотируем класс:
@EActivity(R.layout.main_activity)
public class MainActivity extends Activity {

Теперь перегрузку onCreate можно смело удалить и попробовать скомпилировать.

Запуск последующих Activity

В обычном Activity всегда выполняется метод инициализации элементов интерфейса, и только после его выполнения мы можем вызывать свои методы, обращающиеся к различным View. Для аннотированных Activity метод, который обращается к элементам интерфейса мы аннотируем вот так: @AfterViews
Далее попробуем запустить следующую аннотированную Activity, над которой и будем производить различные эксперименты:
   @AfterViews
    protected void afterViews(){
    	TestActivity_.intent(this).start();  
    }

Вот таким простым способом запускаются аннотированные Activity
Для запуска с передачей экстры: TestActivity_.intent(context).myDateExtra(someDate).start();
где myDateExtra — аннотированное @ Extra сериализуемое protected-поле в TestActivity
Для запуска с флагами: TestActivity_.intent(context).flags(FLAG_ACTIVITY_CLEAR_TOP).start();

Аннотируем Activity по полной

Поиск View:
	  @ViewById
	  protected EditText testactivity_first_edittext;

	  @ViewById(R.id.testactivity_second_textview)
	  protected TextView secondTextView;

Для сохранения состояния не нужно вынимать сохраненные объекты instance state вручную, достаточно:
    @InstanceState
    String  stateToSave;

И при измененнии состояния, например при повороте экрана — это поле не будет пересоздано, а сохранит свое значение.

Получение ресурсов, например строк:
    @StringRes(R.string.hello_world)
    String myHelloString;

Вешаем onClickListener:
	@Click(R.id.testactivity_first_button)
	void myButtonWasClicked() {
	    secondTextView.setText("first button was clicked");
	}

Слушаем изменения текста:
	 @TextChange(R.id.testactivity_second_edittext)
	 void onTextChangesOnSomeTextViews(TextView tv, CharSequence text) {
	 	Toast.makeText(this, "second textview was changed", Toast.LENGTH_SHORT).show();
	 }


Наиудобнейшая работа с потоками

В основном эту библиотеку в своих проектах я использую для работы с потоками.
Для этого используются две аннотации:
@ Background — аннотированный так метод выполнится в фоне
@ UiThread — так должен аннотироваться метод, вызываемый в фоновом потоке, но выполняющийся в потоке UI
Вот код примера:
	@Click(R.id.testactivity_second_button)
	protected void secondButtonWasClicked() {
		backgroundWork();
	}

	@Background
	protected void backgroundWork() {
		publishProgress(0);
		publishProgress(10);
		publishProgress(100);
		onBGTaskFinish("bg task finished");
	}

	@UiThread
	void publishProgress(int progress) {
		testactivity_first_edittext.setText("Background task progress: "+ progress);
	}
	
	@UiThread
	void onBGTaskFinish(String resultText){
		secondTextView.setText(resultText);
	}

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

На этом сегодня все, продолжение следует…

Библиотека
Описание всех аннотаций библиотеки
Быстрое внедрение библиотеки в новый проект
Проект-пример из статьи
Поделиться публикацией

Комментарии 34

    +2
    Мда, на что только люди не идут, чтобы не написать пару лишних строчек кода.

    А вообще, реклама, это конечно хорошо, но хотелось бы, чтобы статья объясняла, что же на самом деле происходит under the hood. Ну вот, например, @Background. Каким образом он реализуется? Каким образом вызывает UIThread? Будет ли он вызываться после пересоздания activity? А так всё весьма поверхностно.
      +1
      реклама? это забугорная либа.
      про потоки можно посмотреть с легкостью, скомпилив проект и посмотрев, что она сгенерирует. Реализовано очень хорошо и стабильно. Больше 20 одновременых нитей в проекте запускалось и никаких падений и nullpointer'ов при поворотах и экстренном закрытии.
      Ответ — нет, при пересоздании не переаттачится. Это минус, но не для всех задач
        +2
        Посмотреть можно и в исходниках библиотеки. Но, все таки, раз уж вы пишите обзор, то простая копипаста с вики проекта — не самая лучшая затея. Вики там и так очень большой и исчерпывающий.
        А многопоточность, мне кажется, реализована самым простым и действенным образом. При этом повороты экрана, как и раньше, придется обрабатывать самому.
        0
        Ну вообще то, по сравнению с Thread/Handler сокращение заметное. Особенно учитывая что хэндлер по хорошему нужно оформлять как статик класс и добавлять в него WeakReference на активити.

        Loaders конечно не заменит, но для мелочевки сильно сэкономит время, а код упростит. ИМХО.
        +1
        Спасибо за статью,

        А есть у кого-нибудь положительный опыт использования этой библиотеки в комбинации с различными build-тулами — Ant, Maven, Gradle?
          0
          Да, у нас настроена в проекте автоматическая сборка Ant'ом
            +1
            Ага, есть, нашел — Maven Ant Gradle
            0
            Какое то сомнительное сокращение кода, выглядит это непривычно. Кто пользовался?
              0
              Вы не работали с аннотациями? Рекомендую с ними разобраться.
              0
              Синтетические примеры — это всегда хорошо и красиво. Самый главный вопрос, работает ли это где-либо вне Activity или Fragment? Простейший пример: «паттерн» ViewHolder для вьюшек элементов списка?

              class ViewHolder {
                 private final View title;
                 private final View subTitle;
              
                 public ViewHolder(Context context, View view) {
                     title = view.findViewById(R.id.some_id);
                     subTitle = view.findViewById(R.id.some_other_id);
                 }
              
                 public void setData(SomeData data) {
                     doSomething();
                 }
              }
              


              Код работы с вью и потоками в Activity часто уходит во всевозможные Helper-ы, ViewHolder-ы и прочие Action-ы, я правильно понимаю, что в этом случае аннотации нам не помогут?
              –1
              Хотелось бы выяснить, во что в итоге заворачиваются @Background и @UiThread, и могут ли они работать не в Activity.
              0
              Сгенерированный код примера работы с потоками, который в статье:
                  @Override
                  public void backgroundWork() {
                      BackgroundExecutor.execute(new Runnable() {
              
                          @Override
                          public void run() {
                              try {
                                  TestActivity_.super.backgroundWork();
                              } catch (RuntimeException e) {
                                  Log.e("TestActivity_", "A runtime exception was thrown while executing code in a runnable", e);
                              }
                          }
              
                      }
                      );
              
              
                  @Override
                  public void publishProgress(final int progress) {
                      handler_.post(new Runnable() {
              
                          @Override
                          public void run() {
                              try {
                                  TestActivity_.super.publishProgress(progress);
                              } catch (RuntimeException e) {
                                  Log.e("TestActivity_", "A runtime exception was thrown while executing code in a runnable", e);
                              }
                          }
              
                      }
                      );
                  }
              
                  @Override
                  public void onBGTaskFinish(final String resultText) {
                      handler_.post(new Runnable() {
              
                          @Override
                          public void run() {
                              try {
                                  TestActivity_.super.onBGTaskFinish(resultText);
                              } catch (RuntimeException e) {
                                  Log.e("TestActivity_", "A runtime exception was thrown while executing code in a runnable", e);
                              }
                          }
              
                      }
                      );
                  }
              
              


              где handler_ = new Handler();
              а тут код BackGroundExecutor
                –3
                А зачем ставить минусы? Если вам просто не нравится эта библиотека или подход к разработке — делайте, как нравится, не гневайтесь на тех, кто предпочитает другие пути
                  +1
                  Не совсем понял связь картинки с содержанием поста. Это для создания хорошего настроения у читателей перед прочтением?
                    +2
                    В руках у девушки логотип AndroidAnnotations. Связь в нем.
                      +1
                      логотип без девушки смотрелся скучно и одиноко
                        0
                        о, точно, вот чуть ниже-то я и не посмотрел
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        1. @ViewById.
                        2. как указал автор, эта аннотация имеет необязательный аргумент типа int, позволяющий задать id искомого View.
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            На мой взгляд, разрешать экземпляры View по id, как вообщем и распределять программу по потокам (AsyncTask), пробрасывать объекты через всю иерархию вызовов, и т.п. — это сквозная логика.
                            А её лучше либо отделять либо вообще избавляться. так как она затрудняет чтение кода ответственного за бизнес-логику.
                        +1
                        Интересная библиотека.

                        Немного смутила ситуация с инъекциями. Например тот же @ViewById задекларированный в классе помеченным @EBean незаметно потащит за собой весь контекст, что может привести к утечке памяти при потери бдительности (а библиотека как раз этому способствует).

                        Она не лишена недостатков, однако при осторожном использовании позволит избавится от бойлерплейтного кода который уже так надоел мне.
                          +4
                          а я влюбился в Android — query
                          впервые среди подобных утилит увидел простой и чистый код без изъебств, 60 килобайт, никаких зависимостей, те же плюшки с сокращением кода + еще с пол-сотни мегаюзабельных утилит из коробки.
                          Вобщем что-то вроде jquery для веб или codeigniter для пхп — только для андроид
                            0
                            Спасибо, даже не знал!
                            Выглядит просто замечательно!
                            Кажется, я тоже влюбился :)
                            +1
                            Не нравятся такого типа либки, которые сами код генерируют и которым нужны всякие символы подчеркивания.
                            Конечно это работает быстрее чем плюшки из Roboguice, но код не становится чище
                              0
                              К сожалению RoboGuice заставляет производительность плакать. Из-за него время запуска приложения может достигать секунд.

                              Особенно печально это при обработке широковещательных сообщений. Что-бы запустить обработку сего сообщения на незапущенном приложении, необходимо создать объект Application, который потянет за собой инициализацию RoboGuice. Казалось бы, подумаешь — немного батарейки сожрет и все. А если представить что это сообщение обработка клика по виджету?

                                0
                                Согласен что Roboguice запуск приложения убивает, сейчас столкнулся с дeлемой Roboguice тормозит а AndroidAnnotations не нравится принцип работы, теперь не знаю что делать(
                                  0
                                  а вы пробовали Dagger?
                                    0
                                    да, но это не то что хочу. мне не нужен просто DI контейнер, мне нужны плюшки, как в roboguice
                              0
                              Подскажите кто то пробовал в продакшене Butter Knife? Решений на подобие Roboguice уже несколько, хотелось бы узнать мнение тех кто может сравнивал и знает плюсы и минусы этих либ.
                                0
                                Используем уже давно, советую попробовать.
                                  0
                                  советую не пробовать. и считать ее Deprecated

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

                              Самое читаемое