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

انواع فضاهای ذخیره‌سازی در اندروید

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

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

فضای اشتراکی: فضایی است که فایل‌ها در آن با نیت اشتراک‌گذاری بین دیگر برنامه‌ها ذخیره می‌شوند

Preferences: فضایی است برای ذخیره‌سازی اطلاعات پایه و مخفی به صورت کلید مقدار

پایگاه داده: داده‌های ساختاربندی شده را ذخیره می‌کند

انتخاب نوع ذخیره سازی مناسب

برای اینکه بتوانیم فضای مناسبی را انتخاب کنیم، بهتر است به این سوالات پاسخ دهیم:

فضای مورد نیازمان چقدر است؟

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

میزان دسترسی به داده‌ها چقدر باید قابل اعتماد باشد؟

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

چه داده‌ای را ذخیره می‌کنید؟

اگر داده تنها برای برنامه شما معنا دارد از فضای مخصوص برنامه استفاده کنید. برای مدیاهای قابل اشتراک، از فضای اشتراکی استفاده کنید که بقیه برنامه‌ها نیز به آن دسترسی داشته باشند. برای داده‌های ساختاریافته از preferences یا پایگاه داده استفاده کنید.

آیا داده‌ها باید برای برنامه خصوصی باشند؟

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

دسترسی‌های لازم برای خواندن و نوشتن در حافظه خارجی

در اندروید دسترسی‌ها برای خواندن و نوشتن در حافظه خارجی به صورت: READ_EXTERNAL_STORAGE و WRTIE_EXTERNAL_STORAGE است.

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

چند نکته در مورد فایل‌ها

فایل‌ها را به طور مداوم باز و بسته نکنید.

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

اشتراک فایل‌ها

برای اینکه بتوانید فایل‌های خاص و یا داده‌های خاصی را با برنامه‌های دیگر به اشتراک بگذارید، اندروید دو راهکار را ارائه می‌دهد:

برای اشتراک فایل با دیگر برنامه‌ها از FileProvider استفاده کنید.

برای اشتراک داده‌ها از content provider استفاده کنید.

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

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

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

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

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

دستیابی به حافظه داخلی

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

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

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

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

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

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

با استفاده از File می‌توان به فایل‌ها دسترسی داشت:

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

ذخیره‌سازی با استفاده از stream

به عنوان یک روش دیگر برای دسترسی به فایل‌ها می‌توان از openFileOutput() برای گرفتن یک FileOutputStream که برای نوشتن در فایل استفاده می‌شود بهره برد.

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}
هشدار: در دستگاه‌هایی که اندروید ۷ به بالا دارند در صورتی که Context.MODE_PRIVATE را به opeFileOutput() ندهید یک SecurtiyException رخ خواهد داد.

برای دسترسی دیگر برنامه‌ها به این پوشه‌ها از طریق FileProvider با پرچم FLAG_GRANT_READ_URI_PERMISSION استفاده کنید.

دسترسی به فایل با استفاده از stream

برای خواندن یک فایل با استفاده از stream می‌توان از opeFIleInput() استفاده کرد.

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

نکته: اگر نیاز به باز کردن فایلی به صورت stream در زمان نصب دارید، آن فایل را به در پوشه  /res/raw پروژه ذخیره کنید. با استفاده از openRawResource() و قراردادن نام فایل با پیشوند R.raw می‌توان به آن دسترسی داشت. این متد یک InputStream بازمی‌گرداند که به شما اجازه خواندن در آن را می‌دهد، اما شما اجازه نوشتن در فایل اصلی را ندارید.

گرفتن لیست فایل‌ها

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

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

ایجاد پوشه‌های داخلی

شما می‌توانید در داخل فضای مخصوص برنامه پوشه‌های خاص خود را مطابق کد زیر تولید کنید:

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

تولید فایل موقت

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

نکته: پوشه موقتی برای ذخیره‌سازی مقدار کمی از داده‌های حساس طراحی شده‌است. برای سنجش میزان فضای آزاد در این پوشه از متد getCacheQuotaBytes() استفاده کنید.

برای ایجاد فایل موقتی از File.createTempFIle() استفاده می‌کنیم:

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

با استفاده از cacheDit از Context می‌توانید به این فایل‌ها دسترسی پیدا کنید:

File cacheFile = new File(context.getCacheDir(), filename);
ممکن است در زمانی که فضای کافی در اختیار نباشد، اندروید این فایل‌های کش را پاک کند، بنابراین قبل از دسترسی به آن‌ها از وجودشان مطمئن شوید.

حذف فایل موقتی

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

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

با داشتن فایل مورد نظر از متد delete() استفاده کنید

cacheFile.delete();

با داشتن نام فایل از متد deleteFile() از Context استفاده کنید:

context.deleteFile(cacheFileName);

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

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

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

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

صحت‌سنجی در دسترس بودن حافظه

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

با استفاده از Enviroment.getExternalStorageState() می‌توان از وضعیت حافظه خارجی مطلع شد. اگر وضعیت به صورت MEDIA_MOUNTED باشد، در نتیجه می‌توان عملیات خواندن و نوشتن را بر روی آن اعمال کرد. اگر وضعیت MEDIA_MOUNTED_READ_ONLY باشد، تنها می‌توان فایل‌ها را خواند.

کد زیر این وضعیت را نشان می‌دهد:

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

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

انتخاب محل حافظه فیزیکی

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

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

File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];
نکته: در اندروید‌های ۴.۳ به قبل، آرایه بازگردانده شده شامل تنها یک مقدار که همان حافظه خارجی اصلی است، می‌شود.

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

برای دسترسی به فایل‌های همیشگی در حافظه خارجی از getExternalFilesDir() مطابق کد زیر استفاده می‌کنیم:

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

ایجاد فایل موقت

برای ایجاد فایل موقت در حافظه خارجی از  getExternalCacheDir() استفاده می‌کنیم:

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

حذف فایل موقت

برای حذف فایل موقت از متد delete همان فایل استفاده می‌کنیم:

externalCacheFile.delete();

بررسی میزان فضای آزاد حافظه

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

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

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

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

کد زیر مثالی از مطلع شدن از فضای آزاد دستگاه را نشان می‌دهد:

// 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 {
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
    // Display prompt to user, requesting that they choose files to remove.
}
نکته: نیازی نیست که برای ذخیره فایل مقدار فضای خالی را چک کنید. می‌توانید همان لحظه فایل را ذخیره کنید و IOException را دریافت کنید. این کار زمانی کاربرد دارد که نمی‌دانید چه مقدار فضا نیاز دارید. به عنوان مثال اگر تصویر png را به jpeg تبدیل کنید نمی‌دانید چه مقدار فضا نیاز خواهید داشت.
Was this article helpful?
Dislike 0
قبلی: آموزش ساخت رابط کاربری (UI) در اندروید
بعدی: ارسال Notification نوتیفیکیشن در اندروید