Hầu hết khi làm ứng dụng chúng ta đều phải lưu lại dữ liệu để dùng cho các lần làm việc sau này, chẳng hạn như lưu thông tin của người dùng, lưu trạng thái của ứng dụng… Có 3 cách lưu trữ dữ liệu đó là:
- Lưu dưới dạng từ điển trong các file ưu tiên
- Lưu file trong thẻ nhớ
- Lưu trong cơ sở dữ liệu SQLite
Trong phần này chúng ta sẽ tìm hiểu về 2 cách đầu tiên. Trong phần sau chúng ta sẽ tìm hiểu về cách lưu dữ liệu với cơ sở dữ liệu SQLite.
Lưu dưới dạng từ điển trong các file ưu tiên
Các file ưu tiên ở đây là các file nằm trong thư mục shared-refs, thư mục này nằm trong thư mục cài đặt ứng dụng trên máy của bạn.
Nếu dữ liệu bạn lưu lại không lớn lắm thì bạn nên sử dụng cách này. Ở đây chúng ta sẽ dùng lớp SharedPreferences
để lưu trữ dữ liệu, đối tượng SharedPreferences
trỏ tới một file XML chứa dữ liệu là các cặp khóa-giá trị và cung cấp các phương thức để đọc và ghi dữ liệu trên file này.
Tạo đối tượng SharedPreferences
Để tạo một đối tượng SharedPreferences
thì chúng ta dùng một trong 2 phương thức:
getSharedPreferences()
— phương thức này sẽ trả về một đối tượngSharedPreferences
trỏ tới một file do chúng ta chỉ định.getPreferences()
— mặc định ứng dụng đã có sẵn một file riêng để lưu dữ liệu, phương thức này sẽ trả về đối tượngSharedPreferences
trỏ tới file đó.
Chúng ta sẽ dùng phương thức getSharedPreferences()
khi muốn lưu dữ liệu trong nhiều file khác nhau, phương thức này nhận vào tên file và chế độ đọc. Ví dụ:
package com.phocode; import android.content.SharedPreferences; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); SharedPreferences sharedPref = getSharedPreferences("save", Context.MODE_PRIVATE); } }
Chế độ MODE_PRIVATE
tức là file này chỉ có thể mở được từ ứng dụng của chúng ta. File save.xml sẽ được tạo ra nếu chưa có.
Còn nếu chúng ta không cần lưu vào nhiều file khác nhau thì có thể dùng file mặc định của ứng dụng bằng cách dùng phương thức getPreferences():
SharedPreferences pref = getPreferences(Context.MODE_PRIVATE);
Ghi dữ liệu lên đối tượng SharedPreferences
Để ghi dữ liệu lên các file ưu tiên này thì chúng ta tạo một đối tượng SharedPreferences.Editor
từ phương thức SharedPreferences.edit().
Lớp SharedPreferences
cung cấp các phương thức ghi các kiểu dữ liệu thông thường như putInt()
để ghi một giá trị số nguyên, putFloat()
ghi số thực, putString()
ghi chuỗi...
các phương thức này nhận vào khóa kèm theo giá trị. Sau khi gọi các phương thức này chúng ta gọi phương thức commit()
thì dữ liệu mới thực sự được ghi vào file. Ví dụ:
package com.phocode; import android.content.Context; import android.content.SharedPreferences; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); SharedPreferences pref = getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putString("Username", "phocode"); editor.commit(); } }
Trong đoạn code trên chúng ta lưu một khóa có tên là Username với giá trị là phocode vào file ưu tiên có sẵn trong ứng dụng.
Đọc dữ liệu từ đối tượng SharedPreferences
Để đọc dữ liệu thì chúng ta gọi các phương thức như getInt(), getFloat(), getString()...
từ đối tượng SharedPreferences.
Các phương thức này nhận vào khóa và một giá trị mặc định, giá trị mặc định này có nghĩa là nếu khóa không tồn tại thì tạo khóa mới có giá trị là giá trị mặc định. Ví dụ:
package com.phocode; import android.content.Context; import android.content.SharedPreferences; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); SharedPreferences pref = getPreferences(Context.MODE_PRIVATE); String username = pref.getString("Username", "Default"); Toast toast = Toast.makeText(getApplicationContext(),username,Toast.LENGTH_LONG); toast.show(); } }
Trong đoạn code trên, nếu khóa Username đã tồn tại thì sẽ trả về giá trị của khóa đó, ngược lại thì tạo một khóa mới là lưu giá trị mặc định là Default. Lớp Toast
là một widget trong Android có tác dụng in một câu thông báo lên màn hình rồi tắt đi.
Lưu dữ liệu trong file của hệ điều hành
Nếu dữ liệu cần lưu trữ khá lớn, chẳng hạn như lưu ảnh… thì chúng ta nên dùng cách này. Để lưu dữ liệu lên file thì chúng ta có thể đơn giản là dùng lớp java.io.FileOutputStream
của Java là đủ rồi. Hệ thống file của Android cũng không khác gì so với hệ thống file trên các nền tảng khác như Linux, Windows…
Internal và External
Các máy Android có 2 khu vực lưu trữ file là Internal và External, hay chúng ta còn gọi là thẻ nhớ trong và thẻ nhớ ngoài. Trong đó Internal là bộ nhớ mặc định có sẵn của từng máy, còn External là thẻ nhớ ngoài thường là mua thêm rồi gắn vào, nếu không cần thì có thể gỡ ra thay cái khác. Ngoài ra còn có một số máy đặc biệt chia thẻ nhớ trong thành 2 phân vùng là Internal và External, tức là những máy này thực chất chỉ có 1 thẻ nhớ trong nhưng trong máy lúc nào cũng hiện ra 2 thẻ nhớ Internal và External và các thao tác với phân vùng External này giống hệt như với một thẻ nhớ ngoài thật.
Đây là bảng so sánh 2 loại thẻ nhớ:
INTERNAL | EXTERNAL |
Luôn có sẵn trong máy | Có thể có hoặc không vì người dùng có thể gỡ thẻ nhớ ra |
Dữ liệu của ứng dụng nào chỉ có thể truy xuất bởi ứng dụng đó | Dữ liệu lưu ở đây có thể được đọc bởi bất kì ứng dụng nào tại bất cứ thời điểm nào |
Khi ứng dụng bị xóa thì dữ liệu lưu trong này cũng bị xóa | Khi ứng dụng bị xóa thì dữ liệu sẽ bị xóa nếu được lưu trong thư mục của ứng dụng |
Theo bảng trên thì chúng ta nên sử dụng khu vực Internal nếu dữ liệu cần lưu trữ là loại dữ liệu riêng tư, không thể cho người khác biết. Còn khu vực External dành cho những dữ liệu được chia sẻ cho các ứng dụng khác hoặc với các thiết bị khác.
Mặc định ứng dụng được cài trên bộ nhớ Internal, nhưng chúng ta có thể sử dụng thuộc tính android:installLocation
để quy định ứng dụng được cài trên bộ nhớ External, việc này sẽ rất có ích cho các ứng dụng có dung lượng lớn, vì thường bộ nhớ Internal được cài mặc định vào máy có dung lượng khá thấp và không thể thay đổi, trong khi bộ nhớ External có thể tháo ra lắp vào dễ dàng vào thường bộ nhớ External có dung lượng cao hơn Internal nhiều.
Lấy quyền sử dụng bộ nhớ External
Để có thể ghi dữ liệu lên bộ nhớ External thì ứng dụng phải được cấp quyền sử dụng bộ nhớ, chúng ta lấy quyền này bằng cách khai báo trong file AndroidManifest.xml
như sau:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ... </manifest>
WRITE_EXTERNAL_STORAGE
là quyền được ghi dữ liệu lên thẻ nhớ, còn READ_EXTERNAL_STORAGE
là quyền đọc dữ liệu. Nếu chúng ta khai báo quyền ghi dữ liệu thì ứng dụng sẽ được cấp cả quyền đọc dữ liệu luôn.
Đối với bộ nhớ Internal thì ứng dụng nào cũng có thể đọc ghi được nên không cần xin quyền.
Lưu file lên bộ nhớ Internal
Mặc định trong thư mục cài đặt của mỗi ứng dụng có 2 thư mục tên là files và cache, trong đó thư mục files lưu các file như bình thường còn thư mục cache lưu các file dùng tạm, chúng ta chỉ nên lưu các dữ liệu ít quan trọng vào thư mục cache và phải xóa đi khi không còn dùng nữa, nếu không hệ điều hành sẽ tự xóa khi bộ nhớ bị đầy, các loại dữ liệu còn lại thì lưu trong thư mục files.
Chúng ta có thể sử dụng lớp java.io.FileOutputStream
của Java là có thể đọc ghi như bình thường. Ví dụ:
package com.phocode; import android.app.Activity; import android.os.Bundle; import java.io.FileOutputStream; import java.io.IOException; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String fileName = "save"; String data = "PhoCode - Open source is the future"; FileOutputStream file; try { file = openFileOutput(fileName, Context.MODE_PRIVATE); file.write(data.getBytes()); file.close(); } catch (IOException e) { e.printStackTrace(); } } }
Mặc định mỗi ứng dụng sẽ được cài đặt vào một thư mục có tên là tên package của ứng dụng. Khi chúng ta ghi file với chế độ Context.MODE_PRIVATE
thì chỉ có ứng dụng của chúng ta mới đọc được file đó, các ứng dụng khác không thể đọc được, nếu muốn ứng dụng khác có thể đọc được và ghi được thì chúng ta dùng chế độ là Context.MODE_WORLD_READABLE
và Context.MODE_WORLD_WRITEABLE,
tuy nhiên 2 chế độ này bị xóa khỏi phiên bản SDK 17.
Lưu file lên bộ nhớ External
Thẻ nhớ ngoài có thể có hoặc không có nên trước khi ghi dữ liệu chúng ta nên kiểm tra xem thẻ nhớ ngoài đang có trong máy hay không đã. Chúng ta có thể dùng phương thức Environment.getExternalStorageState()
để lấy trạng thái của thẻ nhớ, nếu trạng thái đó là Environment.MEDIA_MOUNTED
thì có thẻ nhớ trong máy. Ví dụ:
package com.phocode; import android.os.Environment; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(isExternalStorageWritable()) Toast.makeText(getApplicationContext(), "External Storage Available", Toast.LENGTH_LONG).show(); else Toast.makeText(getApplicationContext(), "External Storage Not Available", Toast.LENGTH_LONG).show(); if(isExternalStorageReadable()) Toast.makeText(getApplicationContext(), "External Storage is Read-Only", Toast.LENGTH_LONG).show(); else Toast.makeText(getApplicationContext(), "External Storage can be read and write", Toast.LENGTH_LONG).show(); } public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if(Environment.MEDIA_MOUNTED.equals(state)) return true; return false; } public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if(Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) return true; return false; } }
Ngoài ra còn có hằng số Environment.MEDIA_MOUNTED_READ_ONLY
cho biết thẻ nhớ này cho phép ghi dữ liệu hay chỉ cho phép đọc không thôi.
Mặc định thẻ nhớ ngoài sẽ chứa những thư mục như Alarm, DCIM, Pictures, Movies… và Android SDK cung cấp cho chúng ta một số hằng số trỏ đến từng thư mục này qua lớp android.os.Enviroment
như Environment.DIRECTORY_DCIM, Environment.DIRECTORY_ALARMS...
Đây là các thư mục public, tức là chúng được dùng để lưu các dữ liệu dùng chung, ai cũng có thể truy cập được. Trong số các thư mục đó có thư mục tên là Android, thư mục này dùng để chứa dữ liệu của từng ứng dụng riêng biệt, tên thư mục của từng ứng dụng là tên đầy đủ của package trong ứng dụng đó, các thư mục này cũng có thể truy xuất bởi bất kì ai, bất kì ứng dụng nào ở bất kì đâu.
Sự khác nhau giữa các thư mục như DCIM, Alarms, Pictures… với các thư mục bên trong thư mục Android là nếu ứng dụng lưu file vào các thư mục dùng chung thì khi ứng dụng bị xóa thì dữ liệu trong đó không bị xóa, còn nếu ứng dụng lưu dữ liệu trong thư mục của riêng nó (trong thư mục Android) thì khi ứng dụng bị xóa thì dữ liệu trong đó cũng bị xóa theo.
Chúng ta có thể lấy đường dẫn đến các thư mục dùng chung thông qua phương thức Activity.getExternalStoragePublicDirectory().
Phương thức này nhận vào tên thư mục dùng chung và trả về một đối tượng java.io.File.
Ví dụ:
File file = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES));
Nếu muốn lấy đường dẫn thư mục của riêng từng ứng dụng trong thư mục Android thì chúng ta gọi phương thức getExternalFilesDir(null).
Sau khi có đường dẫn thư mục, chúng ta có thể tiến hành đọc ghi file như bình thường với lớp FileOutputStream
.
Xem dung lượng còn trống
Nếu bạn biết lượng dữ liệu mà mình sẽ lưu nhưng không biết thẻ nhớ còn trống bao nhiêu, bạn có thể sử dụng 2 phương thức là getFreeSpace()
và getTotalSpace()
của lớp java.io.File
để biết được dung lượng còn trống và tổng dung lượng của bộ nhớ là bao nhiêu. Như thế sẽ tránh được lỗi IOException.
Những thông tin đó được trả về bởi hệ điều hành nhưng thường thì bạn cũng không thể dùng chính xác dung lượng đó, thường thì khi nào dung lượng còn trống hơn 10% thì hãy sử dụng.
Tất nhiên là bạn cũng không cần thiết phải kiểm tra dung lượng còn trống của ổ đĩa, thay vào đó bạn có thể cho ghi dữ liệu lên thẻ nhớ luôn và chỉ cần bắt lỗi IOException
là đủ.
Xóa file
Khi file không còn dùng được nữa bằng phương thức delete()
của lớp java.io.File
.
file.delete();
Nếu dữ liệu được lưu trên vùng Internal thì chúng ta có thể dùng phương thức deleteFile()
của lớp android.app.Activity,
phương thức này sẽ nhận vào tên file:
deleteFile(fileName);
Nhắc lại là mặc định khi ứng dụng bị xóa thì hệ điều hành sẽ xóa toàn bộ file trong khu vực Internal và toàn bộ file trong thư mục của riêng ứng dụng ở External, bạn cũng nên tự tay xóa toàn bộ mọi thứ để tránh lãng phí bộ nhớ.