Lập trình cơ sở dữ liệu trong Android (Phần 2) – Content Provider – Trần Ngọc Minh Notes

Content Provider trong Android

Ở phần 1, chúng ta đã tìm hiểu cách lưu trữ dữ liệu của một ứng dụng trong cơ sở dữ liệu SQLite. Trong trường hợp này, dữ liệu là “tài sản riêng” của ứng dụng và không thể được truy cập bởi các ứng dụng khác trong cùng một thiết bị Android.

Content Provider cung cấp cơ chế truy cập dữ liệu giữa các ứng dụng khác nhau trên cùng một thiết bị Android. Sử dụng Content Provider giống hình thức giao tiếp client/server trong đó ứng dụng truy cập dữ liệu đóng vai trò là client và Content Provider đóng vai trò là server.

Một Content Provider là một lớp con của lớp android.content.ContentProvider và việc tạo một Content Provider liên quan đến việc thực thi các phương thức sau:

Phương thức
Chức năng

onCreate()
Phương thức này được gọi khi một Content Provider được tạo lần đầu tiên và được dùng để thực thi các nhiệm vụ khởi tạo được yêu cầu bởi Content Provider.

query()
Phương thức này được gọi khi một ứng dụng (client) yêu cầu dữ liệu được nhận từ Content Provider. Dữ liệu nhận được chứa trong một đối tượng Cursor.

insert()
Phương thức này được gọi khi một hàng mới được thêm vào cơ sở dữ liệu Provider.

update()
Phương thức này được gọi khi các hàng tồn tại sẵn cần được cập nhật đại diện cho client (ứng dụng).

delete()
Phương thức này được gọi khi các hàng bị xóa từ một bảng.

getType()
Trả về kiểu MIME của dữ liệu được lưu trữ bởi Content Provider.

 Content URI

Một thiết bị Android có thể chứa nhiều Content Providers, do đó, hệ thống phải cung cấp giải pháp để phân biệt các Content Providers. URI là một giải pháp cho phép xác định dữ liệu cụ thể với một Content Provider cụ thể.

Một URI có thể chia thành hai phần: phần xác định Content Provider (hay còn gọi là phần Authority) có hình thức giống một gói (package) trong ứng dụng Android, ví dụ:  com.example.mydbapp.myprovider; phần thứ hai xác định dữ liệu của Content Provider, ví dụ tham chiếu đến một bảng tên student trong Content Provider: com.example.mydbapp.myprovider/student hay xác định dữ liệu chi tiết hơn, ví dụ tham chiếu đến một hàng có giá trị ID là 3 trong bảng student: com.example.mydbapp.myprovider/student/3.

Có những URI có thể mở rộng đến nhiều cấp và lúc này chúng ta có thể dùng lớp UriMatcher.

Đối tượng ContentResolver

Truy cập đến một Content Provider đạt được nhờ đối tượng ContentResolver. Một ứng dụng có thể đạt được tham chiếu đến đối tượng ContentResolver bằng cách gọi phương thức getContentResolver(). Đối  tượng ContentResolver chứa các phương thức tương tự như của một Content Provider như insert(), query(), update(), v.v.

Phần tử <provider>

Một Content Provider, để được sử dụng trong hệ thống Android, phải được khai báo trong tập tin manifest với phần tử <provider>. Phần tử <provider> chứa hai thuộc tính quan trọng là:

  • android:authority: là URI với phần xác định nội dung của Content Provider
  • android:name: chứa tên lớp thực thi Content Provider

Dự án minh họa dùng Content Provider

Tạo dự án

– Tìm đến và sao chép thư mục ứng dụng SQLiteDemoApplication và đổi tên (mặc định là SQLiteDemoApplication-Copy) thành ContentProviderApplication.

– Đóng dự án hiện tại đang mở trên Android Studio bằng cách chọn File > Close Project. Trong cửa sổ Welcome to Android Studio chọn Open an existing Android Studio project:

– Trong Open File or Project tìm đến dự án ContentProviderApplication và nhấn OK

Thêm gói Content Provider

Thêm gói mới bằng cách vào app > java và nhấn chuột phải vào thư mục java chọn New > Package

– Trong hộp thoại Choose Destination Directory chọn …\app\source\main\java và nhấn OK

– Trong cửa sổ New Package, nhập tên gói là com.ngocminhtran.database.provider và OK

– Lúc này trong cửa sổ Project

Tạo các lớp Content Provider

– Sau khi tạo gói com.ngocminhtran.database.provider, chúng ta sẽ thêm các lớp đến gói này bằng cách chọn và nhấn chuột phải vào tên gói (com.ngocminhtran.database.provider) chọn New > Other > Content Provider

– Trong hộp thoại Configure Component chọn chấp nhận tên mặc định của lớp là MyContentProvider và nhập com.ngocminhtran.database.provider.MyContentProvider trong URI Authorities:

Giữ nguyên các tùy chọn khác và nhấn Finish. Lúc này, lớp MyContentProvider.java sẽ xuất hiện trong gói com.ngocminhtran.database.provider

Và chúng ta có thể xem nội dung mặc định của tập tin này bằng cách nhấn đôi chuột trái

Tạo Authority URI và Content URI

Trong tập tin MyContentProvider.java thêm các đoạn mã sau:


package com.ngocminhtran.database.provider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.content.UriMatcher;

public class MyContentProvider extends ContentProvider {
    
   private static final String AUTHORITY
       ="com.ngocminhtran.database.provider.MyContentProvider";
   private static final String STUDENTS_TABLE = "Students";
   public static final Uri CONTENT_URI =
       Uri.parse("content://" + AUTHORITY + "/" + STUDENTS_TABLE);
   public MyContentProvider() {
   }
...

Chúng ta tạo chuỗi tên AUTHORITY chứa thông tin về Authority URI, chuỗi tên STUDENTS_TABLE chứa thông tin về bảng cơ sở dữ liệu (Students) và chuỗi tên CONTENT_URI là kết hợp của hai chuỗi AUTHORITY và STUDENTS_TABLE bắt đầu bằng chuỗi content:// dùng phương thức parse của lớp Uri.

Đối tượng UriMatcher

URI là một giải pháp cho phép xác định dữ liệu, có thể là một hàng hay một bảng cơ sở dữ liệu, với một Content Provider cụ thể. Chúng ta để ý các phương thức trong lớp MyContentProvider như insert(), delete(), getType(), v.v. đều nhận một URI làm tham số. URI này như một tham chiếu đến một bảng cơ sở dữ liệu hay một hàng cụ thể của bảng đó và trách nhiệm chúng ta là chuyển kiểu Uri phù hợp đến các phương thức. Nhiệm vụ này có thể thực hiện một cách dễ dàng nhờ đối tượng UriMatcher. Khi một thể hiện của UriMatcher được tạo, nó sẽ được cấu hình để trả về một số nguyên tương ứng với kiểu Uri mà nó được yêu cầu. Trong ứng dụng hiện tại, chúng ta sẽ cấu hình thể hiện UriMatcher trả về giá trị 1 nếu tham chiếu đến toàn bộ bảng cơ sở dữ liệu (ví dụ Students) và trả về giá trị 2 nếu tham chiếu đến ID của một hàng nào đó. Thêm hai biến số nguyên tương ứng với hai kiểu Uri vào lớp MyContentProvider như sau:


public class MyContentProvider extends ContentProvider {

   private static final String AUTHORITY
       ="com.ngocminhtran.database.provider.MyContentProvider";
   private static final String STUDENTS_TABLE = "Students";
   public static final Uri CONTENT_URI =
      Uri.parse("content://" + AUTHORITY + "/" + STUDENTS_TABLE);
   public static final int STUDENTS = 1;
   public static final int STUDENTS_ID = 2;
   public MyContentProvider() {
   }
...

Kế tiếp, tạo thể hiện UriMatcher và cấu hình nó trả về các giá trị số nguyên phù hợp:


public class MyContentProvider extends ContentProvider {

   private static final String AUTHORITY
      ="com.ngocminhtran.database.provider.MyContentProvider";
   private static final String STUDENTS_TABLE = "Students";
   public static final Uri CONTENT_URI =
       Uri.parse("content://" + AUTHORITY + "/" + STUDENTS_TABLE);
   public static final int STUDENTS = 1;
   public static final int STUDENTS_ID = 2;
   private static final UriMatcher sURIMatcher =
        new UriMatcher(UriMatcher.NO_MATCH);
   static {
       sURIMatcher.addURI(AUTHORITY, STUDENTS_TABLE, STUDENTS);
       sURIMatcher.addURI(AUTHORITY, STUDENTS_TABLE + "/#",STUDENTS_ID);
   }
   public MyContentProvider() {
   }

Thực thi phương thức onCreate()

Phương thức onCreate() sẽ được gọi khi lớp Content Provider được tạo. Mục đích gọi onCreate() trong ứng dụng minh họa này là tạo một thể hiện của lớp DataHandler (một bản sao của lớp DataHandler từ ứng dụng trong Phần 1). Để tạo thể hiện DataHandler và sử dụng các phương thức trong lớp này, chúng ta cần khai báo lớp DataHandler trong tập tin MyContentProvider.java và vì lớp DataHandler được chứa trong gói com.ngocminhtran.sqlitedemoapplication

nên chúng ta khai báo như sau:


package com.ngocminhtran.database.provider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.content.UriMatcher;

import com.ngocminhtran.sqlitedemoapplication.DataHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.text.TextUtils;

public class MyContentProvider extends ContentProvider {

   ...

}

Khởi tạo thể hiện lớp DataHandler trong phương thức onCreate() như sau:


public class MyContentProvider extends ContentProvider {

  ...

   private DataHandler myHandler;

   public MyContentProvider() {
   }

   @Override
   public boolean onCreate() {
      // TODO: Implement this to initialize your content provider on startup.
      myHandler = new DataHandler(getContext(), null, null, 1);
      return false;
   }
...
}

Thực thi phương thức insert()

Khi một ứng dụng khách hay một activity yêu cầu thêm dữ liệu đến cơ sở dữ liệu, phương thức insert() của lớp Content Provider sẽ được gọi. Phương thức insert() mặc định trong tập tin MyContentProvider.java như sau:


@Override
public Uri insert(Uri uri, ContentValues values) {
  // TODO: Implement this to handle requests to insert a new row.
  throw new UnsupportedOperationException("Not yet implemented");
}

Tham số của phương thức insert() là một Uri xác định cơ sở dữ liệu và một đối tượng ContentValues chứa giá trị cần thêm vào cơ sở dữ liệu đó. Bây giờ chúng ta sẽ thay đổi phương thức insert() thực hiện một số chức năng sau:

  • Dùng một đối tượng UriMatcher để xác định kiểu Uri. Trong minh họa của chúng ta là đối tượng sUriMatcher.
  • Phát sinh một ngoại lệ nếu Uri không hợp lệ.
  • Xác định một tham chiếu đến cơ sở dữ liệu cần thêm dữ liệu.
  • Thực thi lệnh truy vấn SQL để thêm dữ liệu đến cơ sở dữ liệu.
  • Thông báo khi cơ sở dữ liệu được thay đổi.
  • Trả về Uri của hàng vừa thêm.

Phương thức insert() lúc này sẽ trông như sau:


@Override
public Uri insert(Uri uri, ContentValues values) {
   int uriType = sURIMatcher.match(uri);
   SQLiteDatabase sqlDB = myHandler.getWritableDatabase();
   long id = 0;
   switch (uriType) {
      case STUDENTS:
         id = sqlDB.insert(DataHandler.TABLE_NAME,null, values);
         break;
      default:
         throw new IllegalArgumentException("Unknown URI: "+ uri);
    }
   getContext().getContentResolver().notifyChange(uri, null);
   return Uri.parse(STUDENTS_TABLE + "/" + id);
}

Thực thi phương thức query()

Khi một content provider được gọi để trả về dữ liệu, phương thức query() của lớp Content Provider sẽ được gọi. Phương thức query() mặc định của tập tin MyContentProvider.java như sau:


@Override
public Cursor query(Uri uri, String[] projection, String selection,
   String[] selectionArgs, String sortOrder) {
     // TODO: Implement this to handle query requests from clients.
     throw new UnsupportedOperationException("Not yet implemented");
}

Khi được gọi, phương thức này sẽ chuyển một vài hay toàn bộ các đối số sau:

  • uri: xác định cơ sở dữ liệu cần truy vấn
  • projection: một hàng trong một bảng dữ liệu
  • selection: lệnh “where” được thực thi
  • selectionArgs: các tham số bổ sung cho thao tác truy vấn chọn
  • sortOrder: thứ tự cho các hàng được chọn

Phương thức query() sẽ thực hiện một số chức năng sau:

  • Dùng một đối tượng UriMatcher để xác định kiểu Uri. Trong minh họa của chúng ta là đối tượng sUriMatcher.
  • Phát sinh một ngoại lệ nếu Uri không hợp lệ.
  • Tạo một truy vấn SQL dựa trên các tham số của phương thức.
  • Thực thi lệnh truy vấn SQL đến cơ sở dữ liệu.
  • Thông báo khi cơ sở dữ liệu được thay đổi.
  • Trả về đối tượng Cursor chứa kết quả truy vấn.

Phương thức query() được thay đổi như sau:


@Override
public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {

    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(DataHandler.TABLE_NAME);
    int uriType = sURIMatcher.match(uri);
    switch (uriType) {
       case STUDENTS_ID:
           queryBuilder.appendWhere(DataHandler.COLUMN_ID + "="
                + uri.getLastPathSegment());
           break;
       case STUDENTS:
           break;
       default:
           throw new IllegalArgumentException("Unknown URI");
     }
    Cursor cursor = queryBuilder.query(myHandler.getReadableDatabase(),projection,
        selection, selectionArgs, null, null, sortOrder);
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    return cursor;
}

Thực thi phương thức update()

Khi một content provider được gọi để thay đổi đến các hàng dữ liệu tồn tại trong bảng của sơ sở dữ liệu, phương thức update() của lớp Content Provider sẽ được gọi. Phương thức update() mặc định của tập tin MyContentProvider.java như sau:


@Override
public int update(Uri uri, ContentValues values, String selection,
   String[] selectionArgs) {
    // TODO: Implement this to handle requests to update one or more rows.
    throw new UnsupportedOperationException("Not yet implemented");
}

Tham số của phương thức update() (tương tự phương thức query()):

  • uri: xác định cơ sở dữ liệu cần truy vấn
  • projection: một hàng trong một bảng dữ liệu
  • selection: lệnh “where” được thực thi
  • selectionArgs: các tham số bổ sung cho thao tác truy vấn chọn
  • sortOrder: thứ tự cho các hàng được chọn

Phương thức update() sẽ thực hiện một số chức năng sau:

  • Dùng một đối tượng UriMatcher để xác định kiểu Uri. Trong minh họa của chúng ta là đối tượng sUriMatcher.
  • Phát sinh một ngoại lệ nếu Uri không hợp lệ.
  • Xác định một tham chiếu đến cơ sở dữ liệu cần thay đổi dữ liệu.
  • Thực thi lệnh truy vấn SQL để cập nhật dữ liệu đến cơ sở dữ liệu.
  • Thông báo khi cơ sở dữ liệu được thay đổi.
  • Trả về số hàng đã được cập nhật.

Phương thức update() được thay đổi như sau:


@Override
public int update(Uri uri, ContentValues values, String selection,
    String[] selectionArgs) {

     int uriType = sURIMatcher.match(uri);
     SQLiteDatabase sqlDB = myHandler.getWritableDatabase();
     int rowsUpdated = 0;
     switch (uriType) {
        case STUDENTS:
           rowsUpdated = sqlDB.update(DataHandler.TABLE_NAME,values,
                    selection, selectionArgs);
           break;
        case STUDENTS_ID:
           String id = uri.getLastPathSegment();
           if (TextUtils.isEmpty(selection)) {
              rowsUpdated = sqlDB.update(DataHandler.TABLE_NAME,
                values, DataHandler.COLUMN_ID + "=" + id, null);
            } else {
              rowsUpdated = sqlDB.update(DataHandler.TABLE_NAME,
                values, DataHandler.COLUMN_ID + "=" + id + " and "
                + selection, selectionArgs);
            }
            break;
         default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
       }
     getContext().getContentResolver().notifyChange(uri, null);
     return rowsUpdated;
}

Thực thi phương thức delete()

Phương thức delete() được gọi khi cần xóa dữ liệu từ một cơ sở dữ liệu. Mặc định, phương thức delete() trong tập tin MyContentProvider.java sẽ như sau:


@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
   // Implement this to handle requests to delete one or more rows.
   throw new UnsupportedOperationException("Not yet implemented");
}

Các đối số uri, selection, và selectionArgs có ý nghĩa tương tự hai phương thức query() và update(). Phương thức delete() thực hiện một số nhiệm vụ sau:

  • Dùng một đối tượng UriMatcher để xác định kiểu Uri. Trong minh họa của chúng ta là đối tượng sUriMatcher.
  • Phát sinh một ngoại lệ nếu Uri không hợp lệ.
  • Xác định một tham chiếu đến cơ sở dữ liệu cần xóa dữ liệu.
  • Thực thi lệnh truy vấn SQL để xóa dữ liệu trong cơ sở dữ liệu.
  • Thông báo khi cơ sở dữ liệu được thay đổi.
  • Trả về số hàng đã được cập nhật.

Phương thức delete() sẽ thay đổi như sau:


@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
  
   int uriType = sURIMatcher.match(uri);
   SQLiteDatabase sqlDB = myHandler.getWritableDatabase();
   int rowsDeleted = 0;
   switch (uriType) {
      case STUDENTS:
         rowsDeleted = sqlDB.delete(DataHandler.TABLE_NAME, selection,
                selectionArgs);
         break;
      case STUDENTS_ID:
         String id = uri.getLastPathSegment();
         if (TextUtils.isEmpty(selection)) {
            rowsDeleted = sqlDB.delete(DataHandler.TABLE_NAME,
               myHandler.COLUMN_ID + "=" + id,null);
          } else {
            rowsDeleted = sqlDB.delete(DataHandler.TABLE_NAME,
                myHandler.COLUMN_ID + "=" + id + " and " + selection,
                selectionArgs);
           }
         break;
      default:
         throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null);
    return rowsDeleted;
}

Khai báo Content Provider trong tập tin Manifest

Content Provider muốn được dùng trong một ứng dụng, nó cần phải được khai báo trong tập tin Manifest của ứng dụng đó. Khai báo Content Provider trong ứng dụng của chúng ta trong tập tin Manifest bằng cách vào app > manifests > AndroidManifest.xml và khai báo:


<application ...>
   <provider
     android:name="com.ngocminhtran.database.provider.MyContentProvider"
     android:authorities="com.ngocminhtran.database.provider.MyContentProvider"
     android:enabled="true"
     android:exported="true">
   </provider>

   <activity>
    ...
   </activity>
</application>

Thay đổi lớp DataHandler

Khi một content provider được thực thi, chúng ta cần thay đổi các phương thức trong lớp DataHandler (tập tin DataHandler.java) để có thể dùng content provider thông qua đối tượng content resolver. Đầu tiên, chúng ta cần import các gói chứa lớp Content Provider và ContentResolver như sau:


import com.ngocminhtran.database.provider.MyContentProvider;
import android.content.ContentResolver;

Kế tiếp, chúng ta cần tham chiếu đến đối tượng ContentResolver dùng phương thức getContentResolver() trong phương thức khởi tạo của lớp DataHandler:


public class DataHandler extends SQLiteOpenHelper {

  private ContentResolver myCR;
  // các biến mô tả cơ sở dữ liệu
  private static final int DATABASE_VERSION = 1;
  private static final String DATABASE_NAME = "StudentsDB.db";
  public static final String TABLE_NAME = "Students";
  public static final String COLUMN_ID = "StudentID";
  public static final String COLUMN_NAME = "StudentName";

  //phương thức khởi tạo
  public DataHandler(Context context, String name,
      SQLiteDatabase.CursorFactory factory, int version) {
       super(context, DATABASE_NAME, factory, DATABASE_VERSION);
       myCR = context.getContentResolver();
  }
…

Bây giờ, chúng ta sẽ thay đổi các phương thức addDataHandler, findFisrtDataHandler, findAllDataHandler, deleteDataHandler và updateDataHandler


//thêm dữ liệu đến bảng Students
public void addDataHandler(Student student) {
  //tạo đối tượng ContentValues
  ContentValues values = new ContentValues();
  //thêm giá trị các cột đến đối tượng ContentValues
  values.put(COLUMN_ID, student.getStudentID());
  values.put(COLUMN_NAME, student.getStudentName());
  myCR.insert(MyContentProvider.CONTENT_URI, values);
}
//tìm kiếm Student theo StudentName
//kết quả trả về là Student đầu tiên trong danh sách kết quả
public Student findFisrtDataHandler(String studentname) {

  String[] projection = {COLUMN_ID, COLUMN_NAME };
  String selection = "studentname = \"" + studentname +"\"";
  Cursor cursor = myCR.query(MyContentProvider.CONTENT_URI,
     projection, selection, null,null);
  Student student = new Student();
  //trả về hàng đầu tiên trong kết quả
  if (cursor.moveToFirst()) {
     cursor.moveToFirst();
     student.setStudentID(Integer.parseInt(cursor.getString(0)));
     student.setStudentName(cursor.getString(1));
     cursor.close();
   } else {
     student = null;
   }
  //trả về sinh viên đầu tiên tìm được
  return student;
}
//tìm kiếm Student theo StudentName
//kết quả trả về là tất cả Student trong danh sách kết quả
public List<Student> findAllDataHandler(String studentname) {
   String[] projection = {COLUMN_ID, COLUMN_NAME };
   String selection = "studentname = \"" + studentname +"\"";
   // Thực thi truy vấn và gán kết quả đến đối tượng Cursor
   Cursor cursor = myCR.query(MyContentProvider.CONTENT_URI,
         projection, selection, null,null);
   //danh sách chứa tất cả các Student tìm được
   List<Student> lst =  new ArrayList<Student>();
   //duyệt qua tất cả các hàng từ hàng đầu tiên
   if(cursor.moveToFirst()) {
    do {
         Student student = new Student();
         student.setStudentID(Integer.parseInt(cursor.getString(0)));
         student.setStudentName(cursor.getString(1));
         lst.add(student);
     }while (cursor.moveToNext());
   }
  //đóng các đối tượng
  cursor.close();
  //trả về danh sách sinh viên tìm được
  return lst;
}
public boolean deleteDataHandler(int ID) {
  boolean result = false;
  String selection = "studentid = \"" + ID +"\"";
  int rowsDeleted =
  myCR.delete(MyContentProvider.CONTENT_URI,selection, null);
  if (rowsDeleted > 0)
     result = true;
  return result;
}
public boolean updateDataHandler(int ID, String name) {

   ContentValues args = new ContentValues();
   args.put(COLUMN_ID, ID);
   args.put(COLUMN_NAME, name);

   boolean result = false;
   String selection = "studentid = \"" + ID + "\"";
   int rowsUpdated =
     myCR.update(MyContentProvider.CONTENT_URI,args,selection,null);
   if (rowsUpdated > 0)
     result = true;
   return result;
}

Đến lúc này chúng ta có thể kiểm tra ứng dụng với các chức năng Add, Find, Delete hay Update tương tự trong phần 1. Điểm khác biệt là chúng ta dùng đối tượng content provider được khai báo trong tập tin manifest. Với đối tượng content provider, các ứng dụng khác có thể truy cập dữ liệu dễ dàng hơn.

Mã nguồn của các tập tin trong ứng dụng có thể xem tại GitHub.

Học Lập trình Android trong Android Studio 3.X

Chia sẻ:

Thích bài này:

Thích

Đang tải…