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

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

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

مراجع إضافية

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

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