เชื่อมต่อกับเครือข่าย

หากต้องการดำเนินการกับเครือข่ายในแอปพลิเคชันของคุณ ไฟล์ Manifest ต้องมีสิทธิ์ต่อไปนี้

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

แนวทางปฏิบัติแนะนำสำหรับการสื่อสารในเครือข่ายที่ปลอดภัย

ก่อนที่จะเพิ่มฟังก์ชันการทํางานของเครือข่ายลงในแอป คุณต้องตรวจสอบว่าข้อมูลภายในแอปจะปลอดภัยเมื่อส่งผ่านเครือข่าย โดยทำตามแนวทางปฏิบัติแนะนำด้านความปลอดภัยของเครือข่ายต่อไปนี้

  • ลดปริมาณข้อมูลผู้ใช้ที่ละเอียดอ่อนหรือเป็นส่วนตัวซึ่งคุณส่งผ่านเครือข่าย
  • ส่งการจราจรของข้อมูลในเครือข่ายทั้งหมดจากแอปผ่าน SSL
  • ลองสร้างการกำหนดค่าความปลอดภัยของเครือข่าย ซึ่งจะช่วยให้แอปของคุณเชื่อถือผู้ออกใบรับรอง (CA) ที่กำหนดเอง หรือจำกัดชุด CA ของระบบที่แอปเชื่อถือสำหรับการสื่อสารที่ปลอดภัย

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้หลักการของเครือข่ายที่ปลอดภัยได้ที่เคล็ดลับด้านความปลอดภัยของเครือข่าย

เลือกไคลเอ็นต์ HTTP

แอปที่เชื่อมต่อเครือข่ายส่วนใหญ่ใช้ HTTP เพื่อส่งและรับข้อมูล แพลตฟอร์ม Android มีไคลเอ็นต์ HttpsURLConnection ซึ่งรองรับ TLS, การอัปโหลดและการดาวน์โหลดสตรีมมิง, การหมดเวลาที่กำหนดค่าได้, IPv6 และการเชื่อมต่อพูล

นอกจากนี้ยังมีไลบรารีของบุคคลที่สามที่มี API ระดับสูงกว่าสำหรับการดำเนินการสร้างเครือข่ายให้ใช้งานอีกด้วย ซึ่งรองรับฟีเจอร์ต่างๆ ที่อำนวยความสะดวก เช่น การจัดรูปแบบข้อมูลของเนื้อหาคำขอและการถอดรูปแบบข้อมูลของเนื้อหาคำตอบ

  • Retrofit: ไคลเอ็นต์ HTTP ที่ปลอดภัยต่อประเภทสําหรับ JVM จาก Square ซึ่งสร้างขึ้นจาก OkHttp Retrofit ช่วยให้คุณสร้างอินเทอร์เฟซไคลเอ็นต์แบบประกาศได้ และรองรับไลบรารีการแปลงเป็นอนุกรมหลายรายการ
  • Ktor: ไคลเอ็นต์ HTTP จาก JetBrains ที่สร้างขึ้นเพื่อ Kotlin โดยเฉพาะและขับเคลื่อนโดย Coroutines Ktor รองรับเครื่องมือ ซีเรียลไลเซอร์ และแพลตฟอร์มต่างๆ

แก้ปัญหาการค้นหา DNS

อุปกรณ์ที่ใช้ Android 10 (API ระดับ 29) ขึ้นไปจะรองรับการค้นหา DNS แบบเฉพาะทางในตัวผ่านทั้งการค้นหาแบบข้อความที่อ่านได้และโหมด DNS-over-TLS DnsResolver API ให้ความละเอียดแบบทั่วไปแบบไม่พร้อมกัน ซึ่งช่วยให้คุณค้นหา SRV, NAPTR และประเภทระเบียนอื่นๆ ได้ การสํารวจคําตอบจะขึ้นอยู่กับแอป

ในอุปกรณ์ที่ใช้ Android 9 (API ระดับ 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);
}

ภายในคลาสที่เก็บข้อมูล ฟังก์ชันสามารถรวมการดำเนินการของเครือข่ายและแสดงผลลัพธ์ การรวมช่องนี้ช่วยให้มั่นใจว่าคอมโพเนนต์ที่เรียกใช้ที่เก็บข้อมูลไม่จำเป็นต้องทราบวิธีจัดเก็บข้อมูล เมื่อเปลี่ยนแปลงวิธีการจัดเก็บข้อมูลในอนาคต ระบบจะแยกข้อมูลดังกล่าวไปยังคลาสที่เก็บด้วย เช่น คุณอาจมีการเปลี่ยนแปลงจากระยะไกล เช่น การอัปเดตปลายทาง API หรือต้องการใช้การแคชในเครื่อง

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

อย่าดำเนินการของเครือข่ายในเทรดหลักเพื่อหลีกเลี่ยงการสร้าง UI ที่ไม่ตอบสนอง โดยค่าเริ่มต้น Android จะกำหนดให้คุณดำเนินการเกี่ยวกับเครือข่ายในเทรดอื่นที่ไม่ใช่เทรด UI หลัก หากคุณพยายามดําเนินการเครือข่ายในเทรดหลัก ระบบจะแสดงข้อผิดพลาด NetworkOnMainThreadException

ในตัวอย่างโค้ดก่อนหน้า ระบบไม่ได้ทริกเกอร์การดำเนินการของเครือข่าย ผู้เรียกใช้ UserRepository ต้องใช้การแยกชุดข้อความโดยใช้ coroutine หรือใช้ฟังก์ชัน enqueue() ดูข้อมูลเพิ่มเติมได้ที่โค้ดแล็บรับข้อมูลจากอินเทอร์เน็ต ซึ่งสาธิตวิธีใช้การแยกชุดข้อความโดยใช้โคโริวทีนของ Kotlin

การดำเนินการเปลี่ยนแปลงการกำหนดค่า

เมื่อเกิดการเปลี่ยนแปลงการกําหนดค่า เช่น การหมุนหน้าจอ ระบบจะทำลายและสร้างข้อมูลโค้ดโค้ดหรือกิจกรรมขึ้นมาใหม่ ข้อมูลที่ไม่ได้บันทึกไว้ในสถานะอินสแตนซ์สําหรับกิจกรรมของข้อมูลโค้ดที่ฝัง ซึ่งเก็บข้อมูลได้เพียงจํานวนเล็กน้อยจะสูญหาย ในกรณีนี้ คุณอาจต้องส่งคำขอเครือข่ายอีกครั้ง

คุณสามารถใช้ ViewModel เพื่อให้ข้อมูลอยู่รอดได้แม้จะมีการเปลี่ยนแปลงการกําหนดค่า คอมโพเนนต์ ViewModel ได้รับการออกแบบมาเพื่อจัดเก็บและจัดการข้อมูลที่เกี่ยวข้องกับ UI ในลักษณะที่คำนึงถึงอายุการใช้งาน เมื่อใช้ 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
            }
        });
    }
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้ได้ในคู่มือที่เกี่ยวข้องต่อไปนี้