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) —— Gọi lại cho tác vụ bất đồng bộ


Bài viết này là phần tiếp theo của loạt bài viết về: Xử lý bất đồng bộ trong phát triển Android và iOS. Phần thứ hai của **"Bài viết này"**. Trong bài viết nàyxóc đĩa, chúng ta sẽ chủ yếu thảo luận về nhiều vấn đề liên quan đến các callback của tác vụ bất đồng bộ.

Trong iOSVSBET, callback thường được thể hiện dưới dạng delegate; trong khi đó, ở Android, callback thường tồn tại dưới hình thứ Dù có hình thức nào đi chăng nữa, callback luôn là một phần không thể tách rời trong thiết kế giao diện (interface). 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 hợp lý không chỉ giúp các lập trình viên dễ dàng quản lý luồng dữ liệu mà còn tạo ra sự linh hoạt và hiệu quả cao trong việc phát triển ứng dụng. Điều này đặc biệt quan trọng khi bạn cần xử lý các tác vụ đồng bộ hoặc bất đồng bộ một cách gọn gàng và sạch sẽ. Nếu callback được xây dựng một cách cẩn thận, nó sẽ trở thành công cụ mạnh mẽ giúp giảm thiểu mã nguồn lặp lại và cải thiện khả năng bảo trì cho dự án. Ngược lại, nếu callback không được thiết kế tốt, nó có thể gây ra sự nhầm lẫn và phức tạp trong quá trình sử dụng.

Vậy khi thiết kế và triển khai giao diện callbackxóc đĩa, chúng ta cần cân nhắc những yếu tố nào? Trước tiên, hãy liệt kê các chủ đề phụ mà bài viết này sẽ thảo luận như sau, sau đó sẽ lần lượt phân tích từng vấn đề một: 1. Tính linh hoạt trong việc xử lý dữ liệu trả về. 2. Sự ổn định của giao diện trong các kịch bản khác nhau. 3. Cách tối ưu hóa hiệu suất khi sử dụ 4. Bảo mật thông tin trong quá trình truyền tải dữ liệu. Hãy cùng đi sâu vào từng khía cạnh để hiểu rõ hơn về tầm quan trọng của việc thiết kế một giao diện callback hiệu quả.

  • Cần phải có callback kết quả.
  • Cần chú trọng vào callback lỗi & mã lỗi nên càng chi tiết càng tốt.
  • Giao diện gọi và giao diện callback cần có mối quan hệ rõ ràng với nhau.
  • 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 thẳng).
  • 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 tổ chức trên GitHub (liên tục cập nhật)boi tu vi, đường dẫn kho lưu trữ là:

Trong nội dung hiện tại của bài viết nàyxóc đĩa, mã nguồn Java được tìm thấy trong package có tên là `com. demos.async. callback`. Đây là nơi mà các đoạn code liên quan đến lập trình asynchronous được triển khai và tổ chức một cách có hệ thống. Package này đóng vai trò quan trọng trong việc giúp người đọc hiểu rõ hơn về cách xử lý callback trong môi trường phát triển ứng dụng Java.


Cần phải có callback kết quả.

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

Tuy nhiênxóc đĩa, 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 trung gian.
  • Callback kết quả.

Và callback kết quả lại 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 được thực hiệnxóc đĩa, 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 ở giữa quá trình; trong khi gọi lại kết quả sẽ chỉ được thực hiện khi nhiệm vụ bất đồng bộ hoàn tất và đạt đượ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 gọi lại kết quả cho thấy rằng lần thực thi giao diện bất đồng bộ này đã chính thức kết thúc.

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

Đ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 trường hợp lỗi có thể xảy raVSBET, và bất kể tình huống nào xuất hiện, cũng phải đảm bảo trả về kết quả Nếu không, điều đó có thể dẫn đến việc quy trình thực thi của bên gọi bị gián đoạn hoàn toàn. Thêm vào đó, khi thiết kế giao diện này, bạn nên xem xét thêm các phương án dự phòng để tránh những rủi ro không đáng có. Việc chủ động xử lý lỗi sẽ giúp tăng cường độ ổn định và tin cậy cho hệ thống tổng thể.

Cần chú trọng vào callback lỗi & mã lỗi nên càng chi tiết càng tốt.

Trước tiênxóc đĩa, hãy xem một ví dụ mã nguồn:

								
									
										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 raVSBET, bạn có thể tùy chỉnh thêm các thông số như tên tệp và kiểm tra không gian ổ đĩa trước khi tải để đảm bảo hoạt động ổn định. */
    void
									 startDownload
									(
									String
									 url
									,
									 String
									 localPath
									);
									
}
									

public
									 interface
									 DownloadListener
									 {
									
    /** * Hàm xử lý callback 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ôngVSBET, false nếu thất bại. * @param url Địa chỉ nguồn tài nguyên được yêu cầu tải xuống. * @param localPath Đường dẫn nơi tệp sẽ được lưu trữ sau khi tải xuống. Chỉ có giá trị khi result=true. */
    void
									 downloadFinished
									(
									boolean
									 result
									,
									 String
									 url
									,
									 String
									 localPath
									);
									

    /** * Hàm callback để theo dõi tiến độ tải xuống. * @param url Địa chỉ tài nguyên cần tải. * @param downloadedSize Kích thước dữ liệu đã tải được tính đến thời điểm hiện tại. * @param totalSize Tổng kích thước của tài nguyên. * * Chú thích thêm: Đây là một hàm quan trọng trong quá trình tải xuốngVSBET, giúp người dùng có thể nắm bắt được tình trạng tải và quản lý hiệu quả hơn. */
    void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									);
									
}
									

								

Giao diện tải xuống này được thiết kế để hỗ trợ việc tải tài nguyên từ URL đã chỉ định. Đây là một giao diện đồng bộVSBET, trong đó người sử dụng sẽ bắt đầu nhiệm vụ tải xuống bằng cách gọi phương thức startDownload và chờ đợi thông bá Khi callback downloadFinished được kích hoạt, nó cho biết rằng nhiệm vụ tải xuống đã kết thúc. Nếu giá trị result trả về là true, điều đó có nghĩa là việc tải xuống thành công; ngược lại, nếu result là false, nhiệm vụ tải xuống đã thất bại. Bên cạnh đó, giao diện này cũng cung cấp khả năng xử lý các tình huống đặc biệt khi tải xuống, chẳng hạn như kiểm tra tính toàn vẹn của dữ liệu hoặc xác nhận đường dẫn lưu trữ trước khi thực hiện quá trình tải. Điều này giúp đảm bảo rằng mọi lỗi tiềm ẩn trong quy trình tải xuống đều được phát hiện và khắc phục kịp thời.

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ốngboi tu vi, trong quá trình tải xuống sẽ nhận được tiến độ tải (với callback giữa chừng), khi tải thành công có thể lấy được kết quả, và khi tải thất bại cũng sẽ nhận được thông báo (thành công và thất bại đều thuộc dạng callback kết quả). Tuy nhiên, nếu trong trường hợp tải xuống thất bại mà muốn biết thêm những lý do cụ thể hơn về sự cố, thì hiện tại giao diện này không thể thực hiện được.

không đủ dung lượng lưu trữ

tiết kiệm thời gian

Về ví dụ mã nguồn của giao diện tải xuống phía trênxóc đĩa, để có thể trả về mã lỗi cụ thể hơn, mã nguồn 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
									;
									// Đầu vào có lỗi.
    public
									 static
									 final
									 int
									 NETWORK_UNAVAILABLE
									 =
									 2
									;
									//Mạng không khả dụng
    public
									 static
									 final
									 int
									 UNKNOWN_HOST
									 =
									 3
									;
									//Lỗi phân giải tên miền
    public
									 static
									 final
									 int
									 CONNECT_TIMEOUT
									 =
									 4
									;
									//Hết thời gian kết nối
    public
									 static
									 final
									 int
									 HTTP_STATUS_NOT_OK
									 =
									 5
									;
									//Yêu cầu tải xuống 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ó chỗ để 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ó chỗ để 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ó chỗ để 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ữ thẻ 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ôngboi tu vi, hàm callback sẽ được thực hiện. * @param url Đường dẫn nguồn của tài nguyên * @param localPath Vị trí lưu trữ sau khi tài nguyên đã được tải xuống */
    void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									);
									
    /** * Khi việc tải xuống thất bạiboi tu vi, hàm callback sẽ được kích hoạt. * @param url Đường dẫn đến tài nguyên * @param errorCode Mã lỗi. Được sử dụng để xác định loại lỗi xảy ra * @param errorMessage Mô tả ngắn gọn về lỗi. Giúp người gọi hiểu rõ hơn về lý do gây ra lỗi */
    void
									 downloadFailed
									(
									String
									 url
									,
									 int
									 errorCode
									,
									 String
									 errorMessage
									);
									

    /** * Hàm callback để theo dõi tiến độ tải xuống. * @param url Địa chỉ tài nguyên cần tải. * @param downloadedSize Kích thước dữ liệu đã tải được tính đến thời điểm hiện tại. * @param totalSize Tổng kích thước của tài nguyên. * * Chú thích thêm: Đây là một hàm quan trọng trong quá trình tải xuốngVSBET, giúp người dùng có thể nắm bắt được tình trạng tải và quản lý hiệu quả hơn. */
    void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									);
									
}
									

								

Trong iOSboi tu vi, Foundation Framework cung cấp một cách tiếp cận có hệ thống để xử lý lỗi thông qua lớp NSError. Đây là một công cụ mạnh mẽ cho phép gói gọn mã lỗi theo cách chung nhất có thể, đồng thời phân loại các lỗi thành các domain khác nhau. Nhờ khả năng linh hoạt này, NSError trở thành lựa chọn hoàn hảo khi định nghĩa giao diện trả về lỗi, giúp việc quản lý và xử lý lỗi trong ứng dụng trở nên dễ dàng và hiệu quả hơn bao giờ hết. Hơn nữa, NSError còn hỗ trợ người lập trình thêm thông tin chi tiết vào lỗi, chẳng hạn như thông điệp mô tả hoặc dữ liệu liên quan, từ đó nâng cao khả năng debug và cải thiện trải nghiệm phát triển phần mềm.

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

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 mã định nghĩa giao diện cho bảng điểm video quảng cáo từ một nền tảng quảng cáo trong nước (một số đoạn mã không liên quan đã được lược bỏ để dễ quan sát hơn).

								
									
										@class
									 IndependentVideoManager
									;
									

@protocol
									 IndependentVideoManagerDelegate
									 <
									NSObject
									>
									
@optional
									
Callback thông báo hiện thị quảng cáo video độc lập Bạn có thể nhận được thông báo khi quảng cáo video độc lập được hiển thị qua callback này. Callback sẽ gửi tín hiệu ngay khi quảng cáo video được tải xong và sẵn sàng để người dùng tương tác. Đây là cơ hội tốt để bạn theo dõi và ghi nhận trạng thái hiện thị của quảng cáoxóc đĩa, từ đó tối ưu hóa chiến lược quảng cáo của mình. Một số lưu ý quan trọng khi xử lý callback này: - Kiểm tra xem quảng cáo đã được tải thành công hay chưa trước khi kích hoạt callback - Xác định vị trí và thời điểm phù hợp để hiển thị quảng cáo - Theo dõi tỷ lệ hiện thị và điều chỉnh tần suất xuất hiện của quảng cáo - Thu thập dữ liệu về hành vi người dùng khi tiếp xúc với quảng cáo Việc xử lý callback một cách hiệu quả sẽ giúp cải thiện trải nghiệm người dùng cũng như tăng cường hiệu quả quảng cáo. Hãy đảm bảo rằng hệ thống của bạn đã được tối ưu để phản ứng nhanh chóng và chính xác với các tín hiệu từ callback này.
...
									

Quản lý điểm - Callback xử lý tích lũy điểm Trong hệ thống quản lý điểmboi tu vi, việc xử lý các sự kiện callback đóng vai trò quan trọng. Khi người dùng thực hiện các hành động như đăng nhập, mua hàng hay tham gia khảo sát, hệ thống sẽ tự động kích hoạt callback để ghi nhận và cập nhật số điểm tương ứng. Một số callback điển hình bao gồm: - Xử lý tích điểm khi mua hàng thành công - Cập nhật điểm khi người dùng hoàn thành nhiệm vụ - Xóa điểm khi phát hiện hành vi gian lận - Ghi nhận lịch sử tích/làm mất điểm Việc thiết lập logic callback cần được tối ưu hóa để đảm bảo tính chính xác và hiệu quả. Đồng thời, cần có cơ chế kiểm tra và giám sát chặt chẽ để tránh sai sót trong quá trình quản lý điểm của người dùng.
...
									

#pragma mark - Hàm trả về trạng thái của video độc lập Hàm này sẽ được gọi khi có sự thay đổi trạng thái liên quan đến video tích hợp trong hệ thống. Nó giúp bạn kiểm soát và quản lý các hoạt động của video một cách hiệu quảboi tu vi, chẳng hạn như khi video đã sẵn sàng để phát, khi video bị lỗi hoặc khi người dùng hoàn tất việ Ví dụ, nếu video không thể tải xuống do kết nối mạng yếu, hàm này sẽ thông báo lỗi để bạn có thể đưa ra phản hồi kịp thời cho người dùng. Ngược lại, nếu video được phát thành công, bạn có thể cung cấp phần thưởng hoặc thực hiện các hành động khác dựa trên trạng thái "thành công". Tính năng này đặc biệt hữu ích trong các ứng dụng có tích hợp quảng cáo video, giúp tăng cường trải nghiệm người dùng và tối ưu hóa hiệu suất của ứng dụng./** * Tường quảng cáo video có khả dụng hay không. * Được gọi sau khi nhận trạng thái bật/tắt độc lập của video. * @param QuảnLýVideoĐộcLập * @param cho_phep */ Các nhà phát triển thường đặt câu hỏi này để xác định liệu tính năng video độc lập có nên được kích hoạt hay không. Điều này giúp tối ưu hóa trải nghiệm người dùng và đảm bảo hiệu suất tốt nhất cho ứng dụng.
-
									 (
									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 về tính khả dụng của video độc lập. * * @param QuanLyQuangCaoDocLap * @param coSan */
-
									 (
									void
									)
									ivManager
									:(
									IndependentVideoManager
									 *
									)
									manager
									
isIndependentVideoAvailable
									:(
									BOOL
									)
									available
									;
									


@end
									

@interface
									 IndependentVideoManager
									 :
									 NSObject
									 {
									
    
}
									

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

...
									

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

#pragma mark - phương pháp liên quan đến việc hiển thị tích điểm Bạn có thể sử dụng các phương pháp sau để quản lý và hiện thị tích điểm một cách độc lập: 1. Phương pháp initWall(): Thiết lập cấu hình ban đầu cho hệ thống tích điểmxóc đĩa, bao gồm thiết lập kích thước, vị trí và giao diện của tích điểm. 2. Phương pháp loadPoints(): Tải dữ liệu điểm từ máy chủ, kiểm tra tính hợp lệ và cập nhật trạng thái của tích điểm. 3. Phương thức displayWall(): Hiển thị giao diện tích điểm trên màn hình chính, đồng thời xử lý các sự kiện tương tác như nhấn nút hoặc chạm màn hình. 4. Phương pháp updatePoints(score: Int): Cập nhật số điểm mới dựa trên kết quả từ hoạt động người dùng, đảm bảo tính nhất quán giữa dữ liệu client và server. 5. Phương thức hideWall(): Ẩn giao diện tích điểm khi người dùng hoàn thành nhiệm vụ hoặc thoát khỏi màn hình. 6. Phương pháp checkRewardStatus(): Kiểm tra trạng thái phần thưởng dựa trên số điểm tích lũy, xác định xem người dùng có đủ điều kiện đổi thưởng hay không. 7. Phương thức processTransaction(): Xử lý giao dịch đổi thưởng, bao gồm xác minh thông tin người dùng và cập nhật số dư điểm. 8. Phương thức logEvent(eventName: String): Ghi lại sự kiện người dùng tương tác với tích điểm để phân tích hành vi và cải thiện trải nghiệm người dùng. Tất cả các phương pháp này đều được thiết kế để hoạt động độc lập, giúp bạn dễ dàng tùy chỉnh và tích hợp vào ứng dụng của mình mà không cần phụ thuộc vào các module khác./** * Dùng rootViewController của ứng dụng để hiển thị và bật lên bảng điểm tích lũy. * Hiển thị video độc lập theo kiểu ModalView bằng cách sử dụng rootViewController của ứng dụng. * @param type Loại bảng điểm tích lũy */
-
									 (
									void
									)
									presentIndependentVideo
									;
									

...
									

Bạn có thể kiểm tra xem tường điểm video độc lập đã sẵn sàng sử dụng hay chưa bằng cách thực hiện một số thao tác sau. Đầu tiênboi tu vi, hãy đảm bảo rằng tất cả các kết nối mạng đều ổn định để tránh bất kỳ sự cố nào xảy ra trong quá trình hoạt động. Tiếp theo, hãy thử chạy một đoạn mã mẫu nhỏ để xem liệu hệ thống có phản hồi đúng như mong đợi hay không. Nếu mọi thứ đều hoạt động trơn tru, nghĩa là tường điểm video độc lập đang ở trạng thái sẵn sàng để sử dụng. Hãy tiếp tục và tận dụng tính năng này để tối ưu hóa trải nghiệm người dùng của bạn!/** * Kiểm tra xem có quảng cáo video độc lập nào có thể phát không */
-
									 (
									void
									)
									checkVideoAvailable
									;
									

#pragma mark - Quản lý điểm số liên quan đến quảng cáo./** * Đánh giá số điểm đã đạt đượcxóc đĩa, dù thành công hay thất bại đều sẽ kích hoạt 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 ủy thác khi thành công hoặc thất bại (hãy đặc biệt chú ý rằng kiểu tham số là unsigned intboi tu vi, tức là số điểm cần tiêu dùng phải có giá trị không âm). * * @param diem Số điểm muốn sử dụng */
-
									 (
									void
									)
									consumeWithPointNumber
									:(
									NSUInteger
									)
									point
									;
									

@end
									

								

Chúng ta hãy phân tích mối quan hệ tương ứng 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ạoxóc đĩa, khi sử dụng **IndependentVideoManager**, bạn có thể gọi một số giao diện chính sau đây: 1. **Giao diện điều chỉnh chất lượng video**: Điều này 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 kết nối mạng hiện tại của họ. 2. **Giao diện quản lý danh sách phát**: Bạn có thể tạo, thêm hoặc xóa các video khỏi danh sách phát một cách dễ dàng mà không cần phải thực hiện nhiều bước phức tạp. 3. **Giao diện tương tác người dùng**: Với tính năng này, người dùng có thể gửi phản hồi trực tiếp hoặc đặt câu hỏi trong quá trình xem video mà không cần rời khỏi ứng dụng. 4. **Giao diện theo dõi trạng thái video**: Giao diện giúp theo dõi tiến trình phát, tình trạng lỗi (nếu có) và thông tin về thời gian đã xem của người dùng. 5. **Giao diện bảo mật dữ liệu**: Đảm bảo rằng mọi thông tin liên quan đến video đều được mã hóa và bảo vệ khỏi các nguy cơ tấn công từ bên ngoài. Tất cả những giao diện này đều được thiết kế để tối ưu hóa trải nghiệm người dùng và tăng cường khả năng tùy chỉnh cho các nhà phát triển.

  • Hiện thị 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 (hàm checkOwnedPoint và consumeWithPointNumber): Hàm checkOwnedPoint sẽ kiểm tra số điểm hiện có của người dùngVSBET, đảm bảo rằng họ không sử dụng quá giới hạn cho phép. Trong khi đó, hàm consumeWithPointNumber sẽ xử lý việc sử dụng điểm một cách hiệu quả, giảm dần số dư điểm dựa trên yêu cầu của người dùng. Hai hàm này phối hợp chặt chẽ để duy trì tính toàn vẹn của hệ thống quản lý điểm, giúp người dùng cảm thấy an tâm khi sử dụng dịch vụ mà không lo bị lỗi hoặc sai sót trong việc trừ điểm. Chúng cũng hỗ trợ việc ghi nhận chi tiết lịch sử tiêu điểm, tạo điều kiện thuận lợi cho việc theo dõi và kiểm soát về sau.

Giao diện (IndependentVideoManagerDelegate) có thể được phân loại thành các nhóm sau đây: 1. **Nhóm xử lý dữ liệu video**: Bao gồm các phương thức nhận và xử lý thông tin liên quan đến videoVSBET, chẳng hạn như tải video, kiểm tra trạng thái của video đang phát. 2. **Nhóm quản lý trạng thái**: Chứa các sự kiện liên quan đến việc thay đổi trạng thái của hệ thống video, ví dụ như khi video bắt đầu phát, tạm dừng hoặc kết thúc. 3. **Nhóm tương tác người dùng**: Tập hợp các phương thức phản hồi hành động từ người dùng, chẳng hạn như nhấn nút điều chỉnh âm lượng, tua video hay chọn chất lượng phát. 4. **Nhóm báo cáo lỗi**: Các phương thức giúp ghi nhận và thông báo về các vấn đề xảy ra trong quá trình hoạt động, bao gồm lỗi mạng, lỗi định dạng video hoặc các sự cố khác. 5. **Nhóm tùy chỉnh nâng cao**: Được sử dụng để hỗ trợ các tính năng đặc biệt, chẳng hạn như tích hợp với hệ thống bên thứ ba, lưu trữ lịch sử phát lại hoặc quản lý quyền truy cập nội dung. Mỗi nhóm này đều đóng vai trò quan trọng, góp phần tạo nên một hệ thống quản lý video linh hoạt và hiệu quả.

  • Lớp callback hiển thị quảng cáo video.
  • Trạng thái tường điểm tích lũy (ivManager:didCheckEnableStatus: và ivManager:isIndependentVideoAvailable:) sẽ giúp bạn kiểm soát và theo dõi tình trạng hoạt động của hệ thống. Hai hàm này đóng vai trò quan trọng trong việc xác định xem tính năng video độc lập có sẵn để sử dụng hay khôngVSBET, đồng thời cho phép người dùng biết liệu tường điểm hiện tại có đang được kích hoạt hay không. Điều này không chỉ tăng cường khả năng quản lý mà còn đảm bảo trải nghiệm người dùng mượt mà và hiệu quả hơn.
  • Lớp quản lý điểm số.

Tóm lạiVSBET, mối liên hệ giữa các phần ở đây khá rõ ràng. Ba loại giao diện trả về 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, cách sắp xếp này giúp người dùng dễ dàng nhận biết và áp dụng các chức năng tương ứng mà không gặp khó khăn trong việc hiểu ý nghĩa của từng phần.

Tuy nhiênVSBET, có một số chi tiết gây nhầm lẫn trong các callback liên quan đến trạng thái của bức tường tích điểm: dường như người gọi sẽ nhận được hai callback từ lớp quản lý tường tích điểm (ivManager:didCheckEnableStatus: và ivManager:isIndependentVideoAvailable:) sau khi gọ Về mặt ý nghĩa mà tên gọi của các callback này thể hiện, việc gọi checkVideoAvailable dường như chỉ nhằm kiểm tra xem liệu có quảng cáo video nào sẵn sàng phát hay không. Điều này khiến callback ivManager:isIndependentVideoAvailable: dường như đã đủ để trả về kết quả mong muốn, và callback ivManager:didCheckEnableStatus: dường như không cần thiết. Mặt khác, xét theo ý nghĩa của ivManager:didCheckEnableStatus: (xem liệu tường video có khả dụng hay không), nó dường như nên được kích hoạt bất cứ khi nào một phương thức nào đó của lớp quản lý được gọi, thay vì chỉ liên quan đến việc gọ Thiết kế callback ở đây có vẻ không rõ ràng trong mối quan hệ giữa các callback và các phương thức gọi, điều này gây khó hiểu cho người sử dụng. Điều này có thể làm cho người lập trình phải mất nhiều thời gian hơn để hiểu cách hoạt động của hệ thống, và đôi khi dẫn đến lỗi hoặc sự không chính xác trong quá trình phát triển ứng dụng. Một số nhà phát triển có thể phải đọc kỹ tài liệu hoặc thậm chí thử nghiệm để hiểu rõ hơn về cách hoạt động của các callback này. Điều này không chỉ làm mất thời gian mà còn có thể ảnh hưởng đến hiệu suất và độ tin cậy của ứng dụng.

Ngoài raxóc đĩa, giao diện IndependentVideoManager cũng gặp 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úcVSBET, nó sẽ hoặc gọi đến hàm xử lý kết quả thành công, hoặc gọi đến hàm xử lý kết quả thất bại. Chỉ một trong hai hàm này được thực thi. Đây là yêu cầu hiển nhiên, nhưng nếu không cẩn thận khi triển khai, có thể dẫn đến vi phạm quy tắc này. Việc quản lý các trạng thái và kiểm soát luồng hoạt động cần được thiết kế chặt chẽ để đảm bảo rằng không bao giờ có tình huống cả hai hàm đều được kích hoạt cùng lúc.

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

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

								

Không có kết quả nào được trả về từ phương thức parseDownloadResult

								
									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 thay đổiboi tu vi, đảm bảo rằng ngay cả khi có tình huống bất ngờ xảy ra, vẫn có thể gửi một callback thất bại cho người gọi.

Tuy nhiênboi tu vi, điều này cũng dẫn đến một vấn đề khác: nếu khi thực thi mã của phương thứ downloadSuccess hoặ downloadFailed xảy ra ngoại lệ, điều đó có thể gây ra việc gọi thêm một lần nữa phương thứ Do đó, các callback cho kết quả thành công và thất bại sẽ không còn được kích hoạt một cách độc lập như dự kiến nữa: hoặc cả hai callback (thành công và thất bại) đều được thực hiện, hoặc có tới hai lần callback thất bại liên tiếp. Điều này có thể gây ra những hậu quả không mong muốn trong quá trình xử lý logic của ứng dụng.

Việc triển khai giao diện callback là trách nhiệm của người gọiboi tu vi, vậy liệu lỗi do người gọi gây ra có thực sự cần chúng ta phải quan tâm? Trước hết, điều này vẫn nên do phía người gọi chịu trách nhiệm xử lý. Việc bên triển khai giao diện callback (người gọi) không nên ném lại ngoại lệ sau khi xảy ra lỗi là điều hiển nhiên. Tuy nhiên, những nhà thiết kế giao diện ở tầng dưới cũng cần cố gắng hết sức. Thông thường, người thiết kế giao diện không thể dự đoán được cách người gọi sẽ hành động như thế nào. Nếu khi xảy ra lỗi, chúng ta có thể đảm bảo rằng vấn đề không làm gián đoạn toàn bộ quy trình hoặc khiến hệ thống bị treo, thì rõ ràng đó là một giải pháp tốt hơn, phải không? Do đó, chúng ta có thể thử thay đổi mã nguồn thành dạng như sau: ```vietnamese try { // Thực hiện các tác vụ chính } catch (Exception e) { // Xử lý lỗi tại đây logError(e); // Ghi lại lỗi vào nhật ký fallbackAction(); // Thực hiện hành động dự phòng } ``` Với cách tiếp cận này, chúng ta không chỉ giúp tăng tính ổn định cho toàn bộ hệ thống mà còn giảm thiểu áp lực cho người dùng cuối. Hơn nữa, việc thêm một số cơ chế tự động phục hồi sẽ giúp hệ thống hoạt động trơn tru hơn trong mọi tình huống. Điều này đặc biệt hữu ích khi người gọi không đủ khả năng hoặc không chú ý đến các vấn đề tiềm ẩn.

								
									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útVSBET, nhưng cũng an toàn hơn.

Mô hình luồng củ

Cơ sở kỹ thuật chính để thực hiện giao diện bất đồng bộxóc đĩa, 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 so với luồng gọi).
  • Bạn có thể sử dụng I/O bất đồng bộ (như yêu cầu mạng bất đồng bộ). Trong trường hợp nàyboi tu vi, ngay cả khi toàn bộ chương trình chỉ có một luồng, bạn vẫn có thể tạo ra giao diện bất đồng bộ. Điều này giúp cải thiện hiệu suất và cho phép chương trình thực hiện nhiều tác vụ cùng lúc mà không cần chờ đợi từng thao tác hoàn tất trước khi bắt đầu thao tác tiếp theo. Với khả năng này, việc xử lý dữ liệu hoặc kết nối mạng trở nên linh hoạt hơn bao giờ hết, đặc biệt là trong các ứng dụng cần sự nhanh chóng và hiệu quả.

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

Về cơ bảnxóc đĩa, có ba chế độ chính để định nghĩa môi trường luồng thực hiện 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ào đi chăng nữaVSBET, kết quả trả về luôn được xử lý trên luồng chính (chẳng hạn như cách AsyncTask hoạt động trong Android). Điều này giúp đảm bảo rằng các thao tác cập nhật giao diện người dùng sẽ diễn ra mượt mà và không bị gián đoạn.
  3. Người gọi có thể tùy chỉnh luồng mà giao diện sẽ được thực hiện (ví dụ như trong iOSxóc đĩa, với NSURLConnection, bạn có thể thiết lập 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 cụ thể cách hoạt động của ứng dụng và đảm bảo rằng các tác vụ quan trọng được thực thi trên đúng luồng cần thiết, từ đó tối ưu hiệu suất và tránh những vấn đề không mong muốn trong quá trình xử lý.

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

Để có thể điều độ mã thực thi sang các luồng khácboi tu vi, chúng ta cần sử dụng phần tiếp theo của loạt bài viết này. Xử lý bất đồng bộ trong phát triển Android và iOS (phần một) — Tổng quan. Cuối cùngboi tu vi, một số công nghệ được đề cập như GCD và NSOperationQueue trong iOS, cũng như phương thức performSelectorXXX, đều là những công cụ hữu ích để quản lý luồng (thread) trong hệ điều hành này. Ở phía Android, ExecutorService, AsyncTask và Handler cũng đóng vai trò tương tự trong việc giúp lập trình viên kiểm soát hiệu quả các tác vụ chạy song song hoặc nền. Tuy nhiên, cần lưu ý rằng ExecutorService không thể sử dụng để lên lịch cho luồng chính (main thread), mà chỉ có thể hoạt động trên các luồng phụ (background threads). Điều quan trọng là chúng ta nên hiểu bản chất của việc lên lịch luồng: trước khi có thể thực thi một đoạn mã trên một luồng cụ thể, luồng đó phải có một vòng lặp sự kiện (event loop). Vòng lặp này, như tên gọi gợi ý, là một vòng lặp liên tục lấy ra các thông điệp từ hàng đợi (message queue) và xử lý chúng. Khi chúng ta thực hiện việc lên lịch cho một đoạn mã, chúng ta đang gửi một thông điệp vào hàng đợi này. Hàng đợi này đã được đảm bảo là an toàn cho luồng (thread-safe queue) trong thực hiện hệ thống, nhờ vậy các nhà phát triển không cần lo lắng về vấn đề an toàn luồng khi thao tác với nó. Trong phát triển ứng dụng khách (client development), hệ điều hành sẽ luôn tạo sẵn một vòng lặp sự kiện cho luồng chính, nhưng đối với các luồng khác, lập trình viên phải tự tay sử dụng công nghệ phù hợp để thiết lập. Điều này nhấn mạnh tầm quan trọng của việc hiểu rõ cách các luồng hoạt động và làm thế nào để chúng giao tiếp với nhau một cách hiệu quả thông qua hàng đợi sự kiện. Việc hiểu rõ bản chất này không chỉ giúp lập trình viên tránh được các lỗi tiềm ẩn mà còn tối ưu hóa hiệu suất của ứng dụng.

Trong phần lớn các trường hợp lập trình trên clientboi tu vi, chúng ta thường mong muốn callback kết quả được thực hiện trên luồng chính (main thread), vì đây là thời điểm thích hợp để cập nhật giao diện người dùng (UI). Còn việc callback giữa chừng sẽ chạy trên luồng nào thì phụ thuộc vào ngữ cảnh cụ thể. Trong ví dụ về Downloader trước đó, callback downloadProgress được sử dụng để báo cáo tiến độ tải xuống, và thông thường, tiến độ tải xuống cũng cần hiển thị trên giao diện người dùng. Do đó, việc chạy callback downloadProgress trên luồng chính sẽ mang lại trải nghiệm tốt hơn cho người dùng. Ngoài ra, khi làm việc với các tác vụ đồng bộ hoặc bất đồng bộ, việc quản lý luồng đúng cách không chỉ giúp cải thiện hiệu suất mà còn đảm bảo tính ổn định của ứng dụng. Nếu một callback quan trọng như downloadProgress không được xử lý trên luồng chính, có thể dẫn đến tình trạng giao diện không phản hồi kịp thời, gây khó chịu cho người dùng. Vì vậy, việc xác định luồng thực thi phù hợp cho từng callback là một yếu tố quan trọng trong quá trình phát triển ứng dụng client.

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

Khi thực hiện một yêu cầu API bất đồng bộboi tu vi, chúng ta thường cần lưu tạm thời một bản ghi chứa thông tin ngữ cảnh liên quan đến lần gọi đó. Khi tác vụ bất đồng bộ hoàn tất và callback được kích hoạt, chúng ta có thể truy xuất lại bản ghi ngữ cảnh này để sử dụng tiếp. Trong thực tế phát triển ứng dụng, việc quản lý ngữ cảnh trong các tác vụ bất đồng bộ là vô cùng quan trọng. Để làm điều này, các lập trình viên thường sử dụng các đối tượng hoặc biến toàn cục để lưu trữ ngữ cảnh tại thời điểm gọi API. Khi callback được thực thi, họ sẽ lấy lại ngữ cảnh đã lưu để xử lý tiếp tục các bước tiếp theo trong quy trình. Đôi khi, thay vì chỉ lưu trữ ngữ cảnh đơn giản, chúng ta còn cần lưu thêm nhiều thông tin bổ sung như trạng thái tiến trình, dữ liệu đầu vào ban đầu, hoặc các tham số tùy chỉnh khác. Điều này giúp đảm bảo rằng quy trình xử lý sau khi callback được kích hoạt vẫn diễn ra chính xác và hiệu quả.

Hãy tiếp tục lấy ví dụ về trình tải xuống trước đó để minh họa. Để có thể thảo luận một cách rõ ràng về các tình huống khác nhauxóc đĩa, ở đây chúng ta sẽ giả định một ví dụ hơi phức tạp hơn. Giả sử rằng chúng ta cần tải xuống nhiều bộ sticker, và mỗi bộ sticker bao gồm nhiều tệp hình ảnh biểu cảm. Sau khi tải xong tất cả các tệp hình ảnh, 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 trên hệ thống). Điều này giúp người dùng có thể sử dụng chúng trong khung nhập liệu của mình.

Giả sử cấu trúc dữ liệu của 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ốngVSBET, 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ử tải xuống gói biểu tượng mà chúng ta cần thực hiện tuân theo định nghĩa giao diện sau:

								
									
										public
									 interface
									 EmojiDownloader
									 {
									
    /** * Khởi động quá trình 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 đâyVSBET, chúng ta định nghĩa các giao diện liên quan đến việc gọi lại, nhưng điều này không phải là trọng tâm trong cuộc thảo luận của chúng ta. */
    // 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 stickerboi tu vi, 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:

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

Lưu ý: Điều được nói đến ở đây là "toàn cục"VSBET, tức là đối với bên trong một trình tải xuống gói 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 DownloaderVSBET, được thiết kế để thực hiện các chức năng tải dữ liệu.
        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 xongVSBET, tiếp tục tải 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ó một sticker đang được tải xuống cùng một lúc. Bạn phải đợi sticker trước hoàn tất quá trình tải mới bắt đầu tải sticker tiếp theo. Điều này có thể gây ra sự chậm trễ nếu bạn cần tải nhiều sticker liên tục.

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 trữ ngữ cảnh.

Dựa trên định nghĩa hiện tại của giao diện Downloaderboi tu vi, chúng ta chỉ có thể sử dụng URL làm khóa để ánh xạ mối quan hệ. Do 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 sao ngữ cảnh cho từng URL riêng lẻ. Dưới đây là mã nguồn 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, []) ``` Phần mã trên cho phép chúng ta thêm ngữ cảnh vào từng URL cụ thể và truy xuất lại ngữ cảnh đó khi cần thiết. Tuy nhiên, cách tiếp cận này có thể dẫn đến sự phức tạp nếu số lượng URL trong mỗi bộ sticker quá lớn.

								
									
										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 cụ thể để quản lý quá trình tải xuống hiệu quả. */
    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 Downloaderxóc đĩa, được thiết kế để thực hiện các chức năng tải dữ liệu.
        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 xongVSBET, tiếp tục tải 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 thấy biến duy nhất để tham chiếu dữ liệu ngữ cảnh một cách chính xác. Trong ví dụ về công cụ tải xuống bộ sưu tập biểu tượng cảm xúc nàyVSBET, biến mà lẽ ra phải là đại diện duy nhất cho việc tải xuống chính là emojiId, nhưng giá trị này lại không thể được truy xuất trong giao diện callback của lớ Do đó, cách giải quyết được chọn là tạo một bản đồ riêng cho mỗi URL để ánh xạ đến dữ liệu ngữ cảnh. Tuy nhiên, hệ quả của việc này là nếu hai bộ sưu tập biểu tượng cảm xúc khác nhau cùng chia sẻ một URL giống nhau, sẽ xảy ra xung đột. Thêm vào đó, cách làm này cũng khá phức tạp khi triển khai và quản lý.

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

Tác vụ {task_id} đã được khởi động 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 phiên bả
        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 xongVSBET, tiếp tục tải 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
									)
									 {
									
        ...
									
    }
									
}
									

								

Việc làm này rõ ràng có những nhược điểm: tạo ra một instance của trình tải về cho mỗi tác vụ tải xuống sẽ đi ngược lại mục đích ban đầu trong thiết kế giao diệ Điều này sẽ dẫn đến việc tạo ra vô số instance dư thừa. Đặc biệtVSBET, khi instance của giao diện là một đối tượng nặng với nhiều tài nguyên, cách làm này sẽ gây ra một lượng đáng kể chi phí về mặt hiệu suất. Hơn nữa, việc quản lý bộ nhớ cũng trở nên phức tạp hơn khi có quá nhiều instance được khởi tạo cùng lúc, có thể dẫn đến tình trạng lãng phí tài nguyên hệ thống.

Ba cách làm trên đều chưa thực sự hoàn hảo. Nguyên nhân xuất phát từ chỗ: giao diện bất đồng bộ downloader ở tầng không hỗ trợ việc truyền bối cảnh (context). Cần lưu ý rằng điều này không liên quan gì đến Context trong hệ thố Đối với tham số nàyxóc đĩa, mỗi người có thể đặt tên khác nhau tùy theo cách hiểu của mình như: - Một số người gọi nó là bộ đệm trạng thái (state buffer) - Một số khác lại gọi là thông tin phiên bản (version info) - Hoặc thậm chí có người nghĩ đó giống như một túi thông tin (info bag) Tuy nhiên, bất kể tên gọi nào đi chăng nữa, vấn đề cốt lõi vẫn là nó thiếu khả năng hỗ trợ truyền tải ngữ cảnh cần thiết cho quá trình xử lý.

  • ngữ cảnh (context).
  • tham số truyền thẳng.
  • callbackData
  • cookie
  • userInfo

Dù tên của tham số này là gìVSBET, vai trò của nó vẫn không thay đổi: khi gọi các giao diện bất đồng bộ, bạn cần truyền nó vào, và khi giao diện trả về bằng cách gọi lại (callback), tham số này 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 ở tầng trên, còn tầng dưới chỉ cần thực hiện việc truyền thẳng (passthrough) mà không cần hiểu ý nghĩa của nó. Ngoài ra, việc sử dụng tham số ngữ cảnh này giúp tăng tính linh hoạt cho hệ thống, cho phép các lớp dưới cùng tập trung vào nhiệm vụ chính của chúng mà không cần quan tâm đến ngữ cảnh cụ thể của yêu cầu. Điều này không chỉ làm giảm sự phức tạp trong mã nguồn mà còn cải thiện khả năng tái sử dụng và bảo trì trong quá trình phát triển phần mềm.

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

								
									
										public
									 interface
									 Downloader
									 {
									
    /** * Thiết lập trình lắ * @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 máy tính sau khi tải xuống. * @param contextData Dữ liệu ngữ cảnhVSBET, 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
									;
									// Đầu vào có lỗi.
    public
									 static
									 final
									 int
									 NETWORK_UNAVAILABLE
									 =
									 2
									;
									//Mạng không khả dụng
    public
									 static
									 final
									 int
									 UNKNOWN_HOST
									 =
									 3
									;
									//Lỗi phân giải tên miền
    public
									 static
									 final
									 int
									 CONNECT_TIMEOUT
									 =
									 4
									;
									//Hết thời gian kết nối
    public
									 static
									 final
									 int
									 HTTP_STATUS_NOT_OK
									 =
									 5
									;
									//Yêu cầu tải xuống 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ó chỗ để 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ó chỗ để 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ó chỗ để 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ữ thẻ SD cục bộ.
    public
									 static
									 final
									 int
									 UNKNOWN_FAILED
									 =
									 10
									;
									//Lỗi chưa xác định khác

    /** * Hàm xử lý callback khi tải xuống thành công. * @param url Đường dẫn nguồn của 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 đi kèm để hỗ trợ thêm cho quá trình xử lý. */
    void
									 downloadSuccess
									(
									String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									);
									
    /** * Hàm xử lý khi tải xuống thất bại. * @param url Địa chỉ nguồn tài nguyên. * @param errorCode Mã lỗixóc đĩa, được sử dụng để xác định vấn đề xảy ra trong quá trình tải. * @param errorMessage Thông báo lỗi ngắn gọn, giúp người dùng hoặc nhà phát triển hiểu rõ hơn về nguyên nhân thất bại. * @param contextData Dữ liệu ngữ cảnh bổ sung, có thể chứa các thông tin liên quan giúp phân tích sâu thêm. * Ngoài ra, bạn cũng có thể thêm tùy chọn ghi log tự động vào file để theo dõi lỗi một cách chi tiết hơn. */
    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 size của dữ liệu đã tải. * @param kich_thuoc_tong tổng size của tài nguyên. * @param du_lieu_lctx dữ liệu ngữ cảnh bổ sung. */
    void
									 downloadProgress
									(
									String
									 url
									,
									 long
									 downloadedSize
									,
									 long
									 totalSize
									,
									 Object
									 contextData
									);
									
}
									

								

Sử dụng giao diện Downloader mới nhất nàyVSBET, trình tải xuống gói biểu tượng trước đó đã có cách thực hiện 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 DownloaderVSBET, được thiết kế để thực hiện các chức năng tải dữ liệu.
        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ứ 0xóc đĩa, 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. Điều này cho phép bạn truy cập vào dữ liệu cụ thể mà không bị giới hạn bởi kiểu chung ban đầuxóc đĩa, từ đó dễ dàng xử lý các yêu cầu theo ngữ cảnh một cách hiệu quả và linh hoạt.
        EmojiDownloadContext
									 downloadContext
									 =
									 (
									EmojiDownloadContext
									)
									 contextData
									;
									

        downloadContext
									.
									localPathList
									.
									add
									(
									localPath
									);
									
        downloadContext
									.
									downloadedEmoji
									++;
									
        EmojiPackage
									 emojiPackage
									 =
									 downloadContext
									.
									emojiPackage
									;
									
        if
									 (
									downloadContext
									.
									downloadedEmoji
									 <
									 emojiPackage
									.
									emojiUrls
									.
									size
									())
									 {
									
            // Chưa tải xongVSBET, tiếp tục tải 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àngboi tu vi, phương pháp thực hiện thứ tư có vẻ hợp lý hơn cả. Nó giúp mã nguồn trở nên cô đọng và không vướng phải các nhược điểm mà ba phương pháp trước mắc phải. Tuy nhiên, phương pháp này đòi hỏi các giao diện bất đồng bộ ở tầng thấp phải có khả năng hỗ trợ truyền ngữ cảnh một cách hoàn chỉnh. Trong thực tế, những giao diện mà chúng ta cần sử dụng thường đã được xác định sẵn và không thể thay đổi. Nếu các giao diện đó không hỗ trợ tốt cho việc truyền tham số ngữ cảnh, 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 làm trước đây. Về cơ bản, việc thảo luận về ba phương pháp đầu tiên không phải là tự tìm rắc rối, mà nhằm giải quyết các trường hợp gặp phải khi làm việc với các giao diện thiếu hỗ trợ đầy đủ cho ngữ cảnh trả về, điều này đôi khi là do các nhà thiết kế giao diện vô tình tạo ra những thách thức như vậy. Ngoài ra, trong quá trình phát triển, việc hiểu rõ các giới hạn của từng phương pháp là rất quan trọng để đưa ra quyết định đúng đắn. Một số nhà phát triển có xu hướng ưu tiên tính linh hoạt cao, nhưng nếu không cẩn thận, họ có thể dẫn đến sự phức tạp không cần thiết. Điều này đặc biệt đúng khi làm việc với các hệ thống lớn, nơi mà mỗi quyết định nhỏ đều có thể ảnh hưởng đến hiệu suất và bảo trì về sau. Vì vậy, cân nhắc kỹ lưỡng giữa các ưu và nhược điểm của từng phương pháp sẽ giúp chúng ta tránh được nhiều rủi ro trong tương lai.

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

Bây giờVSBET, chúng ta 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 cùng xem lại một lần nữa cách định nghĩa giao diện trả về (callback) của một số hệ thống từ góc độ truyền tải ngữ cảnh. Ví dụ như trong iOSVSBET, UIAlertViewDelegate có phương thức alertView:clickedButtonAtIndex:, hoặc UITableViewDataSource có phương thức tableView:cellForRowAtIndexPath:. Những giao diện này đều có tham số đầu tiên trả về chính đối tượng UIView liên quan (thực tế là trong UIKit, hầu hết các giao diện trả về đều được định nghĩa theo cách tương tự). Điều này giúp truyền tải một phần ngữ cảnh, cho phép phân biệt giữa các thể hiện khác nhau của UIView, nhưng không thể sử dụng để phân biệt các sự kiện trả về riêng lẻ từ cùng một thể hiện UIView. Giả sử bạn cần hiển thị nhiều hộp thoại UIAlertView liên tiếp trên cùng một màn hình, thì mỗi lần bạn sẽ phải tạo ra một thể hiện UIAlertView mới và trong callback, bạn có thể phân biệt chúng bằng cách dựa vào thể hiện của UIAlertView đã được trả về. Cách làm này khá giống với phương pháp thứ ba mà chúng ta đã thảo luận trước đó. UIView cũng đã được định nghĩa sẵn một thuộc tính tag để truyền tải ngữ cảnh dạng số nguyên, nhưng nếu bạn muốn truyền ngữ cảnh ở dạng khác, thì bạn chỉ có thể thực hiện như phương pháp thứ ba đã đề cập, tức là tạo ra một lớp con mới của UIView và thêm các tham số ngữ cảnh vào bên trong lớp đó. Trong trường hợp phức tạp hơn, khi bạn muốn xử lý ngữ cảnh đa dạng và linh hoạt hơn, việc sử dụng một lớp con có thể trở nên rườm rà và không thực sự tối ưu. Tuy nhiên, đây vẫn là giải pháp phổ biến nhất trong các trường hợp như vậy. Một số lập trình viên lựa chọn sử dụng từ điển (dictionary) hoặc cấu trúc dữ liệu phức tạp hơn để lưu trữ ngữ cảnh, điều này cho phép họ truyền tải nhiều loại dữ liệu hơn, nhưng cũng đi kèm với nguy cơ giảm hiệu suất và độ sạch của mã nguồn. Vì vậy, việc cân nhắc giữa các phương án này luôn đòi hỏi sự cẩn trọng và hiểu biết rõ về yêu cầu cụ thể của từng dự án.

Mỗi lần UIView được hiển thị lạiVSBET, một instance mới sẽ được tạo ra, điều này không nhất thiết phải được xem là chi phí quá cao. Thực tế, cách sử dụng thông thường của UIView chính là tạo ra và thêm 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 đó thì hoàn toàn khác biệt. Giao diện callback của nó được thiết kế để trả về instance của IndependentVideoManager như là tham số đầu tiên, chẳng hạn như ivManager:isIndependentVideoAvailable:. Có thể suy đoán rằng định nghĩa giao diện callback này chắc chắn đã tham khảo UIKit. Nhưng tình huống của IndependentVideoManager lại rõ ràng khác biệt, thông thường chỉ cần tạo một instance duy nhất và sau đó gọi nhiều lần các phương thức trên cùng một instance để 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 trên cùng một instance, mỗi lần callback mang theo những tham số ngữ cảnh nào. Những gì thực sự cần ở đây liên quan đến khả năng truyền ngữ cảnh, giống như cách mà chúng ta đã thảo luận trong trường hợp thứ tư. Trong khi giao diện kiểu UIKit cung cấp khả năng truyền ngữ cảnh có giới hạn so với nhu cầu thực tế. Trong trường hợp của IndependentVideoManager, việc quản lý ngữ cảnh giữa các lần callback cần phải linh hoạt hơn rất nhiều. Mỗi lần callback không chỉ đơn thuần là truyền tải dữ liệu, mà còn phải đảm bảo rằng ngữ cảnh hiện tại không bị lẫn lộn hoặc mất đi giữa các lần gọi. Điều này đặt ra yêu cầu cao hơn đối với việc thiết kế giao diện, vì cần đảm bảo rằng mọi thông tin ngữ cảnh đều được xử lý chính xác và không bị ảnh hưởng bởi các lần callback trước hoặc sau. Nếu không làm tốt điều này, có thể dẫn đến lỗi logic hoặc các vấn đề về hiệu suất không mong muốn. Ngược lại, UIKit cung cấp một hệ thống callback tương đối ổn định, nhưng nó không đủ linh hoạt để xử lý các trường hợp phức tạp như UIKit chủ yếu tập trung vào các yếu tố cơ bản như tạo và quản lý view, trong khi IndependentVideoManager cần một giải pháp chuyên biệt hơn, có khả năng điều chỉnh và tối ưu hóa các yếu tố ngữ cảnh theo từng yêu cầu cụ thể. Điều này nhấn mạnh tầm quan trọng của việc lựa chọn đúng công cụ hoặc giao diện phù hợp với từng mục đích sử dụng, đặc biệt là trong các ứng dụng đòi hỏi tính linh hoạt và độ chính xác cao.

Trong thiết kế giao diện callbackboi tu vi, khả năng truyền ngữ cảnh, điểm quan trọng nhất 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 xem qua một ví dụ trên Android. Trên nền tảng Androidboi tu vi, giao diện callback thường được biểu thị dưới dạng listener, với mã nguồn điển hình như sau:

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

								

Trong đoạn mã nàyboi tu vi, một đối tượng Button có thể liên kết với nhiều hàm gọi lại (nhiều sự kiện nhấp chuột), nhưng chúng ta không thể phân biệt cách xử lý giữa các hàm gọi lại khác nhau chỉ bằng đoạn mã này. Tuy nhiên, may mắn là chúng ta thực tế cũng không cần phải làm điều đó. Trong lập trình, việc liên kết nhiều hành động với cùng một nút điều khiển thường được sử dụng để tạo ra các chức năng linh hoạt. Nhưng nếu bạn muốn tách biệt từng hành động cụ thể, bạn có thể cân nhắc thêm một tham số bổ sung hoặc một thuộc tính nhận diện cho mỗi lần nhấp. Điều này giúp dễ dàng xác định chính xác hành động nào đang được thực hiện trong từng trường hợp. Như vậy, dù không cần phân biệt giữa các sự kiện trong trường hợp hiện tại, việc hiểu rõ cách hoạt động sẽ giúp bạn thiết kế ứng dụng của mình linh hoạt hơn trong tương lai.

Dựa trên những phân tích ở trênxóc đĩa, chúng ta nhận thấy rằng các công việc thuộc phần "frontend" liên quan đến giao diện View thường không yêu cầu phân biệt giữa các lần gọi lại của một instance interface cụ thể. Do đó, cơ chế truyền ngữ cảnh phức tạp không phải lúc nào cũng cần thiết trong trường hợp này. 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 lâu dài, khả năng truyền ngữ cảnh mạnh mẽ trở nên cực kỳ quan trọng. Chính vì vậy, bài viết trước trong loạt bài này đã nhấn mạnh vấn đề xử lý bất đồng bộ như một công việc gắn liền mật thiết với lập trì Ngoài ra, trong quá trình phát triển ứng dụng, việc hiểu rõ nhu cầu của từng thành phần là rất cần thiết để tối ưu hóa hiệu suất và đảm bảo tính nhất quán trong toàn bộ hệ thống. Điều này không chỉ giúp lập trình viên dễ dàng quản lý trạng thái mà còn tạo điều kiện cho việc mở rộng ứng dụng về sau. Đặc biệt trong các dự án lớn, việc chọn đúng cơ chế truyền ngữ cảnh sẽ quyết định sự ổn định và hiệu quả của toàn bộ nền tảng.

Khi nói đến vấn đề tham số ngữ cảnh (context)boi tu vi, vẫn còn một số vấn đề nhỏ cần lưu ý: ví dụ như trong iOS, tham số ngữ cảnh này khi được sử dụng trong các tác vụ bất đồng bộ (asynchronous tasks) nên là kiểu tham chiếu mạnh (strong reference) hay tham chiếu yếu (weak reference)? Nếu đó là tham chiếu mạnh, thì nếu đối tượng ngữ cảnh mà người gọi truyền vào là một đối tượng lớn như View Controller, điều đó có thể dẫn đến hiện tượng giữ tài nguyên (circular reference), từ đó gây ra rò rỉ bộ nhớ (memory leak). Ngược lại, nếu đó là tham chiếu yếu, nhưng đối tượng ngữ cảnh mà người gọi truyền vào là một đối tượng tạm thời (temporary object), nó sẽ bị giải phóng ngay sau khi được tạo ra, khiến đối tượng không thể truyền tiếp đến nơi cần thiết. Đây thực chất là bài toán hai mặt do cơ chế quản lý bộ nhớ dựa trên đếm tham chiếu (reference counting) mang lại. Điều này phụ thuộc vào kịch bản mà chúng ta kỳ vọng. Trong trường hợp chúng ta đang thảo luận, tham số ngữ cảnh này được dùng để phân biệt giữa các lần gọi callback của cùng một phiên bản giao diện (interface instance). Do đó, tham số ngữ cảnh thường không phải là một đối tượng có vòng đời dài như View Controller, mà là một đối tượng có vòng đời ngắn tương đương với nhiệm vụ bất đồng bộ. Nó sẽ được tạo ra mỗi khi bắt đầu một cuộc gọi giao diện và giải phóng khi nhiệm vụ bất đồng bộ kết thúc (khi callback xảy ra). Vì vậy, trong kịch bản này, chúng ta nên duy trì tham chiếu mạnh cho đối tượng ngữ cảnh mà tham số được truyền vào.

Thứ tự callback.

xáo trộn callback

  • dữ liệu trả về bị xáo trộn
  • Là bên thực hiện giao diệnboi tu vi, 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ự của các hàm callback hay không: nghĩa là đảm bảo các callback sẽ không xảy ra tình trạng lộn xộn về mặt thứ tự. Nếu yêu cầu này được đặt ra, thì điều đó đồng nghĩa với việc độ phức tạp trong việc triển khai giao diện sẽ tăng lên đáng kể. Điều này đòi hỏi phải có sự kiểm soát chặt chẽ hơn trong quá trình thiết kế và lập trình, nhằm đảm bảo rằng mọi hoạt động đều diễn ra theo đúng thứ tự đã định sẵn.

Về phía thực hiện giao diện bất đồng bộxóc đĩa, các yếu tố có thể gây ra thứ tự callback hỗn loạn có thể có:

  • Kết quả trả về thất bại sớm có thể xảy ra thực tế hơn bạn nghĩboi tu vi, nhưng lại rất khó nhận ra rằng điều này có thể làm xáo trộn thứ tự của cá Một ví dụ điển hình là khi một tác vụ bất đồng bộ được thiết kế để chạy trên một luồng bất đồng bộ khác. Tuy nhiên, trước khi được chuyển sang luồng mới này, một lỗi nghiêm trọng đã được phát hiện (ví dụ như tham số đầu vào không hợp lệ), dẫn đến việc toàn bộ tác vụ bị hủy và callback thất bại được kích hoạt. Điều này có nghĩa là những tác vụ bất đồng bộ khởi động sau nhưng lại gặp lỗi sớm có thể sẽ nhận được callback trước cả những tác vụ khởi động trước đó nhưng đang vận hành bình thường. Trong nhiều trường hợp, điều này có thể gây ra sự nhầm lẫn hoặc các vấn đề không mong muốn trong logic xử lý của ứng dụng.
  • kết quả trả về thất bại trước thời hạn
  • Việc thực hiện song song các tác vụ bất đồng bộ. Ở phía sau giao diện bất đồng bộ có thể có một pool thread (nhóm luồng) để thực hiện đồng thờixóc đĩa, do đó thứ tự hoàn thành của các tác vụ bất đồng bộ sẽ là ngẫu nhiên. Các tác vụ bất đồng bộ này thường được quản lý bởi một hệ thống điều phối chuyên dụng, nơi mỗi tác vụ được đưa vào hàng đợi và xử lý theo cách không đồng bộ. Khi pool thread được kích hoạt, các tác vụ trong hàng đợi sẽ được phân phối đến các luồng khác nhau trong nhóm, cho phép chúng chạy đồng thời thay vì tuần tự. Điều này không chỉ giúp tăng hiệu suất mà còn giảm tải đáng kể cho luồng chính, tạo ra một môi trường ổn định hơn cho các ứng dụng phức tạp. Tuy nhiên, do tính chất ngẫu nhiên của việc phân bổ và xử lý, thứ tự hoàn thành của các tác vụ không thể đảm bảo. Điều này đòi hỏi lập trình viên phải thiết kế logic xử lý kết quả một cách linh hoạt, chẳng hạn như sử dụng các cơ chế đồng bộ hóa hoặc kiểm tra trạng thái trước khi tiếp tục xử lý.
  • Các nhiệm vụ bất đồng bộ phụ thuộc khác bị hỗn loạn thứ tự callback.

Dù là trường hợp điều chỉnh lại (callback) bị xáo trộn theo bất kỳ cách nàoxóc đĩa, chúng ta vẫn có thể đảm bảo thứ tự của các callback phù hợp với thứ tự ban đầu khi gọi giao diện. Để làm được đ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 để bắt đầu một tác vụ bất đồng bộ, hãy thêm tham số gọi cũng như một số thông tin ngữ cảnh khác vào hàng đợi. Sau đó, các callback sẽ được thực hiện theo đúng thứ tự mà các phần tử trong hàng đợi được lấy ra, từ đó duy trì sự nhất quán về thứ tự. Điều này giúp chúng ta dễ dàng quản lý và kiểm soát dòng chảy của dữ liệu, ngay cả khi các tác vụ chạy ở chế độ không đồng bộ. Nhờ đó, việc xử lý logic phức tạp trở nên có tổ chức hơn và ít xảy ra lỗi liên quan đến thứ tự hoạt động.

Trong nhiều trường hợpVSBET, người sử dụng giao diện không quá khắt khe, việc các callback bị xáo trộn thứ tự đôi khi sẽ không dẫn đến hậu quả nghiêm trọng. Tất nhiên, điều này chỉ đúng khi họ hiểu rõ vấn đề và có nhận thức đầy đủ về tình huống này. Do đó, việc chúng ta cố gắng đảm bảo callback luôn tuân theo thứ tự trong quá trình triển khai giao diện có thể không còn cần thiết như ban đầu. Tuy nhiên, cách lựa chọn cuối cùng vẫn phụ thuộc vào yêu cầu của từng ngữ cảnh cụ thể cũng như sở thích cá nhân của người thực hiện giao diện. Có thể nói, sự linh hoạt trong việc đưa ra quyết định sẽ giúp tối ưu hóa hiệu quả của cả hệ thống.

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ỉ gồm một phương thức)VSBET, đôi khi chúng ta có thể định nghĩa callback dưới dạ Trong môi trường iOS, chúng ta có thể sử dụng block để đạt được điều này; còn trong Android, có thể áp 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). Trong thực tế phát triển, việc chọn cách sử dụng closure hay không tùy thuộc vào yêu cầu cụ thể của từng nền tảng. Đối với iOS, block giúp tăng tính linh hoạt và dễ đọc mã nguồn hơn, đặc biệt khi xử lý các tác vụ ngắn hạn hoặc logic nhỏ gọn. Còn đối với Android, việc dùng lambda cho phép giảm thiểu khối lượng code cần viết mà vẫn giữ nguyên hiệu quả, đặc biệt là khi làm việc với các thư viện hỗ trợ như RxJava hoặc Kotlin. Dù lựa chọn phương án nào, điều quan trọng là phải đảm bảo rằng mã nguồn vẫn duy trì tính rõ ràng và dễ bảo trì trong quá trình phát triển ứng dụng.

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 callbackxóc đĩa, như sau:

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

    /** * Callback khi quá trình tải xuống hoàn tất. * @param errorCode Mã lỗi. Nếu là SUCCESS thì tải xuống thành côngVSBET, các mã khác cho thấy tải xuống thất bại. * @param url Địa chỉ tài nguyên được yêu cầu tải. * @param localPath Vị trí lưu trữ tài nguyên đã tải trên thiết bị. * @param contextData Dữ liệu ngữ cảnh liên quan. * Ngoài ra, có thể thêm tùy chọn để thông báo trạng thái tiến trình tải xuống chi tiết hơn. */
    void
									 downloadFinished
									(
									int
									 errorCode
									,
									 String
									 url
									,
									 String
									 localPath
									,
									 Object
									 contextData
									);
									
}
									

								

Vậy thìVSBET, giao diện Downloader cũng có thể được đơn giản hóa thay vì cần một giao diện setListener tách 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 chính cấu trúc của nó. Cụ thể như sau: ``` interface Downloader { void download(String url, Callback callback); } interface Callback { void onSuccess(String result); void onFailure(String error); } ``` Bằng cách này, việc xử lý kết quả tải xuống sẽ trở nên linh hoạt và gọn gàng hơn, giúp giảm thiểu các lớp trung gian không cần thiết trong hệ thống.

								
									
										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 xong. * @param contextData Dữ liệu ngữ cảnhboi tu vi, sẽ được truyền lại qua giao diệ Có thể là bất kỳ loại dữ liệu nào. * @param listener Thực thể của giao diện callback để xử lý các sự kiện trong quá trình tải. */
    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 sử dụng sẽ trông gọn gàng và dễ đọc hơn. Thêm vào đóVSBET, giao diện trả về callback có thể nhận giá trị dưới dạng closure, tạo sự linh hoạt cao cho lập trình viên. Tuy nhiên, nếu mức độ lồng nhau của các hàm callback trở nên quá sâu, vấn đề Callback Hell sẽ xuất hiện – một tình trạng mà code trở nên khó quản lý và bảo trì do sự phức tạp gia tăng không kiểm soát. Điều này không chỉ làm giảm hiệu quả công việc mà còn gây nhầm lẫn trong quá trình phát triển phần mềm. http://callbackhell.com Đang tải xuống tệp tin thứ nhất...

								
									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 Hellxóc đĩa, bài viết này http://callbackhell.com Giữ mã nguồn của bạn gọn gàng

Tuy nhiênxóc đĩa, khi nói đến vấn đề lập trình bất đồng bộ trong 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 với mọi tình huống. Thực tế, cho dù đó là code mà chúng ta đọc được từ người khác hay code do chính mình viết, phần lớn thời gian chúng ta đều đối mặt với những tình huống lập trình bất đồng bộ cơ bản. Điều quan trọng cần suy nghĩ cẩn thận không phải là việc áp dụng một khung công cụ nào đó, mà là làm thế nào để giải quyết logic một cách rõ ràng và hiệu quả. Không có gì đảm bảo rằng chỉ cần sử dụng một framework nhất định sẽ tự động giải quyết tất cả các vấn đề.


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

Thực tếxóc đĩa, việc định nghĩa giao diện tốt đòi hỏi một trình độ sâu sắc và kinh nghiệm dày dặn, 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ể làm đượ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 và giao diện trả về hoàn hảo. Tuy nhiên, điều quan trọng cần nhớ là không có bất kỳ lựa chọn nào là hoàn hảo tuyệt đối; thay vào đó, chúng ta cần tìm sự cân bằng giữa các ưu điểm và nhược điểm. Đây chính là nghệ thuật của việc đưa ra quyết định trong lập trình.

Làm thế nào để thực hiện điều này?

  • Logic hoàn chỉnh (các logic giao diện không chồng lấn và không thiếu sót).
  • Có thể tự giải thích được.
  • Có một mô hình trừu tượng hợp lý đằng sau.
  • Quan trọng nhất: Làm người dùng thoải mái và đáp ứng được 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ốcxóc đĩa, 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 này: /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 tôi "Trương Thiết Lệ" trên Weibo.
Tài khoản WeChat của tôi: tielei-blog (Trương Thiết Lệ)
Bài trước: Giải thích bằng một bức ảnh về kiểm soát luồng trong RxJava
Bài sau: Xử lý bất đồng bộ trong phát triển Android và iOS (phần ba) —— Cộng tác giữa nhiều tác vụ bất đồng bộ

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