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

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

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

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

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

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

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

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

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

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

الوصول إلى الملفات الثابتة

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

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

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

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

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

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

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

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

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

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

مراجع إضافية

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

الفيديوهات