الأذونات وأفضل الممارسات
تعمل كل تطبيقات Android داخل عملية Linux معزولة (sandbox). لا يمكنها قراءة ملفات تطبيق آخر، ولا فتح مقبس شبكي، ولا الوصول إلى الكاميرا دون إعلان مسبق، وفي حالة الأذونات الخطرة دون طلب موافقة صريحة من المستخدم أثناء التشغيل. يتناول هذا الدرس دورة حياة الأذونات بالكامل وأنماط التعامل الآمن مع البيانات التي تُميّز التطبيقات الاحترافية عن المشاريع التجريبية.
مستويان من الأذونات
يُقسّم Android الأذونات إلى فئتين رئيسيتين:
- الأذونات العادية — صلاحيات منخفضة الخطورة (مثل
INTERNET وACCESS_NETWORK_STATE). يمنحها النظام تلقائيًا عند التثبيت؛ ما عليك سوى التصريح بها في AndroidManifest.xml.
- الأذونات الخطرة — وصول إلى بيانات المستخدم الحساسة أو الأجهزة (مثل
READ_CONTACTS وCAMERA وACCESS_FINE_LOCATION). يجب التصريح بها وطلبها أثناء التشغيل، ويمكن للمستخدم سحبها في أي وقت من الإعدادات.
صرّح دائمًا بكل إذن تستخدمه في الملف المانيفست، حتى العادي منها. حذف التصريح في الملف المانيفست يتسبب فورًا في SecurityException أثناء التشغيل بغض النظر عما إذا كان المستخدم قد منحه أم لا.
التصريح بالأذونات في الملف المانيفست
افتح AndroidManifest.xml وأضف وسوم <uses-permission> كأبناء مباشرين لعنصر الجذر <manifest>:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<!-- عادي: يُمنح عند التثبيت، بدون نافذة حوار -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- خطر: يجب طلبه أيضًا أثناء التشغيل -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application ...>
...
</application>
</manifest>
تدفق الأذونات أثناء التشغيل
بالنسبة للأذونات الخطرة يكون النمط دائمًا: فحص ← طلب ← معالجة النتيجة. لا تفترض أبدًا أن الإذن ممنوح بين إطلاقات التطبيق — يمكن للمستخدم سحبه في أي لحظة.
الطريقة الحديثة لمعالجة ذلك هي واجهة Activity Result API (ActivityResultLauncher)، المتاحة من androidx.activity:activity:1.2+. تحلّ محل دالة الاستدعاء القديمة onRequestPermissionsResult وتُزيل الكود المتكرر.
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
public class ContactsActivity extends AppCompatActivity {
// سجّل المُطلق قبل onCreate (تعريف على مستوى الحقل)
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
isGranted -> {
if (isGranted) {
loadContacts(); // وافق المستخدم
} else {
showPermissionRationale(); // رفض المستخدم
}
}
);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts);
checkAndRequestContactsPermission();
}
private void checkAndRequestContactsPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED) {
loadContacts(); // الإذن ممنوح مسبقًا
} else if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
// رفض المستخدم سابقًا؛ اشرح السبب قبل طلب الإذن مجددًا
showRationaleDialog();
} else {
requestPermissionLauncher.launch(Manifest.permission.READ_CONTACTS);
}
}
private void loadContacts() {
Toast.makeText(this, "جارٍ تحميل جهات الاتصال...", Toast.LENGTH_SHORT).show();
// ... استعلام ContentResolver
}
private void showPermissionRationale() {
Toast.makeText(this,
"إذن جهات الاتصال ضروري لعرض اتصالاتك.",
Toast.LENGTH_LONG).show();
}
private void showRationaleDialog() {
// اعرض AlertDialog يشرح الحاجة ثم أطلق الطلب عند التأكيد
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("إذن مطلوب")
.setMessage("يقرأ هذا التطبيق جهات اتصالك لاقتراح اتصالات.")
.setPositiveButton("موافق", (d, w) ->
requestPermissionLauncher.launch(Manifest.permission.READ_CONTACTS))
.setNegativeButton("إلغاء", null)
.show();
}
}
تحقق دائمًا من shouldShowRequestPermissionRationale(). إذا أعادت true فقد رفض المستخدم الطلب مرة من قبل — اعرض شرحًا سياقيًا قبل الطلب مجددًا. إطلاق نافذة حوار النظام للمرة الثانية دون شرح هو خطأ شائع في تجربة المستخدم يؤدي إلى رفض دائم.
طلب أذونات متعددة دفعةً واحدة
عندما تحتاج ميزتك إلى عدة أذونات خطرة في آنٍ واحد (مثل CAMERA وRECORD_AUDIO لمكالمة فيديو)، استخدم RequestMultiplePermissions:
private final ActivityResultLauncher<String[]> multiPermissionLauncher =
registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
results -> {
Boolean cameraGranted = results.getOrDefault(Manifest.permission.CAMERA, false);
Boolean audioGranted = results.getOrDefault(Manifest.permission.RECORD_AUDIO, false);
if (Boolean.TRUE.equals(cameraGranted) && Boolean.TRUE.equals(audioGranted)) {
startVideoCall();
} else {
Toast.makeText(this,
"الكاميرا والميكروفون ضروريان لمكالمات الفيديو.",
Toast.LENGTH_LONG).show();
}
}
);
// تشغيله:
multiPermissionLauncher.launch(new String[]{
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
});
التدهور اللطيف — التصميم للرفض
يجب أن يظل تطبيقك قابلًا للاستخدام حين يُرفض الإذن. صمّم كل ميزة مع بديل:
- ميزة الخرائط بدون
ACCESS_FINE_LOCATION يجب أن تتيح للمستخدم إدخال عنوان يدويًا بدلًا من التحديد التلقائي للموقع.
- ميزة استيراد جهات الاتصال بدون
READ_CONTACTS يجب أن تعرض نموذج إدخال يدوي.
- لا تستدع الواجهة البرمجية التي تتطلب الإذن دون فحص مسبق — سيؤدي
SecurityException إلى تعطل تطبيقك.
لا تطلب أذونات لا تحتاجها. طلب أذونات تتجاوز احتياجات ميزتك الفعلية يُضر بثقة المستخدم وقد يُؤدي إلى رفض تطبيقك في مراجعة سياسة الأذونات بمتجر Play. راجع ملفك المانيفست قبل كل إصدار.
التعامل الآمن مع البيانات على الجهاز
تحمي الأذونات الوصول بين التطبيقات، لكنك تحتاج أيضًا إلى حماية البيانات داخل تطبيقك. ثلاثة مبادئ:
1. لا تخزّن الأسرار في SharedPreferences
ملفات XML الخاصة بـ SharedPreferences تقع في /data/data/<package>/shared_prefs/ وهي قابلة للقراءة بواسطة المستخدم الجذر (root) وأي عملية تستغل ثغرة أمنية. لا تخزّن كلمات المرور أو مفاتيح API أو رموز المصادقة هناك كنص عادي. استخدم بدلًا من ذلك مكتبة Jetpack Security (EncryptedSharedPreferences) التي تُغلّف مفتاح AES-256 رئيسيًا مخزّنًا في Android Keystore:
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs", // اسم الملف
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// يُستخدم تمامًا مثل SharedPreferences العادية
securePrefs.edit().putString("auth_token", token).apply();
String token = securePrefs.getString("auth_token", null);
2. تجنّب تسجيل البيانات الحساسة
سجل logcat الخاص بـ Android قابل للقراءة من أي تطبيق يملك READ_LOGS (ومن أي شخص يملك وصول USB). نقّ القيم الحساسة من استدعاءات السجل قبل الإصدار:
// خطأ — الرمز المميز مرئي في logcat
Log.d("Auth", "User token: " + authToken);
// صواب — احجبه بـ BuildConfig
if (BuildConfig.DEBUG) {
Log.d("Auth", "Token acquired (length=" + authToken.length() + ")");
}
فكّر في استخدام قواعد ProGuard/R8 لحذف جميع استدعاءات Log.d وLog.v من إصدار الإنتاج كليًا.
3. استخدم HTTPS والتحقق من الشهادات
منذ Android 9 (API 28)، حركة مرور HTTP غير المشفّرة محجوبة افتراضيًا. يجب أن تستخدم جميع استدعاءات الشبكة HTTPS. إذا احتجت للاتصال بخادم يملك شهادة موقّعة ذاتيًا أثناء الاختبار، هيّئ ملف network_security_config.xml بدلًا من تعطيل جميع فحوصات الشهادات — فتعطيلها الكامل هو عيب أمني خطير:
<!-- res/xml/network_security_config.xml (لإصدارات DEBUG فقط) -->
<network-security-config>
<debug-overrides>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
<!-- في AndroidManifest.xml، وسم APPLICATION فقط -->
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
في الإنتاج، لا تُلغِ التحقق من الشهادات أبدًا. ثق فقط بهيئة إصدار الشهادات الخاصة بك، أو استخدم تثبيت الشهادة (certificate pinning) عبر CertificatePinner في OkHttp للنقاط النهائية عالية القيمة كالمصادقة والدفع.
الخلاصة
نظام الأذونات في Android عملية من خطوتين: التصريح في الملف المانيفست، ثم الفحص والطلب أثناء التشغيل للأذونات الخطرة. استخدم واجهة ActivityResultLauncher API للحصول على تدفق طلب نظيف آمن من دورة الحياة. تعامل دائمًا مع الرفض بلطف — يجب أن يتدهور تطبيقك ولا ينهار. احمِ البيانات الموجودة على الجهاز باستخدام EncryptedSharedPreferences للأسرار، وأبقِ سجلاتك نظيفة في إصدارات الإنتاج، وفرض HTTPS في كل مكان. هذه الممارسات ليست إضافات اختيارية؛ فهي شرط لنشر التطبيق في متجر Play ولكسب ثقة المستخدم.