Javanese Online
Open in Telegram
Статьи и новости, наблюдения и советы. Кодревью: http://javanese.online/%D1%80%D0%B0%D0%B7%D0%B1%D0%BE%D1%80_%D0%BA%D0%BE%D0%B4%D0%B0/ Обсуждение в @javanese_questions Материалы пишет @Harmonizr
Show more723
Subscribers
No data24 hours
+37 days
+530 days
Posts Archive
Throwable — волшебный класс!
fillInStackTrace(): запомнить текущий стек.
Некоторые создают эксепшон и сразу же вызывают этот метод. Это бесполезно, он ведь и так вызывается в конструкторе.
А вот в каком-нибудь другом месте можно навязать любому эксепшону текущий стек. Удачной отладки!
Метод, кстати, нефинальный. Можно переопределить его и сделать no-op, получив дешёвый эксепшон без стека.
getMessage(), getCause(), printStackTrace(), toString() тоже нефинальные. Если хочется пострелять с двух рук, можно переопределить их и бросить оттуда новый exception.
setStackTrace(): вообще вседозволенность. Да, можно использовать его в благих целях, дабы прятать интринсики языка, а можно приколоться над коллегами!Сравнительная характеристика 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 тоже написан таким образом, будто этого органичения не существует. Почему оно так работает? Для меня это открытый вопрос.О сколько нам открытий чудных готовит спека DNS!
Держу в курсе: доменное имя без точки в конце считается неполным, относительным. В эпоху умерших интранетов и локальных сетей об этом уже никто не вспомнит…
Обнаружено в процессе чтения исходников OkHttp.
https://webmasters.stackexchange.com/questions/73934/how-can-urls-have-a-dot-at-the-end-e-g-www-bla-de
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 &Какой алгоритм решит мою задачу?
У нас есть один язык, который самостоятельно выбирает наиболее подходящие алгоритмы и структуры данных из имеющихся. Этим студентом был SQL. Если повезёт, задача выбора алгоритма сведётся к написанию SQL-запроса, а вывод
EXPLAIN прямым текстом назовёт нужный алгоритм.Пол раза в жизни встречал
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)
Реальность:
отрицание, гнев, торг, пост в канал, кастомный флоу.А правильно ответил @dimitreuz: если ответ от неудачного запроса нам ценен, нужно его вычитать и закрыть, освободив сокет для следующего запроса.
IllegalStateException: cannot make a new request because the previous response is still open: please call response.close()
Занятный крэш меня сегодня посетил. Типовое обновление токена. Нетрудно понять, где именно баг, но как именно вы бы его чинили?Вопрос, который мне не задали на конференции: является ли SVG path-data Тьюринг-полным языком?
Курс по ФП на Java
Знаю, что «восьмёрке» уже восемь лет, но этот курс мне скинули буквально месяц назад. Функциональная композиция, стримы-коллекторы, CompletableFuture и прочие монадки в задачах и примерах.
https://stepik.org/course/91497/syllabus
Опять про андроид. И снова сохранение состояния
Есть паттерн такой — назовём его custom composite view: наследуем любой подходящий layout, инфлейтим внутрь него
<merge> и переиспользуем целиком. Описан в этой статье и много где ещё.
Но, как говорится, есть один нюанс. Если на экране более одного такого компонента, айдишки внутри начинают дублироваться. В итоге при сохранении состояния побеждает тот, кто сохранялся последним, щедро раздавая всем компонентам своё состояние при восстановлении. Для вьюшек с пользовательским вводом, таких как текстовые поля, слайдеры, чузеры и вот это всё, проблема прям критичная.
Нужно переопределить dispatch(Save|Restore)InstanceState, которые отвечают за сохранение-восстановление всего поддерева. Далее — вариации:
а) можно вызвать dispatch(Freeze|Thaw)SelfOnly(container), как это делает ресайклер, и в on(Save|Restore)InstanceState сделать всю нужную работу. Придётся унаследовать SavedState, к сожалению;
б) а можно взять какой-нибудь ParcelableSparseArray и передать его в super.dispatchSaveInstanceState и положить его в container под своим id, изолировав таким образом состояние всего компонента в отдельное пространство айдишек.Платформа для донатов
Очередная #идея, которую я (да ладно, будем честны, подслушал и) дарю любому банку/финтеху.
Организация или отдельно взятый волонтёр открывает специальный счёт для донатов. Для счёта генерируется специальная общедоступная страничка — там эквайринг и история операций, к которой автоматом прикрепляются электронные чеки.
Всё, любой благотворительный проект может вылезти из пещерного века, выбросить камень, плуг, соху, десять карт разных банков и систем (потому что эквайринг) и гигабайт фоток чеков на телефоне.
Adapter Delegates
Почему я не использую «классические» адаптерДелегаты?
Смотрим невооружённым глазом:
val adapter = ListDelegationAdapter<List<Animal>>(
catAdapterDelegate(...),
А если приподнять завесу «няшного» Kotlin DSL, то вообще увидим ЭТО:
public boolean isForViewType(List<Animal> items, int position) {
return items.get(position) instanceof Cat;
}
Напрашивается следующий вывод:
• можно забыть нужный делегат (крэш в рантайме),
• а можно забыть удалить лишний (недостижимый код),
• нельзя использовать несколько делегатов для одного типа данных.
Первые два пункта — классика: в Retrofit можно переехать с RxJava на suspend и забыть убрать CallAdapter.Factory, а в Gson есть «изкоробочные» адаптеры для, скажем, AtomicIntegerArray, которые психически здоровому человеку не нужны, а больному — вредны.
Третий пункт — ваще критикал: я хочу пару разных делегатов-заголовков, оба с типом CharSequence но разными шрифтами, отступами, размерами. Или в вертикальном списке имею горизонтальные списки всякого разного, которые сплошь instanceof List.
Короче, я создал, хоть и столько лет спустя.
Под капотом там просто сдвоенный List из элементов и их делегатов.
В комплекте валяется ItemDecoration, который могёт… а, не буду пересказывать скриншот, он в конце readme.
Лучи добра всем трудящимся!Gson 2.9.0
Пару месяцев назад тихой сапой вышло обновление объекта обожания всех рефлектологов и ансейфоведов.
Моё внимание привлекли эти два изменения:
• Support
EnumMap deserialization (#2071)
• Add GsonBuilder.disableJdkUnsafe() (#1904)
Вот и отлично! Не прошло и десяти л… а, нет, прошло :)
Того гляди, скоро и EnumSet начнёт поддерживать.
https://github.com/google/gson/blob/master/CHANGELOG.mdПять стадий написания LayoutManager
1. 😨 Быть такого не может, чтобы ранее такую штуку никто не делал!
2. 😡 Нет, ну почему никто не запилил?!
3. 🤬 Может, как-нибудь попроще, без менеджера?
4. 😢 *гуглинг в гитхабе*
5. 😌 Ладно-ладно, пойду писать.
Итого: Flow (раскладывает в строчку, переносит на новую при необходимости) с возможностью ограничить количество строк и показать специальную вьюшку «ещё 100500».
https://github.com/Miha-x64/FlowLayoutManager/
