Горячее
Лучшее
Свежее
Подписки
Сообщества
Блоги
Эксперты
Войти
Забыли пароль?
или продолжите с
Создать аккаунт
Я хочу получать рассылки с лучшими постами за неделю
или
Восстановление пароля
Восстановление пароля
Получить код в Telegram
Войти с Яндекс ID Войти через VK ID
Создавая аккаунт, я соглашаюсь с правилами Пикабу и даю согласие на обработку персональных данных.
ПромокодыРаботаКурсыРекламаИгрыПополнение Steam
Пикабу Игры +1000 бесплатных онлайн игр
Игра «История одной фермы» - увлекательное и бросающее вызов вашим серым клеточкам приключение, от которого невозможно оторваться!

История одной фермы - маджонг

Маджонг, Казуальные, Приключения

Играть

Топ прошлой недели

  • AlexKud AlexKud 38 постов
  • SergeyKorsun SergeyKorsun 12 постов
  • SupportHuaport SupportHuaport 5 постов
Посмотреть весь топ

Лучшие посты недели

Рассылка Пикабу: отправляем самые рейтинговые материалы за 7 дней 🔥

Нажимая кнопку «Подписаться на рассылку», я соглашаюсь с Правилами Пикабу и даю согласие на обработку персональных данных.

Спасибо, что подписались!
Пожалуйста, проверьте почту 😊

Помощь Кодекс Пикабу Команда Пикабу Моб. приложение
Правила соцсети О рекомендациях О компании
Промокоды Биг Гик Промокоды Lamoda Промокоды МВидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня

Android + Программирование

С этим тегом используют

Игры Смартфон Приложение iOS Телефон Google Play iPhone IT Программист IT юмор Разработка Python Картинка с текстом Юмор Все
379 постов сначала свежее
417
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
Android
1 год назад

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности?⁠⁠

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

С момента выхода первой части статьи из рубрики «сам себе экосистема» прошёл уже практически год! За это время, мы успели с вами реализовать клиенты VK и YouTube, которые работают на Android 2.2+, а также на Windows Phone 8, написать небольшую 2D-игру с нуля весом менее 1Мб, которая работает практически везде и довести существующее приложение до ума, дабы оно работало даже на смартфоне с дисплеем 240x320! Но на дворе 2024 год, люди стремительно переходят из соц. сетей в продвинутые мессенджеры и уже сложно себе представить современного человека, который не пользовался бы «телегой» или даже «вайбером» в качестве основного средства общения. Поэтому я решил реализовать клиент Telegram на смартфоне 14-летней давности на базе официальной реализации MTProto от команды Telegram — TDLib. Сегодня мы с вами: узнаем новые причины мотивации вернуть в строй смартфоны прошлых лет, напишем на C# реле-сервер, который обрабатывает пакеты MTProto и кодирует их в простой текстовый формат датасетов, который можно моментально обработать даже при нестабильном GPRS-соединении на 21-летнем Siemens C60, а также узнаем о разработке миниатюрных Android-приложений на базе «голого» API-системы, которые не тянут за собой никаких зависимостей, в том числе и AppCompat/androidx. Интересно? Тогда жду вас под катом!

❯ Содержание

  1. Но зачем всё это?

  2. Архитектура приложения

  3. Реализация сервера и работа с TDLib

  4. Android-приложение

  5. Как запустить клиент?

  6. Заключение

❯ Но зачем всё это?


На дворе уже стукнул 2024 год, современные смартфоны предлагают какие-то немыслимые мощности относительно тех, которые когда-то были в первых Android-девайсах. Сейчас за сотню баксов можно купить смартфон с хорошей 1080p IPS-матрицей, 4Гб ОЗУ и 8-ядерным шустрым чипсетом, который вполне способен плавно тянуть даже стремительно «жиреющие» на ресурсы клиенты социальных сетей, банков и прочие необходимые в повседневной жизни приложения.
И казалось бы: всё хорошо, покупай себе редмик раз в год или айфон раз в несколько лет и наслаждайся всеми прелестями работы современных приложений…

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Для многих людей смартфон — это лишь инструмент, повседневный компаньон, который помогает облегчить выполнение каких-то задач. Им совершенно не важно, как он выглядит, как ощущается в руках, какой у него дисплей и железо «под капотом», лишь бы работал да и нормально. Но есть и другая категория людей, для которых телефоны, смартфоны и любые портативные гаджеты — это не просто утилитарный девайс, а настоящее инженерное произведение искусства, с которого буквально сдувают пылинки и стараются до последнего пользоваться ими как повседневными устройствами. Хотите пример? Смотрите ниже:

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Фактически, среди современных смартфонов по сути и нет представителей такого нынче вымершего форм-фактора, как сайдслайдеры с физической QWERTY-клавиатурой, боковые раскладушки с двумя дисплеями и даже из QWERTY-моноблоков есть только смартфоны от Unihertz. Даже среди моноблоков с тачскринами нет никакого разнообразия, лишь без-рамочные одинаковые девайсы за исключением устройств от Sony.

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Galaxy S Plus

Раньше меня часто спрашивали, мол, да как ты вообще можешь пользоваться смартфоном 10-летней давности, на котором давно нет официальных клиентов популярных сервисов и только недавно, с развитием блога, мне перестали задавать этот вопрос, поняв, что это бесполезно — ведь это дело принципа и порыва энтузиазма! Смотрите сами: у нас уже есть простенькие, но вполне рабочие клиенты ВК, YouTube, сейчас я допиливаю клиент «Сбера» на СМСках, реализую карты OpenStreetMap (правда пока без адекватной навигации), а в будущем планирую написать приложение для мониторинга погоды и трекинга посылок. Кроме того, в рамках этой статьи мы реализуем с вами клиент Telegram: так чем же это не функционал современного смартфона?

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Но хорошо, с функционалом разобрались, однако для многих читателей слова «старый смартфон» это прямые синонимы «тормозной смартфон», мол «фуу, да как можно пользоваться этим тормозным кирпичом, он же лагает в последней версии моей ВКшечки!». Но давайте поставим вопрос ребром: может, это не столько девайсы немощные, сколько сами приложения, с кодовой базой, которая тянется более 10 лет, откровенно жиреют, обрастают костылями и хаками после далеко не одного поколения программистов, которые над ними работали? :) Один, вот, предпочитал пользоваться чистым AppCompat'ом, другой решил притащить зависимость, которая, например, оптимизирует виртуализацию ListView, третий решил заменить всю сериализацию Json со встроенных классов в Android на что-то стороннее и реализовал это костылями и вот так, по чуть-чуть изначально оптимальный и шустрый код превращается в неповоротливое УГ, которое не рефакторили кучу лет.

На видео Galaxy Pocket Neo — очень дешёвый Android-смартфон из 2011 года с 1-ядерным чипсетом на ~800МГц и 256Мб ОЗУ. При этом всём, Android софтварно рисует все анимации на процессоре, без участия GPU.

А значит у стареньких девайсов всё равно есть шанс быть полезными и стать полноценными повседневными смартфонами даже спустя более чем десять лет после выхода! И в сегодняшнем материале, я вам расскажу об особенностях разработки самопального клиента Telegram с собственным прокси-сервером, которое концептуально допускает реализацию даже на кнопочном Siemens C60 2003 года. Как? Читаем ниже!

❯ Принцип работы


В отличии от ВК (который разрабатывали те же самые люди, что и Telegram), API которого построено на базе REST-запросов и концепции Longpolling'а для моментального получения событий с сервера, Telegram построен на базе собственного протокола под названием MTProto, который может работать поверх любого «транспорта» (протокола нижнего уровня) — TCP, HTTP, WebSocket и т.п. Сам по себе MTProto в современном виде, разработка прожженного математика Николая Дурова и его команды — протокол относительно сложный для реализации «на коленке» и в первую очередь требует довольно серьезного понимания принципов работы современной криптографии, да и документирован он всё ещё не особо хорошо. Кроме того, у MTProto весьма интересный бинарный формат пакетов, эдакий велосипед Protobuf. В долгосрочной перспективе поддерживать свой велосипед MTProto может быть весьма проблематично, учитывая не самую лучшую документацию.

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Но городить велосипед и не нужно, поскольку у команды Telegram есть официальная реализация MTProto — библиотека TDLib, которая инкапсулирует в себе не только детали реализации протокола, но и сетевой ввод/вывод и выбор транспорта, хранение базы данных сообщений и авторизации, автоматическую загрузку фото и видео, конвертация объектов из бинарного формата MTProto в JSON и полная многопоточность и частичная потоко-безопасность. С одной стороны это плюс — уже готовое решение для реализации клиента на новой поддерживаемой платформе, где есть OpenSSL (можно статически слинковать), zlib (линкуется статически), сокеты и файловый ввод/вывод, а также довольно неплохой механизм JSON-based API, которое позволяет использовать библиотеку в любом языке, который поддерживает вызов C-функций, а с другой и минус — библиотека довольно много весит, в одиночку прибавляя ~20Мб веса приложения для каждой архитектуры, у неё течёт память и у нее странный механизм получения данных с сервера (например, нельзя ответить на сообщение, зная его ID, если сообщение предварительно не загружено, при том что на сервере весь ответ — это просто ID, на какое сообщение прилетел ответ).

Понятное дело, что на стареньком смартфоне использовать оригинальный TDLib будет проблематичным — даже если собрать либы современным NDK и запилить JNI-интерфейс, библиотека «жрёт» много ОЗУ (20-100Мб «вхолостую», в зависимости от числа диалогов и частоты прилетающих событий, плюс со временем течет до 1-2Гб, если не использовать базу данных сообщений. Скорее всего, это косяк в реализации пулов, объекты из которых выгружаются при сбросе в базу, но не выгружаются при высоком потреблении ОЗУ) и уж тем-более TDLib не запустить на любимых кнопочных Java-сонериках! Поэтому я решил написать прокси-сервер, который отправляет команды, слушает ивенты TDLib и предоставляет REST-like API для клиентских программ, которые просто вызывают какой-либо метод, а в ответ получают простой и короткий строковой датасет только с необходимыми полями, весом до 10Кб (что позволяет его быстро загрузить даже с GPRS-интернетом), который можно быстро распарсить даже на преусловутом Siemens C60!

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

К сожалению, поскольку TDLib прожорлив, я не смогу захостить на своём сервере инстансы для читателей, которые хотят поюзать приложение, поэтому вам придется ставить и запускать сервер на своём VDS/компьютере с белым IP/роутере, если под него есть .NET Core :)

Клиентом же будет выступать Android-смартфон, где приложение будет фронтэндом данных с сервера. Ничего сложного на первое время нет: первое окно — это список диалогов, второе окно — список сообщений в диалоге + поле для написания сообщения, третье окно — информация о пользователе. Всё это я реализовал за три дня не-напряжной работы «на коленке».

Давайте же перейдем к реализации сервера!

❯ Прокси-сервер


Сервер я решил писать на C#, поскольку у .NET Core сейчас всё очень хорошо с кроссплатформенностью и производительностью. Его можно даже на Raspberry Pi запустить :)

Итак, какая-же архитектура такого сервера может быть? Программа инициализирует TDLib, начинает слушать её события в отдельном потоке, пока в основном потоке крутится HTTP-сервер, который обрабатывает каждый отдельный запрос с клиентского приложения. Почему синхронно? Потому что TDLib фактически не возвращает никаких идентификаторов для возвращаемых датасетов, дабы их можно было отличить друг от друга. Приведу пример: у нас есть метод getChatHistory, который возвращает n-сообщений. При этом TDLib сам определяет, сколько хочет сообщений вернуть (и в первый вызов возвращает одно сообщение вне зависимости от настрое и отправляем пакет message n-раз. При этом в пакете message нет какого-либо ID, который позволял бы ассоциировать текущий объект с какой-либо операцией. Увы!

Начинаем с коммуникации с TDLib. Для работы с библиотекой, мы будем использовать json-интерфейс. Для .NET есть биндинги через C++/CLI, но в таком случае, сервер не будет работать на Linux. Для работы с библиотекой хватит лишь три функции: CreateClientID, которая аллокейтит новый инстанс клиента, Send, которая асинхронно отправляет JSON-объект с командой, которую затем обработает TDLib и Receive, которая ждёт N-секунд и возвращает в виде ASCII-строки (!) JSON-объект с описанием события или данными после одного из запросов. За это у нас отвечает класс TDLibInterface, который объявляет PInvoke-методы для вызова нативных методов из библиотеки. .NET Core сам подгрузит библиотеку tdjson (причём на Linux он добавит ей префикс а-ля libtdjson.so, а на Windows загрузит tdjson.dll) и сам разберется с маршаллингом аргументов функций: например, string автоматически преобразует в const char*. Тем не менее, с const char* возвратами нужно быть аккуратнее — у меня был SIGSEGV, пока я ручками не конвертировал их в обычную строку.

З.Ы: На Пикабу нет отдельного тега для кода, а вставить листинги картинками я не могу из-за ограничения на 25 медиаэлементов. Так что листинги будут совсем без табов, но алгоритм их работы понять можно :)

[DllImport(Library, EntryPoint = "td_create_client_id", CallingConvention = CallingConvention.Cdecl)]
public static extern int CreateClientID();

[DllImport(Library, EntryPoint = "td_send", CallingConvention = CallingConvention.Cdecl)]
public static extern void Send(int id, string request);

[DllImport(Library, EntryPoint = "td_receive", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr RawReceive(double timeOut);

[DllImport(Library, EntryPoint = "td_execute")]
public static extern StringBuilder Execute(string request);

public static unsafe string Receive(double timeOut)
{
IntPtr str = RawReceive(timeOut);

return str != IntPtr.Zero ? new string((sbyte*)str.ToPointer()) : null;
}

Позволю себе чуточку критики в сторону TDLib. Во первых, почему нет s-версии функции с возможностью указать длину входной строки, а tdjson полагается исключительно на \0 в конце строки? Во вторых, почему const char*, а не wchar_t*? Сейчас юникод во входной строке приходится escape'ами превращать в \u-последовательности.
После этого, нам нужно написать обёртку над TDLib, которая будет вызывать для зарегистрированных событий специальные функции, называемые коллбэками. При этом закомментированный WriteLine снизу — это «дебаг» для того, чтобы узнать названия неизвестных мне ивентов :)

В каждом объекте, полученном с помощью receive, есть поле "@type", которое содержит в себе имя класса возвращаемого объекта. Первый же вопрос от читателей — почему я использую JObject с ручным дерганьем нужных полей и вручную пишу JSON в виде строковых литералов вместо нормальной сериализации/десериализации? Ответ прост: во-первых, для актуализации Data-классов придется писать кодогенератор из TL-схемы, а во-вторых иногда TDLib может возвращать немного разные объекты в JSON, из-за чего приходится мудрить с атрибутами на этих самых Data-классах, иначе десериализатор выбросит исключение. Это решается нормальными юнит-тестами на всех вариантах данных, но зачем себе в колени стрелять, если нужен конкретный фиксированный функционал и лишь малое число от всех полей, возвращаемых TDLib?

string recv = NativeInterface.Receive(10.0d);

if (recv != null)
{
JObject json = JObject.Parse(recv);

string type = json["@type"].ToString();

if (!handlers.ContainsKey(type))
{
//Console.WriteLine("Unknown event type: {0}", type);
continue;
}

handlers[type](recv, json);
}

Теперь переходим к самому интересному — обработке событий и реализации синхронного клиента, который позволяет без async/await просто запросить список сообщений и сразу же его получить (такой подход может быть полезен и юзерботам, которые не хотят размазывать стейты по всей программе). Почему без асинков? Честно сказать, мне они просто не нравятся: как привык к концепции wait/notify и коллбэков из Java, так их и юзаю всю жизнь :)

Сначала TDLib запрашивает параметры инициализации (стейт authorizationStateWaitTdlibParameters), затем если пользователь не авторизован — запрашивает номер телефона и код подтверждения (плюс дополнительные шаги для авторизации если они есть). В конце, TDLib возвращает стейт Ready, что означает готовность библиотеки к работе:

private void OnAuthState(string raw, JObject obj)
{
JObject authState = (JObject)obj["authorization_state"];
string type = authState["@type"].ToString();

if (type == "authorizationStateWaitTdlibParameters")
{
Console.WriteLine("Preparing TDLib parameters...");
NativeInterface.Send(InstanceID,
Utils.Format("{" +
"\"@type\": \"setTdlibParameters\", " +
"\"database_directory\": \"tdlib\", " +
"\"api_id\": {0}, " +
"\"api_hash\": \"{1}\", " +
"\"use_chat_info_database\": true," +
"\"use_file_database\": true," +
"\"use_message_database\": true," +
"\"system_language_code\": \"en\", " +
"\"device_model\": \"Phone\", " +
"\"application_version\": \"1.0\" " +
"}", APIId, APIHash));
}

if (type == "authorizationStateWaitPhoneNumber")
{
Console.WriteLine("Sending phone number");
NativeInterface.Send(InstanceID, Utils.Format("{\"@type\": \"setAuthenticationPhoneNumber\", \"phone_number\": \"{0}\" }", PhoneNumber));
}

if(type == "authorizationStateWaitCode")
{
NativeInterface.Send(InstanceID, Utils.Format("{\"@type\": \"checkAuthenticationCode\", \"code\": \"{0}\" }", WaitCode));
}

if(type == "authorizationStateReady")
{
Console.WriteLine("Authorized");

waitHandle.Set();
}
}

...

Client.AttachEventHandler("updateAuthorizationState", OnAuthState);

После этого, можно начать работу с данными. Обратите внимание, мой подход потоко-небезопасен, его нельзя дергать из нескольких потоков одновременно! В коде ниже, я вызываю метод для фетча сообщений, а затем в соответствующем коллбэке от TDLib обрабатываю данные (дабы статья не разрасталась на 20+ минут, я чуть урезал все листинги).

public List<Message> QueryMessagesInChat(long chatId, long lastMessage, int count)
{
messages.Clear();

requestMessageCount = count;
string json = Utils.Format("{\"@type\": \"getChatHistory\", \"chat_id\": \"{0}\", \"from_message_id\": {1}, \"limit\": {2} }", chatId, lastMessage, count);
NativeInterface.Send(InstanceID, json);

waitHandle.WaitOne();
return messages;
}

public User QueryUser(long userId)
{
string json = Utils.Format("{\"@type\": \"getUser\", \"user_id\": \"{0}\" }", userId);
NativeInterface.Send(InstanceID, json);

waitHandle.WaitOne();
return user;
}

Переходим к реализации самого сервера, для наших целей хватит обычного HttpListener. Сначала мы зарегистрируем все поддерживаемые методы и занесем их в ассоциативный список ключ-значение. Сами методы реализованы в виде делегатов, которые принимают лишь один аргумент — список параметров из строки запроса, а возвращают строку — все ответы, за исключением особых (связанных с загрузкой вложений) — текстовые.

private HttpListener listener;
private List<HttpMethodHandler> methods;
private ScheduledRestart restartManager;

private void AddMethod(HttpMethodHandler info)
{
if(info != null)
{
methods.Add(info);
Console.WriteLine("Registered method: {0}", info.Method.Name);
}
}

private void PrepareMethods()
{
AddMethod(Chats.QueryChats);
AddMethod(Chats.QueryMessages);
AddMethod(Chats.SendMessage);
AddMethod(Users.QueryUserInfo);
}

private void PrepareState()
{
// We should fetch dialog list due to TDLib nature of preloading-everything
Client.QueryChats(15);
}

public HttpServer()
{
listener = new HttpListener();
listener.Prefixes.Add("http://+:13377/");

Client = new SyncClient("test");
Client.Start();
Client.WaitUntilReady();

//restartManager = new ScheduledRestart(5);
//restartManager.Start();

methods = new List<HttpMethodHandler>();
PrepareMethods();
PrepareState();
}


...

public void Start()
{
listener.Start();

while(listener.IsListening)
{
HandleRequest(listener.GetContext());
}
}

Переходим к обработке запроса. Метод ищет, зарегистрирован ли запрошенный метод и если да, то парсит строку запроса, которая начинается с "?", которую затем передаёт в виде коллекции ключ->значения обработчику метода:

private void HandleRequest(HttpListenerContext ctx)
{
string method = ctx.Request.Url.LocalPath.Substring(1).ToLower();

if (method.Length < 0)
{
SendResponse(HttpGenericResponse.MethodRequired.ToString(), ctx);
return;
}

foreach(HttpMethodHandler handler in methods)
{
if(method == handler.Method.Name.ToLower())
{
string result = "";

if (ctx.Request.Url.Query.Length > 0)
{
string[] args = ctx.Request.Url.Query.Substring(1).Split('&', StringSplitOptions.RemoveEmptyEntries);
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();

foreach (string arg in args)
{
if (arg.IndexOf('=') >= 0)
keyValuePairs.Add(arg.Substring(0, arg.IndexOf('=')), arg.Substring(arg.IndexOf('=') + 1));
else
keyValuePairs.Add(arg.Substring(0, arg.IndexOf('=')), "");
}

result = handler(keyValuePairs);

if (result == null || result.Length < 1)
{
Console.WriteLine("Suspicious result from {0}", handler.Method.Name);
}
}

SendResponse(result, ctx);
return;
}
}

SendResponse(HttpGenericResponse.UnknownMethod.ToString(), ctx);
}

А сами методы, в свою очередь, дергают соответствующие функции из клиента и формируют на их основе датасет в примитивном формате:

public static string QueryChats(Dictionary<string, string> args)
{
if(args.ContainsKey("count"))
{
int count = int.Parse(args["count"]);
StringBuilder ret = new StringBuilder();

List<Chat> chats = HttpServer.Instance.Client.QueryChats(count);
ret.AppendLine(string.Format("Count={0}", chats.Count));

foreach(Chat chat in chats)
{
ret.AppendLine("Begin");
ret.AppendLine("ID=" + chat.ID);
ret.AppendLine("Date=" + chat.LastMessageDate);
ret.AppendLine("Name=" + chat.Name);
ret.AppendLine("Text=" + Uri.EscapeDataString(chat.LastMessageText));
ret.AppendLine("MsgId=" + chat.LastMessageID);
ret.AppendLine("End");
}

return ret.ToString();
}

return HttpGenericResponse.InternalException.ToString();
}

В результате получаем вот такой простой датасет, который, как я и говорил, легко распарсить и на Siemens C60, и на Atmega328 — да где угодно! В целом, такой сервер можно использовать для реализации бота в телеграме, который будет передавать показания каких-то датчиков, сигнализацию и прочие клевые штуки!

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Переходим к реализации клиента, т.е. приложения на Android. Здесь будет не менее интересно!

❯ Пилим для Android


В геймдеве есть своеобразный мем — некоторые инди-разработчики сначала начинают делать меню, вместо основного геймплея, что становится предметом насмешек среди других разработчиков. Но в разработке приложений для смартфонов всё по другому — здесь как-раз таки хорошо заранее продумывать макет будущего приложения!

Поскольку у нас с вами мессенджер, то главный экран должен представлять из себя список чатов (ListView) и верхнюю панельку, где в будущем могут разместиться настройки и свайп-менюшка:

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Такой вот простой макет.

Каждый пункт меню — это тоже отдельный layout, в котором мы по шаблону строим внешний вид будущего элемента списка. На немолодых устройствах есть смысл использовать как можно меньше контейнеров в layout'е, поскольку пересчет позиций и размеров элементов — одна из самых «тяжелых» операций в UI-фреймворке вообще. Кроме того, не стоит использовать кучу картинок и drawable — в Android 2.x всё 2D рисуется софтварно, аппаратное ускорение появилось только в 3.0 (частично).

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Но дабы в списке диалогов что-то появилось, нужно сначала реализовать фетчинг (получение) этих самых диалогов с сервера! Сам объект, который занимается обработкой запросов называется ClientManager и является синглтоном — он в единственном экземпляре на все время работы программы. Помимо менеджмента «ноды» (т.е. прокси-сервера), токена для авторизации и обработчика ошибок, ClientManager реализует метод для асинхронного запроса информации с сервера и, собственно, формирует строки запросов с помощью соответствующих методов:

public void queryChats(int count, Response resp) {
sendRequest(String.format("%s/QueryChats?count=%d&auth_key=%s", nodeAddress, count, token), resp);
}

Подгрузка чатов и сообщений реализована через Adapter — концепция «виртуальных» списков, которая предполагает что система создаст не 50 элементов интерфейса на каждую кнопку чата, а только 5 и будет их виртуально «мотать по кругу», обновляя только данные в уже существующих элементах. Это позволяет значительно ускорить отрисовку, учитывая то, что Android 2.x Canvas рисуется программно.

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

private void updateDialogList() {
ClientManager.getCurrent().queryChats(50, new ClientManager.Response() {
@override
public void onReady(String str) {
try {
List<Packets.Chat> chats = Packets.parseChatListFromQueryResponse(str);

DialogAdapter adapter = new DialogAdapter();
adapter.setChats(chats);

((ListView) findViewById(R.id.messages_view)).setAdapter(adapter);
} catch (Exception e) {
Toast.makeText(MainActivity.this, "Упс!", Toast.LENGTH_SHORT);
}
}
});
}

Ну вы уже явно замучились видеть простыни кода, давайте посмотрим что у нас вышло!

Шустренько, да? А ведь это ультрабюджетник Alcatel OT-916D, один из последних массовых дешевых QWERTY-смартфонов за 5 000 рублей из 2012 года. Кстати, смартфон подарил мне читатель chuvakoff с Хабра!

Переходим к окну чата. Основной макет почти такой-же, как и у основного окна: только добавилась панелька для ввода сообщения снизу.

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Концептуально, всё тоже самое — запрашиваем данные с сервера, парсим их и загружаем в адаптер, благодаря чему мы сможем листать наш диалог. Однако в сообщения я добавил контекстное меню с стандартными фишками типа копирования, ответа и прочих подобных действий. Поскольку у нас нет ни пушей, ни еще каких-либо средств для поулчения данных о новых сообщениях, я раз в определенный интервал просто получаю сообщения — и если новый датасет отличается от старого — обновляю окошко чата.

view.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
// Reply to...
contextMenu.add(getString(R.string.reply)).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
setReplyContext((Packets.Message) view.getTag());

return true;
}
});

// Copy
contextMenu.add(getString(R.string.copy)).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
ViewGroup vg = (ViewGroup)view;

android.text.ClipboardManager manager = (android.text.ClipboardManager) view.getContext().getSystemService(CLIPBOARD_SERVICE);
manager.setText(((TextView)vg.findViewById(R.id.message_content)).getText());

return true;
}
});

// Send to...
contextMenu.add(getString(R.string.resend)).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
ViewGroup vg = (ViewGroup)view;
String text = ((TextView)vg.findViewById(R.id.message_content)).getText().toString();

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, text);
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, null));

return true;
}
});
}
});

Переходим к реализации поля для ввода сообщения. Здесь всё просто — на серверсайде за это отвечает метод SendMessage. Однако для того, чтобы с нашего клиента можно было ответить на другие сообщения, я ввёл также «контекст ответа», в котором запоминается сообщение, на которое мы хотим ответить. Telegram также поддерживает Markdown, однако его полная поддержка пока не реализована.

EditText editText = ((EditText)findViewById(R.id.message_text));

if(editText.getText().length() > 0) {
long replyTo = replyContext != null ? replyContext.ID : 0;

ClientManager.getCurrent().sendTextMessage(chat.ID, editText.getText().toString(), replyTo, new ClientManager.Response() {
@Override
public void onReady(String str) {

}
});

editText.setText("");
setReplyContext(null);
}

В остальном же, функционал конечно пока совсем базовый, однако клиент работает очень шустро даже бюджетной X10 Mini Pro и позволяет чатится с моими читателями в Telegram. В будущем хотелось бы допилить:

  • Поддержка картинок: Сейчас уже есть кривоватый механизм кэширования изображений на стороне сервера, который позволяет загружать аватарки чатов. В будущем, я добавлю поддержку «галерей» с картинками!

  • Поддержка голосовых сообщений: Не все их любят, но они порой удобны и выручают. Реализую как прослушивание, так и запись!

  • Подробный просмотр профилей и менеджмент чатов: Удаление сообщений, чатов и прочие фишечки из официальных клиентов.

    Казалось бы — до официальных клиентов ещё очень далеко. Но сам факт, чтобы всё это работало достаточно шустро на девайсах, которым уже более 10 лет!

❯ Звучит интересно! Как заюзать твой клиент?


Тут всё очень и очень просто! В первую очередь, нам понадобится ПК с белым IP, роутер (если под него есть сборка dotnet), либо VDS. Виртуальные сервера сейчас стоят копейки, у ТаймВеба есть тариф за 188 рублей в месяц, которого с головой хватит для нашего сервера.

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Такая вот рекламная интеграция (к слову, прокси для всех приложений уже более года крутятся именно на мощностях TimeWeb Cloud)!

Берём уже собранный TDLib и сервер под Windows, или собираем TDLib под Linux, накатываем .NET Core. Пример для Debian/Ubuntu:

sudo apt-get install dotnet

Затем запускаем сервер:

dotnet tdsrv.dll

Программа сначала запросит номер телефона, а затем код подтверждения Telegram. После этого будет создана папка tdlib/, где будут хранится данные вашей сессии, а также файл authkey.txt, где хранится случайный ключ для сессии (md5 phone_number + response code + псевдослучайное число). Не оставляйте его в /var/www/!

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Если всё нормально, программа начнёт слушать порт 13377 на всех сетевых интерфейсах, в т.ч и в локальной сети. После этого, ставим уже предварительно собранный, либо собираем сами в Android Studio APK и в окне авторизации пишем адрес ноды и ключ авторизации. Если всё настроено верно — программа запомнит сервер и будет работать без проблем! Вот так всё легко :) Как видите — всё очень и очень просто!

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Кроме того, буквально за пару дней до публикации статьи я сел вечерком из интереса что-нить под Java-телефоны попилить… и, как и обещал, реализовал Proof of Concept возможности работы Telegram даже на сонериках, которым скоро 20 лет стукнет! А ведь если ещё чуть заморочится, можно запустить приложение даже на преусловутых монохромных сименсах!

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

❯ Заключение


Вот такой у нас получился проект с реализацией лёгкого, примитивного, но тем не менее рабочего клиента Telegram, который на клиентской части вообще не использует никаких зависимостей. Вес собранного APK в release-версии — всего 54 килобайта! Понятное дело что с ростом функционала, вес программы будет увеличиваться, но я обещаю — больше 1Мб он не вырастет :)

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Ну а вам, моим читателям, надеюсь было интересно прочитать такой «двойной материал» не только о разработке сетевой части без использования Apache/nginx/IIS, но и UI-фронтэнда для Android-смартфонов, которым уже более 10 лет!
Исходный код проекта можно найти на моём GitHub: как приложения, так и сервера, а также убедиться в отсутствии каких либо закладок и, если совсем не доверяете, собрать бинарники сами! Для сборки понадобится VS2017 или свежее, а также Android Studio 2.3.2 (если собираете для Android 2.1 и ниже).

Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности? Программирование, Гаджеты, Смартфон, Покупка, Android, Java, Код, Приложение, Telegram, ВКонтакте, Научпоп, Клиенты, Программа, Софт, Кейс, Гик, Электроника, Видео, YouTube, Длиннопост

Друзья! Сейчас на Хабре опросы сломаны, поэтому если у вас есть желание, вы можете проголосовать в комментариях: какой стиль статей вам больше нравится — где больше конкретики и кода с пояснением как конкретно работает та или иная часть программы, или наоборот стиль ближе к научпопу, где фрагментов кода нет, или их значительно меньше? Пишите своё мнение о проекте в комментариях!

Кроме того, у меня есть канал в Telegram, куда я публикую бэкстейдж статей, ссылки на новый материал, свои наработки, а также посты о ремонте девайсов и различные мысли.

Статья подготовлена при поддержке TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud, чтобы не пропускать новые статьи каждую неделю!

Показать полностью 18 2
[моё] Программирование Гаджеты Смартфон Покупка Android Java Код Приложение Telegram ВКонтакте Научпоп Клиенты Программа Софт Кейс Гик Электроника Видео YouTube Длиннопост
79
ermolnik666
1 год назад

Kotlin puzzlers 2⁠⁠

Kotlin puzzlers 2 Android, Приложение, Программа, IT, Kotlin, Разработчики, Разработка, Программирование
Показать полностью 1
[моё] Android Приложение Программа IT Kotlin Разработчики Разработка Программирование
1
112
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
Android
1 год назад

В среду выйдет новый подробный материал из рубрики "сам себе экосистема"⁠⁠

Где я подробно рассказываю о том, как реализовал клиент современного мессенджера Telegram на Android 1.5+ и выше. Таким образом, Telegram будет работать даже на самом первом Android-смартфоне в мире, T-Mobile G1, причём на стоковой прошивке!

В среду выйдет новый подробный материал из рубрики "сам себе экосистема" Опрос, Покупка, Программирование, Гаджеты, Смартфон, Java, Android, Ретро, Ништяки, Мобильные телефоны, Приложение, Гик, Электроника, Linux
Интересно?
Всего голосов:
Показать полностью 1 1
[моё] Опрос Покупка Программирование Гаджеты Смартфон Java Android Ретро Ништяки Мобильные телефоны Приложение Гик Электроника Linux
24
159
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
TECHNO BROTHER
1 год назад

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»?⁠⁠

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Моддинг-сцена с разработкой и портированием кастомных прошивок для Android-устройств существует вот уже более 10 лет. В основном, энтузиасты пытаются проапгрейдить свои устройства путем портирования более свежих версий Android, чем предлагает производитель девайса. Чего уж говорить, если Galaxy S III, которому уже 12 лет стукнуло, получил неофициальный апгрейд до Android 14. Порой мне в голову приходят различные, весьма странные моддерские мысли: например, почему бы не портировать на старенький смартфон… ещё более старую версию Android, дабы посмотреть «что будет». Казалось бы «портировал и портировал», но в процессе работы я столкнулся с множеством интересных нюансов и особенностей работы Android, о которых хотел бы рассказать и вам — моим читателям! Сегодняшняя статья будет в классическом «научпоп»-стиле без кода, зато с подробными объяснениями одной из техник портирования Android-прошивок путем патчинга скриптов для конфигурации системы и подмены Board-specific библиотек, дабы система «увидела» всё необходимое железо! Интересно? Тогда жду вас под катом!

❯ Мотивация


У меня, как и у многих моих читателей, одной из первых версией Android в жизни была 2.x. Наверное, я уже никогда не смогу забыть первые впечатления от использования своего новенького, пусть и бюджетного и слабого Android-смартфона после простеньких китайских кнопочников. Эти ощущения были прекрасными: вот я разблокирую смартфон, потянув «замочек» вправо, свайпаю рабочие столы и тапаю на значок приложения браузера, выполненный в стиле скевоморфизма, загружаю полноценную страницу Википедии через GPRS-сеть (мой первый смартфон не имел 3G) и плавно скроллю страницу, не забывая смахнуть шторку вниз и проверить статус уведомлений в пока ещё совсем простенькой панели нотификаций… Это были по настоящему ламповые впечатления, которые не смог превзойти ни один современный девайс: ни AOSP, ни MIUI, ни OneUI.

Моим первым смартфоном была китайская реплика Samsung Galaxy S III Mini, купленная в самом начале 2013 года. Возможно, кто-то из вас помнит, как подобные дешевые смартфоны и планшеты «Sumsanc» можно было купить на рыночных развалах, в метро и прочих местах, где допускается торговать несертифицированными гаджетами. Даже с учётом накрутки, эти смартфоны стоили всего 2 000 рублей, что было просто «подарком» для цены абсолютно нового гаджета. Девайс был крайне простым для начала 2013 года и имел следующие характеристики:

  • Процессор: Spreadtrum SC6820. Одно ядро Cortex-A5 на частоте до 1ГГц, Mali400 MP в качестве GPU. Чипсет был крайне высоко-интегрированным для своих лет: в одном корпусе располагалось ARM-ядро, GPU, контроллер питания, GPS, множество периферии (например, DAC), а также Baseband-часть GSM-радиотракта. BT/Wi-Fi реализовывались в отдельном комбочипе разработки RDA.

  • Память: 256Мб DDR1 ОЗУ/256Мб NAND-памяти в одном чипе eMCP от Hynix. Предположительно, эти чипы остались на складах ещё со времен первых Android-смартфонов, но очень быстро потеряли актуальность и их, вероятно, отдавали «за бесценок» что позволило ещё сильнее снизить цену производства таких смартфонов.

  • Дисплей: безоговорочно, TFT, обычно с разрешением не выше 480x320, что для 3" дисплея было нормальным, но для 5" — уже несколько маловато. Тем не менее, сами дисплеи были нормальными и глаза от них не «вытекали». Тачскрин обычно ёмкостной, на 2 касания.

  • Android: 2.2, на некоторых похожих моделях встречался 2.3.

  • Аккумулятор: ~1.500мАч, не больше. По форм-фактору напоминает BP-4L, без проблем подходит от многих S60 смартфонов Nokia тех лет.


Не густо, да? Уже в апреле того же года вышел Galaxy S4 с Snapdragon 600, 2Гб ОЗУ и 32Гб встроенной памяти, а мы тут с одноядерным чипсетом и 256Мб ОЗУ сидим. Но мне, будучи школяром, это было за счастье — чего я на нём только не делал, и на PHP какие-то WAP-сайты динамические пытался писать и на FTP заливать, и даже ADT Bundle скачал, дабы попытаться что-то своё запилить под собственный смартфон! В общем, я был счастлив, несмотря на лаги девайса. Именно того девайса у меня уже давным-давно не осталось… но память я всё ещё храню и стараюсь дать новый дом таким китайчикам, которые в большинстве своем оказались на свалке истории в новом мире современных смартфонов!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Но на самом деле, смартфоны 10+ летней давности могут быть интересны и своим форм-фактором: в современном мире едва ли можно найти хоть какие-то телефоны с полноценной QWERTY-клавиатурой (исключение — смартфоны UniHertz, которые стоят недешево) и уж тем более, боковые слайдеры. Поэтому мой интерес к подобным девайсам очень легко объяснить!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Однако, порой мне самому хочется снова пережить эти эмоции и ещё раз походить с подобным девайсом «на каждый день», даже когда на Android 2.2 особо никакие сервисы уже не работают. Отчасти, я решаю свои проблемы сам и пишу клиенты нужных мне сервисов, если они действительно нужны, дабы рано или поздно всё таки вдохнуть новую жизнь в «старенькие» девайсы. И казалось бы, это можно списать на синдром утёнка и банальную ностальгию, но мои ощущения «ламповости» отнюдь не мимолетны и всё равно меня тянет именно на те смартфоны, с тем самым интерфейсом, которые я когда-то увидел впервые!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Пожалуй, сказать что я решил портировать старый Android на отнюдь не новый смартфон «просто так» было бы ложью. Я всё ещё верю в то, что смогу в одиночку хотя бы частично вдохнуть новую жизнь в эти девайсы и позволить им работать с современными сервисами, дабы они могли приносить пользу не только мне, но и другим людям, которые намеренно занимаются дауншифтингом или вынуждены сидеть на девайсах с старыми версиями Android! Сегодняшним нашим подопытным станет один из представителей подобных noname-смартфонов тех лет, реплика Galaxy S III Mini на том самом железе, на котором работал мой первый смартфон. Однако с завода на нём стоял Android 2.3 — слишком свежая, по моему мнению, версия системы, которую я конечно-же захотел откатить до Android 2.2!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Задача облегчалась тем, что смартфоны на этом чипсете с Android 2.2 уже выходили, что позволило мне портировать прошивку путем несложных патчей скриптов инициализации и копирования Platform-specific файлов, дабы завести все необходимые для смартфона модули. А поскольку о таком простом способе портирования свежих и старых прошивок знают далеко не все мои читатели — я решил написать об этом отдельный подробный материал! Давайте же перейдём к практической части нашей статьи.

❯ Первые шаги


Перед тем, как начать портирование системы, нам необходимо разбираться в том, как вообще происходит процесс загрузки Android и какие процессы при этом загружаются. Вкратце, описать весь процесс загрузки можно так:

  • Загрузчик: при включении смартфона, первичный загрузчик BootROM, аппаратно-прошитый в чипсет ещё на этапе изготовления чипа, инициализирует некоторую периферию, загружает вторичный загрузчик из NAND (коим может быть SPL — Second Program Loader, занимающийся инициализацией контроллера DDR и UART) и передаёт ему управление. Вторичный загрузчик в свою очередь передаёт управление U-Boot — в задачи которого входит также инициализация периферии, обработка устройств постоянной памяти (например, NAND или контроллер SD), загрузка ядра Linux и конфигурация самого процесса загрузки. U-Boot можно считать эдакой альтернативной UEFI/BIOS в мире не-x86 устройств. В смартфонах на базе чипов MediaTek и Qualcomm, роль U-Boot выполняет LK — маленькая ОС, в задачи которой входит инициализация периферии и передача управления ядру Linux с помощью программы aboot.

  • Ядро Linux: после загрузки образа ядра с initrd (небольшая файловая система, которая загружается сразу в память и содержит в себе скрипты для конфигурации всего остального) и передачи управления ядру, Linux начинает выполнение программы с PID 0 — /init, в задачи которой входит выполнение скриптов инициализации userspace-окружения системы в init.rc. При этом смартфон уже фактически готов к работе — в одной из своих статей я показывал, как можно приостановить загрузку Android и выполнять свой код, используя все ресурсы смартфона для своих целей.

  • zygote и app_process: помимо запуска необходимых для работы смартфона служб, динамической загрузки драйверов (с помощью insmod) и определения режима загрузки (например, если телефон подключили выключенным к зарядке — необходимо показать анимацию этой самой зарядки), init.rc запускает две программы, одна из которых необходима для функционирования системы. Первая — это bootanimation, которая проигрывает анимацию включения смартфона и app_process, который в одном из режимов работы превращается в zygote — самый важный процесс для работы Android, который предварительно при старте системы загружает системный Java-байткод, отвечающий за отрисовку интерфейса, проигрывание звука и т. п. из framework.jar и другие системные ресурсы (например темы и изображения), а затем при запуске каждого приложения просто клонирует сам себя (с уже загруженными ресурсами) и начинает выполнение байткода любого запущенного Android-приложения или службы.

    Каждое запущенное приложение или служба — это отдельный app_process, в том числе и лаунчер, и Google-сервисы и клиент любого мессенджера.


Всё выглядит просто и логично, не так ли? Подытожив, можно сказать что для того, чтобы система минимально стартовала, нам необходима подходящее ядро для нашего устройства, рабочий init.rc и адекватно запускающийся init.rc. Кроме того, Android зависит от некоторых платформо-специфичных библиотек: в основном, они находятся в /lib/hardware и без них система может не запуститься или что-то может не работать. Особенно осторожно надо подходить к libhardware.so.

Как я уже сказал выше, прошивку мы будем портировать от другого смартфона на том-же чипсете и что забавно — такую же реплику, просто более-раннюю! «Из коробки», мой смартфон работает на Android 2.3, значительно более стабильной, чем изначальный порт 2.2 на эту платформу. Отличий 2.3 от 2.2 достаточно: например, на 2.2 совсем иной цвет шторки, по умолчанию стоит Light-тема, нельзя закрывать уведомления смахиванием и в целом система несколько отличается внешне. Для работы нам нужно будет два образа прошивки: ту, которую будем портировать и та, которая стоковая. Прошивки в смартфонах на платформе Spreadtrum распространяются в формате pac, однако нет никаких проблем подменить образ раздела в ResearchDownload — фирменной утилите для прошивки смартфонов на этом чипсете.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Я решил взять прошивку от FeiTeng N9300 Mini, родная для моего смартфона — M-Horse 9500 Mini. В случае моего девайса, разметка и список разделов между устройствами никак не отличалась, поэтому изначально я напрямую прошил раздел system.img, дабы посмотреть что будет с устройство. Не забывайте, что ядро и init.rc хранится в образе boot.img — поэтому прошивка раздела system безопасна!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

❯ Первый запуск


После прошивки чужого раздела system, смартфон стартовал… однако работал несколько странно: во первых, у нас не было сети, во вторых не работал тачскрин (при родном то ядре), а в третьих, Android ни в какую не видел аккумулятор, вися на 0% и моментально отключаясь, если смартфон не стоит на зарядке, а при попытке воткнуть кабель — смартфон показывал индикацию зарядки, но потребление было на нуле.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Поскольку тачскрина у нас нет, root доступ через adb придется включать «ручками» — для этого нам необходимо перепаковать наш родной раздел boot. Для распаковки и запаковки образов, я пользуюсь MtkImgTool — весьма удобная «кухня» для работы. Вытаскиваем boot.img из pac, закидываем в Unpack/Image/ и распаковываем с помощью Boot -> Unpack -> boot.img

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

В Unpack/boot/ramdisk/default.prop нам необходимо изменить ro.debuggable на 1, а ro.secure на 0. Это даст возможность отлаживать устройство даже если Android фактически не загрузился.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Теперь у нас есть root-консоль устройства, даже если смартфон висит на заставке. Прошиваем обратно образ, пишем adb shell в консоли и смотрим, что же тут не так… Вообще, драйвер тачскрина обычно статически слинкован с ядром, но в случае устройств Spreadtrum — они вынесены в динамические модули ko, которые можно найти в папке /lib/modules/, либо /sps/. Давайте глянем init.sp6820a.init.3rdparty.rc, который отвечает за специфичную для этой модели смартфона инициализацию.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Ага, видим insmod gt868.ko? Это команда загрузки драйвера тачскрина, в нашем случае — это вышеупомянутый GT868. Иногда встречаются другие модели тачскринов, но главное отличие прошивки 2.2 от 2.3 — разные названия папок с драйверами и некоторые службы. Достаём из родного образа драйвер gt868.ko, используя всё тот-же MtkImgTools, распаковывая его как обычный ext2 раздел:

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Пишем в консоли устройства:

adb push / gt868.ko
adb shell
insmod /system/lib/modules/gt868.ko

И наслаждаемся тем, что у нас теперь появился тачскрин! Android сам подхватил новое устройство ввода, поскольку драйвер тачскрина — обычное устройство в /dev/input/. Чтобы драйвер грузился при загрузке, его достаточно добавить в init.sp6820a.3rdparty.rc, предварительно закинув в раздел /system/. Перед этим, раздел нужно перемонтировать для возможности записи:

on boot
insmod /system/gt868.ko

adb shell
busybox mount -o remount,rw /system/
mkdir /lib/modules/
exit
adb push gt868.ko /lib/modules/

После модификации rc-скрипта, нужно обратно запаковать boot.img с помощью MtkImgTools и прошить его с помощью ResearchDownload — тачскрин будет работать даже после перезагрузки!

❯ Поднимаем зарядку и сеть


Переходим к отсутствию связи с аккумулятором и нулевым потреблением АКБ. Здесь мне пришлось несколько покопаться и почитать логи ядра с помощью команды dmesg. Я обратил внимание на то, что некая служба пишет что-то об аккумуляторе, но разобраться было несложно: в папке /system/bin я нашёл программу charge, которая, очевидно, отвечает за настройку КП для старта зарядки. Что она точно делает — мне неизвестно, возможно корректирует какие-то значения в sysfs, возможно с помощью ioctl общается с драйвером КП и даёт разрешение на старт зарядки и обновление информации в sysfs. В любом случае, после замены /system/bin/vcharged на оный из родной прошивки, зарядка заработала.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Для этого мы снова перемонтируем /system/ в режим записи и копируем vcharged, не забыв вернуть обратно необходимые права:

adb push charge /system/bin/
adb shell
chmod 777 /system/bin/charge

Перезагружаем устройство и… зарядка с индикацией появилась!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Вроде всё работает на первый взгляд: и звук, и вибро, и Wi-Fi с Bluetooth… однако сети-то нет! Девайс не определял наличие SIM, а вместо IMEI у нас был null/null:

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Чтобы её поднять, нам необходимо разобраться в том, как работает подсистема взаимодействия с радиомодулем в Android, которая называется ril — Radio Interface Library. RIL предоставляет API для системы, дабы оперировать не напрямую AT-командами (которые могут быть проприетарными, а на некоторых чипсетах, как, например, Qualcomm вообще отсутствовать), а удобным набором функций — например о запросе статуса радиомодуля, начале звонка, поиска сети и т. п. RIL состоит из сервиса rild в /system/bin/ и библиотеки libril.so, которую можно найти в папке /system/lib/. При запуске системы, TelephonyManager открывает сокет с rild и опрашивает его состояние. Именно из TelephonyManager система берет информацию о силе сигнала, название оператора, IMEI и другие данные.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Путем ковыряния в dmesg я понял, что система флудит из-за невозможности запустить проприетарный сервис Spreadtrum — sprd_monitor. При попытке позвонить в 112, смартфон бесконечно пытается включить радиомодуль. Я ковырялся в UI-части исходного кода Android, дабы понять логику работы, но проблема крылась как раз в упомянутых выше службах sprd_monitor. Берём их из /system/bin/ оригинальной прошивки, закидываем их в устройство, не забыв установить права и отправляем систему в ребут:

adb push engappclient /system/bin/
adb push engmodemclient /system/bin/
adb shell
chmod 777 /system/bin/engappclient
chmod 777 /system/bin/engmodemclient

Ошибки в dmesg пропали, IMEI появился, но устройство до сих пор не хочет никуда звонить и просто висит на экране звонка. В настройках смартфон говорит о том, что уровень сигнала недоступен, а значит, радиомодуль до сих пор не работает :(

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост
Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Но и мы так просто не сдаемся! Поковыляв по файловой системе, в директории /system/opl/telephony/bin/ я нашел скрипт, отвечающий за инициализацию радиотракта, который вызывает родной 3rdparty.rc! Запускаем sh-скрипт и обнаруживаем, что сеть появилась и девайс дозвонился в 112, а также увидел SIM-карту!

sh init.tel

Теперь всё полностью работает :) Дабы радиотракт запускался при старте устройства, я перенес часть инита из boot.img от прошивки, которую мы портировали. Для кого-то, казалось бы, это всё достаточно сложно и долго. Но у меня ушел всего один день на полную отладку и запуск такой кастомной прошивки на своем устройстве! Можно сказать, это самый базовый и краткий экскурс в такое нелегкое дело, как моддинг Android-устройств.

Но мы ведь это всё не просто так делали! Давайте глянем, как будет работать такой девайс на Android 2.2 в 2024 году — спустя 14 лет после выхода системы. Всё ли так плохо, как кажется?

❯ Знакомимся с девайсом


Думаю, многие читатели вспомнят этот ламповый интерфейс, обои с одуванчиком и лаунчеры а-ля TouchWiz на тех смартфонах, где интерфейс Samsung был не предусмотрен. А эти «бульк»… их сложно забыть!

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Конечно, изначально может показаться, что устройство плохо подходит для выполнения современных задач: браузер не способен загрузить большинство страниц, а из альтернатив есть только Opera Mini, где вообще нет динамического контента, а официальные клиенты ВК, WhatsApp и YouTube уже давно не работают. Опечаленный читатель может подумать, что девайс, как и многие его ровесники уже давно превратились в звонилки…

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Но это отнюдь не так! Ведь как я уже говорил, я стараюсь своими силами вдохнуть в подобные девайсы новую жизнь, реализуя на них клиенты нужных мне сервисов сам! Да, пусть примитивно и корявенько, далеко не ынтырпрайз-уровень, но эти приложения выполняют свои функции и что, немаловажно, весят очень мало (до 100Кб) и работают крайне шустро! Клиент ВКшечки просто летает, несмотря на то, что фактически реализован только мессенджер с нотификациями и музыка.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост
Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Пожалуй, многие читатели удивятся — но на таких девайсах есть YouTube! Мой самопальный клиент не поддерживает стриминг из сети (да и многие девайсы объективно не потянут), поэтому предварительно загружает видео на MicroSD-флэшку и затем уже их воспроизводит. Как приятный бонус — видео потом можно посмотреть в любой момент в галерее.

Исходников нет, но мы не сдадимся: как и зачем я портировал более старый Android, чем стоял «с завода»? Смартфон, Покупка, Гаджеты, Программирование, Android, Телефон, Ретро, Девайс, Ништяки, Linux, Портирование, Научпоп, Видео, Без звука, Длиннопост

Я помню насколько было лампово слушать музыку с таких девайсов. И если претензии к основному динамику не очень актуальны, то к качеству звука в наушниках были придирки — звук был громкий, но ему не хватало низких частот, из-за чего он звучал несколько плоско, хотя мне и этого хватало — ведь я слушал музыку в наушниках по 200-300 рублей с рынка! Я всё ещё помню те времена, как качал mp3-треки по 2-3 мегабайта через 2G-интернет… слушаешь один трек — как раз загрузится другой и так по кругу наполнял свою фонотеку. Эх времена то какие были! Тем не менее, для некоторых базовых мультимедийных возможностей девайс подходит и сейчас, например в машину в качестве BT-хоста с музыкой.
А ещё на таких девайсах порой клёво скачать какой-нибудь Temple Run образца 2011 года и вспомнить самое начало смартфонного гейминга тех лет… ведь далеко не все игры того времени запускаются на свежих версиях Android!

❯ Заключение


В остальном же, подобные девайсы отнюдь не бесперспективны! Несмотря на совсем не новое железо, они всё ещё могут выполнять многие задачи, стоит лишь снова запилить необходимые приложения для них! Мессенджеры, соц. сети, музыкальные сервисы и даже просмотр видео — всё это доступно даже для таких, казалось бы, «устаревших» девайсов, когда есть запал энтузиазма и жгучее желание походить именно с этим конкретным устройством как с основным!

Для кого-то это просто проявление синдрома утенка или картинки «вот кому-то делать не.»… ну а для меня — это крайне интересное, захватывающее и кайфовое времяпровождение: начиная от аппаратного ковыряния с такими девайсами и копания исходников ядер/драйверов, заканчивая написанием оптимизированных клиентских приложений, которые весят не 100-200Мб, а 100-200Кб :)

Друзья, если у вас есть подобные китайчики и вы не разделяете желания пытаться вдохнуть в них жизнь, но выбрасывать их жалко — можете задонатить их мне :) Как сами видите — девайсы попадают в хорошие руки. Из недавнего — я взял нерабочую, утопленную китайскую копию 14 Pro Max из под СЦ в качестве основного смартфона. Также у меня есть канал в Telegram, куда я выкладываю бэкстейджи статей, различные заметки о ремонте, моддинге, программировании и реверс-инжиниринге и свои мысли. Кому интересно — залетайте!

Понравилась ли вам статья? Какими были ваши первые Android-смартфоны? Пишите в комментариях, будет интересно почитать!

Показать полностью 23 2
[моё] Смартфон Покупка Гаджеты Программирование Android Телефон Ретро Девайс Ништяки Linux Портирование Научпоп Видео Без звука Длиннопост
27
89
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
TECHNO BROTHER
1 год назад

"Сам себе экосистема ч.3" - быть? Хочу услышать ваше мнение!⁠⁠

"Сам себе экосистема ч.3" - быть? Хочу услышать ваше мнение! Опрос, Гаджеты, Программирование, Смартфон, Покупка, Java, Android, Длиннопост

Друзья! Думаю многие мои давние читатели помнят о цикле статей "сам себе экосистема", где я стараюсь вдохнуть новую жизнь в девайсы 10+ летней давности путем разработки собственных клиентов нужных мне сервисов! Уже вышло две статьи из этой рубрики ( Сам себе экосистема: Как я адаптировал старый смартфон под современные реалии и написал клиенты нужных мне сервисов Не дадим Windows Phone умереть! Как я написал свои клиенты VK, YouTube для Nokia Lumia? Сам себе экосистема ч.2 ). Сейчас, вот, держу несколько своих девайсов на Android 2.2 и потихоньку пилю наработки ещё нескольких нужных мне приложений: клиент Сбера на СМСках (по сути, удобная обертка над 900 с виджетами), актуальная погода на неделю вперед, вьювер карт OpenStreetMap и трекинг посылок. ВКшечка и ютубчик, как мы помним, уже есть. Давайте устроим голосование, не одному ли мне это интересно и быть ли третьей статье из рубрики "сам себе экосистема"?

"Сам себе экосистема ч.3" - быть? Хочу услышать ваше мнение! Опрос, Гаджеты, Программирование, Смартфон, Покупка, Java, Android, Длиннопост
Быть?
Всего голосов:
Показать полностью 1 1
[моё] Опрос Гаджеты Программирование Смартфон Покупка Java Android Длиннопост
22
74
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
Android
1 год назад

Друзья, я снова ищу китайские смартфоны-подделки в любом состоянии для будущих статей!⁠⁠

Друзья, я снова ищу китайские смартфоны-подделки в любом состоянии для будущих статей! Смартфон, Гаджеты, Покупка, Поиск, Розыск, Android, Мобильные телефоны, Донат, Телефон, Электроника, Ремонт телефона, Программирование

Как многие пикабушники уже вероятно знают, я пишу статьи об оживлении, моддинге и программировании под различные старые девайсы! Но некоторые девайсы, как, например, дешевые китайские подделки порой найти проблематично: большинство оказалось на свалке, а на онлайн-барахолках их не найти из-за запрета на контрафакт. Для будущего материала, я ищу китайские подделки из начала 2010х: в основном китайские копеечные игровые консоли, Android-реплики айфонов, айпадов, макбуков, Samsung Galaxy, Nokia Lumia/HTC/Sony Xperia и другие подделки на популярные бренды. Можно невключайки/нерабочие/зависающие, почти любое состояние - все постараюсь оживить и поднять. Что с ними происходит потом? Смотрите сами: Альтернативное Apple'водство: Как и зачем я променял оригинальный айфон на китайский нерабочий 14 Pro Max, На помойку? Никак нет! Пишем нативные приложения для китайских кнопочников, Сам себе Linux-смартфон: Выкидываем Android из старого Fly и пилим свою оболочку, Сам себе экосистема, портируем свежий Android на NoName-смартфон, на грани отвала eMMC: переносим Android на MicroSD, накатываем чистый Android на китайский iPhone 5s, бомж-гейминг за копейки с отвальной консолью. Так что не сомневайтесь, девайсы попадают в хорошие руки :) Все стараюсь поднять, оживить и написать про них материал! Есть что-то подобное? Пишите в комменты или в тг @monobogdan. Спасибо!

Показать полностью
[моё] Смартфон Гаджеты Покупка Поиск Розыск Android Мобильные телефоны Донат Телефон Электроника Ремонт телефона Программирование
27
9
stefannorman
stefannorman
1 год назад

Отпугиватель собак⁠⁠

Был тут недавно пост в Горячем про ультразвуковой отпугиватель собак, в комментариях обсуждали, работает или нет, где купить, и т.д.

Возникал мысль - а может ли смартфон в плюс-минус штатном режиме работы издавать ультразвук? Технические характеристики динамика позволяют? И если да, то можно ли программными средствами заставить его такой звук издавать (типа приложение написать)?

Ну и как это проверить на практике, в случае если в теории такое возможно - люди же не слышат ультразвук, а искать для этого собаку на улице не вариант.

Прошу тапками не кидать, я слабо разбираюсь в электронике, может и глупость сказал.

Android Звук Нападение собак Бродячие собаки Программирование Приложение Текст
5
stalkself
stalkself
1 год назад
Серия Программирую, пока программируется

Пятнашки на Android⁠⁠

Решил попробовать сделать приложение для мобильных устройств, используя многострадальный Delphi, переделав одну из своих программ для ПК. Получилось как-то так:

Пятнашки на Android Программирование, Разработка, Инди, Инди игра, Android, Delphi, Паскаль, Pascal, Мобильные игры

Скачать, если кому интересно, можно здесь.

Проверял на нескольких устройствах, для древних Андроидов (четыре и ниже) не подойдет скорее всего. Я просто не нашел настолько старый рабочий SDK.

Что могу сказать от себя - делать мобильные приложения тот еще геморой. Эта простая игра из меня все соки выпила. Было не очень интересно, скорее даже напряжно. Чтобы портировать простой код пришлось решить кучу вспомогательных технических задач. Даже не ожидал, что будет так сложно. Наверно, если бы был программистом, то было бы легче, но имеем то, что имеем.

Показать полностью 1
[моё] Программирование Разработка Инди Инди игра Android Delphi Паскаль Pascal Мобильные игры
0
Посты не найдены
О нас
О Пикабу Контакты Реклама Сообщить об ошибке Сообщить о нарушении законодательства Отзывы и предложения Новости Пикабу Мобильное приложение RSS
Информация
Помощь Кодекс Пикабу Команда Пикабу Конфиденциальность Правила соцсети О рекомендациях О компании
Наши проекты
Блоги Работа Промокоды Игры Курсы
Партнёры
Промокоды Биг Гик Промокоды Lamoda Промокоды Мвидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня
На информационном ресурсе Pikabu.ru применяются рекомендательные технологии