Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android Các kỹ thuật lập trình nâng cao trên Android
CÁC KỸ THUẬT NÂNG CAO TRÊN ANDROID Mục tiêu: Cung cấp kỹ làm việc với tệp liệu nhớ nhớ (thẻ nhớ) Sử dụng SQLite thao tác với sở liệu quan hệ Nội dung: Làm việc với tệp tin Android Có thể nói việc lưu trữ liệu liệu việc làm phổ biến Android Vì Android có nhiều cách cho phép lưu trữ liệu, với công việc khác mà lựa chọn cho phù hợp với yêu cầu Lưu trữ liệu tính quan trọng ứng dụng, giúp cho người dùng dùng lại liệu trước mà khơng cần nhập lại Việc lưu trữ liệu Android bao gồm cách phổ biến sau: Lưu file file xml bạn cần làm chức setting cho máy, hay cần ghi liệu nhỏ (sử dụng SharedPreferences) Cơ chế “cấu hình chia sẻ” (shared preferences) dùng để lưu liệu nhỏ dạng keyvalue (cặp tên khóa – giá trị khóa) Đọc/ghi file nhớ máy hay thẻ nhớ Lưu trữ database sử dụng sở liệu quan hệ cục mà Android hỗ trợ Lưu trữ liệu cố định với shared preferences Android cung cấp sẵn chế đơn giản giúp lưu trữ nhanh liệu ngắn cấu hình ứng dụng, tên đăng nhập, email,… lấy lại liệu ghi lần chạy ứng dụng Cách thức thuận tiện mô tả tập trung cấu hình ta cần lưu lại Activity (thường hình “Settings” ứng dụng) Android cung cấp sẵn loại Activity đặc biệt PreferenceActivity giúp đơn hóa q trình Preference phần quan trọng ứng dụng Android Nó quan trọng giúp cho người dùng có chọn lựa thay đổi tùy chỉnh ứng dụng họ theo nhu cầu riêng biệt cá nhân Android Prefereces thiết lập qua hai cách Bạn có thể: Tạo file preferences.xml thư mục res/xml Thiết lập preferences thơng qua code Ví dụ 7.1 minh họa cách sử dụng Activity Để tạo chức Setting Android, dụng API Preference Android cung cấp Settings danh sách gồm nhiều Preference, với Preference thiết lập Ví dụ: Text message limit, Multimedia message limit, Mỗi Preference xác định lưu trữ giá trị cặp thuộc tính (key - value); lưu trữ đối tượng SharedPreference SharedPreference lưu trữ kiểu liệu như: boolean, float, int, long, string, string set lưu trữ trong: /data/data//shared_prefs/.xml Các loại Preference gồm: CheckBoxPreference: Hiển thị item dạng checkbox Giá trị lưu trữ kiểu boolean EditTextPreference: hiển thị hộp thoại chứa EditText để người sử dụng input liệu vào từ bàn phím Dữ liệu lưu trữ kiểu String ListPreference: hiển thị danh sách gồm nhiều item kiểu radio button (single choice) Dữ liệu lưu trữ kiểu String MultiSelectListPreference: hiển thị danh sách gồm nhiều item kiểu check box (multi choice) Dữ liệu lưu trữ kiểu String Set SwitchPreference: hiển thị toggle trạng thái on/off Dữ liệu lưu trữ kiểu boolean RingtonePreference: cho phép người dùng chọn ringtone từ thiết bị Dữ liệu lưu trữ kiểu String Minh hoạ thiết lập Settings Android Các bước thực tạo Settings: Bước 1: Định nghĩa Preference XML Tạo file: filename.xml thư mục res/xml Khai báo Preference cần sử dụng Trong có thuộc tính quan trọng cần khai báo Preference: android:key - Thuộc tính bắt buộc Preference giá trị kiểu String nhất, sử dụng để xác định Preference android:title - Tiêu đề Preference android:defaultValue - Giá trị mặc định Preference Bước 2: Tạo class MyActivity extent từ class PreferenceAcitivity public class SettingsActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } } Ta sử dụng phương thức addPreferencesFromResource() để gắn preference khai báo XML vào activity Bước 3: Lấy giá trị Preference (nếu cần) Để get giá trị Preference, sử dụng đối tượng SharedPreference Khai báo đối tượng SharedPreference gọi phương thức PreferenceManager.getDefaultSharedPreferences() để gắn giá trị cho đối tượng vừa khai báo SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); Sử dụng phương thức get để lấy giá trị preference dựa vào key: String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, ""); Với arg1: key preference muốn get liệu, key phải trùng với key khai báo XML arg2: giá trị default khơng tìm thấy liệu preference Bước 4: Lắng nghe kiện Preference thay đổi giá trị implement interface: SharedPreference.OnSharedPreferenceChangeListener Đăng ký Listenerc cho đối tượng sharedPreference phương thức registerOnSharedPreferenceChangeListener() Ví dụ: public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { public static final String KEY_PREF_SYNC_CONN="pref_syncConnectionType"; public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key.equals(KEY_PREF_SYNC_CONN)) { Preference connectionPref = findPreference(key); connectionPref.setSummary(sharedPreferences.getString(key , "")); } } } Trong phương thức onSharedPreferenceChanged(), tham số SharedPreference dùng để get giá trị tham số key dùng để tìm kiếm preference cách gọi phương thức findPreference(int key) Sau người dùng thay đổi giá trị Preference, nên cập nhật lại giá trị hiển thị lên cho người dùng thấy thay đổi thuộc tính sumary Sử dụng phương thức setSummary() để thực thay đổi Google đề nghị việc đăng kí huỷ đăng kí lắng nghe kiện thay đổi giá trị preference thực phương thức onResume() onPause() @Override protected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() registerOnSharedPreferenceChangeListener(this); } @Override protected void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences() unregisterOnSharedPreferenceChangeListener(this); } Ví dụ 7.1: Sử dụng PreferenceActivity Để sử dụng PreferenceActivity, trước hết ta mô tả thông tin ta cần lưu lại tài liệu xml thư mục “res/xml”, ví dụ này, ta tạo file “res/xml/myapppreferences.xml” với nội dung sau: Sau đó, ta cần tạo Activity kế thừa từ PreferenceActivity với nội dung sau: import android.os.Bundle; import android.preference.PreferenceActivity; public class AppPreferenceActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); PreferenceManager prefMgr = getPreferenceManager(); prefMgr.setSharedPreferencesName("appPreferences"); // -load the preferences from an XML file addPreferencesFromResource(R.xml.myapppreferences); } } Chạy ứng dụng với Activity trên, ta có hình settings cho ứng dụng: Sau bạn thay đổi giá trị mục cấu hình Activity này, hệ thống tự động lưu lại giá trị chúng để sử dụng lần Việc lưu trữ liệu suốt với người dùng, nhiên với máy ảo Android ta xem cụ thể giá trị Trên thực tế chúng lưu file xml nằm nhớ trong, vùng nhớ truy cập ứng dụng tạo nó, tệp appPreferences.xml (thư mục /data/data/{package-name} /shared_prefs/appPreferences.xml): Nội dung file có dạng sau: HUMG - Software engineer HUMG - 2nd screen text content://settings/system/ringtone Để lấy giá trị cấu hình code, ta làm sau: SharedPreferences appPrefs = getSharedPreferences("appPreferences", MODE_PRIVATE); Toast.makeText(this, appPrefs.getString("editTextPref", ""), Toast.LENGTH_LONG).show(); Trong tên file cấu hình cần mở đặt hàm: prefMgr.setSharedPreferencesName("appPreferences"); cịn editTextPref tên thuộc tính cần lấy giá trị (được đặt file res/xml/myappprefererences.xml trên) Ngoài ta đặt lại giá trị cấu hình tay mà khơng cần thơng qua PreferenceActivity cách sử dụng SharedPreferences.Editor sau: SharedPreferences appPrefs = getSharedPreferences("appPreferences", MODE_PRIVATE); SharedPreferences.Editor prefsEditor = appPrefs.edit(); prefsEditor.putString("editTextPref", ((EditText) findViewById(R.id.txtString)).getText().toString()); prefsEditor.commit(); Sau thay đổi giá trị thuộc tính cần thay đổi, ta cần gọi phương thức commit() lớp Editor để thay đổi có hiệu lực (tiến hành ghi vào file xml nhớ mô tả trên) Làm việc với file liệu văn Trong trường hợp bạn cần lưu lại liệu tương đối phức tạp (khó lưu lại dạng key-value shared preference), ta dùng hệ thống file Trong Android, để làm việc (nhập/xuất) với file, ta dụng lớp gói java.io Trong phần ta xem cách làm việc với file nhớ lẫn nhớ 3.1 Làm việc với file nhớ Chúng ta thực lưu trữ file vào nhớ thiết bị, mặc định file private ứng dụng ứng dụng khác không phép truy cập vào (kể cả người dùng) Khi ứng dụng uninstall, file bị xoá Thư mục lưu trữ: /data/data/// Để tạo file ghi liệu cho file vào nhớ trong, thực bước sau: Bước 1: Tạo đối tượng FileOutputStream lấy giá trị phương thức openFileOutput(fileName, Mode) với tên file chế độ truy cập file Bước 2: Ghi liệu phương thức write() Lưu ý: phương thức write() nhận tham số đầu vào mảng byte[] cần phải chuyển liệu từ s += readString; inputBuffer = new char[READ_BLOCK_SIZE]; } // -set the EditText to the text that has been // read textBox.setText(s); Toast.makeText(getBaseContext(),"File loaded successfully!", Toast.LENGTH_SHORT).show(); } catch (IOException ioe) { ioe.printStackTrace(); } } Trong đoạn mã trên, ta thấy việc đọc ghi vào file tương đối đơn giản quen thuộc, để ghi liệu vào file, ta tạo đối tượng OutputStreamWriter luồng xuất FileOutputStream tiến hành ghi vào qua phương thức write Sau gọi flush để đẩy hết liệu đệm vào file đóng luồng lại: FileOutputStream fOut = openFileOutput("textfile.txt", MODE_PRIVATE); OutputStreamWriter osw = new OutputStreamWriter(fOut); // -write the string to the file osw.write(str); osw.flush(); osw.close(); Để đọc nội dung file này, ta thao tác tương tự, tạo đối tượng InputStreamReader từ luồng nhập liệu (FileInputStream) tiến hành đọc liệu phương thức read() Mỗi lần đọc đọc READ_BLOCK_SIZE byte lưu vào đệm (mảng byte), nội dung chuyển thành string nối thêm vào biến s, trình lặp lại hết nội dung file: FileInputStream fIn = openFileInput("textfile.txt"); InputStreamReader isr = new InputStreamReader(fIn); char[] inputBuffer = new char[READ_BLOCK_SIZE]; String s = ""; int charRead; while ((charRead = isr.read(inputBuffer))>0) { String readString = String.copyValueOf(inputBuffer, 0, charRead); s += readString; inputBuffer = new char[READ_BLOCK_SIZE]; } textBox.setText(s); Một câu hỏi đặt file "textfile.txt" tạo đâu thư mục Câu trả lời tạo nhớ thiết bị, thư mục dành riêng cho ứng dụng (/data/data/{package-name}/files) Chạy ứng dụng, nhập nội dung cho ô nhập liệu bấm Save, ta thấy nội dung văn ghi vào file nhớ Kết quả: Kéo file máy tính để xem nội dung file, ta thấy nội dung văn ta nhập vào trước đó: 3.2 Làm việc với file nhớ External Storage hiểu thẻ nhớ gắn SD card phân vùng nhớ thiết bị thiết bị không đề nghị sử dụng SD card Bộ nhớ nơi lưu trữ liệu có tính chất dùng chung như: hình ảnh, âm thanh, video Dữ liệu lưu trữ nhớ ngồi truy cập từ ứng dụng, người sử dụng trạng thái nhớ ngồi khơng khả dụng - người dùng unmount, toàn liệu lưu nhớ ngồi khơng thể truy cập Đọc ghi liệu nhớ ngoài: tương tự đọc ghi nhớ trong, lưu ý vài điều sau: Đối với nhớ trong, file lưu trữ vào thư mục chứa cài đặt ứng dụng, cần xác định tên file truy xuất nội dung file Đối với nhớ ngoài, cần xác định đường dẫn đến file để ghi đọc file Khi tạo đối tượng FileInputStream để đọc FileOutputStream để ghi, nhớ sử dụng phương thức openFileInput(fileName) phương thức openFileOutput(fileName, mode); sử dụng nhớ ngồi, cần sử dụng tốn tử new để tạo đối tượng FileInputStream đối tượng FileOutputStream File nhớ truy cập ứng dụng tạo Ngồi dung lượng lưu trữ nhớ thường hạn chế nhớ (SDCard) Vì trường hợp ta muốn chia sẻ thông tin lưu trữ với ứng dụng khác, ta nên sử dụng nhớ Làm việc với file nhớ hoàn toàn tương tự với file nhớ trong, khác phần lấy FileInputStream FileOutputStream: Thay dùng: FileOutputStream fOut = openFileOutput("textfile.txt", MODE_PRIVATE); Ta dùng: File sdCard = Environment.getExternalStorageDirectory(); File directory = new File(sdCard.getAbsolutePath() + "/MyFiles"); directory.mkdirs(); File file = new File(directory, "textfile.txt"); FileOutputStream fOut = new FileOutputStream(file); Và thay vì: FileInputStream fIn = openFileInput("textfile.txt"); Ta dùng: File sdCard = Environment.getExternalStorageDirectory(); File directory = new File (sdCard.getAbsolutePath() + "/MyFiles"); File file = new File(directory, "textfile.txt"); FileInputStream fIn = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fIn); Mọi thao tác ứng xử trường hợp trước, khác bị trí lưu trữ file thư mục: Làm việc với sở liệu SQLite 4.1 Giới thiệu SQLite database Mỗi ứng dụng sử dụng liệu, liệu đơn giản cấu trúc Trong Android hệ sở liệu sử dụng SQLite Database, hệ thống mã nguồn mở sử dụng rộng rãi ứng dụng Ví dụ Mozilla Firefox sử dụng SQLite để lưu trữ liệu cấu hình, iPhone sử dụng sở liệu SQLite, Trong Android, sở liệu mà bạn tạo cho ứng dụng ứng dụng có quyền truy cập sử dụng, ứng dụng khác khơng phép Trong phần trước ta tìm hiểu cách lưu liệu vào file vào shared preferences Tuy nhiên với loại liệu quan hệ sử dụng sở liệu quan hệ thuận tiện nhiều Ví dụ ta cần lưu trữ kết kiểm tra sinh viên trường học, dùng sở liệu cho phép truy vấn kết tập sinh viên định theo tiêu chí khác nhau, việc thêm, bớt, thay đổi thông tin thông qua câu truy vấn SQL dễ dàng nhiều so với việc thao tác file Android sử dụng hệ sở liệu SQLite CSDL ứng dụng tạo truy xuất ứng dụng đó, file CSDL nằm nhớ dành riêng cho ứng dụng (/data/data/{package-name}/databases/) SQLite thư viện thực thi chức database engine với đặc điểm giống phần mềm portable, không cần cài đặt, khơng cần cấu hình, khơng cần server, điểm khác so với việc sử dụng SQL Server Oracle, có transaction để đảm bảo tính tồn vẹn an tồn q trình thao tác liệu Có thể so sánh có vài điểm giống với Access, nhìn chung có nhiều khác biệt SQLite phù hợp tình sau: Ứng dụng sử dụng dạng flat file để lưu trữ liệu: từ điển, ứng dụng nhỏ, lưu cấu hình ứng dụng Nó mạnh mẽ nhiều kỹ thuật lưu trữ file thông thường Sử dụng thiết bị nhúng: smart phone, PDA thiết bị di động phù hợp với SQLite Các website có khoảng 100 nghìn lượt xem/ ngày, mặt lý thuyết SQLite đáp ứng gấp 10 lần conn số Là thay hoàn hảo cho database dạng file: nhiều ứng dụng sử dụng fopen(), fread(), fwite() để lưu trữ liệu, SQLite thay hoàn toàn kỹ thuật kỹ thuật đại hơn, dễ dùng tin cậy Sử dụng làm database tạm để lưu trữ liệu lấy từ database SQL server, Oracle Làm database demo cho ứng dụng lớn, dùng để làm mơ hình khái niệm cho ứng dụng Dùng cho giảng dạy: để giảng dạy cho người làm quen với ngôn ngữ truy vấn SQL Một số ưu điểm SQLite database: • Được thực thư viện thay chạy tiến trình riêng biệt • Tin cậy: hoạt động transaction (chuyển giao) nội sở liệu thưc trọn vẹn, không gây lỗi xảy cố phần cứng • Tuân theo chuẩn SQL92 (chỉ có vài đặc điểm khơng hỗ trợ) • Khơng cần cài đặt cấu hình, khơng cần phần mềm phụ trợ • Kích thước chương trình gọn nhẹ, với cấu hình đầy đủ khơng đầy 300 kB • Thực thao tác đơn giản nhanh hệ thống sở liệu khách/chủ khác • Phần mềm tự với mã nguồn mở, thích rõ ràng • Bên cạn đó, Sqlite Database tích hợp với ứng dụng tạo nó, đó: - Giảm tải phụ thuộc bên - Giảm thiểu tối đa độ trễ - Đơn giản giao dịch đồng hóa 4.2 Ví dụ sử dụng SQLite database Một thói quen tốt thường lập trình viên kinh nghiệm sử dụng tập trung tất mã lệnh truy cập đến CSDL vào lớp riêng để thao tác CSDL trở nên suốt với mơi trường ngồi Chúng ta tạo trước lớp vậy, gọi DBAdapter Tạo lớp DBAdapter Trong ví dụ ta tạo CSDL tên MyDB, chứa bảng contacts, bảng chứa trường _id, name email Lớp DBAdapter có mã nguồn sau: import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; public class DBAdapter { static final String KEY_ROWID = "_id"; static final String KEY_NAME = "name"; static final String KEY_EMAIL = "email"; static final String TAG = "DBAdapter"; static final String DATABASE_NAME = "MyDB"; static final String DATABASE_TABLE = "contacts"; static final int DATABASE_VERSION = 2; static final String DATABASE_CREATE = "create table contacts (_id integer primary key autoincrement, " + "name text not null, email text not null);"; final Context context; DatabaseHelper DBHelper; SQLiteDatabase db; public DBAdapter(Context ctx) { this.context = ctx; DBHelper = new DatabaseHelper(context); } private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { try { db.execSQL(DATABASE_CREATE); } catch (SQLException e) { e.printStackTrace(); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS contacts"); onCreate(db); } } // -opens the database public DBAdapter open() throws SQLException { db = DBHelper.getWritableDatabase(); return this; } // -closes the database public void close() { DBHelper.close(); } // -insert a contact into the database public long insertContact(String name, String email) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_NAME, name); initialValues.put(KEY_EMAIL, email); return db.insert(DATABASE_TABLE, null, initialValues); } // -deletes a particular contact public boolean deleteContact(long rowId) { return db.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null)>0; } // -retrieves all the contacts public Cursor getAllContacts() { return db.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME, KEY_EMAIL}, null, null, null, null, null); } // -retrieves a particular contact public Cursor getContact(long rowId) throws SQLException { Cursor mCursor = db.query(true, DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME, KEY_EMAIL}, KEY_ROWID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } // -updates a contact public boolean updateContact(long rowId, String name, String email) { ContentValues args = new ContentValues(); args.put(KEY_NAME, name); args.put(KEY_EMAIL, email); return db.update(DATABASE_TABLE,args,KEY_ROWID+"="+rowId,null)>0; } } Trước tiên ta khai báo số: tên CSDL, tên trường để dễ dàng truy xuất thay đổi trình phát triển Ngoài ta khai báo phiên (do ta tự đánh số) CSDL ứng dụng viết sẵn câu truy vấn dùng để tạo CSDL: static final String KEY_ROWID = "_id"; static final String KEY_NAME = "name"; static final String KEY_EMAIL = "email"; static final String TAG = "DBAdapter"; static final String DATABASE_NAME = "MyDB"; static final String DATABASE_TABLE = "contacts"; static final int DATABASE_VERSION = 2; static final String DATABASE_CREATE = "create table contacts (_id integer primary key autoincrement, " + "name text not null, email text not null);"; Ta tạo thêm lớp cục (lớp DatabaseHelper trên) để trợ giúp cho việc tạo CSDL nâng cấp cấu trúc có thay đổi phiên Lớp kế thừa từ lớp SQLiteOpenHelper Hàm dựng lớp gọi hàm dựng lớp mẹ với tên phiên CSDL ứng dụng: super(context, DATABASE_NAME, null, DATABASE_VERSION); Ngoài lớp ta nạp chồng hàm: Hàm onCreate(): gọi để khởi tạo CSDL lần chạy ứng dụng, hàm ta tiến hành thực thi câu lệnh tạo CSDL (DATABASE_CREATE) Hàm onUpgrade(): gọi ta nâng cấp ứng dụng thay đổi giá trị phiên CSDL (DATABASE_VERSION) Trong ví dụ trên, có thay đổi phiên này, ta xóa CSDL cũ tạo lại Ngồi ta viết thêm hàm để mở CSDL, tạo ghi, cập nhật ghi, lấy tất ghi, lấy ghi theo id… (xem code trên) Sau có lớp DBAdapter này, việc truy xuất CSDL trở nên tương đối đơn giản, đoạn mã minh họa thao tác thêm, bớt, truy vấn CSDL: DBAdapter db = new DBAdapter(this); // - thêm ghi db.open(); long id=db.insertContact("Wei-Meng Lee","weimenglee@learn2develop.net"); id = db.insertContact("Mary Jackson", "mary@jackson.com"); db.close(); // lấy danh sách tất ghi db.open(); Cursor c = db.getAllContacts(); if (c.moveToFirst()) { { DisplayContact(c); } while (c.moveToNext()); } db.close(); // - lấy ghi theo id db.open(); c = db.getContact(2); if (c.moveToFirst()) DisplayContact(c); else Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show(); db.close(); // - cập nhật ghi db.open(); if (db.updateContact(1, "Wei-Meng Lee", "weimenglee@gmail.com")) Toast.makeText(this, "Update successful.", Toast.LENGTH_LONG).show(); else Toast.makeText(this, "Update failed.", Toast.LENGTH_LONG).show(); db.close(); // - xóa ghi db.open(); if (db.deleteContact(1)) Toast.makeText(this,"Delete successful.",Toast.LENGTH_LONG).show(); else Toast.makeText(this, "Delete failed.", Toast.LENGTH_LONG).show(); db.close(); Trong hàm DisplayContact(c) đơn giản hiển thị lên hình nội dung ghi dạng Toast: public void DisplayContact(Cursor c) { Toast.makeText(this, "id: " + c.getString(0) + "\n" + "Name: " + c.getString(1) + "\n" + "Email: " + c.getString(2), Toast.LENGTH_LONG).show(); } Chạy ứng dụng dùng trình duyệt file Emulator, ta thấy file CSDL tạo thư mục /data/data/{package-name}/databases/myDB: Nếu ta lấy file máy tính đọc phần mềm hỗ trợ CSDL SQLite (như NaviCat) ta thấy nội dung bảng contacts bên ...