به شبکه وصل شوید

برای انجام عملیات شبکه در برنامه خود، مانیفست شما باید مجوزهای زیر را داشته باشد:

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

بهترین شیوه‌ها برای ارتباطات امن در شبکه

قبل از اینکه قابلیت شبکه را به برنامه خود اضافه کنید، باید مطمئن شوید که داده‌ها و اطلاعات درون برنامه شما هنگام انتقال از طریق شبکه ایمن می‌مانند. برای انجام این کار، این بهترین شیوه‌های امنیت شبکه را دنبال کنید:

  • میزان داده‌های حساس یا شخصی کاربر را که از طریق شبکه منتقل می‌کنید، به حداقل برسانید.
  • تمام ترافیک شبکه را از برنامه خود از طریق SSL ارسال کنید.
  • ایجاد یک پیکربندی امنیت شبکه را در نظر بگیرید، که به برنامه شما اجازه می‌دهد به مراجع صدور گواهینامه (CA) سفارشی اعتماد کند یا مجموعه CA های سیستمی را که برای ارتباط امن به آنها اعتماد دارد، محدود کند.

برای اطلاعات بیشتر در مورد نحوه اعمال اصول شبکه امن، به نکات امنیتی شبکه مراجعه کنید.

یک کلاینت HTTP انتخاب کنید

اکثر برنامه‌های متصل به شبکه از HTTP برای ارسال و دریافت داده‌ها استفاده می‌کنند. پلتفرم اندروید شامل کلاینت HttpsURLConnection است که از TLS، آپلود و دانلودهای استریمینگ، زمان‌های انتظار قابل تنظیم، IPv6 و ادغام اتصال پشتیبانی می‌کند.

کتابخانه‌های شخص ثالث که APIهای سطح بالاتری برای عملیات شبکه ارائه می‌دهند نیز در دسترس هستند. این کتابخانه‌ها از ویژگی‌های راحتی مختلفی مانند سریال‌سازی بدنه‌های درخواست و غیر سریال‌سازی بدنه‌های پاسخ پشتیبانی می‌کنند.

  • Retrofit : یک کلاینت HTTP از نوع امن برای JVM از Square، ساخته شده بر روی OkHttp. Retrofit به شما امکان می‌دهد یک رابط کلاینت را به صورت اعلانی ایجاد کنید و از چندین کتابخانه سریال‌سازی پشتیبانی می‌کند.
  • Ktor : یک کلاینت HTTP از JetBrains، که کاملاً برای Kotlin ساخته شده و توسط Coroutineها پشتیبانی می‌شود. Ktor از موتورها، سریالایزرها و پلتفرم‌های مختلف پشتیبانی می‌کند.

حل سوالات DNS

دستگاه‌هایی که اندروید ۱۰ (سطح API ۲۹) و بالاتر را اجرا می‌کنند، از جستجوی تخصصی DNS از طریق جستجوی متن ساده و حالت DNS-over-TLS پشتیبانی داخلی دارند. API DnsResolver ، وضوح عمومی و غیرهمزمان را ارائه می‌دهد که به شما امکان می‌دهد SRV ، NAPTR و سایر انواع رکورد را جستجو کنید. تجزیه پاسخ به برنامه واگذار شده است تا انجام شود.

در دستگاه‌هایی که اندروید ۹ (سطح API 28) و پایین‌تر را اجرا می‌کنند، تحلیلگر DNS پلتفرم فقط از رکوردهای A و AAAA پشتیبانی می‌کند. این به شما امکان می‌دهد آدرس‌های IP مرتبط با یک نام را جستجو کنید اما از هیچ نوع رکورد دیگری پشتیبانی نمی‌کند.

برای برنامه‌های مبتنی بر NDK، به android_res_nsend مراجعه کنید.

کپسوله‌سازی عملیات شبکه با یک مخزن

برای ساده‌سازی فرآیند انجام عملیات شبکه و کاهش تکرار کد در بخش‌های مختلف برنامه خود، می‌توانید از الگوی طراحی مخزن (repository) استفاده کنید. مخزن (repository) کلاسی است که عملیات داده را مدیریت می‌کند و یک انتزاع API تمیز را برای برخی از داده‌ها یا منابع خاص ارائه می‌دهد.

شما می‌توانید از Retrofit برای تعریف یک رابط که متد HTTP، URL، آرگومان‌ها و نوع پاسخ را برای عملیات شبکه مشخص می‌کند، استفاده کنید، مانند مثال زیر:

کاتلین

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

جاوا

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

درون یک کلاس مخزن، توابع می‌توانند عملیات شبکه را کپسوله‌سازی کرده و نتایج آنها را نمایش دهند. این کپسوله‌سازی تضمین می‌کند که کامپوننت‌هایی که مخزن را فراخوانی می‌کنند نیازی به دانستن نحوه ذخیره داده‌ها ندارند. هرگونه تغییر در آینده در نحوه ذخیره داده‌ها نیز به کلاس مخزن ایزوله می‌شود. به عنوان مثال، ممکن است یک تغییر از راه دور مانند به‌روزرسانی در نقاط انتهایی API داشته باشید، یا ممکن است بخواهید ذخیره‌سازی محلی را پیاده‌سازی کنید.

کاتلین

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

جاوا

class UserRepository {
    private UserService userService;

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

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

برای جلوگیری از ایجاد یک رابط کاربری بدون پاسخ، عملیات شبکه را روی نخ اصلی انجام ندهید. به طور پیش‌فرض، اندروید از شما می‌خواهد که عملیات شبکه را روی نخی غیر از نخ اصلی رابط کاربری انجام دهید. اگر سعی کنید عملیات شبکه را روی نخ اصلی انجام دهید، خطای NetworkOnMainThreadException رخ می‌دهد.

در مثال کد قبلی، عملیات شبکه در واقع آغاز نمی‌شود. فراخوانی‌کننده‌ی UserRepository باید threading را یا با استفاده از coroutineها یا با استفاده از تابع enqueue() پیاده‌سازی کند. برای اطلاعات بیشتر، به codelab Get data from the internet مراجعه کنید که نحوه‌ی پیاده‌سازی threading را با استفاده از coroutineهای کاتلین نشان می‌دهد.

زنده ماندن در برابر تغییرات پیکربندی

وقتی تغییری در پیکربندی رخ می‌دهد، مانند چرخش صفحه، فرگمنت یا اکتیویتی شما از بین می‌رود و دوباره ایجاد می‌شود. هر داده‌ای که در حالت نمونه برای اکتیویتی فرگمنت شما ذخیره نشده باشد، که فقط می‌تواند مقادیر کمی از داده‌ها را در خود جای دهد، از بین می‌رود. در این صورت، ممکن است لازم باشد درخواست‌های شبکه خود را دوباره انجام دهید.

شما می‌توانید از یک ViewModel برای حفظ داده‌های خود در برابر تغییرات پیکربندی استفاده کنید. کامپوننت ViewModel برای ذخیره و مدیریت داده‌های مرتبط با رابط کاربری به روشی آگاهانه از چرخه عمر طراحی شده است. با استفاده از UserRepository قبلی، ViewModel می‌تواند درخواست‌های شبکه لازم را انجام دهد و نتیجه را با استفاده از LiveData به fragment یا activity شما ارائه دهد:

کاتلین

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
            }

        }
    }
}

جاوا

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
            }
        });
    }
}

برای آشنایی بیشتر با این موضوع، به راهنماهای مرتبط زیر مراجعه کنید: