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

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

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

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

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

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

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

اختيار عميل HTTP

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

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

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

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

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

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

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

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

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

يمكنك استخدام التسوية للإعلان عن واجهة تحدد طريقة 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
            }
        });
    }
}

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