أفضل الممارسات حول أمان التطبيقات

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

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

فرض الاتصالات الآمنة

عندما تحمي البيانات التي تتبادلها بين تطبيقك والتطبيقات الأخرى أو بين تطبيقك وموقع إلكتروني، فإنّك تعمل على تحسين استقرار تطبيقك وحماية البيانات التي ترسلها وتتلقاها.

حماية التواصل بين التطبيقات

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

إظهار أداة اختيار تطبيق

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

Kotlin

val intent = Intent(Intent.ACTION_SEND)
val possibleActivitiesList: List<ResolveInfo> =
        packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)

// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with."

    val chooser = resources.getString(R.string.chooser_title).let { title ->
        Intent.createChooser(intent, title)
    }
    startActivity(chooser)
} else if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

لغة Java

Intent intent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> possibleActivitiesList = getPackageManager()
        .queryIntentActivities(intent, PackageManager.MATCH_ALL);

// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size() > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with."

    String title = getResources().getString(R.string.chooser_title);
    Intent chooser = Intent.createChooser(intent, title);
    startActivity(chooser);
} else if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

معلومات ذات صلة:

تطبيق الأذونات المستندة إلى التوقيع

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <permission android:name="my_custom_permission_name"
                android:protectionLevel="signature" />

معلومات ذات صلة:

عدم السماح بالوصول إلى موفّري المحتوى لتطبيقك

ما لم تكن تنوي إرسال بيانات من تطبيقك إلى تطبيق آخر لا تملكه، يجب عدم السماح صراحةً لتطبيقات المطوّرين الآخرين بالوصول إلى عناصر ContentProvider لتطبيقك. يُعدّ هذا الإعداد مهمًا على وجه الخصوص إذا كان بالإمكان تثبيت تطبيقك على أجهزة تعمل بنظام التشغيل Android 4.1.1 (مستوى واجهة برمجة التطبيقات 16) أو الإصدارات الأقدم، لأنّ سمة android:exported للعنصر <provider> تكون true تلقائيًا على هذه الإصدارات من Android.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application ... >
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            ...
            android:exported="false">
            <!-- Place child elements of <provider> here. -->
        </provider>
        ...
    </application>
</manifest>

طلب بيانات الاعتماد قبل عرض المعلومات الحسّاسة

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

للاطّلاع على مزيد من المعلومات حول طريقة طلب بيانات الاعتماد بالمقاييس الحيوية، يُرجى الاطّلاع على دليل المصادقة بالمقاييس الحيوية.

تطبيق إجراءات أمان الشبكة

توضّح الأقسام التالية كيفية تحسين أمان الشبكة لتطبيقك.

استخدام زيارات بروتوكول أمان طبقة النقل (TLS)

إذا اتصل تطبيقك بخادم ويب يتضمّن شهادة تم إصدارها من مرجع تصديق معروف وموثوق به، يمكنك استخدام طلب HTTPS على النحو التالي:

Kotlin

val url = URL("https://www.google.com")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.connect()
urlConnection.inputStream.use {
    ...
}

لغة Java

URL url = new URL("https://www.google.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.connect();
InputStream in = urlConnection.getInputStream();

إضافة إعدادات أمان الشبكة

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

لإضافة ملف إعداد أمان الشبكة إلى تطبيقك، يُرجى اتّباع الخطوات التالية:

  1. يُرجى توضيح الإعدادات من خلال ملف بيان التطبيق:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
    
  3. أضِف ملف موارد XML، موجود في res/xml/network_security_config.xml.

    حدِّد أنّه يجب استخدام HTTPS في جميع الزيارات إلى نطاقات معيّنة من خلال إيقاف النص الواضح:

    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">secure.example.com</domain>
            ...
        </domain-config>
    </network-security-config>
    

    وأثناء عملية التطوير، يمكنك استخدام العنصر <debug-overrides> للسماح صراحةً بالشهادات التي يثبّتها المستخدم. ويلغي هذا العنصر الخيارات الأساسية للأمان الخاصة بتطبيقك أثناء تصحيح الأخطاء والاختبار بدون التأثير في إعدادات إصدار التطبيق. يوضّح المقتطف التالي كيفية تحديد هذا العنصر في ملف XML الخاص بإعداد أمان الشبكة في تطبيقك:

    <network-security-config>
        <debug-overrides>
            <trust-anchors>
                <certificates src="user" />
            </trust-anchors>
        </debug-overrides>
    </network-security-config>
    

معلومات ذات صلة: إعدادات أمان الشبكة

إنشاء حساب مدير الثقة

ينبغي ألا يقبل مدقق بروتوكول أمان طبقة النقل (TLS) جميع الشهادات. قد تحتاج إلى إعداد مدير الثقة والتعامل مع جميع تحذيرات بروتوكول أمان طبقة النقل (TLS) التي تحدث إذا انطبق أحد الشروط التالية على حالة الاستخدام:

  • أنك تتصل بخادم ويب لديه شهادة موقّعة من مرجع تصديق جديد أو مخصّص.
  • لا يثق الجهاز الذي تستخدمه بمرجع التصديق هذا.
  • لا يمكنك استخدام إعدادات أمان الشبكة.

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

معلومات ذات صلة:

استخدام كائنات WebView بعناية

WebView يجب ألا تتيح العناصر في تطبيقك للمستخدمين الانتقال إلى مواقع إلكترونية خارجة عن سيطرتك. استخدِم قائمة مسموح بها كلما أمكنك ذلك لحظر المحتوى الذي يتم تحميله من خلال عناصر WebView في تطبيقك.

بالإضافة إلى ذلك، يجب عدم تفعيل إتاحة واجهة JavaScript مطلقًا ما لم تكن تتحكّم بشكل كامل في محتوى كائنات WebView في تطبيقك وتثق به بشكل كامل.

استخدام قنوات رسائل HTML

إذا كان لا بد من أن يتوافق تطبيقك مع توافق واجهة JavaScript على الأجهزة التي تعمل بالإصدار Android 6.0 (المستوى 23 لواجهة برمجة التطبيقات) والإصدارات الأحدث، استخدِم قنوات رسائل HTML بدلاً من التواصل بين الموقع الإلكتروني والتطبيق، كما هو موضّح في مقتطف الرمز التالي:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel()

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() {

    override fun onMessage(port: WebMessagePort, message: WebMessage) {
        Log.d(TAG, "On port $port, received this message: $message")
    }
})

// Send a message from channel[1] to channel[0].
channel[1].postMessage(WebMessage("My secure message"))

لغة Java

WebView myWebView = (WebView) findViewById(R.id.webview);

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
WebMessagePort[] channel = myWebView.createWebMessageChannel();

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
    @Override
    public void onMessage(WebMessagePort port, WebMessage message) {
         Log.d(TAG, "On port " + port + ", received this message: " + message);
    }
});

// Send a message from channel[1] to channel[0].
channel[1].postMessage(new WebMessage("My secure message"));

معلومات ذات صلة:

تقديم الأذونات المناسبة

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

استخدام الأغراض لتأجيل الأذونات

كلما أمكن، لا تضِف إذنًا إلى تطبيقك لإكمال إجراء يمكن إكماله في تطبيق آخر، بل استخدِم بدلاً من ذلك الغرض من تأجيل الطلب إلى تطبيق آخر لديه الإذن اللازم.

يوضّح المثال التالي كيفية استخدام نية لتوجيه المستخدمين إلى تطبيق مخصَّص لجهات الاتصال بدلاً من طلب إذنَي READ_CONTACTS و WRITE_CONTACTS:

Kotlin

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent(Intent.ACTION_INSERT).apply {
    type = ContactsContract.Contacts.CONTENT_TYPE
}.also { intent ->
    // Make sure that the user has a contacts app installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

لغة Java

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent insertContactIntent = new Intent(Intent.ACTION_INSERT);
insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);

// Make sure that the user has a contacts app installed on their device.
if (insertContactIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(insertContactIntent);
}

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

معلومات ذات صلة:

مشاركة البيانات بأمان بين التطبيقات

يُرجى اتّباع أفضل الممارسات التالية لمشاركة محتوى تطبيقك مع التطبيقات الأخرى بطريقة أكثر أمانًا:

  • فرض أذونات القراءة فقط أو الكتابة فقط حسب الحاجة.
  • السماح للعملاء بالوصول إلى البيانات لمرة واحدة باستخدام العلامتين FLAG_GRANT_READ_URI_PERMISSION وFLAG_GRANT_WRITE_URI_PERMISSION.
  • عند مشاركة البيانات، استخدِم معرّفات الموارد المنتظمة (URI) content:// وليس معرّفات الموارد المنتظمة (URI) file://. وتنفّذ نُسخ FileProvider هذا الإجراء نيابةً عنك.

يوضح مقتطف الرمز التالي كيفية استخدام علامات منح إذن عنوان URI وأذونات مزود المحتوى لعرض ملف PDF لتطبيق في تطبيق عارض PDF منفصل:

Kotlin

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("content://com.example/personal-info.pdf")

    // This flag gives the started app read access to the file.
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}.also { intent ->
    // Make sure that the user has a PDF viewer app installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

لغة Java

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW);
viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf"));

// This flag gives the started app read access to the file.
viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// Make sure that the user has a PDF viewer app installed on their device.
if (viewPdfIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(viewPdfIntent);
}

ملاحظة: يشكّل تنفيذ الملفات من الدليل الرئيسي للتطبيقات القابلة للكتابة انتهاكًا في W^X. لهذا السبب، لا يمكن للتطبيقات غير الموثوق بها التي تستهدف Android 10 (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث استدعاء exec() في الملفات ضمن الدليل الرئيسي للتطبيق، بل فقط الرمز الثنائي المضمَّن في ملف APK للتطبيق. بالإضافة إلى ذلك، لا يمكن للتطبيقات التي تستهدف الإصدار 10 من نظام التشغيل Android والإصدارات الأحدث تعديل الرمز القابل للتنفيذ من الملفات التي تم فتحها باستخدام dlopen() في الذاكرة. ويتضمن ذلك أي ملفات عناصر مشتركة (.so) مع عمليات نقل النصوص.

معلومات ذات صلة: android:grantUriPermissions

تخزين البيانات بأمان

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

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

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

ملاحظة: إذا كانت البيانات التي تخزّنها حساسة أو خاصة بشكل خاص، ننصحك باستخدام كائنات EncryptedFile المتاحة من مكتبة الأمان بدلاً من كائنات File.

يوضح مقتطف الرمز التالي إحدى الطرق لكتابة البيانات في التخزين:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

لغة Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
String fileToWrite = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToWrite),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

يعرض مقتطف الرمز التالي العملية العكسية، وقراءة البيانات من مساحة التخزين:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

لغة Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToRead),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

معلومات ذات صلة:

تخزين البيانات في وحدة تخزين خارجية بناءً على حالة الاستخدام

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

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

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

التحقّق من مدى توفّر مساحة التخزين

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

التحقق من صحة البيانات

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

يتضمن مقتطف الرمز التالي مثالاً لأداة التحقق من التجزئة:

Kotlin

val hash = calculateHash(stream)
// Store "expectedHash" in a secure location.
if (hash == expectedHash) {
    // Work with the content.
}

// Calculating the hash code can take quite a bit of time, so it shouldn't
// be done on the main thread.
suspend fun calculateHash(stream: InputStream): String {
    return withContext(Dispatchers.IO) {
        val digest = MessageDigest.getInstance("SHA-512")
        val digestStream = DigestInputStream(stream, digest)
        while (digestStream.read() != -1) {
            // The DigestInputStream does the work; nothing for us to do.
        }
        digest.digest().joinToString(":") { "%02x".format(it) }
    }
}

لغة Java

Executor threadPoolExecutor = Executors.newFixedThreadPool(4);
private interface HashCallback {
    void onHashCalculated(@Nullable String hash);
}

boolean hashRunning = calculateHash(inputStream, threadPoolExecutor, hash -> {
    if (Objects.equals(hash, expectedHash)) {
        // Work with the content.
    }
});

if (!hashRunning) {
    // There was an error setting up the hash function.
}

private boolean calculateHash(@NonNull InputStream stream,
                              @NonNull Executor executor,
                              @NonNull HashCallback hashCallback) {
    final MessageDigest digest;
    try {
        digest = MessageDigest.getInstance("SHA-512");
    } catch (NoSuchAlgorithmException nsa) {
        return false;
    }

    // Calculating the hash code can take quite a bit of time, so it shouldn't
    // be done on the main thread.
    executor.execute(() -> {
        String hash;
        try (DigestInputStream digestStream =
                new DigestInputStream(stream, digest)) {
            while (digestStream.read() != -1) {
                // The DigestInputStream does the work; nothing for us to do.
            }
            StringBuilder builder = new StringBuilder();
            for (byte aByte : digest.digest()) {
                builder.append(String.format("%02x", aByte)).append(':');
            }
            hash = builder.substring(0, builder.length() - 1);
        } catch (IOException e) {
            hash = null;
        }

        final String calculatedHash = hash;
        runOnUiThread(() -> hashCallback.onHashCalculated(calculatedHash));
    });
    return true;
}

تخزين البيانات غير الحساسة فقط في ملفات ذاكرة التخزين المؤقت

لتوفير وصول أسرع إلى بيانات التطبيق غير الحساسة، عليك تخزينها في ذاكرة التخزين المؤقت للجهاز. بالنسبة إلى ذاكرات التخزين المؤقت التي يزيد حجمها عن 1 ميغابايت، استخدِم getExternalCacheDir(). بالنسبة إلى ذاكرات التخزين المؤقت التي يبلغ حجمها 1 ميغابايت أو أصغر، استخدِم getCacheDir(). تتيح لك كلتا الطريقتين توفير الكائن File الذي يحتوي على البيانات المخزَّنة مؤقّتًا لتطبيقك.

يوضّح مقتطف الرمز التالي كيفية تخزين ملف نزّله تطبيقك مؤخرًا في ذاكرة التخزين المؤقت:

Kotlin

val cacheFile = File(myDownloadedFileUri).let { fileToCache ->
    File(cacheDir.path, fileToCache.name)
}

لغة Java

File cacheDir = getCacheDir();
File fileToCache = new File(myDownloadedFileUri);
String fileToCacheName = fileToCache.getName();
File cacheFile = new File(cacheDir.getPath(), fileToCacheName);

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

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

معلومات ذات صلة: نظرة عامة على مساحة تخزين البيانات والملفات

استخدام الإعدادات المفضّلة المشتركة في الوضع الخاص

عند استخدام getSharedPreferences() لإنشاء كائنات SharedPreferences في تطبيقك أو الوصول إليها، استخدِم MODE_PRIVATE. بهذه الطريقة، يمكن لتطبيقك فقط الوصول إلى المعلومات داخل ملف التفضيلات المشتركة.

إذا أردت مشاركة البيانات بين التطبيقات، لا تستخدم كائنات SharedPreferences. وبدلاً من ذلك، اتّبع خطوات مشاركة البيانات بشكل آمن في جميع التطبيقات.

توفر "مكتبة الأمان" أيضًا الفئة EncryptedSharedPreferences التي تضم فئة SharedPreferences وتشفِّر المفاتيح والقيم تلقائيًا.

معلومات ذات صلة:

إبقاء الخدمات والتبعيات محدّثة

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

التحقّق من مقدِّم الأمان في "خدمات Google Play"

ملاحظة: لا ينطبق هذا القسم إلا على التطبيقات التي تستهدف الأجهزة التي تم تثبيت خدمات Google Play عليها.

إذا كان تطبيقك يستخدم "خدمات Google Play"، يُرجى التأكّد من تحديثه على الجهاز المثبّت عليه. قم بإجراء التحقق بشكل غير متزامن، خارج مؤشر ترابط واجهة المستخدم. إذا لم يكن الجهاز مُحدّثًا، فشغّل خطأ التفويض.

لمعرفة ما إذا كانت "خدمات Google Play" محدَّثة على الجهاز الذي تم تثبيت تطبيقك عليه، يمكنك اتّباع الخطوات الواردة في الدليل حول تحديث موفِّر الأمان للحماية من استغلال طبقة المقابس الآمنة.

معلومات ذات صلة:

تعديل جميع العناصر الاعتمادية للتطبيق

قبل نشر تطبيقك، تأكَّد من أنّ جميع المكتبات وحِزم تطوير البرامج (SDK) والتبعيات الأخرى محدَّثة:

  • بالنسبة إلى ملحقات الطرف الأول، مثل حزمة تطوير البرامج (SDK) لنظام التشغيل Android، استخدِم أدوات التحديث المتوفّرة في "استوديو Android"، مثل مدير SDK.
  • بالنسبة إلى البرامج التابعة لجهات خارجية، يُرجى التحقّق من المواقع الإلكترونية للمكتبات التي يستخدمها تطبيقك وتثبيت أي تحديثات ورموز تصحيح أمان متاحة.

معلومات ذات صلة: إضافة بناء التبعيات

مزيد من المعلومات

للتعرّف على المزيد من المعلومات حول كيفية تعزيز أمان تطبيقك، يمكنك الاطّلاع على الموارد التالية: