Chào mừng bạn đến với hướng dẫn chi tiết về cơ sở dữ liệu Android SQLite.
SQLite là phương thức lưu trữ dữ liệu phổ biến nhất trong các ứng dụng Android và là nền tảng của nhiều ứng dụng. Nó có thể được sử dụng trực tiếp hoặc thông qua một trình bao bọc của bên thứ ba. Dưới đây là ứng dụng được lập trình sử dụng cơ sở dữ liệu SQLite trong Android.
Android SQLite
Android SQLite là một cơ sở dữ liệu rất nhẹ đi kèm với hệ điều hành Android. Android SQLite sở hữu giao diện SQL rõ ràng với dung lượng bộ nhớ rất nhỏ và tốc độ khá mượt mà. Đối với Android, SQLite được “tích hợp sẵn” vào Android runtime, vì vậy mọi ứng dụng Android đều có thể tạo cơ sở dữ liệu SQLite của riêng mình. API gốc của Android SQLite không phải là JDBC vì JDBC tốn quá nhiều tài nguyên cho một điện thoại thông minh có bộ nhớ hạn chế. Khi cơ sở dữ liệu được tạo thành công, nó sẽ nằm trong thư mục data/data/<package_name>/databases/
, có thể được truy cập từ Android Device Monitor. SQLite là một cơ sở dữ liệu quan hệ điển hình, chứa các bảng (gồm các hàng và cột), chỉ mục, v.v. Chúng ta có thể tạo các bảng riêng để chứa dữ liệu một cách phù hợp. Cấu trúc này được gọi là một lược đồ cơ sở dữ liệu (schema).
Android SQLite SQLiteOpenHelper
Android có các tính năng để xử lý việc thay đổi schema cơ sở dữ liệu. Tính năng này chủ yếu phụ thuộc vào việc sử dụng lớp SQLiteOpenHelper
. SQLiteOpenHelper
để giải quyết hai vấn đề rất phổ biến:
- Khi ứng dụng chạy lần đầu tiên: Tại thời điểm này, chúng ta chưa có cơ sở dữ liệu. Vì vậy, chúng ta sẽ phải tạo các bảng, chỉ mục, dữ liệu ban đầu, v.v.
- Khi ứng dụng được nâng cấp lên một schema mới hơn: Cơ sở dữ liệu của chúng ta vẫn sẽ ở schema cũ từ phiên bản trước đó của ứng dụng. Chúng ta sẽ có tùy chọn thay đổi schema cơ sở dữ liệu để phù hợp với nhu cầu của phần còn lại của ứng dụng.
SQLiteOpenHelper
gói gọn các logic này để tạo và nâng cấp cơ sở dữ liệu theo các thông số kỹ thuật của chúng ta. Để làm được điều đó, chúng ta cần tạo một lớp con tùy chỉnh của SQLiteOpenHelper
triển khai ít nhất ba phương thức sau:
- Constructor: Hàm này nhận
Context
(ví dụ: mộtActivity
), tên của cơ sở dữ liệu, mộtcursor factory
tùy chọn (chúng ta sẽ thảo luận sau), và một số nguyên đại diện cho phiên bản của schema cơ sở dữ liệu mà bạn đang sử dụng (thường bắt đầu từ 1 và tăng lên sau này).public DatabaseHelper (Context context) { super(context, DB_NAME, null, DB_VERSION); }
onCreate(SQLiteDatabase db)
: Phương thức này được gọi khi không có cơ sở dữ liệu có sẵn và ứng dụng cần một cơ sở dữ liệu để xử lý. Nó chuyển cho chúng ta một objectSQLiteDatabase
, trỏ đến một cơ sở dữ liệu mới được tạo. Chúng ta có thể điền các bảng và dữ liệu ban đầu vào cơ sở dữ liệu mới này.onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion)
: Phương thức này được gọi khi phiên bản schema chúng ta cần không khớp với phiên bản schema của cơ sở dữ liệu. Nó chuyển đến một objectSQLiteDatabase
và các số phiên bản cũ và mới. Nhờ đó, chúng ta có thể tìm ra cách tốt nhất để chuyển đổi cơ sở dữ liệu từ schema cũ sang schema mới.
Cần định nghĩa thêm một lớp DBManager
để thực hiện tất cả các thao tác CRUD (Tạo, Đọc, Cập nhật và Xóa) cơ sở dữ liệu.
Mở và Đóng Kết nối Cơ sở dữ liệu Android SQLite
Trước khi thực hiện bất kỳ thao tác cơ sở dữ liệu nào như chèn, cập nhật, xóa bản ghi trong bảng, trước tiên hãy mở kết nối cơ sở dữ liệu bằng cách gọi phương thức getWritableDatabase()
như sau:
Java
public DBManager open() throws SQLException { dbHelper = new DatabaseHelper(context); database = dbHelper.getWritableDatabase(); return this; }
dbHelper
là một instance của lớp con SQLiteOpenHelper
. Để đóng kết nối cơ sở dữ liệu, gọi phương thức sau:
Java
public void close() { dbHelper.close(); }
Chèn bản ghi mới vào bảng cơ sở dữ liệu Android SQLite
Đoạn code sau đây hướng dẫn cách chèn một bản ghi mới vào cơ sở dữ liệu Android SQLite.
Java
public void insert(String name, String desc) { ContentValues contentValue = new ContentValues(); contentValue.put(DatabaseHelper.SUBJECT, name); contentValue.put(DatabaseHelper.DESC, desc); database.insert(DatabaseHelper.TABLE_NAME, null, contentValue); }
ContentValues
tạo một tập hợp giá trị trống. Tôi sẽ nói thêm về các giá trị instance khác khi đi vào phần viết code.
Cập nhật bản ghi trong bảng cơ sở dữ liệu Android SQLite
Đoạn code sau đây cho thấy cách cập nhật một bản ghi duy nhất.
public int update(long _id, String name, String desc) { ContentValues contentValues = new ContentValues(); contentValues.put(DatabaseHelper.SUBJECT, name); contentValues.put(DatabaseHelper.DESC, desc); int i = database.update(DatabaseHelper.TABLE_NAME, contentValues, DatabaseHelper._ID + " = " + _id, null); return i; }
Android SQLite – Xóa một bản ghi
Chúng ta chỉ cần truyền id
của bản ghi cần xóa như sau.
public void delete(long _id) { database.delete(DatabaseHelper.TABLE_NAME, DatabaseHelper._ID + "=" + _id, null); }
Android SQLite Cursor
Một Cursor
đại diện cho toàn bộ kết quả của lệnh truy vấn. Sau khi lệnh truy vấn được thực hiện, tiếp tục là lệnh gọi đến cursor.moveToFirst()
. Gọi moveToFirst()
thực hiện hai tác vụ:
- Cho phép chúng ta kiểm tra xem truy vấn có trả về một tập hợp giá trị rỗng hay không (bằng cách kiểm tra giá trị trả về).
- Di chuyển con trỏ đến kết quả đầu tiên (khi tập hợp không có giá trị rỗng).
Đoạn code sau đây được sử dụng để liệt kê tất cả các bản ghi:
public Cursor fetch() { String[] columns = new String[] { DatabaseHelper._ID, DatabaseHelper.SUBJECT, DatabaseHelper.DESC }; Cursor cursor = database.query(DatabaseHelper.TABLE_NAME, columns, null, null, null, null, null); if (cursor != null) { cursor.moveToFirst(); } return cursor; }
Một cách khác để sử dụng Cursor
là đưa nó vào trong một CursorAdapter
. Tương tự như cách ArrayAdapter
điều chỉnh mảng, CursorAdapter
điều chỉnh các object Cursor
, làm cho dữ liệu của chúng có sẵn cho một AdapterView
như ListView
. Hãy xem một ví dụ dự án thực tế sử dụng SQLite để lưu trữ những dữ liệu quan trọng.
Cấu trúc dự án Android SQLite
Trong ứng dụng này, chúng ta muốn tạo các bản ghi lưu trữ tên Quốc gia và tiền tệ tương ứng của chúng dưới dạng ListView
. Chúng ta sẽ đi vào chi tiết tất cả các tính năng đã thảo luận ở trên.
Code dự án Android SQLite
Ứng dụng bao gồm 5 lớp. Chúng ta bắt đầu với việc định nghĩa DatabaseHelper
, là một lớp con của SQLiteOpenHelper
như sau: DatabaseHelper.java
.
package com.journaldev.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
// Table Name
public static final String TABLE_NAME = "COUNTRIES";
// Table columns
public static final String _ID = "_id";
public static final String SUBJECT = "subject";
public static final String DESC = "description";
// Database Information
static final String DB_NAME = "JOURNALDEV_COUNTRIES.DB";
// database version
static final int DB_VERSION = 1;
// Creating table query
private static final String CREATE_TABLE = "create table " + TABLE_NAME + "(" + _ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + SUBJECT + " TEXT NOT NULL, " + DESC + " TEXT);";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
Như đã đề cập ở trên, tôi đã ghi đè các phương thức onCreate()
và onUpgrade()
bên cạnh hàm tạo. Chúng ta đã gán tên cho cơ sở dữ liệu và bảng lần lượt là JOURNALDEV_COUNTRIES.DB
và COUNTRIES
. Cột chỉ mục được tự động tăng lên mỗi khi một hàng mới được chèn vào. Tên cột cho quốc gia và tiền tệ là “subject” và “description”. Lớp DBManager
là nơi DatabaseHelper
được khởi tạo và các thao tác CRUD được định nghĩa. Dưới đây là đoạn code cho lớp này: DBManager.java
.
package com.journaldev.sqlite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
public class DBManager {
private DatabaseHelper dbHelper;
private Context context;
private SQLiteDatabase database;
public DBManager(Context c) {
context = c;
}
public DBManager open() throws SQLException {
dbHelper = new DatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return this;
}
public void close() {
dbHelper.close();
}
public void insert(String name, String desc) {
ContentValues contentValue = new ContentValues();
contentValue.put(DatabaseHelper.SUBJECT, name);
contentValue.put(DatabaseHelper.DESC, desc);
database.insert(DatabaseHelper.TABLE_NAME, null, contentValue);
}
public Cursor fetch() {
String[] columns = new String[] { DatabaseHelper._ID, DatabaseHelper.SUBJECT, DatabaseHelper.DESC };
Cursor cursor = database.query(DatabaseHelper.TABLE_NAME, columns, null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
}
return cursor;
}
public int update(long _id, String name, String desc) {
ContentValues contentValues = new ContentValues();
contentValues.put(DatabaseHelper.SUBJECT, name);
contentValues.put(DatabaseHelper.DESC, desc);
int i = database.update(DatabaseHelper.TABLE_NAME, contentValues, DatabaseHelper._ID + " = " + _id, null);
return i;
}
public void delete(long _id) {
database.delete(DatabaseHelper.TABLE_NAME, DatabaseHelper._ID + "=" + _id, null);
}
}
Lớp CountryListActivity.java
là activity được khởi chạy khi ứng dụng khởi động. Bố cục được định nghĩa là fragment_emp_list.xml
.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="<https://schemas.android.com/apk/res/android>"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
android:padding="10dp" >
</ListView>
<TextView
android:id="@+id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/empty_list_text" />
</RelativeLayout>
Một component ListView
được định nghĩa để chứa các bản ghi được lưu trữ trong cơ sở dữ liệu. Ban đầu, ListView
sẽ trống, do đó một TextView
được sử dụng để hiển thị điều này. CountryListActivity.java
.
package com.journaldev.sqlite;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
public class CountryListActivity extends ActionBarActivity {
private DBManager dbManager;
private ListView listView;
private SimpleCursorAdapter adapter;
final String[] from = new String[] { DatabaseHelper._ID,
DatabaseHelper.SUBJECT, DatabaseHelper.DESC };
final int[] to = new int[] { R.id.id, R.id.title, R.id.desc };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_emp_list);
dbManager = new DBManager(this);
dbManager.open();
Cursor cursor = dbManager.fetch();
listView = (ListView) findViewById(R.id.list_view);
listView.setEmptyView(findViewById(R.id.empty));
adapter = new SimpleCursorAdapter(this, R.layout.activity_view_record, cursor, from, to, 0);
adapter.notifyDataSetChanged();
listView.setAdapter(adapter);
// OnCLickListiner For List Items
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long viewId) {
TextView idTextView = (TextView) view.findViewById(R.id.id);
TextView titleTextView = (TextView) view.findViewById(R.id.title);
TextView descTextView = (TextView) view.findViewById(R.id.desc);
String id = idTextView.getText().toString();
String title = titleTextView.getText().toString();
String desc = descTextView.getText().toString();
Intent modify_intent = new Intent(getApplicationContext(), ModifyCountryActivity.class);
modify_intent.putExtra("title", title);
modify_intent.putExtra("desc", desc);
modify_intent.putExtra("id", id);
startActivity(modify_intent);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.add_record) {
Intent add_mem = new Intent(this, AddCountryActivity.class);
startActivity(add_mem);
}
return super.onOptionsItemSelected(item);
}
}
Trong activity này, object DBManager
được gọi để thực hiện các thao tác CRUD. Một SimpleCursorAdapter
được định nghĩa để thêm các phần tử vào danh sách từ kết quả truy vấn được trả về trong một object Cursor
. Khi nhấp vào một mục trong danh sách, một intent được thực hiện để mở lớp ModifyCountryActivity
. Menu chứa một mục để thêm một bản ghi mới từ ActionBar
. Tại đây, một intent lại được thực hiện để mở lớp AddCountryActivity
. Sau đây là đoạn code menu.xml
<menu xmlns:android="<https://schemas.android.com/apk/res/android>"
xmlns:app="<https://schemas.android.com/apk/res-auto>"
xmlns:tools="<https://schemas.android.com/tools>"
tools:context="com.example.sqlitesample.MainActivity" >
<item
android:id="@+id/add_record"
android:icon="@android:drawable/ic_menu_add"
android:orderInCategory="100"
android:title="@string/add_record"
app:showAsAction="always"/>
</menu>
Bố cục XML và mã của tệp AddCountryActivity.java
được định nghĩa như sau: activity_add_record.xml
.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="<https://schemas.android.com/apk/res/android>"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp" >
<EditText
android:id="@+id/subject_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/enter_title" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/description_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/enter_desc"
android:inputType="textMultiLine"
android:minLines="5" >
</EditText>
<Button
android:id="@+id/add_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/add_record" />
</LinearLayout>
Đã định nghĩa hai trường EditText
để nhập thông tin về quốc gia và tiền tệ, cùng với một nút để thêm các giá trị này vào cơ sở dữ liệu và hiển thị trên ListView
.AddCountryActivity.java
package com.journaldev.sqlite;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class AddCountryActivity extends Activity implements OnClickListener {
private Button addTodoBtn;
private EditText subjectEditText;
private EditText descEditText;
private DBManager dbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Add Record");
setContentView(R.layout.activity_add_record);
subjectEditText = (EditText) findViewById(R.id.subject_edittext);
descEditText = (EditText) findViewById(R.id.description_edittext);
addTodoBtn = (Button) findViewById(R.id.add_record);
dbManager = new DBManager(this);
dbManager.open();
addTodoBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.add_record:
final String name = subjectEditText.getText().toString();
final String desc = descEditText.getText().toString();
dbManager.insert(name, desc);
Intent main = new Intent(AddCountryActivity.this, CountryListActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(main);
break;
}
}
}
Thao tác CRUD được thực hiện ở đây là thêm một bản ghi mới vào cơ sở dữ liệu. Bố cục XML và code của tệp ModifyCountryActivity.java
được định nghĩa dưới đây: activity_modify_record.xml
.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="<https://schemas.android.com/apk/res/android>"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp" >
<EditText
android:id="@+id/subject_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:ems="10"
android:hint="@string/enter_title" />
<EditText
android:id="@+id/description_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/enter_desc"
android:inputType="textMultiLine"
android:minLines="5" >
</EditText>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="2"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_update" />
<Button
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_delete" />
</LinearLayout>
</LinearLayout>
Phần bố cục và code của ModifyCountryActivity.java
cũng tương tự như bố cục trước đó, ngoại trừ việc thêm vào các nút sửa đổi và xóa.
package com.journaldev.sqlite;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class ModifyCountryActivity extends Activity implements OnClickListener {
private EditText titleText;
private Button updateBtn, deleteBtn;
private EditText descText;
private long _id;
private DBManager dbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Modify Record");
setContentView(R.layout.activity_modify_record);
dbManager = new DBManager(this);
dbManager.open();
titleText = (EditText) findViewById(R.id.subject_edittext);
descText = (EditText) findViewById(R.id.description_edittext);
updateBtn = (Button) findViewById(R.id.btn_update);
deleteBtn = (Button) findViewById(R.id.btn_delete);
Intent intent = getIntent();
String id = intent.getStringExtra("id");
String name = intent.getStringExtra("title");
String desc = intent.getStringExtra("desc");
_id = Long.parseLong(id);
titleText.setText(name);
descText.setText(desc);
updateBtn.setOnClickListener(this);
deleteBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_update:
String title = titleText.getText().toString();
String desc = descText.getText().toString();
dbManager.update(_id, title, desc);
this.returnHome();
break;
case R.id.btn_delete:
dbManager.delete(_id);
this.returnHome();
break;
}
}
public void returnHome() {
Intent home_intent = new Intent(getApplicationContext(), CountryListActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(home_intent);
}
}
Các thao tác CRUD được thực hiện ở đây là cập nhật và xóa một bản ghi. Các hình ảnh dưới đây là ảnh chụp màn hình kết quả cuối cùng của dự án. Hình ảnh đầu tiên là giao diện khi ứng dụng được khởi chạy lần đầu tiên.
Hình ảnh thứ hai là kết quả khi nhấp vào tùy chọn menu từ ActionBar để thêm một bản ghi mới như minh họa bên dưới.
Hình ảnh thứ ba hiển thị kết quả khi 3 bản ghi được thêm vào.
Hình ảnh thứ tư hiển thị kết quả khi bất kỳ mục danh sách nào được nhấp để sửa đổi hoặc xóa một bản ghi.
Hình ảnh cuối cùng là kết quả khi một bản ghi bị xóa. Trong ví dụ này, chúng ta xóa bản ghi đầu tiên.
Mở tệp Cơ sở dữ liệu Android SQLite
Tệp cơ sở dữ liệu được lưu trữ trong bộ nhớ trong và có thể truy cập bằng cách sử dụng Android Device Monitor.
Để xem cơ sở dữ liệu này, chúng ta cần kéo tệp này từ trong ra ngoài màn hình chính (desktop). Thao tác thực hiện bằng cách nhấp vào tùy chọn menu ở trên cùng bên phải như hình minh họa sau.
Để mở tệp này, hãy tải về SQLiteBrowser. Các đoạn code dưới đây hiển thị schema và các bảng trong trình duyệt.
Để xem bảng, hãy chuyển đến tab “Browse Data” ở trên cùng. Bạn sẽ thấy giao diện như sau:
Và đến đây là kết thúc phần hướng dẫn về Android SQLite. Hy vọng hướng dẫn này đã cung cấp cho bạn cái nhìn toàn diện về cách làm việc với SQLite trong các ứng dụng Android. Giờ đây, bạn đã có kiến thức cơ bản để bắt đầu xây dựng các ứng dụng Android “xịn sò” với khả năng lưu trữ dữ liệu hiệu quả.