الوصول إلى الملفات الخاصة بالتطبيقات

في كثير من الحالات، ينشئ تطبيقك ملفات لا تحتاج التطبيقات الأخرى إلى الوصول إليها، أو يجب ألا يمكنها الوصول إليها. يوفّر النظام المواقع التالية لتخزين هذه الملفات الخاصة بالتطبيقات:

  • أدلة وحدة التخزين الداخلية: تشتمل هذه الأدلة على موقع مخصص لتخزين الملفات الدائمة وموقع آخر لتخزين بيانات ذاكرة التخزين المؤقت. يمنع النظام التطبيقات الأخرى من الوصول إلى هذه المواقع، وفي Android 10 (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يتم تشفير هذه المواقع. هذه الخصائص تجعل هذه المواقع مكانًا جيدًا لتخزين البيانات الحساسة التي لا يمكن إلا لتطبيقك نفسه الوصول إليها.

  • أدلة مساحة التخزين الخارجية: تتضمّن هذه الأدلة كلاً من الموقع المخصّص لتخزين الملفات الدائمة وموقع آخر لتخزين بيانات ذاكرة التخزين المؤقت. على الرغم من أنه يمكن لتطبيق آخر الوصول إلى هذه الأدلة إذا كان لدى هذا التطبيق الأذونات المناسبة، فإن الملفات المخزنة في هذه الأدلة مخصصة للاستخدام من قِبل تطبيقك فقط. وإذا كنت تنوي إنشاء ملفات تستطيع التطبيقات الأخرى الوصول إليها تحديدًا، يجب أن يخزِّن تطبيقك هذه الملفات في جزء مساحة التخزين المشتركة في وحدة التخزين الخارجية بدلاً من ذلك.

عندما يلغي المستخدم تثبيت تطبيقك، تتم إزالة الملفات المحفوظة في مساحة التخزين الخاصة بالتطبيق. وبسبب هذا السلوك، يجب عدم استخدام مساحة التخزين هذه لحفظ أي بيانات يتوقع المستخدم الاحتفاظ بها بشكل مستقل عن تطبيقك. على سبيل المثال، إذا سمح تطبيقك للمستخدمين بالتقاط الصور، سيتوقّع المستخدم إمكانية الوصول إلى تلك الصور حتى بعد إلغاء تثبيت تطبيقك. لذا، عليك استخدام مساحة تخزين مشتركة لحفظ هذه الأنواع من الملفات في مجموعة الوسائط المناسبة.

توضح الأقسام التالية كيفية تخزين الملفات والوصول إليها داخل أدلة خاصة بالتطبيقات.

الوصول من وحدة التخزين الداخلية

لكل تطبيق، يوفر النظام أدلة داخل وحدة التخزين الداخلية حيث يمكن للتطبيق تنظيم ملفاته. تم تصميم دليل واحد للملفات الدائمة لتطبيقك، بينما يحتوي دليل آخر على الملفات المخزَّنة مؤقتًا في تطبيقك. لا يتطلب تطبيقك أي أذونات من النظام لقراءة الملفات والكتابة فيها في هذه الأدلة.

ولا يمكن للتطبيقات الأخرى الوصول إلى الملفات المخزنة داخل وحدة التخزين الداخلية. وهذا يجعل وحدة التخزين الداخلية مكانًا جيدًا لبيانات التطبيقات التي ينبغي ألا تصل إليها التطبيقات الأخرى.

ومع ذلك، ضع في اعتبارك أن هذه الأدلة تميل إلى أن تكون صغيرة. قبل كتابة ملفات خاصة بالتطبيق في وحدة التخزين الداخلية، يجب أن يبحث تطبيقك عن المساحة الخالية على الجهاز.

الوصول إلى الملفات الدائمة

إنّ الملفات العادية والمستمرة لتطبيقك موجودة في دليل يمكنك الوصول إليه باستخدام السمة filesDir لكائن السياق. يوفر إطار العمل عدة طرق لمساعدتك في الوصول إلى الملفات وتخزينها في هذا الدليل.

الوصول إلى الملفات وتخزينها

يمكنك استخدام واجهة برمجة التطبيقات File للوصول إلى الملفات وتخزينها.

للمساعدة في الحفاظ على أداء تطبيقك، لا تفتح الملف نفسه وتغلقه عدة مرات.

يوضّح مقتطف الرمز التالي كيفية استخدام File API:

Kotlin

val file = File(context.filesDir, filename)

لغة Java

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

تخزين ملف باستخدام ساحة المشاركات

كبديل لاستخدام واجهة برمجة التطبيقات 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 أو من خلال إدخال الدليل الجذر واسم دليل جديد في دالة إنشاء 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 لكائن السياق وواجهة برمجة التطبيقات File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

لغة Java

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

إزالة ملفات ذاكرة التخزين المؤقت

على الرغم من أن Android يحذف أحيانًا ملفات ذاكرة التخزين المؤقت من تلقاء نفسه، لا يجب الاعتماد على النظام في إزالة هذه الملفات نيابةً عنك. يجب عليك دائمًا الاحتفاظ بملفات ذاكرة التخزين المؤقت لتطبيقك داخل وحدة التخزين الداخلية.

لإزالة ملف من دليل ذاكرة التخزين المؤقت في وحدة التخزين الداخلية، يمكنك استخدام إحدى الطرق التالية:

  • طريقة delete() على كائن File الذي يمثل الملف:

    Kotlin

    cacheFile.delete()
    

    لغة Java

    cacheFile.delete();
    
  • طريقة deleteFile() لسياق التطبيق، مع إدخال اسم الملف:

    Kotlin

    context.deleteFile(cacheFileName)
    

    لغة Java

    context.deleteFile(cacheFileName);
    

الوصول من وحدة التخزين الخارجية

إذا لم توفر وحدة التخزين الداخلية مساحة كافية لتخزين الملفات الخاصة بالتطبيقات، يمكنك استخدام وحدة التخزين الخارجية بدلاً من ذلك. ويوفّر النظام أدلة ضمن وحدة تخزين خارجية يمكن للتطبيق من خلالها تنظيم الملفات التي تقدّم قيمة للمستخدم داخل تطبيقك فقط. وتم تصميم دليل واحد للملفات الدائمة في تطبيقك، بينما يحتوي دليل آخر على الملفات المخزَّنة مؤقتًا في تطبيقك.

على نظام التشغيل Android 4.4 (مستوى واجهة برمجة التطبيقات 19) أو الإصدارات الأحدث، لا يحتاج تطبيقك إلى طلب أي أذونات متعلقة بمساحة التخزين للوصول إلى الأدلة الخاصة بالتطبيق ضمن مساحة تخزين خارجية. وتتم إزالة الملفات المخزنة في هذه الأدلة عند إلغاء تثبيت تطبيقك.

على الأجهزة التي تعمل بنظام التشغيل Android 9 (مستوى واجهة برمجة التطبيقات 28) أو الإصدارات الأقدم، يمكن لتطبيقك الوصول إلى الملفات الخاصة بالتطبيق التي تنتمي إلى تطبيقات أخرى، شرط أن يحصل تطبيقك على الأذونات المناسبة لمساحة التخزين. لمنح المستخدمين المزيد من التحكّم في ملفاتهم والحد من تكدس الملفات، يتم تلقائيًا منح التطبيقات التي تستهدف الإصدار Android 10 (مستوى واجهة برمجة التطبيقات 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;
}

من المهم أن تستخدم أسماء الأدلة التي توفّرها ثوابت واجهة برمجة التطبيقات، مثل DIRECTORY_PICTURES. وتضمن أسماء الأدلة هذه أن يعالج النظام الملفات بشكل صحيح. إذا لم يناسب أي من أسماء الدليل الفرعي المحددة مسبقًا ملفاتك، يمكنك بدلاً من ذلك تمرير null في getExternalFilesDir(). يؤدي ذلك إلى عرض الدليل الخاص بالتطبيق الجذري داخل وحدة تخزين خارجية.

المساحة الخالية لطلب البحث

لا يمتلك العديد من المستخدمين مساحة تخزين كبيرة على أجهزتهم، لذلك يجب أن يستهلك تطبيقك مساحة كبيرة.

إذا كنت تعرف مسبقًا حجم البيانات التي تخزنها، يمكنك معرفة حجم المساحة التي يمكن أن يوفرها الجهاز لتطبيقك من خلال طلب الرقم 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.

مطالبة المستخدم بإزالة بعض الملفات من الجهاز

لطلب أن يختار المستخدم ملفات على الجهاز لإزالتها، استدعِ هدفًا يتضمّن إجراء ACTION_MANAGE_STORAGE. يعرض هذا الغرض رسالة مطالبة للمستخدم. إذا رغبت في ذلك، يمكن أن تظهر هذه المطالبة مقدار المساحة الخالية المتوفرة على الجهاز. لإظهار هذه المعلومات سهلة الاستخدام، استخدم نتيجة العملية الحسابية التالية:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

مطالبة المستخدم بإزالة جميع ملفات ذاكرة التخزين المؤقت

بدلاً من ذلك، يمكنك أن تطلب من المستخدم محو ملفات ذاكرة التخزين المؤقت من جميع التطبيقات على الجهاز. ولإجراء ذلك، عليك استدعاء نية تتضمّن الإجراء المقصود ACTION_CLEAR_APP_CACHE.

مراجع إضافية

لمزيد من المعلومات حول حفظ الملفات في سعة تخزين الجهاز، راجع الموارد التالية.

الفيديوهات الطويلة