Как стать автором
Обновить

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

Спасибо, интересно.
А не сравнивали 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?
про 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
Все верно, там можно установить свой клиент ретрофиту.
А не сравнивали retrofit и android annotations в части работы с REST? Сам android annotations для этого не пользовал, интересно услышать впечатления.


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

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

По поводу ужаса принципиально не согласен, думаю дальше спорить нет смысла.
Если честно, я не нашёл способа прикрутить Groovy к андроиду. Может я где-то не там искал?
я бы добавил еще GSON, очень удобная штука
Мне в AQuery понравилось то, что его не надо как-то специально конфигурировать.
Android Annotations иногда чудеса вытворяет — во-первых, нужно сконфигурировать IDE, во-вторых, иногда eclipse сходит с ума и не видит сгенеренные им классы. Лечится либо явным указанием пакета перед именем сгенеренного класса — либо прятаньем за статическим методом, как по тексту описывается.

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

это да, налдо руками часто указать source directory. в AS таже беда.
+1 за AQuery. Легковесная библиотека заменяющая и Android Annotations и Groundy и Retrofit на 100% да еще и добавляющая функционала на все тысячу процентов. Я уже не говорю о том, что после программирования в groovy-style на ней, возвращаться в дефолтное многословное окружение нет никакого желания. Какой смысл разбираться в куче библиотек если можно кинуть 100 килобайтный jar AQuery и наслаждаться творчеством? Как, как Вы могли не рассмотреть этот бриллиант?
ну насколько я понимаю она частично работает на рефлекшене.
+ просто руки еще не дошли, попробовали пока такую связку. под «попробовали» я имею ввиду нормальный продакшен проект.
как только поюзаю ее — постараюсь написать и о ней.
Да, рефлекшен частично используется для автоматического баиндинга-колбэка-асинктаски-куда-угодно. И это пипец как удобно. Просто она настолько проста, что сперва в голове не укладывается что одна эта библиотека заменяет десяток тяжеловесных разношерстных либ различной степени глюкавости. Единственное чего в ней нет — это работы с БД. Дефолтное кеширование основано на файловой системе, что впрочем в 99% случаев нивелирует мою потребность в БД. Обязательно попробуйте, перешел на нее с год назад, перепробовав десятка 3 различных библиотек-фреймворков, и с тех пор забыл что такое «смотреть налево» перед началом проекта. Теперь вперед и только вперед, и только с Android Query.
Скажите кто использовал, а AQuery не течет по битмапам? Учитывая то что их будет много и разных. Интересует для Android 2.2+
Нет, сама по себе не течет. Конечно нужно делать 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;
    }

}


7 картинок, как у вас в примере, это не много. Или вы это привели как демонстрицию работы с листвью? В частности интересует если тащить много фоток (500+ шт. 180х180) с интернета и кешить им, не потечет ли.
Мне кажется, для таких целей куда лучше и удобнее использовать Picasso.
Не знаете, есть ли какие-то преимущества\недостатки Picasso по сравнению с тем же universal image loader-ом? (кроме более чистого синтаксиса, это я и сам вижу :) )
Каких-то прямых сравнений, замеров производительности и т. п. не проводил, но лично отдаю предпочтение пикассо.

Пикассо действительно имеет хороший апи. Он написан и поддерживается очень крутыми чуваками из Square, сильно оптимизирован, из коробки поддерживает загрузку, трасформации, кеширование, поддержку recycle в списках, интегрируется с OkHttp от того же Square и гарантированно будет развиваться и поддерживаться. В общем, Square тут, как обычно, довольно минималистичны, в то время, как UIL более развесист. Но если вам вся эта кастомизация не нужна, если не нужно делать какой-нибудь FuzzyKeyMemoryCache, я бы отдал предпочтение пикассо.
О, какая-то лапочка поставила минусы, не утрудившись написать, почему, и с чем не согласна :) Впрочем, обычное поведение тех, кому нечего сказать на этом сайте.
Из собственного опыта:
В проекте был Universal Image Loader, затем мне понадобился кастом (хитро загружать и обрабатывать картинку). UIL сразу сдал свои позиции, был в последствии выброшен на помойку и заменен Picasso.
В чем он сдал позиции?
Код грузит из сети картинки, неужели не видно? Их может быть хоть сто, хоть миллион. Вот само приложение из идентичным кодом в адаптере, загружающее картинки, попробуйте обрушить: play.google.com/store/apps/details?id=ru.recoilme.tlen
А, пардон, проглядел. Могли бы и пожалеть нас и вырезать нужный кусок кода.
Жалею. Вот нужный кусок кода. А вот мануал: 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");
Не хочется Вас огорчать, но если записей будет сильно больше 100-200, вы рискуете вылетать с OOM на более старых девайсах даже не по причине картинок, а по-причине List содержащего JSONObject.
Не хочется Вас огорчать, но JSONObject это такой же обжект как и любой другой обжект. Не плодите сущностей сверх необходимого. Если конечно у вас конечно JSONObject ы не по 50000 байт каждый.
Я вовсе не огорчен, ведь знаю, что это не такой же обжект, как любой другой. Вы действительно думаете, что JSONObject сравним с POJO? Это «обжект», сожержащий внутри себя структуру на основе HashMap, которые имеют очень высокий memory overhead. Да и о DOM сериализации хоть сколько-нибудь внушительного json'a (которую подразумевает наличие JSONObject) я уж промолчу.
Конечно сравним. Весь список объектов редко занимает более пары сотни килобайт. И я против того, чтобы создавать класс аналогичной структры и перегонять в него json. Он же у Вас не святым духом DOM сериализуется верно? Это совершенно избыточный оверхед, за редким исключением. Хотя конечно надо смотреть по ситуациии, может быть и достаточно внушительный json, как Вы выразились. Но не в данном случае.
Все верно, у меня обычно он сериализуется не святым духом DOM, а святым духом SAX. К тому же я вообще против объектов в адаптере, я люблю курсор. Но, бывает, мне приходится складывать десяток другой тысяч записей, пришедших в виде json с сервера в базу за умеренное время и при умеренном расходе памяти.
Да я согласен с Вами. Тоже однажды была ситуация с довольно большими json (>500kb) — заюзал jackson github.com/FasterXML/jackson — в качестве SAX парсера. Но это было нужно 1 раз за всю мою жизнь)
Советую использовать Universal-Image-Loader.
Использую его в твиттер клиенте Robird.
Каждый элемент списка имеет Аватарку пользователя + иногда картинка в твите.
Все кешируется и работает просто замечательно.
я пытался описать так сказать высокоуровневые библиотеки. которые помогают делать архитектуру.
понятно что есть еще уйма мелких либ которые облегчают жизнь.
Gson — это всего лишь автоматическая сериализация и десириализация из JSON. Это один из вариантов конвертирования ответа сервера в объекты в ретрофит
Добавлю свои пять копеек. Volley может заменить и Groundy и
Retrofit для клиент-серверного взаимодействия (+ кеширование из коробки)
Ну ретрофит да — но она менее удобная. но она не может заменить сервис.
Да, сервис заменить не может. Но есть мнение, что сервис может быть overkill-решением для, например, rest-взаимодействия. А может и не быть.
мое обоснование почему я так делаю — юзер зашел на экран со списком — мы подтянули его из базу и пошли загружать свежие данные.
в этот момент юзер сворачивает приложение в ожидании загрузки например, андроид килит активити(он может это сделать) — а вместе с ней и тот тред/асинк таск который грузил инфу. юзер разворачивает приложение — активити пересоздается а инфы нет — она просто не догрузилась и все по новой.
пожтому надо юзать сервис — даже если андроид и убъет активити, сервис спокойно догрузит данные, положит их в базу и человек вернувшись в приложение увидит их.
Вы правы в том, что андроид может убивать активити, но это не значит, что запущенные из активити таска или тред тоже будут убиты, они будут висеть в памяти пока не закончат свою работу. Проблема в том, что вернуть результат будет уже некуда, т.к. активити уже убита. Прелесть volley в том, что можно хранить очередь запросов в контексте приложения (не активити) и если юзер уйдет из активити, запущенный запрос будет отработан и помещен в кеш. Когда юзер развернет прилож и стартанет эту же активити, запустится тот же самый запрос, но он сразу вернет закешированный результат
Надо пробовать, смотреть. Вот вам и джава — есть море вариантов делать одно и тоже и никто не скажет какой самый хороший.
Надо попробовать и сравнить. просто такие эксперименты делать на продакшен проекте не будешь. надо сначала на мелком прожекте. где цена ошибки не велика.

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


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

Если размер данных, которые нужно получить, не очень велик, то можно выполнить запрос тем же volley. Для загрузки большого количества данных есть свои механизмы (SyncAdapter, DownloadManager, что-нибудь еще) и тут не поможет ни volley, ни любой другой рест-клиент, ни асинктаски или их обёртки. Запись в бд или файлик на основе этих данных — это уже другая операция и реализуется в зависимости от выбранных механизмов работы с бд. Нетворкинг тут уже ни при чем.
Сервис с :remote флагом будет как отдельный процесс и выгрузка всего UI процесса не обязательно приведет к выгрузке сервиса, особенно когда он с foreground флагом.
для загрузки картинок патались юзать picasso
А почему в android annotations не надо юзать background?
как по мне, идея тредов которые захватывают UI контекст — не айс. потом могут быть мемори лики например
НЛО прилетело и опубликовало эту надпись здесь
я с опаской отношусь к ORM. главная проблема с ними — вытаскивание огромного количества данных в память. вот и все.
курсор хорош тем — что он держит cursor window на базу. и не засоряет память. а когда вы двигаетесь по списку вверх/вниз — он двигает окно.
в Android курсор грузит в память весь result set из базы, для обеспечения хождения в обе стороны. Отсюда и ограничение на 1 мб общего размера данных, которые вернул запрос из sqlite.
пруф в студию плиз.
вот тут — SQLiteCursor четко видно, что юзаеются окошки. посмотрите на метод move например
Мы с удивлением сами наткнулись сами на эту особенность в конце года. Но суть в том, cursor window растет по мере перемещения по записям и когда общий объем прочитанного превысит 1 MB, .next() будет отрабатывать как обычно, но getLong, getString, etc начнут возвращать ошибки как будто вы указали недействительный индекс колонки. Посмотрите поиском android 1 mb sqlite limit на том же stackoverflow
ну cursor window не должен расти, может вы не закрываете курсоры? или вылазит просто при пролистовании списка?
именно простой запрос вида
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.

А мне почему-то для различного рода сетевого взаимодействия больше понравился Android Async Http (http://loopj.com/android-async-http/). Код получается очень компактным, понятным и доки написаны очень грамотно.
Сначала тоже смотрел в сторону Retrofit, но не нашел с ходу в доке, как там извлекать заголовки из полученных с сервера данных (хотя, вероятно, просто плохо искал).
У 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 и больше, скорее всего, не вернусь к нему.
Думал о написании подобного поста, но вы меня опередили.
Внесу свои пять копеек.

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

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

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

Не согласен. Напротив, пару аннотаций @ Background, @ UiThread считаю очень эффективной. Да, не для каждой задачи подойдёт @ Background, т.к. высок риск мемори ликов, но для задач, которые наверняка выполнятся быстро, но которые нельзя делать в UI потоке — это отличное решение. Например, запись в файл, сохранение настроек, небольшие выборки из БД и пр. Разумеется сетевые запросы не стоит делать в @ Background, для этого нужны совершенно другие подходы. Главное, понимать, как работает это всё «под капотом» и, как выразился автор поста, " включать голову".
А вы можете предсказать сколько займет запись файла?
Нет, я не могу этого сделать даже на своём телефоне, не говоря уже о множестве всех остальных. Слишком много факторов влияет на это. Но для записи файла в сотню-другую килобайт аннотация @ Background вполне подойдёт.
Если кому пригодится — использование плагина '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.
Кто-нибудь в курсе, как можно подружить Groundy и Dagger? Есть ли какая-то возможность получить прямую ссылку на созданный GroundyTask и сделать inject dependency?
а зачем «получить прямую ссылку на созданный 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);
    }

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

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

Публикации

Истории