1. 程式人生 > >安卓開發學習之ContentProvider的使用

安卓開發學習之ContentProvider的使用

背景

這幾天在學習安卓程序間通訊,而做為安卓四大元件之一的ContentProvider(內容提供者),也可以實現IPC。

現在記錄一下使用步驟

步驟

1、建立DatabaseOpenHelper

內容提供者的工作方式就和資料庫操作是一樣的,增刪改查,所以我們要先建立一個幫助類來建立資料庫,程式碼如下

public class MyDbHelper extends SQLiteOpenHelper {
    public static final String DB_NAME = "peopleDatabase.db";
    public static final String TABLE_NAME = "person";
    public static final int VERSION = 1;

    public MyDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        this(context);
    }

    public MyDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        this(context);
    }

    public MyDbHelper(Context context) {
        super(context, DB_NAME, null, VERSION, null);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "create table if not exists " + TABLE_NAME + "(id int(3) not null, name varchar(20), description varchar(50), " +
                                "constraint PK_PERSON primary key(id))";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

2、建立自己的內容提供者

程式碼如下

public class PersonProvider extends ContentProvider {
    private static final String TAG = "PersonProvider";
    private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); // uri匹配者
    public static final String AUTH = "com.example.songzeceng.PersonProvider"; // 內容提供者的id
    public static final Uri PERSON_URI = Uri.parse("content://" + AUTH + "/person");
    public static final String TYPE_MORE = "vnd.android.cursor.dir/person"; // 多條查詢的type
    public static final String TYPE_SINGAL = "vnd.android.cursor.item/person"; // 單條查詢的type
    public static final int CODE_SINGAL = 0; // 單條查詢的匹配碼
    public static final int CODE_MORE = 1; // 多條查詢的匹配碼

    public static String TABLE_NAME = "person";

    private SQLiteDatabase mDatabase;
    private Context mContext;

    static {
        MATCHER.addURI(AUTH, "person", CODE_MORE); // 多條查詢
        MATCHER.addURI(AUTH, "person/#", CODE_SINGAL); // 單條查詢,#是數字萬用字元,理解為id
    }

    @Override
    public boolean onCreate() {
        mContext = getContext();
        mDatabase = new MyDbHelper(mContext).getWritableDatabase(); // 獲取可寫資料庫,可寫自然可讀
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 引數列表:表的uri、要查詢的列、where子句、where子句的引數、排序語句
        System.out.println("query uri:"+uri.toString());
        String type = getType(uri);
        if (isTypeValid(type)) {
            Cursor cursor = mDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, sortOrder, null); // 兩個null分別是groupBy和orderBy
            return cursor; // 返回的是遊標
        }
        return null;
    }

    private boolean isTypeValid(String type) {
        return type != null && (TYPE_MORE.equals(type) || TYPE_SINGAL.equals(type));
    }

    @Override
    public String getType(Uri uri) {
        int code = MATCHER.match(uri);
        switch (code) {
            case CODE_SINGAL:
                return TYPE_SINGAL;
            case CODE_MORE:
                return TYPE_MORE;
        }
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        System.out.println("insert uri:"+uri.toString() + "--contentValues:"+values.toString());
        if (isTypeValid(getType(uri))) {
            mDatabase.insert(TABLE_NAME, null, values); // null是nullColumnHack,用於插入空行(也就是ContentValues內容是空)
            mContext.getContentResolver().notifyChange(uri, null); // null是observer,觀察者
        }
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) { // 引數列表:uri、where子句、where引數
        System.out.println("delete uri:"+uri.toString());
        if (isTypeValid(getType(uri))) {
            int count = mDatabase.delete(TABLE_NAME, selection, selectionArgs);
            if (count > 0) {
                mContext.getContentResolver().notifyChange(uri ,null);
            }
        }
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        System.out.println("update uri:"+uri.toString() + "--contentValues:"+values.toString());
        if (isTypeValid(getType(uri))) {
            int count = mDatabase.update(TABLE_NAME, values, selection, selectionArgs);
            if (count > 0) {
                mContext.getContentResolver().notifyChange(uri, null);
            }
        }
        return 0;
    }
}

可以看到,增加和更改的內容是放在ContentValues裡面,這個相當於一個雜湊對映

selection是where子句,selectionArgs則是引數,如果selection是"where id = ? and name = ? ",selectionArgs是[1,"szc"]的話,生成的完整的where子句就是"where id = 1 and name = \"szc\""

不過我直接把引數拼接到selection裡了,所以selectionArgs就成了擺設

另外增加方法的返回值是表的url,修改和刪除的返回值是影響的行數,感覺沒啥大用..

3、清單檔案中註冊

        <provider
            android:name=".PersonProvider" <!--provider類名-->
            android:authorities="com.example.songzeceng.PersonProvider" <!--provider的id-->
            android:exported="true"  <!--是否允許別的程序訪問-->
            android:grantUriPermissions="true" <!--是否允許解析uri-->
            android:process=":provider"> <!--provider所屬程序名-->

        </provider>

4、在客戶端進行CRUD檢驗

程式碼如下

public class MainActivity extends Activity {
    public static final String TAG = "MainActivity";

    private void insertData(Uri providerUrl, int id, String name, String description) {
        ContentValues values = new ContentValues(); 
        values.put("id", id); // 鍵值對
        values.put("name", name);
        values.put("description", description);
        getContentResolver().insert(providerUrl, values); // 必須根據ContentResolver訪問內容提供者
    }

    private void updateData(Uri providerUrl, int id, String name, String description) {
        ContentValues values = new ContentValues();
        values.put("id", id);
        values.put("name", name);
        values.put("description", description);
        getContentResolver().update(providerUrl, values, "id = " + id, null); // 第二個引數是where子句 ,null是where子句引數
    }

    private void deleteData(Uri providerUrl, int id) {
        getContentResolver().delete(providerUrl, "id = " + id, null); // 第二個引數是where子句,null則是其引數
    }

    private void queryData(Uri providerUrl) {
        Cursor cursor = getContentResolver().query(providerUrl, null, null, null, null); // 查詢所有記錄
        while (cursor.moveToNext()) { // 只要遊標沒到底
            int id = cursor.getInt(cursor.getColumnIndex("id")); // getColumnIndex()根據列名獲取列的位置,而後getInt()根據列的位置獲取列的值
            String userName = cursor.getString(cursor.getColumnIndex("name"));
            String description = cursor.getString(cursor.getColumnIndex("description"));

            System.out.println(id + "--" + userName + "--" + description);
        }

        cursor.close(); // 莫忘記關遊標
        System.out.println("----------------------------------");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            Uri providerUrl = PersonProvider.PERSON_URI;

            insertData(providerUrl, 1, "szc", "a simple boy");
            insertData(providerUrl, 2, "jason", "an interesting boy");

            queryData(providerUrl);

            updateData(providerUrl, 2, "dustin", "a brave boy");
            queryData(providerUrl);

            deleteData(providerUrl, 2);
            queryData(providerUrl);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

5、檢視結果

客戶端截圖


服務(provider)端截圖


程序號不同,說明實現了程序間通訊

結語

受限於水平和精力,沒有怎麼鑽研內容提供者的原始碼,大致看了看原始碼和這篇文章,發現客戶端contentResolver的insert()方法最終呼叫了IContentProvider的insert()方法,而IContentProvider介面繼承了IInterface,似乎也是AIDL在幕後操縱,具體我就沒去看了