Еще раз об архитектуре Android приложения или джентльменский набор библиотек

    Вот надумал написать обзор библиотек с помощью которых легко и удобно писать приложения под Android.
    Список вырисовывается такой:

    Если заинтересованны прошу под кат.

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

    Как видно, приложение делится на 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
    		]
    	}
    }
    


    Всем спасибо за внимание
    Поделиться публикацией

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

      +2
      Спасибо, интересно.
      А не сравнивали retrofit и android annotations в части работы с REST? Сам android annotations для этого не пользовал, интересно услышать впечатления.

      И интересно, как retrofit дружит с self signed сертификатами? В «обычной» жизни для них пользуем com.byarger.exchangeit.EasySSLSocketFactory()

      SchemeRegistry schemeRegistry = new SchemeRegistry();
      schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
      if (самоподписанный сертификат?) {
      	schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
      } else {	
      	schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
      }
      ClientConnectionManager connManager = new ThreadSafeClientConnManager(params, schemeRegistry);
      
      httpClient = new DefaultHttpClient(connManager, params);
      


      Есть ли возможность что-то подобное сделать с retrofit?
        +1
        про self signed сертификат — можно создать свой клиент и передать в RestAdapter

        RestAdapter.Builder().setServer(..).setClient(new ApacheClient(client))

        Пример клиента для Apache Http можно посмотреть тут: github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/client/ApacheClient.java
          0
          Все верно, там можно установить свой клиент ретрофиту.
        0
        А не сравнивали retrofit и android annotations в части работы с REST? Сам android annotations для этого не пользовал, интересно услышать впечатления.


        rest из android annotations — это набор аннотаций и обвертка для работы с spring android. его тоже надо будет затянуть с собой
        +3
        Или можно использовать одну либу для всего этого: github.com/yanchenko/droidparts Кстати, недавно выпустил версию 2.0.
        Из моего опыта, reflection + runtime annotation processing при корректном использовании вполне себе ок в плане производительности. Нет смысла усложнять генерацией кода.
          0
          надо попробовать.
            0
            Буду рад ответить на вопросы с тегом [droidparts] на Stack Overflow.
              0
              Мне, как начинающему, не хватает документации. А так хорошая библиотека.
                0
                Если не затруднит, напишите пожалуйста в личку, какие есть вопросы, и какие темы следует осветить в документации.
          0
          Не увидел в списке RxJava %)
            0
            А вообще отличная подборка, спасибо
              0
              RxJava сильно портит читабельность кода
                –1
                Порог вхождения повышает, это да. Но читаемость отличная, если понимаешь. Ну и если язык нормальный, с лямбдами :)

                Callback hell портит читаемость куда хуже.
                  0
                  У нас тут Java) Согласен, callback позволяет запутаться, что и откуда пришло.
                  Но RxJava это просто ужас и дело не в пороге вхождения, который сильно увеличивается. Дело в самой структуре кода.
                    0
                    Таки под Android можно (нужно) писать на ряде альтернативных JVM-языков.

                    По поводу ужаса принципиально не согласен, думаю дальше спорить нет смысла.
                      0
                      Если честно, я не нашёл способа прикрутить Groovy к андроиду. Может я где-то не там искал?
                  0
                  Вот пример хороший, там читаемость (и не только) «обычного» кода хуже:
                  nurkiewicz.blogspot.ru/2014/01/turning-twitter4j-into-rxjavas.html
                +2
                я бы добавил еще GSON, очень удобная штука
                  +2
                  Мне в AQuery понравилось то, что его не надо как-то специально конфигурировать.
                  Android Annotations иногда чудеса вытворяет — во-первых, нужно сконфигурировать IDE, во-вторых, иногда eclipse сходит с ума и не видит сгенеренные им классы. Лечится либо явным указанием пакета перед именем сгенеренного класса — либо прятаньем за статическим методом, как по тексту описывается.

                  А GSON или что-нибудь аналогичное очень жизнь упрощают, это да.
                    +1
                    Android Annotations иногда чудеса вытворяет — во-первых, нужно сконфигурировать IDE, во-вторых, иногда eclipse сходит с ума и не видит сгенеренные им классы

                    это да, налдо руками часто указать source directory. в AS таже беда.
                      0
                      +1 за AQuery. Легковесная библиотека заменяющая и Android Annotations и Groundy и Retrofit на 100% да еще и добавляющая функционала на все тысячу процентов. Я уже не говорю о том, что после программирования в groovy-style на ней, возвращаться в дефолтное многословное окружение нет никакого желания. Какой смысл разбираться в куче библиотек если можно кинуть 100 килобайтный jar AQuery и наслаждаться творчеством? Как, как Вы могли не рассмотреть этот бриллиант?
                        0
                        ну насколько я понимаю она частично работает на рефлекшене.
                        + просто руки еще не дошли, попробовали пока такую связку. под «попробовали» я имею ввиду нормальный продакшен проект.
                        как только поюзаю ее — постараюсь написать и о ней.
                          0
                          Да, рефлекшен частично используется для автоматического баиндинга-колбэка-асинктаски-куда-угодно. И это пипец как удобно. Просто она настолько проста, что сперва в голове не укладывается что одна эта библиотека заменяет десяток тяжеловесных разношерстных либ различной степени глюкавости. Единственное чего в ней нет — это работы с БД. Дефолтное кеширование основано на файловой системе, что впрочем в 99% случаев нивелирует мою потребность в БД. Обязательно попробуйте, перешел на нее с год назад, перепробовав десятка 3 различных библиотек-фреймворков, и с тех пор забыл что такое «смотреть налево» перед началом проекта. Теперь вперед и только вперед, и только с Android Query.
                        +1
                        Скажите кто использовал, а AQuery не течет по битмапам? Учитывая то что их будет много и разных. Интересует для Android 2.2+
                          0
                          Нет, сама по себе не течет. Конечно нужно делать recycle аквери если например внутри getView обрабатываешь.
                          Вот пример работы с битмапами внутри листвью с:
                          — задержкой загрузки при скроллинге
                          — заменой битмапа плейсхолдером
                          — кешированием в памяти
                          — загрузки плэйсхолдеров из ресурсов
                          — отображение битмапа со скалированием и фэдин эффектом
                          import android.app.Activity;
                          import android.graphics.Bitmap;
                          import android.view.View;
                          import android.view.ViewGroup;
                          import android.widget.BaseAdapter;
                          import com.androidquery.AQuery;
                          import com.androidquery.util.AQUtility;
                          import org.json.JSONObject;
                          
                          import java.util.List;
                          
                          /**
                           * Created with IntelliJ IDEA.
                           * User: recoil
                           * Date: 06.09.13
                           * Time: 15:15
                           * To change this template use File | Settings | File Templates.
                           */
                          public class AdpShowcase extends BaseAdapter{
                          
                              private Activity activity;
                              private List<JSONObject> data;
                              protected AQuery listAq;
                              int width;
                              Bitmap placeholder,type3d,typePano,typeSlide,typeAnim,typeVideo,placeholderbuild;
                          
                              public AdpShowcase(Activity _activity, List<JSONObject> _data) {
                                  activity = _activity;
                                  data = _data;
                                  listAq = new AQuery(activity);
                          
                                  placeholder = listAq.getCachedImage(R.drawable.placeholder);
                                  placeholderbuild = listAq.getCachedImage(R.drawable.m_placeholder_construction);
                                  type3d = listAq.getCachedImage(R.drawable.m_type_icons_catalog_3dphoto);
                                  typePano = listAq.getCachedImage(R.drawable.m_type_icons_catalog_panorama);
                                  typeSlide = listAq.getCachedImage(R.drawable.m_type_icons_catalog_slideshow);
                                  typeAnim = listAq.getCachedImage(R.drawable.m_type_icons_catalog_animation);
                                  typeVideo = listAq.getCachedImage(R.drawable.m_type_icons_catalog_video);
                          
                                  width = (activity.getWindowManager().getDefaultDisplay().getWidth()-Utils.dpToPx(8))/2;
                          
                                  AQUtility.debug("width",width);
                              }
                          
                              @Override
                              public int getCount() {
                                  return data.size();
                              }
                          
                              @Override
                              public Object getItem(int position) {
                                  return data.get(position);
                              }
                          
                              @Override
                              public long getItemId(int position) {
                                  return position;
                              }
                          
                              @Override
                              public View getView(int position, View view, ViewGroup parent) {
                          
                                  if (view == null) {
                                      view = activity.getLayoutInflater().inflate(R.layout.item_grid_showcase, parent, false);
                                  }
                          
                                  JSONObject o = data.get(position);
                          
                                  String img = "";
                                  int type = 1;
                                  int subtype = 1;
                                  try {
                                      type = o.optInt("type");
                                      subtype = o.optInt("subType");
                          
                                      JSONObject jsonObject  = o.getJSONObject("placeholder");
                                      jsonObject = jsonObject.getJSONObject("sprite");
                                      img = "http:"+jsonObject.getString("url");
                                      if (position==0)
                                          AQUtility.debug("JSON adapter",img);
                                  }
                                  catch (Exception e) {e.printStackTrace();}
                          
                                  AQuery aq = listAq.recycle(view);
                          
                                  aq.id(R.id.iv_object).width(width,false);
                                  aq.id(R.id.iv_object).height(width,false);
                                  if(aq.shouldDelay(position, view, parent, img)){
                                      aq.id(R.id.iv_object).image(placeholder, 1f);
                                      aq.id(R.id.icon_type).image(null,1f);
                                  }else{
                                      if (!img.contains("/0.jpg")) {
                                          aq.id(R.id.iv_object).image(img, true, false, width, 0, placeholder, AQuery.FADE_IN_NETWORK, 1f);
                                      }
                                      else {
                                          aq.id(R.id.iv_object).image(placeholderbuild,1f);
                                      }
                                      if (type==5 && (subtype==5 || subtype==0)) {
                                          aq.id(R.id.icon_type).image(typeVideo);
                                      }
                                      else {
                                          switch (subtype) {
                                              case 10:
                                                  aq.id(R.id.icon_type).image(typeSlide);
                                                  break;
                                              case 8:
                                                  aq.id(R.id.icon_type).image(typeAnim);
                                                  break;
                                              default:
                                                  if (type==2) {
                                                      aq.id(R.id.icon_type).image(typePano);
                                                  }
                                                  else {
                                                      aq.id(R.id.icon_type).image(type3d);
                                                  }
                                                  break;
                                          }
                                      }
                                  }
                                  return view;
                              }
                          
                          }
                          
                          
                          
                            0
                            7 картинок, как у вас в примере, это не много. Или вы это привели как демонстрицию работы с листвью? В частности интересует если тащить много фоток (500+ шт. 180х180) с интернета и кешить им, не потечет ли.
                              –1
                              Мне кажется, для таких целей куда лучше и удобнее использовать Picasso.
                                0
                                Не знаете, есть ли какие-то преимущества\недостатки Picasso по сравнению с тем же universal image loader-ом? (кроме более чистого синтаксиса, это я и сам вижу :) )
                                  –1
                                  Каких-то прямых сравнений, замеров производительности и т. п. не проводил, но лично отдаю предпочтение пикассо.

                                  Пикассо действительно имеет хороший апи. Он написан и поддерживается очень крутыми чуваками из Square, сильно оптимизирован, из коробки поддерживает загрузку, трасформации, кеширование, поддержку recycle в списках, интегрируется с OkHttp от того же Square и гарантированно будет развиваться и поддерживаться. В общем, Square тут, как обычно, довольно минималистичны, в то время, как UIL более развесист. Но если вам вся эта кастомизация не нужна, если не нужно делать какой-нибудь FuzzyKeyMemoryCache, я бы отдал предпочтение пикассо.
                                    0
                                    О, какая-то лапочка поставила минусы, не утрудившись написать, почему, и с чем не согласна :) Впрочем, обычное поведение тех, кому нечего сказать на этом сайте.
                                    0
                                    Из собственного опыта:
                                    В проекте был Universal Image Loader, затем мне понадобился кастом (хитро загружать и обрабатывать картинку). UIL сразу сдал свои позиции, был в последствии выброшен на помойку и заменен Picasso.
                                      0
                                      В чем он сдал позиции?
                                  0
                                  Код грузит из сети картинки, неужели не видно? Их может быть хоть сто, хоть миллион. Вот само приложение из идентичным кодом в адаптере, загружающее картинки, попробуйте обрушить: play.google.com/store/apps/details?id=ru.recoilme.tlen
                                    0
                                    А, пардон, проглядел. Могли бы и пожалеть нас и вырезать нужный кусок кода.
                                      0
                                      Жалею. Вот нужный кусок кода. А вот мануал: code.google.com/p/android-query/#Image_Loading
                                      //fetch and set the image from internet, cache with file and memory 
                                      aq.id(R.id.image1).image("http://www.vikispot.com/z/images/vikispot/android-w.png");
                                      
                                      0
                                      Не хочется Вас огорчать, но если записей будет сильно больше 100-200, вы рискуете вылетать с OOM на более старых девайсах даже не по причине картинок, а по-причине List содержащего JSONObject.
                                        0
                                        Не хочется Вас огорчать, но JSONObject это такой же обжект как и любой другой обжект. Не плодите сущностей сверх необходимого. Если конечно у вас конечно JSONObject ы не по 50000 байт каждый.
                                          0
                                          Я вовсе не огорчен, ведь знаю, что это не такой же обжект, как любой другой. Вы действительно думаете, что JSONObject сравним с POJO? Это «обжект», сожержащий внутри себя структуру на основе HashMap, которые имеют очень высокий memory overhead. Да и о DOM сериализации хоть сколько-нибудь внушительного json'a (которую подразумевает наличие JSONObject) я уж промолчу.
                                            0
                                            Конечно сравним. Весь список объектов редко занимает более пары сотни килобайт. И я против того, чтобы создавать класс аналогичной структры и перегонять в него json. Он же у Вас не святым духом DOM сериализуется верно? Это совершенно избыточный оверхед, за редким исключением. Хотя конечно надо смотреть по ситуациии, может быть и достаточно внушительный json, как Вы выразились. Но не в данном случае.
                                              0
                                              Все верно, у меня обычно он сериализуется не святым духом DOM, а святым духом SAX. К тому же я вообще против объектов в адаптере, я люблю курсор. Но, бывает, мне приходится складывать десяток другой тысяч записей, пришедших в виде json с сервера в базу за умеренное время и при умеренном расходе памяти.
                                                0
                                                Да я согласен с Вами. Тоже однажды была ситуация с довольно большими json (>500kb) — заюзал jackson github.com/FasterXML/jackson — в качестве SAX парсера. Но это было нужно 1 раз за всю мою жизнь)
                                      0
                                      Советую использовать Universal-Image-Loader.
                                      Использую его в твиттер клиенте Robird.
                                      Каждый элемент списка имеет Аватарку пользователя + иногда картинка в твите.
                                      Все кешируется и работает просто замечательно.
                                0
                                я пытался описать так сказать высокоуровневые библиотеки. которые помогают делать архитектуру.
                                понятно что есть еще уйма мелких либ которые облегчают жизнь.
                                Gson — это всего лишь автоматическая сериализация и десириализация из JSON. Это один из вариантов конвертирования ответа сервера в объекты в ретрофит
                                0
                                Добавлю свои пять копеек. Volley может заменить и Groundy и
                                Retrofit для клиент-серверного взаимодействия (+ кеширование из коробки)
                                  0
                                  Ну ретрофит да — но она менее удобная. но она не может заменить сервис.
                                    0
                                    Да, сервис заменить не может. Но есть мнение, что сервис может быть overkill-решением для, например, rest-взаимодействия. А может и не быть.
                                      +1
                                      мое обоснование почему я так делаю — юзер зашел на экран со списком — мы подтянули его из базу и пошли загружать свежие данные.
                                      в этот момент юзер сворачивает приложение в ожидании загрузки например, андроид килит активити(он может это сделать) — а вместе с ней и тот тред/асинк таск который грузил инфу. юзер разворачивает приложение — активити пересоздается а инфы нет — она просто не догрузилась и все по новой.
                                      пожтому надо юзать сервис — даже если андроид и убъет активити, сервис спокойно догрузит данные, положит их в базу и человек вернувшись в приложение увидит их.
                                        +2
                                        Вы правы в том, что андроид может убивать активити, но это не значит, что запущенные из активити таска или тред тоже будут убиты, они будут висеть в памяти пока не закончат свою работу. Проблема в том, что вернуть результат будет уже некуда, т.к. активити уже убита. Прелесть volley в том, что можно хранить очередь запросов в контексте приложения (не активити) и если юзер уйдет из активити, запущенный запрос будет отработан и помещен в кеш. Когда юзер развернет прилож и стартанет эту же активити, запустится тот же самый запрос, но он сразу вернет закешированный результат
                                          0
                                          Надо пробовать, смотреть. Вот вам и джава — есть море вариантов делать одно и тоже и никто не скажет какой самый хороший.
                                          Надо попробовать и сравнить. просто такие эксперименты делать на продакшен проекте не будешь. надо сначала на мелком прожекте. где цена ошибки не велика.

                                          да и привычка — очень странная вещь.
                                            0
                                            Если в программировании кажется, что одну вещь можно сделать только одним способом, значит плохо искали :))
                                            Так что альтернативы — это нормально.
                                            Другое дело что сам всё не перепробуешь, поэтому для меня ценность таких обзорных статей — как раз составить представление о том, что есть интересного вокруг.
                                            0
                                            Да, все верно. Но есть ньюанс. Точно так же как андроид может убить активити, с такой же легкостью он может избавится и от самого процесса. Это нормально, когда все что требуется от реста это какие-то недолго живущие и ни на что не влияющие данные. Но а что если вам нужно получить какие-то данные, на основе них что-то записать в БД и, например, записать что-нибудь в файлик?
                                              0
                                              Но а что если вам нужно получить какие-то данные, на основе них что-то записать в БД и, например, записать что-нибудь в файлик?


                                              я всегда юзаю сервис
                                                0
                                                Сервис как компонент android-приложения это не отдельный процесс и не отдельный поток. Если ОС решит избавиться от всего процесса — его сервисы тоже будут убиты (правда при старте сервиса с флагом Foreground вероятность смерти процесса почти нулевая).
                                                Но а что если вам нужно получить какие-то данные, на основе них что-то записать в БД и, например, записать что-нибудь в файлик?

                                                Если размер данных, которые нужно получить, не очень велик, то можно выполнить запрос тем же volley. Для загрузки большого количества данных есть свои механизмы (SyncAdapter, DownloadManager, что-нибудь еще) и тут не поможет ни volley, ни любой другой рест-клиент, ни асинктаски или их обёртки. Запись в бд или файлик на основе этих данных — это уже другая операция и реализуется в зависимости от выбранных механизмов работы с бд. Нетворкинг тут уже ни при чем.
                                                  0
                                                  Сервис с :remote флагом будет как отдельный процесс и выгрузка всего UI процесса не обязательно приведет к выгрузке сервиса, особенно когда он с foreground флагом.
                                        0
                                        для загрузки картинок патались юзать picasso
                                        0
                                        А почему в android annotations не надо юзать background?
                                          0
                                          как по мне, идея тредов которые захватывают UI контекст — не айс. потом могут быть мемори лики например
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                            0
                                            я с опаской отношусь к ORM. главная проблема с ними — вытаскивание огромного количества данных в память. вот и все.
                                            курсор хорош тем — что он держит cursor window на базу. и не засоряет память. а когда вы двигаетесь по списку вверх/вниз — он двигает окно.
                                              0
                                              в Android курсор грузит в память весь result set из базы, для обеспечения хождения в обе стороны. Отсюда и ограничение на 1 мб общего размера данных, которые вернул запрос из sqlite.
                                                0
                                                пруф в студию плиз.
                                                вот тут — SQLiteCursor четко видно, что юзаеются окошки. посмотрите на метод move например
                                                  0
                                                  Мы с удивлением сами наткнулись сами на эту особенность в конце года. Но суть в том, cursor window растет по мере перемещения по записям и когда общий объем прочитанного превысит 1 MB, .next() будет отрабатывать как обычно, но getLong, getString, etc начнут возвращать ошибки как будто вы указали недействительный индекс колонки. Посмотрите поиском android 1 mb sqlite limit на том же stackoverflow
                                                    0
                                                    ну cursor window не должен расти, может вы не закрываете курсоры? или вылазит просто при пролистовании списка?
                                                      0
                                                      именно простой запрос вида
                                                      select a,b,c,d from table
                                                      и последующий проход по результатам вперед работает до тех пор, пока не налистается 1 мб данных, после чего методы
                                                      cursor.getXXX(int)
                                                      начинают возвращать ошибки вида
                                                      IllegalStateException Couldn't read row 3, col 7 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.

                                            0
                                            А мне почему-то для различного рода сетевого взаимодействия больше понравился Android Async Http (http://loopj.com/android-async-http/). Код получается очень компактным, понятным и доки написаны очень грамотно.
                                            Сначала тоже смотрел в сторону Retrofit, но не нашел с ходу в доке, как там извлекать заголовки из полученных с сервера данных (хотя, вероятно, просто плохо искал).
                                              0
                                              У Android Async Http есть большая проблема с HTTPS. Точнее с HTTPS-взаимодействием с сайтами с само-подписанными сертификатами

                                              HTTPS not working

                                              Есть решение:
                                              Self-signed certificate and loopj for Android

                                              но это решение не более чем хак (его можно сформулировать как «а давайте просто всем верить!»). про решение автор написал следующее:

                                              Note: Do not implement this in production code you are ever going to use on a network you do not entirely trust. Especially anything going over the public internet.

                                              В общем, после мучений я просто выпилил Android Async Http и больше, скорее всего, не вернусь к нему.
                                              0
                                              Думал о написании подобного поста, но вы меня опередили.
                                              Внесу свои пять копеек.

                                              Для генерации content provider на основе контрактов есть отличная библиотека github.com/TimotheeJeannin/ProviGen.
                                              Да и кода получается намного меньше чем с вашим решением.

                                              Для выполнения background задач советую посмотреть в сторону решения разработчиков приложения Path
                                              github.com/path/android-priority-jobqueue.

                                              Очередь для задач, которые должны будут выполнены в фоне.
                                              Есть такие фичи, как отслеживание коннекта и выполнение задач, когда есть интернет.
                                              Приоритеты для задач. Повторное выполнение задачи, если произошла ошибка. И много других вкусняшек.
                                                0
                                                заглянул в ProviGen — чего там кода меньше? все также описываете контракт, uri, столбцы
                                                а у меня еще и вьюхи можно достаточно легко писать
                                                  0
                                                  На счет вьюх не согласен, это сильно понижает читабельность кода
                                                    0
                                                    Обожаю этот аргумент… «сильно понижает читабельность кода». Его обычно вставляют как какой-то универсальный ответ-клише, когда больше нечего сказать. Можете объяснить, что именно снижает читабельность? Сами вьюхи? Их использование? Их декларация в sqlannotation? Потому что, например, я нахожу views очень удобной вещью, которая избавляет меня от необходимости писать громоздкие конструкции в провайдере для совершения join'ов, ремаппинга проекции и прочих радостей.
                                                0
                                                Android-db-commons конечно всем хорош, но использовать его не хочется только из-за того, что тянет за собой огромную Guava.
                                                  +1
                                                  Если это единственная причина, то Proguard должен вам сильно помочь.
                                                  0
                                                  а где www.datadroidlib.com/? эта либа как раз помогает реализовывать описаную архитектуру UI-ContenProvider-Service
                                                    0
                                                    AndroidAnnotations. Мощнейший инструмент, главное не юзать Background

                                                    Не согласен. Напротив, пару аннотаций @ Background, @ UiThread считаю очень эффективной. Да, не для каждой задачи подойдёт @ Background, т.к. высок риск мемори ликов, но для задач, которые наверняка выполнятся быстро, но которые нельзя делать в UI потоке — это отличное решение. Например, запись в файл, сохранение настроек, небольшие выборки из БД и пр. Разумеется сетевые запросы не стоит делать в @ Background, для этого нужны совершенно другие подходы. Главное, понимать, как работает это всё «под капотом» и, как выразился автор поста, " включать голову".
                                                      0
                                                      А вы можете предсказать сколько займет запись файла?
                                                        0
                                                        Нет, я не могу этого сделать даже на своём телефоне, не говоря уже о множестве всех остальных. Слишком много факторов влияет на это. Но для записи файла в сотню-другую килобайт аннотация @ Background вполне подойдёт.
                                                      0
                                                      Если кому пригодится — использование плагина 'aptlibs' товарища evilduck (до всего доходил опытным путем, куря исходники плагина и разные мануалы).

                                                      buildscript {
                                                          repositories {
                                                              mavenCentral()
                                                          }
                                                          dependencies {
                                                              // добавляем плагин из репозитория, он там есть, обратите внимание, что подключение идет для buildscript 
                                                              classpath 'com.github.hamsterksu:android-aptlibs-gradle-plugin:1.0.0'
                                                          }
                                                      }
                                                      // вот мы его активируем
                                                      apply plugin: 'aptlibs'
                                                      aptlibs {
                                                          // тут можно все перечисленные в статье библиотеки подключить (annotatedSql, androidAnnotations, groundy)
                                                          // для примера подключаем annotatedSql
                                                          annotatedSql {
                                                              version '1.7.8' // ОБЯЗАТЕЛЬНО УКАЗАТЬ ВЕРСИЮ! а то ничего не заработает
                                                          }
                                                      }
                                                      

                                                      Конечно, для уважаемых товарищей evilduck и hamsterksu все понятно и по исходникам, мне же пришлось доходить довольно долго. Было бы здорово чиркануть где-то маленький примерчик на github.
                                                        0
                                                        Кто-нибудь в курсе, как можно подружить Groundy и Dagger? Есть ли какая-то возможность получить прямую ссылку на созданный GroundyTask и сделать inject dependency?
                                                          0
                                                          а зачем «получить прямую ссылку на созданный GroundyTask»? если хотите отменить таск — то надо TaskHandler который получаете после вызова queueUsing.

                                                          я обычно в каждом таске пишу статический метод запуска

                                                          public static TaskHandler start(Context context, BaseFindPrinterCallback callback) {
                                                              return Groundy.create(FindPrinterCommand.class).callback(callback).queueUsing(context);
                                                          } 
                                                          


                                                          и да, я обычно пишу базовый класс калбека для команды типа
                                                          public static abstract class BaseFindPrinterCallback {
                                                          
                                                                  @OnSuccess(FindPrinterCommand.class)
                                                                  public void onSuccess() {
                                                                      onSearchFinished();
                                                                  }
                                                          
                                                                  @OnCallback(value = FindPrinterCommand.class, name = CALLBACK_ADD_PRINTER)
                                                                  public void onAddPrinter(@Param(EXTRA_PRINTER) PrinterInfo printerInfo) {
                                                                      handleAddPrinter(printerInfo);
                                                                  }
                                                          
                                                                  protected abstract void onSearchFinished();
                                                          
                                                                  protected abstract void handleAddPrinter(PrinterInfo printerInfo);
                                                              }
                                                          

                                                            0
                                                            причем здесь callback и отмена задачи? Вопрос был про разруливание зависимостей через dependency injection. Вот, к примеру, зависит Ваш FindPrinterCommand от какого-то сервиса, или менеджера или провайдера и Вам надо этот параметр туда передать. Для примитивов и сериализуемых объектов в Groundy есть .arg(«arg_name», «foo»), а для остального я пока вижу только один выход: синглтоны и фабрики. Но все это плохо вяжется с DI философией, неудобно тестировать и тд… А не имея ссылки или доступа к конструктору объекта его невозможно включить в objectGraph и сделать inject зависимостей. Т.е. меня интересует именно связка Groundy c DI контейнером.
                                                              0
                                                              теперь понимаю задачу, из DI юзал только android annotation. так что не могу сказать как там с dagger быть.

                                                              для остального я пока вижу только один выход: синглтоны и фабрики
                                                              для всего остального есть Parcelable and Serializable.

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

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