Что такое binder android
Русские Блоги
Подробное объяснение Binder в Android
Введение в Binder
Поскольку Binder играет более важную роль в передаче информации Android, для записи анализа Binder написана отдельная статья.
Что такое Binder
Binder, что переводится как связующее, часто появляется в знаниях о межпроцессном взаимодействии Android. Вообще говоря, Binder обычно имеет следующие объяснения:
Заимствовать великого богаCarson_HoИзображение для его представления выглядит следующим образом:
В сочетании с приведенным выше изображением каждый должен иметь более четкое определение Binder.
Сценарии использования Binder
Анализ структуры связующего
Отношения между AIDL и Binder
После записи файла AIDL система сгенерирует файл java, который наследует интерфейс IInterface во время сборки. Это имя файла совпадает с соответствующим именем файла AIDL. В этом файле есть внутренний класс Stub, это класс Binder.
Так что можно считатьAIDL помогает системе сгенерировать соответствующий файл Binder.
Создать файл Binder AIDL
Затем мы пишем файл AIDL. Содержимое файла AIDL находится в области кода ниже. Если вы хотите понять весь процесс AIDL, вы можете обратиться кЭта статья
Затем должен быть тот, который реализует интерфейс Parcelable в каталоге java.Book.javaкласс
После того, как указанные выше файлы будут записаны, соответствующий файл Binder будет немедленно сгенерирован в AndroidStudio, расположение находится в каталоге проекта. /build/generated/source/aidl/debug/ в. Конкретный путь показан на рисунке:
Если не build Как только сообщается об ошибке, вам нужно еще раз проверить, правильно ли написан помощник или указан правильный путь. В AndroidStudio правильная структура каталогов выглядит следующим образом:
Общая структура Binder
После получения соответствующего файла Binder давайте посмотрим на общую структуру Binder:
Детальный разбор методов в Binder
Сначала рассмотрим различные методы в Stub
Помимо метода построения, в Stub есть методы. asBinder() 、 asInterface() с участием onTransact метод. В дополнение к этим трем методам есть два упомянутых выше статических идентификатора для идентификации метода, вызываемого клиентом.
среди них asBinder() Метод эквивалентен методу get, используемому для возврата текущего объекта Binder, этот код относительно прост, мы его пропускаем.
Затем смотрим asInterface() метод:
Тогда посмотрите метод в клиентском прокси Stub.Proxy
Основной метод здесь addBook с участием getBookList Метод, на самом деле, эти два метода все же имеют некоторое сходство, вот анализ getBookList метод
addBook Процесс метода почти такой же, как и выше, вы можете проверить его в полном коде в конце.
Наконец, посмотрите на метод в IBookManager
За исключением двух внутренних классов выше, оставшийся код имеет только два абстрактных интерфейса, то есть методы интерфейса, определенные в IBookManager.aidl. Об этих двух методах нечего сказать, в основном они используются для наследования или реализации Stub и Stub.Proxy.
Полный код вышеуказанного Binder
Здесь прилагается полный объект Binder, чтобы каждый мог провести общее исследование:
Пишем приложения с поддержкой плагинов для Android. Часть 1
Содержание статьи
Приложения с расширяемой функциональностью — обыденная вещь в настольных ОС. Большинство сложных, узкоспециализированных приложений позволяют подключать дополнения в виде плагинов или скриптов. Это удобная, важная и легко реализуемая функция, которая в простейшем случае может быть встроена в приложение с помощью нескольких строк, всего лишь загружающих внешнюю библиотеку. Но как с этим обстоят дела в Android, где каждое приложение замкнуто в свою собственную песочницу, а распространять «просто библиотеки» через маркет нельзя?
Введение
Есть как минимум три способа создать приложение с поддержкой плагинов для Android.
Первый способ заключается в том, чтобы встроить в приложение интерпретатор простого скриптового языка (Lua лучше всего годится на эту роль). Главная фишка этого способа — простота разработки плагинов. Но есть и ряд недостатков:
В общем, не самый удачный, но имеющий право на существование способ.
Второй способ основан на встроенном в среду исполнения механизме динамической загрузки классов. Он позволяет разбить приложение на множество модулей, которые будут подгружены прямо во время его работы. По сути этот способ очень похож на тот, что используется в приложениях для настольных ОС, и имеет те же недостатки:
Тем не менее модули могут быть очень полезны при разработке разного рода бэкдоров и троянов (в образовательных целях, естественно), поэтому тему разработки модульных приложений, основанных на прямой загрузке классов, мы все-таки рассмотрим, но в следующий раз. А сегодня поговорим о третьем способе, самом удобном и полностью соответствующем идеологии Android. Это плагины, подключаемые с помощью RPC-механизма Binder.
DashClock: одно из самых известных приложений с поддержкой плагинов
Xakep #208. Атака на сигналку
Плагины и Binder
Binder — это механизм, с помощью которого в Android реализована система обмена сообщениями и вызова методов между приложениями и системными компонентами. Подробно о Binder я писал в статье, посвященной логированию звонков, однако рассказал только об обмене данными с его помощью. Если же мы хотим применить его для реализации плагинов, нам необходимо разобраться, как использовать возможность удаленного вызова процедур (RPC). Но сначала определимся с архитектурой нашего приложения.
Допустим, у нас есть гипотетическая софтина, к которой нужно прикрутить поддержку плагинов. Каждый плагин должен быть полноценным приложением для Android (с собственным графическим интерфейсом или без, на усмотрение разработчика), так что его тоже можно будет распространять через маркет. Плагины должны поддерживать определенный нами API, с помощью которого приложение сможет вызвать его функции. Приложение должно уметь само находить установленные плагины и добавлять их в список.
С учетом сказанного нам необходимо внести в код приложения следующие изменения:
Самый удобный и простой способ реализации плагинов — в виде сервисов, запускаемых по запросу. Наше приложение будет находить установленные в системе приложения-плагины, в нужные моменты запускать реализованные в них сервисы и вызывать их функции. При этом сервисы будут запущены только тогда, когда они действительно нужны, а система сама позаботится об их завершении и менеджменте ресурсов. Именно так, кстати, работает система плагинов виджета DashClock и многих других приложений.
Вызов функций будет происходить с помощью Binder, а это, как я уже сказал, RPC-механизм, и он требует описания API (интерфейса) с каждой из сторон (приложения и плагинов) с помощью языка AIDL (Android Interface Definition Language). Сам AIDL очень прост, и описание интерфейса на нем почти ничем не отличается от Java. Для нашего примера создадим простой интерфейс, определяющий две функции: run() и name() :
Создай файл IPlugin.aidl с помощью New → AIDL → AIDL file и помести в него эти строки. Затем выполни Build → Make Project, чтобы Android Studio преобразовал AIDL в обычный код на Java.
Простейший плагин
Теперь реализуем сам плагин. Для этого создаем новый проект (пусть его имя будет com.example.plugin1 ), добавляем в него файл IPlugin.aidl (обрати внимание, что он должен точно совпадать с аналогичным файлом из предыдущего раздела) и файл Plugin.java со следующим содержимым:
Поиск плагинов
Теперь нам необходимо реализовать систему поиска установленных плагинов. Проще (и правильнее) всего сделать это с помощью интентов, о которых можно прочитать в упомянутой выше статье. Для этого сначала внесем изменения в файл Manifest нашего плагина, добавив в него следующие строки (в раздел application):
Сам механизм поиска плагинов реализовать довольно просто. Для этого достаточно обратиться к PackageManager с просьбой вернуть список всех приложений, отвечающих на интент com.example.action.PLUGIN :
Чтобы с плагинами было удобнее работать, создадим HashMap и поместим в него имена приложений-плагинов в качестве ключей, а имена их сервисов — в качестве значений:
Запуск функций плагина
В общем, вариантов масса, главное — запомнить, что перед запуском нового плагина необходимо отключаться от предыдущего. Все остальное Android сделает сам: при подключении к плагину запустит сервис, передаст ему управление, а затем завершит сервис при отключении. Никакого лишнего оверхеда на систему, никаких чрезмерных расходов оперативки, даже за поведением плагинов следить не надо, в случае если один или несколько из них начнут грузить систему или выжирать оперативку — система их прибьет.
И еще одна важная деталь. Функции плагина вызываются синхронно, то есть в нашем случае при выполнении plugin.run(2) приложение будет заморожено на две секунды. По-хорошему тут нужно выполнять запуск функции в отдельном потоке, а затем отправлять результат исполнения в основной поток.
Выводы
Как видишь, реализовать приложение с поддержкой плагинов для Android не так уж и сложно. Более того, операционка даже поощряет создание модульных приложений, которые будут передавать управление друг другу, вместо громоздких софтин «все в одном». Главное — научиться пользоваться встроенными в ОС инструментами и понять ее философию.
Евгений Зобнин
Редактор рубрики X-Mobile. По совместительству сисадмин. Большой фанат Linux, Plan 9, гаджетов и древних видеоигр.
Основы безопасности операционной системы Android. Безопасность на уровне Application Framework. Binder IPC
Вступление
После небольшого перерыва я продолжаю объяснять базовые принципы как обеспечивается безопасность в операционной системе Android. Сегодня я начну описывать безопасность на уровне Application Framework. Но чтобы понять данную тему, вначале необходимо рассмотреть как в Android реализован механизм межпроцессного взаимодействия (Inter-Process Communication (IPC)). Этот механизм называется Binder IPC, и сегодня мы будем рассматривать его особенности. Все, кому интересно, добро пожаловать!
Список статей
Зачем нужен Binder IPC?
Как я уже писал в первой статье цикла, каждое приложение в Android выполняется в своей собственной «песочнице» (Application Sandbox). Механизм «песочницы» в Android основан на присвоении каждому приложению уникального user ID (UID) и group ID (GID), таким образом каждому приложению (application или app) в этой операционной системе соответсвует свой уникальный непривилегированный пользователь. Мы рассматривали эту тему в первой статье цикла. В тоже время владельцами всех критических ресурсов системы являются более привилегированные пользователи, имена и идентификаторы которых жестко зашиты в систему (см. system/core/include/private/android_filesystem_config.h). Системные сервисы, которые запускаются от имени этих пользователей, имеют доступ к соответствующим критическим ресурсам системы (например, к GPS данным), в то время как процессы обычных приложений доступ к этим ресурсам получить не могут.
Однако приложения должны иметь возможность «попросить» системные сервисы предоставить им такую информацию, а последние, в свою очередь, должны иметь возможность предоставить такую информацию приложениям. Так как приложения и системные сервисы исполняются в разных процессах, то для организации такого обмена операционной системе необходимо предоставить механизм обмена информацией между процессами. Более того, даже обычные приложения иногда должны взаимодействовать друг с другом. Например, почтовый клиент может «попросить» просмотрщик изображений отобразить графическое приложение к письму.
Как работает Binder IPC?
Взаимодействие между процессами организовано по синхронной клиент-серверной модели. Клиент инициирует соединение и ждет ответа со стороны сервера. Таким образом, взаимодействие между клиентом и сервером происходит последовательно. Такой дизайн позволяет разработчику вызывать удаленные методы словно они находятся в том же самом процессе. Стоит отметить, что это не рассходится с тем, что я писал выше по поводу асинхронного вызова методов: в случае асинхронного вызова, сервер сразу же возвращает пустой ответ клиенту. Схема взаимодействия процессов через Binder представлена на следующем рисунке: приложение (клиент), которое выполняется в процессе Process A, получает доступ к функцианальности, реализованной в сервисе, который выполняется в процессе Process B.
Все взаимодействия между клиентом и сервером в рамках Binder происходят через специальный Linux device driver (драйвер устройства) /dev/binder. В статье «Основы безопасности операционной системы Android. Native user space, ч.1» мы рассматривали процесс загрузки системы. Один из первых демонов, запускаемых процессом init, является ueventd — менеджер внешних устройств в Android. Этот сервис во время старта читает конфигурационный файл ueventd.rc и проигрывает события добавления внешних устройств. Эти события выставляют для устройств разрешения (permissions), а также владельца (owner) и группу (owning group). В следующем примере можно увидеть какие разрешения выставлены для /dev/binder.
Как можно заметить, разрешения для этого устройства выставлены в 0666. Это означает, что любой пользователь системы (а мы помним, что разные приложения в Android — это уникальные пользователи) имеет право писать в и читать из данного устройства. Для взаимодействия с этим драйвером была создана библиотека libbinder. Эта библиотека позволяет сделать процесс взаимодействия с драйвером прозрачным для разработчика приложений. В частности, взаимодействие между клиентом и сервером происходит через proxy (прокси) на стороне клиента и stub на стороне сервера (см. рисунок выше). Proxies и Stubs отвечают за маршалинг данных и комманд передаваемых через драйвер. Т.е. Proxy выстраивает (marshalling) данные и команды, полученные со стороны клиента таким образом, что они могут быть корректно прочитаны (unmarshalling) и однозначно поняты Stub’ом. Вообще, разработчики приложений даже не пишут Stub’ы и Proxy’и для своих приложений. Вместо этого они используют интерфейс, описанный с помощью языка AIDL. Во время компиляции приложения, на основании этого интерфейса генерируется код для Stub’ов и Proxy’и (можете поискать в своих проектах, чтобы увидеть как они выглядят).
На стороне сервера для каждого клиентского запроса создается отдельный Binder поток (см. рисунок выше). Обычно эти потоки называются как Binder Thread #n. Кстати, если мне не изменяет память, то максимальное количество Binder потоков равно 255, а максимальный размер данных, которые могут быть переданы через Binder в рамках одной транзакции составляет 1Mb. Это следует иметь ввиду, когда разрабатываете приложения, передающие или получающие данные от других процессов.
Service Manager
Внимательные читатели должны были отметить для себя тот факт, что до этого я ничего не писал о том, как Android понимает, какому процессу нужно отсылать данные. Технически каждому сервису Binder присваивает 32 битный токен, являющимся уникальным в рамках всех процессов системы (за чем следит драйвер). Этот токен используется как указатель на сервис. Если у клиента есть этот указатель, то он может взаимодействовать с сервисом. Но сначала клиенту необходимо получить это уникальное значение.
Для этого клиент обращается к Binder context manager, который в случае Android называется Service Manager (servicemanager). Service Manager — специальный сервис, Binder токен которого известен всем заранее. Не удивительно, что значение токена для этого сервиса равно 0. Binder драйвер разрешает регистрацию только одного сервиса с таким токеном, поэтому servicemanager — один из первых сервисов, запускаемых в системе. Service Manager можно представить в виде справочника. Вы говорите, что хотите найти сервис с таким-то именем, а вам в ответ возвращают его уникальный токен-номер, который вы можете использовать после для взаимодействия с искомым сервисом. Естественно, сервис сперва должен зарегистрироваться в этом «справочнике».
Безопасность
Сам по себе Binder фреймворк не отвечает за безопасность в системе, но он предоставляет механизмы для её обеспечения. Во-первых, Binder драйвер в каждую транзакцию записывает PID и UID процесса, который инициирует транзакцию. Вызываемый сервис может использовать эту информацию чтобы решить, стоит ли выполнять запрос или нет. Вы можете получить эту информацию используя методы android.os.Binder.getCallingUid(), android.os.Binder.getCallingPid(). Во-вторых, так как Binder токен уникален в рамках системы и его значение не известно априори, то он сам может использоваться как маркер безопасности (смотрите, например, вот эту статью).
Заключение
В следующей части планирую написать о разрешениях (permissions). Материал уже есть, но надо его перевести и подработать. Как всегда буду благодарен за интересные вопросы и пояснения в комментариях, возможно некоторые моменты я неправильно для себя понял.
Русские Блоги
Android8.0 Binder-ориентированные системные сервисы (1)
Проблема фрагментации Android всегда была основной проблемой обновления ОС. Google представила Treble в Android 8.0 для решения долгосрочной проблемы серьезной фрагментации в Android. Treble планирует расширить Binder в треугольную структуру, соответствующую dev / binder, dev / vndbinder, dev / hwbinder, среди них binder и vndbinder совместно используют код посредством разграничения контекста, hwbinder независим, до Android 8.0 уровни Framework и HAL находятся в одном процессе, и производители оборудования должны выполнить большую работу по адаптации для каждого обновления системы Android8.0 разделяет Framework и HAL на два процесса и в то же время расширяет Binder в треугольную структуру для совместимости со старыми версиями системы до Android8.0.Кроме того, уровень HAL совместим со старым драйвером через режимы Binderized и passthrough. Кроме того, он также представлен Чтобы упростить сложность разработки языка HIDL, я должен сказать, что сила исследований и разработок Google слишком сильна.
В предыдущей статье мы упоминали, что система Android запустит SystemServer на заключительном этапе инкубации. Система Android делит систему на модель CS. Затем система должна управлять таким количеством услуг и предоставлять такие клиенты, как услуги. Binder играет важную роль. Система Android основана на ядре Linux.В Linux уже есть много методов межпроцессного взаимодействия, таких как разделение памяти, семафоры, сокеты и другие средства, так почему Android использует Binder? Среди следующих соображений: 1. Эффективность, межпроцессному взаимодействию Binder необходимо копировать данные только один раз в памяти, 2. Объектно-ориентированный, Android использует JAVA в качестве самого верхнего каркаса системы для компиляции языка, и идеи дизайна Binder просто подходят; 3. Система Android Модель CS, служба системы управления архитектурой Binder и предоставление удаленной справки для Клиента, Brillent! Удивительно! Давайте ненадолго пропустим управляемый связыванием дизайн и посмотрим, как архитектура Binder уровня структуры обеспечивает базовую поддержку системы.
1. Сервис менеджер
После запуска системного менеджера службы он зарегистрирует контекст с handle = 0 для драйвера BINDER, а затем обработает запрос от клиента через цикл.
Полный список
— используем биндинг для подключения к сервису
В прошлых уроках мы общались с сервисом асинхронно. Т.е. мы отправляли запрос через startService, а ответ нам приходил когда-нибудь потом посредством PendingIntent или BroadcastReceiver.
Но есть и синхронный способ взаимодействия с сервисом. Он достигается с помощью биндинга (binding, я также буду использовать слово «подключение»). Мы подключаемся к сервису и можем взаимодействовать с ним путем обычного вызова методов с передачей данных и получением результатов. В этом уроке передавать данные не будем. Пока что разберемся, как подключаться и отключаться.
Как вы помните, для запуска и остановки сервиса мы использовали методы startService и stopService. Для биндинга используются методы bindService и unbindService.
Создадим два Application. В одном будет приложение, в другом сервис.
Создадим первый проект:
Project name: P0971_ServiceBindClient
Build Target: Android 2.3.3
Application name: ServiceBindClient
Package name: ru.startandroid.develop.p0971servicebindclient
Create Activity: MainActivity
Добавим в strings.xml строки:
4 кнопки: для запуска, остановки и биндинга сервиса
В onCreate мы создаем Intent, который позволит нам добраться до сервиса.
Объект ServiceConnection позволит нам определить, когда мы подключились к сервису и когда связь с сервисом потеряна (если сервис был убит системой при нехватке памяти). При подключении к сервису сработает метод onServiceConnected. На вход он получает имя компонента-сервиса и объект Binder для взаимодействия с сервисом. В этом уроке мы этим Binder пока не пользуемся. При потере связи сработает метод onServiceDisconnected.
Переменную bound мы используем для того, чтобы знать – подключены мы в данный момент к сервису или нет. Соответственно при подключении мы переводим ее в true, а при потере связи в false.
Далее идут обработчики кнопок. В onClickStart мы стартуем сервис, в onClickStop – останавливаем.
В onClickBind – соединяемся с сервисом, используя метод bindService. На вход передаем Intent, ServiceConnection и флаг BIND_AUTO_CREATE, означающий, что, если сервис, к которому мы пытаемся подключиться, не работает, то он будет запущен.
В onClickUnBind с помощью bound проверяем, что соединение уже установлено. Далее отсоединяемся методом unbindService, на вход передавая ему ServiceConnection. И в bound пишем false, т.к. мы сами разорвали соединение. Метод onServiceDisconnected не сработает при явном отключении.
Создадим второй проект, без Activity:
Project name: P0972_ServiceBindServer
Build Target: Android 2.3.3
Application name: ServiceBindServer
Package name: ru.startandroid.develop.p0972servicebindserver
Создаем сервис MyService.java:
Методы onCreate и onDestroy нам знакомы – они вызываются при создании и уничтожении сервиса. А onBind, onRebind и onUnbind используются при биндинге и мы далее будем смотреть как именно. Метод onStartCommand не используем.
В методе onBind возвращаем пока объект-заглушку Binder. В этом уроке он не будет использован.
Прописываем сервис в манифесте и настраиваем для него IntentFilter с Action = ru.startandroid.develop.p0972servicebindserver.MyService.
Все сохраняем, инсталлим сервис и запускаем приложение.
На самом экране ничего происходить не будет. Все внимание на логи.
Попробуем подключиться к неработающему сервису. Жмем Bind. В логах:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Сервис создался и сработал его метод onBind. Также сработал метод onServiceConnected в ServiceConnection, т.е. Activity теперь знает, что подключение к сервису установлено.
Попробуем отключиться. Жмем Unbind.
MyService onUnbind
MyService onDestroy
Сработал метод Unbind в сервисе и сервис закрылся. Т.е. если мы биндингом запустили сервис, он будет жить, пока живет соединение. Как только мы отключаемся, сервис останавливается. onServiceDisconnected не сработал, т.к. мы сами отключились.
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Теперь в процессах убиваем процесс сервиса
Сработал метод onServiceDisconnected объекта ServiceConnection. Тем самым Activity уведомлено, что соединение разорвано.
Теперь немного подождем (у меня это заняло 5 секунд) и в логах появляются строки:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Сервис запустился и соединение восстановилось. Очень удобно.
Жмем Unbind и отключаемся.
Попробуем снова подключиться к незапущенному сервису, но уже без флага BIND_AUTO_CREATE. Перепишем onClickBind в MainActivity.java:
Вместо флага BIND_AUTO_CREATE мы написали 0.
Сохраняем, запускаем. Жмем Bind. В логах ничего. Мы убрали флаг, сервис сам не создается при подключении.
Давайте запустим его методом startService. Жмем Start. В логах:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Сервис создался и приложение подключилось к нему. Т.е. попыткой биндинга мы оставили некую «заявку» на подключение, и когда сервис был запущен методом startService, он эту заявку увидел и принял подключение.
Отключились от сервиса. Но сервис продолжает жить, потому что он был запущен не биндингом, а методом startService. А там уже свои правила закрытия сервиса. Это мы проходили в прошлых уроках.
Сервис завершил работу.
Попробуем запустить сервис методом startService и, пока он работает, несколько раз подключимся и отключимся. Жмем Start.
Подключаемся и отключаемся, т.е. жмем Bind
MyService onBind
MainActivity onServiceConnected
Сработали методы onBind и onUnbind в сервисе, и onServiceConnected в ServiceConnection.
При повторном подключении к сервису методы onBind и onUnbind не сработали. Только onServiceConnected.
И далее, сколько бы мы не подключались, так и будет.
Остановим сервис – нажмем Stop.
Это поведение можно скорректировать. Для этого необходимо возвращать true в методе onUnbind. Сейчас мы там вызываем метод супер-класса, а он возвращает false.
Перепишем метод Unbind в MyService.java:
Сохраним и инсталлим сервис. Жмем Start, а затем жмем поочередно Bind и Unbind, т.е. подключаемся и отключаемся. Смотрим логи:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
MyService onUnbind
Сервис создан, подключились и отключились. Продолжаем подключаться и отключаться.
MyService onRebind
MainActivity onServiceConnected
MyService onUnbind
MyService onRebind
MainActivity onServiceConnected
MyService onUnbind
Последующие подключения и отключения сопровождаются вызовами методов onRebind и onUnbind. Таким образом, у нас есть возможность обработать в сервисе каждое повторное подключение/отключение.
Вот примерно такой Lifecycle имеет биндинг сервиса. Разумеется, я рассмотрел не все возможные комбинации запуска методов startService, stopService, bindService и unbindService. Оставляю это вам, если есть интерес. Я рассмотрел только типичные случаи.
Также я не рассматривал возможность одновременного подключения нескольких приложений или сервисов к одному сервису. А такая возможность существует. В этом случае, если сервис запущен биндингом, то он будет жить, пока не отключатся все подключившиеся.
На следующем уроке:
— обмен данными в биндинге
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме