Android: Content Provider trong Android

Một thành phần Content Provider cung cấp dữ liệu từ một ứng dụng tới các ứng dụng khác theo yêu cầu. Những yêu cầu này được xử lý bởi các phương thức của lớp ContentResolver. Một Content Provider có thể sử dụng các cách khác nhau để lưu giữ dữ liệu và dữ liệu có thể được lưu giữ trong một cơ sở dữ liệu, một file, hoặc có thể thông qua một mạng.

content provider trong Android
CONTENT PROVIDER

Đôi khi, việc chia sẽ dữ liệu qua các ứng dụng là thực sự cần thiết, khi đó Content Provider trở nên rất hữu ích.

Content Provider giúp bạn tập trung nội dung trong một vị trí và có nhiều các ứng dụng khác nhau có thể truy cập nó khi cần thiết. Một Content Provider vận hành khá giống một cơ sở dữ liệu, nơi mà bạn có thể truy vấn nó, sửa đổi nội dung, cũng như thêm hoặc xóa nội dung bằng việc sử dụng các phương thức insert(), update(), delete() và query(). Trong hầu hết tình huống, dữ liệu này được lưu trữ trong một SQLite database.

Một Content Provider được triển khai như là một lớp con của lớp ContentProvider và phải triển khai một tập APIs chuẩn để cho phép các ứng dụng thực hiện các transaction.

public class StudentsProvider extends ContentProvider {

}

Content URI trong Android

Để truy vấn một Content Provider, bạn xác định chuỗi truy vấn dạng một URI có định dạng sau:

<prefix>://<authority>/<data_type>/<id>

Bảng sau giải thích chi tiết các phần của URI:

PhầnMiêu tảprefixLuôn luôn được thiết lập là contentauthorityXác định tên của Content Provider, ví dụ contacts, browser … Đối với Content Provider bên thứ ba, nó có thể là tên đầy đủ, ví như v1study.com.providerdata_typeChỉ kiểu dữ liệu mà Provider cụ thể cung cấp. Ví dụ, nếu bạn đang lấy tất cả contact từ Content Provider có tên là Contacts, thì dữ liệu sẽ là people và URI sẽ như thế này content://contacts/peopleidXác định bản ghi cụ thể được yêu cầu. Ví dụ, nếu bạn đang tìm số số liên hệ thứ 5 trong Coctacts thì URI sẽ như thế nàycontent://contacts/people/5.

Tạo Content Provider trong Android

Để tạo riêng cho mình một Content Provider, bạn theo 5 bước sau:

  • Đầu tiên, bạn cần tạo một lớp Content Provider kế thừa từ lớp cơ sở ContentProvider.
  • Thứ hai, bạn cần định nghĩa địa chỉ URI của Content Provider, sẽ được sử dụng để truy cập nội dung đó.
  • Tiếp theo, bạn sẽ cần tạo Database cho riêng bạn để giữ nội dung này. Thường thì, Android sử dụng SQLite Database và framework cần thiết để ghi đè phương thức onCreate() sẻ sử dụng phương thức SQLite Open Helper để tạo và mở Database của Provider này. Khi ứng dụng của bạn chạy, phương thức onCreate() được gọi trên mỗi luồng ứng dụng chính.
  • Kế tiếp, bạn sẽ phải triển khai các truy vấn Content Provider để thực hiện các hoạt động cơ sở dữ liệu cụ thể.
  • Cuối cùng, đăng ký Content Provider của bạn trong file activity bằng cách sử dụng thẻ <provider>.

Sau đây là danh sách bạn cần ghi đè trong lớp Content Provider:

content provider
CONTENTPROVIDER

  • onCreate() Phương thức này được gọi khi Provider được bắt đầu.
  • query() Phương thức này nhận một yêu cầu từ người dùng. Kết quả được trả về dưới dạng một đối tượng Cursor.
  • insert() Phương thức này chèn một bản ghi mới vào trong Content Provider.
  • delete() Phương thức này xóa một bản ghi đang tồn tại từ Content Provider.
  • update() Phương thức này cập nhật một bản ghi đang tồn tại trong Content Provider.
  • getType() Phương thức này trả về kiểu MIME của dữ liệu tại URI đã cho.

Ví dụ áp dụng

Ví dụ sau giải thích cách bạn tạo cho riêng mình một ContentProvider.

Sau đây là nội dung của file MainActivity.java. Ta thêm hai phương thức addName() và retrieveStudent() để xử lý tương tác người dùng với ứng dụng.

package

v1study.com.contentproviderv1study;

import

androidx.appcompat.app.AppCompatActivity;

import

android.content.ContentProvider;

import

android.content.ContentValues;

import

android.database.Cursor;

import

android.net.Uri;

import

android.os.Bundle;

import

android.view.View;

import

android.widget.EditText;

import

android.widget.Toast;

public class

MainActivity

extends

AppCompatActivity { @Override

protected void

onCreate(Bundle savedInstanceState) {

super

.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }

public void

addName(View view){ ContentValues contentValues=

new

ContentValues(); contentValues.put(StudentsProvider.NAME,((EditText)findViewById(R.id.name)).getText().toString()); contentValues.put(StudentsProvider.GRADE,((EditText)findViewById(R.id.grade)).getText().toString()); Uri uri=getContentResolver().insert(StudentsProvider.CONTENT_URI,contentValues); Toast.makeText(getBaseContext(),uri.toString(),Toast.LENGTH_LONG).show(); }

public void

retrieveStudent(View view){ String URL=

"content://v1study.com.provider.University/students"

; Uri students=Uri.parse(URL); Cursor cursor=managedQuery(students,

null

,

null

,

null

,

"name"

);

if

(cursor.moveToFirst()){

do

{ Toast.makeText(

this

, cursor.getString(cursor.getColumnIndex(StudentsProvider.NAME)) +

", "

+cursor.getString(cursor.getColumnIndex(StudentsProvider.GRADE)), Toast.LENGTH_SHORT).show(); }

while

(cursor.moveToNext()); } } }

Tạo một file java StudentsProvider.java mới trong package v1study.com.contentproviderv1study ở cùng vị trí với file MainActivity.java và có nội dung như sau:

package

v1study.com.contentproviderv1study

;

import

android.content.ContentProvider

;

import

android.content.ContentValues

;

import

android.content.Context

;

import

android.content.UriMatcher

;

import

android.database.Cursor

;

import

android.database.SQLException

;

import

android.database.sqlite.SQLiteDatabase

;

import

android.database.sqlite.SQLiteOpenHelper

;

import

android.database.sqlite.SQLiteQueryBuilder

;

import

android.net.Uri

;

import

android.content.ContentUris

;

import

android.text.TextUtils

;

import

java.util.HashMap

;

public class

StudentsProvider

extends

ContentProvider

{

static final

String

PROVIDER_NAME =

"v1study.com.provider.University"

;

static final

String

URL =

"content://"

+ PROVIDER_NAME +

"/students"

;

static final

Uri

CONTENT_URI =

Uri

.parse(URL);

static final

String

_ID =

"_id"

;

static final

String

NAME =

"name"

;

static final

String

GRADE =

"grade"

;

private static

HashMap

<

String

,

String

> STUDENTS_MAP;

static final int

STUDENTS =

1

;

static final int

STUDENT_ID =

2

;

static final

UriMatcher

uriMatcher;

static

{ uriMatcher =

new

UriMatcher(

UriMatcher

.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME,

"students"

, STUDENTS); uriMatcher.addURI(PROVIDER_NAME,

"students/#"

, STUDENT_ID); }

private

SQLiteDatabase

sqLiteDatabase

;

static final

String

DATABASE_NAME =

"University"

;

static final

String

STUDENTS_TABLE_NAME =

"students"

;

static final int

DATABASE_VERSION =

1

;

static final

String

CREATE_DB_TABLE =

"create table "

+ STUDENTS_TABLE_NAME +

"(_id integer primary key autoincrement,name text not null,grade text not null)"

;

@Override

public boolean

onCreate

() {

Context context

= getContext();

DatabaseHelper databaseHelper

=

new

DatabaseHelper(

context

);

sqLiteDatabase

=

databaseHelper

.getWritableDatabase();

return

(

sqLiteDatabase

==

null

) ?

false

:

true

; }

@Override

public

Cursor

query

(

Uri

uri,

String

[] strings,

String

s,

String

[] strings1,

String

s1) {

SQLiteQueryBuilder sqLiteQueryBuilder

=

new

SQLiteQueryBuilder();

sqLiteQueryBuilder

.setTables(STUDENTS_TABLE_NAME);

switch

(uriMatcher.match(uri)) {

case

STUDENTS:

sqLiteQueryBuilder

.setProjectionMap(STUDENTS_MAP);

break

;

case

STUDENT_ID:

sqLiteQueryBuilder

.appendWhere(_ID +

"="

+ uri.getPathSegments().get(

1

));

break

;

default

:

throw new

IllegalArgumentException(

"Unknow URI "

+ uri); }

if

(s1 ==

null

|| s1 ==

""

) { s1 = NAME; }

Cursor cursor

=

sqLiteQueryBuilder

.query(

sqLiteDatabase

, strings, s, strings1,

null

,

null

, s1);

cursor

.setNotificationUri(getContext().getContentResolver(), uri);

return

cursor

; }

@Override

public

String

getType

(

Uri

uri) {

switch

(uriMatcher.match(uri)) {

case

STUDENTS:

return

"vnd.android.cursor.dir/vnd/example.students"

;

case

STUDENT_ID:

return

"vnd.android.cursor.item/vnd.example.students"

;

default

:

throw new

IllegalArgumentException(

"Unsupport URI: "

+ uri); } }

@Override

public

Uri

insert

(

Uri

uri,

ContentValues

contentValues) {

long

rowID

=

sqLiteDatabase

.insert(STUDENTS_TABLE_NAME,

""

, contentValues);

if

(

rowID

>

0

) {

Uri _uri

=

ContentUris

.withAppendedId(CONTENT_URI,

rowID

); getContext().getContentResolver().notifyChange(

_uri

,

null

);

return

_uri

; }

throw new

SQLException(

"Fail to ad a record into "

+ uri); }

@Override

public int

delete

(

Uri

uri,

String

s,

String

[] strings) {

int

count =

0

;

switch

(uriMatcher.match(uri)) {

case

STUDENTS: count =

sqLiteDatabase

.delete(STUDENTS_TABLE_NAME, s, strings);

break

;

case

STUDENT_ID:

String id

= uri.getPathSegments().get(

1

); count =

sqLiteDatabase

.delete(STUDENTS_TABLE_NAME, _ID +

"="

+

id

+ (!

TextUtils

.isEmpty(s) ?

" and ("

+ s +

")"

:

""

), strings);

break

;

default

:

throw new

IllegalArgumentException(

"Unknow URI: "

+ uri); } getContext().getContentResolver().notifyChange(uri,

null

);

return

count; }

@Override

public int

update

(

Uri

uri,

ContentValues

contentValues,

String

s,

String

[] strings) {

int

count =

0

;

switch

(uriMatcher.match(uri)) {

case

STUDENTS: count =

sqLiteDatabase

.update(STUDENTS_TABLE_NAME, contentValues, s, strings);

break

;

case

STUDENT_ID: count =

sqLiteDatabase

.update(STUDENTS_TABLE_NAME, contentValues, _ID +

"="

+ uri.getPathSegments().get(

1

) + (!

TextUtils

.isEmpty(s) ?

" and ("

+ s +

")"

:

""

), strings);

break

;

default

:

throw new

IllegalArgumentException(

"Unknow URI: "

+ uri); } getContext().getContentResolver().notifyChange(uri,

null

);

return

0

; }

private static class

DatabaseHelper

extends

SQLiteOpenHelper

{

DatabaseHelper

(

Context

context) {

super

(context, DATABASE_NAME,

null

, DATABASE_VERSION); }

@Override

public void

onCreate

(

SQLiteDatabase

sqLiteDatabase) { sqLiteDatabase.execSQL(CREATE_DB_TABLE); }

@Override

public void

onUpgrade

(

SQLiteDatabase

sqLiteDatabase,

int

i,

int

i1) { sqLiteDatabase.execSQL(

"drop table if exists "

+ STUDENTS_TABLE_NAME); } } }

Còn đây là nội dung đã được sửa đổi của file AndroidManifest.xml. Ở đây, chúng ta đã thêm thẻ <provider…/> để bao Content Provider của chúng ta:

<?

xml version

="1.0"

encoding

="utf-8"

?> <

manifest

xmlns:

android

="http://schemas.android.com/apk/res/android"

xmlns:

dist

="http://schemas.android.com/apk/distribution"

package

="v1study.com.contentproviderv1study"

> <

dist

:module

dist

:instant

="true"

/> <

application

android

:allowBackup

="true"

android

:icon

="@mipmap/ic_launcher"

android

:label

="@string/app_name"

android

:roundIcon

="@mipmap/ic_launcher_round"

android

:supportsRtl

="true"

android

:theme

="@style/AppTheme"

> <

activity

android

:name

=".MainActivity"

> <

intent-filter

> <

action

android

:name

="android.intent.action.MAIN"

/> <

category

android

:name

="android.intent.category.LAUNCHER"

/> </

intent-filter

> </

activity

> <

provider

android

:authorities

="v1study.com.provider.University"

android

:name

="StudentsProvider"

/> </

application

> </

manifest

>

Dưới đây là nội dung của res/layout/activity_main.xml:

<?

xml version

="1.0"

encoding

="utf-8"

?> <

androidx.constraintlayout.widget.ConstraintLayout

xmlns:

android

="http://schemas.android.com/apk/res/android"

xmlns:

app

="http://schemas.android.com/apk/res-auto"

xmlns:

tools

="http://schemas.android.com/tools"

android

:layout_width

="match_parent"

android

:layout_height

="match_parent"

tools

:context

=".MainActivity"

> <

EditText

android

:id

="@+id/grade"

android

:layout_width

="wrap_content"

android

:layout_height

="wrap_content"

android

:width

="320dp"

android

:autofillHints

="@string/grade"

android

:hint

="@string/grade"

android

:inputType

=""

android

:textColor

="#8BC34A"

android

:textColorHint

="#009688"

android

:textSize

="30sp"

android

:textStyle

="bold"

app

:layout_constraintBottom_toBottomOf

="parent"

app

:layout_constraintHorizontal_bias

="0.494"

app

:layout_constraintLeft_toLeftOf

="parent"

app

:layout_constraintRight_toRightOf

="parent"

app

:layout_constraintTop_toTopOf

="parent"

app

:layout_constraintVertical_bias

="0.584"

/> <

EditText

android

:id

="@+id/name"

android

:layout_width

="wrap_content"

android

:layout_height

="wrap_content"

android

:width

="320dp"

android

:autofillHints

="@string/add_name"

android

:hint

="@string/name"

android

:inputType

=""

android

:textColor

="#8BC34A"

android

:textColorHint

="#009688"

android

:textSize

="30sp"

android

:textStyle

="bold"

app

:layout_constraintBottom_toBottomOf

="parent"

app

:layout_constraintLeft_toLeftOf

="parent"

app

:layout_constraintRight_toRightOf

="parent"

app

:layout_constraintTop_toTopOf

="parent"

app

:layout_constraintVertical_bias

="0.468"

/> <

Button

android

:id

="@+id/textView"

android

:layout_width

="wrap_content"

android

:layout_height

="wrap_content"

android

:onClick

="retrieveStudent"

android

:text

="@string/retrieve_student"

android

:textColor

="#FFFFFF"

android

:textSize

="30sp"

android

:textStyle

="bold"

app

:layout_constraintBottom_toBottomOf

="parent"

app

:layout_constraintLeft_toLeftOf

="parent"

app

:layout_constraintRight_toRightOf

="parent"

app

:layout_constraintTop_toTopOf

="parent"

app

:layout_constraintVertical_bias

="0.822"

/> <

Button

android

:id

="@+id/textView3"

android

:layout_width

="wrap_content"

android

:layout_height

="wrap_content"

android

:onClick

="addName"

android

:text

="@string/add_name"

android

:textColor

="#FFFFFF"

android

:textSize

="30sp"

android

:textStyle

="bold"

app

:layout_constraintBottom_toBottomOf

="parent"

app

:layout_constraintLeft_toLeftOf

="parent"

app

:layout_constraintRight_toRightOf

="parent"

app

:layout_constraintTop_toTopOf

="parent"

app

:layout_constraintVertical_bias

="0.71"

/> <

TextView

android

:id

="@+id/textView2"

android

:layout_width

="wrap_content"

android

:layout_height

="wrap_content"

android

:text

="@string/contentproviderv1study"

android

:textColor

="#8BC34A"

android

:textSize

="30sp"

android

:textStyle

="bold"

app

:layout_constraintBottom_toBottomOf

="parent"

app

:layout_constraintLeft_toLeftOf

="parent"

app

:layout_constraintRight_toRightOf

="parent"

app

:layout_constraintTop_toTopOf

="parent"

app

:layout_constraintVertical_bias

="0.071"

/> <

ImageView

android

:id

="@+id/imageView"

android

:layout_width

="170dp"

android

:layout_height

="170dp"

android

:layout_marginStart

="8dp"

android

:layout_marginLeft

="8dp"

android

:layout_marginTop

="8dp"

android

:layout_marginEnd

="8dp"

android

:layout_marginRight

="8dp"

android

:layout_marginBottom

="8dp"

android

:contentDescription

="@string/logo_v1study"

app

:layout_constraintBottom_toBottomOf

="parent"

app

:layout_constraintEnd_toEndOf

="parent"

app

:layout_constraintHorizontal_bias

="0.488"

app

:layout_constraintStart_toStartOf

="parent"

app

:layout_constraintTop_toTopOf

="parent"

app

:layout_constraintVertical_bias

="0.192"

app

:srcCompat

="@mipmap/logo_v1_regular"

/> </

androidx.constraintlayout.widget.ConstraintLayout

>

Đảm bảo bạn có nội dung sau của file res/values/strings.xml:

<

resources

> <

string

name

="app_name"

>ContentProviderV1Study</

string

> <

string

name

="contentproviderv1study"

>ContentProviderV1Study</

string

> <

string

name

="retrieve_student"

>Retrieve Student</

string

> <

string

name

="add_name"

>Add Name</

string

> <

string

name

="logo_v1study"

>Logo V1Study</

string

> <

string

name

="grade"

>Grade</

string

> <

string

name

="name"

>Name</

string

> </

resources

>

Chạy ứng dụng Android vừa tạo ở trên.

content provider trong Android

Bây giờ, bạn thử nhập một Name và Grade của một student rồi nhấn nút Add Name nó sẽ thêm bản ghi student đó vào trong Database và sẽ hiện một thông báo tại cuối màn hình để hiển thị ContentProvider URI cùng với số bản ghi đã thêm vào Database. Hoạt động này sử dụng phương thức insert(). Bạn thử lặp lại tiến trình này để nhập thêm một số student nữa vào cơ sở dữ liệu của Content Provider.

content provider trong Android

Một khi bạn đã nhập một số bản ghi vào Database, lúc này, giả sử bạn muốn đề nghị ContentProvider cung cấp cho chúng ta các bản ghi đó. Bạn chỉ cần nhấn nút Retrieve Student thì ứng dụng sẽ lấy và hiển thị tất cả bản ghi (từng bản ghi một) theo trình triển khai của phương thức query().

retrieve students

Bạn có thể viết các Activity với các hoạt động cập nhật và xóa bằng cách cung cấp các hàm trong file MainActivity.java và sau đó sửa đổi giao diện UI để có các nút cho các hoạt động update và delete này theo cách tương tự chúng ta đã làm với các hoạt động thêm và đọc.

Theo cách này, bạn có thể sử dụng ContentProvider đang tồn tại là Address Book hoặc bạn có thể sử dụng khái niệm Content Provider để phát triển một ứng dụng hướng cơ sở dữ liệu (Database Oriented) đẹp, ở đó bạn có thể thực hiện tất cả các hoạt động trên cơ sở dữ liệu như read, write, update, và delete như đã giải thích trong ví dụ trên.