Список вырисовывается такой:
Если заинтересованны прошу под кат.
Начать надо с того, что языки не мой конек, заранее прощу прощения зачем столько библиотек, чем это обусловлено и зачем надо.
Естественно, все продиктовано архитектурой приложения. Исходя из моего опыта, почти всегда, надо иметь следующую структуру приложения:
Как видно, приложение делится на 3 слоя — «мордочка», хранилище данных и сервис для асинхронных команд, где почти всегда скрыта логика.
Я не хочу разводить холивар и спорить об архитектуре, я просто описываю то как делаю это я и как делают мои коллеги.
Пару слов почему так
Контент провайдер очень мощная штука с уведомлениями об изменениях данных и это все из коробки. Юзаем в основном как обвертку над базой. Способа как туда правильно впихнуть загрузку данных из инета не нашел.
Сервис асинхронных команд — из названия все ясно, выполняет действия асинхронно(в другом потоке). Практически все действия должны быть асинхронны — запись в базу, походы в инет, да и подсчеты.
Почему «асинхронных команд»? Тут тоже все просто — каждое действие — законченная команда. Которая знает с какими параметрами запуститься, что с ними делать и оповещением о своем завершении.
Вот тут evilduck уже все детально описал.
«Мордочка» — набор активити/фрагментов для отображения данных. Хочу заметить, что вся загрузка данных из хранилища должна быть асинхронная(ни каких походов в базу из UI даже за одним числом). И тут нам на помощь приходит лоадер менеджер — тоже фича из коробки. Стоит смотреть в сторону CursorLoader
Велосипед
Наверное, каждый программист делал свой велосипед. Польза от них тоже есть — вы начинаете понимаете все подводные камни, что и зачем нужно. Но писать каждый раз велосипед — не хорошо. А прикатить его из другого проекта нельзя т.к. код продан и является собственностью заказчика.
Конечно ваш код можно оформить в либу, опубликовать ее, в договоре с заказчиком написать, что юзаем либу. Но написать либу на все случаи жизни не так просто, тем более зачем делать еще один велосипед если уже есть, а баги уже найдены и пофикшены. Так в конце концов произошло и со мной.
Я не то что противник рефлекшена, но предпочитаю либы которые генерят код. Его удобно дебажить и всегда можно посмотреть, что там творится.
Ко всему прочему мне нравятся аннотации. Вся моя подборка библиотек практически соответствует этому принципу. Начнем…
Groundy
Вот эта библиотечка реализует command service.
- Команды можно кенселить
- Любители AsyncTask’ов не заметят перехода
- Есть поддержка калбеков и они очень просты в использовании
Я предпочитаю писать статический метод запуска команды и типизировать калбек.
Хотя калбеком может быть любой object я предпочитаю типизировать его. Это поможет компилятору помогать нам.
И оградит вас от соблазна навесить калбеки на паблик методы активити, тем самым нарушив один из столпов ООП — инкапсуляцию 🙂
По тем самым соображениям и нужен статический метод запуска.
public class LoginCommand extends GroundyTask{ private static final String ARG_PASSWORD = "arg_password"; private static final String ARG_USER = "arg_username"; @Override protected TaskResult doInBackground() { String userName = getStringArg(ARG_USER); String password = getStringArg(ARG_PASSWORD); //do something return succeeded(); } public static void start(Context context, BaseLoginCommandCallback callback, String login, String password) { Groundy.create(LoginCommand.class) .arg(ARG_USER, login) .arg(ARG_PASSWORD, password) .callback(callback) .queueUsing(context); } public static abstract class BaseLoginCommandCallback{ @OnSuccess(LoginCommand.class) public void handleSuccess(){ onLoginSuccess(); } @OnFailure(LoginCommand.class) public void handleFailure(){ onLoginError(); } protected abstract void onLoginSuccess(); protected abstract void onLoginError(); } }
Retrofit
Очень простой инструмент для вызова REST сервисов, как хорошо написанных так и не очень. Код правда не генерит, но очень прост.
Я смотрел в сторону Spring Android, но как-то тяжеловат он.
public interface ServicesFootballua { String API_URL = "http://services.football.ua/api"; @GET("/News/GetArchive") NewsArchive getNewsArchive(@Query("pageId") long pageId, @Query("count") long count, @Query("datePublish") String date); }
ну и вот так юзаем
private static ServicesFootballua API = new RestAdapter.Builder() .setServer(ServicesFootballua.API_URL) .build() .create(ServicesFootballua.class); ................................... archive = API.getNewsArchive(PAGE_ID, COUNT, dateFormat.format(getTodayTime()));
можно подставить свой конвертер, http клиент и кучу всего прочего.
AnnotatedSQL
Генерит базу и контент провайдер по аннотациям.
Моя поделка, меня устраивает полностью. Пару статей есть на хабре — тут и тут.
Недавно с пинка evilduck опубликовался в maven central
Смотрел на ORMLite, но мне кажется не подходит оно для андроид. Обычно нам не нужно вытягивать прям все и вся. Обычный sql и вьшки решают почти все.
Android Annotations
Очень долго присматривался к этой либе и недавно решился заюзать ее в продакшене — понравилось, несмотря на то, что надо юзать нагенеренные классы.
Мощнейший инструмент, главное не юзать Background ну или включать голову.
Хороший плюс — можно почти безболезненно выпилить.
Смотрел на AQuery и Dagger, но имхо Android Annotations — уже имеет все это.
Единственной минус — иногда тяжело искать ошибку «почему не компилируется?».
О всех возможностях можно прочитать на офф сайте. Единственное, что хочу добавить — всегда пишу статический метод для запуска активити, создания фрагмента. Это изолирует весь код в одном месте и никто в коде не знает юзаем мы нагенеренный класс или оригинальный.
Например фрагмент AlertDialogFragment будет иметь метод
public static void show(FragmentActivity activity, DialogType type, int titleId, String msg, int positiveTitleId, OnDialogClickListener positiveListener) { DialogUtil.show(activity, DIALOG_NAME, AlertDialogFragment_.builder() .titleId(titleId) .errorMsg(msg) .positiveButtonTitleId(positiveTitleId) .dialogType(type).build() ).setOnClickListener(positiveListener); }
а везде в коде вызов будет типа
AlertDialogFragment.show(BaseActivity.this, DialogType.CONFIRM, R.string.some_title, getString(R.string.some_message), R.string.btn_edit, new OnDialogClickListener() {...............}
и никто не знает о существовании AlertDialogFragment_
Android db-commons
Совсем недавно нашел эту замечательную либу. Т.к. мы юзаем лоадеры везде и всюду, обычно результат это Cursor. Всегда можно заюзать CursorAdapter и отобразить то, что надо.
Но вот эта либка предлагает нам юзать список(List), но над курсором, а со списком всеми любимый ArrayAdapter.
Вот такой симбиоз — вы как бы видите List и юзаете ArrayAdapter, но по факту это курсор и курсор адаптер. Настоящая «уличная магия» 🙂
Ребята не поленились и написали такой себе LazyList с небольшим кешем внутри, все как и положено — внутри LruCache.
Для того что бы получить List вместо Cursor надо написать функцию(transform) конвертации строки курсора в объект и вы получите лоадер который вернет не Cursor, а List
return CursorLoaderBuilder.forUri(URI_ITEMS) .projection(ItemConverter.PROJECTION) .where(ItemTable.ACTIVE_STATUS + " = ?", 1) .where(ItemTable.DESCRIPTION + " like ?", "%" + searchText + "%") .transform(new ItemConverter()).build(getActivity());
Но как мы знаем иногда надо прочитать курсор в какой-то объект, например надо посчитать что-то, для этого у ребят есть метод wrap
public Loader<Integer> onCreateLoader(int i, Bundle bundle) { return CursorLoaderBuilder .forUri(ITEMS_URI) .projection("count(" + ItemTable.GUID + ")") .where(ItemTable.ACTIVE_STATUS + " = ?", 1) .where(ItemTable.STOCK_TRACKING + " = ?", 1) .where(ItemTable.TMP_AVAILABLE_QTY + " <= " + ItemTable.RECOMMENDED_QTY) .wrap(new Function<Cursor, Integer>() { @Override public Integer apply(Cursor c) { if (c.moveToFirst()) { return c.getInt(0); } return 0; } }).build(DashboardActivity.this); }
вот такой нехитрый способ.
Это очень тривиальный пример. Возможности гораздо круче.
При этом в метод wrap все еще выполняется в другом потоке. так что вы можете еще сходить в БД за дополнительными данными.
Как я и сказал это иногда надо.
Окончание
Понятно, что для самого UI используется еще кучка разных либ(в основном компоненты), но это уже зависит от фантазии дизайнера 🙂
Ну и вот такой кусочек билд скрипта для грейдл, что бы apt завелся(да-да уже есть спец плагин, но его еще не пробовал).
Ах, да — качаем android-db-commons-0.1.6.jar в папку libs
ext.androidAnnotationsVersion = '2.7.1'; configurations { apt } dependencies { compile fileTree(dir: 'libs', include: '*.jar') compile 'com.google.guava:guava:13.0.1' compile 'com.telly:groundy:1.3' apt 'com.telly:groundy-compiler:1.3' apt "com.googlecode.androidannotations:androidannotations:${androidAnnotationsVersion}" compile "com.googlecode.androidannotations:androidannotations-api:${androidAnnotationsVersion}" compile 'com.github.hamsterksu:android-annotatedsql-api:1.7.8' apt 'com.github.hamsterksu:android-annotatedsql-processor:1.7.8' } android.applicationVariants.all { variant -> aptOutput = file("${project.buildDir}/source/apt_generated/${variant.dirName}") variant.javaCompile.doFirst { aptOutput.mkdirs() variant.javaCompile.options.compilerArgs += [ '-processorpath', configurations.apt.getAsPath(), '-processor', 'com.annotatedsql.processor.provider.ProviderProcessor,com.annotatedsql.processor.sql.SQLProcessor,com.googlecode.androidannotations.AndroidAnnotationProcessor,com.telly.groundy.GroundyCodeGen', '-AandroidManifestFile=' + variant.processResources.manifestFile, '-s', aptOutput ] } }
Всем спасибо за внимание
ссылка на оригинал статьи http://habrahabr.ru/post/208504/
Добавить комментарий