Có rất nhiều ứng dụng yêu cầu phải hiển thị một lúc rất nhiều ảnh theo dạng lưới, scroll được và phải mượt, bất kể đang có bao nhiêu ảnh đang tải lên.

Đây là một bài toán rất thường gặp, nhưng để minh hoạ cho bài viết này, mình sẽ dùng một use case của chính 200lab là app Dofhunt:

Xem demo


Thật ra điểm mấu chốt là để app mượt chúng ta cần tối thiểu lượng animation chuẩn, memory (ram) tiêu thụ trên màn hình và quản lý tốt việc load/hiển thị ảnh.

“Animation chuẩn” là animation được render bởi GPU. Đối với các cross platform nhưng React Native thì anination sẽ mượt mà khi được render bởi tầng native. Số lượng khung hình (Frame Per Second / FPS) để người dùng trải nghiệm tốt là khoảng 52 – 60 FPS. Ở dưới mức này ứng dụng sẽ có hiện tượng lag.

Như vậy về cơ mặt cơ bản nếu hệ thống không render kịp các frame hình, thì ứng dụng sẽ lag. Số lượng frame bỏ qua càng nhiều thì ta thấy hiện tượng này càng rõ. Rất may, ở các framework hiện tại, chúng ta hầu như chỉ cần gọi các API animation mà không cần phải lập trình lại phần này. Tuy nhiên sẽ có một thứ khác ảnh hưởng trực tiếp đến animation chính là lượng memory (RAM) mà màn hình đang tiêu thụ.

Animation bao gồm tất cả các diễn hoạt trên app, kể cả các navigation như scroll, push màn hình, …

Quay lại với việc hiển thị nhiều hình ảnh và scroll không lag. Vì tất cả hình ảnh này là remote image, thiết bị phải thực hiện tải về rồi mới hiển thị lên. Kỹ thuật khá đơn giản là chúng ta cho việc load hình ở background thread và hiển thị ở main thread (bắt buộc). Cái chính ở đây là… khi nào thì cho phép hình hiện lên, animation thế nào là ổn. Rất may mình xài Flutter nên được hỗ trợ tới 80%, tuy ko can thiệp được sâu hơn nhưng mình tạm hài lòng.

Một số vấn đề cần lưu ý khi làm việc với các list, scrollview:

  1. Bản chất cái lưới hình chúng ta thấy, dù là 1000 hình thì nó chỉ đang render những thứ chúng ta đang thấy, và một phần nhỏ những thứ chúng ta sắp thấy (pre-load hay pre-render). Cơ chế recycle component này là chuẩn của native qua, nếu mấy bạn đang xài cái list mà nó đi render hết thì nên bỏ liền và ngay.
  2. Chi phí cho mỗi lần connect lên kéo hình về máy là rất lớn, từ đó sẽ phát sinh thêm 2 cái nhỏ nhỏ xinh xinh: lấy đúng size client cần và chiến lược caching.
    1. Caching thì dễ hiểu rồi vì trên app nhiều nơi xài lại ảnh chung 1 url (avatar là dễ thấy nhất). Ram trong app được phép khả dụng rất hạn chế, cache xuống disk và gọi lên khi cần là điều rất cần thiết, cơ bản là đọc từ disk lên cũng lẹ hơn.
    2. Đúng size client cần. Nghĩa là: nếu cái view đó to 100×100 points thì bản chất sẽ cần 1 tấm hình 200×200 pixels (tuỳ vào thiết bị có độ phân giải mật độ điểm ảnh bao nhiêu). Tuy nhiên mắt thường khó phân biệt được 200×200 và 180×180 nên mình ăn gian thêm “1 tí”, tiết kiệm thêm kha khá băng thông và ram cũng ít hao hơn.

Làm thế nào để có image đúng size (theo từng kích thước màn hình)?

Có 2 cách mình thường dùng:

  1. Kéo tấm full về rồi scale lại trên client, chấp nhận CPU tăng để scale hình nhưng rồi kết quả đạt được ko đổi. Tuy nhiên băng thông rất lớn, user đợi hình sẽ rất lâu, mở 4G là xác định đốt tiền.
  2. Scale trên server, băng thông giảm mạnh, client khoẻ, nhưng server gánh lại hết. Size trên mobile là vô cực trường hợp 😂.

Ở giải pháp thứ 2, thay vì link ảnh là

https://domain.com/image.jpg

Ta sử dụng:

https://domain.com/image.jpg?w=100&h=100

Trong đó w và h là kích thước ảnh mà client mong muốn để hiển thị.

Đây là giải pháp tốt khi đi với CDN hình, nó giúp ta scale ảnh và cache ở tầng proxy ngoài cùng, chưa kể nếu app global thì ảnh được phân tán khắp nơi, client gần server nào thì load ở server đó. Một số CDN các bạn có thể cân nhắc như: cloudinary.com, imgix.com.

Kết

Tóm lại việc app chúng ta có scroll mượt khi phải load rất nhiều hình hay không phụ thuộc rất lớn vào tối ưu kích thước của mỗi hình ảnh, từ đó giảm thiểu đáng kể lượng memory tiêu thụ, giúp app mượt và đỡ hao pin hơn. Hy vọng qua bài này, các bạn có thể cải tiến app / product của mình.

Các bài viết khác

Leave A Comment