חסימת החנות

משתמשים רבים עדיין מנהלים את פרטי הכניסה שלהם בעצמם כשהם מגדירים מכשיר חדש עם Android. התהליך הידני הזה יכול להיות מאתגר, ולעיתים קרובות הוא פוגע בחוויית המשתמש. ‫Block Store API, ספרייה שמופעלת על ידי Google Play Services, נועד לפתור את הבעיה הזו. הוא מאפשר לאפליקציות לשמור את פרטי הכניסה של המשתמשים בלי המורכבות או סיכוני האבטחה שקשורים לשמירת סיסמאות של משתמשים.

‫Block Store API מאפשר לאפליקציה לאחסן נתונים שאפשר לאחזר בהמשך כדי לאמת מחדש משתמשים במכשיר חדש. כך המשתמשים נהנים מחוויה חלקה יותר, כי הם לא צריכים לראות מסך כניסה כשהם מפעילים את האפליקציה בפעם הראשונה במכשיר החדש.

היתרונות של השימוש ב-Block Store כוללים:

  • פתרון לאחסון מוצפן של פרטי כניסה למפתחים. פרטי הכניסה מוצפנים מקצה לקצה כשאפשר.
  • שמירת אסימונים במקום שמות משתמשים וסיסמאות.
  • הסרת חיכוכים מתהליכי הכניסה.
  • המשתמשים לא צריכים לנהל סיסמאות מורכבות.
  • ‫Google מאמתת את זהות המשתמש.

לפני שמתחילים

כדי להכין את האפליקציה, מבצעים את השלבים שמפורטים בקטעים הבאים.

הגדרת האפליקציה

בקובץ build.gradle ברמת הפרויקט, צריך לכלול את מאגר ה-Maven של Google בקטעים buildscript ו-allprojects:

buildscript {
  repositories {
    google()
    mavenCentral()
  }
}

allprojects {
  repositories {
    google()
    mavenCentral()
  }
}

מוסיפים את התלות ב-Google Play Services בשביל Block Store API אל קובץ ה-build של Gradle של המודול, שבדרך כלל נמצא ב-app/build.gradle:

dependencies {
  implementation 'com.google.android.gms:play-services-auth-blockstore:16.4.0'
}

איך זה עובד

‫Block Store מאפשר למפתחים לשמור ולשחזר עד 16 מערכי בייטים. כך אפשר לשמור מידע חשוב לגבי סשן המשתמש הנוכחי, וגם לשמור את המידע הזה בכל דרך שרוצים. אפשר להצפין את הנתונים האלה מקצה לקצה, והתשתית שתומכת ב-Block Store מבוססת על התשתית של הגיבוי והשחזור.

המדריך הזה עוסק בתרחיש לדוגמה של שמירת טוקן של משתמש ב-Block Store. השלבים הבאים מתארים איך אפליקציה שמשתמשת ב-Block Store פועלת:

  1. במהלך תהליך האימות של האפליקציה, או בכל שלב לאחר מכן, אתם יכולים לאחסן את אסימון האימות של המשתמש ב-Block Store כדי לאחזר אותו מאוחר יותר.
  2. האסימון יישמר באופן מקומי, ואפשר גם לגבות אותו בענן, עם הצפנה מקצה לקצה כשהדבר אפשרי.
  3. הנתונים מועברים כשהמשתמש מתחיל תהליך שחזור במכשיר חדש.
  4. אם המשתמש משחזר את האפליקציה במהלך תהליך השחזור, האפליקציה יכולה לאחזר את האסימון השמור מ-Block Store במכשיר החדש.

שמירת הטוקן

כשמשתמש נכנס לאפליקציה, אתם יכולים לשמור ב-Block Store את אסימון האימות שאתם יוצרים בשביל המשתמש הזה. אפשר לאחסן את הטוקן הזה באמצעות ערך ייחודי של זוג מפתחות, בגודל של עד 4KB לכל רשומה. כדי לאחסן את האסימון, מפעילים את setBytes() ואת setKey() במופע של StoreBytesData.Builder כדי לאחסן את פרטי הכניסה של המשתמש במכשיר המקור. אחרי ששומרים את הטוקן באמצעות Block Store, הטוקן מוצפן ונשמר באופן מקומי במכשיר.

בדוגמה הבאה אפשר לראות איך שומרים את אסימון האימות במכשיר המקומי:

Java

BlockstoreClient client = Blockstore.getClient(this);
byte[] bytes1 = new byte[] { 1, 2, 3, 4 };  // Store one data block.
String key1 = "com.example.app.key1";
StoreBytesData storeRequest1 = StoreBytesData.Builder()
        .setBytes(bytes1)
        // Call this method to set the key-value pair the data should be associated with.
        .setKeys(Arrays.asList(key1))
        .build();
client.storeBytes(storeRequest1)
  .addOnSuccessListener(result -> Log.d(TAG, "stored " + result + " bytes"))
  .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));

Kotlin

val client = Blockstore.getClient(this)

val bytes1 = byteArrayOf(1, 2, 3, 4) // Store one data block.
val key1 = "com.example.app.key1"
val storeRequest1 = StoreBytesData.Builder()
  .setBytes(bytes1) // Call this method to set the key value with which the data should be associated with.
  .setKeys(Arrays.asList(key1))
  .build()
client.storeBytes(storeRequest1)
  .addOnSuccessListener { result: Int ->
    Log.d(TAG,
          "Stored $result bytes")
  }
  .addOnFailureListener { e ->
    Log.e(TAG, "Failed to store bytes", e)
  }

שימוש בטוקן שמוגדר כברירת מחדל

נתונים שנשמרים באמצעות StoreBytes בלי מפתח משתמשים במפתח ברירת המחדל BlockstoreClient.DEFAULT_BYTES_DATA_KEY.

Java

BlockstoreClient client = Blockstore.getClient(this);
// The default key BlockstoreClient.DEFAULT_BYTES_DATA_KEY.
byte[] bytes = new byte[] { 9, 10 };
StoreBytesData storeRequest = StoreBytesData.Builder()
        .setBytes(bytes)
        .build();
client.storeBytes(storeRequest)
  .addOnSuccessListener(result -> Log.d(TAG, "stored " + result + " bytes"))
  .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));

Kotlin

val client = Blockstore.getClient(this);
// the default key BlockstoreClient.DEFAULT_BYTES_DATA_KEY.
val bytes = byteArrayOf(1, 2, 3, 4)
val storeRequest = StoreBytesData.Builder()
  .setBytes(bytes)
  .build();
client.storeBytes(storeRequest)
  .addOnSuccessListener { result: Int ->
    Log.d(TAG,
          "stored $result bytes")
  }
  .addOnFailureListener { e ->
    Log.e(TAG, "Failed to store bytes", e)
  }

אחזור הטוקן

בהמשך, כשמשתמש עובר את תהליך השחזור במכשיר חדש, Google Play Services מאמת קודם את המשתמש ואז מאחזר את הנתונים של Block Store. המשתמש כבר הסכים לשחזר את נתוני האפליקציה כחלק מתהליך השחזור, ולכן לא נדרשות הסכמות נוספות. כשהמשתמש פותח את האפליקציה, אתם יכולים לבקש את הטוקן מ-Block Store באמצעות קריאה ל-retrieveBytes(). אחרי שהאסימון מאוחזר, אפשר להשתמש בו כדי להשאיר את המשתמש מחובר לחשבון במכשיר החדש.

בדוגמה הבאה אפשר לראות איך לאחזר כמה אסימונים על סמך מפתחות ספציפיים.

Java

BlockstoreClient client = Blockstore.getClient(this);

// Retrieve data associated with certain keys.
String key1 = "com.example.app.key1";
String key2 = "com.example.app.key2";
String key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY; // Used to retrieve data stored without a key

List<String> requestedKeys = Arrays.asList(key1, key2, key3); // Add keys to array
RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder()
    .setKeys(requestedKeys)
    .build();

client.retrieveBytes(retrieveRequest)
    .addOnSuccessListener(
        result -> {
          Map<String, BlockstoreData> blockstoreDataMap = result.getBlockstoreDataMap();
          for (Map.Entry<String, BlockstoreData> entry : blockstoreDataMap.entrySet()) {
            Log.d(TAG, String.format(
                "Retrieved bytes %s associated with key %s.",
                new String(entry.getValue().getBytes()), entry.getKey()));
          }
        })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));

Kotlin

val client = Blockstore.getClient(this)

// Retrieve data associated with certain keys.
val key1 = "com.example.app.key1"
val key2 = "com.example.app.key2"
val key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY // Used to retrieve data stored without a key

val requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array

val retrieveRequest = RetrieveBytesRequest.Builder()
  .setKeys(requestedKeys)
  .build()

client.retrieveBytes(retrieveRequest)
  .addOnSuccessListener { result: RetrieveBytesResponse ->
    val blockstoreDataMap =
      result.blockstoreDataMap
    for ((key, value) in blockstoreDataMap) {
      Log.d(ContentValues.TAG, String.format(
        "Retrieved bytes %s associated with key %s.",
        String(value.bytes), key))
    }
  }
  .addOnFailureListener { e: Exception? ->
    Log.e(ContentValues.TAG,
          "Failed to store bytes",
          e)
  }

מתבצע אחזור של כל הטוקנים.

בדוגמה הבאה אפשר לראות איך לאחזר את כל האסימונים שנשמרו ב-BlockStore.

Java

BlockstoreClient client = Blockstore.getClient(this)

// Retrieve all data.
RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder()
    .setRetrieveAll(true)
    .build();

client.retrieveBytes(retrieveRequest)
    .addOnSuccessListener(
        result -> {
          Map<String, BlockstoreData> blockstoreDataMap = result.getBlockstoreDataMap();
          for (Map.Entry<String, BlockstoreData> entry : blockstoreDataMap.entrySet()) {
            Log.d(TAG, String.format(
                "Retrieved bytes %s associated with key %s.",
                new String(entry.getValue().getBytes()), entry.getKey()));
          }
        })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));

Kotlin

val client = Blockstore.getClient(this)

val retrieveRequest = RetrieveBytesRequest.Builder()
    .setRetrieveAll(true)
    .build()

  client.retrieveBytes(retrieveRequest)
    .addOnSuccessListener { result: RetrieveBytesResponse ->
      val blockstoreDataMap =
        result.blockstoreDataMap
      for ((key, value) in blockstoreDataMap) {
        Log.d(ContentValues.TAG, String.format(
          "Retrieved bytes %s associated with key %s.",
          String(value.bytes), key))
      }
    }
    .addOnFailureListener { e: Exception? ->
      Log.e(ContentValues.TAG,
            "Failed to store bytes",
            e)
    }

בדוגמה הבאה אפשר לראות איך לאחזר את מפתח ברירת המחדל.

Java

BlockStoreClient client = Blockstore.getClient(this);
RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder()
    .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY))
    .build();
client.retrieveBytes(retrieveRequest);

Kotlin

val client = Blockstore.getClient(this)

val retrieveRequest = RetrieveBytesRequest.Builder()
  .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY))
  .build()
client.retrieveBytes(retrieveRequest)

מחיקת טוקנים

יכול להיות שתצטרכו למחוק טוקנים מ-BlockStore מהסיבות הבאות:

  • המשתמש עובר את תהליך היציאה מהחשבון.
  • הטוקן בוטל או שהוא לא תקין.

בדומה לאחזור טוקנים, אתם יכולים לציין אילו טוקנים צריך למחוק על ידי הגדרת מערך של מפתחות שצריך למחוק.

בדוגמה הבאה אפשר לראות איך למחוק מפתחות מסוימים:

Java

BlockstoreClient client = Blockstore.getClient(this);

// Delete data associated with certain keys.
String key1 = "com.example.app.key1";
String key2 = "com.example.app.key2";
String key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY; // Used to delete data stored without key

List<String> requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array
DeleteBytesRequest deleteRequest = new DeleteBytesRequest.Builder()
        .setKeys(requestedKeys)
        .build();
client.deleteBytes(deleteRequest)

Kotlin

val client = Blockstore.getClient(this)

// Retrieve data associated with certain keys.
val key1 = "com.example.app.key1"
val key2 = "com.example.app.key2"
val key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY // Used to retrieve data stored without a key

val requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array

val retrieveRequest = DeleteBytesRequest.Builder()
      .setKeys(requestedKeys)
      .build()

client.deleteBytes(retrieveRequest)

מחיקת כל הטוקנים

בדוגמה הבאה מוסבר איך למחוק את כל האסימונים שנשמרו ב-Block Store:

Java

// Delete all data.
DeleteBytesRequest deleteAllRequest = new DeleteBytesRequest.Builder()
        .setDeleteAll(true)
        .build();
client.deleteBytes(deleteAllRequest)
  .addOnSuccessListener(result -> Log.d(TAG, "Any data found and deleted? " + result));

Kotlin

// Delete all data.
val deleteAllRequest = DeleteBytesRequest.Builder()
        .setDeleteAll(true)
        .build()
client.deleteBytes(deleteAllRequest)
  .addOnSuccessListener { result -> Log.d(TAG, "Any data found and deleted? $result") }

הצפנה מקצה לקצה

כדי שההצפנה מקצה לקצה תהיה זמינה, המכשיר צריך לפעול עם Android מגרסה 9 ואילך, והמשתמש צריך להגדיר שיטה לפתיחת הנעילה (קוד אימות, קו פתיחת נעילה או סיסמה) במכשיר. כדי לבדוק אם ההצפנה תהיה זמינה במכשיר, אפשר להפעיל את isEndToEndEncryptionAvailable().

בדוגמה הבאה אפשר לראות איך בודקים אם ההצפנה תהיה זמינה במהלך הגיבוי בענן:

client.isEndToEndEncryptionAvailable()
        .addOnSuccessListener { result ->
          Log.d(TAG, "Will Block Store cloud backup be end-to-end encrypted? $result")
        }

הפעלת הגיבוי בענן

כדי להפעיל גיבוי בענן, מוסיפים את השיטה setShouldBackupToCloud() לאובייקט StoreBytesData. אם המדיניות setShouldBackupToCloud() מוגדרת כ-True, ‏Block Store יגבה מדי פעם בענן את הבייטים שמאוחסנים.

בדוגמה הבאה אפשר לראות איך להפעיל גיבוי בענן רק כשהגיבוי בענן מוצפן מקצה לקצה:

val client = Blockstore.getClient(this)
val storeBytesDataBuilder = StoreBytesData.Builder()
        .setBytes(/* BYTE_ARRAY */)

client.isEndToEndEncryptionAvailable()
        .addOnSuccessListener { isE2EEAvailable ->
          if (isE2EEAvailable) {
            storeBytesDataBuilder.setShouldBackupToCloud(true)
            Log.d(TAG, "E2EE is available, enable backing up bytes to the cloud.")

            client.storeBytes(storeBytesDataBuilder.build())
                .addOnSuccessListener { result ->
                  Log.d(TAG, "stored: ${result.getBytesStored()}")
                }.addOnFailureListener { e ->
                  Log.e(TAG, "Failed to store bytes", e)
                }
          } else {
            Log.d(TAG, "E2EE is not available, only store bytes for D2D restore.")
          }
        }

איך בודקים את זה

כדי לבדוק את תהליכי השחזור, אתם יכולים להשתמש בשיטות הבאות במהלך הפיתוח.

הסרה והתקנה מחדש באותו מכשיר

אם המשתמש מפעיל את שירותי הגיבוי (אפשר לבדוק את זה בהגדרות > Google > גיבוי), הנתונים של Block Store נשמרים גם אם מסירים את האפליקציה ומתקינים אותה מחדש.

כדי לבדוק את זה, אתם יכולים לפעול לפי השלבים הבאים:

  1. משלבים את Block Store API באפליקציית הבדיקה.
  2. משתמשים באפליקציית הבדיקה כדי להפעיל את Block Store API ולאחסן את הנתונים.
  3. מסירים את אפליקציית הבדיקה ומתקינים מחדש את האפליקציה באותו מכשיר.
  4. משתמשים באפליקציית הבדיקה כדי להפעיל את Block Store API ולאחזר את הנתונים.
  5. מוודאים שהבייטים שאוחזרו זהים לאלה שאוחסנו לפני הסרת ההתקנה.

ממכשיר למכשיר

ברוב המקרים, זה ידרוש איפוס להגדרות המקוריות של מכשיר היעד. אחרי זה תוכלו להיכנס לתהליך השחזור האלחוטי של Android או לשחזור באמצעות כבל של Google (במכשירים נתמכים).

שחזור דרך הענן

  1. משלבים את Block Store API באפליקציית הבדיקה. צריך לשלוח את אפליקציית הבדיקה לחנות Play.
  2. במכשיר המקור, משתמשים באפליקציית הבדיקה כדי להפעיל את Block Store API לאחסון הנתונים, כאשר shouldBackUpToCloud מוגדר ל-true.
  3. במכשירים עם Android בגרסה 8.0 (רמת API‏ 26) ואילך, אפשר להפעיל באופן ידני גיבוי בענן של Block Store: עוברים אל הגדרות > Google > גיבוי ולוחצים על הכפתור "גיבוי עכשיו".
    1. כדי לוודא שהגיבוי בענן של Block Store הצליח, אתם יכולים:
      1. אחרי שהגיבוי מסתיים, מחפשים שורות ביומן עם התג [CloudSyncBackupTaskService].
      2. אמורים להופיע קווים כאלה: ..., [CloudSyncBackupTaskService] sync result: SUCCESS, ..., uploaded size: [size] bytes ...
    2. אחרי גיבוי בענן של Block Store, יש תקופת "צינון" של 5 דקות. במהלך 5 הדקות האלה, לחיצה על הכפתור "גיבוי עכשיו" לא תפעיל גיבוי נוסף בענן של Block Store.
  4. מאפסים את מכשיר היעד להגדרות המקוריות ומשחזרים את הנתונים מהענן. בוחרים באפשרות לשחזר את אפליקציית הבדיקה במהלך תהליך השחזור. למידע נוסף על תהליכי שחזור בענן, ראו תהליכי שחזור נתמכים בענן.
  5. במכשיר היעד, משתמשים באפליקציית הבדיקה כדי להפעיל את Block Store API ולאחזר את הנתונים.
  6. מוודאים שהבייטים שאוחזרו זהים לאלה שאוחסנו במכשיר המקור.

דרישות לגבי מכשירים

כאן מפורטות הדרישות למכשירים שמשתתפים בפעולות של Block Store.

הצפנה מקצה לקצה

  • הצפנה מקצה לקצה נתמכת במכשירים עם Android מגרסה 9 (API 29) ואילך.
  • כדי להפעיל הצפנה מקצה לקצה ולהצפין את נתוני המשתמש בצורה נכונה, צריך להגדיר במכשיר נעילת מסך באמצעות קוד אימות, קו פתיחת נעילה או סיסמה.

תהליך שחזור ממכשיר למכשיר

כדי לשחזר ממכשיר למכשיר, צריך מכשיר מקור ומכשיר יעד. אלה יהיו שני המכשירים שביניהם יועברו הנתונים.

כדי לגבות, במכשירי המקור צריכה לפעול מערכת Android מגרסה 6 (API 23) ואילך.

מטרגטים מכשירים עם Android 9 (API 29) ואילך כדי לאפשר שחזור.

מידע נוסף על תהליך השחזור ממכשיר למכשיר זמין במאמר תהליכי שחזור נתמכים ממכשיר למכשיר.

תהליך הגיבוי והשחזור בענן

כדי לגבות ולשחזר בענן, צריך מכשיר מקור ומכשיר יעד.

כדי לגבות, במכשירי המקור צריכה לפעול מערכת Android מגרסה 6 (API 23) ואילך.

מכשירי היעד נתמכים בהתאם לספקים שלהם. במכשירי Pixel אפשר להשתמש בתכונה הזו החל מ-Android 9 (API 29), ובכל שאר המכשירים צריכה לפעול מערכת Android 12 (API 31) ואילך.