Chia sẻ Database giữa các application, sử dụng Content Provider

1. Giới thiệu về Content Provider

Trong mô hình bảo mật Android, một ứng dụng không thể trực tiếp truy cập (đọc / ghi) dữ liệu của các ứng dụng khác. Mỗi ứng dụng có dữ liệu riêng và bộ nhớ riêng của mình.

Content Provider chính là cách tốt nhất để chia sẻ dữ liệu giữa các ứng dụng. Ứng dụng phải tự đăng ký như là một nhà cung cấp dữ liệu. Các ứng dụng khác có thể yêu cầu Android để đọc / ghi dữ liệu đó thông qua một API được định nghĩa trước. Các Content Provider API tuân thủ theo CRUD.

Một số ví dụ về Content Provider: Contacs: cho phép lấy thông tin từ danh bạ để sử dụng ở ứng dụng khác, Media Store: cho phép các ứng dụng khác truy cập, lưu trữ các file media.

  • Cách hoạt động của Content Provider Screenshot from 2015-10-25 10:36:10.png

Content Provider sử dụng các phương thức insert(), query(), update(), delete() để truy cập vào database của ứng dụng.

Một URI riêng được bắt đầu bằng content:// sẽ được gán cho mỗi Content Provider và nó sẽ được sử dụng trên các ứng dụng.

Các lớp ContentProvider là thành phần chính của một Content Provider. Để tạo ra một Content Provider, chúng ta cần

  1. Tạo sub class cho ContentProvider.
  2. Định nghĩa URI, có dạng: content://authority/path/id
  3. Implement tất cả các phương pháp chưa được thực hiện: insert(), update(), query(), delete(), getType().
  4. Khai báo trong AndroidManifest.xml

2. Ví dụ về sử dụng Content Provider chia sẻ dữ liệu giữa các ứng dụng

Ở đây chúng ta sẽ tạo ra 2 ứng dụng, ứng dụng thứ nhất sẽ tạo ra 1 content provider và chia sẻ database của nó, ứng dụng thứ 2 sẽ truy cập vào để lấy dữ liệu.

1. Ứng dụng 1: MyProvider

Tạo ra Content Provider và chia sẻ dữ liệu. Sử dụng database có tên là "mydb", có 1 bảng là "names", bảng có 2 trường là "id" và "name"

URI của content provider này sẽ là: content://com.example.contentproviderexample.MyProvider/cte

Các thành phần của ứng dụng 1:

Layout xml file:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Name" />
    <EditText
        android:id="@+id/txtName"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btnAdd"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickAddName"
        android:text="Add Name" />
</LinearLayout>

Content provider class:

MyProvider.java

package com.example.contentproviderexample;

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;

public class MyProvider extends ContentProvider {
 static final String PROVIDER_NAME = "com.example.contentproviderexample.MyProvider";
 static final String URL = "content://" + PROVIDER_NAME + "/cte";
 static final Uri CONTENT_URI = Uri.parse(URL);

 static final String id = "id";
 static final String name = "name";
 static final int uriCode = 1;
 static final UriMatcher uriMatcher;
 private static HashMap<String, String> values;
 static {
  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  uriMatcher.addURI(PROVIDER_NAME, "cte", uriCode);
  uriMatcher.addURI(PROVIDER_NAME, "cte/*", uriCode);
 }

 @Override
 public int delete(Uri uri, String selection, String[] selectionArgs) {
  int count = 0;
  switch (uriMatcher.match(uri)) {
  case uriCode:
   count = db.delete(TABLE_NAME, 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)) {
  case uriCode:
   return "vnd.android.cursor.dir/cte";

  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }
 }

 @Override
 public Uri insert(Uri uri, ContentValues values) {
  long rowID = db.insert(TABLE_NAME, "", values);
  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 boolean onCreate() {
  Context context = getContext();
  DatabaseHelper dbHelper = new DatabaseHelper(context);
  db = dbHelper.getWritableDatabase();
  if (db != null) {
   return true;
  }
  return false;
 }

 @Override
 public Cursor query(Uri uri, String[] projection, String selection,
   String[] selectionArgs, String sortOrder) {
  SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
  qb.setTables(TABLE_NAME);

  switch (uriMatcher.match(uri)) {
  case uriCode:
   qb.setProjectionMap(values);
   break;
  default:
   throw new IllegalArgumentException("Unknown URI " + uri);
  }
  if (sortOrder == null || sortOrder == "") {
   sortOrder = name;
  }
  Cursor c = qb.query(db, projection, selection, selectionArgs, null,
    null, sortOrder);
  c.setNotificationUri(getContext().getContentResolver(), uri);
  return c;
 }

 @Override
 public int update(Uri uri, ContentValues values, String selection,
   String[] selectionArgs) {
  int count = 0;
  switch (uriMatcher.match(uri)) {
  case uriCode:
   count = db.update(TABLE_NAME, values, selection, selectionArgs);
   break;
  default:
   throw new IllegalArgumentException("Unknown URI " + uri);
  }
  getContext().getContentResolver().notifyChange(uri, null);
  return count;
 }

 private SQLiteDatabase db;
 static final String DATABASE_NAME = "mydb";
 static final String TABLE_NAME = "names";
 static final int DATABASE_VERSION = 1;
 static final String CREATE_DB_TABLE = " CREATE TABLE " + TABLE_NAME
   + " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
   + " name TEXT NOT NULL);";

 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 " + TABLE_NAME);
   onCreate(db);
  }
 }
}

Activity calss:

MainActivity.java

package com.example.contentproviderexample;

import android.app.Activity;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
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) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }

 public void onClickAddName(View view) {
  ContentValues values = new ContentValues();
  values.put(MyProvider.name, ((EditText) findViewById(R.id.txtName))
    .getText().toString());
  Uri uri = getContentResolver().insert(MyProvider.CONTENT_URI, values);
  Toast.makeText(getBaseContext(), "New record inserted", Toast.LENGTH_LONG)
    .show();
 }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.contentproviderexample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.contentproviderexample.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=".MyProvider"
            android:authorities="com.example.contentproviderexample.MyProvider"
            android:exported="true"
            android:multiprocess="true" >
        </provider>
    </application>

</manifest>

Output screenshots:

Ở đây có một EditText để nhập name và Button Add để lưu name vào Database, thực hiện nhập 1 số bản ghi ở đây và chúng sẽ được lấy ra từ ứng dụng thứ 2

1.png

2. Ứng dụng 2: MyUser

Sử dụng URI để truy cập và lấy dữ liệu từ ứng dụng 1 và hiển thị:

Layout xml file:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/btnRetrieve"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickDisplayNames"
        android:text="Display names" />
    <TextView
        android:id="@+id/res"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:clickable="false"
        android:ems="10" >
    </TextView>
</LinearLayout>

Activity class:

MainActivity.java

Chúng ta sẽ sử dụng CusrsorLoader để lấy dữ liệu:

package com.example.contentprovideruser;

import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> {
 TextView resultView=null;
    CursorLoader cursorLoader;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  resultView= (TextView) findViewById(R.id.res);
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }

 public void onClickDisplayNames(View view) {
  getSupportLoaderManager().initLoader(1, null, this);
 }

 @Override
 public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
  cursorLoader= new CursorLoader(this, Uri.parse("content://com.example.contentproviderexample.MyProvider/cte"), null, null, null, null);
  return cursorLoader;
 }

 @Override
 public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
  cursor.moveToFirst();
  StringBuilder res=new StringBuilder();
        while (!cursor.isAfterLast()) {
         res.append("\n"+cursor.getString(cursor.getColumnIndex("id"))+ "-"+ cursor.getString(cursor.getColumnIndex("name")));
            cursor.moveToNext();
        }
        resultView.setText(res);
 }

 @Override
 public void onLoaderReset(Loader<Cursor> arg0) {
  // TODO Auto-generated method stub

 }

 @Override
 public void onDestroy() {
        super.onDestroy();
    }

}

**Output: ** Click vào button Display names, trên màn hình sẽ hiển thị danh sách các bản ghi đã được insert vào database ở ứng dụng 1.

2.png

**Nguồn tham khảo: ** http://www.compiletimeerror.com/2013/12/content-provider-in-android.html#.VixFzLzGInR


All Rights Reserved