الاتصال بالشبكة

لتنفيذ عمليات الشبكة في تطبيقك، يجب أن يتضمّن البيان الأذونات التالية:

 android:name="android.permission.INTERNET" />
 android:name="android.permission.ACCESS_NETWORK_STATE" />

أفضل الممارسات لضمان أمان الاتصالات على الشبكة

قبل إضافة وظيفة التواصل مع الشبكة إلى تطبيقك، عليك التأكّد من أمان البيانات والمعلومات داخل تطبيقك عند نقلها عبر الشبكة. اتّبِع أفضل الممارسات التالية لأمان الشبكات:

  • الحدّ من مقدار بيانات المستخدِم الحساسة أو الشخصية التي تُرسِلها عبر الشبكة
  • أرسِل جميع زيارات الشبكة من تطبيقك عبر بروتوكول SSL.
  • ننصحك بإنشاء إعدادات أمان الشبكة التي تسمح لتطبيقك بالثقة في مراجع التصديق المخصّصة أو تقييد مجموعة مراجع تصديق النظام التي يثق بها للتواصل الآمن.

لمزيد من المعلومات حول كيفية تطبيق مبادئ الشبكات الآمنة، يُرجى الاطّلاع على نصائح أمان الشبكات.

اختيار برنامج HTTP

تستخدم معظم التطبيقات المتصلة بالشبكة بروتوكول HTTP لإرسال البيانات واستلامها. يتضمّن منصّة Android برنامج العميل HttpsURLConnection، الذي يتيح استخدام بروتوكول TLS وعمليات التحميل والتنزيل عبر البث ووقت الاستراحة القابل للضبط وIPv6 وميزة تجميع الاتصالات.

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

  • Retrofit: مكتبة برمجية لاستخدام بروتوكول HTTP بأمان في Java Virtual Machine من Square، وهي مبنية على OkHttp. يتيح لك Retrofit إنشاء واجهة عملاء بشكل تعريفي، كما يتيح استخدام عدة مكتبات لتسلسل البيانات.
  • Ktor: هو عميل HTTP من JetBrains تم إنشاؤه بالكامل بلغة Kotlin ويستند إلى الكوروتينات. يتوافق Ktor مع العديد من المحرّكات و المشفّرات والمنصات.

حلّ طلبات البحث في نظام أسماء النطاقات

تتوفّر في الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android (المستوى 29 من واجهة برمجة التطبيقات) والإصدارات الأحدث ميزة مدمجة لبحث عناوين DNS المتخصصة من خلال عمليات البحث النصية الواضحة ووضع DNS-over-TLS. توفّر واجهة برمجة التطبيقات DnsResolver حلًا عامًا غير متزامن، ما يتيح لك البحث عن SRV وNAPTR وأنواع السجلّات الأخرى. ويُترك للتطبيق تحليل الردّ.

على الأجهزة التي تعمل بالإصدار 9 من نظام التشغيل Android (المستوى 28 لواجهة برمجة التطبيقات) والإصدارات الأقدم، لا يتيح "محوِّل عنوان DNS" في النظام الأساسي سوى سجلَّي A وAAAA. يتيح لك ذلك البحث عن عناوين IP المرتبطة باسم معيّن، ولكنه لا يتيح استخدام أي أنواع أخرى من السجلّات.

بالنسبة إلى التطبيقات المستندة إلى NDK، يُرجى الاطّلاع على android_res_nsend.

تجميع عمليات الشبكة في مستودع

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

يمكنك استخدام Retrofit لتعريف واجهة تحدّد طريقة HTTP و عنوان URL والمَعلمات ونوع الاستجابة لعمليات الشبكة، كما هو موضّح في المثال التالي:

Kotlin

interface UserService {
    @GET("/users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}

Java

public interface UserService {
    @GET("/user/{id}")
    Call getUserById(@Path("id") String id);
}

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

Kotlin

class UserRepository constructor(
    private val userService: UserService
) {
    suspend fun getUserById(id: String): User {
        return userService.getUser(id)
    }
}

Java

class UserRepository {
    private UserService userService;

    public UserRepository(
            UserService userService
    ) {
        this.userService = userService;
    }

    public Call getUserById(String id) {
        return userService.getUser(id);
    }
}

لتجنُّب إنشاء واجهة مستخدم لا تستجيب، لا تُجري عمليات على الشبكة في سلسلت الرسائل الرئيسية. يطلب منك Android تلقائيًا تنفيذ عمليات الشبكة في سلسلت تعليمات برمجية مختلفة عن سلسلة تعليمات واجهة المستخدم الرئيسية. إذا حاولت تنفيذ عمليات على الشبكة في سلسلة التعليمات الرئيسية، سيتم طرح سوى NetworkOnMainThreadException.

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

الحفاظ على الأداء في حال إجراء تغييرات على الإعدادات

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

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

Kotlin

class MainViewModel constructor(
    savedStateHandle: SavedStateHandle,
    userRepository: UserRepository
) : ViewModel() {
    private val userId: String = savedStateHandle["uid"] ?:
        throw IllegalArgumentException("Missing user ID")

    private val _user = MutableLiveData()
    val user = _user as LiveData

    init {
        viewModelScope.launch {
            try {
                // Calling the repository is safe as it moves execution off
                // the main thread
                val user = userRepository.getUserById(userId)
                _user.value = user
            } catch (error: Exception) {
                // Show error message to user
            }

        }
    }
}

Java

class MainViewModel extends ViewModel {

    private final MutableLiveData _user = new MutableLiveData<>();
    LiveData user = (LiveData) _user;

    public MainViewModel(
            SavedStateHandle savedStateHandle,
            UserRepository userRepository
    ) {
        String userId = savedStateHandle.get("uid");
        Call userCall = userRepository.getUserById(userId);
        userCall.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                if (response.isSuccessful()) {
                    _user.setValue(response.body());
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                // Show error message to user
            }
        });
    }
}

لمزيد من المعلومات حول هذا الموضوع، يمكنك الاطّلاع على الأدلة ذات الصلة التالية: