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

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

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

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

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

ACTION_CLEAR_APP_CACHE

مراجع إضافية

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

الفيديوهات