Тему я назвал хардкорной ⛓ не просто так, с каждым постом сложность будет возрастать, однако я постараюсь объяснить на пальцах, погнали 👉
☝️Мы разбираем тему отталкиваясь от проблемы. В прошлом посте мы разбирали пример с банковским приложением и калькулятором. Этот пример довольно синтетический, он далек от реальности. Разберем реальную проблему.
У нас на устройствах есть📍GPS и приложения, которые хотят использовать его: такси, карты, навигатор и т.д. Туева хуча приложений и давать прямой доступ довольно рискованно, потому как вдруг левое приложение захочет в фоне отслеживать нашу позицию🥷.
🤔Значит доступ к датчику GPS должен быть только у некоторого системного сервиса (по сути приложение, только без UI). Сервиса, который поставляется самой системой и которому мы можем доверять (не можем и гугл знает о вас все 🤷♂️).
👉 Теперь приложения не ходят на прямую к датчику GPS, а просят данные у системного сервиса, который в свою очередь решает кому эти данные отдавать, а кому нет, как часто и с какой точностью. Как реализовать такой механизм, в чем отличие этого сервиса от нашего приложения?
💻Каждое приложение в Android работает в своем процессе и со своим экземпляром JVM это называется "Песочница". Механизм песочницы основан на том, что каждому приложению назначается уникальный user ID (UID) и group ID (GID). Значит, у каждого приложения в Android есть свой непривилегированный пользователь 👨.
🤴 В системе также есть привилегированные пользователи, имена и идентификаторы которых жестко зашиты в систему⚙️. Они нужны для сервисов которые имеют доступ к критичным секциям ресурсов (GPS, контакты, ваши любовные смс-ки). У обычных приложений нет доступа к критичным секциям, так как у них непривилегированный пользователь 🤕.
🤗Все круто, безопасно, но как теперь данные то получать с сервиса, ведь наше приложение и сервис работают в разных процессах? Как вы уже догадались ни один из способов описанных в предыдущем посте не подойдет. И тут на сцену выходит Binder.
👉 Binder IPC это фреймворк межпроцессной коммуникации, который пришел на замену System V IPCs. Кто не в теме, System V IPCs по сути это набор низкоуровневых функций, которые реализованы на уровне ядра, позволяющие обмениваться данными между процессами так, будто у них есть общая память (надеюсь меня не читают лютые линуксойды, иначе закидают ссанымы тряпками за такое упрощение 😅).
👉Ну так вот, этот Binder IPC невероятно сложная и крутая штука, которая решает нашу проблему. Фреймворк позволяет синхронно и асинхронно вызывать методы удаленных объектов (значит методы приложений/сервисов, которые работают в другом процессе) так, будто они локальные.
🎛 Все остальные способы взаимодействия между процессами, включая Intents и Content Provider, реализованы используя Binder IPC. При помощи фреймворка мы можем попросить системный сервис дергать функции нашего приложения когда, допустим меняется локация пользователя 📍.
Что это такое мы разобрали, а как он работает разберем в следующем посте 🤚
☝️Мы разбираем тему отталкиваясь от проблемы. В прошлом посте мы разбирали пример с банковским приложением и калькулятором. Этот пример довольно синтетический, он далек от реальности. Разберем реальную проблему.
У нас на устройствах есть📍GPS и приложения, которые хотят использовать его: такси, карты, навигатор и т.д. Туева хуча приложений и давать прямой доступ довольно рискованно, потому как вдруг левое приложение захочет в фоне отслеживать нашу позицию🥷.
🤔Значит доступ к датчику GPS должен быть только у некоторого системного сервиса (по сути приложение, только без UI). Сервиса, который поставляется самой системой и которому мы можем доверять (не можем и гугл знает о вас все 🤷♂️).
👉 Теперь приложения не ходят на прямую к датчику GPS, а просят данные у системного сервиса, который в свою очередь решает кому эти данные отдавать, а кому нет, как часто и с какой точностью. Как реализовать такой механизм, в чем отличие этого сервиса от нашего приложения?
💻Каждое приложение в Android работает в своем процессе и со своим экземпляром JVM это называется "Песочница". Механизм песочницы основан на том, что каждому приложению назначается уникальный user ID (UID) и group ID (GID). Значит, у каждого приложения в Android есть свой непривилегированный пользователь 👨.
🤴 В системе также есть привилегированные пользователи, имена и идентификаторы которых жестко зашиты в систему⚙️. Они нужны для сервисов которые имеют доступ к критичным секциям ресурсов (GPS, контакты, ваши любовные смс-ки). У обычных приложений нет доступа к критичным секциям, так как у них непривилегированный пользователь 🤕.
🤗Все круто, безопасно, но как теперь данные то получать с сервиса, ведь наше приложение и сервис работают в разных процессах? Как вы уже догадались ни один из способов описанных в предыдущем посте не подойдет. И тут на сцену выходит Binder.
👉 Binder IPC это фреймворк межпроцессной коммуникации, который пришел на замену System V IPCs. Кто не в теме, System V IPCs по сути это набор низкоуровневых функций, которые реализованы на уровне ядра, позволяющие обмениваться данными между процессами так, будто у них есть общая память (надеюсь меня не читают лютые линуксойды, иначе закидают ссанымы тряпками за такое упрощение 😅).
👉Ну так вот, этот Binder IPC невероятно сложная и крутая штука, которая решает нашу проблему. Фреймворк позволяет синхронно и асинхронно вызывать методы удаленных объектов (значит методы приложений/сервисов, которые работают в другом процессе) так, будто они локальные.
🎛 Все остальные способы взаимодействия между процессами, включая Intents и Content Provider, реализованы используя Binder IPC. При помощи фреймворка мы можем попросить системный сервис дергать функции нашего приложения когда, допустим меняется локация пользователя 📍.
Что это такое мы разобрали, а как он работает разберем в следующем посте 🤚
👍11❤1
💻 Чтобы глубже и яснее понять как работает Binder IPC вернемся на шаг назад и вспомним что такое Service в Android. Документация Android говорит нам о том, что есть 3 типа сервисов: Background services, Foreground services, и Bound services. Нас интересует последний, Bound services. Cуть в том, что этот тип позволяет подключить Service к Activity и напрямую вызывать методы Service из Activity. Другими словами Bound service позволяет сделать общение между Activity и Service по клиент-серверной модели.
👉 Все из вас кто хоть когда-то залазил в класс Service в Android должны были заметить, что там есть метод onBind() который возвращает объект IBinder. В большинстве случаев мы просто забиваем и возвращаем null, но ведь этот метод там явно не просто так, да еще и интерфейс так называется IBinder, прям в тему, смекаете😉? Чтобы сделать Bound Service, необходимо определить класс, унаследованный от класса Binder и определить в нем некоторую логику которая будет исполняться на сервисе. Например, что-то вроде:
Затем нужно запустить этот сервис через специальный метод контекста bindService(). Когда сервис подключится к нашей Activity вызовется callback onServiceConnected() у объекта ServiceConnection. После чего мы приводим входной аргумент IBinder к нашему конкретному классу и можем напрямую вызывать методы сервиса из нашей Activity:
Это все, теперь можно сделать межпроцессное общение❓
Неа😄 это работает только в том случае, если и Service и Activity работают в одном процессе.
Тогда для чего такие сложности и зачем это нужно❓
🤷 По большей части если речь идет о выполнении в одном процессе оно и правда может быть лишним, так как данные можно передавать и через data слой посредством например RxJava.
Нам сейчас это нужно для понимания того, как работает Binder. Ведь для общения между двумя процессами, нужно лишь слегка исправить наш пример. А что конкретно нужно исправить, обсудим в следующем посте📝
👉 Все из вас кто хоть когда-то залазил в класс Service в Android должны были заметить, что там есть метод onBind() который возвращает объект IBinder. В большинстве случаев мы просто забиваем и возвращаем null, но ведь этот метод там явно не просто так, да еще и интерфейс так называется IBinder, прям в тему, смекаете😉? Чтобы сделать Bound Service, необходимо определить класс, унаследованный от класса Binder и определить в нем некоторую логику которая будет исполняться на сервисе. Например, что-то вроде:
class CalculatorService : Service() {
override fun onBind(intent: Intent?): IBinder = Calculator()
class Calculator : Binder() {
fun sum(first: Int, second: Int): Int = first + second
}
}
Затем нужно запустить этот сервис через специальный метод контекста bindService(). Когда сервис подключится к нашей Activity вызовется callback onServiceConnected() у объекта ServiceConnection. После чего мы приводим входной аргумент IBinder к нашему конкретному классу и можем напрямую вызывать методы сервиса из нашей Activity:
val intent = Intent(this, CalculatorService::class.java)
bindService(intent, object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val calculator = service as CalculatorService.Calculator
val result = calculator.sum(1, 2)
}
override fun onServiceDisconnected(name: ComponentName?) = Unit
}, BIND_AUTO_CREATE)
Это все, теперь можно сделать межпроцессное общение❓
Неа😄 это работает только в том случае, если и Service и Activity работают в одном процессе.
Тогда для чего такие сложности и зачем это нужно❓
🤷 По большей части если речь идет о выполнении в одном процессе оно и правда может быть лишним, так как данные можно передавать и через data слой посредством например RxJava.
Нам сейчас это нужно для понимания того, как работает Binder. Ведь для общения между двумя процессами, нужно лишь слегка исправить наш пример. А что конкретно нужно исправить, обсудим в следующем посте📝
👍4❤1👎1
В прошлом посте разобрали ситуацию как подключить Service к компоненту, чтобы обмениваться данными. Для этого мы использовали наследник класса Binder.
Возникает вопрос, что теперь делать если этот компонент и сервис находятся в разных процессах? Варианта два: Messenger и AIDL.
☝️Для начала рассмотрим вариант с Messenger. По сути Messenger это некая обертка над Handler, которая позволяет принимать сообщения из других процессов. У Messenger довольно простое API, рассмотрим на примере. Для начала делаем сервис:
👉 Затем уже известный код в Activity:
На этом все 👐, теперь можно отправлять сообщения в другой сервис. На практике Messenger используют крайне редко, функционал довольно ограничен, да и построено все на коллбэках.
Если мы захотим, чтобы такой сервис еще и отвечал обратно, т.е отправлял сообщения нам, то на клиенте мы тоже должны создавать объект Messenger, который нужно будет передавать в каждом сообщении. И опять-таки, сообщения мы будем принимать через Handler, что означает, что мы можем принимать сообщения только на том потоке, где этот Handler создан. Если кто-то хочет примеров код, то го сюда и сюда.
Чувствуете муторная фигня ☹️, очень много действий нужно сделать, и все общение при этом асинхронное. Мы же хотим, чтобы общение происходило синхронно, чтобы вызывать функции на другом процессе, как будто они в нашем, без этих сообщений и коллбэков.
Для этого опускаемся еще на уровень ниже ⤵️.
Возникает вопрос, что теперь делать если этот компонент и сервис находятся в разных процессах? Варианта два: Messenger и AIDL.
☝️Для начала рассмотрим вариант с Messenger. По сути Messenger это некая обертка над Handler, которая позволяет принимать сообщения из других процессов. У Messenger довольно простое API, рассмотрим на примере. Для начала делаем сервис:
class CalculatorMessengerService : Service() {
class IncomingHandler(private val context: Context, looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> Toast.makeText(context, "hello", Toast.LENGTH_SHORT).show()
else -> super.handleMessage(msg)
}
}
}
override fun onBind(intent: Intent?): IBinder? {
val messenger = Messenger(IncomingHandler(this))
return messenger.binder
}
}
👉 Затем уже известный код в Activity:
val intent = Intent(this, CalculatorMessengerService::class.java)
bindService(intent, object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) = Unit
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val messenger = Messenger(service)
val message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
messenger.send(message)
}
}, BIND_AUTO_CREATE)
На этом все 👐, теперь можно отправлять сообщения в другой сервис. На практике Messenger используют крайне редко, функционал довольно ограничен, да и построено все на коллбэках.
Если мы захотим, чтобы такой сервис еще и отвечал обратно, т.е отправлял сообщения нам, то на клиенте мы тоже должны создавать объект Messenger, который нужно будет передавать в каждом сообщении. И опять-таки, сообщения мы будем принимать через Handler, что означает, что мы можем принимать сообщения только на том потоке, где этот Handler создан. Если кто-то хочет примеров код, то го сюда и сюда.
Чувствуете муторная фигня ☹️, очень много действий нужно сделать, и все общение при этом асинхронное. Мы же хотим, чтобы общение происходило синхронно, чтобы вызывать функции на другом процессе, как будто они в нашем, без этих сообщений и коллбэков.
Для этого опускаемся еще на уровень ниже ⤵️.
👍10
Второй способ взаимодействовать с сервисом на другом процессе это AIDL.
👉 AIDL или Android Interface Definition Language, это язык, который позволяет нам описывать интерфейсы межпроцессного общения. Язык чем-то похож на урезанную java с некоторыми интересными ключевыми словами.
🙌 Суть в чем, описываем в интерфейсе те функции какие мы хотим вызывать на другом процессе, а также входные аргументы и тип результата. Пример того, как может выглядеть этот интерфейс:
В простых примерах синтаксис вообще не отличается от java (кстати заметили ублюдский🤬 префикс "I" будто мы в C#, в AIDL по-прежнему используется этот пережиток прошлого). После того как мы написали интерфейс, компилятор генерирует классы Proxy и Stub.
Proxy – то, что использует клиент, чтобы вызывать функции другого процесса так, будто они находятся в одном процессе.
Stub – использует сервис, или по другому сервис теперь должен реализовать методы, которые мы определили в интерфейсе.
Проще будет понять на примере, сервис будет выглядеть так:
На стороне клиента мы должны теперь получить Proxy, делаем мы это через байндинг этого сервиса и уже сгенеренных методов ICalculator:
На этом все, теперь можно напряму вызывать методы другого процесса.
Proxy делает "маршалинг" (Marshalling), т.е перегоняет аргументы функций в последовательность байт. Stub соответственно делает "анмаршалинг" (Unmarshaling), перегоняет поток байт обратно в объекты, которые описаны в AIDL.
🤓 Еще раз пройдемся по всем компонентам которые у нас есть, советую параллельно смотреть в картинку.
У каждого Binder есть Token, по сути это просто некоторый desriptor, который помогает системе понять что это конкретно за реализация Binder. Если заглянуть в метод sum, который сгенерирован в proxy, мы увидим что там первой строчкой при маршалинге будет:
Эта строчка означает, что когда мы передаем массив байт в Stub, первые байты будут нести информацию о самом Binder.
Интерфейс IBinder это интерфейс, который описывает методы нужные для межпроцессной коммуникации. Binder стандартная реализация интерфейса IBinder, в Binder реализованы все сложные методы коммуникации и который мы можем расширять, чтобы использовать. Stub наследует Binder и нам остается только реализовать те методы которые мы указали в AIDL. Proxy использует объект IBinder, просто передать данные в Stub.
И теперь представь 🌈, все Intent, ContentProvider, все сервисы которые мы получаем через getSystemService() - все это работает через Binder и AIDL, круто не правда ли?
🎉 Если ты дочитал до сюда и примерно сможешь это пересказать, то я тебя поздравляю ты охренеть какая умничка и теперь сможешь выпендится на собесе. Тема очень сложная и используется только на очень больших проектах которым мало одного процесса или в каких-нибудь SDK, как например Яндекс.Метрика.
В посте было очень много упрощений, чтобы не делать его супер большим, если тебе понравилась тема стоит посмотреть этот доклад, который расскажет все это, но подробнее.
👉 AIDL или Android Interface Definition Language, это язык, который позволяет нам описывать интерфейсы межпроцессного общения. Язык чем-то похож на урезанную java с некоторыми интересными ключевыми словами.
🙌 Суть в чем, описываем в интерфейсе те функции какие мы хотим вызывать на другом процессе, а также входные аргументы и тип результата. Пример того, как может выглядеть этот интерфейс:
// ICalculator.aidl
interface ICalculator {
int sum(int first, int second);
}
В простых примерах синтаксис вообще не отличается от java (кстати заметили ублюдский🤬 префикс "I" будто мы в C#, в AIDL по-прежнему используется этот пережиток прошлого). После того как мы написали интерфейс, компилятор генерирует классы Proxy и Stub.
Proxy – то, что использует клиент, чтобы вызывать функции другого процесса так, будто они находятся в одном процессе.
Stub – использует сервис, или по другому сервис теперь должен реализовать методы, которые мы определили в интерфейсе.
Проще будет понять на примере, сервис будет выглядеть так:
override fun onBind(intent: Intent?): IBinder {
return object : ICalculator.Stub() {
override fun sum(first: Int, second: Int): Int {
return first + second
}
}
}
На стороне клиента мы должны теперь получить Proxy, делаем мы это через байндинг этого сервиса и уже сгенеренных методов ICalculator:
val intent = Intent(this, CalculatorMessengerService::class.java)
bindService(intent, object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) = Unit
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val proxy = ICalculator.Stub.asInterface(service)
val sum = proxy.sum(1, 4)
}
}, BIND_AUTO_CREATE)
На этом все, теперь можно напряму вызывать методы другого процесса.
Proxy делает "маршалинг" (Marshalling), т.е перегоняет аргументы функций в последовательность байт. Stub соответственно делает "анмаршалинг" (Unmarshaling), перегоняет поток байт обратно в объекты, которые описаны в AIDL.
🤓 Еще раз пройдемся по всем компонентам которые у нас есть, советую параллельно смотреть в картинку.
У каждого Binder есть Token, по сути это просто некоторый desriptor, который помогает системе понять что это конкретно за реализация Binder. Если заглянуть в метод sum, который сгенерирован в proxy, мы увидим что там первой строчкой при маршалинге будет:
_data.writeInterfaceToken(DESCRIPTOR);
Эта строчка означает, что когда мы передаем массив байт в Stub, первые байты будут нести информацию о самом Binder.
Интерфейс IBinder это интерфейс, который описывает методы нужные для межпроцессной коммуникации. Binder стандартная реализация интерфейса IBinder, в Binder реализованы все сложные методы коммуникации и который мы можем расширять, чтобы использовать. Stub наследует Binder и нам остается только реализовать те методы которые мы указали в AIDL. Proxy использует объект IBinder, просто передать данные в Stub.
И теперь представь 🌈, все Intent, ContentProvider, все сервисы которые мы получаем через getSystemService() - все это работает через Binder и AIDL, круто не правда ли?
🎉 Если ты дочитал до сюда и примерно сможешь это пересказать, то я тебя поздравляю ты охренеть какая умничка и теперь сможешь выпендится на собесе. Тема очень сложная и используется только на очень больших проектах которым мало одного процесса или в каких-нибудь SDK, как например Яндекс.Метрика.
В посте было очень много упрощений, чтобы не делать его супер большим, если тебе понравилась тема стоит посмотреть этот доклад, который расскажет все это, но подробнее.
🔥10
{1/3} Итак, Classloader. Погнали.
👉 Нужна эта штука как можно догадаться по названию для загрузки классов. Происходит это так. Есть код написанный на языках kotlin/java, затем мы отдаем эти файлы компилятору javac который уже генерирует файлы с расширением .class.
Далее эти файлы упаковываются в jar (или в apk если мы говором про android) что по сути является обычным архивом, просто с другим расширением, чтобы не путать с zip.
🤓 Суть в чем, JVM не загружает сразу все классы, она подгружает их лениво 🦥. Могут быть классы которые так и не подгрузятся JVM, несмотря на то, что они скомпилированны и лежат в jar.
За загрузку классов отвечает специальный класс (мде вот такая вот тавтология)Classloader. Это некоторый волшебный класс, один из тех классов который находится на границе между JVM и нативным кодом.
👉 Когда JVM считает, что ей нужно загрузить определенный класс, она просить Classloader это сделать. Classloader в свою очередь лезет в файловую систему, в сеть, еще куда-нибудь чтобы эти самые классы подгрузить.
Classloader это всего лишь интерфейс, а реализаций может быть много. Например, может быть Classloader который загружает классы по сети, так работали Applet.
☝️Чтобы понять идею того, как вообще работает Classloader, нужно уяснить 3 основных принципа. Представим что у нас есть реализация Classloader Parent, и есть класс наследник который расширяет функционал класса Parent – Child. Child наследует Parent все логично
👉 Нужна эта штука как можно догадаться по названию для загрузки классов. Происходит это так. Есть код написанный на языках kotlin/java, затем мы отдаем эти файлы компилятору javac который уже генерирует файлы с расширением .class.
Далее эти файлы упаковываются в jar (или в apk если мы говором про android) что по сути является обычным архивом, просто с другим расширением, чтобы не путать с zip.
🤓 Суть в чем, JVM не загружает сразу все классы, она подгружает их лениво 🦥. Могут быть классы которые так и не подгрузятся JVM, несмотря на то, что они скомпилированны и лежат в jar.
За загрузку классов отвечает специальный класс (мде вот такая вот тавтология)Classloader. Это некоторый волшебный класс, один из тех классов который находится на границе между JVM и нативным кодом.
👉 Когда JVM считает, что ей нужно загрузить определенный класс, она просить Classloader это сделать. Classloader в свою очередь лезет в файловую систему, в сеть, еще куда-нибудь чтобы эти самые классы подгрузить.
Classloader это всего лишь интерфейс, а реализаций может быть много. Например, может быть Classloader который загружает классы по сети, так работали Applet.
☝️Чтобы понять идею того, как вообще работает Classloader, нужно уяснить 3 основных принципа. Представим что у нас есть реализация Classloader Parent, и есть класс наследник который расширяет функционал класса Parent – Child. Child наследует Parent все логично
👍4
{2/3} 3 принципа: принцип делегирования, принцип видимости и принцип уникальности.
👉 Принцип делегирования
Если система попросит Child загрузить какой-то класс, то в первую очередь Child передаст этот запрос на загрузку класса к Parent. И если Parent не знает откуда взять такой класс или не может его загрузить, только тогда уже Child попытается его загрузить.
👉 Принцип видимости
Child видит все классы который загрузил Parent. Однако Parent не может видеть классы, которые загрузил Child.
👉 Принцип уникальности
Этот принцип гарантирует нам, что класс будет загружен только единожды. Другими словами, если Parent загрузил класс, то Child уже точно этот класс загружать не будет. Помимо этого Parent обязан закешировать класс и больше не пытаться его загрузить из внешнего источника.
По дефолту есть 3 основных Classloader: Bootstrap, Extension, System или Application ClassLoader.
Рассмотрим вкратце кто за что отвечает:
Bootstrap - подгружает классы, которые поставляются JRE, т.е базовые классы пакета java.lang, всякие String и т.д.
Extension - наследник класса Bootstrap;, отвечает за загрузку классов из папки jre/lib/ext или в директории которая будет прописана в системных настройках под ключом java.ext.dirs. Мы можем положить свой jar в эту папку и не прокидывать эту зависимость в наше приложение. Однако запускаться корректно оно теперь будет только на этом компе)
Application ClassLoader; - наследник класса Extension, и этот Classloader уже отвечает за загрузку class файлов из нашего jar/apk. Именно этот Classloader подгружает классы нашего приложения.
Для Application ClassLoader нужно указать с какого файла нужно начать подгружать классы нашего приложения, другими словами относительный путь до класса, в котором есть тот самый
Есть 3️⃣ варианта, команда -cp при запуске нашего jar из консоли, переменная среды CLASSPATH, или самое распространённое это сделать в jar Manifest файл. В этом Manifest файле прописать строку Class-Path. Последнее обычно делает gradle за нас.
Для Android приложения этого делать не нужно, за нас это все делает система, нам же просто нужно указать точки входа типа Activity, Service и т.д.
👉 Принцип делегирования
Если система попросит Child загрузить какой-то класс, то в первую очередь Child передаст этот запрос на загрузку класса к Parent. И если Parent не знает откуда взять такой класс или не может его загрузить, только тогда уже Child попытается его загрузить.
👉 Принцип видимости
Child видит все классы который загрузил Parent. Однако Parent не может видеть классы, которые загрузил Child.
👉 Принцип уникальности
Этот принцип гарантирует нам, что класс будет загружен только единожды. Другими словами, если Parent загрузил класс, то Child уже точно этот класс загружать не будет. Помимо этого Parent обязан закешировать класс и больше не пытаться его загрузить из внешнего источника.
По дефолту есть 3 основных Classloader: Bootstrap, Extension, System или Application ClassLoader.
Рассмотрим вкратце кто за что отвечает:
Bootstrap - подгружает классы, которые поставляются JRE, т.е базовые классы пакета java.lang, всякие String и т.д.
Extension - наследник класса Bootstrap;, отвечает за загрузку классов из папки jre/lib/ext или в директории которая будет прописана в системных настройках под ключом java.ext.dirs. Мы можем положить свой jar в эту папку и не прокидывать эту зависимость в наше приложение. Однако запускаться корректно оно теперь будет только на этом компе)
Application ClassLoader; - наследник класса Extension, и этот Classloader уже отвечает за загрузку class файлов из нашего jar/apk. Именно этот Classloader подгружает классы нашего приложения.
Для Application ClassLoader нужно указать с какого файла нужно начать подгружать классы нашего приложения, другими словами относительный путь до класса, в котором есть тот самый
public static void main
Есть 3️⃣ варианта, команда -cp при запуске нашего jar из консоли, переменная среды CLASSPATH, или самое распространённое это сделать в jar Manifest файл. В этом Manifest файле прописать строку Class-Path. Последнее обычно делает gradle за нас.
Для Android приложения этого делать не нужно, за нас это все делает система, нам же просто нужно указать точки входа типа Activity, Service и т.д.
👍3
Эскюзмуа, я немножечко опаздасьон. Давай-те предствим что я просто поставил рассылку продолжения серии постов на через 3 месяца 😄
Итак вернемся к нашей теме! Как происходит загрузка класса? JVM запускает тот самый
Application Classloader не лезет сразу же в файловую систему, чтобы подгрузить классы. Для начала он ищет этот класс в кеше, если его там нет, то просит загрузить этот класс своего предка Последний использует аналогичный метод, эдакая обратная порука, где каждый пытается спихнуть работу на старшего.
Если ни один из Parentо’в не смог загрузить класс, только тогда Application Classloader лезет в файл/сеть и куда-то еще чтобы найти этот класс. И если он его не находит то кидает тот самый ClassNotFoundException или NoClassDefFoundError. Последние две ошибки вы могли ловить если в gradle указали compileOnly вместо implement (т.е сделали так, чтобы классы были доступны только во время компиляции)
👉Для чего это нужно?
1️⃣ Ну во-первых, чтобы вы знали куда копать если вдруг встретите ClassNotFoundException или NoClassDefFoundError.
2️⃣ Во-вторых эта технология уже ближе к хардкорному rocket science, и я надеюсь, что кто меня читает не хотят всю жизнь перекладывать жесоны. Знание этой технологии позволяет делать штуки вроде JRebel, она позволяет сделать подгрузку измененых классов без перезапуска приложения, что очень кстати для тех кто разрабатывает сервера. Помимо этого, она позволяет сделать штуку вроде импакт анализа о которой мы поговорим позже)
Итак вернемся к нашей теме! Как происходит загрузка класса? JVM запускает тот самый
static void main, и начинает подгружать классы указанные в import. Сначала она идет в Application Classloader.
Application Classloader не лезет сразу же в файловую систему, чтобы подгрузить классы. Для начала он ищет этот класс в кеше, если его там нет, то просит загрузить этот класс своего предка Последний использует аналогичный метод, эдакая обратная порука, где каждый пытается спихнуть работу на старшего.
Если ни один из Parentо’в не смог загрузить класс, только тогда Application Classloader лезет в файл/сеть и куда-то еще чтобы найти этот класс. И если он его не находит то кидает тот самый ClassNotFoundException или NoClassDefFoundError. Последние две ошибки вы могли ловить если в gradle указали compileOnly вместо implement (т.е сделали так, чтобы классы были доступны только во время компиляции)
👉Для чего это нужно?
1️⃣ Ну во-первых, чтобы вы знали куда копать если вдруг встретите ClassNotFoundException или NoClassDefFoundError.
2️⃣ Во-вторых эта технология уже ближе к хардкорному rocket science, и я надеюсь, что кто меня читает не хотят всю жизнь перекладывать жесоны. Знание этой технологии позволяет делать штуки вроде JRebel, она позволяет сделать подгрузку измененых классов без перезапуска приложения, что очень кстати для тех кто разрабатывает сервера. Помимо этого, она позволяет сделать штуку вроде импакт анализа о которой мы поговорим позже)
👍3
Я давно ничего не постил в канал, сейчас решил прервать молчание, так как сейчас я менторю джунов и тех кто только начинает вливаться в профессию и у меня появились мысли которыми могу поделится. Когда только начинаешь вливаться в разработку часто источником информации являются книги или различные статьи. Беда книг по Android в том, что они быстро устаревают. Устаревают после каждого google IO, а статьи порой пишутся просто, чтобы написать.
Поэтому я и создал этот канал где пишу про фундаментальные штуки, которые меняются оочень редко и стараюсь не писать про то, что можно и так легко найти в документации.
Поэтому я и создал этот канал где пишу про фундаментальные штуки, которые меняются оочень редко и стараюсь не писать про то, что можно и так легко найти в документации.
👍3
1️⃣ Хранение переменных в Activity
Это касается всего что связано с UI. Почему это проблема? Да потому что Activity – компонент приложения Android фреймворка. Это значит, что мы вообще её не контролируем. Activity контролирует система, в любой момент Activity может просто перестать существовать и вы ничего с этим не сделаете. Всегда думайте о том, что будет если Activity пересоздастся!
Это же касается и списков. Не нужно во ViewHolder делать какие-либо переменные, потому как Adapter это тоже фреймворк, который мы не контролируем.
Да можно сохранять переменные в bundle, но не все туда можно сохранить, не говоря уже о том, что он ограничен по размеру (1kb это кстати популярный вопрос на собесах). Если можно избежать переменных в Activity, Fragment... лучше не делать. Хотите сохранить переменные сохраняйте их во ViewModel или что-то другое что не связано с компонентами приложения. Только не сохраняйте данные в статику, об этом я напишу в других постах.
❗️Важно это не относится к данным типа id которые мы можем передавать из одной Activity в другую, эти штуки мелкие и вот их мы передаем из через Bundle.
2️⃣ Обработка ошибок
Часто можно заметить, что на исключение которые прилетают при запросе в сеть либо забивают, либо делают что-то вроде
Удивительно, но на этом даже подрываются даже опытные разрабы. Всегда держите это в голове, особенно если у вас несколько запросов, которые идут подряд. Докапывайтесь до бизнес аналитиков и дизайнеров чтобы они описали что делать, если запрос упадет.
3️⃣ Переусложнение
Вот это вообще боль. Кто-то однажды сказал, что лучший джун, это джун который выгорел, и это блин действительно так. Это уже больше относиться с soft скиллам, а не hard, но это супер важно. У всех у нас есть амбиции стать крутыми разрабами, показать что мы можем решить любую задачу и написать сложный код. Я понимаю, сам таким был, охото написать весь проект на вечер, прикрутить крутую анимацию даже если она не нужна, написать более оптимизированный код принеся в жертву читабельность.
Совет один НЕ НУЖНО ЭТО ДЕЛАТЬ. Поверьте у вас в карьере еще будет возможность показать что вы крутые, не бегите впереди паровоза. Очень большая вероятность того, что вы наломаете дров и придется переделывать.
Возникает вопрос "А как понять что переусложню?". Лучше всего руководствоваться логикой. Если для решения простой задачи, типа изменения элемента в списке или перекрашивания кнопки у вас получается прям дофига кода стоит задуматься. До вас уже решили все задачи, погуглите, смотрите код других проектов, чтение кода других очень сильно прокачивает.
Суть – не нужно писать сложный код, нужно писать настолько простой код насколько это вообще возможно. Чем меньше кода нужно для решения задачи тем лучше. Есть даже такой принцип KISS.
4️⃣ Преждевременная оптимизация
Это сильно связано с прошлым пунктом. Суть в том, что не нужно писать супер быстрый код. Если вы не пишете игру или операционную систему, то вам не нужно выжимать все из железа. Да конечно не нужно использовать заведомо дурацкие решения типа сортировки пузырьком, или ходить в базу данных на UI потоке.
При этом не нужно на начально этапе париться о том, как сделать мега быструю отрисовку или как сделать параллельный алгоритм где достаточно однопоточного. Страуструп сказал "Преждевременная оптимизация корень всех зол", поэтому лучше парьтесь над читабельностью кода и его краткостью, оптимизацию будете делать позже, или никогда 😄.
Это касается всего что связано с UI. Почему это проблема? Да потому что Activity – компонент приложения Android фреймворка. Это значит, что мы вообще её не контролируем. Activity контролирует система, в любой момент Activity может просто перестать существовать и вы ничего с этим не сделаете. Всегда думайте о том, что будет если Activity пересоздастся!
Это же касается и списков. Не нужно во ViewHolder делать какие-либо переменные, потому как Adapter это тоже фреймворк, который мы не контролируем.
Да можно сохранять переменные в bundle, но не все туда можно сохранить, не говоря уже о том, что он ограничен по размеру (1kb это кстати популярный вопрос на собесах). Если можно избежать переменных в Activity, Fragment... лучше не делать. Хотите сохранить переменные сохраняйте их во ViewModel или что-то другое что не связано с компонентами приложения. Только не сохраняйте данные в статику, об этом я напишу в других постах.
❗️Важно это не относится к данным типа id которые мы можем передавать из одной Activity в другую, эти штуки мелкие и вот их мы передаем из через Bundle.
2️⃣ Обработка ошибок
Часто можно заметить, что на исключение которые прилетают при запросе в сеть либо забивают, либо делают что-то вроде
printStackTrace
. 🙅♂️ Не делайте так, хотя бы в лог отправляйте исключение. Вот тут закон Мерфи работает вообще на максимум, все что может упасть упадет, но приложение должно продолжать работу. Продолжать работу это значит что не просто проглотить ошибку, а сообщить об этом пользователю и сказать что ему делать дальше. Удивительно, но на этом даже подрываются даже опытные разрабы. Всегда держите это в голове, особенно если у вас несколько запросов, которые идут подряд. Докапывайтесь до бизнес аналитиков и дизайнеров чтобы они описали что делать, если запрос упадет.
3️⃣ Переусложнение
Вот это вообще боль. Кто-то однажды сказал, что лучший джун, это джун который выгорел, и это блин действительно так. Это уже больше относиться с soft скиллам, а не hard, но это супер важно. У всех у нас есть амбиции стать крутыми разрабами, показать что мы можем решить любую задачу и написать сложный код. Я понимаю, сам таким был, охото написать весь проект на вечер, прикрутить крутую анимацию даже если она не нужна, написать более оптимизированный код принеся в жертву читабельность.
Совет один НЕ НУЖНО ЭТО ДЕЛАТЬ. Поверьте у вас в карьере еще будет возможность показать что вы крутые, не бегите впереди паровоза. Очень большая вероятность того, что вы наломаете дров и придется переделывать.
Возникает вопрос "А как понять что переусложню?". Лучше всего руководствоваться логикой. Если для решения простой задачи, типа изменения элемента в списке или перекрашивания кнопки у вас получается прям дофига кода стоит задуматься. До вас уже решили все задачи, погуглите, смотрите код других проектов, чтение кода других очень сильно прокачивает.
Суть – не нужно писать сложный код, нужно писать настолько простой код насколько это вообще возможно. Чем меньше кода нужно для решения задачи тем лучше. Есть даже такой принцип KISS.
4️⃣ Преждевременная оптимизация
Это сильно связано с прошлым пунктом. Суть в том, что не нужно писать супер быстрый код. Если вы не пишете игру или операционную систему, то вам не нужно выжимать все из железа. Да конечно не нужно использовать заведомо дурацкие решения типа сортировки пузырьком, или ходить в базу данных на UI потоке.
При этом не нужно на начально этапе париться о том, как сделать мега быструю отрисовку или как сделать параллельный алгоритм где достаточно однопоточного. Страуструп сказал "Преждевременная оптимизация корень всех зол", поэтому лучше парьтесь над читабельностью кода и его краткостью, оптимизацию будете делать позже, или никогда 😄.
👍19❤2
Сначала разберем как может умереть Activity. Смерть Activity может произойти в двух случаях:
👉 Первый это когда что-то меняется в окружении, пользователь может заменить локаль, тему, ориентацию (телефона, а не свою), и т.д. В этом случае текущая Activity пересоздастся. Почему это сделано именно так? Так тупо проще, ведь нам нужно тянуть новые темы, ресурсы, картинки, а пересчитывать это все супер сложно.
👉 Второй, это когда наша Activity начинает кушать ну прям дофига ресурсов. Кейс достаточно редкий, и в основном появляется если вы делаете редактор фото или видео. В этом кейсе система берет кувалду и начинает рушить сначала все что вокруг нашего приложения, а после берется за наши Activity которые в фоне.
Activity которые оказались в фоне просто умирают, у них даже onDestroy не вызывается, но вызываются методы onSavedInstantState. Когда мы возвращаемся назад в эти экраны, система их оживляет обратно. Все ViewModel продолжают жить, и все что мы сохранили в Bundle тоже.
Со смертью Activity все довольно просто. Каких-то движений с вашей стороны не нужно, всякие мелкие Id сохраняем в Bundle, все остальное во ViewModel и не паримся. Однако есть случай когда и ViewModel не спасет.
Есть такое понятие как процесс. Это некоторый объект системы, т.е штука которой система выдает ресурсы в виде времени процессора, памяти в оперативке и места на диске. Наше приложение работает в каком-то процессе (порой может даже в нескольких писал об этом тут). Процесс так же как и Activity может умереть.
Как это обычно происходит. Мы отправляем наше приложение в фон (сворачиваем) система через какое-то время думает ага, пользователь вот про это приложение забыл, освобожу ка я память. Берет и выгружает процесс. Что значит выгружает процесс? Просто все что было связано с этим приложением в оперативной памяти затирает. Затирает статику, затирает все ViewModel, вообще все что не сохраняется в Bundle.
В этот момент система говорит Activity, всем Activity которые сейчас есть в этом приложении. Причем неважно видны они пользователю или нет, система говорит, сохраняйте что-то важное в Bundle, все что не сохранено в Bundle будет забыто на веки.
А после у нас два путя.
👉 Первый мы давно не заходим в приложение, и система окончательно все очищает, даже сохранённый Bundle, а когда пользователь возвращается в приложение оно запускается с самого начала, с первой Activity и т.д.
👉 Второй, это когда пользователь успевает вернуться в приложение до того, как система прибьет его окончательно. И когда пользователь вернется в приложение, угадайте какая Activity появится первой? Правильно, та которую он видел последней. А все остальные, ну их как бы нет, типо вообще нет, ни ViewModel ничего, только то, что сохранено в Bundle. Если пользователь начнет переходить назад они будут снова возвращаться с того света, но уже без ViewModel и т.д.
Так почему нельзя сохранять что-то в статику? Причина в выгрузке процесса приложения из памяти. Представим что у вас есть Activity X и Y. В Activity X вы получаете какие-то данные с сети и сохраняете их в статику. Затем переходим в Activity Y которая уже использует эти данные.
Дальше просто наш второй кейс. Пользователь сворачивает приложение, система выгружает его их памяти. Потом пользователь решает вернуться в приложение и у нас открывается Activity Y, которая пытается пойти в статику и получить данные. А данных там нет, так как вся статика которую сохраняла Activity X была очищена, и мы в лучшем случае просто падаем и быстро узнаем о проблеме.
👉 Первый это когда что-то меняется в окружении, пользователь может заменить локаль, тему, ориентацию (телефона, а не свою), и т.д. В этом случае текущая Activity пересоздастся. Почему это сделано именно так? Так тупо проще, ведь нам нужно тянуть новые темы, ресурсы, картинки, а пересчитывать это все супер сложно.
👉 Второй, это когда наша Activity начинает кушать ну прям дофига ресурсов. Кейс достаточно редкий, и в основном появляется если вы делаете редактор фото или видео. В этом кейсе система берет кувалду и начинает рушить сначала все что вокруг нашего приложения, а после берется за наши Activity которые в фоне.
Activity которые оказались в фоне просто умирают, у них даже onDestroy не вызывается, но вызываются методы onSavedInstantState. Когда мы возвращаемся назад в эти экраны, система их оживляет обратно. Все ViewModel продолжают жить, и все что мы сохранили в Bundle тоже.
Со смертью Activity все довольно просто. Каких-то движений с вашей стороны не нужно, всякие мелкие Id сохраняем в Bundle, все остальное во ViewModel и не паримся. Однако есть случай когда и ViewModel не спасет.
Есть такое понятие как процесс. Это некоторый объект системы, т.е штука которой система выдает ресурсы в виде времени процессора, памяти в оперативке и места на диске. Наше приложение работает в каком-то процессе (порой может даже в нескольких писал об этом тут). Процесс так же как и Activity может умереть.
Как это обычно происходит. Мы отправляем наше приложение в фон (сворачиваем) система через какое-то время думает ага, пользователь вот про это приложение забыл, освобожу ка я память. Берет и выгружает процесс. Что значит выгружает процесс? Просто все что было связано с этим приложением в оперативной памяти затирает. Затирает статику, затирает все ViewModel, вообще все что не сохраняется в Bundle.
В этот момент система говорит Activity, всем Activity которые сейчас есть в этом приложении. Причем неважно видны они пользователю или нет, система говорит, сохраняйте что-то важное в Bundle, все что не сохранено в Bundle будет забыто на веки.
А после у нас два путя.
👉 Первый мы давно не заходим в приложение, и система окончательно все очищает, даже сохранённый Bundle, а когда пользователь возвращается в приложение оно запускается с самого начала, с первой Activity и т.д.
👉 Второй, это когда пользователь успевает вернуться в приложение до того, как система прибьет его окончательно. И когда пользователь вернется в приложение, угадайте какая Activity появится первой? Правильно, та которую он видел последней. А все остальные, ну их как бы нет, типо вообще нет, ни ViewModel ничего, только то, что сохранено в Bundle. Если пользователь начнет переходить назад они будут снова возвращаться с того света, но уже без ViewModel и т.д.
Так почему нельзя сохранять что-то в статику? Причина в выгрузке процесса приложения из памяти. Представим что у вас есть Activity X и Y. В Activity X вы получаете какие-то данные с сети и сохраняете их в статику. Затем переходим в Activity Y которая уже использует эти данные.
Дальше просто наш второй кейс. Пользователь сворачивает приложение, система выгружает его их памяти. Потом пользователь решает вернуться в приложение и у нас открывается Activity Y, которая пытается пойти в статику и получить данные. А данных там нет, так как вся статика которую сохраняла Activity X была очищена, и мы в лучшем случае просто падаем и быстро узнаем о проблеме.
👍31❤1
Одна из основных проблем индустрии это сложность. Сложность систем делает изменения дорогими для компании, а разрабов делает несчастными. Никому не хочется чинить баги в запутанном коде, а охото быстрее писать новые фичи. Естественно решением этой проблемы это сделать код проще, но как это сделать? Я думал про совет который бы давал четкие рекомендации, по тому как это сделать.
Существует такая штука, как функциональное программирование (ФП). Тема ФП довольно обширна, в вузе это выносят на целый семестр. Само по себе ФП это больше академическая история нежели промышленная. Однако, как и инженерия использует достижения физики, мы также можем стырить пару идей из ФП.
Есть несколько довольно простых и четких понятий из ФП, которые легко понять и начать использовать. Они довольно неплохо улучшат ваш код:
👉 Чистые функции
👉 Иммутабельность
👉 Избегать исключений для бизнес логики
Все 3 не поместятся в один пост, поэтому я сделаю по каждой из этих концепций отдельный пост.
Существует такая штука, как функциональное программирование (ФП). Тема ФП довольно обширна, в вузе это выносят на целый семестр. Само по себе ФП это больше академическая история нежели промышленная. Однако, как и инженерия использует достижения физики, мы также можем стырить пару идей из ФП.
Есть несколько довольно простых и четких понятий из ФП, которые легко понять и начать использовать. Они довольно неплохо улучшат ваш код:
👉 Чистые функции
👉 Иммутабельность
👉 Избегать исключений для бизнес логики
Все 3 не поместятся в один пост, поэтому я сделаю по каждой из этих концепций отдельный пост.
👍17🔥3❤2
Что такое чистая функция? По определению это функция без side effect. Side effect это когда функция делает то, что сразу не очевидно. Например функция проверки даты рождения которая еще шлет оповещение. В чистой функции результат зависит только от входных параметров и ничего больше. Чтобы понять, без заумных слов достаточно простого примера:
Это просто функция умножения. Результат функции зависит только от входных параметров и ничего более. Сколько бы раз мы не вызывали эту функцию, если аргументы неизменны результат не изменится.
Теперь пример не очень чистой функции:
Эта функция считает сколько времени прошло с момента time. Как можно понять она каждый раз будет выдавать разное значение, значит это уже не чистая функция. Часто у начинающих можно встретить функцию типа:
Подвох в том, что каждый раз вызывая эту функцию меняется состояние объекта, а значит ее результат зависит от того, столько раз мы ее вызовем. Пример достаточно вымышленный и упрощенный, но описывает суть. Функций с side effect стоит избегать по максимуму.
Почему это так важно? Мы пишем код, с которым будут работать другие. Даже если вы один разработчик на проекте помните, что через пол года вы уже другой человек, который ничего не будет помнить про этот код. Как упражнение держите в голове, что вашей функцией будет пользоваться психопат, который не будет заглядывать внутрь функции. Что будет если он вызовет функцию 5 раз подряд, или будет вызывать функции не в том порядке, в котором вы задумывали? Вы должны предоставлять такой API который не позволит выстрелить себе в ногу.
Самый простой способ это сделать – стараться всегда писать чистые функции. Разумеется всегда писать их не получится, у вас всегда будут функции которые оповещают остальные части системы, меняют состояния View, что-то логируют. Эти функции 100% не получится сделать чистыми.
Совет тут такой, что лучше разделять логику. Делайте отдельно чистые функции и отдельно функции которые меняют состояние системы. Если можно сделать вместо одной большой грязной функции несколько, то лучше сделать отдельно чистую и отдельно функцию с sife effect.
fun multi(x: Int, y: Int): Int = x * y
Это просто функция умножения. Результат функции зависит только от входных параметров и ничего более. Сколько бы раз мы не вызывали эту функцию, если аргументы неизменны результат не изменится.
Теперь пример не очень чистой функции:
fun ticksElapsedFrom(time: Long): Int {
val now = System.currentTimeMillis()
return now - time
}
Эта функция считает сколько времени прошло с момента time. Как можно понять она каждый раз будет выдавать разное значение, значит это уже не чистая функция. Часто у начинающих можно встретить функцию типа:
fun getCount() : Int {
count++
return count;
}
Подвох в том, что каждый раз вызывая эту функцию меняется состояние объекта, а значит ее результат зависит от того, столько раз мы ее вызовем. Пример достаточно вымышленный и упрощенный, но описывает суть. Функций с side effect стоит избегать по максимуму.
Почему это так важно? Мы пишем код, с которым будут работать другие. Даже если вы один разработчик на проекте помните, что через пол года вы уже другой человек, который ничего не будет помнить про этот код. Как упражнение держите в голове, что вашей функцией будет пользоваться психопат, который не будет заглядывать внутрь функции. Что будет если он вызовет функцию 5 раз подряд, или будет вызывать функции не в том порядке, в котором вы задумывали? Вы должны предоставлять такой API который не позволит выстрелить себе в ногу.
Самый простой способ это сделать – стараться всегда писать чистые функции. Разумеется всегда писать их не получится, у вас всегда будут функции которые оповещают остальные части системы, меняют состояния View, что-то логируют. Эти функции 100% не получится сделать чистыми.
Совет тут такой, что лучше разделять логику. Делайте отдельно чистые функции и отдельно функции которые меняют состояние системы. Если можно сделать вместо одной большой грязной функции несколько, то лучше сделать отдельно чистую и отдельно функцию с sife effect.
👍27❤1
По названию понятно, что это что-то про неизменяемость. Просто запомните неизменяемость ваш лучший друг. Это значит – всегда предпочитайте неизменяемые коллекции вместо изменяемых (List вместо MutableList) и val вместо var. Изменяемость приводит к багам которые порой очень трудно отловить. Когда же объект полностью иммутабельный можно не парится что какие-то поля будут изменены без вашего ведома.
Начнем с базового примера:
Вроде бы обычный объект, что может пойти не так? Основная проблема этого объекта, что в любой момент можно заменить число, ссылку на список или сам список. Если вы передаете этот объект в какую-нибудь функцию, а потом еще куда-то, можно получить не консистентное состояние объекта. Как это может произойти: у вас было 3 числа в списке, и вы ожидаете что там будет 3 числа, а тут бац, а список пустой. Потому что какая-то функция не была чистой, она сначала сохранила ссылку на объект, а потом изменила состояние этого объекта.
Теперь рассмотрим тот же объект:
В этом случае мы защищены от любых изменений, можно передавать этот объект куда хочется, даже между потоками, ничего не произойдет. Возникает вопрос, что делать если нужно поменять что-то в этом объекте? Все просто, тупо создаем новый, сильно много памяти вы не скушаете, Android начиная с 7 версии очень шустро умеет избавляться от таких объектов. Про преждевременную оптимизацию писал в предыдущем посте.
Эту концепцию можно расширить не только на data class, но и в других классах бизнес логики. Если можно избавится от переменных нахер их. Все коллекции в data class делаем неизменяемыми, да и вообще все в data class лучше делать неизменяемыми. Изменяемыми коллекциями можно пользоваться только в рамках одного класса, а если отдаете список наружу, то уже отдавайте только неизменяемый.
Начнем с базового примера:
data class SomeInfo(
var count: Int,
var someList: MutableList<Int>
)
Вроде бы обычный объект, что может пойти не так? Основная проблема этого объекта, что в любой момент можно заменить число, ссылку на список или сам список. Если вы передаете этот объект в какую-нибудь функцию, а потом еще куда-то, можно получить не консистентное состояние объекта. Как это может произойти: у вас было 3 числа в списке, и вы ожидаете что там будет 3 числа, а тут бац, а список пустой. Потому что какая-то функция не была чистой, она сначала сохранила ссылку на объект, а потом изменила состояние этого объекта.
Теперь рассмотрим тот же объект:
data class SomeInfo(
val count: Int,
val someList: List<Int>
)
В этом случае мы защищены от любых изменений, можно передавать этот объект куда хочется, даже между потоками, ничего не произойдет. Возникает вопрос, что делать если нужно поменять что-то в этом объекте? Все просто, тупо создаем новый, сильно много памяти вы не скушаете, Android начиная с 7 версии очень шустро умеет избавляться от таких объектов. Про преждевременную оптимизацию писал в предыдущем посте.
Эту концепцию можно расширить не только на data class, но и в других классах бизнес логики. Если можно избавится от переменных нахер их. Все коллекции в data class делаем неизменяемыми, да и вообще все в data class лучше делать неизменяемыми. Изменяемыми коллекциями можно пользоваться только в рамках одного класса, а если отдаете список наружу, то уже отдавайте только неизменяемый.
👍21❤1
Объяснить концепцию можно через разницу в работе с исключениями м/у Java и Kotlin. В Java есть проверяемые и не проверяемые исключения. Когда используем проверяемые исключения компилятор заставляет их обрабатывать. Непроверяемые исключения как понятно из названия можно обрабатывать, можно нет компилятору пофиг. В Kotlin все исключения непроверяемые.
Творческое упражнение на подумоть. Как в Kotlin заставить пользователя функции заставить обрабатывать исключение на уровне компилятора? Вопрос кстати встречается на собесах!
В Kotlin есть только один способ заставить пользователя обрабатывать исключение на уровне компилятора. Нужно возвращать не сразу результат, а некоторую обертку над результатом.
Есть такой класс в стандартной библиотеки Kotlin – Result. Сам класс нельзя использовать как возвращаемый аргумент, т.к этот класс используется в корутинах. На сам класс пофиг, важна концепция. Если нужно заставить разработчика обработать ошибку, то вместо того, чтобы просто бросать исключение, мы можем возвращать Result. В одном случае в Result это нужный результат, во втором случае Result это класс с полем типа Thowable. Затем при помощи магии оператора when можно просто с ним работать. В таком случае у разраба не останется выбора кроме как проверить результат на success или fail.
Возможно вас заинтересовало а что не так с исключениями, поставьте пару сердец под постом и я сделаю отдельный про это.
Творческое упражнение на подумоть. Как в Kotlin заставить пользователя функции заставить обрабатывать исключение на уровне компилятора? Вопрос кстати встречается на собесах!
В Kotlin есть только один способ заставить пользователя обрабатывать исключение на уровне компилятора. Нужно возвращать не сразу результат, а некоторую обертку над результатом.
Есть такой класс в стандартной библиотеки Kotlin – Result. Сам класс нельзя использовать как возвращаемый аргумент, т.к этот класс используется в корутинах. На сам класс пофиг, важна концепция. Если нужно заставить разработчика обработать ошибку, то вместо того, чтобы просто бросать исключение, мы можем возвращать Result. В одном случае в Result это нужный результат, во втором случае Result это класс с полем типа Thowable. Затем при помощи магии оператора when можно просто с ним работать. В таком случае у разраба не останется выбора кроме как проверить результат на success или fail.
Возможно вас заинтересовало а что не так с исключениями, поставьте пару сердец под постом и я сделаю отдельный про это.
❤53👍11