Xử lý bất đồng bộ trong phát triển Android và iOS Các mối quan hệ hợp tác giữa các tác vụ bất đồng bộ được thảo luận chi tiết dưới ba tình huống ứng dụng khác nhau. Ba tình huống ứng dụng này bao gồm: Trong phần thứ ba của tác phẩm nàyđá gà trực tiếp app, chúng ta sẽ tập trung những vấn đề có thể phát sinh khi thực hiện đồng thời nhiều tác vụ bất đồng bộ. Khi làm việc với các tác vụ như vậy, có rất nhiều yếu tố cần được cân nhắc để đảm bảo hiệu quả và độ ổn định của toàn bộ quy trình.
Thông thườngđá gà trực tiếp app, chúng ta cần thực hiện nhiều tác vụ bất đồng bộ và khiến chúng phối hợp với nhau để hoàn thành yêu cầu. Bài viết này sẽ phân tích ba mối quan hệ phối hợp giữa các tác vụ bất đồng bộ dựa trên các tình huống sử dụng phổ biến trong thực tế: 1. **Phối hợp tuần tự**: Các tác vụ được thực hiện lần lượt theo thứ tự xác định, mỗi tác vụ chỉ bắt đầu khi tác vụ trước đó đã hoàn tất. Điều này giúp đảm bảo rằng dữ liệu từ tác vụ trước sẽ sẵn sàng cho tác vụ tiếp theo. 2. **Phối hợp song song**: Nhiều tác vụ được khởi chạy cùng lúc mà không phụ thuộc lẫn nhau. Cách làm này tối ưu hóa hiệu suất bằng cách tận dụng khả năng xử lý đa luồng của hệ thống. 3. **Phối hợp điều kiện**: Một số tác vụ chỉ được kích hoạt khi một điều kiện cụ thể được đáp ứng. Điều này giúp kiểm soát chặt chẽ hơn quá trình thực thi và đảm bảo tính linh hoạt trong việc đáp ứng các yêu cầu phức tạp. Hy vọng những giải thích này sẽ giúp bạn hiểu rõ hơn về cách các tác vụ bất đồng bộ có thể tương tác với nhau một cách hiệu quả.
Ba mối quan hệ hợp tác trên sẽ được thảo luận cụ thể thông qua ba ví dụ ứng dụng trong bài viết này. Ba ứng dụng này bao gồm:
yêu cầu mạng song song
Lưu ý: Mã nguồn trong loạt bài viết này đã được sắp xếp gọn gàng trên GitHub (liên tục cập nhật)99win club, đường dẫn kho lưu trữ mã nguồn là:
Trong bài viết nàybxh ngoai hang anh, mã nguồn Java được đề cập nằm trong package có tên là com. demos.async. multitask. Đây là nơi mà các đoạn code liên quan đến lập trình đa nhiệm và xử lý bất đồng bộ được tổ chức một cách có hệ thống để người đọc dễ dàng theo dõi và hiểu rõ cách hoạt động của chúng. Package này đóng vai trò như một thư viện tham chiếu cho các ví dụ cụ thể trong bài, giúp minh họa sâu hơn về chủ đề đang được thảo luận.
Thực thi theo thứ tự nối tiếp
Một ví dụ điển hình là việc sử dụng đa cấp bộ nhớ đệm cho tài nguyên tĩnh99win club, trong đó người ta thường nhắc đến nhất chính là việc bộ nhớ đệm đa cấp cho hình ảnh tĩnh. Khi tải một hình ảnh tĩnh trên client, thông thường sẽ có ít nhất hai cấp bộ nhớ đệm: bộ nhớ đệm cấp 1 (Memory Cache) và bộ nhớ đệm cấp 2 (Disk Cache). Quy trình tải hình ảnh này diễn ra như sau: Trước tiên, khi người dùng yêu cầu một hình ảnh, hệ thống sẽ kiểm tra xem hình ảnh đó đã tồn tại trong bộ nhớ đệm cấp 1 hay chưa. Nếu hình ảnh nằm trong bộ nhớ đệm Memory Cache, nó sẽ được truy xuất ngay lập tức với tốc độ cực nhanh. Tuy nhiên, nếu không tìm thấy trong Memory Cache, hệ thống sẽ tiếp tục kiểm tra ở cấp 2, đó là Tại đây, hình ảnh sẽ được đọc từ ổ cứng hoặc bộ nhớ lưu trữ cố định khác, và sau đó được trả về cho ứng dụng. Cách làm này giúp tối ưu hóa hiệu suất và giảm tải đáng kể cho máy chủ, đồng thời tăng tốc đáng kể quá trình tải hình ảnh đối với người dùng cuối.
thực hiện theo thứ tự
Tìm kiếm Disk Cache
Trước tiênbxh ngoai hang anh, chúng ta cần xác định rõ giao diện cho hai tác vụ bất đồng bộ là "Bộ nhớ đệm Disk" và "Yêu cầu mạng". Việc này sẽ giúp chúng ta có một khung sườn rõ ràng để quản lý và tương tác với từng tác vụ một cách hiệu quả.
public
interface
ImageDiskCache
{
/** * Lấy đồng bộ hình ảnh Bitmap từ bộ nhớ đệm. * @param key Khóa để truy xuất dữ liệu trong bộ nhớ đệm * @param callback Hàm trả về đối tượng Bitmap đã lưu trong bộ nhớ đệm */
void
getImage
(
String
key
,
AsyncCallback
<
Bitmap
>
callback
);
/** * Lưu đối tượng Bitmap vào bộ nhớ đệm. * @param key Khóa để xác định vị trí của dữ liệu trong bộ nhớ đệm. * @param bitmap Đối tượng Bitmap cần được lưu trữ. * @param callback Hàm trả về kết quả hiện tại của thao tác lưu99win club, cho biết việc lưu thành công hay thất bại. */
void
putImage
(
String
key
,
Bitmap
bitmap
,
AsyncCallback
<
Boolean
>
callback
);
}
Giao diện ImageDiskCache được sử dụng để lưu và truy xuất Cache đĩa cho hình ảnh. Trong đóđá gà trực tiếp app, tham số AsyncCallback là định nghĩa của một giao diện gọi lại bất đồng bộ chung. Mã định nghĩa của nó như sau (giao diện này sẽ còn được đề cập đến trong phần sau của bài viết): ```java public interface AsyncCallback
/**<d>Loại dữ liệu tham số trả về từ giao diện bất đồng bộ.</d>
public
interface
AsyncCallback
<
D
>
{
void
onResult
(
D
data
);
}
Để tải hình ảnh qua mạngđá gà trực tiếp app, chúng ta trực tiếp gọi bài viết trước đó "... Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ Trong tài liệu được đề cập đếnđá gà trực tiếp app, chúng ta sẽ tìm hiểu về giao diện Downloader (chú ý: phiên bản của giao diện Downloader có tham số contextData ở cuối).
Phát yêu cầu tải xuống mạng
// Kiểm tra cache thứ cấp: cache đĩa
imageDiskCache
.
getImage
(
url
,
new
AsyncCallback
<
Bitmap
>()
{
@Override
public
void
onResult
(
Bitmap
bitmap
)
{
if
(
bitmap
!=
null
)
{
// cache đĩa trúng đích99win club, nhiệm vụ tải về kết thúc sớm.
imageMemCache
.
putImage
(
url
,
bitmap
);
successCallback
(
url
,
bitmap
,
contextData
);
}
else
{
// Cả hai cấp cache đều không trúng đíchđá gà trực tiếp app, gọi trình tải về để tải.
downloader
.
startDownload
(
url
,
getLocalPath
(
url
),
contextData
);
}
}
});
Mã ví dụ thực hiện callback kết quả thành công của Downloader như sau:
@Override
public
void
downloadSuccess
(
final
String
url
,
final
String
localPath
,
final
Object
contextData
)
{
// Giải mã hình ảnh99win club, là một hoạt động tốn thời gian, thực hiện bất đồng bộ.
imageDecodingExecutor
.
execute
(
new
Runnable
()
{
@Override
public
void
run
()
{
final
Bitmap
bitmap
=
decodeBitmap
(
new
File
(
localPath
));
// Điều độ lại lên luồng chính.
mainHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
if
(
bitmap
!=
null
)
{
imageMemCache
.
putImage
(
url
,
bitmap
);
imageDiskCache
.
putImage
(
url
,
bitmap
,
null
);
successCallback
(
url
,
bitmap
,
contextData
);
}
else
{
// Giải mã thất bại.
failureCallback
(
url
,
ImageLoaderListener
.
BITMAP_DECODE_FAILED
,
contextData
);
}
}
});
}
});
}
Thực thi đồng thờibxh ngoai hang anh, hợp nhất kết quả
Một ví dụ điển hình là khi bạn gửi nhiều yêu cầu mạng cùng một lúc (các API từ xa)bxh ngoai hang anh, sau đó đợi nhận được tất cả các phản hồi từ các yêu cầu này và xử lý dữ liệu thu thập được một cách đồng bộ, cập nhật giao diện người dùng. Cách làm này giúp giảm thiểu thời gian chờ tổng thể bằng cách tận dụng việc gửi đồng thời các yêu cầu mạng, cho phép hệ thống hoạt động nhanh hơn và hiệu quả hơn.
Chúng tôi sẽ đưa ra ví dụ mã nguồn dựa trên tình huống đơn giản nhất của hai yêu cầu mạ
Trước hếtbxh ngoai hang anh, vẫn cần định nghĩa trước các giao diện bất đồng bộ cần thiết, tức là định nghĩa giao diện API từ xa.
/**
public
interface
HttpService
{
/** * Gửi yêu cầu HTTP. * @param apiUrl Đường dẫn API cần truy cập * @param request Đối tượng tham số yêu cầu (được biểu diễn dưới dạng Java Bean) * @param listener Thiết bị lắng nghe phản hồi để xử lý kết quả * @param contextData Dữ liệu truyền qua để giữ liên kết ngữ cảnh * @param<t>Loại mô hình yêu cầu<r>Loại mô hình phản hồi</r></t>
<
T
,
R
>
void
doRequest
(
String
apiUrl
,
T
request
,
HttpListener
<?
super
T
,
R
>
listener
,
Object
contextData
);
}
/**<t>Loại mô hình yêu cầu<r>Loại mô hình phản hồi</r></t>
public
interface
HttpListener
<
T
,
R
>
{
/** * Giao diện callback được gọi khi có kết quả (thành công hoặc thất bại) từ yêu cầu. * @param apiUrl Đường URL của yêu cầu * @param request Mô hình yêu cầu (request model) * @param result Kết quả yêu cầu (bao gồm phản hồi hoặc lý do lỗi) * @param contextData Tham số truyền qua để giữ ngữ cảnh */
void
onResult
(
String
apiUrl
,
T
request
,
HttpResult
<
R
>
result
,
Object
contextData
);
}
Điều cần lưu ý là trong giao diện HttpServicebxh ngoai hang anh, tham số yêu cầu request được định nghĩa bằng kiể Nếu có một lớp thực hiện giao diện này, thì trong mã thực hiện, nó sẽ phải chuyển đổi đối tượng request (mà có thể là bất kỳ lớp Java Bean nào) thành các tham số của yêu cầu HTTP thông qua cơ chế phản xạ, tùy thuộc vào loại đối tượng thực tế được truyền vào. Tất nhiên, ở đây chúng ta chỉ tập trung vào việc phân tích giao diện, còn việc cụ thể hóa cách triển khai sẽ không được đề cập đến trong phạm vi này.
Tham số kết quả trả về result có kiểu HttpResult99win club, điều này giúp nó có thể biểu thị cả kết quả phản hồi thành công lẫn kết quả phản hồi thất bại. Định nghĩa của HttpResult như sau:
/** * Lớp HttpResult được thiết kế để đóng gói kết quả của một yêu cầu HTTP. Khi máy chủ phản hồi thành côngbxh ngoai hang anh, mã lỗi errorCode sẽ có giá trị là SUCCESS và nội dung phản hồi từ máy chủ sẽ được chuyển đổi thành thuộc tính response; Ngược lại, nếu máy chủ không thể phản hồi một cách hợp lệ, mã lỗi errorCode sẽ khác giá trị SUCCESS và giá trị của thuộc tính response sẽ không còn hợp lệ. * */Loại mô hình phản hồi
public
class
HttpResult
<
R
>
{
/**
public
static
final
int
SUCCESS
=
0
;
// Thành công
public
static
final
int
REQUEST_ENCODING_ERROR
=
1
;
// Lỗi xảy ra khi mã hóa yêu cầu
public
static
final
int
RESPONSE_DECODING_ERROR
=
2
;
// Lỗi xảy ra khi giải mã phản hồi
public
static
final
int
NETWORK_UNAVAILABLE
=
3
;
// Mạng không khả dụng
public
static
final
int
UNKNOWN_HOST
=
4
;
// Tên miền không thể phân giải
public
static
final
int
CONNECT_TIMEOUT
=
5
;
// Kết nối quá thời gian
public
static
final
int
HTTP_STATUS_NOT_OK
=
6
;
// Yêu cầu tải về trả về mã khác 200
public
static
final
int
UNKNOWN_FAILED
=
7
;
// Lỗi chưa xác định khác
private
int
errorCode
;
private
String
errorMessage
;
/** * Trả về là phản hồi mà máy chủ gửi lại. * Chỉ khi errorCode được thiết lập thành SUCCESS99win club, giá trị của phản hồi mới mang tính hợp lệ. */
private
R
response
;
public
int
getErrorCode
()
{
return
errorCode
;
}
public
void
setErrorCode
(
int
errorCode
)
{
this
.
errorCode
=
errorCode
;
}
public
String
getErrorMessage
()
{
return
errorMessage
;
}
public
void
setErrorMessage
(
String
errorMessage
)
{
this
.
errorMessage
=
errorMessage
;
}
public
R
getResponse
()
{
return
response
;
}
public
void
setResponse
(
R
response
)
{
this
.
response
=
response
;
}
}
Kết quả HttpResult cũng bao gồm một kiểu Generic Rđá gà trực tiếp app, đây chính là loại tham số phản hồi được trả về khi yêu cầu thành công. Tương tự, trong việc triển khai có thể xảy ra của HttpService, cơ chế phản chiếu (reflection) sẽ tiếp tục được sử dụng để biến đổi nội dung phản hồi từ yêu cầu (có thể là một chuỗi JSON) thành kiểu dữ liệu R (nó có thể là bất kỳ đối tượng Java nào). Điều này giúp tăng tính linh hoạt và khả năng tương thích cao cho hệ thống, đảm bảo rằng dữ liệu nhận được luôn được xử lý đúng cách dựa trên định nghĩa của kiểu R đã được chỉ định trước đó.
Rồi99win club, với sự hiện diện của giao diện HttpService, chúng ta có thể minh họa cách gửi đồng thời hai yêu cầu mạng. Hãy tưởng tượng rằng bạn đang xây dựng một ứng dụng cần phải tải xuống dữ liệu từ hai nguồn khác nhau cùng một lúc để hiển thị thông tin đầy đủ và nhanh chóng cho người dùng. Điều này không chỉ giúp cải thiện hiệu suất mà còn tạo ra trải nghiệm mượt mà hơn cho người sử dụng.
public
class
MultiRequestsDemoActivity
extends
AppCompatActivity
{
private
HttpService
httpService
=
new
MockHttpService
();
/**
private
Map
<
String
,
Object
>
httpResults
=
new
HashMap
<
String
,
Object
>();
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_multi_requests_demo
);
// Đồng thời khởi động hai yêu cầu bất đồng bộ
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest1
(),
new
HttpListener
<
HttpRequest1
,
HttpResponse1
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest1
request
,
HttpResult
<
HttpResponse1
>
result
,
Object
contextData
)
{
// Đưa kết quả yêu cầu vào cache
httpResults
.
put
(
"request-1"
,
result
);
if
(
checkAllHttpResultsReady
())
{
// Cả hai yêu cầu đã kết thúc
HttpResult
<
HttpResponse1
>
result1
=
result
;
HttpResult
<
HttpResponse2
>
result2
=
(
HttpResult
<
HttpResponse2
>)
httpResults
.
get
(
"request-2"
);
if
(
checkAllHttpResultsSuccess
())
{
// Cả hai yêu cầu đều thành công
processData
(
result1
.
getResponse
(),
result2
.
getResponse
());
}
else
{
// Hai yêu cầu không hoàn tất hoàn toàn99win club, xử lý như thất bại
processError
(
result1
.
getErrorCode
(),
result2
.
getErrorCode
());
}
}
}
},
null
);
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest2
(),
new
HttpListener
<
HttpRequest2
,
HttpResponse2
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest2
request
,
HttpResult
<
HttpResponse2
>
result
,
Object
contextData
)
{
// Đưa kết quả yêu cầu vào cache
httpResults
.
put
(
"request-2"
,
result
);
if
(
checkAllHttpResultsReady
())
{
// Cả hai yêu cầu đã kết thúc
HttpResult
<
HttpResponse1
>
result1
=
(
HttpResult
<
HttpResponse1
>)
httpResults
.
get
(
"request-1"
);
HttpResult
<
HttpResponse2
>
result2
=
result
;
if
(
checkAllHttpResultsSuccess
())
{
// Cả hai yêu cầu đều thành công
processData
(
result1
.
getResponse
(),
result2
.
getResponse
());
}
else
{
// Hai yêu cầu không hoàn tất hoàn toànđá gà trực tiếp app, xử lý như thất bại
processError
(
result1
.
getErrorCode
(),
result2
.
getErrorCode
());
}
}
}
},
null
);
}
/** * Kiểm tra xem tất cả các yêu cầu đã có kết quả hay chưa * @return */
private
boolean
checkAllHttpResultsReady
()
{
int
requestsCount
=
2
;
for
(
int
i
=
1
;
i
<=
requestsCount
;
i
++)
{
if
(
httpResults
.
get
(
"request-"
+
i
)
==
null
)
{
return
false
;
}
}
return
true
;
}
/** * Kiểm tra xem tất cả các yêu cầu đã thành công hay chưa * @return */
private
boolean
checkAllHttpResultsSuccess
()
{
int
requestsCount
=
2
;
for
(
int
i
=
1
;
i
<=
requestsCount
;
i
++)
{
HttpResult
<? > result
=
(
HttpResult
<? >) httpResults
.
get
(
"request-"
+
i
);
if
(
result
==
null
||
result
.
getErrorCode
()
!=
HttpResult
.
SUCCESS
)
{
return
false
;
}
}
return
true
;
}
private
void
processData
(
HttpResponse1
data1
,
HttpResponse2
data2
)
{
// TODO: Cập nhật giao diện người dùng99win club, hiển thị kết quả yêu cầu. Code ở đây bị lược bỏ.
}
private
void
processError
(
int
errorCode1
,
int
errorCode2
)
{
// TODO: Cập nhật giao diện người dùngbxh ngoai hang anh, hiển thị lỗi. Code ở đây bị lược bỏ.
}
}
Trước tiênbxh ngoai hang anh, chúng ta cần đợi cho đến khi cả hai yêu cầu hoàn tất trước khi có thể hợp nhất kết quả của chúng. Để xác định xem cả hai yêu cầu bất đồng bộ đã hoàn thành chưa, chúng ta phải kiểm tra trạng thái hoàn tất của tất cả các yêu cầu mỗi khi một trong số chúng trả về kết quả thô Điều quan trọng cần lưu ý ở đây là chúng ta có thể áp dụng phương pháp kiểm tra này dựa trên một điều kiện quan trọng: onResult của HttpService đã được lên lịch để chạy trên luồng chính (main thread). Trong bài viết trước, " Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ Trong phần “mô hình luồng của hàm gọi lại” trong tài liệu đã phân tích kỹ lưỡng về môi trường luồng nơi các hàm gọi lại xảy ra. Giả sử rằng onResult của cả hai yêu cầu đã được lên lịch để chạy trên luồng chínhbxh ngoai hang anh, thì thứ tự thực thi của hai hàm gọi lại onResult chỉ có thể có hai kịch bản: hoặc là onResult của yêu cầu đầu tiên được thực thi trước, sau đó đến lượt onResult của yêu cầu thứ hai; hoặc ngược lại, onResult của yêu cầu thứ hai sẽ chạy trước và tiếp theo là onResult của yêu cầu đầu tiên. Dù kịch bản nào xảy ra đi chăng nữa, các dòng mã trong phần xử lý bên trong onResult vẫn luôn hoạt động chính xác và hiệu quả. Một điểm cần lưu ý thêm là khi hai yêu cầu cùng chạy trên luồng chính, sự đồng bộ giữa chúng phụ thuộc vào cách mà hệ thống quản lý lịch trình luồng. Điều này đảm bảo rằng dù thứ tự thực thi có thay đổi, logic kiểm tra bên trong onResult vẫn duy trì tính ổn định và không bị ảnh hưởng. Điều này đặc biệt quan trọng trong các ứng dụng đa luồng, nơi mà việc sắp xếp thứ tự thực thi phải được kiểm soát cẩn thận để tránh lỗi logic hoặc kết quả không mong muốn.
Tuy nhiênđá gà trực tiếp app, nếu phương thức onResult của HttpService được thực thi trên các luồng khác nhau, hai callback onResult của các yêu cầu có thể chạy xen kẽ với nhau, dẫn đến các vấn đề về đồng bộ bên trong các đoạn mã kiểm tra và xử lý. Điều này có thể gây ra sự không ổn định khi dữ liệu từ các yêu cầu khác nhau bị trộn lẫn hoặc không được xử lý theo đúng thứ tự mong muốn. Khi đó, việc quản lý trạng thái hoặc cập nhật dữ liệu trở nên phức tạp hơn, đặc biệt là khi cả hai luồng cùng truy cập vào cùng một tài nguyên hoặc đối tượng chung.
So với việc thực hiện theo thứ tự trước sau mà chúng ta đã đề cập trước đó99win club, việc thực hiện đồng thời ở đây chắc chắn sẽ mang lại độ phức tạp đáng kể. Nếu không có yêu cầu đặc biệt mạnh mẽ về việc cải thiện hiệu suất mà việc chạy song song mang lại, có lẽ chúng ta sẽ cảm thấy hài lòng hơn khi lựa chọn cách làm tuần tự, giúp logic mã nguồn dễ hiểu và dễ quản lý hơn. Việc lựa chọn giữa hai phương án này cũng giống như việc bạn quyết định đi xe đạp hay ô tô trong một chuyến đi ngắn. Nếu khoảng cách không quá xa, xe đạp vẫn là lựa chọn hợp lý vì sự đơn giản và tiện lợi của nó. Tuy nhiên, khi cần di chuyển nhanh chóng qua quãng đường dài, thì xe ô tô sẽ trở thành sự lựa chọn tối ưu, dù nó phức tạp hơn trong việc vận hành và bảo trì.
Thực thi đồng thời với ưu tiên cho một bên
Một ví dụ điển hình là bộ nhớ đệm trang. Giả sử một trang web cần hiển thị một danh sách dữ liệu động. Nếu mỗi lần người dùng mở trangbxh ngoai hang anh, hệ thống chỉ lấy dữ liệu từ máy chủ, thì trong trường hợp không có kết nối mạng hoặc mạng chậm, trang sẽ bị trống trong thời gian dài, gây khó chịu cho người dùng. Trong những tình huống như vậy, việc hiển thị dữ liệu cũ thường vẫn tốt hơn là để trang trắng hoàn toàn. Do đó, chúng ta có thể nghĩ đến việc thêm một cơ chế bộ nhớ đệm cục bộ để lưu trữ dữ liệu danh sách một cách bền vững. Điều này giúp cải thiện trải nghiệm người dùng và giảm tải đáng kể cho máy chủ khi có nhiều yêu cầu cùng lúc.
Cache cục bộ cũng là một nhiệm vụ bất đồng bộđá gà trực tiếp app, mã giao diện được định nghĩa như sau:
public
interface
LocalDataCache
{
/** * Lấy đồng bộ đối tượng HttpResponse từ bộ nhớ đệm cục bộ. * @param key Chuỗi khóa để xác định vị trí của đối tượng trong bộ nhớ đệm * @param callback Hàm trả về đối tượng được lưu trữ trong bộ nhớ đệm */
void
getCachingData
(
String
key
,
AsyncCallback
<
HttpResponse
>
callback
);
/** * Lưu đối tượng HttpResponse vào bộ nhớ cache. * @param key Khóa để xác định vị trí lưu trữ * @param data Đối tượng HttpResponse cần được lưu giữ. * @param callback Hàm trả về kết quả của hoạt động lưu trữđá gà trực tiếp app, cho biết thành công hoặc thất bại. */
void
putCachingData
(
String
key
,
HttpResponse
data
,
AsyncCallback
<
Boolean
>
callback
);
}
Dữ liệu được lưu trong bộ nhớ cache cục bộ này chính là đối tượng HttpResponse mà chúng ta đã nhận được từ máy chủ trước đó. Giao diện gọi lại bất đồng bộ AsyncCallback99win club, như đã đề cập ở phần trước, cho phép xử lý dữ liệu khi tác vụ hoàn thành. Trong thực tế, khi làm việc với các yêu cầu mạng, việc sử dụng bộ nhớ cache giúp cải thiện đáng kể hiệu suất ứng dụng. Khi dữ liệu cần thiết đã tồn tại trong bộ nhớ cache, chúng ta có thể truy xuất trực tiếp thay vì gửi thêm yêu cầu đến máy chủ, giảm tải cho hệ thống và mang lại trải nghiệm mượt mà hơn cho người dùng. AsyncCallback đóng vai trò quan trọng trong việc nhận phản hồi từ bộ nhớ cache hoặc từ kết quả của yêu cầu mạng và xử lý nó theo cách linh hoạt nhất.
Khi trang web được mở rabxh ngoai hang anh, chúng ta có thể khởi động đồng thời nhiệm vụ đọc từ bộ nhớ đệm cục bộ và nhiệm vụ gửi yêu cầu đến API từ xa. Trong đó, nhiệm vụ thứ hai sẽ có ưu tiên cao hơn so với nhiệm vụ đầu tiên. Hơn nữa, để tối ưu hóa hiệu suất, chúng ta có thể thiết lập một cơ chế điều phối thông minh giữa hai quy trình này. Ví dụ như khi bộ nhớ đệm cục bộ chưa sẵn sàng, hệ thống sẽ tự động ưu tiên xử lý yêu cầu API từ xa trước, đồng thời hiển thị thông báo cho người dùng biết rằng dữ liệu đang được tải. Điều này không chỉ đảm bảo trải nghiệm người dùng mượt mà mà còn tăng cường khả năng phản hồi của ứng dụng trong mọi tình huống.
public
class
PageCachingDemoActivity
extends
AppCompatActivity
{
private
HttpService
httpService
=
new
MockHttpService
();
private
LocalDataCache
localDataCache
=
new
MockLocalDataCache
();
/**
private
boolean
dataFromHttpReady
;
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_page_caching_demo
);
// Đồng thời khởi động yêu cầu dữ liệu cục bộ và yêu cầu HTTP từ xa
final
String
userId
=
"xxx"
;
localDataCache
.
getCachingData
(
userId
,
new
AsyncCallback
<
HttpResponse
>()
{
@Override
public
void
onResult
(
HttpResponse
data
)
{
if
(
data
!=
null
&&
!
dataFromHttpReady
)
{
// Cache có dữ liệu cũ & yêu cầu HTTP từ xa chưa trả vềbxh ngoai hang anh, hiển thị dữ liệu cũ trước
processData
(
data
);
}
}
});
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest
(),
new
HttpListener
<
HttpRequest
,
HttpResponse
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest
request
,
HttpResult
<
HttpResponse
>
result
,
Object
contextData
)
{
if
(
result
.
getErrorCode
()
==
HttpResult
.
SUCCESS
)
{
dataFromHttpReady
=
true
;
processData
(
result
.
getResponse
());
// Lấy dữ liệu mới từ HTTP99win club, cập nhật cache cục bộ
localDataCache
.
putCachingData
(
userId
,
result
.
getResponse
(),
null
);
}
else
{
processError
(
result
.
getErrorCode
());
}
}
},
null
);
}
private
void
processData
(
HttpResponse
data
)
{
// TODO: Cập nhật giao diện người dùng99win club, hiển thị dữ liệu. Code ở đây bị lược bỏ.
}
private
void
processError
(
int
errorCode
)
{
// TODO: Cập nhật giao diện người dùng99win club, hiển thị lỗi. Code ở đây bị lược bỏ.
}
}
Dù việc đọc dữ liệu từ bộ nhớ đệm cục bộ thường nhanh hơn rất nhiều so với việc lấy dữ liệu từ mạng99win club, nhưng vì cả hai đều là các giao diện bất đồng bộ, vẫn có khả năng logic rằng dữ liệu từ mạng sẽ được trả về trước khi dữ liệu từ bộ nhớ đệm thực hiệ Hơn nữa, trong bài viết trước của chúng ta có đề cập đến chủ đề “... (Đoạn này đã được chuyển sang tiếng Việt hoàn toàn và không còn bất kỳ ký tự nào không thuộc tiếng Việt.) Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Quay lại sau khi hoàn thành tác vụ bất đồng bộ callback kết quả thành công xảy ra sớm
Trong mã code phía trên99win club, nếu dữ liệu từ mạng trả về trước khi dữ liệu từ bộ nhớ cache hoàn tất việc callback, chúng ta sẽ ghi nhận một dấu hiệu dạng boolean có tên là Khi nhiệm vụ lấy dữ liệu từ bộ nhớ cache kết thúc, chúng ta sẽ kiểm tra dấu hiệu này để quyết định bỏ qua dữ liệu từ cache. Thêm vào đó, việc sử dụng một cơ chế như vậy không chỉ giúp tối ưu hóa hiệu suất mà còn đảm bảo rằng dữ liệu mới nhất từ mạng được ưu tiên sử dụng, tránh trường hợp hiển thị thông tin lỗi thời cho người dùng. Điều này đặc biệt quan trọng trong các ứng dụng yêu cầu tính thời gian thực cao, chẳng hạn như các nền tảng truyền thông xã hội hoặc các dịch vụ thời tiết trực tuyến.
thực hiện đồng thời với ưu tiên một bên
thực thi song songđá gà trực tiếp app, ưu tiên một bên
vũ khí hạng nặng
Yêu cầu mạng song song
thực hiện yêu cầu mạng song song
Chúng ta có thể coi hai yêu cầu mạng đồng thời như là hai đối tượng Observable và sử dụng hoạt động zip để kết hợp kết quả của chúng. Điều này dường như làm cho mọi thứ trở nên đơn giản hơn rất nhiều. Tuy nhiên99win club, trước tiên chúng ta cần giải quyết một vấn đề khác: gói gém giao diện yêu cầu mạng bất đồng bộ được đại diện bởi HttpService thành một đối tượ Để làm điều đó, chúng ta sẽ tạo ra một lớp hoặc hàm chuyển đổi các thao tác bất đồng bộ trong HttpService thành một luồng dữ liệ Điều này không chỉ giúp chúng ta dễ dàng quản lý mà còn tạo điều kiện thuận lợi cho việc sử dụng các hoạt động nâng cao như zip trong chuỗi xử lý sự kiện. Với cách tiếp cận này, chúng ta có thể linh hoạt hơn trong việc kiểm soát các yêu cầu mạng đồng thời và dễ dàng xử lý dữ liệu khi cả hai yêu cầu hoàn tất.
Thông thườngđá gà trực tiếp app, việc bao gói một tác vụ đồng bộ thành một Observable khá đơn giản, nhưng khi nói đến việc chuyển đổi một tác vụ bất đồng bộ đã có sẵn thành Observable thì lại không còn dễ dàng như vậy. Trong trường hợp này, chúng ta cần sử dụng AsyncOnSubscribe để xử lý vấn đề. AsyncOnSubscribe sẽ giúp chúng ta quản lý và kết nối các luồng dữ liệu bất đồng bộ một cách hiệu quả, đảm bảo rằng dữ liệu được xử lý đúng thứ tự và không bị mất mát trong quá trình thực thi.
public
class
MultiRequestsDemoActivity
extends
AppCompatActivity
{
private
HttpService
httpService
=
new
MockHttpService
();
private
TextView
apiResultDisplayTextView
;
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_multi_requests_demo
);
apiResultDisplayTextView
=
(
TextView
)
findViewById
(
R
.
id
.
api_result_display
);
/** * Trước tiên99win club, sử dụng cơ chế AsyncOnSubscribe để đóng gói hai yêu cầu thành hai Observable riêng biệt */
Observable
<
HttpResponse1
>
request1
=
Observable
.
create
(
new
AsyncOnSubscribe
<
Integer
,
HttpResponse1
>()
{
@Override
protected
Integer
generateState
()
{
return
0
;
}
@Override
protected
Integer
next
(
Integer
state
,
long
requested
,
Observer
<
Observable
<?
extends
HttpResponse1
>>
observer
)
{
final
Observable
<
HttpResponse1
>
asyncObservable
=
Observable
.
create
(
new
Observable
.
OnSubscribe
<
HttpResponse1
>()
{
@Override
public
void
call
(
final
Subscriber
<?
super
HttpResponse1
>
subscriber
)
{
// Khởi động yêu cầu bất đồng bộ đầu tiên
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest1
(),
new
HttpListener
<
HttpRequest1
,
HttpResponse1
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest1
request
,
HttpResult
<
HttpResponse1
>
result
,
Object
contextData
)
{
// Yêu cầu bất đồng bộ đầu tiên kết thúc99win club, gửi kết quả vào asyncObservable
if
(
result
.
getErrorCode
()
==
HttpResult
.
SUCCESS
)
{
subscriber
.
onNext
(
result
.
getResponse
());
subscriber
.
onCompleted
();
}
else
{
subscriber
.
onError
(
new
Exception
(
"request1 failed"
));
}
}
},
null
);
}
});
observer
.
onNext
(
asyncObservable
);
observer
.
onCompleted
();
return
1
;
}
});
Observable
<
HttpResponse2
>
request2
=
Observable
.
create
(
new
AsyncOnSubscribe
<
Integer
,
HttpResponse2
>()
{
@Override
protected
Integer
generateState
()
{
return
0
;
}
@Override
protected
Integer
next
(
Integer
state
,
long
requested
,
Observer
<
Observable
<?
extends
HttpResponse2
>>
observer
)
{
final
Observable
<
HttpResponse2
>
asyncObservable
=
Observable
.
create
(
new
Observable
.
OnSubscribe
<
HttpResponse2
>()
{
@Override
public
void
call
(
final
Subscriber
<?
super
HttpResponse2
>
subscriber
)
{
// Khởi động yêu cầu bất đồng bộ thứ hai
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest2
(),
new
HttpListener
<
HttpRequest2
,
HttpResponse2
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest2
request
,
HttpResult
<
HttpResponse2
>
result
,
Object
contextData
)
{
// Yêu cầu bất đồng bộ thứ hai kết thúc99win club, gửi kết quả vào asyncObservable
if
(
result
.
getErrorCode
()
==
HttpResult
.
SUCCESS
)
{
subscriber
.
onNext
(
result
.
getResponse
());
subscriber
.
onCompleted
();
}
else
{
subscriber
.
onError
(
new
Exception
(
"reques2 failed"
));
}
}
},
null
);
}
});
observer
.
onNext
(
asyncObservable
);
observer
.
onCompleted
();
return
1
;
}
});
// Đối với hai Observable đại diện cho các request99win club, sử dụng zip để hợp nhất kết quả của chúng.
Observable
.
zip
(
request1
,
request2
,
new
Func2
<
HttpResponse1
,
HttpResponse2
,
List
<
Object
>>()
{
@Override
public
List
<
Object
>
call
(
HttpResponse1
response1
,
HttpResponse2
response2
)
{
List
<
Object
>
responses
=
new
ArrayList
<
Object
>(
2
);
responses
.
add
(
response1
);
responses
.
add
(
response2
);
return
responses
;
}
}).
subscribe
(
new
Subscriber
<
List
<
Object
>>()
{
private
HttpResponse1
response1
;
private
HttpResponse2
response2
;
@Override
public
void
onNext
(
List
<
Object
>
responses
)
{
response1
=
(
HttpResponse1
)
responses
.
get
(
0
);
response2
=
(
HttpResponse2
)
responses
.
get
(
1
);
}
@Override
public
void
onCompleted
()
{
processData
(
response1
,
response2
);
}
@Override
public
void
onError
(
Throwable
e
)
{
processError
(
e
);
}
});
}
private
void
processData
(
HttpResponse1
data1
,
HttpResponse2
data2
)
{
// TODO: Cập nhật giao diện người dùngbxh ngoai hang anh, hiển thị dữ liệu. Code ở đây bị lược bỏ.
}
private
void
processError
(
Throwable
e
)
{
// TODO: Cập nhật giao diện người dùng99win club, hiển thị lỗi. Code ở đây bị lược bỏ.
}
chuyển đổi HttpService thành Observable
thực thi đồng thời với ưu tiên cho một bên
thực thi tuần tự liên tiếp
thực thi tuần tự nối tiếp
Hơn nữa99win club, một vấn đề không thể bỏ qua là trong nhiều trường hợp, quyền lựa chọn không nằm trong tay chúng ta. Có lẽ cấu trúc mã nguồn mà chúng ta nhận được đã tạo ra các mối quan hệ hợp tác giữa các nhiệm vụ bất đồng bộ phức tạp. Điều mà chúng ta cần làm chính là khi tình huống như vậy xảy ra, luôn giữ được sự bình tĩnh và có khả năng xác định rõ ràng tình hình hiện tại thuộc loại nào ngay cả khi phải đối mặt với dòng mã đầy rẫy logic khó hiểu. Khi đó, việc đầu tiên cần làm là cố gắng hiểu rõ toàn bộ cấu trúc của mã nguồn mà mình đang làm việc. Hãy tưởng tượng rằng bạn đang khám phá một mê cung bí ẩn, mỗi đoạn mã đều là một hành lang có thể dẫn đến những kết quả khác nhau. Việc phân tích và tìm ra điểm bắt đầu cho quá trình xử lý là vô cùng quan trọng. Một khi đã xác định được luồng chính, bạn sẽ dễ dàng hơn trong việc theo dõi và kiểm soát các nhiệm vụ bất đồng bộ, tránh bị lạc vào các nhánh phụ không cần thiết. Điều này đòi hỏi sự kiên nhẫn và kỹ năng tốt để phân tích từng bước một. Hãy tưởng tượng bạn là một thám tử, cần lần theo dấu vết để giải quyết một vụ án. Mỗi dòng mã đều chứa manh mối, và bạn phải kết nối chúng lại thành một bức tranh hoàn chỉnh. Chỉ khi nắm vững toàn cảnh, bạn mới có thể đưa ra quyết định đúng đắn về cách xử lý tiếp theo. Và điều quan trọng nhất vẫn là luôn duy trì sự tập trung cao độ, bởi chỉ cần một phút lơ là có thể khiến bạn bỏ sót những chi tiết quan trọng.
(Kết thúc)
Các bài viết được chọn lọc khác :