Bài 17: Lưu trữ dữ liệu trong Android - Lập trình Android cơ bản

Đăng bởi: Admin | Lượt xem: 8833 | Chuyên mục: Android


1. Giới thiệu 

Đã có lúc nào bạn có thắc mắc rằng tại sao một ứng dụng nhỏ bé như danh bạ trong điện thoại có thể lưu trữ hàng trăm hàng nghìn số điện thoại? hay làm sao để lưu điểm cao nhất trong game mà mình đã tạo ra ?....v.v Có muôn vàn câu hỏi xung quanh vấn đề này , và hôm nay chúng ta sẽ cùng tìm lời giải cho những câu hỏi đó.
Để lưu trữ dữ liệu trong Android thì nhà phát triển đã cung cấp nhiều lựa chọn trong việc lưu trữ dữ liệu như:
  1. Internal file storage: Lưu trữ các file riêng tư của ứng dụng (app-private files) trong hệ thống file của thiết bị.
  2. External file storage: Lưu trữ file trên hệ thống file chia sẻ ra được bên ngoài. Thường dùng cho các file mà người dùng được chia sẽ, chẳng hạn như hình ảnh, video.
  3. Shared Preferences: Lưu trữ dữ liệu nguyên thủy bởi các cặp key-value.
  4. Database: Lưu trữ các dữ liệu có cấu trúc trong cơ sở dữ liệu private.
Ngoại trư một số loại file trên External Storage, thì tất cả các cách lưu trữ này dành cho dữ liệu riêng tư của ứng dụng - dữ liệu không thể truy cập tự nhiên vào các ứng dụng khác. Nếu bạn muốn chia sẻ file với các ứng dụng khác bạn nên sử dụng FileProvider API
Nếu bạn muốn chia sẻ dữ liệu cho ứng dụng khác sử dụng bạn có thể dùng Content Provider ( chúng ta sẽ tìm hiểu ở những bài sau ) . Content Provider đưa cho bạn toàn quyền kiểm soát quyền read/write có sẵn cho các ứng dụng khác, bất kể các thức bạn đã lưu trữ dữ liệu (mặc dù thường là database).
Nào bây giờ chúng ta cùng tìm hiểu từng "đứa" một nhé.

2. Internal file storage.

Android Internal storage là nơi lưu trữ dữ liệu cá nhân của từng ứng dụng, các dữ liệu được tạo ra và lưu trữ này sẽ được sự dụng riêng cho từng ứng dụng đó và các ứng dụng khác sẽ không thể truy cập vào được. Khi ứng dụng đó được gỡ bỏ khỏi thiết bị android thì các file dữ liệu được lưu tại bộ nhớ trong này sẽ bị xóa bỏ theo. Khi chúng ta làm việc với các file dữ liệu ở bộ nhớ trong thì chỉ có thể làm việc với tên file đơn giản mà không thể làm việc với tên file có đường dẫn.
Theo mặc định thì các files được lưu vào Internal Storage (bộ nhớ trong) sẽ là private đối với ứng dụng của bạn, và các ứng dụng khác sẽ không kết nối được (người dùng cũng không được trừ khi chúng có quyền truy cập root). Điều này làm cho Internal Storage là một nơi lưu trữ dữ liệu tốt mà người dùng không cần truy cập trực tiếp. Hệ thống cung cấp một private directory trong hệ thống file cho mỗi ứng dụng, nơi bạn có thể sắp xếp bất kì tệp nào mà ứng dụng của bạn cần.
Để hiểu rõ hơn thì chúng ta sẽ đi vào một ví dụ đơn giản sau :
Trước tiên bạn phải có một file xml để tạo giao diện đã :
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:textSize="25sp"
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Internal storage" />

    <EditText
        android:id="@+id/myInputText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center">
        <requestFocus />
    </EditText>

    <LinearLayout
        android:gravity="center"
        android:layout_gravity="center"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_weight="1"
            android:id="@+id/btnSave"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Lưu vào" />

        <Button
            android:layout_weight="1"
            android:id="@+id/btnDisplay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Lấy dữ liệu " />
    </LinearLayout>

    <TextView
        android:id="@+id/responseText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceMedium" />

</LinearLayout>
Hàm MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    Button btnSave, btnDisplay;
    EditText myInputText;
    TextView responseText;

    private String filename = "internalStorage.txt";
    private String filepath = "vncoder.vm";
    File myInternalFile;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        ContextWrapper contextWrapper = new ContextWrapper(
                getApplicationContext());
        File directory = contextWrapper.getDir(filepath, Context.MODE_PRIVATE);
        myInternalFile = new File(directory, filename);

    }
    private void initView() {
        myInputText = (EditText) findViewById(R.id.myInputText);
        responseText = (TextView) findViewById(R.id.responseText);
        btnSave = (Button) findViewById(R.id.btnSave);
        btnSave.setOnClickListener(this);
        btnDisplay = (Button) findViewById(R.id.btnDisplay);
        btnDisplay.setOnClickListener(this);
    }

    public void onClick(View v) {

        String myData = "";
        switch (v.getId()) {
            case R.id.btnSave:
                try {
                    FileOutputStream fos = new FileOutputStream(myInternalFile);
                    fos.write(myInputText.getText().toString().getBytes());
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                myInputText.setText("");
                responseText
                        .setText("Đã được lưu vào bộ nhớ trong");
                break;

            case R.id.btnDisplay:
                try {
                    FileInputStream fis = new FileInputStream(myInternalFile);
                    DataInputStream in = new DataInputStream(fis);
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(in));
                    String strLine;
                    while ((strLine = br.readLine()) != null) {
                        myData = myData + strLine;
                    }
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                myInputText.setText(myData);
                responseText
                        .setText("Lấy dữ liệu từ bộ nhớ trong");
                break;
        }
    }
}
Các bạn lưu ý khi sử dụng Internal storage thì điều kiện bắt buộc là phải có file chứ dữ liệu :
Còn phần đọc ghi thì tương tự như java cơ bản ở đây chi tiết ở trong code:
Kết quả :
Ghi dữ liệu.
Đọc dữ liệu.
Xem file dữ liệu trong chương trình : 
Để biết file được tạo bởi chương trình nằm ở đâu, trong Android Studio ta vào Tools/Android/Android Device Monitor. Hộp thoại hiện lên ta chọn sang thẻ File Explorer và chọn đến data/data/app_trong_thiet_bi/thu_muc_ta_tao/file_vua_tao
Để nhanh chóng hơn bạn có thể tổ hợp phím Ctrl + shift + A để mở hộp thoại rồi gõ "Device File Explorer" 

3. External file storage

External Storage là nơi lưu trữ dữ liệu ngoài của Android, các file dữ liệu lưu trữ mà bạn lưu trữ tại đây không được hệ thống áp dụng bảo mật.
Thông thường có 2 loại lưu trữ ngoài (External Storage) là lưu trữ ngoài tại ổ cứng điện thoại và lưu trữ tại ổ cứng lưu động như thẻ nhớ (SD card). Dữ liệu được tạo ra sẽ không bị ràng buộc bởi ứng dụng, khi ta xóa ứng dụng tạo ra dữ liệu tại bộ nhớ ngoài thì dữ liệu đó không bị mất đi.
Bạn nên sử dụng bộ nhớ ngoài cho dữ liệu mà có thể truy cập vào các ứng dụng khác và vẫn còn lưu khi ứng dụng của bạn bị người dùng gỡ cài đặt. Chăng hạn như những bức ảnh được chụp hoặc những file đã được download xuống trước đó. Hệ thống sẽ cung cấp các thư mục pubic chuẩn cho các loại file này, do đó người dùng có các vị trí cho photos, ringtones, music, ...
Bạn cũng có thể lưu file vào External Storage trong thư mục dành riêng cho ứng dụng của bạn mà hệ thống sẽ xóa khi người dùng gỡ cài đặt ứng dụng này trên điện thoại. Đây có thể là giải pháp thay thế cho bộ nhớ trong nếu cần thêm dung lượng, nhưng các file ở đây không đảm bảo có thể truy cập được bởi vì người dùng có thể xóa, hay tháo thẻ nhớ SD. Và các file này có thể đọc được mọi nơi, chúng chỉ sao lưu vào một nơi mà không được chia sẽ với các ứng dụng khác.
Đi ngay vào ví dụ đơn giản sau để hiểu rõ hơn : 
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width = "fill_parent"
    android:layout_height = "fill_parent"
    android:orientation = "vertical"
    android:gravity = "center">

    <TextView
        android:gravity = "center"
        android:layout_width = "fill_parent"
        android:layout_height = "wrap_content"
        android:text = "External Storage " />

    <EditText

        android:layout_gravity="center"
        android:id = "@+id/myInputText"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        android:ems = "10"
        android:gravity = "center"
        android:inputType = "textMultiLine"

        >
        <requestFocus />
    </EditText>

    <Button
        android:id="@+id/btnSave"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:text = "Lưu vào bộ nhớ ngoài" />

    <Button
        android:id = "@+id/btnDisplay"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:text = "Lấy dữ liệu từ bộ nhớ ngoài" />

    <TextView
        android:id = "@+id/responseText"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:padding = "5dp"
        android:text = ""
        android:textAppearance = "?android:attr/textAppearanceMedium" />
</LinearLayout>
MainActivity.java
public class MainActivity extends Activity implements OnClickListener {
    Button btnSave, readFromExternalStorage;
    private String filename = "vncoder.txt";
    private String filepath = "vncoder";
    TextView responseText;
    EditText myInputText;
    File myExternalFile;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
         if (!isExternalStorageAvailable() || isExternalStorageReadOnly()) {
            btnSave.setEnabled(false);
        } else {
            myExternalFile = new File(getExternalFilesDir(filepath), filename);
        }

    }

    private void initView() {
        myInputText = (EditText) findViewById(R.id.myInputText);
        responseText = (TextView) findViewById(R.id.responseText);
        btnSave = (Button) findViewById(R.id.btnSave);
        btnSave.setOnClickListener(this);
        readFromExternalStorage = (Button) findViewById(R.id.btnDisplay);
        readFromExternalStorage.setOnClickListener(this);
    }
    public void onClick(View v) {

        String myData = "";
        switch (v.getId()) {
            case R.id.btnSave:
                try {
                    FileOutputStream fos = new FileOutputStream(myExternalFile);
                    fos.write(myInputText.getText().toString().getBytes());
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                myInputText.setText("");
                responseText.setText("Dữ liệu đã được lưu vào bộ nhớ ngoài");
                break;

            case R.id.btnDisplay:
                try {
                    FileInputStream fis = new FileInputStream(myExternalFile);
                    DataInputStream in = new DataInputStream(fis);
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(in));
                    String strLine;
                    while ((strLine = br.readLine()) != null) {
                        myData = myData + strLine;
                    }
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                myInputText.setText(myData);
                responseText.setText("Được lấy ra từ bộ nhớ ngoài");
                break;
        }
    }


    private static boolean isExternalStorageReadOnly() {
        String extStorageState = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(extStorageState)) {
            return true;
        }
        return false;
    }


    private static boolean isExternalStorageAvailable() {
        String extStorageState = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(extStorageState)) {
            return true;
        }
        return false;
    }
}
Chú ý : so với  Internal storage thì External Storage ta viết hàm kiểm tra xem thiết bị có bộ nhớ ngoài hay không và kiểm tra bộ nhớ ngoài đó có read only (Không cho phép ghi dữ liệu) Hai trường hợp này đều dẫn tới việc không thể ghi dữ liệu lên đó được. Sau đó xét trường hợp thỏa mãn ghi được dữ liệu ta sẽ tạo một file có tên là vncoder.txt và một thư mục vncoder.

4. Shared Preferences

Bạn đang chơi một trò chơi trên Android, trước khi chơi trò chơi đó bạn lựa chọn các thông số của trò chơi chẳng hạn độ sáng trong trò chơi, mức độ âm lượng, và độ khó. Sau khi chơi xong bạn tắt trò chơi và có thể tiếp tục chơi vào ngày hôm sau. SharedPreferences cho phép bạn lưu lại các các thông số bạn đã thiết lập trước đó, để cho phép khi bạn chơi lại các thiết lập đó có thể sử dụng mà không cần phải thiết lập lại.
SharedPreferences lưu các dữ liệu thô dưới dạng các cặp khóa và giá trị (key-value) vào các tập tin của ứng dụng. Bạn cũng có thể chọn một chế độ lưu trữ riêng tư (PRIVATE) mà các ứng dụng khác không thể truy cập vào các tập tin này, chính vì vậy nó là an toàn.

5.Database

Android cung cấp hỗ trợ đầy đủ cho SQLite Database. Bất kì cơ sở dữ liệu mà bạn tạo chỉ có thể truy cập được bởi ứng dụng của bạn. Tuy nhiên thay vì sử dụng các SQLite API trực tiếp, thì hiện tại Android khuyến khích các Developers tên tạo và tương tác với cơ sở dữ liệu của mình với Room Databse, một component trong Android Architecture của gói Jetpack.
Room cung cấp một abstract class ánh xạ đối tượng cho phép truy cập cơ sở dữ liệu nhanh chóng, tốc độ trong khi đã phát huy khai thác toàn bộ sức mạnh của SQLite Database.
Mặc dù bạn có thể sử dụng với SQLite Database nhưng hiện thì đã xem như tốn rất nhiều thời gian và công sức để sử dụng ^^:
  1. Các truy vấn cứng SQL thì không được Compiler time checking.
  2. Khi cấu trúc cơ sở dữ liệu thay đổi thì bạn phải update một cách thủ công. Quá trình này gây tốn thời gian vã dễ bị lỗi.
  3. Bạn phải viết nhiều mã để chuyển đổi giữa các truy vấn SQL và các đối tượng dữ liệu java.
Thư viện Room sẽ giải quyết hết những vấn đề này trong khi cung cấp một lớp trừu tượng trên SQLite.
Ở Bài sau chúng ta sẽ đi tìm hiểu rõ hơn về SQLite cũng như room trong Android

6.Tổng kết.

Qua bài viết này mình đã liệt kê ra những cách lưu trữ dữ liệu mà Android cung cấp cho ứng dụng. Ngoài ra bạn cũng có thể lưu trữ dữ liệu của mình bằng cách sử dụng Network Connection. Để làm việc với internet thì các bạn có thể sử dụng các gói java.net và android.net được cung cấp.
Mong bài viết đã có một cái nhìn tổng quan cho các bạn bắt đầu tìm hiểu về Storage trong Android. Xin cám ơn rất nhiều!
Bài tiếp theo: Service trong Android >>
vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!