По-явански
رفتن به کانال در Telegram
Пишу про Java-технологии, Kotlin и Android-разработку. Редко но метко. Кодревью: http://javanese.online/%D1%80%D0%B0%D0%B7%D0%B1%D0%BE%D1%80_%D0%BA%D0%BE%D0%B4%D0%B0/ Поболтать IRL: @coffeeCodeEverywhere Автор: @Harmonizr Менеджер по рекламе: /dev/null
نمایش بیشتر827
مشترکین
اطلاعاتی وجود ندارد24 ساعت
-17 روز
+530 روز
آرشیو پست ها
827
Редактируемый список —
EditText внутри RecyclerView
Очень много лулзов на StackOverflow по этому вопросу. Написать адаптер с эдиттекстами под силу каждому, а вот получить назад отредактированные данные могут не только лишь все.
Рассмотрим для начала очевидно нерабочий способ: for-each по вьюшечкам. Как ни странно, я видел такое решение в продакшене. Чтобы оно работало, ресайклер был завёрнут в скроллвью!
Следующий сомнительный вариант: нацепить TextWatcher и записывать результат Editable.toString() на каждый тык пользователя по клавиатуре.
Название Editable, кстати, намекает нам на мутабельность редактируемого буфера. Так возникает ещё одна стратегия — дёрнуть Editable getText(), а не слушать каждое изменение. Тогда байндинг будет выглядеть так:
editText.setText(editables[position] ?: strings[position])
editables[position] = editText.text
EditText создаст мутабельную копию строки. А можно сделать это самостоятельно, потому что в editables уже может лежать нужный буфер. Только нужно отвадить EditText от копирования, заменив Editable.Factory.
Итоговый gist и ответ на SO.827
Недавно меня посетила красивая #идея — вот бы приложения карт умели показывать слои, предоставленные другими приложениями! Открываешь Organic, 2GIS или что там сейчас есть из картографии — а туда сразу подтягиваются такси, самокаты, каршеринг, общественный транспорт, парковки — в зависимости от того, какие провайдеры установлены. Философия UNIX во всей красе.
Как и большинство других идей, я не первый, к кому они приходят. Например, есть вот такая закрытая/коммерческая интеграция. А хочется — публичный интерфейс для всех.
827
Интерфейсы коллекций
Вчера задали мне этот вопрос на собесе. Ну я и перечисляю:
Iterable, Collection, List, Set, SortedSet, NavigableSet, Queue, Deque; Map, ConcurrentMap — тут остановился в ожидании реакции собеседующего. Глазами хлопаю, мол, что, надо ещё?
Оказывается, это почти всё. Для пущего эффекта можно выучить: BlockingQueue, TransferQueue, BlockingDeque; SortedMap, NavigableMap, ConcurrentNavigableMap.827
Опаньки, оказывается, из типового параметра можно убрать нуллабельность (подсветка синтаксиса тоже удивлена).
Источник
827
Repost from Android Broadcast
🔴 Стартует собеседование в прямом эфире
Проводит Михаил Горюнов @Harmonizr, разработчик Android-приложений, библиотек и инструментов.
Миша будет собеседовать на позицию Android разработчика без уровня (требования здесь)
P.S. Запись останется на канале
#AndroidBroadcast #собеседование
827
Repost from Android Broadcast
🔴 Стартует собеседование в прямом эфире
Проводи Михаила Горюнова @Harmonizr, разработчик Android-приложений, библиотек и инструментов.
Миша будет собеседовать на позицию Android разработчика без уровня (требования здесь)
P.S. Запись останется на канале
#AndroidBroadcast #собеседование
827
Парсинг ботокоманд
/add_thing Name Description /add_thing Name 'Long description'Как распарсить эти команды и не сойти с ума? Внутри JDK периодически используется
StringTokenizer, но это просто String.split() образца 1995 года. А нужный класс, который понимает кавычки, называется StreamTokenizer, и, как следует из названия, он ещё и стрим разобрать может.827
Repost from Android Broadcast
Сбор заявок на участие в собеседовании
Мы ищем кандидата, кто пройдет в прямом эфире собеседование на вакансию, идентичную реальной, в прямом эфире у Михаила Горюнова @Harmonizr , разработчика Android-приложений, библиотек и инструментов.
Миша будет собеседовать на позицию Android разработчика (речь не идет про уровень или года опыт), а с требованиями можно ознакомиться здесь
@android_broadcast #собеседование
827
Transient Iterator
Что может выстрелить в этом кусочке кода:
map.entries.toSet()? А вот что: entrySet().iterator().next() возвращает себя — этот же итератор, который впридачу реализовывает Map.Entry. В итоге из энтрей непустой Map всегда получается Set из единственного Entry.
С одной стороны, это жёсткая подстава. С другой — экономный способ просмотреть любое количество элементов за константную память. Поэтому в тех местах, где это может быть полезно, я предоставляю оба варианта — «нормальный» итератор и «мимолётный». Так же можно поступить с итератором по строкам файла: не возбраняется фильтровать, искать префиксы или какие-то символы, а потом уже выгружать в память необходимое подмножество элементов.827
Эффективное ООП
это ваше ООП неэффективно, объекты нинужны, они только память тратят. Нужна функция file_count_lines(path)! абстракция либо неверно выбрана, либо коряво реализована. Рассмотрим оба варианта.
Другая абстракция:
Files.readAllLines(path).size
Страшно? Жутко? Жизнь — боль? Вывод: Iterator<CharSequence> & Closeable. Файл проматывается по мере доступа к элементам. CharSequence можно выгрузить в память методом toString(), до этого он просто помнит отступ и длину в файле, а charAt() заглядывает в mmap.
Некорявая реализация: List<CharSequence> & Closeable. Написать такой лист сильно сложнее, чем итератор, но нет ничего невозможного:
• size при первом вызове проматывает файл и строит индекс — складывает позиции всех \n в массив интов — и возвращает его размер,
• get(at) проматывает файл до нужного места, достраивая по пути индекс, если он неполный.
Естественно, оба метода используют один и тот же индекс и умеют достраивать его по мере необходимости.
Гипотетический Files.listOfLines(path).forEach() пробегает итератором по файлу ровно один раз.
Самый тяжёлый сценарий, Files.listOfLines(path).map(CharSequence::toString), приведёт к накладным расходам в виде промежуточных CharSequence и индекса, но это копейки.827
Приняв во внимание обратную связь сообщества, редакция приняля решение прекратить ведение канала на неопределённый срок.
827
Throwable — волшебный класс!
fillInStackTrace(): запомнить текущий стек.
Некоторые создают эксепшон и сразу же вызывают этот метод. Это бесполезно, он ведь и так вызывается в конструкторе.
А вот в каком-нибудь другом месте можно навязать любому эксепшону текущий стек. Удачной отладки!
Метод, кстати, нефинальный. Можно переопределить его и сделать no-op, получив дешёвый эксепшон без стека.
getMessage(), getCause(), printStackTrace(), toString() тоже нефинальные. Если хочется пострелять с двух рук, можно переопределить их и бросить оттуда новый exception.
setStackTrace(): вообще вседозволенность. Да, можно использовать его в благих целях, дабы прятать интринсики языка, а можно приколоться над коллегами!827
Сравнительная характеристика JDBC и Android SQLite
API SQLite в Android сильно отличается от привычного для джавистов JDBC. Вот краткий справочник, что как называется и каким образом себя ведёт.
Помощник для миграции
В Android мы получаем базу данных от
SQLiteOpenHelper, где есть коллбэки для миграции. В JDBC такой штуки нет, зато есть flywaydb.org и liquibase.org.
База данных
Отправная точка в Android называется SQLiteDatabase, в JDBC — DataSource, зачастую из HikariCP.
Соединение
JDBC: DataSource#getConnection() → Connection.
В Android же соединения спрятаны от нас, под капотом реализован пул.
Готовимся выполнить запрос
JDBC: Connection#createStatement() → Statement
Android: неть, не готовимся.
Выполнить запрос
JDBC: Statement#execute(String) → ResultSet
Android: SQLiteDatabase#(raw)Query(…) → Cursor
Таким образом, Android избавляет нас от получения коннекшона и создания стейтмента, мы получаем курсор на руки буквально вызовом одного метода.
// JDBC
hikariPool.connection.use { conn ->
conn.createStatement().use { stmt ->
stmt.execute("SELECT …").use { rs ->
TODO()
}
}
}
// Android SQLite
db.rawQuery("SELECT …").use { cur ->
TODO()
}
Чтение
ResultSet и Cursor очень похожи, но в деталях есть разница. Например, ResultSet не знает своего размера.
И нужно не забыть закрыть Statement заодно с ResultSet.
Транзакции
Android SQLiteDatabase:
beginTransaction(NonExclusive)() setTransactionSuccessful() endTransaction()JDBC
Connection:
setAutoCommit(false) commit() || rollback() setAutoCommit(true)Да, слово «транзакция» здесь вообще не фигурирует. Кстати, в одном из предыдущих постов у меня потекла транзакция именно потому что я забыл вернуть autoCommit. Мутации Для них удобнее всего подходят подготовленные запросы. JDBC:
Connection#prepareStatement(SQL) → PreparedStatement
Android: SQLiteDatabase#compileStatement(SQL) → SQLiteStatement
Тут принципиальной разницы нет.
Вре́менные таблицы и триггеры
Вот здесь спряталась хитрость. В любой СУБД временные таблицы существуют только в пределах соединения, их создавшего. Именно в SQLite есть ещё один нюанс — триггеры тоже локальны для соединения ввиду отсутствия сервера. В Android же это ограничение не действует, как если бы SQLiteDatabase оперировала лишь одним соединением. InvalidationTracker в Room тоже написан таким образом, будто этого органичения не существует. Почему оно так работает? Для меня это открытый вопрос.827
О сколько нам открытий чудных готовит спека DNS!
Держу в курсе: доменное имя без точки в конце считается неполным, относительным. В эпоху умерших интранетов и локальных сетей об этом уже никто не вспомнит…
Обнаружено в процессе чтения исходников OkHttp.
https://webmasters.stackexchange.com/questions/73934/how-can-urls-have-a-dot-at-the-end-e-g-www-bla-de
827
OOM Resurrector
У меня на сервере постгрес раз в неделю съедает всю доступную память и своп. Настройки крутил, всё уменьшал, но нет никакого аналога нашего
-Xmx, поэтому и нет гарантии, что постгря не падёт жертвой ООМ-киллера.
В интернете есть скрипт OOM-воскресителя, прикладываю свою версию, которая ориентируется на RAM+swap.
#!/bin/bash
THRESHOLD=1000 # MB
INTERVAL=60 # s
while :
do
free=$(free -tm | awk '/^Total:/{print $4}')
echo "Free $free""MB"
if [ "$free" -lt $THRESHOLD ]
then
systemctl restart postgresql@14-main
echo '$ systemctl restart postgresql@14-main'
fi
sleep $INTERVAL
done
Команда:
nohup ./pg-resurrect.sh > pg-resurrect.log &827
Какой алгоритм решит мою задачу?
У нас есть один язык, который самостоятельно выбирает наиболее подходящие алгоритмы и структуры данных из имеющихся. Этим студентом был SQL. Если повезёт, задача выбора алгоритма сведётся к написанию SQL-запроса, а вывод
EXPLAIN прямым текстом назовёт нужный алгоритм.827
Пол раза в жизни встречал
ViewModel + MutableStateFlow. Попросили объяснить, как использовать. Чем дальше в лес, тем меньше понимания, как и зачем этим вообще пользоваться и под какими веществами это придумали 🤦
Ожидание:
val vm by viewModels(factory = { MyCoolViewModel(args) })
Реальность:
val vm: MyCoolViewModel by viewModels(factoryProducer = {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(...): T =
MyCoolViewModel(args) as T
}
})
Ожидание:
val stateSmth = savedStateHandle.getMutableStateFlow("smth", defaultSmth)
Реальность:
отрицание, гнев, торг, пост в канал, кастомный флоу.827
А правильно ответил @dimitreuz: если ответ от неудачного запроса нам ценен, нужно его вычитать и закрыть, освободив сокет для следующего запроса.
