天天看點

【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續

1. Content Providers 介紹

    Content Providers 管理通路結構化的資料集。它們可以封裝這些資料,并且為定義安全的資料提供機制。Content providers 是标準的接口,它能将一個線程中的資料與其他線程中的運作的代碼進行連接配接。也就是說 Content providers 支援跨應用間的通路。     當你想要在 Content provider[内容提供者] 中通路資料,你要在你的應用程式的 Context 中使用 ContentResolver[内容解析者] 對象與 provider 進行連接配接作為用戶端[通過 Cotext中getContentResolver()方法] 。這個ContentResolver 會與provider對象進行連接配接,provider 它是一個繼承Content provider的類的執行個體。provide這個對象會接受從用戶端請求的資料,執行請求動作,傳回結果。     如果你不打算将自己的資料分享給其他應用程式,就不必要開發自己的 内容提供者[提供者], 然而你想要在你的應用程式中提供自定義的搜尋建議你需要開發内容提供者,如果你想要複制資料或者檔案到其它應用程式,你就需要擁有自己的 provider 。     Android自己提供了一個 content providers 來管理資料,如果 音頻,視訊,或者個人資訊等。你可以在 android.provider 包中檢視,這些提供者可以給任何應用程式所通路。     Calendar Provider :http://developer.android.com/guide/topics/providers/calendar-provider.html 

    Contacts Provider :http://developer.android.com/guide/topics/providers/contacts-provider.html

2. 如何建立一個内容提供者

1) 什麼時候決定建立一個内容提供者呢?

1). 你需要提供完整的資料和檔案給其他的應用程式. 2). 你想要允許使用者從你的應用程式複制完整的資料到其他的應用程式 3). 你想要使用搜尋架構來提供自定義的搜尋建議。

2) 建立内容提供者需要掌握的知識

1). 自定義一個内容提供者供别的應用程式去通路,Content provider 是基于資料庫的,它在外層封裝了有一套非正常範的增删查改的操作資料庫的對外接口,如下圖所示
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續

2). 檢視API文檔中 ContentProvider 類的說明,可以發現這是一個抽象類  

   Content providers是Android應用程式中主要的組成部分之一,為應用程式提供對外的内容,它封裝了資料然後提供給那些通過簡單 ContentResolver

接口的應用程式,這些資料是可以跨應用通路的。    對與解析者來說,它會通過 ContentResolver 會發出一個請求,這個請求是否通過是通過去檢查由系統給予的 URI 的授權,授權是由 content provider 注冊的,在AndroidMainifest.xml 中去注冊這種授權。UriMatcher 類可以幫助解析 URI.[也就是通過這個類去比對URI,詳細參考第5點 ]

   在這裡為什麼要注冊 URI授權呢?

   原因是一個應用程式中可以有多個 content provider, 但是每一個content provider 的授權都是唯一的,是以内容解析者可以通過這種URL的授權來找到自己想要的 content provider. 這裡的 URI 的授權也可以了解為content provider對外的一個通路的路徑。

3). 注意定義好一個内容提供者之後需要在 AndroidMainifest.xml 中注冊

   android:authorities 授權屬性,表示外部應用程式通路目前内容提供者的标示符,它是自定義的,一般我們是以 "包名 + 類名" 的形式來定義的。有些人可以了解為 URI 路徑

4). 檢視 UriMatcher 類的概要描述  

   這是一個在 content provider 中幫助比對  URIs 的實用類。

   檢視 public void addURI (String authority, String path, int code)方法

   這個方法是用來表示在  content provider 裡面添加外部對其的比對的規則,當 URI 被比對的時候,就會傳回 code 碼, URI節點可以精确的比對字元串, "*" 号比對任何的字元串 "#" 号隻能比對數字。[比如删除一條記錄中 ID 往往是數字]

   參數說明
   authority : 授權, 就是 AndroidMainifest.xml 中的授權    path : 比對路徑(通常是一個表名)[* 可以作為比對任意字元的通配符, # 可以作為比對數字的通配符]。【注意這裡如果是單條記錄,需要添加 /# 标示符】

5). 檢視 content provider 中的 public abstract String getType (Uri uri) 方法

    根據給定的 URI 來實作處理 MIME類型的請求, 對于單條記錄傳回的 MIME 類型是以 

vnd.android.cursor.item 開始的, 對于多條記錄傳回的MIME類型是以vnd.android.cursor.dir/ 開始的. 這個方法可以在多線程環境下被調用。 詳細參考Processes and Threads.

6). 檢視 content provider 中 public abstract Uri insert (Uri uri, ContentValues values) 方法

    實作這個方法來處理插入一個新行的請求, 在插入後可以友好的調用 notifyChange() 方法。

    參數說明: 
uri : 這種格式 "content:// URI" 的插入請求[後續會認真的講解這一部分内容]。 此處不為空 values : 添加到資料庫的 ContentValues 類型集合。部落格前面章節講過次内容,詳情可以去參考。此處不為空
傳回

   新插入選項的URI,可以給其他使用者去使用。

7). 删除操作     檢視 content provider 的 public abstract int delete (Uri uri, String selection, String[] selectionArgs) 方法     實作這個方法主要是用來處理删除一行或者多行的請求操作,實作這個删除操作需要 有 selection 語句,你可以在删除之後調用 notifyDelete() 方法來做友好的提示     實作這個方法還需要在 URI的末尾解析出行的ID,如果一個指定的行被删除之後,例如,用戶端在建立SQL語句的時候會通過 content://contacts/people/22 的這個URI,來解析出末尾的ID号,确定删除ID為 22 的這個記錄。

參數說明:

URI : 完整的URI路徑,包括行ID selection : 可選項,在删除行的時候适用
傳回 :
影響資料庫的行數

3. 代碼實作

1) actvity_main.xml 程式界面的布局檔案, 這裡隻是定義了5個按鈕,不貼出來了

2) DBOpenHelper.java 主要是用來做資料庫的建立使用

package com.android.contentproviderdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBOpenHelper extends SQLiteOpenHelper {

    private static final String TAG = "DBOpenHelper";
    private static String name = "mydb.db";
    private static int version = 1; // 初始版本号是一

    public DBOpenHelper(Context context) {
        super(context, name, null, version);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void onCreate(SQLiteDatabase database) {
        // TODO Auto-generated method stub
        String sql = "create table student (id integer primary key autoincrement, name varchar(64), address varchar(64))";
        database.execSQL(sql); //對資料庫的表的建立
        Log.i(TAG, "ahuier--> SQLite create succeed!");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub

    }

}
           
3) StudentProvider.java 也就是本例子中最重要的代碼,實作 Content Provider中增删查改的操作
package com.android.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

public class StudentProvider extends ContentProvider {

    private final String TAG = "StudentProvider";
    private DBOpenHelper helper = null;

    private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    /*
     * 這裡為何要做這種 操作單條記錄 或者 操作多條記錄的标志位呢?
     * 原因是因為 外部程式 操作 ContentProvider 的方法,僅僅隻能通過一個 URI 來通路,
     * 是以要定義兩個标志位來識别外部需要操作的是單條記錄還是多條記錄 (比如删除單條記錄或者删除多條記錄)
     */
    private static final int STUDENT = 1; // 操作單條記錄
    private static final int STUDENTS = 2; // 操作多條記錄
    // 添加對外部的比對規則
    static {
        URI_MATCHER.addURI("com.android.contentproviderdemo.StudentProvider",
                "student", STUDENTS);
        URI_MATCHER.addURI("com.android.contentproviderdemo.StudentProvider",
                "student/#", STUDENT);
    }

    public StudentProvider() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean onCreate() {
        // 初始化的時候執行個體化 helper 對象
        helper = new DBOpenHelper(getContext());
        return true;
    }

    // query() 方法傳回的是一個 Cursor 的遊标,詳細方法參考 Android API文檔
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        Cursor cursor = null;
        try {
            SQLiteDatabase databse = helper.getReadableDatabase();
            int flag = URI_MATCHER.match(uri);
            switch (flag) {
                case STUDENT:
                    long id = ContentUris.parseId(uri);
                    String where_value = " id = " + id;
                    if (selection != null && !selection.equals("")) {
                        where_value += " and " + selection;
                    }
                    // 這邊具體查詢方式如果有不懂,可以參考前面即将 SQLite的查詢。
                    cursor = databse.query("student", null, where_value, selectionArgs, null, null,
                            null, null);
                    break;

                case STUDENTS:
                    cursor = databse.query("student", null, selection, selectionArgs, null, null, 
                            null, null);
                    break;
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        return cursor;
    }

    /*
     * 它的作用是根據URI傳回該URI所對應的資料的MIME類型字元串。
     * 這個MIME類型字元串的作用是要比對AndroidManifest.xml檔案
     * <activity>标簽下<intent-filter>标簽的子标簽<data>的屬性 android:mimeType。
     * 如果不一緻,則會導緻對應的Activity無法啟動。
     */
    @Override
    public String getType(Uri uri) {
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case STUDENT:
                return "vnd.android.cursor.item";
            case STUDENTS:
                return "vnd.android.cursor.dir/";
        }
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Uri resultUri = null;
        /*
         * URI_MATCHER.match(uri) 上面已經在定義了比對規則,是以這裡是用外部傳過來的URI比對内部定義好的規則,
         * 如果比對成功則進行操作,反之不進行操作。
         */
        int flag = URI_MATCHER.match(uri);
        switch (flag) {
            case STUDENTS:
                SQLiteDatabase database = helper.getWritableDatabase();
                // 注意這邊我們使用的是資料庫的 database.insert()方法,是以要用 withAppendedId()
                // 這種方式來傳回URI
                long id = database.insert("student", null, values); // 插入目前行的行号
                resultUri = ContentUris.withAppendedId(uri, id);
                break;
        }
        Log.i(TAG, "ahuier----->" + resultUri.toString());
        // 傳回新插入選項的URI,可以給其他使用者去使用
        return resultUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = -1; // count作為影響資料庫的行數
        try {
            int flag = URI_MATCHER.match(uri);
            SQLiteDatabase database = helper.getWritableDatabase();
            switch (flag) {
                case STUDENT:
                    /*
                     * 傳遞過來的URI的格式:content://com.android.contentproviderdemo.StudentProvider/student/1
                     * 以下代碼其實就是用來建構 SQL 中删除單挑記錄的語句
                     * delete from student where id = ? // id 通過用戶端傳遞過來的。
                     */
                    long id = ContentUris.parseId(uri); // 解析出 URI 末尾的ID号
                    String where_value = " id = " + id;
                    if (selection != null && !selection.equals("")) {
                        where_value += " and " + selection;
                    }
                    // 注意這邊的count的是SQLite中的delete()方法,傳回的是一個影響資料庫的數目
                    count = database.delete("student", where_value, selectionArgs);
                    break;

                case STUDENTS:
                    // 傳遞過來的 URI格式:content://com.android.contentproviderdemo.StudentProvider/student
                    // 删除多條記錄
                    count = database.delete("studuent", selection, selectionArgs);
                    break;
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        return count;
    }

    // 更新的方法與删除方法類似,讀者可以自己去查 Android官方文檔
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        int count = -1;
        try {
            // 更新資料庫的語句 : update table set name = ?, address = ? where id = ?
            SQLiteDatabase database = helper.getWritableDatabase();
            long id = ContentUris.parseId(uri);
            int flag = URI_MATCHER.match(uri);
            switch (flag) {
                case STUDENT:
                    String where_value = " id = " + id;
                    if (selection != null && !selection.equals("")) {
                        where_value += " and " + selection;
                    }
                    count = database.update("student", values, where_value, selectionArgs);
                    break;

                case STUDENTS:
                    // TODO 這裡一般情況下, 不會去更新全部表格
                    break;
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        return count;
    }

}
           
4) MainActivity.java 界面程式代碼,主要是作為 Content Resolver 代碼解析者。跨應用操作資料庫也可以用這個代碼來使用的
package com.android.contentproviderdemo;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";
    private Button button1, button2, button3, button4, button5;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initComponent();
        // 建立資料庫
        button1.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                DBOpenHelper helper = new DBOpenHelper(MainActivity.this);
                // 調用 getWritableDatabase()或者 getReadableDatabase()其中一個方法将資料庫建立  
                helper.getWritableDatabase();
            }
        });
        
        // 插入資料
        button2.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                MainActivity.this.insert();
            }
        });
        
        // 删除資料
        button3.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                MainActivity.this.delete();
            }
        });
        
        // 修改資料
        button4.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                MainActivity.this.update();
            }
        });
        
        // 查詢資料
        button5.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                MainActivity.this.query();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
    private void initComponent(){
        button1 = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);
        button3 = (Button) findViewById(R.id.button3);
        button4 = (Button) findViewById(R.id.button4); 
        button5 = (Button) findViewById(R.id.button5);
    }
    
    private void insert(){
        // 通路内容提供者的步驟:
        // 1. 需要一個内容解析者
        ContentResolver contentResolver = MainActivity.this.getContentResolver();
        /*
         * URI的構成
         * 它是通過 Uri.parse("content://")方法來構成的
         * 構成結構是 :"content://" + "授權路徑"  + "/" + "辨別符 "
         */
        Uri uri = Uri.parse("content://com.android.contentproviderdemo.StudentProvider/student");
        ContentValues values = new ContentValues();
        values.put("name", "AHuier");
        values.put("address", "XIAMEN");
        contentResolver.insert(uri, values);
    }   
    
    private void delete() {
        ContentResolver contentResolver = MainActivity.this.getContentResolver();
        // 删除單條記錄,如果要删除多行記錄 :content://com.android.contentproviderdemo.StudentProvider/student
        /*
         * 1 表示目前删除 id = 1的這條記錄,這個 id = 1 會傳遞到 StudentProvider 中去比對URI規則後解析出 ID ,然後對其進行執行SQL的語句
         */
        Uri uri = Uri.
                parse("content://com.android.contentproviderdemo.StudentProvider/student/1");
        contentResolver.delete(uri, null, null);
    }
    
    private void update() {
        ContentResolver contentResolver = MainActivity.this.getContentResolver();
        Uri uri = Uri.
                parse("content://com.android.contentproviderdemo.StudentProvider/student/2");
        ContentValues values = new ContentValues();
        values.put("name", "HUI");
        values.put("address", "Beijing");
        contentResolver.update(uri, values, null, null);
    }
    
    //查詢的結果是一個遊标,也就是傳回的查詢記錄,查詢可能傳回一條記錄,也可能傳回多條記錄
    private void query() {
        ContentResolver contentResolver = MainActivity.this.getContentResolver();
        // 查詢單條記錄 : content://com.android.contentproviderdemo.StudentProvider/student/2
        // 查詢多條記錄 : content://com.android.contentproviderdemo.StudentProvider/student
        Uri uri = Uri.parse("content://com.android.contentproviderdemo.StudentProvider/student/2");
        // select * from student where id = 2;
        Cursor cursor = contentResolver.query(uri, null, null, null, null);
        while(cursor.moveToNext()){
            Log.i(TAG, "ahuier---->" + cursor.getString(cursor.getColumnIndex("name")));
        }
    }
}
           

4. 程式實作結果

1). 程式主界面
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續
2). 往資料插入3個資料
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續
3). 删除 id = 1 的資料
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續
4). 修改 id = 2 的資料
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續
3). 查詢 id = 2 的名稱
【Android 開發】:Content Provider (内容提供者) 詳解1. Content Providers 介紹2. 如何建立一個内容提供者3. 代碼實作4. 程式實作結果5. 後續

5. 後續

  如何來證明其實跨應用進行通信呢?

另外再寫一個應用,操作資料庫的方式與上述代碼的中的 MainActivity.java 方式類似,也是作為 Content Resolver 解析者的使用

詳情參考 : http://developer.android.com/guide/topics/providers/content-providers.html

源代碼下載下傳 : http://download.csdn.net/my/uploads