Latest web development tutorials

Android 內容提供者(Content Provider)

內容提供者組件通過請求從一個應用程序向其他的應用程序提供數據。 這些請求由類ContentResolver 的方法來處理。 內容提供者可以使用不同的方式來存儲數據。 數據可以被存放在數據庫,文件,甚至是網絡。

圖片

有時候需要在應用程序之間共享數據。 這時內容提供者變得非常有用。

內容提供者可以讓內容集中,必要時可以有多個不同的應用程序來訪問。 內容提供者的行為和數據庫很像。 你可以查詢,編輯它的內容,使用insert(), update(), delete() 和query() 來添加或者刪除內容。 多數情況下數據被存儲在SQLite 數據庫。

內容提供者被實現為類ContentProvider 類的子類。 需要實現一系列標準的API,以便其他的應用程序來執行事務。

public class MyApplication extends  ContentProvider {

}

內容URI

要查詢內容提供者,你需要以如下格式的URI的形式來指定查詢字符串:

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

以下是URI中各部分的具體說明:

部分 說明
prefix 前綴:一直被設置為content://
authority 授權:指定內容提供者的名稱,例如聯繫人,瀏覽器等。 第三方的內容提供者可以是全名,如:cn.programmer.statusprovider
data_type 數據類型:這個表明這個特殊的內容提供者中的數據的類型。 例如:你要通過內容提供者Contacts來獲取所有的通訊錄,數據路徑是people,那麼URI將是下面這樣:content://contacts/people
id 這個指定特定的請求記錄。 例如:你在內容提供者Contacts中查找聯繫人的ID號為5,那麼URI看起來是這樣:content://contacts/people/5

創建內容提供者

這裡描述創建自己的內容提供者的簡單步驟。

  • 首先,你需要繼承類ContentProviderbase 來創建一個內容提供者類。
  • 其次,你需要定義用於訪問內容的你的內容提供者URI地址。
  • 接下來,你需要創建數據庫來保存內容。 通常,Android 使用SQLite 數據庫,並在框架中重寫onCreate() 方法來使用SQLiteOpenHelper 的方法創建或者打開提供者的數據庫。 當你的應用程序被啟動,它的每個內容提供者的onCreate() 方法將在應用程序主線程中被調用。
  • 最後,使用<provider.../>標籤在AndroidManifest.xml 中註冊內容提供者。

以下是讓你的內容提供者正常工作,你需要在類ContentProvider 中重寫的一些方法:

圖片

  • onCreate():當提供者被啟動時調用。
  • query():該方法從客戶端接受請求。 結果是返回指針(Cursor)對象。
  • insert():該方法向內容提供者插入新的記錄。
  • delete():該方法從內容提供者中刪除已存在的記錄。
  • update():該方法更新內容提供者中已存在的記錄。
  • getType():該方法為給定的URI返回元數據類型。

實例

該實例解釋如何創建自己的內容提供者。 讓我們按照下面的步驟:

步驟 描述
1 使用Android Studio 創建Android 應用程序並命名為Content Provider,在包cn.uprogrammer.contentprovider 下,並建立空活動。
2 修改主要活動文件MainActivity.java 來添加兩個新的方法onClickAddName() 和onClickRetrieveStudents()。
3 在包cn.uprogrammer.contentprovider 下創建新的Java 文件StudentsProvider.java 來定義實際的提供者,並關聯方法。
4 使用<provider.../>標籤在AndroidManifest.xml 中註冊內容提供者。
5 修改res/layout/activity_main.xml 文件的默認內容來包含添加學生記錄的簡單界面。
6 無需修改strings.xml,Android Studio 會注意strings.xml 文件。
7 啟動Android 模擬器來運行應用程序,並驗證應用程序所做改變的結果。

下面是修改的主要活動文件src/cn.uprogrammer.contentprovider/MainActivity.java 的內容。 該文件包含每個基礎的生命週期方法。 我們添加了兩個新的方法,onClickAddName() 和onClickRetrieveStudents() 來讓應用程序處理用戶交互。

package cn.uprogrammer.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 cn.uprogrammer.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());
        }
    }
}

在包cn.uprogrammer.contentprovider下創建新的文件StudentsProvider.java。 以下是src/cn.uprogrammer.contentprovider/StudentsProvider.java的內容。

package cn.uprogrammer.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);
        }
    }
}

以下是修改後的AndroidManifest.xml文件。 這裡添加了<provider.../>標籤來包含我們的內容提供者:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.uprogrammer.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="cn.uprogrammer.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>

下面是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.uprogrammer.cn"
        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"
        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>

確保res/values/strings.xml文件中有以下內容:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Content Provider</string>
    <string name="action_settings">Settings</string>

</resources>

讓我們運行剛剛修改的Content Provider 應用程序。 我假設你已經在安裝環境時創建了AVD。 打開你的項目中的活動文件,點擊工具欄中的 圖片 圖標來在Android Studio 中運行應用程序。 Android Studio 在AVD 上安裝應用程序並啟動它。 如果一切順利,將在模擬器窗口上顯示如下:

圖片

輸入姓名和年級,並點擊"添加"按鈕,這將在數據中添加一條學生記錄,並在底部刪除一條信息。 信息內容顯示包含添加進數據庫的記錄數的內容提供者URI。 這個操作使用了insert()方法。 重複這個過程在我們的內容提供者的數據庫中添加更多的學生。

圖片

一旦你完成數據庫記錄的添加,是時候向內容提供者要求給回這些記錄。 點擊"查詢"按鈕,這將通過實現的query() 方法來獲取並顯示所有的數據記錄。

你可以在MainActivity.java 中提供回調方法,來編寫更新和刪除的操作,並修改用戶界面來添加更新和刪除操作。

你可以通過這種方式使用已有的內容提供者,如通訊錄。 你也可以通過這種方式來開發一個優秀的面向數據庫的應用,你可以像上面介紹的實例那樣來執行素有的數據庫操作,如讀、寫、更新和刪除。