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

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

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

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

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

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

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

اختيار عميل HTTP

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

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

  • Retrofit: هو برنامج آمن من حيث النوع لتنفيذ طلبات HTTP على جهاز JVM، وهو مبني على OkHttp. تتيح لك Retrofit إنشاء واجهة عميل بشكل تعريفي، كما أنّها تتوافق مع العديد من مكتبات التسلسل.
  • Ktor: هي أداة HTTP من JetBrains، تم إنشاؤها بالكامل للغة Kotlin وتعمل باستخدام الكوروتينات. يتوافق Ktor مع محركات ومسلسلات ومنصات مختلفة.

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

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

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

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

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

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

يمكنك استخدام 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<User> 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<User> 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<User>()
    val user = _user as LiveData<User>

    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> _user = new MutableLiveData<>();
    LiveData<User> user = (LiveData<User>) _user;

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

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

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