DataStore   Một phần của Android Jetpack.

Jetpack DataStore là một giải pháp lưu trữ dữ liệu cho phép bạn lưu trữ các cặp khoá-giá trị hoặc đối tượng đã nhập có vùng đệm giao thức. DataStore sử dụng coroutine Kotlin và Dòng (Flow) để lưu trữ dữ liệu một cách không đồng bộ, nhất quán và có thể chia sẻ.

Nếu bạn hiện đang sử dụng SharedPreferences để lưu trữ dữ liệu, hãy cân nhắc việc chuyển sang DataStore.

DataStore Preference và DataStore Proto

Có hai phương thức triển khai DataStore: DataStore Preference và DataStore Proto.

  • Preference DataStore lưu trữ và truy cập vào dữ liệu bằng các khoá. Phương thức triển khai này không yêu cầu có giản đồ được xác định trước và không đảm bảo an toàn về loại.
  • Proto DataStore lưu trữ dữ liệu dưới dạng các phiên bản của một loại dữ liệu tuỳ chỉnh. Phương thức triển khai này yêu cầu bạn xác định một giản đồ bằng cách sử dụng bộ đệm giao thức, nhưng có đảm bảo an toàn về loại.

Thiết lập

Để sử dụng Jetpack DataStore trong ứng dụng của bạn, hãy thêm đoạn mã sau vào tệp Gradle tuỳ thuộc vào phương thức triển khai mà bạn muốn dùng:

Preferences DataStore

Groovy

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation "androidx.datastore:datastore-preferences:1.0.0"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0"
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-preferences-core:1.0.0"
    }
    

Kotlin

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation("androidx.datastore:datastore-preferences:1.0.0")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-preferences-core:1.0.0")
    }
    

Proto DataStore

Groovy

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation "androidx.datastore:datastore:1.0.0"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.0.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-rxjava3:1.0.0"
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-core:1.0.0"
    }
    

Kotlin

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation("androidx.datastore:datastore:1.0.0")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.0.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.0.0")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-core:1.0.0")
    }
    

Lưu trữ các cặp khoá-giá trị với DataStore Preference

Phương thức triển khai Bộ tuỳ chọn DataStore sử dụng các lớp DataStorePreferences để lưu trữ các cặp khoá-giá trị đơn giản vào ổ đĩa.

Tạo một DataStore Preference

Hãy sử dụng tính năng uỷ quyền thuộc tính do preferencesDataStore tạo để tạo một bản sao của Datastore<Preferences>. Gọi tệp kotlin của bạn một lần ở cấp cao nhất và truy cập tệp thông qua thuộc tính này trong suốt phần còn lại của ứng dụng. Thao tác này giúp bạn dễ dàng giữ DataStore ở dạng một singleton. Ngoài ra, hãy sử dụng RxPreferenceDataStoreBuilder nếu bạn đang dùng RxJava. Thông số bắt buộc name là tên của DataStore Preference.

Kotlin

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

Java

RxDataStore<Preferences> dataStore =
  new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();

Đọc trong một DataStore Preference

Do DataStore Preference không sử dụng giản đồ được xác định trước, bạn phải sử dụng hàm loại khoá tương ứng để xác định khoá cho mỗi giá trị mà bạn cần lưu trữ trong phiên bản DataStore<Preferences>. Ví dụ: để xác định khoá cho một giá trị số nguyên, hãy sử dụng intPreferencesKey(). Sau đó, dùng thuộc tính DataStore.data để hiển thị giá trị được lưu trữ phù hợp bằng Flow.

Kotlin

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

Java

Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));

Ghi vào một DataStore Preference

DataStore Preference cung cấp một hàm edit() có thể chia sẻ cập nhật dữ liệu trong một DataStore. Thông số transform của hàm chấp nhận một khối mã để bạn có thể cập nhật các giá trị nếu cần. Tất cả mã trong khối biến đổi được coi là một lượt chia sẻ.

Kotlin

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

Java

Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
  MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
  Integer currentInt = prefsIn.get(INTEGER_KEY);
  mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
  return Single.just(mutablePreferences);
});
// The update is completed once updateResult is completed.

Lưu trữ các đối tượng đã nhập bằng DataStore Proto

Phương thức triển khai DataStore Proto sử dụng DataStore và các bộ đệm giao thức để lưu trữ các đối tượng đã nhập vào ổ đĩa.

Xác định một giản đồ

DataStore Proto yêu cầu một giản đồ được xác định trước trong tệp proto trong thư mục app/src/main/proto/. Giản đồ này xác định loại cho các đối tượng mà bạn lưu trữ trong DataStore Proto. Để tìm hiểu thêm về cách xác định một giản đồ proto, hãy xem hướng dẫn về ngôn ngữ protobuf.

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

Tạo một DataStore Proto

Bạn cần thực hiện hai bước để tạo một DataStore Proto cho việc lưu trữ các đối tượng đã nhập:

  1. Xác định một lớp triển khai Serializer<T>, trong đó T là loại được xác định trong tệp proto. Lớp chuyển đổi tuần tự này cho DataStore biết cách đọc và ghi loại dữ liệu của bạn. Hãy đảm bảo bạn cung cấp giá trị mặc định để trình chuyển đổi tuần tự sử dụng nếu chưa có tệp nào được tạo.
  2. Sử dụng tính năng uỷ quyền thuộc tính do dataStore tạo để tạo một bản sao của DataStore<T>, trong đó T là loại được xác định trong tệp proto. Hãy gọi tệp kotlin của bạn một lần ở cấp cao nhất và truy cập tệp thông qua tính năng uỷ quyền thuộc tính này trong suốt thời gian còn lại của ứng dụng. Tham số filename cho DataStore biết nên sử dụng tệp nào để lưu trữ dữ liệu, và tham số serializer cho DataStore biết tên của lớp chuyển đổi tuần tự được xác định trong bước 1.

Kotlin

object SettingsSerializer : Serializer<Settings> {
  override val defaultValue: Settings = Settings.getDefaultInstance()

  override suspend fun readFrom(input: InputStream): Settings {
    try {
      return Settings.parseFrom(input)
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException("Cannot read proto.", exception)
    }
  }

  override suspend fun writeTo(
    t: Settings,
    output: OutputStream) = t.writeTo(output)
}

val Context.settingsDataStore: DataStore<Settings> by dataStore(
  fileName = "settings.pb",
  serializer = SettingsSerializer
)

Java

private static class SettingsSerializer implements Serializer<Settings> {
  @Override
  public Settings getDefaultValue() {
    Settings.getDefaultInstance();
  }

  @Override
  public Settings readFrom(@NotNull InputStream input) {
    try {
      return Settings.parseFrom(input);
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException(“Cannot read proto.”, exception);
    }
  }

  @Override
  public void writeTo(Settings t, @NotNull OutputStream output) {
    t.writeTo(output);
  }
}

RxDataStore<Byte> dataStore =
    new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();

Đọc trong một DataStore Proto

Hãy sử dụng DataStore.data để hiển thị Flow của thuộc tính phù hợp trong đối tượng được lưu trữ của bạn.

Kotlin

val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
  .map { settings ->
    // The exampleCounter property is generated from the proto schema.
    settings.exampleCounter
  }

Java

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(settings -> settings.getExampleCounter());

Ghi vào một DataStore Proto

DataStore Proto cung cấp một hàm updateData() có thể chia sẻ cập nhật đối tượng được lưu trữ. updateData() cho bạn biết trạng thái hiện tại của dữ liệu làm phiên bản cho loại dữ liệu và cập nhật dữ liệu có thể chia sẻ trong thao tác đọc-ghi-sửa đổi nguyên tử.

Kotlin

suspend fun incrementCounter() {
  context.settingsDataStore.updateData { currentSettings ->
    currentSettings.toBuilder()
      .setExampleCounter(currentSettings.exampleCounter + 1)
      .build()
    }
}

Java

Single<Settings> updateResult =
  dataStore.updateDataAsync(currentSettings ->
    Single.just(
      currentSettings.toBuilder()
        .setExampleCounter(currentSettings.getExampleCounter() + 1)
        .build()));

Sử dụng DataStore trong mã đồng bộ

Một trong những lợi ích chính của DataStore là API không đồng bộ, nhưng không phải lúc nào bạn cũng có thể thay đổi mã xung quanh thành không đồng bộ. Điều này có thể xảy ra trong trường hợp bạn đang làm việc với một cơ sở mã hiện có sử dụng ổ đĩa I/O đồng bộ hoặc nếu bạn có một phần phụ thuộc không cung cấp API không đồng bộ.

Coroutine của Kotlin cung cấp trình tạo coroutine runBlocking() để giúp thu hẹp khoảng cách giữa mã đồng bộ và không đồng bộ. Bạn có thể sử dụng runBlocking() để đọc dữ liệu trong DataStore một cách đồng bộ. RxJava cung cấp các phương thức chặn trên Flowable. Mã sau đây chặn luồng gọi cho đến khi DataStore trả về dữ liệu:

Kotlin

val exampleData = runBlocking { context.dataStore.data.first() }

Java

Settings settings = dataStore.data().blockingFirst();

Việc thực hiện các thao tác I/O đồng bộ trên luồng giao diện người dùng có thể khiến ANR hoặc giao diện người dùng bị giật. Bạn có thể giảm thiểu những vấn đề này bằng cách tải trước không đồng bộ dữ liệu trong DataStore:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

Java

dataStore.data().first().subscribe();

Bằng cách này, DataStore đọc không đồng bộ dữ liệu và lưu dữ liệu vào bộ nhớ đệm trong bộ nhớ. Các lượt đọc đồng bộ sau này sử dụng runBlocking() có thể nhanh hơn hoặc có thể tránh được toàn bộ thao tác của ổ đĩa I/O nếu lần đọc ban đầu hoàn tất.

Gửi ý kiến phản hồi

Hãy chia sẻ phản hồi và ý kiến của bạn với chúng tôi thông qua các tài nguyên sau:

Công cụ theo dõi lỗi
Báo cáo sự cố để chúng tôi có thể sửa lỗi.

Tài nguyên khác

Để tìm hiểu thêm về Jetpack DataStore, hãy xem thêm các tài nguyên sau:

Blog

Lớp học lập trình