Формат WebP является наиболее экономичным с точки зрения сжатия без потери качества. Он лучше по сжатию, чем PNG или JPEG, поддерживает прозрачность и используется во многих Android-приложениях.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2🤔2
Этот инструмент показывает время, затраченное на отрисовку каждого кадра. Использование этого инструмента позволяет выявить "тяжелые" кадры и измерить улучшения после оптимизации.
Предоставляет набор инструментов для анализа производительности приложения.
Создание и использование тестов производительности помогает количественно оценить улучшения. Вы можете использовать библиотеку Jetpack Benchmark для создания и выполнения тестов производительности.
build.gradle:dependencies {
androidTestImplementation "androidx.benchmark:benchmark-junit4:1.1.0"
androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation "androidx.test:rules:1.3.0"
}import androidx.benchmark.junit4.BenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ExampleBenchmark {
@get:Rule
val benchmarkRule = BenchmarkRule()
@Test
fun myFunctionBenchmark() {
benchmarkRule.measureRepeated {
// Вызов вашей функции или кода для тестирования производительности
myFunction()
}
}
}
Используйте журналирование для измерения времени выполнения определенных операций.
val startTime = System.currentTimeMillis()
// Ваш код
val endTime = System.currentTimeMillis()
Log.d("Performance", "Время выполнения: ${endTime - startTime} мс")
StrictMode помогает обнаружить операции, которые могут замедлить работу приложения, такие как работа с сетью или базой данных в главном потоке.
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}Systrace позволяет собирать и анализировать трассировки производительности системы, предоставляя детализированные данные о времени выполнения различных операций.
adb shell am broadcast -a com.android.systemui.screenshot.ScreenshotService.ACTION_SYSTRACE.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
– Background service работает в фоне, может быть остановлен системой при нехватке ресурсов.
– Foreground service требует уведомление и имеет приоритет, не убивается системой просто так.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
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
👍3
2. Сохранить данные в ViewModel, чтобы они переживали пересоздание.
3. Сохранить данные в локальном хранилище, например, SharedPreferences.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В многомодульных проектах каждый модуль может иметь свой
AndroidManifest.xml, чтобы: Задавать зависимости (
uses-permission, uses-feature) для конкретного модуля. Определять компоненты (
Activity, Service, BroadcastReceiver) для каждого модуля. Автоматически объединять манифесты всех модулей в
AndroidManifest.xml главного (app) модуля. При сборке Gradle автоматически сливает (
merge) все AndroidManifest.xml в один итоговый файл. app/
├── src/main/AndroidManifest.xml ← Главный манифест
├── java/com/example/MainActivity.kt
├── res/
├── build.gradle
feature_login/
├── src/main/AndroidManifest.xml ← Манифест модуля `login`
├── java/com/example/login/LoginActivity.kt
├── res/
├── build.gradle
feature_chat/
├── src/main/AndroidManifest.xml ← Манифест модуля `chat`
├── java/com/example/chat/ChatActivity.kt
├── res/
├── build.gradle
При сборке манифесты модулей сливаются в манифест главного модуля.
<manifest package="com.example.app">
<application>
<activity android:name=".MainActivity" />
</application>
</manifest>
feature_login (feature_login/src/main/AndroidManifest.xml) <manifest>
<application>
<activity android:name=".login.LoginActivity" />
</application>
</manifest>
feature_chat (feature_chat/src/main/AndroidManifest.xml) <manifest>
<application>
<activity android:name=".chat.ChatActivity" />
</application>
</manifest>
После объединения итоговый
AndroidManifest.xml выглядит так <manifest package="com.example.app">
<application>
<activity android:name=".MainActivity" />
<activity android:name=".login.LoginActivity" />
<activity android:name=".chat.ChatActivity" />
</application>
</manifest>
Например, модуль
camera требует CAMERA, но другие модули — нет. <uses-permission android:name="android.permission.CAMERA" />
Каждый модуль добавляет только свои компоненты (например,
LoginActivity в feature_login). Например, модуль
feature_map использует Google Maps, но feature_login — нет. <uses-library android:name="com.google.android.maps" />
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Data Class автоматически генерирует equals, hashCode, toString, copy, и componentN функции. Они удобны для хранения данных и значительно упрощают работу с неизменяемыми структурами.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Лямбда-выражения представляют собой компактный способ представления анонимных функций, который особенно полезен для создания коротких блоков выполнения кода, предназначенных для последующей передачи другим функциям. Лямбды широко используются для упрощения работы с коллекциями, потоками данных, асинхронным кодом и в ситуациях, где требуется функциональный интерфейс.
Были введены в Java 8 и являются частью усилий по добавлению функциональных возможностей в язык. Лямбды в Java чаще всего используются с функциональными интерфейсами, которые являются интерфейсами с одним абстрактным методом.
(parameters) -> expression
или
(parameters) -> { statements; }Поддерживает более гибкие и выразительные лямбды по сравнению с Java. Лямбды могут использоваться как с функциональными интерфейсами, так и в качестве части синтаксиса языка, благодаря чему Kotlin особенно удобен для функционального программирования.
{ parameters -> code body }Лямбда без параметров
{ println("Hello, World!") } Лямбда с одним параметром
{ a: Int -> a * a } Лямбда с несколькими параметрами
{ a: Int, b: Int -> a + b } Лямбда с телом, содержащим несколько выражений
{ s: String ->
println(s)
s.length
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
volatile гарантирует, что все потоки видят актуальное значение переменной, а не её локальную копию из кеша. Это не делает операцию атомарной, но обеспечивает согласованность чтения/записи.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1💊1
TransactionTooLargeException — это исключение, которое возникает, если передаваемые данные превышают лимит 1MB в Binder. Чаще всего ошибка появляется при передаче больших объектов через
Intent, Bundle, `onSaveInstanceState(). Передача большого
Bitmap через Intent (ОШИБКА!) val bitmap: Bitmap = getLargeBitmap() // ❌ Очень большой объект
val intent = Intent(this, ImageActivity::class.java)
intent.putExtra("image", bitmap) // ❌ Ошибка: TransactionTooLargeException
startActivity(intent)
Ошибка
android.os.TransactionTooLargeException: data parcel size 2MB is larger than 1MB
Использовать
FileProvider и Uri вместо Bitmap val file = File(context.cacheDir, "image.png")
file.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
val intent = Intent(this, ImageActivity::class.java).apply {
putExtra("image_uri", uri.toString())
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(intent)
Ошибка: сохранение больших данных в
onSaveInstanceState() override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable("largeData", myLargeObject) // ❌ ОШИБКА!
super.onSaveInstanceState(outState)
}Решение → Используем
ViewModel, чтобы хранить данные в памяти: class MyViewModel : ViewModel() {
var largeData: MyLargeObject? = null
}Если данные не нужны постоянно → используем
SharedPreferences getSharedPreferences("app_prefs", MODE_PRIVATE).edit()
.putString("last_data", jsonData)
.apply()Если данные большие и важные → используем
Room @Dao
interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveData(data: MyData)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Это пример try-with-resources — конструкции, которая автоматически закрывает ресурс, реализующий интерфейс AutoCloseable. Здесь BufferedReader закрывается автоматически по завершении блока, даже если возникло исключение. Это упрощает управление ресурсами и снижает риск утечек.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
При наследовании
data class от какого-либо суперкласса в Kotlin, важно понимать, как работают свойства (поля) суперкласса и как они влияют на функциональность и структуру data class. Поля (свойства), объявленные в суперклассе, автоматически становятся доступными в классе-наследнике. Вы можете использовать их в наследуемом классе как обычно. Однако свойства суперкласса не участвуют в автоматически сгенерированных функциях
equals(), hashCode(), и toString() для data class.У
data class Kotlin генерирует функции equals(), hashCode(), toString(), copy() и другие. Эти функции работают только с параметрами, объявленными в первичном конструкторе data-класса. Поля, которые находятся в суперклассе, не участвуют в этих функциях.Это связано с тем, что контракт
data class предполагает, что все его ключевые данные (data) определяются только параметрами первичного конструктора. Это позволяет гарантировать, что две одинаковые сущности будут сравниваться и обрабатываться корректно, основываясь только на данных самого data class.// Суперкласс с полем name
open class Person(val name: String)
// Наследуемый data-класс
data class Employee(val id: Int, val position: String) : Person(name = "Default")
val employee1 = Employee(1, "Developer")
val employee2 = Employee(1, "Developer")
println(employee1 == employee2) // true, так как сравнение основано только на id и position
println(employee1.toString()) // Employee(id=1, position=Developer)
Если вы хотите, чтобы поля суперкласса учитывались в логике
equals() или hashCode(), вам нужно переопределить эти функции вручную.data class Employee(val id: Int, val position: String) : Person(name = "Default") {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Employee || !super.equals(other)) return false
return id == other.id && position == other.position && name == other.name
}
override fun hashCode(): Int {
return 31 * super.hashCode() + id.hashCode() + position.hashCode()
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
- Примитивные типы хранят значения напрямую, не могут быть null, и не имеют методов.
- Ссылочные типы — это объекты, хранят ссылки на данные в памяти и могут быть null.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Чтобы добавить кастомные атрибуты в
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
💊2👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
ViewModel и onSaveInstanceState служат для сохранения данных при изменении конфигурации активности или фрагмента (например, при повороте экрана). Однако они решают эту задачу по-разному и имеют разные области применения. ViewModel используется для хранения и управления данными, связанных с UI, таким образом, чтобы они сохранялись при изменении конфигурации (например, при повороте экрана). Когда система уничтожает и пересоздаёт
Activity или Fragment, ViewModel остаётся в памяти до полного уничтожения владельца (например, выхода из Activity). class MyViewModel : ViewModel() {
var counter: Int = 0 // Переменная, которая сохраняется при повороте экрана
}Использование в
Activityclass MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// Теперь viewModel.counter сохранится при повороте экрана
}
}до полного уничтожения
Activity или Fragment. списки, модели, API-данные
улучшая архитектуру.
например, при закрытии приложения
Метод
onSaveInstanceState() используется для сохранения данных в Bundle, который система автоматически передаёт при пересоздании Activity или Fragment. Когда
Activity уничтожается (например, при повороте экрана), система вызывает onSaveInstanceState(), в котором можно сохранить небольшие данные (строки, числа и т. д.). После пересоздания Activity эти данные можно восстановить из savedInstanceState. class MainActivity : AppCompatActivity() {
private var counter: Int = 0 // Значение, которое мы хотим сохранить
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Восстанавливаем данные, если они есть
counter = savedInstanceState?.getInt("counter_key") ?: 0
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("counter_key", counter) // Сохраняем значение перед уничтожением Activity
}
}EditText). BundleСтавь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
В RxJava параллельность достигается через:
- Разные источники (Single, Observable) с subscribeOn разными пулами или общим
- Затем их можно объединить с помощью операторов вроде zip, merge, combineLatest.
Всё это позволяет выполнять запросы одновременно, а затем собрать их результат в одну цепочку.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊8🔥1
В Android есть несколько способов навигации между экранами. Давай разберём основные.
Каждый экран – это отдельная
Activity, переход осуществляется с помощью Intent. val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
Один
Activity содержит несколько Fragment, навигация через FragmentManager. supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment())
.addToBackStack(null)
.commit()
Google рекомендует
Navigation Component для удобной работы с Fragment. findNavController().navigate(R.id.action_firstFragment_to_secondFragment)
Используется
BottomNavigationView или TabLayout для переключения между экранами. bottomNavigationView.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home -> replaceFragment(HomeFragment())
R.id.profile -> replaceFragment(ProfileFragment())
}
true
}Боковое меню (
DrawerLayout) с пунктами навигации. drawerLayout.openDrawer(GravityCompat.START)
Навигация через ссылки (например,
myapp://profile/123). <intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="myapp" android:host="profile"/>
</intent-filter>
Используется
NavHost и NavController. NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile") { ProfileScreen(navController) }
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Для этого необходимо аннотировать класс Application аннотацией
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В
interface можно определять свойства (val или var), но их реализация зависит от того, есть ли у них get и set. В интерфейсе можно объявить
val (только для чтения) или var (для чтения и записи), но без инициализации. interface User {
val name: String // Объявляем свойство, но не реализуем
var age: Int // Может изменяться, но без начального значения
}Использование в классе
class Person(override val name: String) : User {
override var age: Int = 25 // Обязательно реализовать
}Можно задать кастомный геттер прямо в интерфейсе.
interface User {
val name: String
val greeting: String
get() = "Привет, $name!" // Реализация внутри интерфейса
}Использование в классе
class Person(override val name: String) : User
fun main() {
val user = Person("Андрей")
println(user.greeting) // Привет, Андрей!
}
Если в интерфейсе объявить
var, то нельзя задать реализацию геттера и сеттера — их должен реализовать класс. interface User {
var age: Int // Только объявление, без реализации
}Использование в классе
class Person : User {
override var age: Int = 30 // Реализуем свойство
}В
interface нельзя использовать field (бэкинг-поле), потому что у интерфейсов нет состояния. interface User {
var age: Int
get() = field // Ошибка! Нельзя использовать `field` в интерфейсе
set(value) { field = value }
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Extension-функция выглядит как будто добавлена в класс, но на самом деле это статическая функция, которой не требуется менять сам класс. Она даёт синтаксический сахар, не добавляя нового в байткод класса.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4