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

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

  • دایرکتوری های ذخیره سازی داخلی: این دایرکتوری ها هم یک مکان اختصاصی برای ذخیره فایل های دائمی دارند و هم مکان دیگری برای ذخیره داده های کش. این سیستم از دسترسی سایر برنامه ها به این مکان ها جلوگیری می کند و در اندروید 10 (سطح API 29) و بالاتر، این مکان ها رمزگذاری می شوند. این ویژگی ها این مکان ها را به مکانی مناسب برای ذخیره داده های حساس تبدیل می کند که فقط خود برنامه شما می تواند به آنها دسترسی داشته باشد.

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

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

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

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

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

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

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

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

فایل‌های معمولی و دائمی برنامه شما در دایرکتوری قرار دارند که می‌توانید با استفاده از ویژگی 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 در کد مبتنی بر جاوا، دایرکتوری های تودرتو ایجاد کنید یا یک دایرکتوری داخلی باز کنید:

کاتلین

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

فایل های کش را حذف کنید

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

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

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

    کاتلین

    cacheFile.delete()

    جاوا

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

    کاتلین

    context.deleteFile(cacheFileName)

    جاوا

    context.deleteFile(cacheFileName);

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

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

در Android نسخه 4.4 (سطح API 19) یا بالاتر، برنامه شما نیازی به درخواست مجوزهای مربوط به فضای ذخیره سازی برای دسترسی به دایرکتوری های خاص برنامه در حافظه خارجی ندارد. فایل های ذخیره شده در این دایرکتوری ها با حذف نصب برنامه حذف می شوند.

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

فضای خالی را جستجو کنید

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

اگر از قبل می دانید چه مقدار داده ذخیره می کنید، می توانید با فراخوانی 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 است. این intent یک درخواست را به کاربر نمایش می دهد. در صورت تمایل، این اعلان می تواند میزان فضای خالی موجود در دستگاه را نشان دهد. برای نمایش این اطلاعات کاربرپسند، از نتیجه محاسبه زیر استفاده کنید:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

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

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

منابع اضافی

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

ویدیوها