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

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

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

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

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

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

دسترسی از حافظه داخلی

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

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

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

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

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

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

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

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

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

کاتلین

val file = File(context.filesDir, filename)

جاوا

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

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

به عنوان جایگزینی برای استفاده از API File ، می‌توانید تابع 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 استفاده کنید.

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

برای خواندن یک فایل به صورت stream، از 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() در کد مبتنی بر کاتلین یا با ارسال دایرکتوری ریشه و نام دایرکتوری جدید به سازنده File در کد مبتنی بر جاوا، دایرکتوری‌های تو در تو ایجاد کنید یا یک دایرکتوری داخلی باز کنید:

کاتلین

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 از یک شیء context و API File ، به فایلی در این دایرکتوری دسترسی پیدا می‌کند:

کاتلین

val cacheFile = File(context.cacheDir, filename)

جاوا

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

حذف فایل‌های کش

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

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

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

    کاتلین

    cacheFile.delete()

    جاوا

    cacheFile.delete();
  • متد deleteFile() مربوط به context برنامه، که نام فایل را ارسال می‌کند:

    کاتلین

    context.deleteFile(cacheFileName)

    جاوا

    context.deleteFile(cacheFileName);

دسترسی از حافظه خارجی

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

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

در دستگاه‌هایی که اندروید ۹ (سطح API 28) یا پایین‌تر را اجرا می‌کنند، برنامه شما می‌تواند به فایل‌های مخصوص برنامه که متعلق به برنامه‌های دیگر هستند دسترسی داشته باشد، مشروط بر اینکه برنامه شما مجوزهای ذخیره‌سازی مناسب را داشته باشد. برای کنترل بیشتر کاربران بر فایل‌هایشان و محدود کردن شلوغی فایل‌ها، به برنامه‌هایی که اندروید ۱۰ (سطح API 29) و بالاتر را هدف قرار می‌دهند، به طور پیش‌فرض دسترسی محدود به فضای ذخیره‌سازی خارجی یا فضای ذخیره‌سازی محدود داده می‌شود. هنگامی که فضای ذخیره‌سازی محدود فعال باشد، برنامه‌ها نمی‌توانند به دایرکتوری‌های مخصوص برنامه که متعلق به برنامه‌های دیگر هستند دسترسی داشته باشند.

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

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

شما می‌توانید با فراخوانی تابع 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 در فایل مانیفست تعریف می‌کنید. برنامه‌های مدیریت فایل می‌توانند این اکتیویتی را حتی زمانی که برنامه شما اکتیویتی را export نمی‌کند، فراخوانی کنند ؛ یعنی زمانی که اکتیویتی شما android:exported را روی false تنظیم می‌کند.

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

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

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

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

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

منابع اضافی

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

ویدیوها