Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
Android
val khai báo biến chỉ đọc, không thể gán lại sau khi khởi tạo (tương tự final trong Java), còn var khai báo biến có thể thay đổi giá trị.
- Dùng
vallàm mặc định giúp code an toàn hơn và phù hợp với nguyên tắc immutability trong kiến trúc MVVM. - Kotlin compiler khuyến khích dùng
valở mọi nơi có thể.
Trong Kotlin, biến mặc định không được phép null để loại bỏ NullPointerException.
- Muốn cho phép null, bạn khai báo kiểu nullable bằng
?(ví dụString?). - Compiler bắt buộc bạn xử lý null ngay lúc biên dịch qua các toán tử như
?.(safe call) hoặc!!(ép buộc). - Điều này giúp các lỗi liên quan đến null không thể lọt qua bước compile.
Extension function cho phép bạn thêm method mới vào class có sẵn mà không cần kế thừa hay sửa code gốc.
Ví dụ có thể thêm method thẳng vào class String: fun String.isPhoneNumber(): Boolean { return this.length == 10 }.
- Tên hàm được đặt trước bằng tên class cần mở rộng, và
thistrỏ đến instance đang gọi. - Cực kỳ hữu ích trong Android khi cần tạo helper method cho các class của platform.
Data class tự động sinh ra các method boilerplate như equals(), hashCode(), toString() và copy().
- Data class cần ít nhất một tham số trong constructor và rất phù hợp để chứa dữ liệu (như response API hay model database).
- Chúng không thể là abstract, open, sealed, hay inner class, và tất cả tham số constructor phải được đánh dấu
valhoặcvar.
Companion object cho phép định nghĩa các thành viên kiểu static (hằng số, factory method) bên trong class, thuộc về class chứ không phải instance.
- Mỗi class chỉ có một companion object.
- Có thể gọi trực tiếp mà không cần tạo instance:
MyClass.myStaticMethod(). - Giống
statictrong Java nhưng linh hoạt hơn vì có thể implement interface.
Activity lifecycle có 6 method chính theo thứ tự: onCreate() (khởi tạo), onStart() (trở nên visible), onResume() (có focus), onPause() (mất focus), onStop() (không còn visible), onDestroy() (dọn dẹp).
- Hiểu lifecycle rất quan trọng vì bạn phải lưu state trong
onSaveInstanceState()và khôi phục trongonCreate(). - Memory leak thường xảy ra khi giữ strong reference đến Activity quá vòng đời của nó.
Activity là component độc lập chiếm toàn màn hình, quản lý vòng đời UI cho một màn hình.
- Fragment là component UI nhẹ hơn, có thể tái sử dụng, và luôn tồn tại trong một Activity — có thể swap vào/ra tùy ý.
- Fragment giúp xây dựng UI linh hoạt thích ứng với nhiều kích thước màn hình, còn Activity đại diện cho các màn hình riêng biệt trong app.
Intent là object nhắn tin để yêu cầu một hành động từ component khác trong app.
- Explicit intent chỉ định chính xác component cần khởi động (ví dụ mở một Activity cụ thể trong app).
- Implicit intent khai báo action mà không chỉ định component, để hệ thống tìm app phù hợp xử lý (ví dụ mở URL trong browser).
- Cả hai loại đều có thể truyền dữ liệu qua bundle.
Normal permission (internet, vibrate) được cấp tự động khi cài đặt.
- Dangerous permission (camera, location, contacts) phải xin lúc runtime.
- Để xin runtime permission: tạo
ActivityResultContract, dùngregisterForActivityResult(), và kiểm traContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTEDtrước khi dùng tính năng được bảo vệ. - Từ Android 6 trở đi, bỏ qua runtime permission sẽ làm app crash.
Intent filter được khai báo trong manifest để chỉ định loại implicit intent mà Activity có thể xử lý.
- Chúng định nghĩa action, category, và kiểu dữ liệu.
- Khi bạn gửi implicit intent, hệ thống tìm tất cả app có Intent filter phù hợp.
Ví dụ Intent với action ACTION_VIEW và data http:// có thể mở browser.
- Phải thêm
android.intent.category.DEFAULTđể implicit intent hoạt động.
Process là một instance riêng biệt của app với bộ nhớ và tài nguyên riêng, chạy độc lập với các process khác.
- Thread là đơn vị thực thi nhẹ hơn trong một process, chia sẻ bộ nhớ với các thread khác.
- Android app có main thread (UI thread) xử lý toàn bộ UI, còn background thread xử lý tác vụ nặng.
- Tuyệt đối không được block main thread.
Jetpack Compose là bộ công cụ UI declarative hiện đại cho phép xây dựng giao diện Android bằng Kotlin function thay vì XML.
- Nó đang thay thế XML vì code ngắn gọn hơn, type-safe, và dễ tái sử dụng hơn.
- Compose tự động cập nhật UI khi state thay đổi, loại bỏ boilerplate và giúp UI dễ test hơn.
- Google đã tuyên bố đây là cách tiếp cận được khuyến nghị cho UI Android.
@Composable đánh dấu một Kotlin function là pure, idempotent (cùng input → cùng output), và được gọi bởi Compose runtime chứ không phải do bạn gọi thủ công — đây là ràng buộc quan trọng nhất.
- Function phải trả về Unit, không có side effect trực tiếp, và chạy nhanh.
- Annotation
@Composablebáo cho Compose runtime theo dõi recomposition và state change.
Modifier là object có thể chain lại để thêm behavior, styling, hay layout cho composable.
Ví dụ: .size(100.dp), .background(Color.Blue), .padding(16.dp), .clickable { }, .weight(1f). Thứ tự quan trọng vì modifier chain từ trái sang phải: .padding().background() khác với .background().padding(). Modifier là lập trình hàm ứng dụng tốt nhất, kết hợp các behavior nhỏ thành behavior phức tạp.
ViewModel lưu trữ dữ liệu UI theo cách lifecycle-aware, tồn tại qua configuration change như xoay màn hình.
- Nó không giữ reference đến View (ngăn memory leak) và là nơi lý tưởng để đặt business logic, gọi API, và quản lý state.
- Tạo ViewModel mỗi màn hình bằng
viewModel()trong Compose hoặcViewModelProvidertrong Activity/Fragment. - Tuyệt đối không truyền Activity hay View vào ViewModel.
Room là lớp trừu tượng type-safe trên SQLite, tự động sinh code lúc compile, ngăn SQL injection và lỗi runtime.
- Bạn định nghĩa entity (data class), DAO (database access object với query), và Database class.
- Room hỗ trợ LiveData/Flow để quan sát thay đổi database.
- An toàn và dễ dùng hơn SQLite thô rất nhiều, và tích hợp tốt với coroutine và Compose.
DAO là interface với các method cho thao tác CRUD trên database. Bạn annotate method với @Query, @Insert, @Update, @Delete. Room sinh implementation lúc compile.
Ví dụ: @Query("SELECT * FROM users WHERE id = :id") suspend fun getUser(id: Int): User. DAO là nơi định nghĩa query, và dùng suspend function giúp query tự động chạy trên background thread.
Retrofit là HTTP client type-safe chuyển REST API thành Kotlin interface.
- Bạn định nghĩa interface với method được annotate bằng HTTP verb như
@GET,@POST, thêm kiểu request/response, và Retrofit tự sinh implementation. - Nó tự động deserialize JSON response thành Kotlin object, xử lý header, và hỗ trợ cả callback lẫn coroutine.
- OkHttp là thư viện networking nền mà Retrofit dùng.
Sealed class giới hạn một class hierarchy thành tập hợp subclass cố định, được định nghĩa trong cùng một file hoặc package.
- Rất phù hợp để biểu diễn các trạng thái giới hạn như response API (Success/Error/Loading).
- Khi dùng với
when, compiler bắt buộc xử lý hết tất cả subclass, không cần nhánhelse, giúp code an toàn hơn rất nhiều.
Scope function thực thi một block code trên một object trong một scope tạm thời. let chạy code chỉ khi object khác null (trả về kết quả lambda). run giống let nhưng dùng this thay vì tham số. apply cấu hình object và trả về chính object đó (dùng khi khởi tạo). also giống apply nhưng truyền object vào làm tham số. with giống run nhưng cú pháp khác.
Chọn loại dựa vào nhu cầu null-safety, giá trị trả về, hay cách tham chiếu object.
Coroutine là các hàm nhẹ có thể tạm dừng và tiếp tục, cho phép lập trình bất đồng bộ hiệu quả mà không block thread.
- Khác với callback dễ dẫn đến "callback hell", coroutine cho phép viết code bất đồng bộ trông như code tuần tự, dễ đọc hơn nhiều.
- Một thread có thể chạy hàng nghìn coroutine nhờ cơ chế suspension, tiết kiệm bộ nhớ và tăng hiệu suất đáng kể.
launch là coroutine fire-and-forget, trả về Job và không trả về kết quả. async dùng khi cần thực hiện tác vụ và lấy kết quả về, trả về Deferred.
- Dùng
launchcho tác vụ độc lập (như cập nhật UI), cònasynckhi cần kết quả trả về (như fetch data từ network). - Phải gọi
await()trên Deferred để lấy kết quả.
Suspend function là hàm được đánh dấu bằng từ khóa suspend, có thể bị tạm dừng và tiếp tục bởi coroutine.
- Chúng chỉ có thể được gọi từ suspend function khác hoặc trong một coroutine scope.
- Suspend function là nền tảng của Kotlin coroutines, cho phép viết code bất đồng bộ theo kiểu tuần tự.
Ví dụ điển hình là bất kỳ hàm nào được đánh dấu suspend bên trong viewModelScope.launch {}.
Bạn có thể dùng try-catch thông thường bên trong coroutine để xử lý exception cục bộ.
- Với exception không được bắt, dùng
CoroutineExceptionHandlerđể bắt ở cấp coroutine scope.viewModelScopedùng SupervisorJob nên coroutine con thất bại không hủy coroutine anh em, nhưng exception không được bắt sẽ KHÔNG tự động được log — chúng sẽ crash coroutine đó trong im lặng trừ khi bạn thêmCoroutineExceptionHandlerhoặc try-catch. - Structured concurrency đảm bảo exception trong coroutine con được truyền lên đúng cách cho scope cha.
Destructuring cho phép bạn tách một object thành các biến riêng lẻ trong một câu lệnh duy nhất.
Ví dụ val (name, age) = person trích xuất thuộc tính từ data class.
- Cũng có thể dùng trong vòng lặp:
for ((key, value) in map). - Hoạt động với data class, Pair, List, và rất hữu ích để code dễ đọc hơn khi làm việc với nhiều giá trị.
Fragment lifecycle phức tạp hơn Activity: onAttach(), onCreate(), onCreateView(), onViewCreated(), onStart(), onResume(), onPause(), onStop(), onDestroyView(), onDestroy(), onDetach().
- Điểm khác biệt quan trọng là View của Fragment có thể bị destroy (
onDestroyView) trong khi Fragment vẫn còn trong bộ nhớ. - Vì vậy phải dọn dẹp View references trong
onDestroyView()để tránh memory leak.
replace() xóa Fragment hiện tại và thêm Fragment mới vào chỗ đó — khi back thì Fragment bị replace sẽ được tạo lại. add() giữ Fragment hiện có và chồng Fragment mới lên trên — khi back thì quay về Fragment cũ.
Dùng replace() để điều hướng giữa các màn hình, còn add() cho overlay như dialog hay bottom sheet.
Service là component chạy nền không có tương tác người dùng, thích hợp cho các tác vụ chạy lâu như phát nhạc, tải file, hay theo dõi vị trí.
- Service không có UI nhưng chạy trên main thread mặc định, nên cần offload tác vụ nặng sang background thread hoặc coroutine.
- Foreground service hiển thị notification liên tục và dùng cho tác vụ nền ưu tiên cao.
BroadcastReceiver là component lắng nghe các broadcast trong toàn hệ thống (như pin yếu, thay đổi network, hay broadcast tùy chỉnh).
- Bạn đăng ký nó trong manifest (static) hoặc trong code (dynamic), và nó kích hoạt
onReceive()khi nhận được broadcast phù hợp. - Tránh thực hiện tác vụ nặng trong
onReceive()vì phải hoàn thành nhanh — dùng WorkManager cho tác vụ dài.
ContentProvider là lớp trừu tượng cung cấp quyền truy cập có kiểm soát vào dữ liệu của app cho các app hoặc process khác.
- Nó dùng URI để xác định resource và implement các thao tác CRUD qua
query(),insert(),update(),delete(). - Danh bạ, lịch, và ảnh đều dùng ContentProvider.
- Chúng đảm bảo tính nhất quán dữ liệu, bảo mật qua permission, và cho phép chia sẻ dữ liệu qua ranh giới app.
onSaveInstanceState() là callback để lưu trạng thái UI tạm thời (như vị trí scroll) trước khi activity bị destroy do configuration change.
- Bundle này được truyền vào
onCreate()hoặconRestoreInstanceState(). - Khác với lưu trữ lâu dài như SharedPreferences hay database.
- Dùng nó cho trạng thái tạm thời dễ lưu hơn là phải tạo lại UI từ đầu.
mutableStateOf() tạo một state object có thể quan sát, kích hoạt recomposition khi thay đổi. remember lưu cache giá trị state qua các lần recomposition để không bị tạo lại mỗi lần.
Kết hợp lại: `val count = remember { mutableStateOf
- }
hoặc ngắn gọn hơn làvar count by remember { mutableStateOf - }
. Khicount` thay đổi, chỉ các composable đang đọc nó mới recompose, không phải cả màn hình
remember giữ state qua các lần recomposition trong scope của composable nhưng mất khi configuration change như xoay màn hình. rememberSaveable persist state qua configuration change bằng cách tự động lưu vào Bundle.
Dùng rememberSaveable cho dữ liệu người dùng nhập (form fields) và remember cho state UI tạm thời (animation values). rememberSaveable có overhead cao hơn một chút.
State hoisting nghĩa là chuyển state từ composable con lên composable cha, làm cho composable con trở nên stateless.
- Composable stateless nhận state qua tham số và callback để cập nhật state, giúp tái sử dụng trên nhiều màn hình.
Ví dụ hoist state của TextField lên cha để nhiều composable có thể đọc.
- Điều này cải thiện khả năng test, tái sử dụng, và làm data flow một chiều rõ ràng hơn.
LaunchedEffect là side-effect function khởi chạy một coroutine scope gắn với lifecycle của composable. Dùng cho các tác vụ một lần như load data, animation, hay analytics. Nó chạy khi key parameter thay đổi và hủy khi composable rời khỏi composition.
Ví dụ: LaunchedEffect(userId) { loadUserData(userId) } load lại data mỗi khi userId thay đổi.
Dùng Compose Navigation library với NavController và NavHost.
- Định nghĩa
NavGraphvới các màn hình và route, rồi dùngnavController.navigate(route)để điều hướng. - State được giữ lại khi quay về màn hình trước.
- Với Compose Navigation 2.8+ (stable từ Oct 2024), dùng type-safe routes qua
@Serializablethay vì string-based routes.BackHandlercho phép tùy chỉnh hành vi nút back.
Dùng MaterialTheme (Material 3) hoặc CompositionLocal tùy chỉnh để tập trung hóa các thuộc tính theme.
- Định nghĩa màu sắc, typography, và shape trong theme object, dùng
CompositionLocalProviderđể cung cấp chúng cho cây composable. - Wrap app trong
MaterialTheme(colorScheme = colorScheme, typography = typography) { App() }— lưu ý tham số làcolorScheme(Material 3), không phảicolors(Material 2). - Hỗ trợ dark/light mode qua
isSystemInDarkTheme().
MVVM (Model-View-ViewModel) tách UI logic khỏi business logic: Model chứa dữ liệu và business rule, View hiển thị UI (Activity/Fragment/Composable), ViewModel chứa UI state và logic, tồn tại qua configuration change.
- ViewModel expose LiveData/StateFlow để View observe.
- Sự tách biệt này cải thiện khả năng test (test ViewModel không cần UI), tái sử dụng, và bảo trì.
- Hầu hết app Android ngày nay đều dùng MVVM.
Repository pattern trừu tượng hóa các nguồn dữ liệu (local database, remote API, cache) sau một interface duy nhất, để ViewModel không cần biết dữ liệu đến từ đâu.
- Sự decoupling này giúp test dễ hơn (mock repository) và cho phép đổi nguồn dữ liệu mà không cần sửa ViewModel.
- Repository điều phối giữa dữ liệu local và remote, implement caching strategy, và là single source of truth.
StateFlow là lựa chọn ưu tiên cho code mới (2025) — tích hợp tự nhiên với coroutine, luôn có value non-null có thể đọc an toàn, và hoạt động tốt với Compose.
- LiveData lifecycle-aware và tự động unsubscribe nhưng đang ở chế độ maintenance.
- StateFlow yêu cầu initial value; LiveData thì không.
- Khi dùng StateFlow trong Fragment, observe trong
repeatOnLifecycleđể tránh memory leak.
Dependency injection nghĩa là cung cấp dependency của một object qua tham số thay vì object tự tạo bên trong.
Lợi ích: test dễ hơn (truyền mock dependency), loose coupling, linh hoạt khi đổi implementation.
Ví dụ: thay vì val database = Database.getInstance() bên trong class, hãy truyền vào: class MyClass(val database: Database). Code modular và testable hơn nhiều.
Hilt là thư viện DI được xây trên Dagger, thiết kế riêng cho Android.
- Nó loại bỏ boilerplate bằng cách cung cấp module có sẵn cho Android component.
- Đánh dấu Application với
@HiltAndroidApp, Activity/Fragment với@AndroidEntryPoint, và inject bằng@Inject. - Hilt tự động quản lý scope (như
@Singleton) và tích hợp liền mạch với ViewModel và composable.
Hilt scope kiểm soát thời gian sống của dependency instance: @Singleton sống suốt vòng đời app, @ActivityScoped theo vòng đời Activity, @ViewModelScoped theo ViewModel, và @FragmentScoped theo Fragment.
- Scope mặc định là unscoped (tạo instance mới mỗi lần).
- Chọn scope dựa vào thời gian muốn tái sử dụng instance.
@Singletonphổ biến nhất nhưng dùng loại khác để quản lý bộ nhớ tốt hơn.
Interceptor chặn HTTP request/response để chỉnh sửa chúng. Application interceptor chạy một lần cho mỗi request, còn network interceptor chạy cho mỗi lần gọi network thực tế. Dùng phổ biến: thêm auth header, log request/response, xử lý token refresh, cache.
Ví dụ: OkHttpClient().addInterceptor { chain -> chain.proceed(chain.request().newBuilder().addHeader("Authorization", token).build()) }.
Dùng Interceptor để thêm auth header vào mọi request.
- Khi token hết hạn, interface Authenticator xử lý token refresh bằng cách chặn response 401 và retry với token mới.
- Lưu token an toàn trong EncryptedSharedPreferences.
- Gửi token trong Authorization header:
Authorization: Bearer token. - Xử lý token hết hạn một cách graceful để người dùng không thấy lỗi.
DataStore là sự thay thế hiện đại cho SharedPreferences, được xây dựng trên coroutine và Flow cho lưu trữ dữ liệu type-safe và transactional.
- Async-first (không blocking), hỗ trợ protocol buffer cho type safety, và an toàn hơn SharedPreferences vốn có vấn đề threading.
- Dùng
context.dataStore.data.map { it.setting }để đọc vàcontext.dataStore.updateData { it.copy(setting = value) }để ghi.
WorkManager lên lịch background work có thể defer được, tồn tại qua app restart và system reboot.
- Nó tự động xử lý Doze Mode và App Standby, chọn cơ chế scheduling tốt nhất (JobScheduler, BroadcastReceiver).
- Dùng cho tác vụ đáng tin cậy, không khẩn cấp như sync data, upload file, hay báo cáo định kỳ.
- Tạo
Workersubclass, dùngOneTimeWorkRequesthoặcPeriodicWorkRequest, và enqueue vớiWorkManager.enqueue().
Paging 3 là thư viện phân trang được khuyến nghị, load hiệu quả dataset lớn từ nguồn local/remote.
- Bạn tạo
PagingSource(hoặcRemoteMediatorcho network+database), dùngPagerđể tạo paginated stream, và collectPagingDatatrong UI vớicollectAsLazyPagingItems(). - Nó xử lý tự động loading, appending, retry, và deduplication, loại bỏ lỗi phân trang thủ công.
Observer pattern cho phép các object (observer) đăng ký nhận thông báo về thay đổi trạng thái của object khác (subject).
- Trong Android, LiveData/StateFlow là các implementation của pattern này.
- Khi bạn gọi
observe()hoặccollect(), bạn đang đăng ký làm observer. - Khi dữ liệu thay đổi, tất cả observer được thông báo, decoupling nguồn dữ liệu khỏi UI.
Configuration change destroy và recreate Activity/Fragment.
- Bảo toàn state bằng: SavedInstanceState (UI state tạm thời), ViewModel (logic và data), và SharedPreferences/DataStore (data lâu dài).
- Không nên dùng
android:configChangesđể tắt recreation trừ khi thực sự cần thiết. - Trong Compose, state tự động tồn tại qua rotation nếu dùng
rememberSaveable. - State management đúng cách giúp rotation trong suốt với người dùng.
LaunchedEffect, DisposableEffect, và SideEffect?LaunchedEffect khởi chạy coroutine cho tác vụ bất đồng bộ. DisposableEffect dùng để thiết lập và dọn dẹp resource với một cleanup block (như event listener). SideEffect chạy sau mỗi lần recomposition, không có tham số, hiếm khi dùng (chủ yếu cho logging).
Dùng LaunchedEffect cho load data, DisposableEffect cho resource, và tránh SideEffect trong hầu hết trường hợp.
derivedStateOf là gì và khi nào nên dùng?derivedStateOf tạo ra một state dẫn xuất được tính toán từ các state khác và chỉ recompose khi kết quả tính toán thay đổi.
Ví dụ: val isFormValid = derivedStateOf { name.isNotEmpty() && email.isNotEmpty() }. Đây là cách tối ưu để ngăn recomposition không cần thiết khi input thay đổi nhưng không ảnh hưởng đến giá trị dẫn xuất. Hữu ích cho các tính toán phức tạp trên state.
CompositionLocal là gì và hoạt động như thế nào?CompositionLocal là cách truyền giá trị ngầm định xuống cây composable mà không cần truyền qua tham số ở mỗi cấp.
- Dùng
CompositionLocalProvider(LocalValue provides value) { Content() }để cung cấp giá trị. - Truy cập bằng
LocalValue.current. - Hữu ích cho theme color, navigation controller, hay các giá trị cần dùng xuyên suốt cây.
- Tuy nhiên lạm dụng sẽ làm code khó theo dõi.
MVI (Model-View-Intent) dùng luồng dữ liệu một chiều như Flux hay Redux.
- View gửi intent (hành động người dùng) lên ViewModel, ViewModel xử lý và emit ViewState mới để cập nhật View.
- Khác với MVVM nơi ViewModel có thể expose nhiều StateFlow, MVI dùng một state object duy nhất được cập nhật theo một hướng.
- Có thể đoán trước hơn nhưng cần nhiều boilerplate hơn.
Clean Architecture gồm 3 layer: Presentation (UI, ViewModel), Domain (business logic, use case), và Data (database, API, repository).
- Mỗi layer độc lập và có thể test riêng.
- Domain layer là Kotlin thuần không phụ thuộc Android, test được trên JVM.
- Data layer cung cấp repository mà domain layer sử dụng.
- Cấu trúc này ngăn tight coupling và cải thiện tổ chức code.
Use Case (hay Interactor) đóng gói một thao tác business logic duy nhất, nhận input và trả về output.
Ví dụ: LoginUseCase, FetchUserDataUseCase. Use Case là phần của Domain layer trong Clean Architecture và độc lập với UI framework. Chúng giúp logic tái sử dụng được, test được, và dễ hiểu. Tạo Use Case khi một logic cần được dùng từ nhiều ViewModel hoặc cần test độc lập.
R8 (người kế nhiệm hiện đại của ProGuard) shrink code không dùng (tree shaking), obfuscate tên class/method để bảo mật, và optimize code cho runtime performance.
- Chạy trong release build, giảm đáng kể kích thước app và thời gian khởi động.
- R8 đã bật mặc định từ AGP 3.4 — không cần thêm
android.enableR8 = truetrong gradle.properties với AGP hiện tại (8.x). - Hiểu cách cấu hình rules R8 rất quan trọng cho production release.
App Bundle là định dạng phân phối Android (upload lên Play Store) tự sinh APK được tối ưu cho từng cấu hình thiết bị.
- Thay vì một APK 100MB, người dùng chỉ nhận 20-30MB với tính năng phù hợp thiết bị của họ.
- Google Play tự động xử lý APK splitting theo density, ngôn ngữ, ABI, và dynamic feature.
- Luôn dùng App Bundle cho release.
Dynamic feature module là các tính năng tùy chọn/theo yêu cầu mà người dùng có thể download sau khi cài.
- Dùng cho tính năng lớn không phải ai cũng cần (như AR filter hay advanced editor).
- Chúng được download theo yêu cầu, giảm kích thước cài đặt ban đầu.
- Implement qua conditional delivery trong Google Play Console.
- Truy cập bằng
SplitCompatvà dynamic feature API.
Đo lường bằng Android Profiler (CPU, memory, network).
- Các metric quan trọng: thời gian khởi động app (cold/warm/hot), frame rate (60fps, hoặc 90/120fps trên thiết bị high-refresh), memory footprint, và battery drain.
- Tối ưu bằng: giảm tác vụ trên main thread, lazy-loading, caching, layout hiệu quả (Compose loại bỏ bottleneck do nested XML nhưng không phải lúc nào cũng nhanh hơn hoàn toàn — dùng Baseline Profile để giảm startup overhead), load ảnh đúng cách, và xử lý tác vụ nền đúng chỗ.
- Profile trước để tìm bottleneck — tối ưu sớm là lãng phí.
Compose Testing dùng semantics-based matcher thay vì view hierarchy. Dùng ComposeTestRule, createComposeRule(), và matcher như onNodeWithText(), onNodeWithContentDescription(). Khác Espresso tìm view theo cấu trúc, Compose testing query cây composable theo ngữ nghĩa.
Ví dụ: composeTestRule.onNodeWithText("Button").performClick(). Đáng tin cậy và nhanh hơn Espresso truyền thống.
KMP cho phép chia sẻ code giữa Android, iOS, và web platform bằng Kotlin.
- Bạn viết business logic một lần trong common module, rồi code platform-specific cho từng nền tảng.
- Với Android app, KMP cho phép chia sẻ code với iOS hay backend service.
- Không bắt buộc nhưng mạnh mẽ cho tổ chức support nhiều platform.
- Setup với plugin
kotlin-multiplatformvà source setcommonMain,androidMain.
Unit test chạy trên JVM không cần Android dependency (nhanh, không cần thiết bị).
- Dùng cho ViewModel, Repository, và utility logic.
- Instrumentation test chạy trên thiết bị/emulator với đầy đủ Android framework (chậm, cần thiết bị).
- Dùng cho Activity, Fragment, và UI behavior.
- Theo testing pyramid: 70% unit test, 20% integration, 10% UI test.
- Tool: JUnit + Mockito cho unit, Espresso/Compose Testing cho UI.
Tránh memory leak bằng cách: không giữ strong reference đến Activity/View (dùng weak ref), unregister listener khi không dùng, implement singleton đúng cách với application context.
- Lưu ý:
viewModelScopevàlifecycleScopetự động bị cancel khi ViewModel cleared hoặc lifecycle destroyed — chỉ cần cancel thủ công với customCoroutineScope. - Dùng
LeakCanaryđể tự động phát hiện leak. - Monitor bằng memory tab trong Android Profiler.
Setup GitHub Actions/GitLab CI để chạy test, build APK/Bundle, và deploy tự động.
- Pipeline điển hình: test mỗi push, build signed release khi có tag, upload lên Firebase App Distribution hoặc Play Store.
- Dùng secret cho signing key.
- Implement pre-commit hook để bắt lỗi sớm ở local.
- Automated testing giảm human error và đảm bảo build nhất quán.
Certificate pinning đảm bảo app chỉ giao tiếp với server có certificate SSL cụ thể, ngăn chặn tấn công man-in-the-middle. Dùng CertificatePinner của OkHttp để pin public key hash.
Ví dụ: CertificatePinner.Builder().add("example.com", "sha256/AAAAAAA...").build(). Cần xử lý việc xoay certificate. Implement cho app nhạy cảm (banking, social network) hoặc dữ liệu có giá trị cao.
Cold flow (như flow {} hay PagingSource) chỉ phát giá trị khi có collector và bắt đầu lại từ đầu cho mỗi collector.
- Hot flow (như
StateFlow,SharedFlow) phát giá trị bất kể có collector hay không và chia sẻ cùng một stream.StateFlowlà hot flow có current value — dùng cho state.Flowthông thường là cold — dùng cho luồng event. - Hot flow hiệu quả bộ nhớ hơn khi có nhiều collector.