Trang chủ > Phát triển di động > Nội dung chính

Xử lý bất đồng bộ trong phát triển Android và iOS (phần ba) —— Hợp tác giữa nhiều tác vụ bất đồng bộ


Xử lý bất đồng bộ trong phát triển Android và iOS Các mối quan hệ hợp tác giữa các tác vụ bất đồng bộ được thảo luận chi tiết dưới ba tình huống ứng dụng khác nhau. Ba tình huống ứng dụng này bao gồm: Trong phần thứ ba của tác phẩm nàyđá gà trực tiếp app, chúng ta sẽ tập trung những vấn đề có thể phát sinh khi thực hiện đồng thời nhiều tác vụ bất đồng bộ. Khi làm việc với các tác vụ như vậy, có rất nhiều yếu tố cần được cân nhắc để đảm bảo hiệu quả và độ ổn định của toàn bộ quy trình.

Thông thườngđá gà trực tiếp app, chúng ta cần thực hiện nhiều tác vụ bất đồng bộ và khiến chúng phối hợp với nhau để hoàn thành yêu cầu. Bài viết này sẽ phân tích ba mối quan hệ phối hợp giữa các tác vụ bất đồng bộ dựa trên các tình huống sử dụng phổ biến trong thực tế: 1. **Phối hợp tuần tự**: Các tác vụ được thực hiện lần lượt theo thứ tự xác định, mỗi tác vụ chỉ bắt đầu khi tác vụ trước đó đã hoàn tất. Điều này giúp đảm bảo rằng dữ liệu từ tác vụ trước sẽ sẵn sàng cho tác vụ tiếp theo. 2. **Phối hợp song song**: Nhiều tác vụ được khởi chạy cùng lúc mà không phụ thuộc lẫn nhau. Cách làm này tối ưu hóa hiệu suất bằng cách tận dụng khả năng xử lý đa luồng của hệ thống. 3. **Phối hợp điều kiện**: Một số tác vụ chỉ được kích hoạt khi một điều kiện cụ thể được đáp ứng. Điều này giúp kiểm soát chặt chẽ hơn quá trình thực thi và đảm bảo tính linh hoạt trong việc đáp ứng các yêu cầu phức tạp. Hy vọng những giải thích này sẽ giúp bạn hiểu rõ hơn về cách các tác vụ bất đồng bộ có thể tương tác với nhau một cách hiệu quả.

  • Thực thi tuần tự sau khi hoàn thành.
  • Thực thi song song và kết hợp kết quả.
  • Thực thi song song với ưu tiên cho một bên.

Ba mối quan hệ hợp tác trên sẽ được thảo luận cụ thể thông qua ba ví dụ ứng dụng trong bài viết này. Ba ứng dụng này bao gồm:

  • Cache đa cấp.
  • Yêu cầu mạ
  • Cache trang.

yêu cầu mạng song song

Lưu ý: Mã nguồn trong loạt bài viết này đã được sắp xếp gọn gàng trên GitHub (liên tục cập nhật)99win club, đường dẫn kho lưu trữ mã nguồn là:

Trong bài viết nàybxh ngoai hang anh, mã nguồn Java được đề cập nằm trong package có tên là com. demos.async. multitask. Đây là nơi mà các đoạn code liên quan đến lập trình đa nhiệm và xử lý bất đồng bộ được tổ chức một cách có hệ thống để người đọc dễ dàng theo dõi và hiểu rõ cách hoạt động của chúng. Package này đóng vai trò như một thư viện tham chiếu cho các ví dụ cụ thể trong bài, giúp minh họa sâu hơn về chủ đề đang được thảo luận.


Nhiều nhiệm vụ bất đồng bộ thực hiện tuần tự sau khi hoàn thành.

Thực thi theo thứ tự nối tiếp

Một ví dụ điển hình là việc sử dụng đa cấp bộ nhớ đệm cho tài nguyên tĩnh99win club, trong đó người ta thường nhắc đến nhất chính là việc bộ nhớ đệm đa cấp cho hình ảnh tĩnh. Khi tải một hình ảnh tĩnh trên client, thông thường sẽ có ít nhất hai cấp bộ nhớ đệm: bộ nhớ đệm cấp 1 (Memory Cache) và bộ nhớ đệm cấp 2 (Disk Cache). Quy trình tải hình ảnh này diễn ra như sau: Trước tiên, khi người dùng yêu cầu một hình ảnh, hệ thống sẽ kiểm tra xem hình ảnh đó đã tồn tại trong bộ nhớ đệm cấp 1 hay chưa. Nếu hình ảnh nằm trong bộ nhớ đệm Memory Cache, nó sẽ được truy xuất ngay lập tức với tốc độ cực nhanh. Tuy nhiên, nếu không tìm thấy trong Memory Cache, hệ thống sẽ tiếp tục kiểm tra ở cấp 2, đó là Tại đây, hình ảnh sẽ được đọc từ ổ cứng hoặc bộ nhớ lưu trữ cố định khác, và sau đó được trả về cho ứng dụng. Cách làm này giúp tối ưu hóa hiệu suất và giảm tải đáng kể cho máy chủ, đồng thời tăng tốc đáng kể quá trình tải hình ảnh đối với người dùng cuối.

  1. Đầu tiên kiểm tra cache bộ nhớbxh ngoai hang anh, nếu trúng đích thì trả về ngay lập tức; nếu không, tiến hành bước tiếp theo.
  2. Tiếp tục kiểm tra cache đĩa99win club, nếu trúng đích thì trả về ngay lập tức; nếu không, tiến hành bước tiếp theo.
  3. Phát yêu cầu mạng để tải và giải mã tệp hình ảnh.

thực hiện theo thứ tự

Tìm kiếm Disk Cache

Trước tiênbxh ngoai hang anh, chúng ta cần xác định rõ giao diện cho hai tác vụ bất đồng bộ là "Bộ nhớ đệm Disk" và "Yêu cầu mạng". Việc này sẽ giúp chúng ta có một khung sườn rõ ràng để quản lý và tương tác với từng tác vụ một cách hiệu quả.

								
									
										public
									 interface
									 ImageDiskCache
									 {
									
    /** * Lấy đồng bộ hình ảnh Bitmap từ bộ nhớ đệm. * @param key Khóa để truy xuất dữ liệu trong bộ nhớ đệm * @param callback Hàm trả về đối tượng Bitmap đã lưu trong bộ nhớ đệm */
    void
									 getImage
									(
									String
									 key
									,
									 AsyncCallback
									<
									Bitmap
									>
									 callback
									);
									
    /** * Lưu đối tượng Bitmap vào bộ nhớ đệm. * @param key Khóa để xác định vị trí của dữ liệu trong bộ nhớ đệm. * @param bitmap Đối tượng Bitmap cần được lưu trữ. * @param callback Hàm trả về kết quả hiện tại của thao tác lưu99win club, cho biết việc lưu thành công hay thất bại. */
    void
									 putImage
									(
									String
									 key
									,
									 Bitmap
									 bitmap
									,
									 AsyncCallback
									<
									Boolean
									>
									 callback
									);
									
}
									

								

Giao diện ImageDiskCache được sử dụng để lưu và truy xuất Cache đĩa cho hình ảnh. Trong đóđá gà trực tiếp app, tham số AsyncCallback là định nghĩa của một giao diện gọi lại bất đồng bộ chung. Mã định nghĩa của nó như sau (giao diện này sẽ còn được đề cập đến trong phần sau của bài viết): ```java public interface AsyncCallback{ void onSuccess(T result); void onFailure(Throwable error); } ``` Trong đóđá gà trực tiếp app, phương thức `onSuccess` sẽ được thực thi khi hoạt động xử lý hoàn tất thành công, và `onFailure` sẽ được kích hoạt nếu có lỗi xảy ra trong quá trình thực hiện.

								
									/**<d>Loại dữ liệu tham số trả về từ giao diện bất đồng bộ.</d>
public
									 interface
									 AsyncCallback
									 <
									D
									>
									 {
									
    void
									 onResult
									(
									D
									 data
									);
									
}
									

								

Để tải hình ảnh qua mạngđá gà trực tiếp app, chúng ta trực tiếp gọi bài viết trước đó "... Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ Trong tài liệu được đề cập đếnđá gà trực tiếp app, chúng ta sẽ tìm hiểu về giao diện Downloader (chú ý: phiên bản của giao diện Downloader có tham số contextData ở cuối).

Phát yêu cầu tải xuống mạng

								
									// Kiểm tra cache thứ cấp: cache đĩa
    imageDiskCache
									.
									getImage
									(
									url
									,
									 new
									 AsyncCallback
									<
									Bitmap
									>()
									 {
									
        @Override
									
        public
									 void
									 onResult
									(
									Bitmap
									 bitmap
									)
									 {
									
            if
									 (
									bitmap
									 !=
									 null
									)
									 {
									
                // cache đĩa trúng đích99win club, nhiệm vụ tải về kết thúc sớm.
                imageMemCache
									.
									putImage
									(
									url
									,
									 bitmap
									);
									
                successCallback
									(
									url
									,
									 bitmap
									,
									 contextData
									);
									
            }
									
            else
									 {
									
                // Cả hai cấp cache đều không trúng đíchđá gà trực tiếp app, gọi trình tải về để tải.
                downloader
									.
									startDownload
									(
									url
									,
									 getLocalPath
									(
									url
									),
									 contextData
									);
									
            }
									
        }
									
    });
									

								

Mã ví dụ thực hiện callback kết quả thành công của Downloader như sau:

								
									@Override
									
    public
									 void
									 downloadSuccess
									(
									final
									 String
									 url
									,
									 final
									 String
									 localPath
									,
									 final
									 Object
									 contextData
									)
									 {
									
        // Giải mã hình ảnh99win club, là một hoạt động tốn thời gian, thực hiện bất đồng bộ.
        imageDecodingExecutor
									.
									execute
									(
									new
									 Runnable
									()
									 {
									
            @Override
									
            public
									 void
									 run
									()
									 {
									
                final
									 Bitmap
									 bitmap
									 =
									 decodeBitmap
									(
									new
									 File
									(
									localPath
									));
									
                // Điều độ lại lên luồng chính.
                mainHandler
									.
									post
									(
									new
									 Runnable
									()
									 {
									
                    @Override
									
                    public
									 void
									 run
									()
									 {
									
                        if
									 (
									bitmap
									 !=
									 null
									)
									 {
									
                            imageMemCache
									.
									putImage
									(
									url
									,
									 bitmap
									);
									
                            imageDiskCache
									.
									putImage
									(
									url
									,
									 bitmap
									,
									 null
									);
									
                            successCallback
									(
									url
									,
									 bitmap
									,
									 contextData
									);
									
                        }
									
                        else
									 {
									
                            // Giải mã thất bại.
                            failureCallback
									(
									url
									,
									 ImageLoaderListener
									.
									BITMAP_DECODE_FAILED
									,
									 contextData
									);
									
                        }
									
                    }
									
                });
									
            }
									
        });
									
    }
									

								

Nhiều nhiệm vụ bất đồng bộ thực hiện song song và kết hợp kết quả.

Thực thi đồng thờibxh ngoai hang anh, hợp nhất kết quả

Một ví dụ điển hình là khi bạn gửi nhiều yêu cầu mạng cùng một lúc (các API từ xa)bxh ngoai hang anh, sau đó đợi nhận được tất cả các phản hồi từ các yêu cầu này và xử lý dữ liệu thu thập được một cách đồng bộ, cập nhật giao diện người dùng. Cách làm này giúp giảm thiểu thời gian chờ tổng thể bằng cách tận dụng việc gửi đồng thời các yêu cầu mạng, cho phép hệ thống hoạt động nhanh hơn và hiệu quả hơn.

Chúng tôi sẽ đưa ra ví dụ mã nguồn dựa trên tình huống đơn giản nhất của hai yêu cầu mạ

Trước hếtbxh ngoai hang anh, vẫn cần định nghĩa trước các giao diện bất đồng bộ cần thiết, tức là định nghĩa giao diện API từ xa.

								
									/**
public
									 interface
									 HttpService
									 {
									
    /** * Gửi yêu cầu HTTP. * @param apiUrl Đường dẫn API cần truy cập * @param request Đối tượng tham số yêu cầu (được biểu diễn dưới dạng Java Bean) * @param listener Thiết bị lắng nghe phản hồi để xử lý kết quả * @param contextData Dữ liệu truyền qua để giữ liên kết ngữ cảnh * @param<t>Loại mô hình yêu cầu<r>Loại mô hình phản hồi</r></t>
    <
									T
									,
									 R
									>
									 void
									 doRequest
									(
									String
									 apiUrl
									,
									 T
									 request
									,
									 HttpListener
									<?
									 super
									 T
									,
									 R
									>
									 listener
									,
									 Object
									 contextData
									);
									
}
									

/**<t>Loại mô hình yêu cầu<r>Loại mô hình phản hồi</r></t>
public
									 interface
									 HttpListener
									 <
									T
									,
									 R
									>
									 {
									
    /** * Giao diện callback được gọi khi có kết quả (thành công hoặc thất bại) từ yêu cầu. * @param apiUrl Đường URL của yêu cầu * @param request Mô hình yêu cầu (request model) * @param result Kết quả yêu cầu (bao gồm phản hồi hoặc lý do lỗi) * @param contextData Tham số truyền qua để giữ ngữ cảnh */
    void
									 onResult
									(
									String
									 apiUrl
									,
									 T
									 request
									,
									 HttpResult
									<
									R
									>
									 result
									,
									 Object
									 contextData
									);
									
}
									

								

Điều cần lưu ý là trong giao diện HttpServicebxh ngoai hang anh, tham số yêu cầu request được định nghĩa bằng kiể Nếu có một lớp thực hiện giao diện này, thì trong mã thực hiện, nó sẽ phải chuyển đổi đối tượng request (mà có thể là bất kỳ lớp Java Bean nào) thành các tham số của yêu cầu HTTP thông qua cơ chế phản xạ, tùy thuộc vào loại đối tượng thực tế được truyền vào. Tất nhiên, ở đây chúng ta chỉ tập trung vào việc phân tích giao diện, còn việc cụ thể hóa cách triển khai sẽ không được đề cập đến trong phạm vi này.

Tham số kết quả trả về result có kiểu HttpResult99win club, điều này giúp nó có thể biểu thị cả kết quả phản hồi thành công lẫn kết quả phản hồi thất bại. Định nghĩa của HttpResult như sau:

								
									/** * Lớp HttpResult được thiết kế để đóng gói kết quả của một yêu cầu HTTP. Khi máy chủ phản hồi thành côngbxh ngoai hang anh, mã lỗi errorCode sẽ có giá trị là SUCCESS và nội dung phản hồi từ máy chủ sẽ được chuyển đổi thành thuộc tính response; Ngược lại, nếu máy chủ không thể phản hồi một cách hợp lệ, mã lỗi errorCode sẽ khác giá trị SUCCESS và giá trị của thuộc tính response sẽ không còn hợp lệ. * */Loại mô hình phản hồi
public
									 class
									 HttpResult
									 <
									R
									>
									 {
									
    /**
    public
									 static
									 final
									 int
									 SUCCESS
									 =
									 0
									;
									// Thành công
    public
									 static
									 final
									 int
									 REQUEST_ENCODING_ERROR
									 =
									 1
									;
									// Lỗi xảy ra khi mã hóa yêu cầu
    public
									 static
									 final
									 int
									 RESPONSE_DECODING_ERROR
									 =
									 2
									;
									// Lỗi xảy ra khi giải mã phản hồi
    public
									 static
									 final
									 int
									 NETWORK_UNAVAILABLE
									 =
									 3
									;
									// Mạng không khả dụng
    public
									 static
									 final
									 int
									 UNKNOWN_HOST
									 =
									 4
									;
									// Tên miền không thể phân giải
    public
									 static
									 final
									 int
									 CONNECT_TIMEOUT
									 =
									 5
									;
									// Kết nối quá thời gian
    public
									 static
									 final
									 int
									 HTTP_STATUS_NOT_OK
									 =
									 6
									;
									// Yêu cầu tải về trả về mã khác 200
    public
									 static
									 final
									 int
									 UNKNOWN_FAILED
									 =
									 7
									;
									// Lỗi chưa xác định khác

    private
									 int
									 errorCode
									;
									
    private
									 String
									 errorMessage
									;
									
    /** * Trả về là phản hồi mà máy chủ gửi lại. * Chỉ khi errorCode được thiết lập thành SUCCESS99win club, giá trị của phản hồi mới mang tính hợp lệ. */
    private
									 R
									 response
									;
									

    public
									 int
									 getErrorCode
									()
									 {
									
        return
									 errorCode
									;
									
    }
									

    public
									 void
									 setErrorCode
									(
									int
									 errorCode
									)
									 {
									
        this
									.
									errorCode
									 =
									 errorCode
									;
									
    }
									

    public
									 String
									 getErrorMessage
									()
									 {
									
        return
									 errorMessage
									;
									
    }
									

    public
									 void
									 setErrorMessage
									(
									String
									 errorMessage
									)
									 {
									
        this
									.
									errorMessage
									 =
									 errorMessage
									;
									
    }
									

    public
									 R
									 getResponse
									()
									 {
									
        return
									 response
									;
									
    }
									

    public
									 void
									 setResponse
									(
									R
									 response
									)
									 {
									
        this
									.
									response
									 =
									 response
									;
									
    }
									
}
									

								

Kết quả HttpResult cũng bao gồm một kiểu Generic Rđá gà trực tiếp app, đây chính là loại tham số phản hồi được trả về khi yêu cầu thành công. Tương tự, trong việc triển khai có thể xảy ra của HttpService, cơ chế phản chiếu (reflection) sẽ tiếp tục được sử dụng để biến đổi nội dung phản hồi từ yêu cầu (có thể là một chuỗi JSON) thành kiểu dữ liệu R (nó có thể là bất kỳ đối tượng Java nào). Điều này giúp tăng tính linh hoạt và khả năng tương thích cao cho hệ thống, đảm bảo rằng dữ liệu nhận được luôn được xử lý đúng cách dựa trên định nghĩa của kiểu R đã được chỉ định trước đó.

Rồi99win club, với sự hiện diện của giao diện HttpService, chúng ta có thể minh họa cách gửi đồng thời hai yêu cầu mạng. Hãy tưởng tượng rằng bạn đang xây dựng một ứng dụng cần phải tải xuống dữ liệu từ hai nguồn khác nhau cùng một lúc để hiển thị thông tin đầy đủ và nhanh chóng cho người dùng. Điều này không chỉ giúp cải thiện hiệu suất mà còn tạo ra trải nghiệm mượt mà hơn cho người sử dụng.

								
									
										public
									 class
									 MultiRequestsDemoActivity
									 extends
									 AppCompatActivity
									 {
									
    private
									 HttpService
									 httpService
									 =
									 new
									 MockHttpService
									();
									
    /**
    private
									 Map
									<
									String
									,
									 Object
									>
									 httpResults
									 =
									 new
									 HashMap
									<
									String
									,
									 Object
									>();
									

    @Override
									
    protected
									 void
									 onCreate
									(
									Bundle
									 savedInstanceState
									)
									 {
									
        super
									.
									onCreate
									(
									savedInstanceState
									);
									
        setContentView
									(
									R
									.
									layout
									.
									activity_multi_requests_demo
									);
									

        // Đồng thời khởi động hai yêu cầu bất đồng bộ
        httpService
									.
									doRequest
									(
									"http://..."
									,
									 new
									 HttpRequest1
									(),
									
                new
									 HttpListener
									<
									HttpRequest1
									,
									 HttpResponse1
									>()
									 {
									
                    @Override
									
                    public
									 void
									 onResult
									(
									String
									 apiUrl
									,
									
                                         HttpRequest1
									 request
									,
									
                                         HttpResult
									<
									HttpResponse1
									>
									 result
									,
									
                                         Object
									 contextData
									)
									 {
									
                        // Đưa kết quả yêu cầu vào cache
                        httpResults
									.
									put
									(
									"request-1"
									,
									 result
									);
									
                        if
									 (
									checkAllHttpResultsReady
									())
									 {
									
                            // Cả hai yêu cầu đã kết thúc
                            HttpResult
									<
									HttpResponse1
									>
									 result1
									 =
									 result
									;
									
                            HttpResult
									<
									HttpResponse2
									>
									 result2
									 =
									 (
									HttpResult
									<
									HttpResponse2
									>)
									 httpResults
									.
									get
									(
									"request-2"
									);
									
                            if
									 (
									checkAllHttpResultsSuccess
									())
									 {
									
                                // Cả hai yêu cầu đều thành công
                                processData
									(
									result1
									.
									getResponse
									(),
									 result2
									.
									getResponse
									());
									
                            }
									
                            else
									 {
									
                                // Hai yêu cầu không hoàn tất hoàn toàn99win club, xử lý như thất bại
                                processError
									(
									result1
									.
									getErrorCode
									(),
									 result2
									.
									getErrorCode
									());
									
                            }
									
                        }
									
                    }
									
                },
									
                null
									);
									
        httpService
									.
									doRequest
									(
									"http://..."
									,
									 new
									 HttpRequest2
									(),
									
                new
									 HttpListener
									<
									HttpRequest2
									,
									 HttpResponse2
									>()
									 {
									
                    @Override
									
                    public
									 void
									 onResult
									(
									String
									 apiUrl
									,
									
                                         HttpRequest2
									 request
									,
									
                                         HttpResult
									<
									HttpResponse2
									>
									 result
									,
									
                                         Object
									 contextData
									)
									 {
									
                        // Đưa kết quả yêu cầu vào cache
                        httpResults
									.
									put
									(
									"request-2"
									,
									 result
									);
									
                        if
									 (
									checkAllHttpResultsReady
									())
									 {
									
                            // Cả hai yêu cầu đã kết thúc
                            HttpResult
									<
									HttpResponse1
									>
									 result1
									 =
									 (
									HttpResult
									<
									HttpResponse1
									>)
									 httpResults
									.
									get
									(
									"request-1"
									);
									
                            HttpResult
									<
									HttpResponse2
									>
									 result2
									 =
									 result
									;
									
                            if
									 (
									checkAllHttpResultsSuccess
									())
									 {
									
                                // Cả hai yêu cầu đều thành công
                                processData
									(
									result1
									.
									getResponse
									(),
									 result2
									.
									getResponse
									());
									
                            }
									
                            else
									 {
									
                                // Hai yêu cầu không hoàn tất hoàn toànđá gà trực tiếp app, xử lý như thất bại
                                processError
									(
									result1
									.
									getErrorCode
									(),
									 result2
									.
									getErrorCode
									());
									
                            }
									
                        }
									
                    }
									
                },
									
                null
									);
									
    }
									

    /** * Kiểm tra xem tất cả các yêu cầu đã có kết quả hay chưa * @return */
    private
									 boolean
									 checkAllHttpResultsReady
									()
									 {
									
        int
									 requestsCount
									 =
									 2
									;
									
        for
									 (
									int
									 i
									 =
									 1
									;
									 i
									 <=
									 requestsCount
									;
									 i
									++)
									 {
									
            if
									 (
									httpResults
									.
									get
									(
									"request-"
									 +
									 i
									)
									 ==
									 null
									)
									 {
									
                return
									 false
									;
									
            }
									
        }
									
        return
									 true
									;
									
    }
									

    /** * Kiểm tra xem tất cả các yêu cầu đã thành công hay chưa * @return */
    private
									 boolean
									 checkAllHttpResultsSuccess
									()
									 {
									
        int
									 requestsCount
									 =
									 2
									;
									
        for
									 (
									int
									 i
									 =
									 1
									;
									 i
									 <=
									 requestsCount
									;
									 i
									++)
									 {
									
            HttpResult
									<? > result
									 =
									 (
									HttpResult
									<? >) httpResults
									.
									get
									(
									"request-"
									 +
									 i
									);
									
            if
									 (
									result
									 ==
									 null
									 ||
									 result
									.
									getErrorCode
									()
									 !=
									 HttpResult
									.
									SUCCESS
									)
									 {
									
                return
									 false
									;
									
            }
									
        }
									
        return
									 true
									;
									
    }
									

    private
									 void
									 processData
									(
									HttpResponse1
									 data1
									,
									 HttpResponse2
									 data2
									)
									 {
									
        // TODO: Cập nhật giao diện người dùng99win club, hiển thị kết quả yêu cầu. Code ở đây bị lược bỏ.
    }
									

    private
									 void
									 processError
									(
									int
									 errorCode1
									,
									 int
									 errorCode2
									)
									 {
									
        // TODO: Cập nhật giao diện người dùngbxh ngoai hang anh, hiển thị lỗi. Code ở đây bị lược bỏ.
    }
									
}
									

								

Trước tiênbxh ngoai hang anh, chúng ta cần đợi cho đến khi cả hai yêu cầu hoàn tất trước khi có thể hợp nhất kết quả của chúng. Để xác định xem cả hai yêu cầu bất đồng bộ đã hoàn thành chưa, chúng ta phải kiểm tra trạng thái hoàn tất của tất cả các yêu cầu mỗi khi một trong số chúng trả về kết quả thô Điều quan trọng cần lưu ý ở đây là chúng ta có thể áp dụng phương pháp kiểm tra này dựa trên một điều kiện quan trọng: onResult của HttpService đã được lên lịch để chạy trên luồng chính (main thread). Trong bài viết trước, " Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ Trong phần “mô hình luồng của hàm gọi lại” trong tài liệu đã phân tích kỹ lưỡng về môi trường luồng nơi các hàm gọi lại xảy ra. Giả sử rằng onResult của cả hai yêu cầu đã được lên lịch để chạy trên luồng chínhbxh ngoai hang anh, thì thứ tự thực thi của hai hàm gọi lại onResult chỉ có thể có hai kịch bản: hoặc là onResult của yêu cầu đầu tiên được thực thi trước, sau đó đến lượt onResult của yêu cầu thứ hai; hoặc ngược lại, onResult của yêu cầu thứ hai sẽ chạy trước và tiếp theo là onResult của yêu cầu đầu tiên. Dù kịch bản nào xảy ra đi chăng nữa, các dòng mã trong phần xử lý bên trong onResult vẫn luôn hoạt động chính xác và hiệu quả. Một điểm cần lưu ý thêm là khi hai yêu cầu cùng chạy trên luồng chính, sự đồng bộ giữa chúng phụ thuộc vào cách mà hệ thống quản lý lịch trình luồng. Điều này đảm bảo rằng dù thứ tự thực thi có thay đổi, logic kiểm tra bên trong onResult vẫn duy trì tính ổn định và không bị ảnh hưởng. Điều này đặc biệt quan trọng trong các ứng dụng đa luồng, nơi mà việc sắp xếp thứ tự thực thi phải được kiểm soát cẩn thận để tránh lỗi logic hoặc kết quả không mong muốn.

Tuy nhiênđá gà trực tiếp app, nếu phương thức onResult của HttpService được thực thi trên các luồng khác nhau, hai callback onResult của các yêu cầu có thể chạy xen kẽ với nhau, dẫn đến các vấn đề về đồng bộ bên trong các đoạn mã kiểm tra và xử lý. Điều này có thể gây ra sự không ổn định khi dữ liệu từ các yêu cầu khác nhau bị trộn lẫn hoặc không được xử lý theo đúng thứ tự mong muốn. Khi đó, việc quản lý trạng thái hoặc cập nhật dữ liệu trở nên phức tạp hơn, đặc biệt là khi cả hai luồng cùng truy cập vào cùng một tài nguyên hoặc đối tượng chung.

So với việc thực hiện theo thứ tự trước sau mà chúng ta đã đề cập trước đó99win club, việc thực hiện đồng thời ở đây chắc chắn sẽ mang lại độ phức tạp đáng kể. Nếu không có yêu cầu đặc biệt mạnh mẽ về việc cải thiện hiệu suất mà việc chạy song song mang lại, có lẽ chúng ta sẽ cảm thấy hài lòng hơn khi lựa chọn cách làm tuần tự, giúp logic mã nguồn dễ hiểu và dễ quản lý hơn. Việc lựa chọn giữa hai phương án này cũng giống như việc bạn quyết định đi xe đạp hay ô tô trong một chuyến đi ngắn. Nếu khoảng cách không quá xa, xe đạp vẫn là lựa chọn hợp lý vì sự đơn giản và tiện lợi của nó. Tuy nhiên, khi cần di chuyển nhanh chóng qua quãng đường dài, thì xe ô tô sẽ trở thành sự lựa chọn tối ưu, dù nó phức tạp hơn trong việc vận hành và bảo trì.

Nhiều nhiệm vụ bất đồng bộ thực hiện song song với ưu tiên cho một bên.

Thực thi đồng thời với ưu tiên cho một bên

Một ví dụ điển hình là bộ nhớ đệm trang. Giả sử một trang web cần hiển thị một danh sách dữ liệu động. Nếu mỗi lần người dùng mở trangbxh ngoai hang anh, hệ thống chỉ lấy dữ liệu từ máy chủ, thì trong trường hợp không có kết nối mạng hoặc mạng chậm, trang sẽ bị trống trong thời gian dài, gây khó chịu cho người dùng. Trong những tình huống như vậy, việc hiển thị dữ liệu cũ thường vẫn tốt hơn là để trang trắng hoàn toàn. Do đó, chúng ta có thể nghĩ đến việc thêm một cơ chế bộ nhớ đệm cục bộ để lưu trữ dữ liệu danh sách một cách bền vững. Điều này giúp cải thiện trải nghiệm người dùng và giảm tải đáng kể cho máy chủ khi có nhiều yêu cầu cùng lúc.

Cache cục bộ cũng là một nhiệm vụ bất đồng bộđá gà trực tiếp app, mã giao diện được định nghĩa như sau:

								
									
										public
									 interface
									 LocalDataCache
									 {
									
    /** * Lấy đồng bộ đối tượng HttpResponse từ bộ nhớ đệm cục bộ. * @param key Chuỗi khóa để xác định vị trí của đối tượng trong bộ nhớ đệm * @param callback Hàm trả về đối tượng được lưu trữ trong bộ nhớ đệm */
    void
									 getCachingData
									(
									String
									 key
									,
									 AsyncCallback
									<
									HttpResponse
									>
									 callback
									);
									

    /** * Lưu đối tượng HttpResponse vào bộ nhớ cache. * @param key Khóa để xác định vị trí lưu trữ * @param data Đối tượng HttpResponse cần được lưu giữ. * @param callback Hàm trả về kết quả của hoạt động lưu trữđá gà trực tiếp app, cho biết thành công hoặc thất bại. */
    void
									 putCachingData
									(
									String
									 key
									,
									 HttpResponse
									 data
									,
									 AsyncCallback
									<
									Boolean
									>
									 callback
									);
									
}
									

								

Dữ liệu được lưu trong bộ nhớ cache cục bộ này chính là đối tượng HttpResponse mà chúng ta đã nhận được từ máy chủ trước đó. Giao diện gọi lại bất đồng bộ AsyncCallback99win club, như đã đề cập ở phần trước, cho phép xử lý dữ liệu khi tác vụ hoàn thành. Trong thực tế, khi làm việc với các yêu cầu mạng, việc sử dụng bộ nhớ cache giúp cải thiện đáng kể hiệu suất ứng dụng. Khi dữ liệu cần thiết đã tồn tại trong bộ nhớ cache, chúng ta có thể truy xuất trực tiếp thay vì gửi thêm yêu cầu đến máy chủ, giảm tải cho hệ thống và mang lại trải nghiệm mượt mà hơn cho người dùng. AsyncCallback đóng vai trò quan trọng trong việc nhận phản hồi từ bộ nhớ cache hoặc từ kết quả của yêu cầu mạng và xử lý nó theo cách linh hoạt nhất.

Khi trang web được mở rabxh ngoai hang anh, chúng ta có thể khởi động đồng thời nhiệm vụ đọc từ bộ nhớ đệm cục bộ và nhiệm vụ gửi yêu cầu đến API từ xa. Trong đó, nhiệm vụ thứ hai sẽ có ưu tiên cao hơn so với nhiệm vụ đầu tiên. Hơn nữa, để tối ưu hóa hiệu suất, chúng ta có thể thiết lập một cơ chế điều phối thông minh giữa hai quy trình này. Ví dụ như khi bộ nhớ đệm cục bộ chưa sẵn sàng, hệ thống sẽ tự động ưu tiên xử lý yêu cầu API từ xa trước, đồng thời hiển thị thông báo cho người dùng biết rằng dữ liệu đang được tải. Điều này không chỉ đảm bảo trải nghiệm người dùng mượt mà mà còn tăng cường khả năng phản hồi của ứng dụng trong mọi tình huống.

								
									
										public
									 class
									 PageCachingDemoActivity
									 extends
									 AppCompatActivity
									 {
									
    private
									 HttpService
									 httpService
									 =
									 new
									 MockHttpService
									();
									
    private
									 LocalDataCache
									 localDataCache
									 =
									 new
									 MockLocalDataCache
									();
									
    /**
    private
									 boolean
									 dataFromHttpReady
									;
									

    @Override
									
    protected
									 void
									 onCreate
									(
									Bundle
									 savedInstanceState
									)
									 {
									
        super
									.
									onCreate
									(
									savedInstanceState
									);
									
        setContentView
									(
									R
									.
									layout
									.
									activity_page_caching_demo
									);
									

        // Đồng thời khởi động yêu cầu dữ liệu cục bộ và yêu cầu HTTP từ xa
        final
									 String
									 userId
									 =
									 "xxx"
									;
									
        localDataCache
									.
									getCachingData
									(
									userId
									,
									 new
									 AsyncCallback
									<
									HttpResponse
									>()
									 {
									
            @Override
									
            public
									 void
									 onResult
									(
									HttpResponse
									 data
									)
									 {
									
                if
									 (
									data
									 !=
									 null
									 &&
									 !
									dataFromHttpReady
									)
									 {
									
                    // Cache có dữ liệu cũ & yêu cầu HTTP từ xa chưa trả vềbxh ngoai hang anh, hiển thị dữ liệu cũ trước
                    processData
									(
									data
									);
									
                }
									
            }
									
        });
									
        httpService
									.
									doRequest
									(
									"http://..."
									,
									 new
									 HttpRequest
									(),
									
                new
									 HttpListener
									<
									HttpRequest
									,
									 HttpResponse
									>()
									 {
									
                    @Override
									
                    public
									 void
									 onResult
									(
									String
									 apiUrl
									,
									
                                         HttpRequest
									 request
									,
									
                                         HttpResult
									<
									HttpResponse
									>
									 result
									,
									
                                         Object
									 contextData
									)
									 {
									
                        if
									 (
									result
									.
									getErrorCode
									()
									 ==
									 HttpResult
									.
									SUCCESS
									)
									 {
									
                            dataFromHttpReady
									 =
									 true
									;
									
                            processData
									(
									result
									.
									getResponse
									());
									
                            // Lấy dữ liệu mới từ HTTP99win club, cập nhật cache cục bộ
                            localDataCache
									.
									putCachingData
									(
									userId
									,
									 result
									.
									getResponse
									(),
									 null
									);
									
                        }
									
                        else
									 {
									
                            processError
									(
									result
									.
									getErrorCode
									());
									
                        }
									
                    }
									
                },
									
                null
									);
									
    }
									


    private
									 void
									 processData
									(
									HttpResponse
									 data
									)
									 {
									
        // TODO: Cập nhật giao diện người dùng99win club, hiển thị dữ liệu. Code ở đây bị lược bỏ.
    }
									

    private
									 void
									 processError
									(
									int
									 errorCode
									)
									 {
									
        // TODO: Cập nhật giao diện người dùng99win club, hiển thị lỗi. Code ở đây bị lược bỏ.
    }
									
}
									

								

Dù việc đọc dữ liệu từ bộ nhớ đệm cục bộ thường nhanh hơn rất nhiều so với việc lấy dữ liệu từ mạng99win club, nhưng vì cả hai đều là các giao diện bất đồng bộ, vẫn có khả năng logic rằng dữ liệu từ mạng sẽ được trả về trước khi dữ liệu từ bộ nhớ đệm thực hiệ Hơn nữa, trong bài viết trước của chúng ta có đề cập đến chủ đề “... (Đoạn này đã được chuyển sang tiếng Việt hoàn toàn và không còn bất kỳ ký tự nào không thuộc tiếng Việt.) Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ callback kết quả thành công xảy ra sớm

Trong mã code phía trên99win club, nếu dữ liệu từ mạng trả về trước khi dữ liệu từ bộ nhớ cache hoàn tất việc callback, chúng ta sẽ ghi nhận một dấu hiệu dạng boolean có tên là Khi nhiệm vụ lấy dữ liệu từ bộ nhớ cache kết thúc, chúng ta sẽ kiểm tra dấu hiệu này để quyết định bỏ qua dữ liệu từ cache. Thêm vào đó, việc sử dụng một cơ chế như vậy không chỉ giúp tối ưu hóa hiệu suất mà còn đảm bảo rằng dữ liệu mới nhất từ mạng được ưu tiên sử dụng, tránh trường hợp hiển thị thông tin lỗi thời cho người dùng. Điều này đặc biệt quan trọng trong các ứng dụng yêu cầu tính thời gian thực cao, chẳng hạn như các nền tảng truyền thông xã hội hoặc các dịch vụ thời tiết trực tuyến.

thực hiện đồng thời với ưu tiên một bên

thực thi song songđá gà trực tiếp app, ưu tiên một bên

Sử dụng RxJava zip để thực hiện yêu cầu mạng song song

vũ khí hạng nặng

Yêu cầu mạng song song

thực hiện yêu cầu mạng song song

Chúng ta có thể coi hai yêu cầu mạng đồng thời như là hai đối tượng Observable và sử dụng hoạt động zip để kết hợp kết quả của chúng. Điều này dường như làm cho mọi thứ trở nên đơn giản hơn rất nhiều. Tuy nhiên99win club, trước tiên chúng ta cần giải quyết một vấn đề khác: gói gém giao diện yêu cầu mạng bất đồng bộ được đại diện bởi HttpService thành một đối tượ Để làm điều đó, chúng ta sẽ tạo ra một lớp hoặc hàm chuyển đổi các thao tác bất đồng bộ trong HttpService thành một luồng dữ liệ Điều này không chỉ giúp chúng ta dễ dàng quản lý mà còn tạo điều kiện thuận lợi cho việc sử dụng các hoạt động nâng cao như zip trong chuỗi xử lý sự kiện. Với cách tiếp cận này, chúng ta có thể linh hoạt hơn trong việc kiểm soát các yêu cầu mạng đồng thời và dễ dàng xử lý dữ liệu khi cả hai yêu cầu hoàn tất.

Thông thườngđá gà trực tiếp app, việc bao gói một tác vụ đồng bộ thành một Observable khá đơn giản, nhưng khi nói đến việc chuyển đổi một tác vụ bất đồng bộ đã có sẵn thành Observable thì lại không còn dễ dàng như vậy. Trong trường hợp này, chúng ta cần sử dụng AsyncOnSubscribe để xử lý vấn đề. AsyncOnSubscribe sẽ giúp chúng ta quản lý và kết nối các luồng dữ liệu bất đồng bộ một cách hiệu quả, đảm bảo rằng dữ liệu được xử lý đúng thứ tự và không bị mất mát trong quá trình thực thi.

								
									
										public
									 class
									 MultiRequestsDemoActivity
									 extends
									 AppCompatActivity
									 {
									
    private
									 HttpService
									 httpService
									 =
									 new
									 MockHttpService
									();
									

    private
									 TextView
									 apiResultDisplayTextView
									;
									

    @Override
									
    protected
									 void
									 onCreate
									(
									Bundle
									 savedInstanceState
									)
									 {
									
        super
									.
									onCreate
									(
									savedInstanceState
									);
									
        setContentView
									(
									R
									.
									layout
									.
									activity_multi_requests_demo
									);
									

        apiResultDisplayTextView
									 =
									 (
									TextView
									)
									 findViewById
									(
									R
									.
									id
									.
									api_result_display
									);
									

        /** * Trước tiên99win club, sử dụng cơ chế AsyncOnSubscribe để đóng gói hai yêu cầu thành hai Observable riêng biệt */

        Observable
									<
									HttpResponse1
									>
									 request1
									 =
									 Observable
									.
									create
									(
									new
									 AsyncOnSubscribe
									<
									Integer
									,
									 HttpResponse1
									>()
									 {
									
            @Override
									
            protected
									 Integer
									 generateState
									()
									 {
									
                return
									 0
									;
									
            }
									

            @Override
									
            protected
									 Integer
									 next
									(
									Integer
									 state
									,
									 long
									 requested
									,
									 Observer
									<
									Observable
									<?
									 extends
									 HttpResponse1
									>>
									 observer
									)
									 {
									
                final
									 Observable
									<
									HttpResponse1
									>
									 asyncObservable
									 =
									 Observable
									.
									create
									(
									new
									 Observable
									.
									OnSubscribe
									<
									HttpResponse1
									>()
									 {
									
                    @Override
									
                    public
									 void
									 call
									(
									final
									 Subscriber
									<?
									 super
									 HttpResponse1
									>
									 subscriber
									)
									 {
									
                        // Khởi động yêu cầu bất đồng bộ đầu tiên
                        httpService
									.
									doRequest
									(
									"http://..."
									,
									 new
									 HttpRequest1
									(),
									
                                new
									 HttpListener
									<
									HttpRequest1
									,
									 HttpResponse1
									>()
									 {
									
                                    @Override
									
                                    public
									 void
									 onResult
									(
									String
									 apiUrl
									,
									 HttpRequest1
									 request
									,
									 HttpResult
									<
									HttpResponse1
									>
									 result
									,
									 Object
									 contextData
									)
									 {
									
                                        // Yêu cầu bất đồng bộ đầu tiên kết thúc99win club, gửi kết quả vào asyncObservable
                                        if
									 (
									result
									.
									getErrorCode
									()
									 ==
									 HttpResult
									.
									SUCCESS
									)
									 {
									
                                            subscriber
									.
									onNext
									(
									result
									.
									getResponse
									());
									
                                            subscriber
									.
									onCompleted
									();
									
                                        }
									
                                        else
									 {
									
                                            subscriber
									.
									onError
									(
									new
									 Exception
									(
									"request1 failed"
									));
									
                                        }
									
                                    }
									
                                },
									
                                null
									);
									
                    }
									
                });
									
                observer
									.
									onNext
									(
									asyncObservable
									);
									
                observer
									.
									onCompleted
									();
									
                return
									 1
									;
									
            }
									
        });
									

        Observable
									<
									HttpResponse2
									>
									 request2
									 =
									 Observable
									.
									create
									(
									new
									 AsyncOnSubscribe
									<
									Integer
									,
									 HttpResponse2
									>()
									 {
									
            @Override
									
            protected
									 Integer
									 generateState
									()
									 {
									
                return
									 0
									;
									
            }
									

            @Override
									
            protected
									 Integer
									 next
									(
									Integer
									 state
									,
									 long
									 requested
									,
									 Observer
									<
									Observable
									<?
									 extends
									 HttpResponse2
									>>
									 observer
									)
									 {
									
                final
									 Observable
									<
									HttpResponse2
									>
									 asyncObservable
									 =
									 Observable
									.
									create
									(
									new
									 Observable
									.
									OnSubscribe
									<
									HttpResponse2
									>()
									 {
									
                    @Override
									
                    public
									 void
									 call
									(
									final
									 Subscriber
									<?
									 super
									 HttpResponse2
									>
									 subscriber
									)
									 {
									
                        // Khởi động yêu cầu bất đồng bộ thứ hai
                        httpService
									.
									doRequest
									(
									"http://..."
									,
									 new
									 HttpRequest2
									(),
									
                                new
									 HttpListener
									<
									HttpRequest2
									,
									 HttpResponse2
									>()
									 {
									
                                    @Override
									
                                    public
									 void
									 onResult
									(
									String
									 apiUrl
									,
									 HttpRequest2
									 request
									,
									 HttpResult
									<
									HttpResponse2
									>
									 result
									,
									 Object
									 contextData
									)
									 {
									
                                        // Yêu cầu bất đồng bộ thứ hai kết thúc99win club, gửi kết quả vào asyncObservable
                                        if
									 (
									result
									.
									getErrorCode
									()
									 ==
									 HttpResult
									.
									SUCCESS
									)
									 {
									
                                            subscriber
									.
									onNext
									(
									result
									.
									getResponse
									());
									
                                            subscriber
									.
									onCompleted
									();
									
                                        }
									
                                        else
									 {
									
                                            subscriber
									.
									onError
									(
									new
									 Exception
									(
									"reques2 failed"
									));
									
                                        }
									
                                    }
									
                                },
									
                                null
									);
									
                    }
									
                });
									
                observer
									.
									onNext
									(
									asyncObservable
									);
									
                observer
									.
									onCompleted
									();
									
                return
									 1
									;
									
            }
									
        });
									

        // Đối với hai Observable đại diện cho các request99win club, sử dụng zip để hợp nhất kết quả của chúng.
        Observable
									.
									zip
									(
									request1
									,
									 request2
									,
									 new
									 Func2
									<
									HttpResponse1
									,
									 HttpResponse2
									,
									 List
									<
									Object
									>>()
									 {
									
            @Override
									
            public
									 List
									<
									Object
									>
									 call
									(
									HttpResponse1
									 response1
									,
									 HttpResponse2
									 response2
									)
									 {
									
                List
									<
									Object
									>
									 responses
									 =
									 new
									 ArrayList
									<
									Object
									>(
									2
									);
									
                responses
									.
									add
									(
									response1
									);
									
                responses
									.
									add
									(
									response2
									);
									
                return
									 responses
									;
									
            }
									
        }).
									subscribe
									(
									new
									 Subscriber
									<
									List
									<
									Object
									>>()
									 {
									
            private
									 HttpResponse1
									 response1
									;
									
            private
									 HttpResponse2
									 response2
									;
									

            @Override
									
            public
									 void
									 onNext
									(
									List
									<
									Object
									>
									 responses
									)
									 {
									
                response1
									 =
									 (
									HttpResponse1
									)
									 responses
									.
									get
									(
									0
									);
									
                response2
									 =
									 (
									HttpResponse2
									)
									 responses
									.
									get
									(
									1
									);
									
            }
									

            @Override
									
            public
									 void
									 onCompleted
									()
									 {
									
                processData
									(
									response1
									,
									 response2
									);
									
            }
									

            @Override
									
            public
									 void
									 onError
									(
									Throwable
									 e
									)
									 {
									
                processError
									(
									e
									);
									
            }
									

        });
									
    }
									

    private
									 void
									 processData
									(
									HttpResponse1
									 data1
									,
									 HttpResponse2
									 data2
									)
									 {
									
        // TODO: Cập nhật giao diện người dùngbxh ngoai hang anh, hiển thị dữ liệu. Code ở đây bị lược bỏ.
    }
									

    private
									 void
									 processError
									(
									Throwable
									 e
									)
									 {
									
        // TODO: Cập nhật giao diện người dùng99win club, hiển thị lỗi. Code ở đây bị lược bỏ.
    }
									

								

chuyển đổi HttpService thành Observable

thực thi đồng thời với ưu tiên cho một bên

thực thi tuần tự liên tiếp


thực thi tuần tự nối tiếp

Hơn nữa99win club, một vấn đề không thể bỏ qua là trong nhiều trường hợp, quyền lựa chọn không nằm trong tay chúng ta. Có lẽ cấu trúc mã nguồn mà chúng ta nhận được đã tạo ra các mối quan hệ hợp tác giữa các nhiệm vụ bất đồng bộ phức tạp. Điều mà chúng ta cần làm chính là khi tình huống như vậy xảy ra, luôn giữ được sự bình tĩnh và có khả năng xác định rõ ràng tình hình hiện tại thuộc loại nào ngay cả khi phải đối mặt với dòng mã đầy rẫy logic khó hiểu. Khi đó, việc đầu tiên cần làm là cố gắng hiểu rõ toàn bộ cấu trúc của mã nguồn mà mình đang làm việc. Hãy tưởng tượng rằng bạn đang khám phá một mê cung bí ẩn, mỗi đoạn mã đều là một hành lang có thể dẫn đến những kết quả khác nhau. Việc phân tích và tìm ra điểm bắt đầu cho quá trình xử lý là vô cùng quan trọng. Một khi đã xác định được luồng chính, bạn sẽ dễ dàng hơn trong việc theo dõi và kiểm soát các nhiệm vụ bất đồng bộ, tránh bị lạc vào các nhánh phụ không cần thiết. Điều này đòi hỏi sự kiên nhẫn và kỹ năng tốt để phân tích từng bước một. Hãy tưởng tượng bạn là một thám tử, cần lần theo dấu vết để giải quyết một vụ án. Mỗi dòng mã đều chứa manh mối, và bạn phải kết nối chúng lại thành một bức tranh hoàn chỉnh. Chỉ khi nắm vững toàn cảnh, bạn mới có thể đưa ra quyết định đúng đắn về cách xử lý tiếp theo. Và điều quan trọng nhất vẫn là luôn duy trì sự tập trung cao độ, bởi chỉ cần một phút lơ là có thể khiến bạn bỏ sót những chi tiết quan trọng.

(Kết thúc)

Các bài viết được chọn lọc khác


Bài viết gốcbxh ngoai hang anh, vui lòng ghi rõ nguồn và bao gồm mã QR bên dưới! Nếu không, từ chối tái bản!
Liên kết bài viết: /9ejfxdmx.html
Hãy theo dõi tài khoản Weibo cá nhân của tôi: Tìm kiếm tên "Trương Tiết Lệ" trên Weibo.
Tài khoản WeChat của tôi: tielei-blog (Trương Tiết Lệ)
Bài trước: Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ
Bài sau: Phép thuật của trẻ nhỏ

Bài viết mới nhất