Android: Library Load ảnh Glide Hoạt động Như Thế Nào - Tynk Blog

Ngày xưa lúc chưa biết mấy cái thư viện load ảnh như Glide, Picasso, Fresco, Universal Image Loader,… mình cũng như bao các Android dev gà mờ khác phải lao tâm khổ tứ mấy cái vụ crash app do load quá nhiều ảnh dẫn đến ngốn hết bộ nhớ được cấp phát cho app, hay load ảnh từ trên internet mất tới vài giây mới hiển thị, thậm chí show ảnh ra list thì trượt lên trượt xuống lag như chơi game cấu hình cao trên máy tính cấu hình cùi vậy T.T

Android (phiên bản từ 3.1 trở lên) cũng đã hỗ trợ vấn đề này với API LruCache – 1 dạng cache với dung lượng nhất định, mỗi khi một phần tử có value được truy cập thì sẽ được đưa lên đầu của 1 queue (cache), như vậy phần tử có value ít truy cập nhất là phần tử cuối cùng của queue. Chính vì vậy, khi cache bị đầy, thì đẩy phần tử cuối cùng đó ra khỏi queue cho Garbage Collector (GC) xử lý.

Bởi vì 1 phần tử1 key tương ứng với 1 value duy nhất nên khi cần thêm 1 phần tử thì kiểm tra xem cache có chứa phần tử nào có key nào giống key của phần tử đó không, nếu không thì thêm vào cache. Ngoài ra, ta chỉ cần dựa vào key để truy cập phần tử.

Mặc dù đó là sự nỗ lực đáng tuyên dương cho Android, nhưng nhiêu đó là chưa đủ với một ứng dụng yêu cầu load ảnh nhiều, dung lượng khá lớn, chưa kể ứng dụng đó đòi hỏi về mặt UI/UX cao (load ảnh nhanh, mượt, nhẹ như lông hồng).

giphy-facebook_s

Tóm lại, 3 vấn đề mà các Android dev luôn gặp trong việc load ảnh lên UI:

  • Ngốn bộ nhớ
  • Load ảnh chậm
  • UI chạy không trơn tru, lướt màn hình khá lag

Và các library dùng để cache + load ảnh: Glide, Picasso, Fresco, UIL,…ra đời để giải quyết các vấn đề trên như các vị đấng cứu nhân hạ thế xuống ban phúc cho trần gian đầy khổ hạnh.

583073__jesus-saves-all-people_p
Nguồn: http://bezistena.blog.bg

Vài ngày trước mình có đọc vài nguồn về cách hoạt động của Glide, cá nhân mình đã sử dụng Picasso, Glide, UIL thì Glide có vẻ là thư viện thích nhất của mình, nên hôm nay viết bài để giúp mọi người biết thêm bản chất của library này, đồng thời có cái ôn lại để phòng khi quên ^^.

1. Out Of Memory (ngốn bộ nhớ/ hết vùng nhớ) – chuyện nhỏ:

Hệ điều hành Android cũng như các hệ điều hành khác, mỗi ứng dụng hay chương trình đều được cấp phát 1 vùng nhớ nhất định – nơi lưu trữ dữ liệu, tài nguyên,… sẽ được truy cập bởi ứng dụng, chương trình đó.

Giả sử 1 ứng dụng được cấp phát vùng nhớ với dung lượng 50MB, và ứng dụng đó thực hiện load 100 bức ảnh, mỗi bức ảnh có dung lượng là 1MB, vậy ta có: 100*1 =100MB > 50MB => Out Of Memory (OOM).

Để giải quyết việc này, Glide sử dụng kĩ thuật gọi là Downsampling – thay vì load chính xác kính thước của ảnh vào view chứa, Glide sẽ scale kích thước ảnh sao bằng hoặc nhỏ hơn kích thước view chứa. Ví dụ, ta có bức ảnh có kích thước 500*500 và view chứa có kích thước là 200*200, thì Glide sẽ scale kích thước ảnh xuống thành 200*200 hoặc nhỏ hơn.

Với câu lệnh sau :

GlideApp.with(context).load(url_of_image).into(imageView);

Glide cần biết kích thước của imageView để dựa vào đó mà scale kích thước của ảnh nếu cần thiết. 

Qua đó Glide giúp ứng dụng sử dụng ít bộ nhớ nhất có thể, vì vậy khả năng OOM cũng được giảm đi rất nhiều. Thật tuyệt phải không nào ???  (y)

2. Load ảnh chậm:

Việc load 1 hay nhiều bức ảnh lên giao diện của app tốn rất nhiều bởi lý do: nhiều tác vụ được thực hiện ở trong UI thread (main thread) như download byte ảnh từ Internet, decode byte thành ảnh, chưa kể ảnh có dung lượng lớn…, đẫn đến nhiều tác vụ chạy đến mức không cần thiết.

1 vấn đề lớn là ta không thể cancel các tác vụ load ảnh được. Nhưng Glide có thể làm được điều này, cancel các tác vụ không cần thiết, và chỉ load các ảnh đã sẵn sàng hiển thị cho người dùng.

Dựa vào 2 câu lệnh sau:

GlideApp.with(activity).load(url_of_image).into(imageView);;

GlideApp.with(fragment).load(url_of_image).into(imageView);;

Glide dựa vào life cycle của activity hay fragment hiện thời để xác định lúc nào tiếp tục, hủy bỏ các tác vụ load ảnh hay ảnh nào cần load hay hủy bỏ load.

Đó mới là cách thứ nhất 🙂

Cách thứ hai, Glide sử dụng 2 cache:

  • Memory Cache (Cache ở RAM)
  • Disk Cache (Cache ở ổ đĩa hay CPU)

Sử dụng cache có thể giúp ta hạn chế download, decode ảnh lần nữa, vừa tăng performance cho ứng dụng.

Cùng xem Glide sử dụng 2 cache này như thế nào nhé 🙂

  1. Glide kiểm tra xem ảnh được nó được lưu trữ ở memory cache chưa
  2. Nếu rồi thì chỉ cần load ảnh thôi
  3. Nếu chưa có, thì kiểm tra ở disk cache
  4. Nếu ảnh được lưu trữ ở disk cache, lưu trữ ảnh vào memory cache, sau đó load ảnh vào view
  5. Nếu không tồn tại ảnh trong ở cả 2 cache, download ảnh, hay load từ điện thoại, sau đó lưu trữ ảnh ở 2 cacheload vào view.

Qua cách này, ta vừa load ảnh nhanh hơn và hiệu quả hơn, vừa hạn chế download + decode ảnh không cần thiết. (y)

3. UI không reponsive, lag giật cấp thấp, màn hình động nhẹ, tầm nhìn xa đéo còn trông thấy gì

Có thể bạn không biết, cứ mỗi 16ms thì Android sẽ update UI 1 lần, và giả sử việc load ảnh trong UI thread ngốn hơn 16ms đó sẽ dẫn đến UI mất đi 1 “frame“. Càng nhiều frame bị mất đi dẫn đến việc app chay không mượt, giật lag.

Cứ tưởng tượng bạn chơi game 30fps vs 60fps đi, càng nhiều khung hình trên 1 giây, trải nghiệm game càng mịn hơn, sắc nét hơn, Đúng không nào ??

30fps_vs-_60fps_02.jpg

Chưa kể mặc dù ta đã load ảnh ở backgound nhưng đôi khi vẫn có triệu chứng app lag giật kinh niên. Why the hell ?

Đơn giản là vì theo thời gian khi nhiều ảnh được load đồng nghĩa cấp phát thêm vùng nhớ để sử dụng, và bắt buộc Garbage Collector (GC) phải làm việc để dọn dẹp vùng nhớ, xử lý các object khác không còn sử dụng nữa,…

Và đây là mấu chốt vấn đề, bởi vì: Ứng dụng Android và GC không bao giờ làm việc cùng lúc với nhau, ứng dụng chạy thì GC phải nghỉ, và ngược lại.

Khoảng cách giao phiên làm việc của 2 thanh niên này rất nhỏ dến mức ta không nhận ra, nhưng nếu GC cứ làm việc liên tục sẽ dẫn đến UI của app sẽ đơ như mặt tiền của bọn con gái ngực lép, hay giật như mấy chế bị đông kinh ^^.

Để giải quyết vấn đề trên, Glide tạo 1 Bitmap Pool.

Bitmap Pool là nơi chứa các bitmap không còn sử dụng nhưng có thể sử dụng lại để decode, load bitmap mới vào cùng vùng nhớ. Pool này hoạt động dựa trên khái niệm inBitmap .

Để dễ hiểu, mình có đoạn code sau:

Bitmap oldImage = BitmapFactory.decodeFile(pathOfOldImage); imageView.setImageBitmap(oldImage); // sử dụng thuộc tính inBitmap để sử dụng lại vùng nhớ của oldImage // cho viêc decode và load newImage BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathOfNewImage, options); options.inMutable = true; options.inBitmap = oldImage; options.inJustDecodeBounds = false; Bitmap newImage = BitmapFactory.decodeFile(pathOfNewImage, options); imageView.setImageBitmap(newImage)

Khi Glide cần load 1 bitmap mới, Glide sẽ vào trong Bitmap pool tìm một bitmap phù hợp cho việc load bitmap mới vào vùng nhớ của nó. Tất nhiên kích thước bitmap mới phải bằng hoặc bé hơn bitmap , để có thể tận dụng được vùng nhớ của bitmap .

Khi 1 bitmap hiển thị trên UI không còn sử dụng, Glide sẽ xem xét tính tái sử dụng của bitmap đó để đẩy vào Bitmap pool.

Qua cách này, ta hạn chế việc cấp phát bộ nhớ liên tục, tần suất GC phải làm việc, app chiếm được phần lớn thời gian để chạy, trải nghiệm sử dụng app tốt hơn, mượt hơn, dễ chịu hơn.

Bonus: Các thư viện khác như Picasso, Fresco đều hoạt động dựa trên những khái niệm trên.

Lời kết

Vậy là ta đã nắm được phần nào cách các thư viện Android load ảnh hoạt động như thế nào rồi nhỉ ? Mình không phải là kiểu người viết lời kết ấn tượng nên không gì để viết nhiều. Cũng khuya rồi, mình xin dừng đập bàn phím, và cày phim (phim gì thích hợp cho đêm khuya tĩnh lặng trăng sáng ngời trên trời mây nhỉ ? ^^)

Bonus phần 2: mời các bạn thưởng thức rock đêm

Nếu có ý kiến hay phản hồi đừng ngại comment nhé !!!

Chia sẻ:

  • Twitter
  • Facebook
Thích Đang tải...

Có liên quan

Từ khóa » Thư Viện Glide Android