一篇就夠了系列之ContentProvider全解析

前言:

上一篇介紹廣播接收者(BroadcastReceover),本篇繼續,記敘下Android四大組件最後一個不常用,但是非常有用的ContentProvider。Google文檔

本文主要從以下幾個方面介紹:

  • 特點
  • 優缺點
  • 數據訪問
  • 創建內容提供器
  • 工作機制

特點

  1. Android四大組件之一,需要進行註冊,一般有name,authorities,export等屬性
  2. 是一種定義數據共享的接口,並是不android數據存儲的方式之一
  3. 跨進程數據訪問
  4. android系統很多系統應用都是使用ContentProvider方式進行儲存的(圖片,通訊錄,視頻,音頻等)
  5. 數據更新監聽方便

優缺點

優點:

  1. 爲數據訪問提供統一的接口,解決了不同儲存方式需要不同的API的訪問所帶來的繁瑣
  2. 跨進程數據的訪問,實現了不同App數據訪問提供了很大的便利

缺點:

  1. 不能單獨使用,必須需要和其他的儲存方式結合使用

數據訪問

下面示例如何獲取通訊錄信息:
Activity:

public class ContentProviderActivity extends AppCompatActivity {

    private ContentResolver mContentResolver;//
    StringBuilder builder=new StringBuilder("");
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        mContentResolver=getContentResolver();
        tv= (TextView) findViewById(R.id.tv_show_contract);
        findViewById(R.id.tv_press_contract).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //動態申請權限
                if(ContextCompat.checkSelfPermission(ContentProviderActivity.this, android.Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(ContentProviderActivity.this,new String[]{Manifest.permission.READ_CONTACTS},1);
                }else{
                    readContacts();
                }
            }
        });
    }

    private void readContacts(){
        Cursor cursor=null;
        try {
            cursor=mContentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
            if(cursor!=null){
                while(cursor.moveToNext()){
                    String displayName=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    builder.append(displayName);
                    builder.append(": ");
                    builder.append(number);
                    builder.append("   ");
                    Log.i("wy",displayName+"   "+number);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(cursor!=null){
                cursor.close();
            }
            tv.setText(builder);
        }
    }
}

以上:

  • 藉助ContentResolver類訪問內容提供器中共享的數據,其提供了一系列的增刪改查(CRUD)操作,其中insert方法用於添加數據,update更新數據,delete刪除數據,query用於查詢數據。和SQLiteDatabase類似,只是參數上稍微有些區別,ContentResolver中接收的參數是不接收表名參數的,而是使用一個Uri參數代碼(關於Uri內容歡迎參考一篇就夠了系列之Activity全解析中關於Scheme的介紹)。
  • 由於聯繫人信息是危險權限,所以需要動態申請,同時manifest中也需要申明
  • 代碼中的查詢等方法可以看到是個常量,其實也源碼的一個封裝。

創建內容提供器

public class MyContentProvider extends ContentProvider {

    public static final int DIR1=0;
    public static final int ITEM1=1;
    public static final int DIR2=2;
    public static final int ITEM2=3;

    public static final String AUTHRITY="com.wenyi.interview.provider";
    private static UriMatcher uriMatcher;
    private MyDatabaseHelper dbhelper;

    static {
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHRITY,"book",DIR1);
        uriMatcher.addURI(AUTHRITY,"book/#",ITEM1);
        uriMatcher.addURI(AUTHRITY,"category",DIR2);
        uriMatcher.addURI(AUTHRITY,"category/#",ITEM2);
    }


    /**
     * 初始化內容提供器的時候調用,通常會在這裏完成對數據庫的創建和升級等操作
     * @return true表示初始化成功,false表示初始化失敗
     * 只有當contentResolver嘗試訪問我們的程序中的數據是,內容提供器纔會被初始化
     */
    @Override
    public boolean onCreate() {
        dbhelper=new MyDatabaseHelper(getContext(),"MyStore.db",null,2);
        return true;
    }

    /**
     * 查詢數據
     * @param uri 確定查詢哪張表
     * @param projection 確定查詢哪些列
     * @param selection 約束查詢哪些行
     * @param selectionArgs 約束查詢哪些行
     * @param sortOrder 用於對結果進行排序,查詢的結果存放在Cursor對象中返回
     * @return
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteDatabase db=dbhelper.getReadableDatabase();
        Cursor cursor=null;
        switch (uriMatcher.match(uri)){
            case DIR1:
                cursor=db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
                break;
            case ITEM1:
                String bookid=uri.getPathSegments().get(1);
                cursor=db.query("Book",projection,"id=?",new String[]{bookid},null,null,sortOrder);
                break;
            case DIR2:
                cursor=db.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
                break;
            case ITEM2:
                String categoryid=uri.getPathSegments().get(1);
                cursor=db.query("Category",projection,"id=?",new String[]{categoryid},null,null,sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    /**
     *
     * @param uri
     * @return 傳入的內容URI來返回相應的MIME類型
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case DIR1:
                return "vnd.android.cursor.dir/vnd.com.wenyi.interview.provider.book";
            case ITEM1:
                return "vnd.android.cursor.item/vnd.com.wenyi.interview.provider.book";
            case DIR2:
                return "vnd.android.cursor.dir/vnd.com.wenyi.interview.provider.category";
            case ITEM2:
                return "vnd.android.cursor.item/vnd.com.wenyi.interview.provider.category";
            default:
                break;
        }
        return null;
    }

    /**
     * 添加數據
     * @param uri 確定要添加到的表
     * @param values 待添加的數據
     * @return 添加完成後,返回一個用於表示這條心記錄的URI
     */
    @Nullable
    @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;
    }
}

代碼有點長,現在來細細分析:

  1. 自定義MyContentProvider繼承自ContentProvider,重寫相應的方法,方法都有解釋
  2. 四個常量分別表示訪問Book表中的說有數據,訪問Book表中的單條數據,訪問Category表中的所有數據和訪問Category表中的單條數據
  3. 在manifest中進行註冊,<provider
    android:exported="true"
    android:enabled="true"
    android:authorities="com.wenyi.interview.provider"
    android:name=".FourComponet.contentProvider.MyContentProvider"/>
  4. 代碼中,UriMatche類,可以用來配皮Uri類型數據,其中,“*”和“#”分別表示匹配任意長度的任意字符和任意長度的數字。
  5. 創建MyDatabaseHelper對象,繼承自SQLiteOpenHelper,方便數據可操作,因爲內容提供着只是接口標準,需要和數據存儲方式結合使用
  6. 只在query中寫了相應代碼,其他方法也需要寫類似代碼
  7. getType方法中,返回值很奇特。實際上,這是MIME字符格式,MIME主要由3部分組成,在Android中,格式爲: a: 必須以vnd開頭 b: 如果內容Uri以路徑結尾,則後面接 android.cursor.dir/。如果內容Uri以id結尾,則後面接android.cursor.item/ c: 在最後面接 vnd.authority.path
  8. 這樣,我們的內容提供器就創建完成了,通過這種機制,必須知道URI,才能順利的訪問該數據庫中的內容,比保證了可訪問,又保證了安全性。

工作機制

暫不分析

歡迎閱讀其他系列文章:

一篇就夠了系列之Activity全解析

一篇就夠了系列之Service全解析

一篇就夠了系列之BroadcastReceiver全解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章