גישה לקבצים ספציפיים לאפליקציה

במקרים רבים האפליקציה יוצרת קבצים שאפליקציות אחרות לא צריכות לגשת אליהם, או שהן לא צריכות לגשת אליהם. המערכת מספקת את המיקומים הבאים לאחסון קובצי אפליקציה ספציפיים:

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

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

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

בקטעים הבאים מוסבר איך לאחסן קבצים בספריות ספציפיות לאפליקציה ולגשת אליהם.

גישה מהאחסון הפנימי

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

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

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

גישה לקבצים קבועים

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

לגשת לקבצים ולאחסן אותם

אפשר להשתמש ב-API של File כדי לגשת לקבצים ולאחסן אותם.

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

קטע הקוד הבא מדגים איך משתמשים ב-API File:

Kotlin

val file = File(context.filesDir, filename)

Java

File file = new File(context.getFilesDir(), filename);

אחסון קובץ באמצעות סטרימינג

במקום להשתמש ב-API File, אפשר לשלוח קריאה ל-openFileOutput() כדי לקבל FileOutputStream שכותב לקובץ בתוך הספרייה filesDir.

קטע הקוד הבא מראה איך לכתוב טקסט בקובץ:

Kotlin

val filename = "myfile"
val fileContents = "Hello world!"
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
        it.write(fileContents.toByteArray())
}

Java

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

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

גישה לקובץ באמצעות סטרימינג

כדי לקרוא קובץ בסטרימינג, משתמשים ב-openFileInput():

Kotlin

context.openFileInput(filename).bufferedReader().useLines { lines ->
    lines.fold("") { some, text ->
        "$some\n$text"
    }
}

Java

FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

צפייה ברשימת הקבצים

כדי לקבל מערך שמכיל את שמות כל הקבצים בספרייה filesDir, קוראים ל-fileList(), כפי שמוצג בקטע הקוד הבא:

Kotlin

var files: Array<String> = context.fileList()

Java

Array<String> files = context.fileList();

יצירת ספריות בתים

אפשר גם ליצור ספריות בתים או לפתוח ספרייה פנימית על ידי קריאה ל-getDir() בקוד שמבוסס על Kotlin, או על ידי העברת ספריית הבסיס ושם ספרייה חדש ל-constructor‏ File בקוד שמבוסס על Java:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

File directory = context.getFilesDir();
File file = new File(directory, filename);

יצירת קובצי מטמון

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

כדי ליצור קובץ ששמור במטמון, צריך להפעיל את הפונקציה File.createTempFile():

Kotlin

File.createTempFile(filename, null, context.cacheDir)

Java

File.createTempFile(filename, null, context.getCacheDir());

האפליקציה ניגשת לקובץ בתיקייה הזו באמצעות המאפיין cacheDir של אובייקט ההקשר ו-API‏ File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

File cacheFile = new File(context.getCacheDir(), filename);

הסרת קבצים מהמטמון

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

כדי להסיר קובץ מהספרייה של המטמון באחסון הפנימי, משתמשים באחת מהשיטות הבאות:

  • ה-method delete() באובייקט File שמייצגת את הקובץ:

    Kotlin

    cacheFile.delete()
    

    Java

    cacheFile.delete();
    
  • השיטה deleteFile() של ההקשר של האפליקציה, עם העברת שם הקובץ:

    Kotlin

    context.deleteFile(cacheFileName)
    

    Java

    context.deleteFile(cacheFileName);
    

גישה מהתקן אחסון חיצוני

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

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

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

איך בודקים שיש נפח אחסון פנוי

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

אפשר לשלוח שאילתה לגבי מצב האחסון באמצעות קריאה ל-Environment.getExternalStorageState(). אם המצב המוחזר הוא MEDIA_MOUNTED, תוכלו לקרוא ולכתוב קבצים ספציפיים לאפליקציה באחסון החיצוני. אם מדובר ב-MEDIA_MOUNTED_READ_ONLY, אפשר רק לקרוא את הקבצים האלו.

לדוגמה, תוכלו להיעזר בשיטות הבאות כדי לבדוק את זמינות האחסון:

Kotlin

// Checks if a volume containing external storage is available
// for read and write.
fun isExternalStorageWritable(): Boolean {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

// Checks if a volume containing external storage is available to at least read.
fun isExternalStorageReadable(): Boolean {
     return Environment.getExternalStorageState() in
        setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}

Java

// Checks if a volume containing external storage is available
// for read and write.
private boolean isExternalStorageWritable() {
    return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}

// Checks if a volume containing external storage is available to at least read.
private boolean isExternalStorageReadable() {
     return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
            Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}

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

adb shell sm set-virtual-disk true

בחירת מיקום אחסון פיזי

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

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

Kotlin

val externalStorageVolumes: Array<out File> =
        ContextCompat.getExternalFilesDirs(applicationContext, null)
val primaryExternalStorage = externalStorageVolumes[0]

Java

File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];

גישה לקבצים קבועים

כדי לגשת לקבצים ספציפיים לאפליקציה מאחסון חיצוני, קוראים לפונקציה getExternalFilesDir().

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

קטע הקוד הבא מדגים איך להפעיל את getExternalFilesDir():

Kotlin

val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)

Java

File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

יצירת קובצי מטמון

כדי להוסיף קובץ ספציפי לאפליקציה למטמון באחסון החיצוני, צריך לקבל הפניה ל-externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

File externalCacheFile = new File(context.getExternalCacheDir(), filename);

הסרת קובצי מטמון

כדי להסיר קובץ מהספרייה של המטמון החיצוני, משתמשים בשיטה delete() באובייקט File שמייצג את הקובץ:

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

תוכן מדיה

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

Kotlin

fun getAppSpecificAlbumStorageDir(context: Context, albumName: String): File? {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    val file = File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName)
    if (!file?.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created")
    }
    return file
}

Java

@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (file == null || !file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

חשוב להשתמש בשמות של ספריות שסופקו על ידי קבועי API כמו DIRECTORY_PICTURES. שמות הספריות האלה מבטיחים שהקבצים יטופלו כראוי על ידי המערכת. אם אף אחד משמות ספריות המשנה שהוגדרו מראש לא מתאים לקבצים שלכם, תוכלו להעביר את null אל getExternalFilesDir() במקום זאת. הפעולה הזו תחזיר את הספרייה הספציפית של האפליקציה ברמה הבסיסית (root) בתוך האחסון החיצוני.

שליחת שאילתה לגבי נפח אחסון פנוי

למשתמשים רבים אין הרבה מקום פנוי באחסון במכשירים שלהם, לכן חשוב להשתמש במקום באפליקציה בצורה מושכלת.

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

אם יש מספיק מקום לשמירת נתוני האפליקציה, צריך להתקשר למספר allocateBytes(). אחרת, האפליקציה יכולה לבקש מהמשתמש להסיר קבצים מסוימים מהמכשיר או להסיר את כל הקבצים מהמטמון מהמכשיר.

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

Kotlin

// App needs 10 MB within internal storage.
const val NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

val storageManager = applicationContext.getSystemService<StorageManager>()!!
val appSpecificInternalDirUuid: UUID = storageManager.getUuidForPath(filesDir)
val availableBytes: Long =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid)
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
        appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP)
} else {
    val storageIntent = Intent().apply {
        // To request that the user remove all app cache files instead, set
        // "action" to ACTION_CLEAR_APP_CACHE.
        action = ACTION_MANAGE_STORAGE
    }
}

Java

// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

StorageManager storageManager =
        getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
            appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
    // To request that the user remove all app cache files instead, set
    // "action" to ACTION_CLEAR_APP_CACHE.
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
}

יצירת פעילות לניהול אחסון

האפליקציה יכולה להצהיר וליצור פעילות מותאמת אישית שאחרי ההשקה שלה המשתמש יכול לנהל את הנתונים שהאפליקציה אחסנה במכשיר של המשתמש. אתם מצהירים על פעילות מותאמת אישית של 'ניהול מרחב' באמצעות המאפיין android:manageSpaceActivity בקובץ המניפסט. אפליקציות ניהול קבצים יכולות להפעיל את הפעילות הזו גם אם האפליקציה לא מייצאת את הפעילות. כלומר, אם הפעילות מגדירה את הערך של android:exported לערך false.

לבקש מהמשתמש להסיר קבצים מסוימים מהמכשיר

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

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

בקשה מהמשתמש להסיר את כל הקבצים שבמטמון

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

מקורות מידע נוספים

למידע נוסף על שמירת קבצים באחסון של המכשיר, עיינו במקורות המידע הבאים:

סרטונים