Mổ Xẻ Kiến Trúc Telegram — Phần 1: Android
Bài viết phân tích kiến trúc kỹ thuật của Telegram Android client dựa trên source code chính thức tại github.com/DrKLO/Telegram. Đây là Phần 1 trong series 2 phần (Phần 2 sẽ phân tích iOS).
0. Tại sao Telegram đáng để mổ xẻ?
Telegram là một trong số rất ít messenger thương mại còn open-source toàn bộ client code. Với hơn 1 tỷ user active hàng tháng, codebase của họ là một case study cực kỳ giá trị về:
- Hiệu năng — chat list scroll mượt 60fps trên thiết bị 5 năm tuổi.
- Bandwidth efficiency — MTProto tối ưu cho mạng 2G/3G yếu (vốn là context Telegram được thiết kế ban đầu ở Nga).
- Battery life — push notification, background sync, file download đều phải tiết kiệm pin.
- Multi-account — kiến trúc tách biệt state cho từng account chạy song song trong cùng process.
- Cross-platform native code reuse — phần lớn network/crypto/media code share giữa Android, iOS, Desktop.
Repository chính thức: DrKLO/Telegram, ngôn ngữ: Java 43.3%, C++ 32.8%, C 14.3%, Assembly 4.5% (theo thống kê GitHub). Con số 4.5% Assembly nói lên rằng đây không phải app Android “bình thường” — nó là một dự án kỹ thuật nghiêm túc với hot path được tối ưu xuống tận cấp lệnh CPU.
1. Tổng quan tech stack
| Thành phần | Công nghệ |
|---|---|
| Build system | Gradle 8.x + AGP, NDK r22+, CMake 3.10+ |
| Min SDK / Target SDK | 21 / 35 (cũ vẫn hỗ trợ Android 5.0 Lollipop) |
| Ngôn ngữ chính | Java (UI/business), C/C++ (network, crypto, media), Kotlin (~1.5%, một số module mới) |
| UI framework | Pure Android View system — không dùng Fragment/Jetpack Compose/RxJava/Dagger |
| Async | DispatchQueue tự viết — không dùng coroutines/RxJava |
| DI | Không dùng — singleton pattern + manual injection |
| Network | MTProto 2.0 viết tay bằng C++, không dùng OkHttp/Retrofit cho main protocol |
| Database | SQLite raw qua JNI wrapper, không dùng Room/SQLDelight |
| Image loading | ImageLoader tự viết, không dùng Glide/Coil/Picasso |
Điểm đáng chú ý: Telegram gần như không dùng thư viện Android phổ biến nào. Toàn bộ Glide, Retrofit, Room, Hilt, Coroutines, RxJava, Compose — họ tự viết hết. Đây không phải NIH (Not Invented Here) syndrome — đây là quyết định kỹ thuật có lý do:
- Kiểm soát hiệu năng end-to-end. Glide cache eviction policy khác với cái họ cần. OkHttp không hiểu MTProto. Room không xử lý được tốc độ insert mà Telegram đòi hỏi.
- APK size. Mỗi thư viện thêm vài trăm KB. Telegram chạy ở thị trường có user dùng máy 16GB ROM.
- Cross-platform consistency. Native code share được giữa Android/iOS/Desktop. Nếu dùng Glide thì iOS không có tương đương.
2. Cấu trúc module
Telegram/
├── TMessagesProj/ # Module thư viện chính (core)
├── TMessagesProj_App/ # App chính cho Google Play
├── TMessagesProj_AppHuawei/ # Biến thể Huawei AppGallery (HMS thay GMS)
├── TMessagesProj_AppHockeyApp/ # Biến thể Beta distribution
├── TMessagesProj_AppStandalone/ # APK độc lập (không qua Play Store)
├── TMessagesProj_AppTests/ # Unit/instrumentation tests
├── Tools/ # Build tools (apkdiff, apkfrombundle...)
└── gradle/ # Gradle wrapper
Tại sao tách TMessagesProj riêng?
TMessagesProj là Android Library Module (com.android.library). Tất cả logic — UI, business, network, native code — đều nằm ở đây. Các module TMessagesProj_App* chỉ là wrapper mỏng chứa:
AndroidManifest.xmlđặc thù từng store (Huawei không có Firebase, dùng HMS Push Kit)google-services.json/agconnect-services.jsonriêng- Build flavor config riêng (App ID, signing key, ProGuard rules)
Lợi ích: cùng một codebase build ra 4 APK khác nhau cho 4 kênh phân phối, không phải maintain 4 fork. Đây là pattern rất đáng học cho team Việt Nam đang phải release cùng app lên CH Play, Huawei AppGallery, Samsung Galaxy Store, và sideload APK.
Build variants điển hình
// Trong TMessagesProj_App/build.gradle
flavorDimensions "abi", "type"
productFlavors {
afat { dimension "abi" } // arm64-v8a only
bundleAfat { dimension "abi" } // App Bundle, multi-ABI
bundleAfat_SDK23 { dimension "abi"; minSdkVersion 23 }
standalone { dimension "type" }
HA_private { dimension "type" } // Hockey App private beta
}
3. Cấu trúc package Java
org.telegram/
├── messenger/ # Business logic (~160+ classes)
│ ├── ApplicationLoader.java # Application class — entry point
│ ├── NotificationCenter.java # Event bus toàn app
│ ├── DispatchQueue.java # Thread pool tự viết
│ ├── ConnectionsManager.java # JNI facade cho MTProto
│ ├── MessagesController.java # State manager cho messages
│ ├── ContactsController.java # State manager cho contacts
│ ├── MediaController.java # Audio/video playback
│ ├── DownloadController.java # Download queue
│ ├── SendMessagesHelper.java # Send pipeline
│ ├── FileLoader.java # Multi-part file loader
│ ├── ImageLoader.java # Image cache + decode
│ ├── voip/ # VoIP integration layer
│ ├── camera/ # CameraX wrapper riêng
│ └── video/ # Video encoder/transcoder
│
├── ui/ # Presentation layer (~210+ classes)
│ ├── ActionBar/ # Custom ActionBar (không dùng AppCompat)
│ ├── Adapters/ # RecyclerView adapters
│ ├── Cells/ # Custom View cho list items
│ ├── Components/ # Reusable views
│ │ ├── chat/
│ │ ├── voip/
│ │ ├── Reactions/
│ │ ├── Premium/
│ │ └── Paint/ # Photo editor brushes
│ ├── Stories/ # Stories feature
│ ├── ChatActivity.java # Màn chat — file lớn nhất repo (~30k dòng)
│ └── ProfileActivity.java
│
├── tgnet/ # Network protocol layer
│ ├── tl/ # TL (Type Language) generated code
│ │ ├── TLRPC.java # ~100k+ dòng autogen
│ │ ├── TL_messages.java
│ │ ├── TL_chats.java
│ │ └── ...
│ ├── ConnectionsManager.java # Java side of JNI
│ ├── TLObject.java
│ └── NativeByteBuffer.java # Direct ByteBuffer wrapper
│
├── SQLite/ # Database layer
│ └── SQLiteDatabase.java # SQLite via JNI, không dùng android.database
│
└── PhoneFormat/ # Phone number formatting (libphonenumber-style)
Một quan sát quan trọng
Telegram Android không dùng Fragment. ChatActivity, ProfileActivity, SettingsActivity… thực chất không phải Android Activity mà là class kế thừa từ BaseFragment — một abstraction tự viết của họ. Toàn app chỉ có một Activity duy nhất là LaunchActivity, và mọi “màn hình” đều là custom View được swap vào container của activity đó.
Lý do:
- Fragment lifecycle phức tạp và bug-prone (đặc biệt restore state)
- Animation chuyển màn hình kiểm soát được tốt hơn
- Memory footprint thấp hơn (không tạo FragmentManager state)
- iOS dùng
UINavigationControllerpush/pop → Android tự cài navigation tương tự để cross-platform UX nhất quán
Đây là lý do scroll giữa các màn hình trong Telegram cảm giác “iOS-like” và mượt hơn hầu hết app Android khác. Trade-off: bạn mất hết tooling của Jetpack Navigation, không dùng được Compose Navigation, lifecycle phải tự manage.
4. Native code (JNI) — phần “thật” của Telegram
TMessagesProj/jni/ # ~548MB, 6,300+ files
├── tgnet/ # MTProto Protocol (Telegram tự viết)
│ ├── ConnectionsManager.cpp # ~180KB — orchestration trung tâm
│ ├── Datacenter.cpp # DC selection, auth key
│ ├── Connection.cpp # TCP socket per DC
│ ├── ConnectionSocket.cpp # Raw socket I/O
│ ├── Handshake.cpp # MTProto auth key generation
│ ├── MTProtoScheme.cpp # Wire format serialization
│ └── ApiScheme.cpp # ~65KB — TL schema cho API
│
├── ffmpeg/ # Prebuilt static libs cho từng ABI
│ └── arm64-v8a/
│ ├── libavcodec.a
│ ├── libavformat.a
│ └── libswscale.a
│
├── boringssl/ # OpenSSL fork của Google
├── opus/ # Voice codec (RFC 6716)
├── rlottie/ # Lottie animations (Samsung's renderer)
├── voip/
│ ├── webrtc/ # WebRTC (Google's)
│ ├── libtgvoip/ # Telegram VoIP cũ (1-1 calls)
│ └── tgcalls/ # tgcalls mới (group calls + video)
├── third_party/
│ ├── libyuv/ # YUV color space conversion
│ ├── openh264/ # H.264 software encoder
│ └── breakpad/ # Crash dump generation
├── tde2e/ # Telegram E2E encryption
├── mozjpeg/ # JPEG encoding (better quality/size)
│
├── jni.c # JNI entry point — JNI_OnLoad
├── image.cpp # ~40KB — image decode/blur/resize
├── video.c # Video metadata parsing
├── audio.c # ~32KB — Opus encode/decode
├── gifvideo.cpp # ~40KB — GIF rendering
├── lottie.cpp # rlottie binding
└── CMakeLists.txt # ~27KB — build config
JNI registration flow
Khi process Android start, Java load libtmessages.XX.so. Trong native lib, hàm JNI_OnLoad() là điểm khởi tạo:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
vm->GetEnv((void **)&env, JNI_VERSION_1_6);
imageOnJNILoad(vm, reserved, env); // Image processing
videoOnJNILoad(vm, reserved, env); // Video processing
registerNativeTgNetFunctions(vm, env); // MTProto network
tgvoipOnJNILoad(vm, reserved, env); // VoIP
return JNI_VERSION_1_6;
}
Mỗi module tự register native methods cho lớp Java tương ứng. Pattern này giúp:
- Lazy loading từng module
- Naming conflict tránh được nhờ namespace JNI
- Symbol stripping aggressive hơn ở release build
Tại sao network layer phải viết bằng C++?
Đây là câu hỏi mọi developer mới nhìn vào Telegram codebase đều hỏi. Câu trả lời có 4 ý:
1. MTProto cần kiểm soát socket cấp thấp. MTProto chạy trên TCP nhưng có TLS-mimicking obfuscation layer (để bypass DPI ở các nước censor). OkHttp/Java networking không cho bạn touch raw bytes ở mức cần thiết.
2. Cross-platform code reuse. File ConnectionsManager.cpp được dùng nguyên xi trên iOS, macOS, Linux Desktop, Windows. Nếu viết bằng Java thì phải port sang Swift/Objective-C, mất công gấp 4 lần.
3. Performance. Mã hóa AES-IGE, hash SHA-256 cho mỗi message. JIT của ART tốt nhưng vẫn chậm hơn native cho crypto hot path 2-3 lần. Với user gửi 100 message/phút, cộng dồn pin tiêu thụ đáng kể.
4. Memory layout control. TL serialization yêu cầu serialize/deserialize binary structure trực tiếp. Java buộc bạn copy qua byte[], native dùng được pointer trực tiếp lên NativeByteBuffer (DirectByteBuffer trong Java đối ứng).
5. Network layer — MTProto chi tiết
Sơ đồ luồng
┌─────────────────────────────────────────────────────────┐
│ Java Layer │
│ ┌───────────────────────────────────────────────────┐ │
│ │ ConnectionsManager.java │ │
│ │ - sendRequest(TLObject, callback) │ │
│ │ - bindRequestToGuid(reqId, guid) │ │
│ │ - cancelRequest(reqId, notifyServer) │ │
│ └───────────────────────────────────────────────────┘ │
│ │ JNI (native methods) │
├─────────────────────────┼───────────────────────────────┤
│ Native Layer │
│ ┌───────────────────────────────────────────────────┐ │
│ │ tgnet::ConnectionsManager.cpp │ │
│ │ - Datacenter routing (DC1-DC5) │ │
│ │ - Connection pooling (4 conns/DC) │ │
│ │ - Request queue + retry logic │ │
│ │ - Salt rotation │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Datacenter.cpp │ │
│ │ - auth_key generation (DH-2048) │ │
│ │ - server_salt management │ │
│ │ - IP address rotation (IPv4/IPv6/MEDIA/DOWNLOAD) │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Connection.cpp / ConnectionSocket.cpp │ │
│ │ - epoll-based event loop │ │
│ │ - obfuscated2 transport (random padding) │ │
│ │ - AES-IGE encryption per packet │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Các loại connection per Datacenter
Mỗi DC có 4 loại connection chạy song song:
| Loại | Mục đích |
|---|---|
ConnectionTypeGeneric | RPC calls thông thường (sendMessage, getDialogs…) |
ConnectionTypePush | Long-poll cho updates real-time |
ConnectionTypeDownload | File download (parallel, có thể có 8 sockets) |
ConnectionTypeUpload | File upload |
Tách connection theo loại có lý do quan trọng: nếu user đang download file 500MB, bạn không muốn message text bị block. Mỗi loại có queue riêng, priority riêng, retry policy riêng.
Datacenter là gì và tại sao có nhiều?
Telegram chia data center theo địa lý (DC1=Miami, DC2=Amsterdam, DC3=Miami, DC4=Amsterdam, DC5=Singapore — đại khái vậy, có thay đổi theo thời gian). User được assign cho home DC dựa trên số điện thoại. Nhưng khi chat với user ở DC khác, app phải biết route request sang đúng nơi.
Logic này nằm ở Datacenter.cpp và ConnectionsManager.cpp. Khi gặp lỗi FILE_MIGRATE_X hay USER_MIGRATE_X trả về từ server, native code tự switch DC và retry — Java layer không hề biết. Đây là lý do Java side thấy network code “đơn giản kỳ lạ”: phức tạp đã bị giấu hết xuống native.
TL (Type Language) schema
Telegram dùng schema language tự viết tên là TL để định nghĩa wire format. Ví dụ:
message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true
media_unread:flags.5?true silent:flags.13?true
id:int from_id:flags.8?Peer peer_id:Peer
date:int message:string ... = Message;
Schema này được parse và generate code ra cho cả Java (TLRPC.java) và C++ (TLClassStore.cpp). Đây là lý do TLRPC.java có hơn 100k dòng — toàn bộ là autogen từ .tl schema.
Khi schema thay đổi (Telegram thêm feature mới), chỉ cần update file .tl, regenerate code, là cả Android và iOS có ngay binding mới.
6. Design patterns chủ đạo
6.1 NotificationCenter — event bus toàn app
Telegram dùng pattern Observer cực kỳ heavy. Có hơn 300 loại event được định nghĩa trong NotificationCenter.java:
// Đăng ký
NotificationCenter.getInstance(currentAccount)
.addObserver(this, NotificationCenter.didReceiveNewMessages);
// Phát event (thường từ controller)
NotificationCenter.getInstance(currentAccount)
.postNotificationName(NotificationCenter.didReceiveNewMessages,
dialogId, messages, scheduled);
// Nhận event
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.didReceiveNewMessages) {
long dialogId = (Long) args[0];
ArrayList<MessageObject> messages = (ArrayList<MessageObject>) args[1];
// update UI
}
}
Đây là lý do code Telegram lỏng lẻo về compile-time type safety — Object... args dễ gây ClassCastException nếu sai. Trade-off đổi lấy:
- Loose coupling cực mạnh giữa các component
- Multi-account dễ implement (mỗi account 1 NotificationCenter instance)
- Cross-class communication không cần inject
Nếu là project bạn viết mới hôm nay, mình sẽ khuyên dùng Flow/SharedFlow của Coroutines hoặc EventBus library với typed events. Nhưng Telegram bắt đầu từ 2013, khi những thứ đó chưa có.
6.2 DispatchQueue — kế thừa từ iOS GCD
DispatchQueue queue = new DispatchQueue("messages_queue");
queue.postRunnable(() -> {
// chạy trên background thread riêng của queue
});
queue.postRunnable(runnable, 1000); // delay 1s
DispatchQueue là tên trùng với Grand Central Dispatch của iOS — không phải tình cờ. Telegram porting concept này từ iOS sang. Mỗi controller quan trọng có queue riêng:
messagesQueue— DB read/write cho messagesstorageQueue— disk I/O chungnetworkQueue— network callback dispatchcacheOutQueue— image cache eviction
Điểm khác ExecutorService của Java: DispatchQueue là single-thread, đảm bảo serial execution. Bạn không phải lock vì tất cả task chạy tuần tự.
6.3 Controller pattern — Singleton per account
public class MessagesController extends BaseController {
private static volatile MessagesController[] Instance = new MessagesController[UserConfig.MAX_ACCOUNT_COUNT];
public static MessagesController getInstance(int num) {
MessagesController localInstance = Instance[num];
if (localInstance == null) {
synchronized (MessagesController.class) {
localInstance = Instance[num];
if (localInstance == null) {
Instance[num] = localInstance = new MessagesController(num);
}
}
}
return localInstance;
}
}
MAX_ACCOUNT_COUNT mặc định là 3 (Free) hoặc 4 (Premium). Mỗi account có instance riêng cho mọi controller:
AccountInstance.getInstance(0).getMessagesController();
AccountInstance.getInstance(1).getMessagesController(); // account khác, instance khác
Đây là backbone của multi-account. Switching account chỉ là đổi currentAccount integer, không phải re-login hay tear down instance.
7. Database layer
Telegram không dùng Room, không dùng android.database.sqlite mà dùng SQLite C API trực tiếp qua JNI wrapper riêng:
public class SQLiteDatabase {
public SQLiteCursor queryFinalized(String sql, Object... args) throws SQLiteException;
public SQLitePreparedStatement executeFast(String sql);
public void beginTransaction();
public void commitTransaction();
}
Tại sao không dùng SQLiteOpenHelper của Android?
- Multi-threading kiểm soát kém. Android wrapper có internal lock khó debug khi contention cao.
- Bind argument hiệu quả hơn. Native binding tránh overhead boxing/unboxing.
- Custom serialization. TL objects được serialize thẳng vào SQLite BLOB column dưới dạng binary, không cần JSON/Protobuf.
- Custom indexes + virtual tables. Họ dùng FTS4 cho search, custom index cho dialog ordering.
Tables chính
| Table | Nội dung |
|---|---|
messages_v2 | Tin nhắn (TL serialized blob + metadata columns) |
dialogs | Conversation list với pinned/unread state |
users | User cache |
chats | Group/channel cache |
media_v4 | Media files index theo type (photo/video/doc/audio) |
enc_chats | Secret chats |
randoms_v2 | Random IDs cho deduplication |
pending_tasks | Task queue cho delayed operations |
Schema có version, migration tự viết bằng raw SQL ALTER. Khi version mismatch, code chạy chuỗi migration step-by-step (giống Room nhưng làm tay).
8. UI architecture — tại sao mượt?
8.1 Cells — không dùng RecyclerView ViewHolder mặc định
org.telegram.ui.Cells.* chứa hàng trăm class kế thừa View (không phải ViewGroup!) — ví dụ DialogCell, ChatMessageCell, UserCell. Đây là sự khác biệt quan trọng:
public class DialogCell extends BaseCell {
private StaticLayout nameLayout; // Tự draw text
private StaticLayout messageLayout;
private ImageReceiver avatarImage; // Custom image holder
@Override
protected void onDraw(Canvas canvas) {
avatarImage.draw(canvas);
nameLayout.draw(canvas);
messageLayout.draw(canvas);
// tự draw checkmark, time, badge...
}
}
Mỗi cell là một View thuần (không phải ViewGroup), tự tay draw text/image bằng StaticLayout/Canvas. Lợi ích:
- No view inflation cost. XML inflation chậm — Telegram tránh hoàn toàn.
- No measure/layout overhead. ViewGroup phải traverse children →
Viewkhông có children. - Pixel-perfect control. Animation tween được từng pixel của text.
- Memory siêu thấp. Một cell = 1 View object thay vì 5-10 nested Views.
Cái giá phải trả: viết một cell cần ~500-1000 dòng code. Đổi lại scroll 60fps trên Galaxy J2 (RAM 1GB, 2016).
8.2 ImageReceiver — image loading riêng
Thay vì Glide/Coil, Telegram có ImageReceiver. Workflow:
- Cell gọi
imageReceiver.setImage(imageLocation, filter, ...) ImageLoadercheck 3 cấp cache: memory → disk → network (qua MTProto download connection)- Khi có bitmap,
imageReceiver.invalidate()→ cellinvalidate()→ redraw - Bitmap được scale/blur/round corner trực tiếp trong
onDraw()của cell
Khác biệt với Glide: ImageReceiver là thành viên của Cell, không phải attach vào ImageView. Khi cell scroll khỏi viewport, ImageReceiver.onDetachedFromWindow() huỷ pending request — hành vi này tự động và đúng đắn.
8.3 Theme system
Telegram có theme system cực mạnh, hỗ trợ:
- Light / Dark / Custom user themes
- Per-chat wallpaper
- Animated theme transition
- Vibrant gradient backgrounds
Mọi color được lấy qua Theme.getColor(Theme.key_chat_inBubble) chứ không hard-code. Khi user đổi theme, NotificationCenter.didApplyNewTheme trigger → mọi cell invalidate() → redraw với màu mới. Không cần Activity restart.
9. Build system
9.1 NDK + CMake
# CMakeLists.txt (rút gọn)
add_library(tmessages.${SOURCESET_VERSION} SHARED
jni.c
image.cpp
video.c
audio.c
${TGNET_SOURCES}
${VOIP_SOURCES}
)
target_link_libraries(tmessages.${SOURCESET_VERSION}
avformat avcodec avutil swscale swresample # ffmpeg
ssl crypto # boringssl
opus
rlottie
tgvoip tgcalls
log z OpenSLES EGL GLESv2 jnigraphics
)
# NEON assembly cho ARM
if (${ANDROID_ABI} STREQUAL "armeabi-v7a")
target_compile_options(... -mfpu=neon)
endif()
Đây là CMakeLists hàng nghìn dòng. Build NDK lần đầu mất 15-30 phút trên máy mạnh.
9.2 ABI strategy
Telegram ship cả arm64-v8a, armeabi-v7a, x86, x86_64. Cho mỗi ABI họ build:
- Toàn bộ
.cpp/.c→.soriêng - Prebuilt static libs (ffmpeg, boringssl) đã có sẵn
.acho từng ABI trong repo
App Bundle (.aab) cho phép Play Store split APK theo ABI của device → user chỉ download phần ABI cần thiết, tiết kiệm vài chục MB.
9.3 Reproducible builds
Telegram public commit script apkdiff.py cho phép verify rằng APK trên Play Store build từ chính source code public trên GitHub. Đây là một trong số ít messenger thương mại làm được điều này. Quy trình:
- Clone repo, build APK từ source.
- Tải APK trên Play Store về.
- Chạy
apkdiff.py our.apk store.apk— chỉ chấp nhận khác biệt về signing/timestamp/keystore.
Đây là pattern đáng học cho team làm app yêu cầu trust cao (banking, healthcare).
10. Performance optimizations đáng học
10.1 Pre-rendering text với StaticLayout
// Tạo StaticLayout 1 lần khi data thay đổi, KHÔNG phải mỗi onDraw
nameLayout = new StaticLayout(name, namePaint, width,
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
// onDraw chỉ gọi draw() — không reflow text
@Override
protected void onDraw(Canvas canvas) {
nameLayout.draw(canvas); // O(1) thực tế
}
StaticLayout cache toàn bộ glyph positions. So với TextView (gọi measure mỗi lần), tốc độ nhanh hơn 5-10x cho list scroll.
10.2 RLE-like stickers compression
Stickers Telegram dùng định dạng .tgs — Lottie JSON nén bằng gzip. Để render mượt 60fps, file Lottie được parse 1 lần, animation frames cache vào memory. rlottie (engine của Samsung) còn nhanh hơn airbnb/lottie-android — Telegram contribute ngược cho Samsung khá nhiều.
10.3 Voice waveform tiền xử lý
Khi user nhận voice message, server đính kèm sẵn waveform array (~64 bytes biểu diễn biên độ âm thanh). Client không cần decode audio để vẽ waveform — chỉ vẽ bars từ array có sẵn. Audio chỉ decode khi user nhấn play.
10.4 Photo blur preview
Trước khi photo full-size load xong, Telegram show blur preview chỉ 100-300 bytes (thumbnail size 32×32 đã blur sẵn từ server). Lúc full image về, fade transition. UX tốt hơn placeholder grey nhiều, mà bandwidth gần như zero.
11. Những điểm “lạ” có lý do
| Quyết định | Lý do thực sự |
|---|---|
| Tự viết SQLite wrapper | Cần multi-thread access pattern khác SQLiteOpenHelper |
| Không dùng Glide/Coil | ImageReceiver tích hợp với MTProto download path |
| Không dùng Coroutines | Codebase 2013, refactor sang giờ tốn quá nhiều |
| Custom Fragment system | Match iOS UINavigationController behavior |
| Cells tự draw bằng Canvas | Performance trên thiết bị low-end |
100k dòng TLRPC.java | Autogen từ .tl schema, không edit tay |
| Native MTProto | Cross-platform reuse + crypto performance |
| Singleton everywhere | Multi-account architecture |
Object... args trong NotificationCenter | Loose coupling — đổi type-safety lấy linh hoạt |
12. Bài học cho developer Việt
Nếu bạn đang làm app messaging/social/marketplace có yêu cầu real-time mạnh:
1. Network layer nên tách hẳn. Việc Telegram để network ở native code tách biệt hoàn toàn business logic là một bài học lớn. Khi bạn đổi thuật toán retry, đổi codec, đổi protocol — không phải động vào UI/business code.
2. Event bus có giá trị thật. Với multi-account, real-time updates từ nhiều nguồn (push, polling, user action), event bus pattern (NotificationCenter / SharedFlow / EventBus) cứu rất nhiều code.
3. Đừng sợ tự viết. Glide/Retrofit/Room tốt cho 90% app. Nhưng nếu bạn là 10% còn lại (real-time, low-end device target, custom protocol), tự viết core component có thể là quyết định đúng. Telegram là minh chứng.
4. Profile trên thiết bị thật, không phải emulator. Telegram tối ưu xuống Galaxy J2 — máy 1GB RAM. Bạn không cần đi xa thế, nhưng test trên device thật của user (Vivo Y15, Samsung A02s, Xiaomi Redmi 9A) sẽ phát hiện vấn đề mà Pixel emulator không thấy.
5. Cross-platform native code không phải science fiction. React Native, Flutter có vị trí của nó. Nhưng nếu bạn cần performance peak và team có C++ skill, share native module giữa Android/iOS là chiến lược của Telegram, Signal, WhatsApp — 3 messenger lớn nhất thế giới.
13. Tài nguyên tham khảo
- Repo chính: github.com/DrKLO/Telegram
- MTProto spec: core.telegram.org/mtproto
- TL schema: core.telegram.org/schema
- API documentation: core.telegram.org/api
- Reproducible builds: core.telegram.org/reproducible-builds
- Security guidelines: core.telegram.org/mtproto/security_guidelines
Phần 2: iOS sẽ phân tích Telegram-iOS — nơi codebase gây sốc nhiều hơn nữa: 90% Swift, kiến trúc với SignalProducer (ReactiveSwift), ComponentKit-style UI từ thời Buzz, Tuples chứa hàng trăm field, và hệ thống module Bazel-based với hơn 300+ module. Đặc biệt sẽ so sánh trực tiếp các quyết định khác biệt giữa hai platform: tại sao iOS dùng async stream còn Android dùng NotificationCenter, tại sao iOS có 300 module còn Android chỉ có 2.
Tài liệu được biên soạn dựa trên source code công khai của DrKLO/Telegram và tài liệu kiến trúc thực hành.