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 hai) —— Quay lại sau khi hoàn thành 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: Phần thứ hai của “”. Trong bài viết nàybxh ngoai hang anh, chúng ta sẽ thảo luận về nhiều vấn đề liên quan đến callback của tác vụ bất đồng bộ.

Trong iOSsv 88, callback thường được thể hiện dưới dạng delegate; còn trong Android, callback lại thường tồn tại dưới hình thứ Tuy nhiên, bất kể hình thức biểu đạt ra sao, callback luôn là một phần không thể tách rời trong thiết kế giao diện. Việc thiết kế callback tốt hay xấu sẽ trực tiếp ảnh hưởng đến thành công hay thất bại của toàn bộ thiết kế giao diện. Một interface với callback logic chặt chẽ và hợp lý sẽ giúp các nhà phát triển dễ dàng sử dụng hơn, đồng thời tạo nền tảng vững chắc cho sự ổn định và hiệu quả của ứng dụng.

Trong quá trình thiết kế và triển khai giao diện callbacksv 88, chúng ta cần cân nhắc những yếu tố nào? Trước tiên, hãy cùng liệt kê các chủ đề phụ mà bài viết này sẽ đề cập đến, sau đó sẽ lần lượt phân tích từng vấn đề một: 1. **Khả năng mở rộng (Scalability):** Giao diện callback phải linh hoạt đến mức nào để có thể xử lý thêm nhiều yêu cầu mới trong tương lai mà không làm suy giảm hiệu suất? 2. **Tính đồng bộ và bất đồng bộ:** Liệu giao diện của bạn nên được xây dựng theo kiểu đồng bộ hay bất đồng bộ? Điều này ảnh hưởng lớn đến cách quản lý tài nguyên và phản hồi của hệ thống. 3. **Khả năng kiểm soát lỗi:** Khi xảy ra lỗi trong quá trình thực thi callback, chúng ta cần có cơ chế như thế nào để đảm bảo ứng dụng vẫn hoạt động ổn định? 4. **Bảo mật (Security):** Làm thế nào để bảo vệ dữ liệu nhạy cảm khi truyền tải giữa các dịch vụ thô 5. **Thiết kế API dễ sử dụng:** Điều gì khiến một giao diện callback trở nên thân thiện với người dùng? Có phải là sự đơn giản, rõ ràng hay chỉ cần tối ưu hóa thời gian phản hồi? 6. **Hiệu suất và độ trễ (Performance & Latency):** Chúng ta cần đo lường và tối ưu hóa bao nhiêu phần trăm thời gian thực thi để đạt hiệu quả cao nhất? 7. **Quản lý trạng thái (State Management):** Khi sử dụng callback, làm thế nào để duy trì trạng thái đúng đắn cho các tác vụ đang chạy? Sau khi đã xác định rõ các yếu tố trên, chúng ta sẽ đi sâu vào phân tích chi tiết từng điểm một để hiểu rõ hơn về tầm quan trọng của việc thiết kế một giao diện callback hoàn chỉnh.

  • Cần phải tạo kết quả callback bắt buộc.
  • Cần chú trọng vào callback khi thất bại và mã lỗi nên được mô tả chi tiết.
  • Giao diện gọi và giao diện callback cần có mối quan hệ rõ ràng.
  • Callback kết quả thành công và callback kết quả thất bại nên loại trừ lẫn nhau.
  • Mô hình luồng củ
  • Tham số ngữ cảnh callback (thông số truyền qua).
  • Thứ tự callback.
  • Callback dưới dạng closure và

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)sv 88, đường dẫn kho lưu trữ mã nguồn là:

Trong bài viết nàysv 88, mã nguồn Java được đề cập nằm trong gói (package) có tên là `com. demos.async. callback`. Đây là nơi chứa các đoạn mã liên quan đến lập trình bất đồng bộ và xử lý callback trong dự án demo. Package này đóng vai trò quan trọng để minh họa cách hoạt động của mô hình lập trình theo sự kiệ


Cần phải tạo kết quả callback bắt buộc.

Khi giao diện được thiết kế theo dạng bất đồng bộbxh ngoai hang anh, kết quả cuối cùng của giao diện sẽ được trả về cho người gọi thô

Tuy nhiênsv 88, giao diện callback không phải lúc nào cũng truyền kết quả cuối cùng. Thực tế, chúng ta có thể chia callback thành hai loại:

  • Callback giữa chừng.
  • Callback kết quả.

Và callback kết quả bao gồm callback kết quả thành công và callback kết quả thất bại.

Gọi lại giữa chừng có thể được kích hoạt khi nhiệm vụ bất đồng bộ bắt đầu thực hiệnkeo nha cai hom nay, khi có sự cập nhật về tiến độ, hoặc khi một sự kiện quan trọng khác xảy ra trong quá trình thực hiện; trong khi đó, hàm gọi lại kết quả chỉ sẽ được kích hoạt khi nhiệm vụ bất đồng bộ hoàn tất và đã có một kết quả rõ ràng (thành công hoặc thất bại). Sự xuất hiện của hàm gọi lại kết quả đánh dấu việc hoàn thành toàn bộ quá trình thực thi của giao diện bất đồng bộ này.

Kết quả phải được trả về dưới dạng callbacksv 88,

Điểm khó ở đây là việc thực hiện giao diện cần phải xử lý cẩn trọng mọi tình huống lỗi có thể xảy rakeo nha cai hom nay, và bất kỳ trường hợp nào xuất hiện cũng phải tạo ra callback kết quả. Nếu không, có thể dẫn đến gián đoạn toàn bộ quy trình thực thi của bên gọi. Điều này đặc biệt quan trọng khi giao diện được sử dụng trong các hệ thống phức tạp, nơi một sự cố nhỏ có thể gây ra hậu quả nghiêm trọng. Vì vậy, việc thiết kế và kiểm tra kỹ lưỡng các kịch bản tiềm năng là điều không thể thiếu để đảm bảo tính ổn định và độ tin cậy của ứng dụng.

Cần chú trọng vào callback khi thất bại và mã lỗi nên được mô tả chi tiết.

Trước tiênkeo nha cai hom nay, hãy xem một ví dụ về mã:

								
									
										public
									 interface
									 Downloader
									 {
									
    /** * Cài đặt trình lắng nghe. * @param listener Trình lắng nghe cần được cấu hình. */
    void
									 setListener
									(
									DownloadListener
									 listener
									);
									
    /** * Khởi động quá trình tải xuống tài nguyên. * @param url Địa chỉ nguồn tài nguyên cần tải về. * @param localPath Vị trí lưu trữ trên máy tính sau khi tải xuống. * Ngoài rabxh ngoai hang anh, bạn có thể tùy chỉnh thêm các thông số như tốc độ tải, thời gian chờ giữa các yêu cầu để tối ưu hóa hiệu suất tải xuống. */
    void
									 startDownload
									(
									String
									 url
									,
									 String
									 localPath
									);
									
}
									

public
									 interface
									 DownloadListener
									 {
									
    /** * Hàm xử lý sự kiện khi quá trình tải xuống hoàn tất. * @param result Kết quả của việc tải xuống. Trả về true nếu thành côngsv 88, false nếu thất bại. * @param url Địa chỉ nguồn tài nguyên được yêu cầu tải. * @param localPath Đường dẫn nơi lưu trữ tài nguyên đã tải. Chỉ có giá trị khi result = true. * Ngoài ra, chúng ta cũng nên kiểm tra thêm xem đường dẫn localPath có hợp lệ hay không trước khi sử dụng. * Một số trường hợp đặc biệt như bị gián đoạn kết nối mạng trong quá trình tải xuống cần được xử lý cẩn thận. */
    void
									 downloadFinished
									(
									boolean
									 result
									,
									 String
									 url
									,
									 String
									 localPath
									);
									

    /** * Hàm callback để theo dõi tiến độ tải xuống. * @param url Địa chỉ nguồn của tài nguyên. * @param downloadedSize Kích thước đã tải thành công cho đến thời điểm hiện tại. * @param totalSize Tổng kích thước của tài nguyên cần tải. * Ngoài rakeo nha cai hom nay, bạn có thể thêm các thông tin bổ sung như % hoàn thành hoặc thời gian dự kiến còn lại trong phiên bản tùy chỉnh của hàm này. */
    void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									);
									
}
									

								

Giao diện tải xuống này được thiết kế để tải tài nguyên từ URL đã chỉ định. Đây là một giao diện đồng bộbxh ngoai hang anh, nơi người gọi sẽ kích hoạt nhiệm vụ tải xuống bằng cách gọi phương thức startDownload và sau đó chờ đợi sự kiện trả về Khi callback downloadFinished xảy ra, điều đó có nghĩa là nhiệm vụ tải xuống đã hoàn thành. Nếu giá trị result trả về là true, nghĩa là quá trình tải xuống đã thành công; nếu không, nó cho thấy việc tải xuống đã thất bại. Ngoài ra, trong quá trình tải xuống, có thể xảy ra các trường hợp ngoại lệ cần xử lý, chẳng hạn như mất kết nối mạng hoặc lỗi từ máy chủ. Do đó, việc kiểm tra trạng thái và xử lý logic sai sót trở nên rất quan trọng để đảm bảo tính ổn định của ứng dụng. Một số hệ thống cũng cung cấp tùy chọn lưu tạm thời dữ liệu tải xuống để tránh mất dữ liệu giữa các phiên làm việc, giúp cải thiện hiệu suất tổng thể.

Giao diện này về cơ bản đã khá hoàn chỉnh và có thể xử lý toàn bộ quy trình tải xuống tài nguyên: chúng ta có thể sử dụng giao diện này để khởi động một nhiệm vụ tải xuốngkeo nha cai hom nay, nhận được tiến độ tải xuống trong quá trình thực hiện (với các callback giữa chừng), đồng thời nhận được kết quả khi tải xuống thành công hoặc nhận được thông báo khi tải xuống thất bại (cả thành công và thất bại đều thuộc loại callback kết quả). Tuy nhiên, nếu muốn biết thêm chi tiết về lý do dẫn đến thất bại khi tải xuống không thành công, thì hiện tại giao diện này chưa thể đáp ứng yêu cầu đó.

mạng không ổn định

tiết kiệm thời gian

Về ví dụ mã cho giao diện tải xuống phía trênkeo nha cai hom nay, để có thể trả về mã lỗi chi tiết hơn, mã code của DownloadListener đã được sửa đổi như sau:

								
									
										public
									 interface
									 DownloadListener
									 {
									
    /**
    public
									 static
									 final
									 int
									 SUCCESS
									 =
									 0
									;
									// Thành công
    public
									 static
									 final
									 int
									 INVALID_PARAMS
									 =
									 1
									;
									// Dữ liệu đầu vào sai.
    public
									 static
									 final
									 int
									 NETWORK_UNAVAILABLE
									 =
									 2
									;
									// Mạng không khả dụng
    public
									 static
									 final
									 int
									 UNKNOWN_HOST
									 =
									 3
									;
									// Tên miền không thể phân giải
    public
									 static
									 final
									 int
									 CONNECT_TIMEOUT
									 =
									 4
									;
									// Kết nối quá thời gian
    public
									 static
									 final
									 int
									 HTTP_STATUS_NOT_OK
									 =
									 5
									;
									// Yêu cầu tải về trả về mã khác 200
    public
									 static
									 final
									 int
									 SDCARD_NOT_EXISTS
									 =
									 6
									;
									// Thẻ SD không tồn tại (không có nơi để lưu tài nguyên tải xuống).
    public
									 static
									 final
									 int
									 SD_CARD_NO_SPACE_LEFT
									 =
									 7
									;
									// Không đủ không gian trên thẻ SD (không có nơi để lưu tài nguyên tải xuống).
    public
									 static
									 final
									 int
									 READ_ONLY_FILE_SYSTEM
									 =
									 8
									;
									// Hệ thống tệp chỉ đọc (không có nơi để lưu tài nguyên tải xuống).
    public
									 static
									 final
									 int
									 LOCAL_IO_ERROR
									 =
									 9
									;
									// Lỗi liên quan đến việc lưu trữ SD cục bộ.
    public
									 static
									 final
									 int
									 UNKNOWN_FAILED
									 =
									 10
									;
									// Lỗi chưa xác định khác

    /** * Khi tải xuống thành công sẽ thực hiệ * @param url Địa chỉ nguồn của tài nguyên. * @param localPath Vị trí lưu trữ tài nguyên sau khi tải xuống. * Ngoài rasv 88, hệ thống cũng có thể kiểm tra tính toàn vẹn của dữ liệu tải xuống để đảm bảo rằng tài nguyên đã được lưu đúng cách. */
    void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									);
									
    /** * Khi việc tải xuống thất bạisv 88, hàm callback sẽ được kích hoạt. * @param url Đường dẫn tài nguyên. * @param errorCode Mã lỗi, giúp xác định chính xác vấn đề xảy ra trong quá trình tải xuống. * @param errorMessage Mô tả ngắn gọn về lỗi, hỗ trợ người dùng hoặc nhà phát triển hiểu rõ hơn về lý do gây ra sự cố. */
    void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									);
									

    /** * Hàm callback để theo dõi tiến độ tải xuống. * @param url Địa chỉ nguồn của tài nguyên. * @param downloadedSize Kích thước đã tải thành công cho đến thời điểm hiện tại. * @param totalSize Tổng kích thước của tài nguyên cần tải. * Ngoài rasv 88, bạn có thể thêm các thông tin bổ sung như % hoàn thành hoặc thời gian dự kiến còn lại trong phiên bản tùy chỉnh của hàm này. */
    void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									);
									
}
									

								

Trong iOSsv 88, Foundation Framework cung cấp một lớp NSError để xử lý và đóng gói các lỗi một cách có hệ thống trong ứng dụng. NSError cho phép gói gọn mã lỗi theo cách rất phổ quát và phân loại chúng thành các domain khác nhau, từ đó giúp lập trình viên dễ dàng quản lý và xác định nguyên nhân của vấn đề. Đây là một công cụ tuyệt vời khi định nghĩa các giao diện trả về lỗi, đặc biệt là trong các trường hợp xử lý kết quả thất bại, nơi mà việc cung cấp thông tin rõ ràng và chính xác về lỗi là vô cùng cần thiết.

Giao diện gọi và giao diện callback cần có mối quan hệ rõ ràng.

Chúng ta sẽ phân tích vấn đề này thông qua một ví dụ định nghĩa giao diện thực tế.

Bạn có thể xem qua định nghĩa giao diện của tường điểm quảng cáo video từ một nền tảng quảng cáo trong nước (để dễ quan sátbxh ngoai hang anh, một số mã không liên quan đã được lược bỏ).

								
									
										@class
									 IndependentVideoManager
									;
									

@protocol
									 IndependentVideoManagerDelegate
									 <
									NSObject
									>
									
@optional
									
#pragma mark - Sự kiện thông báo hiện thị quảng cáo video Hàm này sẽ được kích hoạt khi quảng cáo video được hiển thị. Nó đóng vai trò quan trọng trong việc theo dõi và xử lý các sự kiện liên quan đến quá trình hiện thị quảng cáosv 88, giúp bạn có thể đo lường hiệu quả chiến dịch quảng cáo hoặc thực hiện những thao tác cần thiết khi quảng cáo video xuất hiện trước người dùng. Bạn có thể tận dụng sự kiện này để: - Ghi nhận thời điểm quảng cáo được phát. - Kiểm tra trạng thái của quảng cáo (đã hiện thị thành công hay chưa). - Tích hợp thêm các tính năng tương tác khác với người dùng khi quảng cáo video đang hiển thị. Việc xử lý đúng cách sự kiện này sẽ góp phần nâng cao trải nghiệm người dùng và tối ưu hóa hiệu suất của ứng dụng.
...
									

Quản lý điểm - gọi lại callback Bạn có thể dễ dàng quản lý và theo dõi số điểm của người dùng với tính năng này. Nó cho phép bạn thiết lập các quy tắc linh hoạt để cộng hoặc trừ điểm dựa trên hành động cụ thể của người dùng. Hãy tưởng tượng rằng mỗi hành động của người dùngkeo nha cai hom nay, từ đăng nhập hàng ngày đến chia sẻ sản phẩm, đều có thể được thưởng hoặc phạt bằng điểm. Hãy tạo ra một hệ thống phần thưởng hấp dẫn để khuyến khích người dùng tương tác nhiều hơn với ứng dụng của bạn. Bạn cũng có thể đặt giới hạn mức điểm tối đa và tối thiểu, điều này giúp duy trì sự công bằng trong việc trao thưởng và đảm bảo rằng không ai có thể lợi dụng hệ thống. Hãy biến điểm thành một yếu tố thúc đẩy sự gắn kết giữa người dùng và ứng dụng, đồng thời tạo ra một môi trường cạnh tranh lành mạnh để mọi người luôn cảm thấy hứng thú khi sử dụng sản phẩm của bạn.
...
									

Dưới đây là phần callback trạng thái cho hệ thống tích điểm () độc lập: ```objc // Delegate phương thức thông báo trạng thái của tích điểm - (void)wallDidReceiveStatus:(NSDictionary *)statusInfo { // Trạng thái hiện tại của tích điểm NSString *currentState = statusInfo[@state]; if ([currentState isEqualToString:@available]) { NSLog(@Tích điểm đang sẵn sàng để sử dụng); } else if ([currentState isEqualToString:@disabled]) { NSLog(@Tích điểm đã bị vô hiệu hóa); } else if ([currentState isEqualToString:@loading]) { NSLog(@Đang tải nội dung tích điểm...); } // Các thông tin bổ sung NSNumber *pointBalance = statusInfo[@balance]; NSDate *lastUpdated = [NSDate dateWithTimeIntervalSince1970:[statusInfo[@timestamp] doubleValue]]; NSLog(@Số dư tích điểm: %@ - Được cập nhật lần cuối vào %@sv 88, pointBalance, lastUpdated); } ``` Lưu ý khi sử dụng: - Luôn kiểm tra trạng thái trước khi thực hiện thao tác với tích điểm - Xử lý các trường hợp ngoại lệ như không có kết nối mạng - Cập nhật UI dựa trên thông tin trạng thái và số dư mới nhất Bạn có thể mở rộng thêm các xử lý khác tùy theo yêu cầu cụ thể của ứng dụng.Quản lý độc lập Video
-
									 (
									void
									)
									ivManager
									:(
									IndependentVideoManager
									 *
									)
									manager
									
didCheckEnableStatus
									:(
									BOOL
									)
									enable
									;
									

/** * Video quảng cáo độc lập có sẵn để phát không? * Được gọi sau khi kiểm tra xong trạng thái video độc lập. * * @param Quán lý Video Độc lập * @param có_sẵn sàng */
-
									 (
									void
									)
									ivManager
									:(
									IndependentVideoManager
									 *
									)
									manager
									
isIndependentVideoAvailable
									:(
									BOOL
									)
									available
									;
									


@end
									

@interface
									 IndependentVideoManager
									 :
									 NSObject
									 {
									
    
}
									

@property
									(
									nonatomic
									,
									assign
									)
									id
									<
									IndependentVideoManagerDelegate
									>
									delegate
									;
									

...
									

#pragma mark - Khởi tạo phương pháp liên quan đến khởi động.
...
									

Phần mã độc lập - Phương pháp liên quan đến việc hiển thị tích phân Tính năng này đóng vai trò quan trọng trong việc quản lý và điều hướng các hoạt động liên quan đến tích phân. Đầu tiênbxh ngoai hang anh, chúng ta cần thiết lập một cơ chế tự động để cập nhật số điểm tích lũy cho người dùng mỗi khi họ thực hiện một hành động cụ thể. Điều này có thể bao gồm đăng nhập hàng ngày, chia sẻ nội dung hoặc hoàn thành nhiệm vụ. Tiếp theo, chúng ta sẽ phát triển một hệ thống xếp hạng dựa trên số điểm tích lũy. Người dùng với số điểm cao nhất sẽ được công nhận và thưởng thêm điểm. Điều này không chỉ khuyến khích người dùng tham gia nhiều hơn mà còn tạo ra một môi trường cạnh tranh lành mạnh. Ngoài ra, việc hiển thị số điểm của người dùng cần được tối ưu hóa để đảm bảo rằng nó luôn chính xác và dễ dàng truy cập. Chúng ta nên cung cấp một giao diện trực quan, nơi người dùng có thể theo dõi lịch sử tích điểm của mình và hiểu rõ cách họ đã đạt được những thành tích đó. Với sự kết hợp của các tính năng này, chúng ta có thể tạo ra một nền tảng tích phân hấp dẫn và hiệu quả, mang lại giá trị cao cho cả người dùng lẫn doanh nghiệp./** * Dùng rootViewController của ứng dụng để hiển thị và chọn loại bảng điểm. * Hiển thị video độc lập theo kiểu mô hình xem với rootViewController của ứng dụng. * * @param type Loại bảng điểm được chọn */
-
									 (
									void
									)
									presentIndependentVideo
									;
									

...
									

Bạn có thể kiểm tra xem bảng tích điểm video độc lập có khả dụng hay không bằng cách thực hiện một số thao tác sau. Trước tiênbxh ngoai hang anh, hãy đảm bảo rằng kết nối mạng của bạn ổn định và thiết bị đang hoạt động bình thường. Tiếp theo, mở ứng dụng liên quan đến bảng tích điểm video và kiểm tra xem giao diện có xuất hiện không. Nếu mọi thứ hoạt động trơn tru, bạn sẽ thấy các tùy chọn để kiếm điểm thông qua việ Hãy thử xem một vài video để xác nhận rằng hệ thống đang hoạt động đúng cách. Nếu gặp bất kỳ sự cố nào, hãy kiểm tra lại cài đặt hoặc liên hệ với đội ngũ hỗ trợ kỹ thuật để được giúp đỡ./** * Kiểm tra xem quảng cáo video độc lập có sẵn để phát không */
-
									 (
									void
									)
									checkVideoAvailable
									;
									

#pragma mark - Quản lý điểm tích lũy liên quan đến quảng cáo./** * Kiểm tra số điểm đã đạt đượcbxh ngoai hang anh, dù thành công hay thất bại đều sẽ gọi lại phương thức tương ứng trong đại lý (delegate). * */
-
									 (
									void
									)
									checkOwnedPoint
									;
									
/** * Sử dụng số điểm đã chỉ định và gọi lại phương thức tương ứng trong trình điều phối khi thành công hoặc thất bại (hãy chú ý đặc biệt rằng kiểu tham số là unsigned intbxh ngoai hang anh, tức là số điểm cần tiêu dùng phải là giá trị không âm). * * @param diem Số điểm cần sử dụng */
-
									 (
									void
									)
									consumeWithPointNumber
									:(
									NSUInteger
									)
									point
									;
									

@end
									

								

Hãy cùng phân tích mối quan hệ giữa giao diện gọi và giao diện callback trong đoạn định nghĩa giao diện này.

Bên cạnh giao diện khởi tạobxh ngoai hang anh, khi sử dụng IndependentVideoManager, bạn có thể gọi một số giao diện chính sau đây: - Giao diện cấu hình chất lượng video: Cho phép người dùng tùy chỉnh độ phân giải và tốc độ khung hình phù hợp với thiết bị của mình. - Giao diện điều khiển phát lại: Bao gồm các chức năng như tạm dừng, tiếp tục, tua nhanh hoặc tua lùi đoạn video. - Giao diện quản lý âm thanh: Dành cho việc điều chỉnh âm lượng hoặc bật/tắt tiếng theo ý muốn. - Giao diện hiển thị thông tin: Cung cấp các dữ liệu liên quan đến trạng thái phát video hiện tại. Các giao diện này sẽ giúp bạn tối ưu hóa trải nghiệm khi làm việc vớ

  • Hiển thị video độc lập (presentIndependentVideo).
  • Kiểm tra xem có quảng cáo video nào có thể phát hay không (checkVideoAvailable).
  • Quản lý điểm tích lũy (kiểm tra số điểm đã sở hữu và sử dụng điểm với số lượng cụ thể: checkOwnedPoint và consumeWithPointNumber:). Ngoài rasv 88, hệ thống này còn cho phép người dùng theo dõi chi tiết từng giao dịch điểm của mình, từ đó có thể đưa ra quyết định tiêu dùng thông minh hơn. Một số tính năng nổi bật khác bao gồm xác nhận số dư điểm trước khi thực hiện giao dịch và tự động cập nhật trạng thái sau khi hoàn tất quy đổi hoặc sử dụng điểm.

Giao diện (IndependentVideoManagerDelegate) có thể được chia thành các loại sau đây: 1. **Loại xử lý sự kiện cơ bản**: Cho phép nhận thông báo về trạng thái của video như đang phátbxh ngoai hang anh, tạm dừng hoặc hoàn tất. 2. **Xử lý lỗi**: Cung cấp phương thức để nhận diện và phản hồi khi có lỗi xảy ra trong quá trình tải hoặc phát video. 3. **Quản lý thời gian**: Gồm các hàm liên quan đến việc theo dõi thời gian hiện tại của video, chẳng hạn như thời gian đã xem hay thời gian còn lại. 4. **Tùy chỉnh người dùng**: Cho phép người dùng điều chỉnh thiết lập video như âm lượng, chất lượng hoặc độ phân giải theo ý muốn. 5. **Xử lý dữ liệu tùy chọn**: Được sử dụng để truyền thêm thông tin bổ sung như dữ liệu phân tích hoặc nhận diện người dùng. Các loại giao diện này giúp lập trình viên dễ dàng quản lý và tối ưu hóa trải nghiệm video cho người dùng một cách linh hoạt và hiệu quả.

  • Lớp callback hiển thị quảng cáo video.
  • Bạn có thể tham khảo các phương thức liên quan đến trạng thái của bức tường điểm số (integral wall) như: ivManager:didCheckEnableStatus: và ivManager:isIndependentVideoAvailable:. Những phương thức này giúp kiểm tra tình trạng hoạt động cũng như khả năng phát quảng cáo độc lập trong ứng dụng.
  • Lớp quản lý điểm tích lũy.

Nhìn chungkeo nha cai hom nay, mối liên hệ giữa các phần ở đây khá rõ ràng, và ba loại giao diện callback này cơ bản đều có thể tương ứng một-một với ba phần giao diện gọi trước đó. Ngoài ra, việc sắp xếp này cũng giúp người dùng dễ dàng hiểu được cách thức hoạt động giữa các thành phần, từ đó có thể điều chỉnh hoặc tùy biến hiệu quả hơn theo nhu cầu của mình.

Tuy nhiênbxh ngoai hang anh, có một số chi tiết gây mơ hồ trong các callback liên quan đến trạng thái của hệ thống tích điểm: sau khi người gọi thực hiện phương thức checkVideoAvailable, họ sẽ nhận được hai callback từ lớp quản lý tích điểm (như ivManager:didCheckEnableStatus: và ivManager:isIndependentVideoAvailable:). Về mặt lý thuyết, tên của phương thức checkVideoAvailable dường như chỉ nhằm kiểm tra xem có video quảng cáo nào sẵn sàng để phát hay không. Vì vậy, việc có thêm callback ivManager:isIndependentVideoAvailable: dường như đã đủ để trả về kết quả mong muốn, và có vẻ không cần thiết phải có callback thứ hai là ivManager:didCheckEnableStatus:. Mặt khác, dựa trên ý nghĩa mà ivManager:didCheckEnableStatus thể hiện (xem liệu bảng video có khả dụng hay không), nó có vẻ như sẽ hoạt động ở bất kỳ thời điểm nào phương thức nào được gọi, thay vì chỉ liên quan đến việc gọ Thiết kế của các callback này trong mối quan hệ với các phương thức gọi tương ứng dường như không nhất quán và gây nhầm lẫn cho người dùng. Có lẽ, nếu ivManager:didCheckEnableStatus: được sử dụng để xác định tính năng tổng quát hơn, chẳng hạn như trạng thái hoạt động của toàn bộ hệ thống bảng video, thì nó sẽ hợp lý hơn. Còn hiện tại, cách phân chia này khiến người dùng khó hiểu về mục đích cụ thể cũng như phạm vi áp dụng của từ Điều này có thể làm cho việc tích hợp và sử dụng API trở nên phức tạp hơn so với mong đợi ban đầu.

Ngoài rabxh ngoai hang anh, giao diện IndependentVideoManager cũng gặp phải một số vấn đề trong việc thiết kế tham số ngữ cảnh, và vấn đề này sẽ được đề cập lại ở phần sau của bài viết.

Callback kết quả thành công và callback kết quả thất bại nên loại trừ lẫn nhau.

Khi một nhiệm vụ bất đồng bộ kết thúcsv 88, nó sẽ hoặc gọi hàm trả về kết quả thành công, hoặc gọi hàm trả về kết quả thất bại. Chỉ một trong hai hàm này có thể được kích hoạt. Đây là yêu cầu hiển nhiên, nhưng nếu không cẩn thận trong quá trình triển khai, rất có thể bạn sẽ không tuân thủ được quy tắc này. Thực tế, nhiều lập trình viên đôi khi quên rằng chỉ một trong hai trạng thái này có thể xảy ra, dẫn đến những lỗi khó phát hiện trong mã nguồn của họ.

Giả sử giao diện Downloader mà chúng ta đã đề cập trước đó trong quá trình tạo callback kết quả cuối cùng sẽ có mã như sau:

								
									int
									 errorCode
									 =
									 parseDownloadResult
									(
									result
									);
									
    if
									 (
									errorCode
									 ==
									 SUCCESS
									)
									 {
									
        listener
									.
									downloadSuccess
									(
									url
									,
									 localPath
									)
									
    }
									
    else
									 {
									
        listener
									.
									downloadFailed
									(
									url
									,
									 errorCode
									,
									 getErrorMessage
									(
									errorCode
									));
									
    }
									

								

phải có kết quả trả về

								
									try
									 {
									
        int
									 errorCode
									 =
									 parseDownloadResult
									(
									result
									);
									
        if
									 (
									errorCode
									 ==
									 SUCCESS
									)
									 {
									
            listener
									.
									downloadSuccess
									(
									url
									,
									 localPath
									)
									
        }
									
        else
									 {
									
            listener
									.
									downloadFailed
									(
									url
									,
									 errorCode
									,
									 getErrorMessage
									(
									errorCode
									));
									
        }
									
    }
									
    catch
									 (
									Exception
									 e
									)
									 {
									
        listener
									.
									downloadFailed
									(
									url
									,
									 UNKNOWN_FAILED
									,
									 getErrorMessage
									(
									UNKNOWN_FAILED
									));
									
    }
									

								

Mã đã được sửa đổi như vậykeo nha cai hom nay, đã đảm bảo rằng ngay cả khi xảy ra tình huống không ngờ tới, cũng có thể tạo callback thất bại cho người gọi.

Tuy nhiênsv 88, điều này cũng dẫn đến một vấn đề khác: nếu khi thực hiệ downloadSuccess hoặ downloadFailed, mã triển khai của giao diện phát sinh lỗi ngoại lệ, nó có thể dẫn đến việc gọ downloadFailed thêm một lần nữa. Do đó, việc gọi callback cho kết quả thành công và thất bại sẽ không còn được đảm bảo là độc lập hoặc loại trừ lẫn nhau nữa: hoặc cả hai callback (thành công và thất bại) đều được kích hoạt, hoặc thậm chí xảy ra liên tiếp hai lần callback thất bại. Điều này có thể gây ra các hậu quả không mong muốn trong logic ứng dụng, chẳng hạn như làm gián đoạn quy trình xử lý dữ liệu hoặc tạo ra các hành vi không ổn định trong hệ thống. Vì vậy, việc kiểm soát chặt chẽ ngoại lệ trong các callback là điều cần thiết để tránh những tình huống phức tạp này.

Đã xảy ra lỗi: {e}

								
									int
									 errorCode
									;
									
    try
									 {
									
        errorCode
									 =
									 parseDownloadResult
									(
									result
									);
									
    }
									
    catch
									 (
									Exception
									 e
									)
									 {
									
        errorCode
									 =
									 UNKNOWN_FAILED
									;
									
    }
									
    if
									 (
									errorCode
									 ==
									 SUCCESS
									)
									 {
									
        try
									 {
									
            listener
									.
									downloadSuccess
									(
									url
									,
									 localPath
									)
									
        }
									
        catch
									 (
									Throwable
									 e
									)
									 {
									
            e
									.
									printStackTrace
									();
									
        }
									
    }
									
    else
									 {
									
        try
									 {
									
            listener
									.
									downloadFailed
									(
									url
									,
									 errorCode
									,
									 getErrorMessage
									(
									errorCode
									));
									
        }
									
        catch
									 (
									Throwable
									 e
									)
									 {
									
            e
									.
									printStackTrace
									();
									
        }
									
    }
									

								

Mã callback phức tạp hơn một chút nhưng cũng an toàn hơn.

Mô hình luồng củ

Cơ sở kỹ thuật chính cho việc thực hiện giao diện bất đồng bộ chủ yếu có hai:

  • Đa luồng (mã thực hiện của giao diện nằm trong luồng bất đồng bộ khác với luồng gọi).
  • Bạn có thể thực hiện I/O bất đồng bộ (như yêu cầu mạng bất đồng bộ). Trong trường hợp nàykeo nha cai hom nay, ngay cả khi toàn bộ chương trình chỉ có một luồng duy nhất, bạn vẫn có thể tạo ra giao diện bất đồng bộ. Điều này giúp tăng cường hiệu suất tổng thể mà không cần phụ thuộc vào việc mở rộng số lượng luồng, cho phép chương trình của bạn tiếp tục xử lý các tác vụ khác trong khi chờ đợi phản hồi từ mạng.

Dù là trường hợp nàobxh ngoai hang anh, chúng ta đều cần có định nghĩa rõ ràng về môi trường luồng khi callback xảy ra.

Về cơ bảnkeo nha cai hom nay, có ba chế độ chính để định nghĩa môi trường luồng thực thi callback kết quả:

  1. Gọi giao diện ở luồng nào thì callback kết quả sẽ xảy ra ở luồng đó.
  2. Dù bạn gọi giao diện từ luồng nàokeo nha cai hom nay, kết quả trả về sẽ luôn được xử lý trên luồng chính (chẳng hạn như trong Android với AsyncTask). Điều này có nghĩa là bất kể tác vụ nền đang chạy ở đâu, khi hoàn thành, nó sẽ tự động chuyển kết quả về luồng chính để cập nhật giao diện người dùng hoặc thực hiện các thao tác khác cần thiết.
  3. Người gọi có thể tùy chỉnh luồng mà giao diện sẽ xảy ra (ví dụ như trong iOSsv 88, với NSURLConnection, bạn có thể đặt luồng chạy của callback thông qua phương thức scheduleInRunLoop:forMode:). Điều này cho phép bạn kiểm soát chính xác cách và nơi các sự kiện được xử lý, mang lại tính linh hoạt cao cho ứng dụng của mình. Bạn có thể chọn giữa các chế độ khác nhau để đảm bảo hiệu suất tối ưu hoặc đáp ứng nhu cầu cụ thể của từng trường hợp sử dụng.

Rõ ràng chế độ thứ ba linh hoạt nhất vì nó bao gồm cả hai chế độ đầu tiên.

Để có thể điều phối mã thực thi sang các luồng khácsv 88, chúng ta cần sử dụng trong phần trước. Xử lý bất đồng bộ trong Android và iOS (phần một) —— Tổng quan. Cuối cùngsv 88, một số công nghệ mà chúng ta có thể nhắc đến như GCD và NSOperationQueue trong iOS, cũng như phương thức performSelectorXXX, đều là những công cụ mạnh mẽ giúp quản lý luồng thực thi. Ở phía Android, các kỹ thuật như ExecutorService, AsyncTask, Handler cũng đóng vai trò tương tự trong việc điều độ tác vụ giữa các luồng. Tuy nhiên, cần lưu ý rằng ExecutorService chỉ có thể được sử dụng để phân phối công việc cho các luồng phụ (background thread), chứ không thể sử dụng để gửi tác vụ trực tiếp lên luồng chính (main thread). Để hiểu rõ hơn về bản chất của việc điều độ luồng, chúng ta cần nhận ra rằng việc gửi một đoạn mã nào đó để thực thi trên một luồng nhất định chỉ có thể xảy ra nếu như luồng đó có một vòng lặp sự kiện (Event Loop). Điều này có nghĩa là luồng đó sẽ liên tục kiểm tra và xử lý các tin nhắn từ hàng đợi (message queue). Khi chúng ta tiến hành điều độ luồng, điều đó đồng nghĩa với việc chúng ta đang thêm tin nhắn vào hàng đợi này. Hàng đợi này đã được hệ thống đảm bảo an toàn trong môi trường đa luồng (thread-safe), nhờ đó nhà phát triển không cần phải lo lắng quá nhiều về vấn đề đồng bộ hóa. Trong lập trình ứng dụng di động, hệ điều hành luôn đảm bảo rằng luồng chính (main thread) sẽ luôn có một Event Loop sẵn sàng hoạt động. Tuy nhiên, đối với các luồng khác, nhà phát triển cần chủ động sử dụng các công nghệ phù hợp để tạo ra và quản lý luồng đó. Điều này không chỉ giúp tối ưu hiệu suất mà còn đảm bảo tính ổn định của ứng dụng. Nhờ có sự hỗ trợ từ các công cụ như vậy, việc điều độ luồng trở nên dễ dàng hơn bao giờ hết, đồng thời mang lại trải nghiệm mượt mà cho người dùng cuối.

Trong phần lớn các trường hợp lập trình clientbxh ngoai hang anh, chúng ta thường mong muốn callback kết quả xảy ra trên luồng chính (main thread), bởi vì đây là thời điểm mà giao diện người dùng (UI) thường được cập nhật. Còn việc callback ở giữa sẽ chạy trên luồng nào phụ thuộc vào ngữ cảnh cụ thể của ứng dụng. Trong ví dụ về Downloader trước đó, callback downloadProgress được sử dụng để phản hồi tiến độ tải xuống. Thông thường, tiến độ tải xuống cũng được thiết kế để hiển thị trên giao diện, do đó việc điều độ callback downloadProgress để thực thi trên luồng chính sẽ mang lại hiệu quả tốt hơn. Điều này giúp đảm bảo rằng cả quá trình tải xuống và việc cập nhật UI đều diễn ra trơn tru và không gây ra lỗi hiển thị.

Tham số ngữ cảnh callback (thông số truyền qua).

Khi thực hiện một yêu cầu giao tiếp bất đồng bộkeo nha cai hom nay, chúng ta thường cần tạm thời lưu giữ một tập hợp dữ liệu ngữ cảnh liên quan đến lần gọi đó. Khi nhiệm vụ bất đồng bộ hoàn thành và callback được kích hoạt, chúng ta có thể truy cập lại ngữ cảnh này để xử lý thêm. Trong thực tế, việc lưu trữ ngữ cảnh này có thể bao gồm nhiều thông tin khác nhau như ID yêu cầu, trạng thái hiện tại của ứng dụng, hoặc các tham số đầu vào mà hệ thống cần khi trả về kết quả. Điều này giúp đảm bảo rằng ngay cả khi luồng thực thi bị chia cắt do tính chất bất đồng bộ, chúng ta vẫn có thể duy trì tính nhất quán trong quá trình xử lý. Ví dụ, giả sử bạn đang phát triển một ứng dụng web và cần gọi API bên thứ ba để lấy dữ liệu từ cơ sở dữ liệu. Trong thời gian chờ đợi phản hồi từ API, bạn có thể lưu ngữ cảnh yêu cầu vào một hàng đợi nội bộ hoặc lưu tạm thời trong bộ nhớ cache. Khi nhận được phản hồi, bạn sẽ có toàn quyền truy cập vào ngữ cảnh đã lưu trước đó để hoàn tất quá trình xử lý. Điều này không chỉ tăng cường khả năng quản lý trạng thái mà còn giúp ứng dụng trở nên linh hoạt hơn trong việc xử lý các tình huống bất định xảy ra trong quá trình vận hành.

Hãy tiếp tục sử dụng công cụ tải xuống mà chúng ta đã đề cập trước đó làm ví dụ. Để có thể thảo luận rõ ràng hơn về các trường hợp khác nhaukeo nha cai hom nay, ở đây chúng ta sẽ đặt ra một tình huống phức tạp hơn chút ít. Giả sử rằng chúng ta cần tải xuống nhiều bộ sticker, mỗi bộ sticker này bao gồm nhiều tệp hình ảnh biểu cảm. Sau khi tải hoàn tất tất cả các hình ảnh biểu cảm, chúng ta cần cài đặt các bộ sticker này vào máy tính cá nhân (có thể liên quan đến việc chỉnh sửa cơ sở dữ liệu cục bộ), nhằm giúp người dùng có thể sử dụng chúng trong bảng nhập liệu. Ngoài ra, để đảm bảo quá trình này diễn ra suôn sẻ, chúng ta cũng cần phải kiểm tra xem hệ thống của người dùng có đủ không gian lưu trữ và các phần mềm hỗ trợ cần thiết hay không. Điều này sẽ giúp tránh những lỗi không đáng có trong quá trình cài đặt hoặc thậm chí là mất mát dữ liệu. Đồng thời, cần có một cơ chế tự động sao lưu trước khi tiến hành thay đổi bất kỳ thông tin nào trong cơ sở dữ liệu, nhằm đảm bảo tính toàn vẹn dữ liệu và khả năng khôi phục trong trường hợp xảy ra sự cố.

Giả sử cấu trúc dữ liệu gói biểu tượng được định nghĩa như sau:

								
									
										public
									 class
									 EmojiPackage
									 {
									
    /**
    public
									 long
									 emojiId
									;
									
    /**
    public
									 List
									<
									String
									>
									 emojiUrls
									;
									
}
									

								

Trong quá trình tải xuốngsv 88, chúng ta cần lưu một cấu trúc ngữ cảnh như sau:

								
									
										public
									 class
									 EmojiDownloadContext
									 {
									
    /**
    public
									 EmojiPackage
									 emojiPackage
									;
									
    /**
    public
									 int
									 downloadedEmoji
									;
									
    /**
    public
									 List
									<
									String
									>
									 localPathList
									 =
									 new
									 ArrayList
									<
									String
									>();
									
}
									

								

Giả sử bộ tải xuống biểu tượng mà chúng ta muốn thực hiện tuân thủ định nghĩa giao diện sau đây:

								
									
										public
									 interface
									 EmojiDownloader
									 {
									
    /** * Bắt đầu tải xuống gói biểu tượng cảm xúc được chỉ định * @param packageEmoji */
    void
									 startDownloadEmoji
									(
									EmojiPackage
									 emojiPackage
									);
									

    /** * Tại đâybxh ngoai hang anh, chúng ta định nghĩa giao diện liên quan đến các hàm, bỏ qua điều này. Đây không phải là trọng tâm mà chúng ta đang tìm hiểu. */
    // TODO: Định nghĩa giao diệ
}
									

								

Nếu sử dụng giao diện Downloader đã có sẵn để thực hiện công cụ tải xuống bộ sưu tập stickersv 88, tùy thuộc vào cách truyền bối cảnh, chúng ta có thể áp dụng ba phương pháp khác nhau như sau:

(1) Lưu trữ toàn cầu một ngữ cảnh.

Lưu ý: "Toàn cầu" ở đây là so với bên trong một bộ tải xuống biểu tượng. Mã như sau:

								
									
										public
									 class
									 MyEmojiDownloader
									 implements
									 EmojiDownloader
									,
									 DownloadListener
									 {
									
    /**
    private
									 EmojiDownloadContext
									 downloadContext
									;
									
    private
									 Downloader
									 downloader
									;
									

    public
									 MyEmojiDownloader
									()
									 {
									
        Bạn có thể khởi tạo một đối tượng tải xuống. MyDownloader là một phiên bản cụ thể của giao diện Downloadersv 88, được thiết kế để thực hiện các tác vụ tải xuống theo cách riêng của nó.
        downloader
									 =
									 new
									 MyDownloader
									();
									
        downloader
									.
									setListener
									(
									this
									);
									
    }
									

    @Override
									
    public
									 void
									 startDownloadEmoji
									(
									EmojiPackage
									 emojiPackage
									)
									 {
									
        if
									 (
									downloadContext
									 ==
									 null
									)
									 {
									
            // Tạo dữ liệu ngữ cảnh tải xuống.
            downloadContext
									 =
									 new
									 EmojiDownloadContext
									();
									
            downloadContext
									.
									emojiPackage
									 =
									 emojiPackage
									;
									
            // Bắt đầu tải xuống tệp hình ảnh biểu tượng thứ 0.
            downloader
									.
									startDownload
									(
									emojiPackage
									.
									emojiUrls
									.
									get
									(
									0
									),
									
                    getLocalPathForEmoji
									(
									emojiPackage
									,
									 0
									));
									
        }
									
    }
									

    @Override
									
    public
									 void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									)
									 {
									
        downloadContext
									.
									localPathList
									.
									add
									(
									localPath
									);
									
        downloadContext
									.
									downloadedEmoji
									++;
									
        EmojiPackage
									 emojiPackage
									 =
									 downloadContext
									.
									emojiPackage
									;
									
        if
									 (
									downloadContext
									.
									downloadedEmoji
									 <
									 emojiPackage
									.
									emojiUrls
									.
									size
									())
									 {
									
            // Chưa tải xongkeo nha cai hom nay, tiếp tục tải xuống tệp hình ảnh biểu tượng tiếp theo.
            String
									 nextUrl
									 =
									 emojiPackage
									.
									emojiUrls
									.
									get
									(
									downloadContext
									.
									downloadedEmoji
									);
									
            downloader
									.
									startDownload
									(
									nextUrl
									,
									
                    getLocalPathForEmoji
									(
									emojiPackage
									,
									 downloadContext
									.
									downloadedEmoji
									));
									
        }
									
        else
									 {
									
            // Đã tải xong.
            installEmojiPackageLocally
									(
									emojiPackage
									,
									 downloadContext
									.
									localPathList
									);
									
            downloadContext
									 =
									 null
									;
									
        }
									
    }
									

    @Override
									
    public
									 void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									)
									 {
									
        ...
									
    }
									

    @Override
									
    public
									 void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 String
									 getLocalPathForEmoji
									(
									EmojiPackage
									 emojiPackage
									,
									 int
									 i
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 void
									 installEmojiPackageLocally
									(
									EmojiPackage
									 emojiPackage
									,
									 List
									<
									String
									>
									 localPathList
									)
									 {
									
        ...
									
    }
									
}
									

								

Hạn chế của cách làm này nằm ở chỗ chỉ có thể tải xuống một bộ biểu cảm tại một thời điểm. Bạn phải đợi quá trình tải xuống hoàn tất cho bộ biểu cảm trước đó trước khi có thể bắt đầu tải xuống bộ biểu cảm tiếp theo. Điều này đôi khi khiến việc sử dụng trở nên khá chậm rãi và mất nhiều thời gian hơn so với mong đợi.

lưu toàn cục một bản ngữ cảnh

(2) Sử dụng mối quan hệ ánh xạ để lưu ngữ cảnh.

Dựa trên định nghĩa hiện tại của giao diện Downloaderbxh ngoai hang anh, chúng ta chỉ có thể sử dụng URL làm khóa để ánh xạ trong cấu trúc này. Vì một bộ sticker thường bao gồm nhiều URL khác nhau, chúng ta buộc phải tạo ra một bản ghi ngữ cảnh riêng cho từng URL. Dưới đây là đoạn mã tương ứng: ```python class Downloader: def __init__(self): context_mapping = {} def add_context(self, url, context): context_mapping: context_mapping[url] = [] context_mapping[url].append(context) def get_context(self, url): context_mapping.get(url, []) ``` Trong đoạn mã trên, mỗi khi nhận được một URL, chúng ta sẽ kiểm tra xem nó đã tồn tại trong bản đồ ngữ cảnh hay chưa. Nếu chưa, chúng ta khởi tạo một danh sách trống để lưu trữ ngữ cảnh liên quan. Sau đó, bất kỳ ngữ cảnh nào thuộc về URL đó đều được thêm vào danh sách. Điều này đảm bảo rằng mọi URL trong bộ sticker đều có thể được xử lý một cách độc lập và linh hoạt.

								
									
										public
									 class
									 MyEmojiDownloader
									 implements
									 EmojiDownloader
									,
									 DownloadListener
									 {
									
    /** * Lưu giữ mối liên kết giữa URL và ngữ cảnh tải xuống biểu tượng cảm xúc. * Mỗi URL sẽ được ánh xạ đến một đối tượng ngữ cảnh tải xuống cụ thể. */
    private
									 Map
									<
									String
									,
									 EmojiDownloadContext
									>
									 downloadContextMap
									;
									
    private
									 Downloader
									 downloader
									;
									

    public
									 MyEmojiDownloader
									()
									 {
									
        downloadContextMap
									 =
									 new
									 HashMap
									<
									String
									,
									 EmojiDownloadContext
									>();
									
        Bạn có thể khởi tạo một đối tượng tải xuống. MyDownloader là một phiên bản cụ thể của giao diện Downloadersv 88, được thiết kế để thực hiện các tác vụ tải xuống theo cách riêng của nó.
        downloader
									 =
									 new
									 MyDownloader
									();
									
        downloader
									.
									setListener
									(
									this
									);
									
    }
									

    @Override
									
    public
									 void
									 startDownloadEmoji
									(
									EmojiPackage
									 emojiPackage
									)
									 {
									
        // Tạo dữ liệu ngữ cảnh tải xuống.
        EmojiDownloadContext
									 downloadContext
									 =
									 new
									 EmojiDownloadContext
									();
									
        downloadContext
									.
									emojiPackage
									 =
									 emojiPackage
									;
									
        // Tạo mối quan hệ ánh xạ cho mỗi URL.
        for
									 (
									String
									 emojiUrl
									 :
									 emojiPackage
									.
									emojiUrls
									)
									 {
									
            downloadContextMap
									.
									put
									(
									emojiUrl
									,
									 downloadContext
									);
									
        }
									
        // Bắt đầu tải xuống tệp hình ảnh biểu tượng thứ 0.
        downloader
									.
									startDownload
									(
									emojiPackage
									.
									emojiUrls
									.
									get
									(
									0
									),
									
                getLocalPathForEmoji
									(
									emojiPackage
									,
									 0
									));
									
    }
									

    @Override
									
    public
									 void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									)
									 {
									
        EmojiDownloadContext
									 downloadContext
									 =
									 downloadContextMap
									.
									get
									(
									url
									);
									
        downloadContext
									.
									localPathList
									.
									add
									(
									localPath
									);
									
        downloadContext
									.
									downloadedEmoji
									++;
									
        EmojiPackage
									 emojiPackage
									 =
									 downloadContext
									.
									emojiPackage
									;
									
        if
									 (
									downloadContext
									.
									downloadedEmoji
									 <
									 emojiPackage
									.
									emojiUrls
									.
									size
									())
									 {
									
            // Chưa tải xongsv 88, tiếp tục tải xuống tệp hình ảnh biểu tượng tiếp theo.
            String
									 nextUrl
									 =
									 emojiPackage
									.
									emojiUrls
									.
									get
									(
									downloadContext
									.
									downloadedEmoji
									);
									
            downloader
									.
									startDownload
									(
									nextUrl
									,
									
                    getLocalPathForEmoji
									(
									emojiPackage
									,
									 downloadContext
									.
									downloadedEmoji
									));
									
        }
									
        else
									 {
									
            // Đã tải xong.
            installEmojiPackageLocally
									(
									emojiPackage
									,
									 downloadContext
									.
									localPathList
									);
									
            // Xóa mối quan hệ ánh xạ cho mỗi URL.
            for
									 (
									String
									 emojiUrl
									 :
									 emojiPackage
									.
									emojiUrls
									)
									 {
									
                downloadContextMap
									.
									remove
									(
									emojiUrl
									);
									
            }
									
        }
									
    }
									

    @Override
									
    public
									 void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									)
									 {
									
        ...
									
    }
									

    @Override
									
    public
									 void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 String
									 getLocalPathForEmoji
									(
									EmojiPackage
									 emojiPackage
									,
									 int
									 i
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 void
									 installEmojiPackageLocally
									(
									EmojiPackage
									 emojiPackage
									,
									 List
									<
									String
									>
									 localPathList
									)
									 {
									
        ...
									
    }
									
}
									

								

Phương pháp này cũng có những hạn chế: không phải lúc nào cũng có thể tìm được một biến duy nhất để tham chiếu đến dữ liệu ngữ cảnh. Trong ví dụ về công cụ tải xuống sticker nàybxh ngoai hang anh, biến mà lẽ ra nên đại diện duy nhất cho việc tải xuống là emojiId, nhưng trong giao diện callback của lớp Downloader lại không thể lấy được giá trị này. Do đó, giải pháp thay thế là tạo một bản đồ từ mỗi URL đến dữ liệu ngữ cảnh. Tuy nhiên, cách làm này dẫn đến hậu quả là nếu hai bộ sticker khác nhau chia sẻ cùng một URL, sẽ xảy ra xung đột. Ngoài ra, việc triển khai phương pháp này khá phức tạp và đòi hỏi nhiều nỗ lực hơn.

(3) Tạo một phiên bản giao diện cho mỗi nhiệm vụ bất đồng bộ.

Tạo nhiệm vụ bất đồng bộ {task_id} với dữ liệu: {data}

								
									
										public
									 class
									 MyEmojiDownloader
									 implements
									 EmojiDownloader
									 {
									
    @Override
									
    public
									 void
									 startDownloadEmoji
									(
									EmojiPackage
									 emojiPackage
									)
									 {
									
        // Tạo dữ liệu ngữ cảnh tải xuống.
        EmojiDownloadContext
									 downloadContext
									 =
									 new
									 EmojiDownloadContext
									();
									
        downloadContext
									.
									emojiPackage
									 =
									 emojiPackage
									;
									
        // Tạo một Downloader mới cho mỗi lần tải xuống.
        final
									 EmojiUrlDownloader
									 downloader
									 =
									 new
									 EmojiUrlDownloader
									();
									
        // Lưu dữ liệu ngữ cảnh vào instance củ
        downloader
									.
									downloadContext
									 =
									 downloadContext
									;
									

        downloader
									.
									setListener
									(
									new
									 DownloadListener
									()
									 {
									
            @Override
									
            public
									 void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									)
									 {
									
                EmojiDownloadContext
									 downloadContext
									 =
									 downloader
									.
									downloadContext
									;
									
                downloadContext
									.
									localPathList
									.
									add
									(
									localPath
									);
									
                downloadContext
									.
									downloadedEmoji
									++;
									
                EmojiPackage
									 emojiPackage
									 =
									 downloadContext
									.
									emojiPackage
									;
									
                if
									 (
									downloadContext
									.
									downloadedEmoji
									 <
									 emojiPackage
									.
									emojiUrls
									.
									size
									())
									 {
									
                    // Chưa tải xongkeo nha cai hom nay, tiếp tục tải xuống tệp hình ảnh biểu tượng tiếp theo.
                    String
									 nextUrl
									 =
									 emojiPackage
									.
									emojiUrls
									.
									get
									(
									downloadContext
									.
									downloadedEmoji
									);
									
                    downloader
									.
									startDownload
									(
									nextUrl
									,
									
                            getLocalPathForEmoji
									(
									emojiPackage
									,
									 downloadContext
									.
									downloadedEmoji
									));
									
                }
									
                else
									 {
									
                    // Đã tải xong.
                    installEmojiPackageLocally
									(
									emojiPackage
									,
									 downloadContext
									.
									localPathList
									);
									
                }
									
            }
									

            @Override
									
            public
									 void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									)
									 {
									
                //TODO:
									
            }
									

            @Override
									
            public
									 void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									)
									 {
									
                //TODO:
									
            }
									
        });
									

        // Bắt đầu tải xuống tệp hình ảnh biểu tượng thứ 0.
        downloader
									.
									startDownload
									(
									emojiPackage
									.
									emojiUrls
									.
									get
									(
									0
									),
									
                getLocalPathForEmoji
									(
									emojiPackage
									,
									 0
									));
									
    }
									

    private
									 static
									 class
									 EmojiUrlDownloader
									 extends
									 MyDownloader
									 {
									
        public
									 EmojiDownloadContext
									 downloadContext
									;
									
    }
									

    /**
    private
									 String
									 getLocalPathForEmoji
									(
									EmojiPackage
									 emojiPackage
									,
									 int
									 i
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 void
									 installEmojiPackageLocally
									(
									EmojiPackage
									 emojiPackage
									,
									 List
									<
									String
									>
									 localPathList
									)
									 {
									
        ...
									
    }
									
}
									

								

Điều này tất nhiên có những nhược điểm rõ ràng: việc tạo ra một tải xuống cho mỗi tác vụ tải xuống sẽ đi ngược lại mục đích ban đầu của giao diệ Điều này sẽ dẫn đến việc tạo ra rất nhiều instance dư thừa. Đặc biệtkeo nha cai hom nay, khi instance của giao diện là một đối tượng lớn và nặng, cách làm này sẽ gây ra rất nhiều chi phí không cần thiết. Một khi số lượng tác vụ tăng cao, hệ thống sẽ nhanh chóng tiêu tốn tài nguyên và trở nên kém hiệu quả. Điều này không chỉ ảnh hưởng đến hiệu suất tổng thể mà còn có thể làm chậm tiến độ xử lý các tác vụ khác.

thông tin trạng thái

  • ngữ cảnh (context).
  • Tham số truyền qua.
  • callbackData
  • cookie
  • userInfo

Dù tên của tham số này là gìkeo nha cai hom nay, vai trò của nó vẫn luôn giống nhau: khi gọi một giao diện bất đồng bộ, tham số này sẽ được truyền vào và khi giao diện trả về callback, nó cũng sẽ được truyền trở lại. Tham số ngữ cảnh này được định nghĩa bởi người gọi phía trên, còn phần thực hiện giao diện dưới tầng cơ sở không cần hiểu ý nghĩa của nó, mà chỉ cần đảm bảo chuyển tiếp giá trị đó mà thôi. Người lập trình ở tầng cao hơn có thể linh hoạt sử dụng tham số ngữ cảnh này để lưu trữ thông tin bổ sung cần thiết cho việc xử lý sau này. Điều này giúp tăng tính linh hoạt cho hệ thống, cho phép các giao diện bên dưới tập trung vào nhiệm vụ chính mà không cần quan tâm đến ngữ nghĩa cụ thể của dữ liệu. Nhờ vậy, quá trình phát triển và bảo trì mã nguồn trở nên dễ dàng hơn, đồng thời giảm thiểu rủi ro sai sót do sự phụ thuộc lẫn nhau giữa các tầng trong hệ thống.

Giao diện Downloader hỗ trợ tham số ngữ cảnh đã được thay đổi như sau:

								
									
										public
									 interface
									 Downloader
									 {
									
    /** * Thiết lập trình lắng nghe sự kiện. * @param listener Trình lắng nghe cần được cấu hình. */
    void
									 setListener
									(
									DownloadListener
									 listener
									);
									
    /** * Khởi động quá trình tải xuống tài nguyên. * @param url Địa chỉ của tài nguyên cần tải xuống. * @param localPath Vị trí lưu trữ trên thiết bị sau khi tải xuống. * @param contextData Dữ liệu ngữ cảnhbxh ngoai hang anh, sẽ được truyền trở lại qua giao diệ Có thể là bất kỳ loại dữ liệu nào. */
    void
									 startDownload
									(
									String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									);
									
}
									
public
									 interface
									 DownloadListener
									 {
									
    /**
    public
									 static
									 final
									 int
									 SUCCESS
									 =
									 0
									;
									// Thành công
    public
									 static
									 final
									 int
									 INVALID_PARAMS
									 =
									 1
									;
									// Dữ liệu đầu vào sai.
    public
									 static
									 final
									 int
									 NETWORK_UNAVAILABLE
									 =
									 2
									;
									// Mạng không khả dụng
    public
									 static
									 final
									 int
									 UNKNOWN_HOST
									 =
									 3
									;
									// Tên miền không thể phân giải
    public
									 static
									 final
									 int
									 CONNECT_TIMEOUT
									 =
									 4
									;
									// Kết nối quá thời gian
    public
									 static
									 final
									 int
									 HTTP_STATUS_NOT_OK
									 =
									 5
									;
									// Yêu cầu tải về trả về mã khác 200
    public
									 static
									 final
									 int
									 SDCARD_NOT_EXISTS
									 =
									 6
									;
									// Thẻ SD không tồn tại (không có nơi để lưu tài nguyên tải xuống).
    public
									 static
									 final
									 int
									 SD_CARD_NO_SPACE_LEFT
									 =
									 7
									;
									// Không đủ không gian trên thẻ SD (không có nơi để lưu tài nguyên tải xuống).
    public
									 static
									 final
									 int
									 READ_ONLY_FILE_SYSTEM
									 =
									 8
									;
									// Hệ thống tệp chỉ đọc (không có nơi để lưu tài nguyên tải xuống).
    public
									 static
									 final
									 int
									 LOCAL_IO_ERROR
									 =
									 9
									;
									// Lỗi liên quan đến việc lưu trữ SD cục bộ.
    public
									 static
									 final
									 int
									 UNKNOWN_FAILED
									 =
									 10
									;
									// Lỗi chưa xác định khác

    /** * Hàm trả về kết quả khi tải xuống thành công. * @param url Đường dẫn đến tài nguyên cần tải. * @param localPath Vị trí lưu trữ tài nguyên sau khi tải xuống. * @param contextData Dữ liệu ngữ cảnh liên quan. */
    void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									);
									
    /** * Hàm trả về khi việc tải xuống thất bại. * @param url Địa chỉ tài nguyên cần tải. * @param errorCode Mã lỗikeo nha cai hom nay, dùng để xác định loại vấn đề xảy ra. * @param errorMessage Mô tả ngắn gọn về lỗi giúp người sử dụng hiểu rõ hơn về nguyên nhân. * @param contextData Dữ liệu ngữ cảnh liên quan, có thể hỗ trợ thêm thông tin cho quá trình xử lý lỗi. */
    void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									,
									 Object
									 contextData
									);
									

    /** * Hàm trả về tiến độ tải xuống. * @param dia_chi_nguon Địa chỉ nguồn tài nguyên. * @param kich_thuoc_da_tai_xuong Kích thước đã tải xuống. * @param kich_thuoc_tong Tổng kích thước của tài nguyên. * @param du_lieu_khung Context data liên quan. */
    void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									,
									 Object
									 contextData
									);
									
}
									

								

Sử dụng giao diện Downloader mới nhất nàykeo nha cai hom nay, bộ tải xuống biểu tượng trước đó đã có cách triển khai thứ tư.

(4) Sử dụng giao diện bất đồng bộ hỗ trợ truyền ngữ cảnh.

Mã như sau:

								
									
										public
									 class
									 MyEmojiDownloader
									 implements
									 EmojiDownloader
									,
									 DownloadListener
									 {
									
    private
									 Downloader
									 downloader
									;
									

    public
									 MyEmojiDownloader
									()
									 {
									
        Bạn có thể khởi tạo một đối tượng tải xuống. MyDownloader là một phiên bản cụ thể của giao diện Downloaderbxh ngoai hang anh, được thiết kế để thực hiện các tác vụ tải xuống theo cách riêng của nó.
        downloader
									 =
									 new
									 MyDownloader
									();
									
        downloader
									.
									setListener
									(
									this
									);
									
    }
									

    @Override
									
    public
									 void
									 startDownloadEmoji
									(
									EmojiPackage
									 emojiPackage
									)
									 {
									
        // Tạo dữ liệu ngữ cảnh tải xuống.
        EmojiDownloadContext
									 downloadContext
									 =
									 new
									 EmojiDownloadContext
									();
									
        downloadContext
									.
									emojiPackage
									 =
									 emojiPackage
									;
									
        // Bắt đầu tải xuống tệp hình ảnh biểu tượng thứ 0bxh ngoai hang anh, tham số ngữ cảnh được truyền vào.
        downloader
									.
									startDownload
									(
									emojiPackage
									.
									emojiUrls
									.
									get
									(
									0
									),
									
                getLocalPathForEmoji
									(
									emojiPackage
									,
									 0
									),
									
                downloadContext
									);
									

    }
									

    @Override
									
    public
									 void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									)
									 {
									
        Bạn có thể thực hiện việc ép kiểu xuống (down-casting) thông qua tham số contextData của callback interface để lấy được các tham số ngữ cảnh. Việc này không chỉ giúp bạn truy cập vào dữ liệu cần thiết mà còn cho phép bạn mở rộng thêm các tính năng liên quan đến ngữ cảnh trong ứng dụngsv 88, từ đó tối ưu hóa hiệu suất và sự linh hoạt của mã nguồn.
        EmojiDownloadContext
									 downloadContext
									 =
									 (
									EmojiDownloadContext
									)
									 contextData
									;
									

        downloadContext
									.
									localPathList
									.
									add
									(
									localPath
									);
									
        downloadContext
									.
									downloadedEmoji
									++;
									
        EmojiPackage
									 emojiPackage
									 =
									 downloadContext
									.
									emojiPackage
									;
									
        if
									 (
									downloadContext
									.
									downloadedEmoji
									 <
									 emojiPackage
									.
									emojiUrls
									.
									size
									())
									 {
									
            // Chưa tải xongbxh ngoai hang anh, tiếp tục tải xuống tệp hình ảnh biểu tượng tiếp theo.
            String
									 nextUrl
									 =
									 emojiPackage
									.
									emojiUrls
									.
									get
									(
									downloadContext
									.
									downloadedEmoji
									);
									
            downloader
									.
									startDownload
									(
									nextUrl
									,
									
                    getLocalPathForEmoji
									(
									emojiPackage
									,
									 downloadContext
									.
									downloadedEmoji
									),
									
                    downloadContext
									);
									
        }
									
        else
									 {
									
            // Đã tải xong.
            installEmojiPackageLocally
									(
									emojiPackage
									,
									 downloadContext
									.
									localPathList
									);
									
        }
									
    }
									

    @Override
									
    public
									 void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									,
									 Object
									 contextData
									)
									 {
									
        ...
									
    }
									

    @Override
									
    public
									 void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									,
									 Object
									 contextData
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 String
									 getLocalPathForEmoji
									(
									EmojiPackage
									 emojiPackage
									,
									 int
									 i
									)
									 {
									
        ...
									
    }
									

    /**
    private
									 void
									 installEmojiPackageLocally
									(
									EmojiPackage
									 emojiPackage
									,
									 List
									<
									String
									>
									 localPathList
									)
									 {
									
        ...
									
    }
									
}
									

								

Rõ ràngbxh ngoai hang anh, phương pháp thực hiện thứ 4 có vẻ hợp lý hơn cả. Nó mang lại mã nguồn gọn gàng và không mắc phải các nhược điểm của ba phương pháp trước. Tuy nhiên, nó đòi hỏi giao diện đồng bộ dưới cùng mà chúng ta sử dụng cần hỗ trợ truyền ngữ cảnh một cách hoàn chỉnh. Trong thực tế, các giao diện mà chúng ta cần gọi thường đã được xác định sẵn và không thể thay đổi. Nếu giao diện đó không hỗ trợ truyền ngữ cảnh tốt, thì chúng ta không còn lựa chọn nào khác ngoài việc áp dụng một trong ba cách đầu tiên. Về cơ bản, việc thảo luận về ba phương pháp này không phải là tự chuốc phiền phức, mà là để đối phó với những giao diện không đủ hỗ trợ ngữ cảnh trả về. Những người thiết kế giao diện này đôi khi vô tình tạo ra những thách thức như vậy cho chúng ta.

lưu trữ toàn cục một bản sao của ngữ cảnh

Bây giờkeo nha cai hom nay, chúng ta có thể dễ dàng rút ra kết luận: Một định nghĩa giao diện callback tốt nên có khả năng truyền dữ liệu ngữ cảnh tùy chỉnh.

Chúng ta hãy xem xét lại một số định nghĩa giao diện callback của hệ thống từ góc độ truyền bá ngữ cảnh. Ví dụ như trong iOSkeo nha cai hom nay, phương thức `alertView:clickedButtonAtIndex:` của giao diện đại diện `UIAlertViewDelegate`, hoặc phương thức `tableView:cellForRowAtIndexPath:` của nguồn dữ liệu bảng `UITableViewDataSource`. Các phương thức callback này thường có tham số đầu tiên là một instance của `UIView` (thực tế là trong UIKit, hầu hết các phương thức callback đều được định nghĩa theo cách tương tự). Điều này giúp truyền ngữ cảnh nhất định, cho phép chúng ta phân biệt giữa các instance `UIView` khác nhau, nhưng không thể phân biệt giữa các sự kiện callback riêng lẻ của cùng một instance `UIView`. Giả sử trong một màn hình, bạn cần hiển thị nhiều lần hộp thoại cảnh báo `UIAlertView`. Mỗi khi muốn hiển thị một hộp thoại mới, bạn phải tạo một instance `UIAlertView` mới và sau đó sử dụng thông tin trả về trong callback để xác định lần hiển thị nào đang được thực hiện. Cách này khá giống với giải pháp thứ ba mà chúng ta đã thảo luận trước đó. Ngoài ra, `UIView` cũng đã cung cấp thuộc tính `tag` để truyền ngữ cảnh dạng nguyên, nhưng nếu bạn muốn truyền ngữ cảnh phức tạp hơn hoặc thuộc kiểu khác, thì cách duy nhất là làm như giải pháp thứ ba đã đề xuất - tạo ra một subclass của `UIView` và thêm vào đó các tham số ngữ cảnh tùy chỉnh. Điều thú vị là việc sử dụng subclass có thể mang lại lợi ích lớn hơn khi bạn cần quản lý nhiều trạng thái hoặc ngữ cảnh phức tạp. Bạn có thể lưu trữ bất kỳ loại dữ liệu nào trong các biến của subclass, từ chuỗi đến đối tượng tùy chỉnh, và dễ dàng truy cập chúng ngay trong phương thứ Điều này giúp giảm thiểu sự phụ thuộc vào các giá trị toàn cục hoặc các thao tác tìm kiếm phức tạp để xác định ngữ cảnh cụ thể. Tuy nhiên, nó cũng đồng thời tăng thêm trách nhiệm cho lớp view, có thể dẫn đến việc khó bảo trì và mở rộng trong tương lai. Tóm lại, dù giải pháp sử dụng `tag` hay subclass đều có ưu nhược điểm riêng, việc lựa chọn phương án nào phụ thuộc vào yêu cầu cụ thể của ứng dụng và mức độ phức tạp của ngữ cảnh mà bạn cần xử lý.

Mỗi lần UIView được hiển thị lạisv 88, một phiên bản mới của nó sẽ được tạo ra. Điều này không nhất thiết phải coi là một gánh nặng lớn, vì cách sử dụng điển hình của UIView chính là tạo ra các thể hiện mới và thêm chúng vào cấu trúc View để hiển thị. Tuy nhiên, ví dụ về IndependentVideoManager mà chúng ta đã đề cập trước đó lại có tình huống khác biệt. Giao diện callback của nó được thiết kế sao cho tham số đầu tiên trả về thể hiện của IndependentVideoManager, chẳng hạn như ivManager:isIndependentVideoAvailable:. Có thể suy đoán rằng cách định nghĩa callback này chắc chắn đã tham khảo UIKit. Nhưng trong trường hợp của IndependentVideoManager, thường chỉ cần tạo ra một thể hiện duy nhất và sau đó gọi nhiều lần các phương thức trên cùng một thể hiện đó để phát quảng cáo nhiều lần. Điều quan trọng hơn ở đây là việc phân biệt các callback khác nhau xảy ra trên cùng một thể hiện, mỗi lần callback mang theo những tham số ngữ cảnh nào. Thực tế, khả năng truyền ngữ cảnh mà chúng ta cần ở đây tương tự như cách tiếp cận mà chúng ta đã thảo luận ở cách thứ tư. Trong khi giao diện kiểu UIKit cung cấp khả năng truyền ngữ cảnh thì nó không đủ linh hoạt cho yêu cầu cụ thể này.

Trong thiết kế giao diện callbacksv 88, khả năng truyền ngữ cảnh quan trọng là: Nó có thể phân biệt giữa nhiều lần callback của một phiên bản giao diện duy nhất hay không.

Hãy cùng tìm hiểu thêm một ví dụ trên Android. Trên nền tảng Androidkeo nha cai hom nay, giao diện callback thường được thể hiện dưới dạng listener, với đoạn mã điển hình như sau: ```java public interface MyCallbackListener { void onTaskCompleted(String result); } class MyClass { private MyCallbackListener callback; public void setCallback(MyCallbackListener callback) { callback = callback; } public void performTask() { // Một số xử lý logic String result = "Kết quả"; if (callback != null) { onTaskCompleted(result); } } } ``` Trong ví dụ này, `MyCallbackListener` là một listener được sử dụng để thông báo khi nhiệm vụ hoàn thành. Đây là cách phổ biến để thực hiện các hành động callback trong lập trì

								
									
										Button
									 button
									 =
									 (
									Button
									)
									 findViewById
									(...);
									
button
									.
									setOnClickListener
									(
									new
									 View
									.
									OnClickListener
									()
									 {
									
    @Override
									
    public
									 void
									 onClick
									(
									View
									 v
									)
									 {
									
        ...
									
    }
									
});
									

								

Trong đoạn mã nàykeo nha cai hom nay, một instance của Button có thể liên kết với nhiều hàm callback (nhiều sự kiện nhấn chuột), nhưng chúng ta không thể phân biệt giữa các callback khác nhau từ cùng một button chỉ thông qua đoạn mã này. Tuy nhiên, thật may mắn là trong thực tế, chúng ta cũng không cần phải làm điều đó. Thay vào đó, nếu muốn xử lý từng trường hợp riêng biệt khi button được nhấn, bạn có thể thêm một tham số hoặc giá trị đặc trưng nào đó vào hà Điều này giúp xác định rõ ràng hơn nguồn gốc của sự kiện và cho phép bạn thực hiện hành động phù hợp dựa trên ngữ cảnh cụ thể. Ví dụ, bạn có thể truyền một chuỗi nhận dạng hoặc một đối tượng liên quan đến hàm callback để kiểm soát logic theo cách linh hoạt hơn.

Dựa trên những phân tích đã trình bàysv 88, chúng ta có thể thấy rằng các công việc liên quan đến giao diện View, vốn thường được coi là thuộc về phần "frontend", thường không đòi hỏi phải phân biệt rõ ràng giữa các lần gọi trả về của một giao diện cụ thể. Do đó, việc truyền bối cảnh phức tạp trong trường hợp này cũng không cần thiết. Ngược lại, đối với các tác vụ bất đồng bộ thuộc phần "backend", đặc biệt là những tác vụ có chu kỳ sống kéo dài, khả năng truyền bối cảnh mạnh mẽ trở nên cực kỳ quan trọng. Vì lý do đó, bài viết trước của loạt bài này đã chọn tập trung vào vấn đề xử lý bất đồng bộ như một phần không thể thiếu trong công việc lập trình "backend". Đây chính là lý do khiến chủ đề này được ưu tiên thảo luận trong ngữ cảnh liên quan đến phần sau của hệ thống.

Khi nói về tham số ngữ cảnh (context)keo nha cai hom nay, vẫn còn một số vấn đề nhỏ cần lưu ý. Ví dụ, trên iOS, tham số ngữ cảnh trong quá trình thực thi tác vụ bất đồng bộ nên được giữ bằng kiểu tham chiếu mạnh (strong) hay yếu (weak)? Nếu là tham chiếu mạnh, việc gọi phương thức có thể dẫn đến hiện tượng tham chiếu vòng nếu đối tượng ngữ cảnh được truyền vào là một đối tượng lớn như Điều này có thể gây ra rò rỉ bộ nhớ. Ngược lại, nếu sử dụng tham chiếu yếu, khi đối tượng ngữ cảnh được truyền vào là đối tượng tạm thời, nó có thể bị hủy ngay lập tức sau khi được tạo ra, khiến đối tượng không thể truyền qua đúng cách. Đây thực chất là một bài toán nan giải do cơ chế quản lý bộ nhớ dựa trên đếm tham chiếu mang lại. Tuy nhiên, điều quan trọng là phải hiểu rõ ngữ cảnh của tình huống mà chúng ta đang làm việc. Trong trường hợp chúng ta đang thảo luận, tham số ngữ cảnh được sử dụng để phân biệt các lần gọi callback khác nhau của một giao diện cụ thể. Do đó, đối tượng ngữ cảnh được truyền vào thường sẽ không phải là một đối tượng lớn với chu kỳ sống lâu dài, mà là một đối tượng nhỏ hơn, có chu kỳ sống gần giống với nhiệm vụ bất đồng bộ. Nó sẽ được tạo ra khi nhiệm vụ bắt đầu và giải phóng sau khi nhiệm vụ kết thúc (khi callback xảy ra). Vì vậy, trong ngữ cảnh này, chúng ta nên duy trì tham chiếu mạnh cho đối tượng ngữ cảnh được truyền vào, đảm bảo rằng nó tồn tại suốt thời gian nhiệm vụ diễn ra và không bị hủy trước thời điểm cần thiết.

Thứ tự callback.

loạn thứ tự callback

  • dữ liệu trả về bị lộn xộn
  • Là bên thực hiện giao diệnsv 88, khi triển khai giao diện, chúng ta cần xác định rõ liệu có cần đảm bảo thứ tự gọi lại hay không: đảm bảo rằng sẽ không xảy ra tình trạng hỗn loạn trong chuỗi gọi lại. Nếu yêu cầu đảm bảo điều này, thì độ phức tạp của việc triển khai giao diện sẽ tăng lên đáng kể. Trong quá trình phát triển, nếu phải xử lý các trường hợp mà thứ tự hoạt động là yếu tố then chốt, chúng ta cần thiết kế thêm cơ chế kiểm soát chặt chẽ để tránh bất kỳ sự xáo trộn nào trong chuỗi sự kiện. Điều này có thể bao gồm việc sử dụng các đối tượng khóa hoặc cơ chế đồng bộ hóa, từ đó làm cho mã nguồn trở nên phức tạp hơn và khó duy trì hơn so với các giải pháp thông thường.

Từ góc nhìn thực hiện giao diện bất đồng bộkeo nha cai hom nay, các yếu tố có thể gây ra sự lộn xộn của callback có thể là:

  • Kết quả thất bại được trả về sớm. Thực tếkeo nha cai hom nay, điều này rất dễ xảy ra nhưng lại khó nhận ra rằng nó có thể gây ra sự hỗn loạn trong thứ tự trả về kết quả. Một ví dụ điển hình là khi một tác vụ bất đồng bộ (asynchronous task) thường được chuyển giao để thực hiện trên một luồng bất đồng bộ khác, nhưng trước khi được chuyển giao, một lỗi nghiêm trọng đã được phát hiện (như tham số đầu vào không hợp lệ), dẫn đến việc hủy bỏ toàn bộ tác vụ và kích hoạt hàm trả về kết quả thất bại. Trong trường hợp này, tác vụ bất đồng bộ bị thất bại sớm nhưng khởi động muộn có thể sẽ nhận được kết quả trước cả những tác vụ khởi động sớm hơn nhưng đang chạy ổn định. Điều này cho thấy rằng, khi làm việc với các tác vụ bất đồng bộ, cần phải đặc biệt cẩn trọng để tránh tình trạng thứ tự trả về kết quả bị đảo lộn, dẫn đến sự nhầm lẫn hoặc sai sót trong xử lý logic của chương trình. Các lập trình viên cần xem xét kỹ lưỡng cách quản lý luồng thực thi và đảm bảo rằng các cơ chế kiểm tra lỗi được đặt ở vị trí hợp lý, nhằm giảm thiểu rủi ro xảy ra hiện tượng này.
  • kết quả trả về thất bại trước thời hạn
  • Việc thực hiện song song của tác vụ bất đồng bộ. Đằng sau giao diện bất đồng bộ có thể có một nhóm luồng chạy song songbxh ngoai hang anh, nhờ đó thứ tự hoàn thành của các tác vụ bất đồng bộ này sẽ là ngẫu nhiên. Thêm vào đó, khi sử dụng nhóm luồng này, hệ thống sẽ tự động phân bổ tài nguyên và sắp xếp các nhiệm vụ dựa trên mức độ ưu tiên hoặc tình trạng sẵn sàng của mỗi luồng, tạo ra sự linh hoạt nhưng cũng làm tăng tính không thể đoán trước trong việc xác định thời điểm hoàn thành từng tác vụ.
  • Các tác vụ bất đồng bộ phụ thuộc khác gây ra sự lộn xộn củ

Dù đó là trường hợp bị hoãn hay thứ tự hỗn loạnbxh ngoai hang anh, chúng ta vẫn có cách để đảm bảo rằng thứ tự của các callback luôn khớp với thứ tự ban đầu khi gọi các giao diện. Để làm điều này, chúng ta có thể thiết lập một hàng đợi (queue). Khi mỗi lần gọi giao diện và khởi động nhiệm vụ bất đồng bộ, hãy thêm thông số gọi cùng với một số thông tin ngữ cảnh khác vào hàng đợi. Sau đó, hãy chắc chắn rằng các callback sẽ được thực thi theo đúng thứ tự mà các mục trong hàng đợi được lấy ra. Điều này giúp duy trì tính nhất quán giữa việc gọi giao diện và phản hồi từ các tác vụ bất đồng bộ.

Có lẽ trong nhiều trường hợpsv 88, bên sử dụng giao diện không quá khắt khe, việc các sự kiện trả về bị xáo trộn thứ tự đôi khi sẽ không gây ra hậu quả nghiêm trọng. Tất nhiên, điều này chỉ đúng khi bên sử dụng hiểu rõ vấn đề và có cách tiếp cận phù hợp. Điều đó đồng nghĩa với việc chúng ta không cần phải quá lo lắng về việc đảm bảo thứ tự của các sự kiện trả về trong quá trình triển khai giao diện. Tuy nhiên, lựa chọn cuối cùng vẫn phụ thuộc vào yêu cầu cụ thể của từng ngữ cảnh ứng dụng cũng như sở thích cá nhân của người thực hiện giao diện. Có thể nói, đây là một bài toán cân bằng giữa hiệu suất, độ tin cậy và sự linh hoạt trong phát triển phần mềm.

Callback dưới dạng closure và

Khi số lượng phương thức của giao diện đồng bộ hóa ít và giao diện trả về callback tương đối đơn giản (chỉ có một phương thức)bxh ngoai hang anh, chúng ta đôi khi có thể định nghĩa callback dưới dạ Trong hệ sinh thái iOS, bạn có thể tận dụng block để thực hiện điều này; trong Android, bạn có thể sử dụng lớp ẩn danh nội bộ (tương đương với biểu thức lambda trong Java 8 trở lên). Điều thú vị là việc sử dụng closure không chỉ giúp giảm thiểu mã nguồn mà còn làm cho logic xử lý dữ liệu trở nên rõ ràng hơn. Đặc biệt trong Android, các lớp ẩn danh nội bộ cho phép chúng ta viết mã ngắn gọn và dễ đọc, thay vì phải tạo ra nhiều lớp riêng lẻ. Điều này đặc biệt hữu ích khi bạn cần xử lý các tác vụ ngắn gọn hoặc các tác vụ phụ trợ. Ngược lại, trong iOS, việc sử dụng block không chỉ đơn thuần là một cách tiếp cận lập trình mà còn phản ánh phong cách thiết kế của Apple, giúp người dùng dễ dàng tích hợp các chức năng phức tạp vào ứng dụng của mình một cách linh hoạt và hiệu quả.

Giả sử Listener tải xuống trước đó đã được đơn giản hóa thành chỉ có một phương thức callbacksv 88, như sau:

								
									
										public
									 interface
									 DownloadListener
									 {
									
    /**
    public
									 static
									 final
									 int
									 SUCCESS
									 =
									 0
									;
									// Thành công
    //... Các mã lỗi khác (bỏ qua).

    /** * Khi quá trình tải xuống hoàn tấtbxh ngoai hang anh, hàm này sẽ được thực thi. * @param errorCode Mã lỗi. Nếu là SUCCESS, tải xuống thành công; các mã khác cho biết tải xuống thất bại. * @param url Địa chỉ nguồn tài nguyên. * @param localPath Vị trí lưu trữ tài nguyên sau khi tải về. * @param contextData Dữ liệu ngữ cảnh bổ sung. * Lưu ý rằng bạn có thể sử dụng contextData để lưu trữ bất kỳ thông tin nào cần thiết trong suốt quá trình tải xuống. */
    void
									 downloadFinished
									(
									int
									 errorCode
									,
									 String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									);
									
}
									

								

Vậy thìkeo nha cai hom nay, giao diện Downloader cũng có thể được đơn giản hóa thay vì cần một giao diện setListener riêng biệt. Thay vào đó, giao diện tải xuống có thể nhận trực tiếp giao diện callback ngay trong cấu trúc của nó. Ví dụ như sau: ```java public interface Downloader { void download(String url, Callback callback); } public interface Callback { void onSuccess(String result); void onFailure(Exception e); } ``` Như vậy, việc xử lý kết quả tải xuống sẽ được tích hợp ngay trong giao diện chính, giúp giảm thiểu sự phức tạp và tăng tính rõ ràng cho mã nguồn.

								
									
										public
									 interface
									 Downloader
									 {
									
    /** * Khởi động quá trình tải xuống tài nguyên. * @param url Địa chỉ nguồn tài nguyên cần tải về. * @param localPath Vị trí lưu trữ trên thiết bị sau khi tải xuống. * @param contextData Dữ liệu ngữ cảnhsv 88, sẽ được truyền trở lại qua giao diệ Có thể là bất kỳ loại nào. * @param listener Thực thể giao diện callback để xử lý kết quả. */
    void
									 startDownload
									(
									String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									,
									 DownloadListener
									 listener
									);
									
}
									

								

Giao diện bất đồng bộ được định nghĩa theo cách này có lợi thế là mã nguồn khi gọi sẽ gọn gàng hơnsv 88, đồng thời tham số của hàm trả về (listener) có thể truyền dưới dạ Tuy nhiên, nếu mức độ lồng nhau quá sâu, vấn đề Callback Hell sẽ xuất hiện, khiến code trở nên rối rắm và khó bảo trì. Điều này thường xảy ra khi bạn cần xử lý nhiều lớp logic liên tiếp mà không có cơ chế quản lý hợp lý, dẫn đến việc khó theo dõi luồng thực thi và dễ mắc lỗi trong quá trình phát triển. http://callbackhell.com Hãy tưởng tượng rằng bạn sử dụng giao diện Downloader để tải xuống liên tục ba tệp tin. Trong trường hợp nàysv 88, sẽ có đến ba lớp đóng gói (closure) được lồng vào nhau như sau:

								
									final
									 Downloader
									 downloader
									 =
									 new
									 MyDownloader
									();
									
    downloader
									.
									startDownload
									(
									url1
									,
									 localPathForUrl
									(
									url1
									),
									 null
									,
									 new
									 DownloadListener
									()
									 {
									
        @Override
									
        public
									 void
									 downloadFinished
									(
									int
									 errorCode
									,
									 String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									)
									 {
									
            if
									 (
									errorCode
									 !=
									 DownloadListener
									.
									SUCCESS
									)
									 {
									
                //... Xử lý lỗi.
            }
									
            else
									 {
									
                // Tải xuống URL thứ hai.
                downloader
									.
									startDownload
									(
									url2
									,
									 localPathForUrl
									(
									url2
									),
									 null
									,
									 new
									 DownloadListener
									()
									 {
									
                    @Override
									
                    public
									 void
									 downloadFinished
									(
									int
									 errorCode
									,
									 String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									)
									 {
									
                        if
									 (
									errorCode
									 !=
									 DownloadListener
									.
									SUCCESS
									)
									 {
									
                            //... Xử lý lỗi.
                        }
									
                        else
									 {
									
                            // Tải xuống URL thứ ba.
                            downloader
									.
									startDownload
									(
									url3
									,
									 localPathForUrl
									(
									url3
									),
									 null
									,
									 new
									 DownloadListener
									(
									

                            )
									 {
									
                                @Override
									
                                public
									 void
									 downloadFinished
									(
									int
									 errorCode
									,
									 String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									)
									 {
									
                                    //... Xử lý kết quả cuối cùng.
                                }
									
                            });
									
                        }
									
                    }
									
                });
									
            }
									
        }
									
    });
									

								

Đối với Callback Hellbxh ngoai hang anh, bài viết này http://callbackhell.com Giữ mã nguồn của bạn gọn gàng

Tuy nhiênsv 88, đối với toàn bộ vấn đề lập trình bất đồng bộ liên quan đến việc xử lý tác vụ không đồng bộ, các giải pháp như ReactiveX không phải lúc nào cũng phù hợp trong mọi tình huống. Thực tế cho thấy, bất kể là chúng ta đang đọc code của người khác hay tự viết code của mình, đa số các trường hợp đều gặp phải những tình huống cơ bản trong lập trình bất đồng bộ. Điều cần suy nghĩ kỹ càng chủ yếu là vấn đề logic, chứ không phải cứ áp dụng một khung (framework) nào đó là có thể giải quyết mọi vấn đề một cách tự động. Đôi khi, việc hiểu rõ và thiết kế logic phù hợp mới thực sự là chìa khóa giúp giải quyết các thách thức trong lập trình bất đồng bộ.


Tất cả chúng ta đều nhận thấy rằng bài viết này đã dành phần lớn nội dung để giải thích những điều tưởng chừng như hiển nhiênbxh ngoai hang anh, có thể khiến người đọc cảm thấy hơi dài dòng. Tuy nhiên, khi xem xét kỹ hơn, chúng ta sẽ phát hiện ra rằng nhiều giao diện đồng bộ mà chúng ta thường xuyên tiếp xúc không phải là dạng tối ưu mà chúng ta mong muốn. Để khai thác chúng một cách hiệu quả hơn, điều quan trọng là chúng ta cần hiểu rõ những hạn chế của chúng. Vì vậy, việc bỏ thời gian để tổng kết và xem xét lại các trường hợp khác nhau là hoàn toàn đáng giá. Điều này không chỉ giúp chúng ta hiểu sâu sắc hơn về vấn đề mà còn cho phép cải thiện cách sử dụng chúng trong tương lai.

Dẫu saokeo nha cai hom nay, việc định nghĩa giao diện tốt đòi hỏi sự am tường và kỹ năng sâu sắc, ngay cả những người đã làm việc nhiều năm cũng không phải ai cũng có thể đạt được. Bài viết này cũng không hướng dẫn cụ thể cách nào để tạo ra giao diện hay và callback interface hoàn hảo. Thực tế, không có lựa chọn nào là hoàn hảo tuyệt đối cả; điều chúng ta cần là khả năng đánh đổi giữa các ưu điểm và nhược điểm. Mỗi quyết định đều mang theo những hệ quả riêng, và chỉ khi hiểu rõ vấn đề, ta mới có thể đưa ra lựa chọn phù hợp nhất.

Cuối cùngbxh ngoai hang anh, chúng ta có thể cố gắng tóm tắt những tiêu chí để đánh giá chất lượng của một giao diện (một tiêu chuẩn không quá nghiêm ngặt), và tôi nghĩ có thể rút ra được một số điểm sau đây: Trước hết, một giao diện tốt cần phải đơn giản và dễ hiểu. Người dùng không nên mất quá nhiều thời gian để tìm hiểu cách sử dụng nó. Nó nên được thiết kế sao cho bất kỳ ai cũng có thể bắt đầu sử dụng mà không cần hướng dẫn chi tiết. Thứ hai, sự nhất quán trong thiết kế là điều rất quan trọng. Nếu các yếu tố trên giao diện thay đổi liên tục về màu sắc, hình dáng hoặc chức năng, người dùng sẽ cảm thấy bối rối. Một giao diện ổn định sẽ tạo ra sự tin tưởng và dễ sử dụng hơn. Thứ ba, khả năng tương tác cao là yếu tố không thể thiếu. Giao diện cần phản hồi nhanh chóng và chính xác với mọi thao tác của người dùng. Điều này giúp tăng trải nghiệm tích cực và giảm thiểu sự khó chịu khi sử dụng. Cuối cùng, tính linh hoạt cũng là một tiêu chí đáng lưu ý. Một giao diện lý tưởng cần có khả năng thích nghi với nhiều loại thiết bị và kích thước màn hình khác nhau, từ điện thoại di động đến máy tính để bàn. Hy vọng rằng với những tiêu chí này, bạn có thể đánh giá và cải thiện chất lượng của bất kỳ giao diện nào mà mình đang làm việc!

  • Logic đầy đủ (các logic giao diện không chồng chéo và không thiếu sót).
  • Có thể giải thích hợp lý.
  • Có một mô hình trừu tượng phù hợp với lý thuyết.
  • Quan trọng nhất: Làm cho người gọi thoải mái và đáp ứng nhu cầu.

(Kết thúc)

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


Bài viết gốcsv 88, 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: /iox6st0y.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: Giải thích bằng một hình ảnh về điều khiển luồng trong RxJava
Bài sau: 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ộ