DataStore: nó là gì, kiến thức cơ bản về lưu trữ dữ liệu và thay thế SharedPreferences

Tác giả: IT Sectr Đã đăng: 2026-03-12 Thời gian đọc: 9 phút

DataStore là một thành phần từ thư viện Jetpack được thiết kế để lưu trữ một lượng nhỏ dữ liệu trong các ứng dụng Android. Không giống như SharedPreferences, nó hoạt động không đồng bộ và đảm bảo tính nhất quán của dữ liệu khi truy cập đồng thời. Theo Google, 2024, DataStore sử dụng Kotlin Coroutines và Flow, giúp nó an toàn cho luồng chính và phù hợp với kiến trúc phản ứng.

Các Điểm Chính

  • DataStore — sự thay thế cho SharedPreferences với API không đồng bộ và kiểu dữ liệu qua Protocol Buffers
  • Preferences DataStore — kho lưu trữ khóa-giá trị đơn giản với đọc qua Flow và giao dịch
  • Proto DataStore — bộ lưu trữ có kiểu với di chuyển lược đồ tự động
  • SharedPreferences — API đồng bộ chặn luồng UI khi khối lượng lớn
  • Di chuyển được thực hiện qua giao diện SharedPreferencesMigration mà không mất dữ liệu

DataStore là gì?

DataStore là giải pháp của Google để lưu trữ dữ liệu cục bộ trong Android, được giới thiệu vào năm 2020 như một giải pháp thay thế cho SharedPreferences. Nó hỗ trợ hai chế độ: Preferences DataStore (cặp khóa-giá trị đơn giản) và Proto DataStore (lược đồ có kiểu dựa trên Protocol Buffers).

Ưu điểm chính là không đồng bộ hoàn toàn: tất cả các thao tác đọc trả về Flow từ Kotlin Coroutines và viết được thực hiện trong ngữ cảnh coroutine. Điều này loại bỏ việc chặn luồng chính, vốn là vấn đề điển hình của SharedPreferences khi xử lý khối lượng lớn dữ liệu.

DataStore đảm bảo tính nguyên tử của các thao tác: viết đồng thời không gây mất dữ liệu nhờ mô hình giao dịch của nó. Nếu hai thành phần sửa đổi cùng một giá trị đồng thời, DataStore xử lý xung đột một cách chính xác thông qua cơ chế compare-and-swap.

Theo Google I/O 2023, DataStore được sử dụng trong 40% các dự án Android mới và Google khuyến nghị di chuyển từ SharedPreferences trong tất cả các ứng dụng yêu cầu độ ổn định của lưu trữ cài đặt.

Kiến trúc DataStore

Cốt lõi của DataStore là SingleProcessDataStore — một triển khai hoạt động trong một tiến trình duy nhất. Nó sử dụng bộ lưu trữ dựa trên tệp với khóa ở cấp tệp: khi ghi dữ liệu, tệp bị khóa, ngăn ngừa hỏng hóc khi truy cập đồng thời.

DataStore tự động xử lý lỗi giải tuần tự hóa: nếu tệp bị hỏng, nó trả về giá trị mặc định và ghi đè lên tệp. Hành vi này có thể cấu hình thông qua corruptionHandler, có thể được đặt khi tạo DataStore.

Các vấn đề của SharedPreferences mà DataStore giải quyết

SharedPreferences gặp ba vấn đề cơ bản: đọc đồng bộ từ đĩa trên luồng chính, thiếu đảm bảo nguyên tử cho việc ghi đồng thời và không có khả năng theo dõi thay đổi một cách phản ứng. DataStore giải quyết cả ba: Flow để quan sát, khóa tệp cho tính nguyên tử và API không đồng bộ cho an toàn luồng.

DataStore hoạt động như thế nào trong Android?

DataStore lưu trữ dữ liệu trong các tệp trên bộ nhớ trong của thiết bị. Preferences DataStore sử dụng định dạng tệp tương tự SharedPreferences nhưng có thêm siêu dữ liệu để kiểm tra tính toàn vẹn. Proto DataStore sử dụng định dạng nhị phân Protocol Buffers, giúp giảm kích thước tệp và tăng tốc độ tuần tự hóa.

Khi đọc dữ liệu, DataStore tải toàn bộ tệp vào bộ nhớ một lần, sau đó người đăng ký nhận trạng thái hiện tại qua Flow. Các thay đổi được truyền tới tất cả người đăng ký đang hoạt động một cách tự động — không cần đăng ký trình lắng nghe thủ công như SharedPreferences.

Cách Preferences DataStore hoạt động

Preferences DataStore sử dụng cơ chế tuần tự hóa tích hợp dựa trên Map. Mỗi mục nhập là một cặp chuỗi và kiểu nguyên thủy (Int, Boolean, Float, Long, String, Set). Dữ liệu được lưu trữ trong tệp XML, tương tự SharedPreferences, nhưng với ghi nguyên tử thông qua khóa tệp.

Ví dụ tạo Preferences DataStore: extension preferencesDataStore trên Context tạo một singleton với tên tệp. Khi gọi nhiều lần, cùng một phiên bản được trả về — điều này loại bỏ trùng lặp tệp và nhầm lẫn giữa các phiên bản lưu trữ khác nhau.

Cách Proto DataStore hoạt động

Proto DataStore yêu cầu định nghĩa lược đồ dữ liệu thông qua tệp .proto và biên dịch với plugin protobuf. Lớp Java được tạo ra được sử dụng làm điểm vào duy nhất cho tất cả các trường — điều này loại bỏ lỗi gõ sai khóa, thường gặp với SharedPreferences.

Lược đồ Proto DataStore được định nghĩa một lần và hỗ trợ thêm trường mới mà không mất dữ liệu cũ. Nếu phiên bản mới của ứng dụng thêm một trường có giá trị mặc định, tệp cũ sẽ được giải tuần tự hóa chính xác — tương thích ngược được tích hợp trong giao thức.

Preferences DataStore và Proto DataStore: So sánh

Việc lựa chọn giữa Preferences DataStoreProto DataStore phụ thuộc vào độ phức tạp của dữ liệu và yêu cầu về kiểu. Cả hai tùy chọn đều không đồng bộ và giao dịch, nhưng khác nhau về an toàn kiểu và hiệu suất tuần tự hóa.

Đặc điểmPreferences DataStoreProto DataStore
KiểuYếu (khóa-giá trị)Mạnh (lớp được tạo)
Tuần tự hóaXML (tích hợp)Protocol Buffers (protobuf)
Kích thước tệpLớn (XML có thể đọc)Nhỏ (nhị phân)
Độ phức tạpThấp (không cần .proto)Trung bình (cần .proto)
Di chuyển lược đồKhông có lược đồTự động (proto)
Tương thíchSharedPreferences (qua di chuyển)Chỉ Proto DataStore

Khi nào chọn Preferences DataStore

Preferences DataStore phù hợp cho các cài đặt đơn giản: cờ tính năng, chuỗi mã thông báo ủy quyền, số lần khởi chạy ứng dụng. Nếu dữ liệu nhỏ (tới 10–15 khóa) và không yêu cầu lược đồ nghiêm ngặt, Preferences DataStore cung cấp ngưỡng vào tối thiểu mà không cần kết nối plugin protobuf.

Khi nào chọn Proto DataStore

Proto DataStore phù hợp khi cấu trúc dữ liệu phức tạp hoặc có thể thay đổi giữa các phiên bản ứng dụng. Ví dụ: cài đặt hồ sơ người dùng hoặc cấu hình thử nghiệm A/B với 20+ trường. Protobuf cung cấp kiểu mạnh và di chuyển tự động, loại bỏ lỗi thời gian chạy do không khớp khóa.

Cách di chuyển từ SharedPreferences sang DataStore

Google cung cấp cơ chế di chuyển tích hợp thông qua lớp SharedPreferencesMigration. Việc di chuyển được thực hiện một lần khi khởi chạy đầu tiên sau khi cập nhật ứng dụng: DataStore đọc dữ liệu từ SharedPreferences, ghi vào định dạng riêng và đánh dấu di chuyển hoàn tất.

Di chuyển hỗ trợ chuyển đổi tùy chỉnh: nếu khóa trong SharedPreferences không khớp với khóa DataStore mong muốn, bạn có thể chỉ định hàm chuyển đổi thông qua SharedPreferencesMigration. Điều này cho phép đổi tên khóa và thay đổi kiểu dữ liệu trong quá trình di chuyển.

Di chuyển từng bước

Đầu tiên, thêm DataStore vào build.gradle và tạo phiên bản DataStore với di chuyển: SharedPreferencesMigration chấp nhận tên tệp SharedPreferences và tập hợp các khóa cần chuyển. Thứ hai, xóa tất cả mã hoạt động qua SharedPreferences và thay thế bằng các lệnh gọi DataStore. Thứ ba, kiểm tra di chuyển: khi khởi chạy đầu tiên, dữ liệu sẽ xuất hiện trong DataStore và tệp SharedPreferences cũ sẽ không còn được sử dụng.

kotlin
val Context.dataStore by preferencesDataStore(
    name = "settings",
    produceMigrations = { context ->
        listOf(
            SharedPreferencesMigration(context, "old_prefs")
        )
    }
)

Ví dụ sử dụng DataStore trong mã

DataStore tích hợp dễ dàng vào dự án hiện có. Dưới đây là các ví dụ thực tế cho Preferences DataStore và Proto DataStore — cả hai đều minh họa việc đọc, ghi và quan sát phản ứng dữ liệu.

Preferences DataStore: Đọc và ghi cài đặt

Trong ví dụ này, Preferences DataStore lưu trữ ba cài đặt: chủ đề tối, tên người dùng và số lần khởi chạy. Đọc được thực hiện qua extension .data, trả về Flow. Ghi được thực hiện qua hàm tạm dừng .edit, đảm bảo tính nguyên tử của các thay đổi.

kotlin
val Context.settingsDataStore by preferencesDataStore(name = "settings")

val isDarkMode: Flow<Boolean> = settingsDataStore.data
    .map { preferences ->
        preferences[booleanPreferencesKey("dark_mode")] ?: false
    }

suspend fun toggleDarkMode() {
    settingsDataStore.edit { prefs ->
        val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
        prefs[booleanPreferencesKey("dark_mode")] = !current
    }
}

Proto DataStore: Lược đồ và cách sử dụng

Proto DataStore yêu cầu định nghĩa tệp .proto. Sau khi biên dịch, một lớp UserSettings được tạo ra để đọc và ghi. Di chuyển phiên bản lược đồ được mô tả trong cùng tệp .proto và được áp dụng tự động.

kotlin
// user_preferences.proto
syntax = "proto3";

message UserPreferences {
    string display_name = 1;
    int32 notification_count = 2;
    bool notifications_enabled = 3;
}

// Đọc từ DataStore
val userPreferencesFlow: Flow<UserPreferences> =
    protoDataStore.data

// Ghi giá trị mới
suspend fun updateDisplayName(name: String) {
    protoDataStore.updateData { prefs ->
        prefs.toBuilder()
            .setDisplayName(name)
            .build()
    }
}

Quan sát phản ứng các thay đổi

DataStore tích hợp với kiến trúc MVVM thông qua ViewModel. Flow từ DataStore được thu thập qua .stateIn và sử dụng trong UI. Mỗi khi dữ liệu thay đổi, UI tự động cập nhật — không cần cập nhật thủ công hay LiveData.

kotlin
class SettingsViewModel(
    private val dataStore: DataStore<Preferences>
) : ViewModel() {

    val uiState: StateFlow<SettingsUiState> =
        dataStore.data
            .map { prefs ->
                SettingsUiState(
                    isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
                    counter = prefs[intPreferencesKey("launch_count")] ?: 0
                )
            }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = SettingsUiState()
            )
}

Các Câu Hỏi Thường Gặp

DataStore tốt hơn SharedPreferences như thế nào?

DataStore hoạt động không đồng bộ (không chặn luồng UI), hỗ trợ truy cập đồng thời qua giao dịch và cho phép đăng ký phản ứng các thay đổi qua Flow. SharedPreferences là API đồng bộ có nguy cơ ANR với khối lượng dữ liệu lớn và không có hỗ trợ phản ứng tích hợp.

Có thể sử dụng DataStore với Java không?

DataStore được viết bằng Kotlin và yêu cầu Kotlin Coroutines. Sử dụng từ Java có thể nhưng bất tiện: bạn sẽ phải tạo wrapper với CompletableFuture hoặc quản lý coroutine thủ công. Đối với các dự án Java, Google khuyến nghị giữ lại SharedPreferences hoặc thêm Kotlin vào mô-đun.

DataStore có phù hợp để lưu trữ khối lượng lớn dữ liệu không?

DataStore tải toàn bộ tệp vào bộ nhớ khi đọc, vì vậy nó không phù hợp để lưu trữ danh sách hoặc đối tượng lớn. Đối với các tình huống như vậy, hãy sử dụng Room hoặc SQLite. DataStore được tối ưu hóa cho cài đặt và dữ liệu có cấu trúc nhỏ — lên tới hàng trăm kilobyte.

Làm thế nào để xử lý lỗi tệp DataStore bị hỏng?

Khi tạo DataStore, bạn có thể truyền corruptionHandler — một hàm được gọi khi tệp bị hỏng. Theo mặc định, DataStore ném ngoại lệ CorruptionException. Trong corruptionHandler, bạn có thể trả về dữ liệu trống, sau đó DataStore sẽ ghi đè tệp với trạng thái chính xác.

Proto DataStore có yêu cầu tệp .proto không?

Có, Proto DataStore yêu cầu định nghĩa lược đồ trong tệp .proto và kết nối protobuf-gradle-plugin. Nếu dự án nhỏ và dữ liệu đơn giản, sẽ dễ dàng hơn khi sử dụng Preferences DataStore — nó không yêu cầu cấu hình xây dựng bổ sung.

Tổng kết

  • DataStore — sự thay thế hiện đại cho SharedPreferences, hoạt động với Kotlin Coroutines và Flow
  • Preferences DataStore — khóa-giá trị đơn giản không có lược đồ, phù hợp cho cài đặt
  • Proto DataStore — lưu trữ có kiểu với lược đồ protobuf và tự động di chuyển
  • Di chuyển từ SharedPreferences được tích hợp trong DataStore qua SharedPreferencesMigration
  • An toàn luồng — tất cả thao tác đều không đồng bộ, loại bỏ chặn UI
  • Phản ứng — Flow thông báo cho người đăng ký mỗi khi dữ liệu thay đổi
  • Khuyến nghị — sử dụng DataStore trong tất cả các dự án Android mới, di chuyển các dự án hiện có khi làm việc với cài đặt

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm