Android四大元件之ContentProvider

Tybyq發表於2019-08-26

前言

Hi,大家好,我們又雙叒叕見面啦,為了讓大家快速的學習Android知識,我們每天都在更新文章,相信小夥伴們已經開始眼熟我們了!這一期我們講解ContentProvider(內容提供者)相關知識,他也是我們近期更新的Android四大元件中最後一個。話不多說,讓我們趕緊開始學習吧~

簡介

ContentProvider是Android系統中為開發者專門提供的不同應用間進行資料共享的元件,其提供了一套標準的介面用來獲取以及運算元據,准許開發者把自己的應用資料根據需求開放給其他應用進行增刪改查,而無須擔心直接開放資料庫許可權而帶來的安全問題。系統預置了許多ContentProvider用於獲取使用者資料,例如訊息、聯絡人、日程表等。

具體形式如下圖所示:

使用方式

1 ContentResolver

在ContentProvider的使用過程中,需要借用ContentResolver來控制ContentProvider所暴露處理的介面,作為代理來間接操作ContentProvider以獲取資料。

在 Context.java 的原始碼中如下抽象方法

/** Return a ContentResolver instance for your application's package. */
    public abstract ContentResolver getContentResolver();

所以可以在所有繼承Context的類中透過 getContentResovler() 方法獲取ContentResolver

ContentResolver contentResolver = getContentResovler();
2 ContentProvider

ContentProvider作為Android四大元件之一,並沒有Activity那樣複雜的生命週期,只有簡單地onCreate過程。

建立一個自定義ContentProvider的方式是繼承ContentProvider類並實現其六個抽象方法:

/** 
* @author: 下碼看花 
* date: 2019/8/8 
* description: ContentProvider例子 
*/public class MyContentProvider extends ContentProvider {	/**     
	* 執行初始化工作     
	* @return     
	*/    
	@Override    
	public boolean onCreate() {      	return false;    
	}    
	/**     
	* 查詢資料     
	* @param uri     
	* @param projection     
	* @param selection     
	* @param selectionArgs     
	* @param sortOrder     
	* @return     
	*/    
	@Override    
	public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String 		selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {      	return null;    
	}    
	/**     
	* 透過Uri返回對應的MIME型別     
	* @param uri     
	* @return     
	*/    
	@Override    
	public String getType(@NonNull Uri uri) {      	return null;    
	}    
	/**     
	* 插入新資料     
	* @param uri     
	* @param values     
	* @return     
	*/    
	@Override    
	public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {		return null;   
	} 
	
	/**     
	* 刪除已有資料    
	* @param uri     
	* @param selection     
	* @param selectionArgs     
	* @return     
	*/   
	@Override    
	public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] 		selectionArgs) {		return 0;    
	}	/**     
	* 更新資料     
	* @param uri     
	* @param values     
	* @param selection     
	* @param selectionArgs     
	* @return     
	*/    
	@Override    
	public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String 		selection, @Nullable String[] selectionArgs) {		return 0;    
	}
}

之後,需要在AdnroidManifest.xml中對ContentProvider進行註冊

<!-- xxx為上一層包名 --><provider
            android:name=".MyContentProvider"
            android:authorities="com.xmkh.MyContentProvider"
            android:exported="true"/>

name:自定義的ContentProvider的全稱類名。

authorities:自定義ContentProvider的唯一標識,外部應用透過該屬性值來訪問我們的ContentProvider。因此該屬性值必須是唯一的,建議在命名時以包名為字首。

exported:表明是否允許其他應用呼叫ContentProvider,true表示支援,false表示不支援。預設值根據開發者的屬性設定而會有所不同,如果包含 Intent-Filter 則預設值為true,否則為false。

3 Uri

觀察MyContentProvider中的幾個方法,可以發現除了 onCreate() 方法外,其它五個抽象方法都包含了一個Uri(統一資源識別符號)引數,透過這個物件可以來匹配對應的請求。那麼從ContentProvider的資料操作方法可以看出都依賴於Uri,而對於Uri有其固定的資料格式,例如:

比如,ContentProvider中操作的資料可以都是從SQLite資料庫中獲取的,而資料庫中可能存在許多張表,這時候就需要用到Uri來表明是要操作哪個資料庫、運算元據庫的哪張表了

/** 
* @author: 下碼看花 
* date: 2019/8/8 
* description: URI組裝程式碼示例
*/public class TestContract {    public static final String CONTENT_AUTHORITY = "com.xmkh.contentproviderdemo.MyContentProvider";    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);    public static final String PATH_ARTICLE = "article";    public static final class TestEntry implements BaseColumns {        public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_ARTICLE).build();        protected static Uri buildUri(long id) {            return ContentUris.withAppendedId(CONTENT_URI, id);
        }        protected static final String TABLE_NAME = "article";        public static final String COLUMN_NAME = "name";
    }
}

從上面程式碼我們可以看到,我們建立了一個  content://com.xmkh.contentproviderdemo.MyContentProvider/article的uri,並且開了一個靜態方法,用以在有新資料產生時根據id生成新的Uri。

那麼如何確定一個Uri是要執行哪項操作呢?這裡需要用到UriMatcher來幫助ContentProvider匹配Uri,它僅包含了兩個方法:

addURI(String authority, String path, int code):用於向UriMatcher物件註冊Uri。authtity是在AndroidManifest.xml中註冊的ContentProvider的authority屬性值;path表示一個路徑,可以設定為萬用字元,#表示任意數字,*表示任意字元;兩者組合成一個Uri,而code則代表該Uri對應的標識碼

match(Uri uri):匹配傳入的Uri。返回addURI()方法中傳遞的code引數,如果找不到匹配的標識碼則返回-1

實戰

講了這麼多,相信大家已經有一個初步的瞭解,為了讓我們加深記憶,跟我一起寫一個demo吧!

首先,自定義一個ContentProvider,然後向其寫入和讀取資料,使用SQLite作為ContentProvider的資料儲存地址和資料來源,因此需要先建立一個SQLiteOpenHelper,建立一個名為"article.db"的資料庫,包含“article”和“author”兩張表:

/** 
* @author: 下碼看花 
* date: 2019/8/8 
* description: 演示demo資料庫
*/public class DbOpenHelper extends SQLiteOpenHelper {    
	/**
     * 資料庫名
     */
    private static final String DATA_BASE_NAME = "article.db";    /**
     * 資料庫版本號
     */
    private static final int DATE_BASE_VERSION = 1;    /**
     * 表名-文章
     */
    public static final String ARTICLE_TABLE_NAME = "article";    /**
     * 表名-作者
     */
    public static final String AUTHOR_TABLE_NAME = "author";    /**
     * 建立表-文章(兩列:主鍵自增長、文章名稱)
     */
    private final String CREATE_ARTICLE_TABLE = "create table " + ARTICLE_TABLE_NAME
            + "(_id integer primary key autoincrement, articleName text)";    /**
     * 建立表-作者(三列:主鍵自增長、作者名、性別)
     */
    private final String CREATE_AUTHOR_TABLE = "create table " + AUTHOR_TABLE_NAME
            + "(_id integer primary key autoincrement, authorName text, sex text)";    public DbOpenHelper(Context context) {        super(context, DATA_BASE_NAME, null, DATE_BASE_VERSION);
    }    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_ARTICLE_TABLE);
        db.execSQL(CREATE_AUTHOR_TABLE);
    }    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

自定義ContentProvider 當中,getTableName(Uri uri)方法用於判定Uri指向的資料庫表名 然後在initProviderData()方法中向資料庫插入一些原始資料

為了方便大家理解,我們將上述出現的程式碼進行修改,展示給大家:

/** 
* @author: 下碼看花 
* date: 2019/8/8 
* description: ContentProvider例子 
*/public class MyContentProvider extends ContentProvider {    
    private Context mContext;    private SQLiteDatabase sqLiteDatabase;    public static final String AUTHORITY = "com.xmkh.MyContentProvider";    public static final int ARTICLE_URI_CODE = 0;    public static final int AUTHOR_URI_CODE = 1;    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);    static {
        uriMatcher.addURI(AUTHORITY, DbOpenHelper.ARTICLE_TABLE_NAME, ARTICLE_URI_CODE);
        uriMatcher.addURI(AUTHORITY, DbOpenHelper.AUTHOR_TABLE_NAME, AUTHOR_URI_CODE);
    }    private String getTableName(Uri uri) {
        String tableName = null;        switch (uriMatcher.match(uri)) {            case ARTICLE_URI_CODE:
                tableName = DbOpenHelper.ARTICLE_TABLE_NAME;                break;            case AUTHOR_URI_CODE:
                tableName = DbOpenHelper.AUTHOR_TABLE_NAME;                break;
        }        return tableName;
    }    public MyContentProvider() {
    }    /**
     * 執行初始化工作
     *
     * @return
     */
    @Override
    public boolean onCreate() {
        mContext = getContext();
        initArticleProviderData();        return false;
    }    //初始化原始資料
    private void initArticleProviderData() {
        sqLiteDatabase = new DbOpenHelper(mContext).getWritableDatabase();
        sqLiteDatabase.beginTransaction();
        ContentValues contentValues = new ContentValues();
        contentValues.put("articleName", "Android四大元件之Activity");
        sqLiteDatabase.insert(DbOpenHelper.ARTICLE_TABLE_NAME, null, contentValues);
        contentValues.put("articleName", "Android四大元件之BroadcastReceiver");
        sqLiteDatabase.insert(DbOpenHelper.ARTICLE_TABLE_NAME, null, contentValues);
        contentValues.put("articleName", "Android四大元件之Service");
        sqLiteDatabase.insert(DbOpenHelper.ARTICLE_TABLE_NAME, null, contentValues);
        contentValues.clear();
        contentValues.put("authorName", "ptt");
        contentValues.put("sex", "女");
        sqLiteDatabase.insert(DbOpenHelper.AUTHOR_TABLE_NAME, null, contentValues);
        contentValues.put("authorName", "HiYoung");
        contentValues.put("sex", "男");
        sqLiteDatabase.insert(DbOpenHelper.AUTHOR_TABLE_NAME, null, contentValues);
        contentValues.put("authorName", "gy");
        contentValues.put("sex", "男");
        sqLiteDatabase.insert(DbOpenHelper.AUTHOR_TABLE_NAME, null, contentValues);
        sqLiteDatabase.setTransactionSuccessful();
        sqLiteDatabase.endTransaction();
    }    /**
     * 查詢資料
     *
     * @param uri
     * @param projection
     * @param selection
     * @param selectionArgs
     * @param sortOrder
     * @return
     */
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        String tableName = getTableName(uri);        if (tableName == null) {            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }        return sqLiteDatabase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
    }    /**
     * 透過Uri返回對應的MIME型別
     *
     * @param uri
     * @return
     */
    @Override
    public String getType(@NonNull Uri uri) {        return null;
    }    /**
     * 插入新資料
     *
     * @param uri
     * @param values
     * @return
     */
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        String tableName = getTableName(uri);        if (tableName == null) {            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        sqLiteDatabase.insert(tableName, null, values);
        mContext.getContentResolver().notifyChange(uri, null);        return uri;
    }    /**
     * 刪除已有資料
     *
     * @param uri
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        String tableName = getTableName(uri);        if (tableName == null) {            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }        int count = sqLiteDatabase.delete(tableName, selection, selectionArgs);        if (count > 0) {
            mContext.getContentResolver().notifyChange(uri, null);
        }        return count;
    }    /**
     * 更新資料
     *
     * @param uri
     * @param values
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        String tableName = getTableName(uri);        if (tableName == null) {            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }        int row = sqLiteDatabase.update(tableName, values, selection, selectionArgs);        if (row > 0) {
            mContext.getContentResolver().notifyChange(uri, null);
        }        return row;
    }
}

ContentProvider建立好切記一定要在 AndroidManifest.xml中註冊!(前文已經提到了如何註冊,我就不再複述啦~)

然後分別操作article和author兩張表,向其插入一條資料後Log輸出所有的資料

/** 
* @author: 下碼看花 
* date: 2019/8/8 
* description: ContentProvider實戰 
*/public class MainActivity extends AppCompatActivity {    
 	 private final String TAG = "MainActivity";    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Uri articleUri = Uri.parse("content://com.xmkh.MyContentProvider/article");
        ContentValues contentValues = new ContentValues();
        contentValues.put("articleName", "Android四大元件之ContentProvider");
        getContentResolver().insert(articleUri, contentValues);
        Cursor articleCursor = getContentResolver().query(articleUri, new String[]{"_id", "articleName"}, null, null, null);        if (articleCursor != null) {            while (articleCursor.moveToNext()) {
                Log.e(TAG, "ID:" + articleCursor.getInt(articleCursor.getColumnIndex("_id"))
                        + "  ArticleName:" + articleCursor.getString(articleCursor.getColumnIndex("articleName")));
            }
            articleCursor.close();
        }
        Uri authorUri = Uri.parse("content://com.xmkh.MyContentProvider/author");
        contentValues.clear();
        contentValues.put("authorName", "Austen");
        contentValues.put("sex", "男");
        getContentResolver().insert(authorUri, contentValues);
        Cursor authorCursor = getContentResolver().query(authorUri, new String[]{"_id", "authorName", "sex"}, null, null, null);        if (authorCursor != null) {            while (authorCursor.moveToNext()) {
                Log.e(TAG, "ID:" + authorCursor.getInt(authorCursor.getColumnIndex("_id"))
                        + "  AuthorName:" + authorCursor.getString(authorCursor.getColumnIndex("authorName"))
                        + "  Sex:" + authorCursor.getString(authorCursor.getColumnIndex("sex")));
            }
            authorCursor.close();
        }
    }
}

得到的輸出結果是:

結語

ContentProvider作為Android的四大元件之一,雖說我們平時用的並不多,但是作為安卓四大元件之一,其地位不容忽視。 小夥伴們趕緊上手實操,把它靈活的運用到專案中,讓我們每天一起快樂的進步吧~


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31557424/viewspace-2654933/,如需轉載,請註明出處,否則將追究法律責任。

相關文章