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