Android - Proveedor de contenido (Content Provider)
El componente proveedor de contenido proporciona datos de una aplicación a otras aplicaciones mediante solicitudes. Estas solicitudes son manejadas por los métodos de la clase ContentResolver. Los proveedores de contenido pueden almacenar datos de diferentes maneras. Los datos pueden guardarse en bases de datos, archivos, e incluso en la red.

A veces es necesario compartir datos entre aplicaciones. En este momento, el proveedor de contenido se vuelve muy útil.
Los proveedores de contenido permiten centralizar el contenido, y cuando sea necesario, múltiples aplicaciones diferentes pueden acceder a él. El comportamiento del proveedor de contenido es muy similar al de una base de datos. Puedes consultar y editar su contenido, usar insert(), update(), delete() y query() para agregar o eliminar contenido. En la mayoría de los casos, los datos se almacenan en una base de datos SQLite.
El proveedor de contenido se implementa como una subclase de la clase ContentProvider. Se necesita implementar una serie de API estándar para que otras aplicaciones puedan realizar transacciones.
public class MyApplication extends ContentProvider {
}
URI de contenido
Para consultar un proveedor de contenido, debes especificar la cadena de consulta en forma de una URI con el siguiente formato:
<prefix>://<authority>/<data_type>/<id>
A continuación se explica detalladamente cada parte de la URI:
| Parte | Descripción |
|---|---|
| prefix | Prefijo: siempre se establece como content:// |
| authority | Autoridad: especifica el nombre del proveedor de contenido, por ejemplo, contactos, navegador, etc. El proveedor de contenido de terceros puede ser su nombre completo, como: cn.programmer.statusprovider |
| data_type | Tipo de datos: esto indica el tipo de datos dentro de este proveedor de contenido en particular. Por ejemplo: si deseas obtener todos los contactos a través del proveedor de contenido Contacts, y la ruta de datos es people, entonces la URI sería así: content://contacts/people |
| id | Esto especifica un registro de solicitud en particular. Por ejemplo: si buscas el contacto con ID número 5 en el proveedor de contenido Contacts, la URI se vería así: content://contacts/people/5 |
Crear un proveedor de contenido
Aquí se describen los pasos simples para crear tu propio proveedor de contenido.
- Primero, necesitas heredar de la clase ContentProviderbase para crear una clase de proveedor de contenido.
- En segundo lugar, necesitas definir la dirección URI de tu proveedor de contenido para acceder al contenido.
- A continuación, necesitas crear una base de datos para guardar el contenido. Normalmente, Android usa la base de datos SQLite, y se sobrescribe el método onCreate() en el framework para usar los métodos de SQLiteOpenHelper y crear o abrir la base de datos del proveedor. Cuando tu aplicación se inicia, el método onCreate() de cada proveedor de contenido se invocará en el hilo principal de la aplicación.
- Finalmente, registra el proveedor de contenido en AndroidManifest.xml usando la etiqueta <provider.../>.
A continuación se presentan algunos métodos que debes sobrescribir en la clase ContentProvider para que tu proveedor de contenido funcione correctamente:

- onCreate(): se llama cuando se inicia el proveedor.
- query(): este método acepta solicitudes del cliente. El resultado es devolver un objeto Cursor (puntero).
- insert(): este método inserta nuevos registros en el proveedor de contenido.
- delete(): este método elimina registros existentes del proveedor de contenido.
- update(): este método actualiza registros existentes en el proveedor de contenido.
- getType(): este método devuelve el tipo de metadatos para una URI dada.
Ejemplo
Este ejemplo explica cómo crear tu propio proveedor de contenido. Sigamos los siguientes pasos:
| Paso | Descripción |
|---|---|
| 1 | Crea una aplicación de Android con Android Studio y asígnala el nombre Content Provider, en el paquete com.ejemplo.contentprovider, y crea una actividad vacía. |
| 2 | Modifica el archivo de actividad principal MainActivity.java para agregar dos nuevos métodos onClickAddName() y onClickRetrieveStudents(). |
| 3 | Crea un nuevo archivo Java StudentsProvider.java en el paquete com.ejemplo.contentprovider para definir el proveedor real y asociar los métodos. |
| 4 | Registra el proveedor de contenido en AndroidManifest.xml usando la etiqueta <provider.../>. |
| 5 | Modifica el contenido predeterminado del archivo res/layout/activity_main.xml para incluir una interfaz simple para agregar registros de estudiantes. |
| 6 | No es necesario modificar strings.xml; Android Studio se encargará del archivo strings.xml. |
| 7 | Inicia el emulador de Android para ejecutar la aplicación y verifica los resultados de los cambios realizados en la aplicación. |
A continuación se muestra el contenido del archivo de actividad principal modificado src/com.ejemplo.contentprovider/MainActivity.java. Este archivo contiene cada método básico del ciclo de vida. Hemos agregado dos nuevos métodos, onClickAddName() y onClickRetrieveStudents(), para que la aplicación maneje la interacción del usuario.
package com.ejemplo.contentprovider;
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;
import com.ejemplo.contentprovider.R;
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.menu_main, menu);
return true;
}
public void onClickAddName(View view) {
// Add a new student record
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) {
// Retrieve student records
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());
}
}
}
Crea el nuevo archivo StudentsProvider.java en el paquete com.ejemplo.contentprovider. A continuación se muestra el contenido de src/com.ejemplo.contentprovider/StudentsProvider.java.
package com.ejemplo.contentprovider;
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);
}
/**
* 数据库特定常量声明
*/
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);";
/**
* 创建和管理提供者内部数据源的帮助类.
*/
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);
/**
* 如果不存在,则创建一个可写的数据库。
*/
db = dbHelper.getWritableDatabase();
return (db == null)? false:true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
/**
* 添加新学生记录
*/
long rowID = db.insert( STUDENTS_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 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 == ""){
/**
* 默认按照学生姓名排序
*/
sortOrder = NAME;
}
Cursor c = qb.query(db, projection, selection, selectionArgs,null, null, sortOrder);
/**
* 注册内容URI变化的监听器
*/
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)){
/**
* 获取所有学生记录
*/
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("Unsupported URI: " + uri);
}
}
}
A continuación se muestra el archivo AndroidManifest.xml modificado. Aquí se ha agregado la etiqueta <provider.../> para incluir nuestro proveedor de contenido:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ejemplo.contentprovider"
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.ejemplo.contentprovider.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>
A continuación se muestra el contenido del archivo res/layout/activity_main.xml:
<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="内容提供者实例"
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="www.example.com"
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/ic_launcher.html"
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="添加"
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="姓名"
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="年级"
android:textColorHint="@android:color/holo_blue_bright" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查询"
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>
Asegúrate de que el archivo res/values/strings.xml tenga el siguiente contenido:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Content Provider</string>
<string name="action_settings">Settings</string>
</resources>
Vamos a ejecutar la aplicación Content Provider que acabamos de modificar. Supongo que ya creaste un AVD al instalar el entorno. Abre el archivo de actividad en tu proyecto y haz clic en el
icono para ejecutar la aplicación en Android Studio. Android Studio instala la aplicación en el AVD y la inicia. Si todo va bien, se mostrará lo siguiente en la ventana del emulador:

Ingresa el nombre y el grado, y haz clic en el botón 'Agregar'. Esto agregará un registro de estudiante en los datos y mostrará un mensaje en la parte inferior. El contenido del mensaje muestra la URI del proveedor de contenido que incluye el número de registros agregados a la base de datos. Esta operación utiliza el método insert(). Repite este proceso para agregar más estudiantes a la base de datos de nuestro proveedor de contenido.

Una vez que hayas terminado de agregar registros a la base de datos, es hora de solicitar al proveedor de contenido que devuelva estos registros. Haz clic en el botón 'Consultar', esto obtendrá y mostrará todos los registros de datos a través del método query() implementado.
Puedes proporcionar métodos de devolución de llamada en MainActivity.java para escribir operaciones de actualización y eliminación, y modificar la interfaz de usuario para agregar operaciones de actualización y eliminación.
Puedes usar proveedores de contenido existentes de esta manera, como los contactos. También puedes usar este método para desarrollar una excelente aplicación orientada a bases de datos. Puedes realizar todas las operaciones de base de datos, como lectura, escritura, actualización y eliminación, como en el ejemplo presentado anteriormente.
Otras extensiones