Android – Cung cấp nội dung (Content providers)



Content Provider là một Component có nhiệm vụ cung cấp dữ liệu từ một ứng dụng này cho các ứng dụng khác theo yêu cầu. Content Provider đặc biệt hữu dụng trong việc chia sẻ dữ liệu giữa các ứng dụng. Các yêu cầu này được quản lý bởi các phương thức của lớp ContentResolver. Một Content Provider có thể dùng nhiều cách để lưu dữ liệu như là trong cơ sở dữ liệu, trong tập tin hoặc thậm chí trên mạng nội bộ, Internet.

Hình dưới đây mô tả vị trí của Content Provider trong mô hình chia sẻ dữ liệu giữa các ứng dụng:

content

ContentProvider

Content Provider giúp bạn tập trung hóa nội dung vào một chỗ để nhiều ứng dụng khác nhau có thể truy cập tới khi cần thiết. Một Content Provider hoạt động khá giống với có sở dữ liệu, trong đó bạn có thể truy vấn, thay đổi, thêm hoặc xóa nội dung với sự giúp đỡ của các phương thức insert(), update(), delete(), and query() methods. Trong phần lớn ứng dụng, dữ liệu được lưu trong cơ sở dữ liệu SQlite.

Một Content Provider được định nghĩa như là một lớp con của lớp ContentProvider và bạn phải viết lại các APIs cơ bản để cho phép các ứng dụng khác thực hiện việc truyền dữ liệu.

public class My Application extends  ContentProvider {

}

Content URI

Để truy vấn một Content Provider, bạn phải chỉ ra chuỗi truy vấn dưới dạng URI như sau:

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

Ta gọi đó là Content Provider URI. Trong bảng sau là chi tiết các phần của URI:

Thành phần Mô tả
prefix Giá trị luôn luôn là content://
authority Tên của, ví dụ như contacts, browser, com.tutorialspoint.statusprovider
data_type Dạng dữ liệu, ví dụ như bạn lấy tất cả thông tin từ danh bạ (Contact) từ Contact Content Provider, thì <authority> sẽ là contacts và <data_type> là people. URI của Content Provider sẽ là content://contacts/people
id Chỉ ra record nào  trong cơ sở dữ liêu bạn muốn truy vấn. Ví dụ bạn đang tìm Contact thứ 5 trong danh bạ, URI sẽ là content://contacts/people/5.

Tạo Content Provider

Các bước để tạo một Content Provider khá đơn giản:

  • Tạo một lớp con Content Provider kế thừa từ lớp ContentProvider.
  • Định nghĩa Content Provider URI dùng để truy cập tới nội dung của Content Provider.
  • Tạo cơ sở dữ liệu để lưu nội dung. Thông thường, cơ sở dữ liệu SQLite và framework cần phải viết lại phương thức onCreate(), phương thức này giúp tạo và mở cơ sở dữ liệu. Khi ứng dụng khởi chạy, phương thức onCreate() của từng Content Provider được gợi trên thread chính của ứng dụng.
  • Viết lại truy vấn của Content Provider để thực hiện các thao tác trên cơ sở dữ liệu.
  • Khai báo Content Provider vừa tạo trong tập tin manifest dùng thẻ <provider>.

Sau đây là các phương thức bạn phải viết lại khi kết thừa lớp ContentProvider để Content Provider của bạn hoạt động: insert(), update(), delete(), query().

  • onCreate(): phương thức này được gọi khi Content Provider được kích hoạt.
  • query(): phương thức này nhận yêu cầu từ client. Kết quả được trả lại là một đối tượng Cursor.
  • insert(): phương thức này thêm một record mới vào Content Provider.
  • delete(): phương thức này xóa một record khỏi Content Provider.
  • update(): phương thức này thay đổi giá trị một record trong Content Provider.
  • getType(): phương thức này trả lại kiểu dữ liệu MIME (Multipurpose Internet Mail Extensions) của dữ liệu tại URI nhất định.

Ví dụ

Ví dụ sau đây sẽ hướng dẫn bạn tạo một ContentProvider mới. Hãy làm theo các bước trong bảng sau:

Bước Mô tả
1 Tạo một ứng dụng mới và đặt tên là  My Application dưới package com.example.My Application, với blank Activity.
2 Chỉnh sửa tập tin MainActivity.java bằng việc thêm 2 phương thức onClickAddName()onClickRetrieveStudents().
3 Tạo 1 tâp tin java mới đặt tên là StudentsProvider.java dưới package com.example.My Application để định nghĩa một Content Provider mới và các phương thức.
4 Khai báo Content Provider mới này trong AndroidManifest.xml bằng thẻ <provider…/>
5 Chỉnh sửa tập tin res/layout/activity_main.xml bằng cách thêm phần giao diện để thêm các record sinh viên.
6 Bạn không cần thay đổi tập tin string.xml mà IDE sẽ làm việc này giúp bạn.
7 Chạy ứng dụng trên Android emulator và kiểm tra kết quả.

Sau đây là nội dụng tập tin src/com.example.My Application/MainActivity.java đã thay đổi với 2 phương thức mới onClickAddName()onClickRetrieveStudents() để điều khiển việc tương tác của người dùng trên giao diện.

package com.example.My Application;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.database.Cursor;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
   }

   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.main, menu);
      return true;
   }

   public void onClickAddName(View view) {
      // Thêm một record sinh viên mới
      ContentValues values = new ContentValues();

      values.put(StudentsProvider.NAME, 
      ((EditText)findViewById(R.id.editText2)).getText().toString());
      
      values.put(StudentsProvider.GRADE, 
      ((EditText)findViewById(R.id.editText3)).getText().toString());

      Uri uri = getContentResolver().insert(
      StudentsProvider.CONTENT_URI, values);
      
      Toast.makeText(getBaseContext(), 
      uri.toString(), Toast.LENGTH_LONG).show();
   }

   public void onClickRetrieveStudents(View view) {
      
      // Đọc các records sinh viên
      String URL = "content://com.example.provider.College/students";
      
      Uri students = Uri.parse(URL);
      Cursor c = managedQuery(students, null, null, null, "name");
      
      if (c.moveToFirst()) {
         do{
            Toast.makeText(this, 
            c.getString(c.getColumnIndex(StudentsProvider._ID)) + 
            ", " +  c.getString(c.getColumnIndex( StudentsProvider.NAME)) + 
            ", " + c.getString(c.getColumnIndex( StudentsProvider.GRADE)), 
            Toast.LENGTH_SHORT).show();
         } while (c.moveToNext());
      }
   }
}

Tạo tập tin mới, đặt tên là StudentsProvider.java dưới package com.example.My Application, chứa đoạn code như bên dưới:

package com.example.My Application;

import java.util.HashMap;

import android.content.ContentProvider;
import android.content.ContentUris;
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.text.TextUtils;

public class StudentsProvider extends ContentProvider {

   static final String PROVIDER_NAME = "com.example.provider.College";
   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_PROJECTION_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);
   }

   /**
   * Database specific constant declarations
   */
   private SQLiteDatabase db;
   static final String DATABASE_NAME = "College";
   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);";

   /**
   * Helper class that actually creates and manages 
   * the provider's underlying data repository.
   */
   private static class DatabaseHelper extends SQLiteOpenHelper {
      DatabaseHelper(Context context){
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }
      
      @Override
      public void onCreate(SQLiteDatabase db)
      {
         db.execSQL(CREATE_DB_TABLE);
      }
      
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         db.execSQL("DROP TABLE IF EXISTS " +  STUDENTS_TABLE_NAME);
         onCreate(db);
      }
   }
   
   @Override
   public boolean onCreate() {
      Context context = getContext();
      DatabaseHelper dbHelper = new DatabaseHelper(context);
   
      /**
      * Create a write able database which will trigger its 
      * creation if it doesn't already exist.
      */
      db = dbHelper.getWritableDatabase();
      return (db == null)? false:true;
   }

   @Override
   public Uri insert(Uri uri, ContentValues values) {
      /**
      * Add a new student record
      */
      long rowID = db.insert(	STUDENTS_TABLE_NAME, "", values);
      
      /** 
      * If record is added successfully
      */
      
      if (rowID > 0)
      {
         Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
         getContext().getContentResolver().notifyChange(_uri, null);
         return _uri;
      }
      throw new SQLException("Failed to add a record into " + uri);
   }
   
   @Override
   public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
      SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
      qb.setTables(STUDENTS_TABLE_NAME);
      
      switch (uriMatcher.match(uri)) {
         case STUDENTS:
         qb.setProjectionMap(STUDENTS_PROJECTION_MAP);
         break;
         
         case STUDENT_ID:
         qb.appendWhere( _ID + "=" + uri.getPathSegments().get(1));
         break;
         
         default:
         throw new IllegalArgumentException("Unknown URI " + uri);
      }
      
      if (sortOrder == null || sortOrder == ""){
         /** 
         * By default sort on student names
         */
         sortOrder = NAME;
      }
      Cursor c = qb.query(db,	projection,	selection, selectionArgs,null, null, sortOrder);
      
      /**
      * register to watch a content URI for changes
      */
      c.setNotificationUri(getContext().getContentResolver(), uri);
      return c;
   }

   @Override
   public int delete(Uri uri, String selection, String[] selectionArgs) {
      int count = 0;
      
      switch (uriMatcher.match(uri)){
         case STUDENTS:
         count = db.delete(STUDENTS_TABLE_NAME, selection, selectionArgs);
         break;
         
         case STUDENT_ID:
         String id = uri.getPathSegments().get(1);
         count = db.delete( STUDENTS_TABLE_NAME, _ID +  " = " + id + 
         (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
         break;
         
         default: 
         throw new IllegalArgumentException("Unknown URI " + uri);
      }
      
      getContext().getContentResolver().notifyChange(uri, null);
      return count;
   }
   
   @Override
   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
      int count = 0;
      
      switch (uriMatcher.match(uri)){
         case STUDENTS:
         count = db.update(STUDENTS_TABLE_NAME, values, selection, selectionArgs);
         break;
         
         case STUDENT_ID:
         count = db.update(STUDENTS_TABLE_NAME, values, _ID + " = " + uri.getPathSegments().get(1) + 
         (!TextUtils.isEmpty(selection) ? " AND (" +selection + ')' : ""), selectionArgs);
         break;
         
         default: 
         throw new IllegalArgumentException("Unknown URI " + uri );
      }
      getContext().getContentResolver().notifyChange(uri, null);
      return count;
   }

   @Override
   public String getType(Uri uri) {
      switch (uriMatcher.match(uri)){
         /**
         * Get all student records 
         */
         case STUDENTS:
         return "vnd.android.cursor.dir/vnd.example.students";
         
         /** 
         * Get a particular student
         */
         case STUDENT_ID:
         return "vnd.android.cursor.item/vnd.example.students";
         
         default:
         throw new IllegalArgumentException("Unsupported URI: " + uri);
     }
  }
}

Sau đây là tập tin AndroidManifest.xml đã được chỉnh sửa, trong đó thẻ <provider…/> dùng để khai báo Content Provider:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.My Application"
   android:versionCode="1"
   android:versionName="1.0" >
   
   <uses-sdk
      android:minSdkVersion="8"
      android:targetSdkVersion="22" />
      
      <application
         android:allowBackup="true"
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
         
      <activity
         android:name="com.example.My Application.MainActivity"
         android:label="@string/app_name" >
            
         <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
            
      </activity>
         
      <provider android:name="StudentsProvider" 
         <android:authorities="com.example.provider.College">
      </provider>
         
   </application>
</manifest>

Sau đây là nội dung của res/layout/activity_main.xml, trong đó thêm phần để nhập thông tin cho record sinh viên:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"/">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Content provider"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:textSize="30dp" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tutorials point "
        android:textColor="#ff87ff09"
        android:textSize="30dp"
        android:layout_below="@+id/textView1"
        android:layout_centerHorizontal="true" />

    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageButton"
        android:src="@drawable/abc"
        android:layout_below="@+id/textView2"
        android:layout_centerHorizontal="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="Add Name"
        android:layout_below="@+id/editText3"
        android:layout_alignRight="@+id/textView2"
        android:layout_alignEnd="@+id/textView2"
        android:layout_alignLeft="@+id/textView2"
        android:layout_alignStart="@+id/textView2"
        android:onClick="onClickAddName"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_below="@+id/imageButton"
        android:layout_alignRight="@+id/imageButton"
        android:layout_alignEnd="@+id/imageButton" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText2"
        android:layout_alignTop="@+id/editText"
        android:layout_alignLeft="@+id/textView1"
        android:layout_alignStart="@+id/textView1"
        android:layout_alignRight="@+id/textView1"
        android:layout_alignEnd="@+id/textView1"
        android:hint="Name"
        android:textColorHint="@android:color/holo_blue_light" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText3"
        android:layout_below="@+id/editText"
        android:layout_alignLeft="@+id/editText2"
        android:layout_alignStart="@+id/editText2"
        android:layout_alignRight="@+id/editText2"
        android:layout_alignEnd="@+id/editText2"
        android:hint="Grade"
        android:textColorHint="@android:color/holo_blue_bright" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Retrive student"
        android:id="@+id/button"
        android:layout_below="@+id/button2"
        android:layout_alignRight="@+id/editText3"
        android:layout_alignEnd="@+id/editText3"
        android:layout_alignLeft="@+id/button2"
        android:layout_alignStart="@+id/button2"
        android:onClick="onClickRetrieveStudents"/>

</RelativeLayout>

Kiểm tra tập tin res/values/strings.xml xem đã có những nội dung sau đây chưa:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">My Application</string>
    <string name="action_settings">Settings</string>
</resources>;

Hãy thử chạy ứng dụng, bạn sẽ thấy trên màn hình Emulator chạy ứng dụng hiện lên như trong hình bên dưới:

content2

Hãy nhập tên và lớp học của sinh viên mới, sau đó bấm nút Add Name, một record sinh viên mới sẽ được thêm vào cơ sở dữ liệu. Hãy thử bấm nút Retrive Student để đọc và hiển thị các record ra màn hình, kết quả giống như hình bên dưới. Bạn có thể thêm một vài record mới nữa và kiểm tra kết quả hiện ra trên màn hình.

content3

Bạn có thể tự thực hành xóa record, chỉnh sửa các trường của record, lấy kiểu dữ liệu.

1433 Total Views 4 Views Today