دسترسی به فایل‌های مختص برنامه

در بسیاری از موارد، برنامه‌تان فایل‌هایی ایجاد می‌کند که برنامه‌های دیگر نیازی به دسترسی به آن‌ها ندارند، یا نباید به آن‌ها دسترسی داشته باشند. سیستم مکان‌های زیر را برای ذخیره کردن چنین فایل‌های مختص برنامه ارائه می‌دهد:

  • دایرکتوری‌های حافظه داخلی: این دایرکتوری‌ها هم شامل مکان اختصاصی برای ذخیره فایل‌های دائمی و هم مکان دیگری برای ذخیره داده‌های حافظه نهان است. سیستم از دسترسی برنامه‌های دیگر به این مکان‌ها جلوگیری می‌کند و در Android نسخه ۱۰ (سطح API‏ ۲۹) و بالاتر، این مکان‌ها رمزگذاری می‌شوند. این ویژگی‌ها این مکان‌ها را به جای خوبی برای ذخیره داده‌های حساس تبدیل می‌کند که فقط خود برنامه‌تان می‌تواند به آن‌ها دسترسی داشته باشد.

  • دایرکتوری‌های فضای ذخیره‌سازی خارجی: این دایرکتوری‌ها شامل یک مکان اختصاصی برای ذخیره فایل‌های دائمی و مکان دیگری برای ذخیره داده‌های حافظه نهان است. اگرچه ممکن است برنامه دیگری به این دایرکتوری‌ها دسترسی داشته باشد اگر آن برنامه اجازه‌های مناسب را داشته باشد، فایل‌های ذخیره‌شده در این دایرکتوری‌ها فقط برای استفاده برنامه شما درنظر گرفته شده‌اند. اگر به‌طور خاص قصد دارید فایل‌هایی ایجاد کنید که برنامه‌های دیگر باید بتوانند به آن‌ها دسترسی داشته باشند، برنامه‌تان باید این فایل‌ها را در بخش فضای ذخیره‌سازی مشترک فضای ذخیره‌سازی خارجی ذخیره کند.

وقتی کاربر برنامه شما را حذف نصب می‌کند، فایل‌های ذخیره‌شده در فضای ذخیره‌سازی مختص برنامه برداشته می‌شود. به‌دلیل این رفتار، نباید از این فضای ذخیره‌سازی برای ذخیره کردن چیزی که کاربر انتظار دارد مستقل از برنامه شما باقی بماند استفاده کنید. برای مثال، اگر برنامه شما به کاربران اجازه می‌دهد عکس بگیرند، کاربر انتظار دارد که حتی پس‌از حذف نصب برنامه شما بتواند به آن عکس‌ها دسترسی داشته باشد. بنابراین، باید به‌جای آن از فضای ذخیره‌سازی مشترک برای ذخیره کردن این نوع فایل‌ها در مجموعه رسانه مناسب استفاده کنید.

بخش‌های زیر نحوه ذخیره و دسترسی به فایل‌ها را در دایرکتوری‌های مختص برنامه شرح می‌دهد.

دسترسی از فضای ذخیره‌سازی داخلی

برای هر برنامه، سیستم دایرکتوری‌هایی را در حافظه داخلی ارائه می‌دهد که برنامه می‌تواند فایل‌هایش را در آن‌ها سازمان‌دهی کند. یک فهرست راهنما برای فایل‌های پایدار برنامه‌تان طراحی شده است و فهرست راهنمای دیگر حاوی فایل‌های ذخیره‌شده در حافظه نهان برنامه‌تان است. برنامه شما برای خواندن و نوشتن فایل‌ها در این فهرست‌های راهنما به هیچ اجازه سیستمی نیاز ندارد.

برنامه‌های دیگر نمی‌توانند به فایل‌های ذخیره‌شده در حافظه داخلی دسترسی داشته باشند. این امر باعث می‌شود فضای ذخیره‌سازی داخلی مکان خوبی برای داده‌های برنامه‌ای باشد که برنامه‌های دیگر نباید به آن دسترسی داشته باشند.

اما به‌خاطر داشته باشید که این دایرکتوری‌ها معمولاً کوچک هستند. قبل‌از نوشتن فایل‌های مختص برنامه در فضای ذخیره‌سازی داخلی، برنامه‌تان باید فضای آزاد را در دستگاه جستجو کند.

دسترسی به فایل‌های دائمی

فایل‌های معمولی و دائمی برنامه‌تان در دایرکتوری‌ای قرار دارند که می‌توانید بااستفاده از ویژگی filesDir شیء زمینه به آن دسترسی پیدا کنید. این چارچوب چندین روش برای کمک به شما در دسترسی به فایل‌ها و ذخیره آن‌ها در این دایرکتوری ارائه می‌دهد.

دسترسی به فایل‌ها و ذخیره آن‌ها

می‌توانید از File API برای دسترسی به فایل‌ها و ذخیره کردن آن‌ها استفاده کنید.

برای کمک به حفظ عملکرد برنامه‌تان، یک فایل را چند بار باز و بسته نکنید.

تکه‌کد زیر نحوه استفاده از File API را نشان می‌دهد:

کاتلین

val file = File(context.filesDir, filename)

جاوا

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

ذخیره کردن فایل بااستفاده از جریان داده

به‌عنوان جایگزینی برای استفاده از File API، می‌توانید openFileOutput() را فراخوانی کنید تا FileOutputStream را دریافت کنید که در فایلی در دایرکتوری filesDir می‌نویسد.

تکه‌کد زیر نحوه نوشتن نوشتار در فایل را نشان می‌دهد:

کاتلین

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

جاوا

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() استفاده کنید:

کاتلین

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

جاوا

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();
}

مشاهده فهرست فایل‌ها

با فراخوانی fileList()، همان‌طور که در تکه‌کد زیر نشان داده شده است، می‌توانید آرایه‌ای حاوی نام همه فایل‌های موجود در دایرکتوری filesDir دریافت کنید:

کاتلین

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

جاوا

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

ایجاد دایرکتوری‌های تودرتو

همچنین می‌توانید با فراخوانی getDir() در کد مبتنی بر Kotlin یا با ارسال دایرکتوری ریشه و نام دایرکتوری جدید به سازنده File در کد مبتنی بر Java، دایرکتوری‌های تودرتو ایجاد کنید یا دایرکتوری داخلی را باز کنید:

کاتلین

context.getDir(dirName, Context.MODE_PRIVATE)

جاوا

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

ایجاد فایل‌های حافظه نهان

اگر لازم است داده‌های حساس را فقط به‌طور موقت ذخیره کنید، باید از دایرکتوری حافظه نهان تعیین‌شده برنامه در فضای ذخیره‌سازی داخلی برای ذخیره داده‌ها استفاده کنید. همان‌طور که برای همه فضاهای ذخیره‌سازی مختص برنامه صادق است، فایل‌های ذخیره‌شده در این دایرکتوری وقتی کاربر برنامه شما را حذف نصب می‌کند برداشته می‌شوند، هرچند فایل‌های این دایرکتوری ممکن است زودتر برداشته شوند.

برای ایجاد فایل ذخیره‌شده در حافظه نهان، File.createTempFile() را فراخوانی کنید:

کاتلین

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

جاوا

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

برنامه شما بااستفاده از ویژگی cacheDir شیء زمینه و File API به فایلی در این دایرکتوری دسترسی پیدا می‌کند:

کاتلین

val cacheFile = File(context.cacheDir, filename)

جاوا

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

برداشتن فایل‌های حافظه نهان

هرچند Android گاهی اوقات فایل‌های حافظه نهان را به‌طور خودکار حذف می‌کند، نباید برای پاک‌سازی این فایل‌ها به سیستم متکی باشید. همیشه باید فایل‌های حافظه نهان برنامه‌تان را در حافظه داخلی نگهداری کنید.

برای برداشتن فایل از دایرکتوری حافظه نهان در فضای ذخیره‌سازی داخلی، از یکی از روش‌های زیر استفاده کنید:

  • روش delete() در شیء File که نشان‌دهنده فایل است:

    کاتلین

    cacheFile.delete()

    جاوا

    cacheFile.delete();
  • روش deleteFile() بافت برنامه، با ارسال نام فایل:

    کاتلین

    context.deleteFile(cacheFileName)

    جاوا

    context.deleteFile(cacheFileName);

دسترسی از فضای ذخیره‌سازی خارجی

اگر حافظه داخلی فضای کافی برای ذخیره فایل‌های مختص برنامه ندارد، به‌جای آن از حافظه خارجی استفاده کنید. سیستم دایرکتوری‌هایی را در فضای ذخیره‌سازی خارجی ارائه می‌دهد که در آن برنامه می‌تواند فایل‌هایی را سازمان‌دهی کند که فقط در برنامه شما برای کاربر ارزش ایجاد می‌کنند. یک دایرکتوری برای فایل‌های دائمی برنامه شما طراحی شده است و دایرکتوری دیگر حاوی فایل‌های ذخیره‌شده در حافظه نهان برنامه شما است.

در Android نسخه ۴.۴ (سطح میانای برنامه‌سازی کاربردی ۱۹) یا بالاتر، برنامه‌تان برای دسترسی به دایرکتوری‌های مختص برنامه در فضای ذخیره‌سازی خارجی نیازی به درخواست اجازه‌های مرتبط با فضای ذخیره‌سازی ندارد. فایل‌های ذخیره‌شده در این دایرکتوری‌ها هنگام حذف نصب برنامه برداشته می‌شوند.

در دستگاه‌هایی که Android 9 (سطح API‏ ۲۸) یا پایین‌تر را اجرا می‌کنند، برنامه‌تان می‌تواند به فایل‌های مختص برنامه که متعلق به برنامه‌های دیگر است دسترسی داشته باشد، به‌شرطی که برنامه‌تان اجازه‌های فضای ذخیره‌سازی مناسب را داشته باشد. برای اینکه کاربران کنترل بیشتری بر فایل‌هایشان داشته باشند و برای محدود کردن به‌هم‌ریختگی فایل‌ها، برنامه‌هایی که Android 10 (میانای برنامه‌سازی کاربردی سطح ۲۹) و بالاتر را هدف قرار می‌دهند، به‌طور پیش‌فرض دسترسی هدف‌دار به فضای ذخیره‌سازی خارجی یا فضای ذخیره‌سازی هدف‌دار دریافت می‌کنند. وقتی فضای ذخیره‌سازی محدودشده فعال باشد، برنامه‌ها نمی‌توانند به دایرکتوری‌های مختص برنامه که متعلق به برنامه‌های دیگر است دسترسی داشته باشند.

تأیید کنید که فضای ذخیره‌سازی دردسترس است

چون فضای ذخیره‌سازی خارجی در یک حجم فیزیکی قرار دارد که ممکن است کاربر بتواند آن را بردارد، قبل‌از تلاش برای خواندن داده‌های مختص برنامه از فضای ذخیره‌سازی خارجی یا نوشتن داده‌های مختص برنامه در آن، بررسی کنید که حجم دردسترس باشد.

می‌توانید با فراخوانی Environment.getExternalStorageState()، وضعیت جلد را پُرسمان کنید. اگر وضعیت برگردانده‌شده MEDIA_MOUNTED باشد، می‌توانید فایل‌های مختص برنامه را در فضای ذخیره‌سازی خارجی بخوانید و بنویسید. اگر MEDIA_MOUNTED_READ_ONLY باشد، فقط می‌توانید این فایل‌ها را بخوانید.

برای مثال، روش‌های زیر برای تعیین دردسترس بودن فضای ذخیره‌سازی مفید هستند:

کاتلین

// 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)
}

جاوا

// 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() تماس بگیرید. همان‌طور که در تکه‌کد نشان داده شده است، اولین عنصر در آرایه برگردانده‌شده به‌عنوان حجم فضای ذخیره‌سازی خارجی اصلی درنظر گرفته می‌شود. از این درایو استفاده شود، مگراینکه پر یا غیرقابل‌دسترس باشد.

کاتلین

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

جاوا

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

دسترسی به فایل‌های دائمی

برای دسترسی به فایل‌های مختص برنامه از فضای ذخیره‌سازی خارجی، getExternalFilesDir() را فراخوانی کنید.

برای کمک به حفظ عملکرد برنامه‌تان، یک فایل را چند بار باز و بسته نکنید.

تکه‌کد زیر نحوه فراخوانی getExternalFilesDir() را نشان می‌دهد:

کاتلین

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

جاوا

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

ایجاد فایل‌های حافظه نهان

برای افزودن فایل مختص برنامه به حافظه پنهان در فضای ذخیره‌سازی خارجی، مرجعی برای externalCacheDir دریافت کنید:

کاتلین

val externalCacheFile = File(context.externalCacheDir, filename)

جاوا

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

برداشتن فایل‌های حافظه نهان

برای برداشتن فایل از دایرکتوری حافظه نهان خارجی، از روش delete() در شیء File که نشان‌دهنده فایل است استفاده کنید:

کاتلین

externalCacheFile.delete()

جاوا

externalCacheFile.delete();

محتوای رسانه

اگر برنامه شما با فایل‌های رسانه‌ای کار می‌کند که فقط در برنامه شما برای کاربر ارزش ایجاد می‌کنند، بهتر است آن‌ها را در دایرکتوری‌های مختص برنامه در فضای ذخیره‌سازی خارجی ذخیره کنید، همان‌طور که در تکه‌کد زیر نشان داده شده است:

کاتلین

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
}

جاوا

@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() منتقل کنید. این کار دایرکتوری ریشه مختص برنامه را در فضای ذخیره‌سازی خارجی برمی‌گرداند.

فضای آزاد پُرسمان

بسیاری از کاربران فضای ذخیره‌سازی زیادی در دستگاهشان ندارند، بنابراین برنامه‌تان باید به‌طور سنجیده از فضا استفاده کند.

اگر ازقبل می‌دانید چقدر داده ذخیره می‌کنید، می‌توانید با فراخوانی getAllocatableBytes() ببینید دستگاه چقدر فضا می‌تواند برای برنامه‌تان فراهم کند. مقدار برگشتی getAllocatableBytes() ممکن است بزرگ‌تر از مقدار فعلی فضای خالی در دستگاه باشد. دلیلش این است که سیستم فایل‌هایی را شناسایی کرده است که می‌تواند از فهرست‌های راهنمای حافظه پنهان برنامه‌های دیگر بردارد.

اگر فضای کافی برای ذخیره کردن داده‌های برنامه‌تان وجود دارد، allocateBytes() را فراخوانی کنید. درغیراین‌صورت، برنامه‌تان می‌تواند از کاربر بخواهد برخی‌از فایل‌ها را از دستگاه بردارد یا همه فایل‌های حافظه نهان را از دستگاه بردارد.

تکه‌کد زیر نمونه‌ای از نحوه پرس‌وجوی فضای خالی در دستگاه توسط برنامه شما را نشان می‌دهد:

کاتلین

// 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
    }
}

جاوا

// 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 تنظیم می‌کند.

از کاربر بخواهید برخی‌از فایل‌های دستگاه را بردارد

برای درخواست از کاربر برای انتخاب فایل‌های موجود در دستگاه برای حذف، قصدی را فراخوانی کنید که شامل کنش ACTION_MANAGE_STORAGE باشد. این هدف پیام‌واره‌ای را به کاربر نمایش می‌دهد. درصورت تمایل، این پیام‌واره می‌تواند مقدار فضای آزاد دردسترس در دستگاه را نشان دهد. برای نمایش این اطلاعات کاربرپسند، از نتیجه محاسبه زیر استفاده کنید:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

از کاربر بخواهید همه فایل‌های حافظه پنهان را بردارد

یا می‌توانید از کاربر بخواهید فایل‌های حافظه نهان را از همه برنامه‌های دستگاه پاک کند. برای انجام این کار، قصدی را فراخوانی کنید که شامل کنش قصد ACTION_CLEAR_APP_CACHE باشد.

منابع بیشتر

برای اطلاعات بیشتر درباره ذخیره کردن فایل‌ها در فضای ذخیره‌سازی دستگاه، به منابع زیر مراجعه کنید.

ویدیوها