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

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

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

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

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

في نظام التشغيل 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.

مصادر إضافية

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

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