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

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

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

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

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

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

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

اختيار عميل HTTP

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

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

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

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

تتضمّن الأجهزة التي تعمل بنظام التشغيل Android 10 (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث دعمًا مضمّنًا لعمليات بحث نظام أسماء النطاقات المتخصّصة من خلال عمليات بحث النص الواضح ووضع "نظام أسماء النطاقات عبر بروتوكول أمان طبقة النقل". توفّر واجهة برمجة تطبيقات 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
            }
        });
    }
}

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