Thực hành tốt nhất trên Android Kotlin Coroutine

Đây là một tập hợp duy trì liên tục các thực tiễn tốt nhất để sử dụng Kotlin Coroutines trên Android. Vui lòng bình luận dưới đây nếu bạn có bất kỳ đề xuất về bất cứ điều gì cần được thêm vào.

  1. Xử lý vòng đời Android

Theo cách tương tự như bạn sử dụng CompositeDisposeables với RxJava, Kotlin Coroutines phải bị hủy vào đúng thời điểm với nhận thức về Android Liveciking với các hoạt động và phân đoạn.

a) Sử dụng Chế độ xem Android

Đây là cách dễ nhất để thiết lập coroutines vì ​​vậy họ đang đóng xuống vào đúng thời điểm, nhưng nó chỉ hoạt động bên trong một ViewModel Android trong đó có một chức năng onCleared rằng công việc coroutine có thể được tin cậy hủy từ:

chế độ xem riêng tưModelJob = Công việc ()
private val uiScope = CoroutineScope (Công văn.Main + viewModelJob)
ghi đè lên niềm vui onCleared () {
 super.onCleared ()
 uiScope.coroutineContext.celonChildren ()
}

Lưu ý: kể từ ViewModels 2.1.0-alpha01, điều này không còn cần thiết nữa. Bạn không còn phải dùng viewmodel của mình để triển khai CoroutineScope, onCleared hoặc thêm một công việc. Chỉ cần sử dụng view viewModelscope.launch {}. Lưu ý rằng 2.x có nghĩa là ứng dụng của bạn sẽ cần phải có trên AndroidX vì tôi không chắc chắn họ có kế hoạch chuyển dữ liệu này sang phiên bản 1.x của ViewModels.

b) Sử dụng Quan sát vòng đời

Kỹ thuật khác này tạo ra một phạm vi mà bạn đính kèm vào một hoạt động hoặc đoạn (hoặc bất kỳ thứ gì khác thực hiện Vòng đời của Android):

/ **
 * Bối cảnh Coroutine tự động bị hủy khi UI bị hủy
 * /
lớp UiLifecyclScope: CoroutineScope, LifecyclObserver {

    công việc cuối cùng varinit var: công việc
    ghi đè val coroutineContext: CoroutineContext
        get () = job + Công văn. Chính

    @OnLifecyclEvent (Vòng đời.Event.ON_START)
    vui vẻ onCreate () {
        công việc = công việc ()
    }

    @OnLifecyclEvent (Vòng đời.Event.ON_PAUSE)
    vui hủy () = job.cattery ()
}
... Bên trong Hỗ trợ Hoạt động Lib hoặc Đoạn
private val uiScope = UiLifecyclScope ()
ghi đè lên niềm vui onCreate (yetInstanceState: bundle) {
  super.onCreate (yetInstanceState)
  lifecycl.addObserver (uiScope)
}

c) Toàn cầu

Nếu bạn sử dụng GlobalScope, thì đó là một phạm vi kéo dài tuổi thọ của ứng dụng. Bạn sẽ sử dụng điều này để thực hiện đồng bộ hóa nền, làm mới repo, v.v. (không gắn với vòng đời Activity).

d) Dịch vụ

Các dịch vụ có thể hủy công việc của họ trong onDestroy:

dịch vụ val riêngJob = Công việc ()
private val serviceScope = CoroutineScope (Dispatchers.Main + serviceJob)
ghi đè lên niềm vui onCleared () {
 super.onCleared ()
 serviceJob.celon ()
}

2. Xử lý ngoại lệ

a) Trong async so với launch vs runBlocking

Điều quan trọng cần lưu ý là các ngoại lệ trong khối {} khởi chạy sẽ làm sập ứng dụng mà không có trình xử lý ngoại lệ. Luôn thiết lập trình xử lý ngoại lệ mặc định để truyền dưới dạng tham số để khởi chạy.

Một ngoại lệ trong khối {} runBlocking sẽ làm sập ứng dụng trừ khi bạn thêm một lần thử. Luôn thêm thử / bắt nếu bạn sử dụng runBlocking. Lý tưởng nhất, chỉ sử dụng runBlocking cho các bài kiểm tra đơn vị.

Một ngoại lệ được ném trong khối async {} sẽ không lan truyền hoặc chạy cho đến khi khối được chờ đợi vì nó thực sự là một Java hoãn lại bên dưới. Hàm / phương thức gọi sẽ bắt ngoại lệ.

b) Bắt ngoại lệ

Nếu bạn sử dụng async mã chạy mà có thể ném ngoại lệ, bạn phải quấn mã trong một coroutineScope ngoại lệ bắt đúng cách (nhờ LouisC ví dụ):

thử {
    coroutineScope {
        val mayFailAsync1 = async {
            mayFail1 ()
        }
        val mayFailAsync2 = async {
            mayFail2 ()
        }
        useResult (mayFailAsync1.await (), mayFailAsync2.await ())
    }
} bắt (e: IOException) {
    // xử lý việc này
    ném MyIoException ("Lỗi khi làm IO", e)
} Catch (e: AnotherException) {
    // xử lý việc này quá
    ném MyOtherException ("Lỗi khi làm gì đó", e)
}

Khi bạn bắt ngoại lệ, bọc nó trong một ngoại lệ (tương tự như những gì bạn làm cho RxJava) để bạn có được dòng stacktrace trong mã của riêng bạn thay vì nhìn thấy một stacktrace với chỉ đang coroutine.

c) Ghi nhật ký ngoại lệ

Nếu sử dụng GlobalScope.launch hoặc một diễn viên, luôn vượt qua trong một trình xử lý ngoại lệ có thể ghi lại các ngoại lệ. Ví dụ.

val errorHandler = CoroutineExceptionHandler {_, ngoại lệ ->
  // đăng nhập vào Crashlytics, logcat, v.v.
}
val job = GlobalScope.launch (errorHandler) {
...
}

Hầu như luôn luôn, bạn nên sử dụng phạm vi có cấu trúc trên Android và nên sử dụng trình xử lý:

val errorHandler = CoroutineExceptionHandler {_, ngoại lệ ->
  // đăng nhập vào Crashlytics, logcat, v.v.; có thể được phụ thuộc tiêm
}
val giám sát = Người giám sát () // đã hủy w / Vòng đời hoạt động
với (CoroutineScope (coroutineContext + giám sát viên)) {
  val cái gì đó = khởi chạy (errorHandler) {
    ...
  }
}

Và nếu bạn sử dụng async và chờ đợi, hãy luôn thử / bắt như mô tả ở trên, nhưng đăng nhập khi cần.

d) Xem xét kết quả / Lỗi niêm phong lớp

Xem xét sử dụng một lớp niêm phong kết quả có thể giữ một lỗi thay vì ném ngoại lệ:

lớp niêm phong Kết quả  {
  lớp dữ liệu Thành công (dữ liệu val: T): Kết quả ()
  Lỗi lớp dữ liệu (lỗi val: E): Kết quả ()
}

e) Tên bối cảnh Coroutine

Khi khai báo lambda async, bạn cũng có thể đặt tên như vậy:

async (CoroutineName ("MyCoroutine")) {}

Nếu bạn đang tạo chủ đề của riêng mình để chạy, bạn cũng có thể đặt tên cho nó khi tạo trình thực thi luồng này:

newSingleThreadContext ("MyCoroutineThread")

3. Nhóm điều hành và kích thước nhóm mặc định

Coroutines thực sự là đa nhiệm hợp tác (có hỗ trợ trình biên dịch) trên kích thước nhóm luồng hạn chế. Điều đó có nghĩa là nếu bạn thực hiện việc chặn thứ gì đó trong coroutine của mình (ví dụ: sử dụng API chặn), bạn sẽ buộc toàn bộ chuỗi cho đến khi hoàn thành thao tác chặn. Coroutine cũng sẽ không bị đình chỉ trừ khi bạn thực hiện một sản lượng hoặc trì hoãn, vì vậy nếu bạn có một vòng xử lý dài, hãy chắc chắn kiểm tra xem coroutine đã bị hủy hay chưa (hãy gọi cho Đảm bảoActive () trên phạm vi) để bạn có thể giải phóng các chủ đề; điều này tương tự như cách RxJava hoạt động.

Các coroutines Kotlin có một vài người điều phối được xây dựng (tương đương với lịch trình trong RxJava). Bộ điều phối chính (nếu bạn không chỉ định bất cứ thứ gì để chạy) là giao diện người dùng; bạn chỉ nên thay đổi các thành phần UI trong ngữ cảnh này. Ngoài ra còn có một Dispatchers.Uninedined có thể nhảy giữa UI và các chủ đề nền để nó không phải là một chủ đề; điều này thường không nên được sử dụng ngoại trừ trong các bài kiểm tra đơn vị. Có một bộ xử lý.IO để xử lý IO (các cuộc gọi mạng thường xuyên bị đình chỉ). Cuối cùng, có một Dispatchers.Default là nhóm luồng nền chính nhưng điều này bị giới hạn ở số lượng CPU.

Trong thực tế, bạn nên sử dụng một giao diện cho các trình điều phối phổ biến được truyền qua thông qua trình xây dựng lớp lớp của bạn để bạn có thể trao đổi các giao diện khác nhau để kiểm tra. Ví dụ.:

giao diện CoroutineDispatchers {
  UI UI: Người điều phối
  val IO: Công văn
  val Tính toán: Công văn
  vui vẻ newThread (tên val: String): Bộ điều phối
}

4. Tránh tham nhũng dữ liệu

Không có chức năng treo sửa đổi dữ liệu bên ngoài chức năng. Ví dụ, điều này có thể có sửa đổi dữ liệu ngoài ý muốn nếu hai phương thức được chạy từ các luồng khác nhau:

danh sách val = mutableListOf (1, 2)
tạm dừng cập nhật thú vịList1 () {
  danh sách [0] = danh sách [0] + 1
}
tạm dừng cập nhật thú vịList2 () {
  list.clear ()
}

Bạn có thể tránh loại vấn đề này bằng cách:
- có các coroutines của bạn trả lại một đối tượng bất biến thay vì tiếp cận và thay đổi một
- chạy tất cả các coroutines này trong một ngữ cảnh có luồng đơn mà mà Google tạo ra thông qua: newSingleThreadContext (Ngữ cảnh tên vụ

5. Làm cho hạnh phúc

Những quy tắc này cần được thêm vào để phát hành bản dựng ứng dụng của bạn:

-keepnames lớp kotlinx.coroutines.iternal.MainDispatcherFactory {}
-keepnames lớp kotlinx.coroutines.CoroutineExceptionHandler {}
-keep classmembernames lớp kotlinx. ** {dễ bay hơi ; }

6. Tương tác với Java

Nếu bạn làm việc trên một ứng dụng cũ, bạn chắc chắn sẽ có một đoạn mã Java đáng kể. Bạn có thể gọi coroutines từ Java bằng cách trả về CompleteableFuture (hãy chắc chắn bao gồm tạo phẩm kotlinx-coroutines-jdk8):

doS SomethingAsync (): CompleteableFuture > =
   GlobalScope.future {doS Something ()}

7. Trang bị thêm Don Cần Cần với Nội dung

Nếu bạn sử dụng bộ điều hợp Retrofit coroutines, bạn sẽ nhận được một Trì hoãn sử dụng cuộc gọi async okhttp bên dưới mui xe. Vì vậy, bạn không cần phải thêm withContext (Dispatchers.IO) như bạn phải làm với RxJava để đảm bảo mã chạy trên một luồng IO; nếu bạn không sử dụng bộ điều hợp Retrofit coroutines và gọi trực tiếp cuộc gọi Retrofit, bạn cần vớiContext.

Phòng DB Thành phần vòm của Android cũng tự động hoạt động trên bối cảnh không có giao diện người dùng, do đó, bạn không cần phải có nội dung.

Người giới thiệu:

  • https://medium.com/capital-one-tech/kotlin-coroutines-on-android-things-i-wish-i-knew-at-the-beginning-c2f0b1f16cff
  • https://speakerdeck.com/elizarov/fresh-async-with-kotlin
  • https://medium.com/@michaelbukachi/coroutines-and-idling-resource-c1866bfa5b5d
  • https://blog.kotlin-academy.com/kotlin-coroutines-cheat-sheet-8cf1e284dc35
  • https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5?linkId=63267804
  • https://proandroiddev.com/managing-exceptions-in-nested-coroutine-scopes-9f23fd85e61