В Android есть два основных типа потоков:
1. UI Thread (главный поток) → отвечает за интерфейс и обработку событий.
2. Worker Thread (фоновый поток) → используется для долгих операций (запросы в сеть, работа с БД).
- Отвечает за отрисовку интерфейса (Activity, Fragment, View).
- Обрабатывает нажатия, свайпы, анимации.
- Все
setText(), setImageResource() и другие UI-методы работают только здесь. Если делать тяжёлые операции в UI Thread, приложение зависнет (ANR - Application Not Responding)! val url = URL("https://example.com")
val connection = url.openConnection() as HttpURLConnection // ❌ Ошибка!- Выполняет долгие операции, не блокируя UI:
Запросы в сеть (Retrofit, OkHttp)
Чтение/запись в БД (Room, SQLite)
Обработку файлов, JSON, XML
Любые тяжёлые вычисления
CoroutineScope(Dispatchers.IO).launch {
val url = URL("https://example.com")
val connection = url.openConnection() as HttpURLConnection // ✅ Работает!
}Способ 1:
Coroutines (лучший вариант)CoroutineScope(Dispatchers.IO).launch {
val data = fetchData() // Работает в `Worker Thread`
withContext(Dispatchers.Main) {
textView.text = data // Назад в `UI Thread`
}
}Способ 2:
Handler + Thread (старый вариант) val handler = Handler(Looper.getMainLooper())
Thread {
val data = fetchData() // Работает в `Worker Thread`
handler.post {
textView.text = data // Назад в `UI Thread`
}
}.start()
Способ 3:
AsyncTask (УСТАРЕЛ, НЕ ИСПОЛЬЗОВАТЬ!) class MyTask : AsyncTask<Void, Void, String>() {
override fun doInBackground(vararg params: Void?): String {
return fetchData() // `Worker Thread`
}
override fun onPostExecute(result: String) {
textView.text = result // `UI Thread`
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁15👍5💊1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2🤯2👀1
Чтобы чат работал быстро и без лагов, нужно:
Оптимизировать
RecyclerView (ViewHolder, DiffUtil, Payloads). Минимизировать работу в
UI Thread (использовать Coroutines, Paging 3). Оптимизировать загрузку изображений (Glide, Coil).
Использовать Room + Flow / LiveData для офлайн-кеша.
Вместо
notifyDataSetChanged() лучше использовать ListAdapter, который сам обновляет только изменённые сообщения. class ChatAdapter : ListAdapter<Message, ChatViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Message>() {
override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean =
oldItem == newItem // data class сравнит автоматически
}
}
}Если, например, только статус сообщения изменился, не нужно перерисовывать всё сообщение!
override fun getChangePayload(oldItem: Message, newItem: Message): Any? {
return if (oldItem.status != newItem.status) "STATUS_CHANGED" else null
}Пример в
onBindViewHolder() override fun onBindViewHolder(holder: ChatViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains("STATUS_CHANGED")) {
holder.updateStatus() // Обновляем только статус
} else {
super.onBindViewHolder(holder, position, payloads)
}
}Запросы в БД в
Worker Thread (Coroutines + Room) fun loadMessages(): Flow<List<Message>> = messageDao.getMessages()
Используем
Paging 3 для подгрузки сообщений val pager = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { messageDao.getPagedMessages() }
).flow.cachedIn(viewModelScope)
Если чат содержит аватары или изображения, они должны загружаться лениво и в фоновом потоке.
Glide.with(context)
.load(user.avatarUrl)
.circleCrop()
.placeholder(R.drawable.placeholder)
.into(imageView)
Чтобы сообщения не загружались из сети при каждом запуске, сохраняем их в Room.
@Dao
interface MessageDao {
@Query("SELECT * FROM messages ORDER BY timestamp DESC")
fun getMessages(): Flow<List<Message>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMessages(messages: List<Message>)
}
Используем
setHasFixedSize(true), если сообщения не меняют размер recyclerView.setHasFixedSize(true)
Используем
setItemViewCacheSize(), чтобы кэшировать ViewHolder recyclerView.setItemViewCacheSize(20)
Отключаем анимации
RecyclerView (если лагает) (recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
По умолчанию нельзя, потому что дженерики стираются (
Type Erasure) во время компиляции. fun <T> printType(value: T) {
println(T::class.simpleName) // ❌ Ошибка! Нельзя использовать `T::class`
}Если используем
inline fun, можно сделать дженерик "реальным" (reified). inline fun <reified T> printType(value: T) {
println(T::class.simpleName) // ✅ Работает!
}
fun main() {
printType(123) // Int
printType("Hello") // String
printType(3.14) // Double
}Если дженерик используется в классе, то
reified не поможет. class MyGenericClass<T>(private val type: KClass<T>) {
fun printType() {
println(type.simpleName) // ✅ Работает
}
}
fun main() {
val obj = MyGenericClass(String::class)
obj.printType() // String
}Для сложных дженериков (
List<T>, Map<K, V>) используем typeOf<T>() (только с reified). import kotlin.reflect.typeOf
inline fun <reified T> printGenericType() {
val type = typeOf<T>()
println(type) // ✅ List<Int>, Map<String, Boolean> и т. д.
}
fun main() {
printGenericType<List<Int>>() // kotlin.collections.List<kotlin.Int>
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥1
Софткод (Softcode, Soft Coding) — это подход к программированию, при котором логика программы хранится в конфигурационных файлах, базе данных или других внешних источниках, а не жёстко (hardcoded) прописана в коде.
Пример Hardcode (жёстко зашито в коде)
val apiUrl = "https://api.example.com" // ❌ Если URL изменится, надо менять код
Пример Softcode (гибко через конфигурацию)
val apiUrl = Config.get("api_url") // ✅ Загружается из настроекКонфигурационные файлы (
config.json, .properties, .xml). База данных (логика, настройки, права пользователей).
API и сервер (получение UI-элементов, бизнес-логики с сервера).
Скриптовые языки (скрипты загружаются динамически).
Softcode через
SharedPreferences (конфигурация в памяти)val sharedPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val theme = sharedPrefs.getString("app_theme", "light") // ✅ Загружаем тему из настроекSoftcode через
remoteConfig (Firebase Remote Config)val minVersion = Firebase.remoteConfig.getInt("min_supported_version")Softcode через JSON-файл (читаем конфиг из assets)
fun getConfigValue(context: Context, key: String): String {
val json = context.assets.open("config.json").bufferedReader().use { it.readText() }
val jsonObject = JSONObject(json)
return jsonObject.getString(key) // ✅ Получаем значение из JSON
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Это область, в которой выполняются корутины в Kotlin. Определяет жизненный цикл корутин и позволяет отменять их при завершении scope.
- GlobalScope – живет все время работы приложения, но редко используется.
- viewModelScope – в ViewModel, отменяется при уничтожении ViewModel.
- lifecycleScope – в Activity/Fragment, отменяется при уничтожении UI.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥3
Библиотека Lifecycle в Android Jetpack помогает управлять и контролировать жизненный цикл компонентов Android, таких как Activities и Fragments. Она упрощает создание компонентов, которые осведомлены о своем жизненном цикле и могут корректно реагировать на изменения в нем. Это позволяет избегать утечек памяти и некорректной работы компонентов при изменении конфигураций или переходах между состояниями.
Это интерфейс, который определяет класс как владеющий жизненным циклом. Activity и Fragment уже реализуют этот интерфейс.
class MyActivity : AppCompatActivity(), LifecycleOwner {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}Это интерфейс, который позволяет классу наблюдать за изменениями в жизненном цикле компонента. Методы, аннотированные
@OnLifecycleEvent, будут вызываться при соответствующих событиях жизненного цикла.class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
// Код, который выполняется при старте жизненного цикла
Log.d("MyObserver", "onStart called")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {
// Код, который выполняется при остановке жизненного цикла
Log.d("MyObserver", "onStop called")
}
}Это класс, который содержит информацию о текущем состоянии и позволяет другим объектам наблюдать за изменениями в жизненном цикле.
class MyActivity : AppCompatActivity() {
private lateinit var myObserver: MyObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myObserver = MyObserver()
lifecycle.addObserver(myObserver)
}
}dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Log.d("MyObserver", "Activity resumed")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
Log.d("MyObserver", "Activity paused")
}
}class MyActivity : AppCompatActivity() {
private lateinit var myObserver: MyObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myObserver = MyObserver()
lifecycle.addObserver(myObserver)
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
1. synchronized блоки в Java/Kotlin работают на уровне потоков, а не корутин – это разные механизмы синхронизации.
2. Блокировка потоков замедляет работу – Mutex работает в асинхронном стиле, не блокируя потоки.
3. Глобальные synchronized блоки не учитывают отмену корутин – если корутина отменена, synchronized не освобождает ресурс.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Чтобы добавить кастомные атрибуты в
Custom View, нужно: Создать
attrs.xml и описать атрибуты. Добавить их в
styleable и получить в Custom View. Использовать атрибуты в
XML или Kotlin. Создаём файл
res/values/attrs.xml, если его нет: <resources>
<declare-styleable name="CustomButton">
<attr name="customText" format="string"/>
<attr name="customTextSize" format="dimension"/>
<attr name="customTextColor" format="color"/>
</declare-styleable>
</resources>
Теперь создаём
CustomButton.ktclass CustomButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatButton(context, attrs, defStyleAttr) {
init {
context.theme.obtainStyledAttributes(attrs, R.styleable.CustomButton, 0, 0).apply {
try {
val text = getString(R.styleable.CustomButton_customText) ?: "Default"
val textSize = getDimension(R.styleable.CustomButton_customTextSize, 16f)
val textColor = getColor(R.styleable.CustomButton_customTextColor, Color.BLACK)
setText(text)
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
setTextColor(textColor)
} finally {
recycle() // Освобождаем ресурсы
}
}
}
}
Теперь можно использовать
CustomButton в activity_main.xml <com.example.customviews.CustomButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:customText="Нажми меня"
app:customTextSize="20sp"
app:customTextColor="@android:color/holo_red_dark"/>
Можно обновлять свойства прямо в Kotlin
val button = findViewById<CustomButton>(R.id.customButton)
button.text = "Новый текст"
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
button.setTextColor(Color.BLUE)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
2. Постепенно внедрять Compose для новых экранов или функций, сохраняя старые части на XML.
3. Разделить проект на модули, чтобы переключение между Compose и View не влияло на весь код.
4. Проводить тщательное тестирование и устранять баги на каждом этапе интеграции.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2🤯2😁1
Конструктор (
constructor)Конструктор суперкласса (
super)Статический блок инициализации (
static {})Обычный блок инициализации (
{})При создании объекта в Java выполняются шаги:
Статические блоки суперкласса (
static {} в родителе) Статические блоки текущего класса (
static {}) Обычные блоки инициализации суперкласса (
{} в родителе) Конструктор суперкласса (
super(...)) Обычные блоки инициализации текущего класса (
{}) Конструктор текущего класса
class Parent {
static { System.out.println("1️⃣ Статический блок Parent"); }
{ System.out.println("3️⃣ Обычный блок Parent"); }
Parent() {
System.out.println("4️⃣ Конструктор Parent");
}
}
class Child extends Parent {
static { System.out.println("2️⃣ Статический блок Child"); }
{ System.out.println("5️⃣ Обычный блок Child"); }
Child() {
System.out.println("6️⃣ Конструктор Child");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("🔹 Создаём объект Child");
new Child();
}
}Вывод в консоль
Статический блок Parent
Статический блок Child
Создаём объект Child
Обычный блок Parent
Конструктор Parent
Обычный блок Child
Конструктор Child
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Forwarded from easyoffer
Я боялся, что провалю собеседование. Так появился easyoffer
Когда я только начинал искать первую работу программистом, меня пугала мысль, что я просто не смогу ответить на вопросы на собеседовании.
Типа… ты потратил месяцы на то, чтобы учиться, писал pet-проекты, собирал резюме, рассылаешь отклики — и всё может закончиться на одном-единственном вопросе, на который ты не знаешь ответ.
Я реально боялся.
Я смотрел видео mock-собеседований на YouTube, останавливал каждое, выписывал вопросы в Notion. Потом вручную писал к ним ответы. И потом ещё по нескольку раз перечитывал. Такой вот "тренажёр" на коленке.
📎 (там на картинке — один из моих реальных списков в Notion, ставь 🔥 если тоже так делал)
В какой-то момент я посчитал — у меня уже было выписано больше 500 вопросов. Я почувствовал ужас.
Потому что невозможно всё это зазубрить. А что, если спросят как раз тот, к которому я не успел подготовиться?..
Тогда и пришла идея
А что если понять, какие из вопросов встречаются чаще всего? Чтобы не учить всё подряд, а сфокусироваться на главном.
Так родился easyoffer.
Сначала — просто как пет-проект, чтобы показать в резюме и подготовиться к собесам. А потом оказалось, что он реально помогает людям. За первые месяцы его посетили сотни тысяч человек. И я понял: это больше, чем просто пет-проект.
Сейчас я делаю EasyOffer 2.0
И уже не один, а вместе с вами.
В новой версии будут:
– вопросы из реальных собесов, с фильтрацией по грейду, компании, типу интервью
– тренажёр с карточками (по принципу интервальных повторений — как в Anki)
– база задач с интервью
– тренажёр «реальное собеседование», чтобы отрепетировать как в жизни
Каждая фича упрощает и сокращает время на подготовку. Все эти штуки я бы мечтал иметь, когда сам готовился к собеседованиям.
Я делаю всё на свои деньги. Никаких инвесторов. Только вы и я.
Если вы хотите помочь — сейчас самое важное время.
Краудфандинг уже стартовал. Благодаря нему я смогу привлечь больше людей для разработки, сбору и обработки собеседований.
Все, кто поддержат проект до релиза, получат:
🚀 1 год PRO-доступа по цене месячной подписки. Его можно активировать в любое время, например когда начнете готовится к собесам.
➕ Доступ к закрытому бета-тесту
Поддержать 👉 https://planeta.ru/campaigns/easyoffer
Спасибо, что верите в этот проект 🙌
Когда я только начинал искать первую работу программистом, меня пугала мысль, что я просто не смогу ответить на вопросы на собеседовании.
Типа… ты потратил месяцы на то, чтобы учиться, писал pet-проекты, собирал резюме, рассылаешь отклики — и всё может закончиться на одном-единственном вопросе, на который ты не знаешь ответ.
Я реально боялся.
Я смотрел видео mock-собеседований на YouTube, останавливал каждое, выписывал вопросы в Notion. Потом вручную писал к ним ответы. И потом ещё по нескольку раз перечитывал. Такой вот "тренажёр" на коленке.
📎 (там на картинке — один из моих реальных списков в Notion, ставь 🔥 если тоже так делал)
В какой-то момент я посчитал — у меня уже было выписано больше 500 вопросов. Я почувствовал ужас.
Потому что невозможно всё это зазубрить. А что, если спросят как раз тот, к которому я не успел подготовиться?..
Тогда и пришла идея
А что если понять, какие из вопросов встречаются чаще всего? Чтобы не учить всё подряд, а сфокусироваться на главном.
Так родился easyoffer.
Сначала — просто как пет-проект, чтобы показать в резюме и подготовиться к собесам. А потом оказалось, что он реально помогает людям. За первые месяцы его посетили сотни тысяч человек. И я понял: это больше, чем просто пет-проект.
Сейчас я делаю EasyOffer 2.0
И уже не один, а вместе с вами.
В новой версии будут:
– вопросы из реальных собесов, с фильтрацией по грейду, компании, типу интервью
– тренажёр с карточками (по принципу интервальных повторений — как в Anki)
– база задач с интервью
– тренажёр «реальное собеседование», чтобы отрепетировать как в жизни
Каждая фича упрощает и сокращает время на подготовку. Все эти штуки я бы мечтал иметь, когда сам готовился к собеседованиям.
Я делаю всё на свои деньги. Никаких инвесторов. Только вы и я.
Если вы хотите помочь — сейчас самое важное время.
Краудфандинг уже стартовал. Благодаря нему я смогу привлечь больше людей для разработки, сбору и обработки собеседований.
Все, кто поддержат проект до релиза, получат:
🚀 1 год PRO-доступа по цене месячной подписки. Его можно активировать в любое время, например когда начнете готовится к собесам.
➕ Доступ к закрытому бета-тесту
Поддержать 👉 https://planeta.ru/campaigns/easyoffer
Спасибо, что верите в этот проект 🙌
👍2🔥1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3😁3👀1
Подключение
BroadcastReceiver в Android состоит из двух основных шагов: создание самого ресивера и его регистрация. Ресивер можно зарегистрировать как статически в манифесте, так и динамически в коде.Создадим простой
BroadcastReceiver, который будет реагировать на определённое событие, например, на получение SMS.import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Сообщение получено!", Toast.LENGTH_SHORT).show();
}
}
Можно зарегистрировать ресивер в файле
AndroidManifest.xml. Это удобно, когда вы хотите, чтобы ресивер всегда был активен и слушал определённые системные события, например, перезагрузку устройства или получение SMS.<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
Иногда нужно регистрировать ресивер только на время выполнения определённой активности или службы. В этом случае лучше использовать динамическую регистрацию.
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBroadcastReceiver = new MyBroadcastReceiver();
}
@Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(myBroadcastReceiver, filter);
}
@Override
protected void onStop() {
super.onStop();
unregisterReceiver(myBroadcastReceiver);
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
- ViewModel — проверка логики отображения, работы с LiveData/StateFlow.
- UseCase / Interactor — основная бизнес-логика.
- Repository (если изолирован от сети) — для проверки логики агрегации данных.
- Вспомогательные утилиты и мапперы — чтобы гарантировать корректность трансформации данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1
data class — класс для хранения данных с автогенерацией equals(), hashCode(), copy(). sealed class — ограниченная иерархия классов, используется для when. data class автоматически создаёт: equals() и hashCode() → сравнение объектов по значениям. copy() → удобное копирование с изменением параметров. toString() → красивый вывод. data class User(val id: Int, val name: String)
fun main() {
val user1 = User(1, "Alice")
val user2 = user1.copy(name = "Bob") // Создаём копию с новым именем
println(user1) // User(id=1, name=Alice)
println(user2) // User(id=1, name=Bob)
}
sealed class используется, когда есть фиксированное число подклассов. sealed class NetworkState {
object Loading : NetworkState()
data class Success(val data: String) : NetworkState()
data class Error(val message: String) : NetworkState()
}
fun handleState(state: NetworkState) {
when (state) {
is NetworkState.Loading -> println("Загрузка...")
is NetworkState.Success -> println("Данные: ${state.data}")
is NetworkState.Error -> println("Ошибка: ${state.message}")
}
}Да! Это лучший вариант для управления состояниями:
sealed class Result {
object Loading : Result()
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2