posted by Trần Phước Tú on 2015-12-27 19:06

HTML5 với rất nhiều cải tiến đã mang đến cho các lập trình viên nhiều công cụ để có thể tạo ra các ứng dụng web phức tạp với nhiều tính năng mà trước đây chỉ có thể nghĩ là làm được trên nền native chứ không thể trên nền web.

 

Có nhu cầu được đặt ra là có 1 ứng dụng web có thể download các hình ảnh về máy người sử dụng để có thể display các hình ảnh khi không có kết nối mạng hoặc để tăng performance của ứng dụng vì không cần phải download image mỗi khi cần hiển thị nữa mà lấy ngay từ bộ nhớ local. Với HTML5 thì chúng ta có đủ công cụ để giải quyết.

 

Chúng ta sẽ bỏ qua giải pháp là sử dụng bộ nhớ cache của trình duyệt để lưu images, vì rõ ràng đây là giải pháp không an toàn vì bộ nhớ cache của trình duyệt có thể bị xóa bất cứ lúc nào và ta không thể control được.

 

I. HTML5 mang lại các giải pháp lưu trữ cục bộ sau:

 

1. Web Storage:

Lưu các giá trị kiểu string dạng map key-value, không hỗ trợ tìm kiếm, query...Dạng lưu trữ này chỉ phù hợp cho các data đơn giản, ví dụ các setting, vì vậy không phù hợp với yêu cầu đặt ra

 

2. Web SQL storage:

Mang lại các tính năng tương tự như 1 CSDL quan hệ, nhưng tương lai sẽ không còn được phát triển nữa. Chúng ta sẽ không muốn dùng 1 tính năng mà không còn được hỗ trợ phát triển.

 

3. FileSystem API:

Đây là giải pháp sẽ mang lại cho các ứng dụng web khả năng tạo và lưu trữ các file vật lý vào máy của user. Đáng tiếc là tính năng này không được đưa implement rộng rãi trên các browser, và cũng chưa trở thành chuẩn của HTML5. Hiện nay chỉ có trình duyệt Chrome là có support, nếu ứng dụng chỉ cần chạy tốt trên Chrome thì đây sẽ là giải pháp rất tốt.

 

4. IndexedDB:

IndexedDB sẽ thay thế hoàn toàn Web SQL trong tương lai và hiện tại được support rộng rãi trên các trình duyệt phổ biến. IndexedDB lưu trữ dữ liệu theo dạng key-value, có hỗ trợ index, query, vì vậy có thể lưu trữ các kiểu dữ liệu phức tạp và tìm kiếm rất nhanh trên IndexedDB.

 

Chúng ta sẽ chọn IndexedDB vì các tiêu chí sau:

+Lưu trữ dữ liệu cục bộ

+Hỗ trợ tìm kiếm dữ liệu đã lưu trữ

+Được support rộng rãi nên ứng dụng của chúng ta có thể hoạt động trên nhiều loại trình duyệt

 

Có thể tham khảo về cách sử dụng IndexedDB tại đây

 

II. Lưu image vào IndexedDB

 

1. Download images(tất nhiên thông qua Ajax)

Nhưng vấn đề là kiểu dữ liệu của image không phải là text mà là binary. Nếu không dùng HTML5 thì cách đơn giản nhất là chúng ta phải thay đổi api để server gửi về image dưới dạng kiểu string(base64), hoặc là trong trường hợp chúng ta không thể thay đổi api được thì phải handle bằng JS tương đối khó khăn, như ví dụ dưới đây:


 

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);

// Hack to pass bytes through unprocessed.
xhr.overrideMimeType('text/plain; charset=x-user-defined');

xhr.onreadystatechange = function (e) {
    if (this.readyState == 4 && this.status == 200) {
        var binStr = this.responseText;
        for (var i = 0, len = binStr.length; i < len; ++i) {
            var c = binStr.charCodeAt(i);
            var byte = c & 0xff; // byte at offset i
        }
    }
};

xhr.send();

 

Với HTML5 thì chúng ta có kiểu dữ liệu Blob và XMLHttpRequest 2 có thể hiểu kiểu response Blob, vì vậy bây giờ 1 image chỉ đơn giản là 1 Blob

 

2. Lưu Blob của image vào IndexedDB

Trong khi IndexedDB đã có ở hầu hết các trình duyệt, nhưng để save blob vào IndexedDB thì chỉ có 1 vài trình duyệt hỗ trợ, ví dụ Chrome hỗ trợ nhưng đáng tiếc là Safari chưa hỗ trợ. Vì vậy buộc phải chuyển kiểu Blob thành string để lưu vào IndexedDB. Việc chuyển đổi cũng đơn giản thôi:

var fr = new FileReader();
fr.onload = function (e) {
    result = e.target.result;
};
fr.readAsDataURL(blob);

 

Kết quả của đoạn mã trên là result sẽ chứa chuỗi base64 của image, chuỗi base64 này có thể gán trực tiếp vào thuộc tính src của thẻ <img> để view image. Chuỗi result này sẽ được lưu vào db

 

III. Về cách tổ chức lưu trữ dữ liệu trên IndexedDB:

 

Về database struct chúng ta cũng cần phải lưu ý, với yêu cầu đặt ra của bài viết thì thông thường chúng ta nghĩ là tạo 1 object store, ví dụ có tên là images, và các thuộc tính của image như id, title, description…, cộng thêm thuộc tính imageData để chứa chuỗi base64 của image đều được chứa trong object store đó. Nhưng cách này có 1 vấn đề đó là do 1 image thường có dữ liệu tương đối lớn(kéo theo thuộc tính imageData có kích thước cũng lớn), nên khi view tất cả image thì buộc ta phải query 1 lúc tất cả dữ liệu trong object store images ra, nên nếu số lượng image lớn thì app sẽ rất tốn bộ nhớ và performance sẽ rất tệ.

 

Vì vậy chúng ta sẽ tổ chức lại dữ liệu theo kiểu khác, đó là đưa thuộc tính imageData ra 1 object store riêng, ví dụ ta sẽ tạo 1 object store có tên là image_data, có các key là imageId chứa image id, và data chứa chuỗi base64 của image. Object store images bây giờ chứa các thuộc tính còn lại nên sẽ rất nhẹ. Khi cần view tất cả image ra thì ta vẫn sẽ query tất cả dữ liệu từ object store images, nhưng vì dữ liệu nhẹ nên không còn ảnh hưởng đến bộ nhớ và performance nữa, còn image thật sự thì ta sẽ tạo các async tasks để lấy từ store image_data và render vào image trên view sau dựa trên imageId

 

IV. Offline app:

 

Sau khi download xong image thì app đã có thể hoạt động mà không cần network, nhưng để app thật sự gọi là offline, tức là khi user refresh page thì app vẫn chạy được chứ không phải ra lỗi No network, thì chúng ta cần phải áp dụng Application Cache. Để hiểu về Application Cache thì có thể vào đây

Nói đơn giản thì Application Cache là cơ chế để chúng ta hướng dẫn trình duyệt cần phải cache những file resource nào trong application của chúng ta để khi không có network thì trình duyệt sẽ lấy các file đã cache để chạy app.

 

V. Demo app:

App demo được xây dựng trên BackboneJS theo đúng yêu cầu đã đặt ra lúc đầu. App có thể hoạt động trên hầu hết các trình duyệt hiên nay

Source code có thể download tại: https://gitlab.com/tranphuoctu/html5imageoffline

Demo có thể xem tại: http://dev.co-mit.com/html5imageoffline

Một số hình ảnh về app:

 


 

Refs:

https://developer.chrome.com/apps/offline_storage

http://mobilehtml5.org/


 


Leave a Comment

Fields with * are required.