Android第一行代碼(十):內容提供器

內容提供器(ContentProvider)主要用于不同的程序之間實現數據共享的功能,并保證被訪數據的安全性。使用內容提供器是Android是實現跨程序共享數據的標準方式。

內容提供器的用法一般有兩種:

  1. 使用現有的內容提供器來讀取和操作相應程序中的數據
  2. 創建自己的內容提供器給我們程序的數據提供外部訪問的接口

想訪問內容提供器中共享的數據,要借助工具類ContentResolver類。可通過Context中的getContentResolver()方法獲取該類的實例。
ContentResolver類中提供了一系列方法對數據進行CRUD操作:

  1. insert()用于添加數據.
  2. update()用于更新數據.
  3. delete()用于刪除數據.
  4. query()用于查詢數據.

使用步驟:

  1. ContentResolver中的增刪改查方法都不接收表名參數,而使用一個Uri參數代替,這個參數稱為內容URI.
    內容URI為內容提供器的數據建立了唯一的標識符.
    Uri由authority和path組成。其中authority為“包名.provider”,path為表名。標準的Uri如下:

     content://com.example.app.provider/table1  
    
  2. 獲取的URI字符串需要解析為URI對象:

     Uri uri = Uri.parse("content://com.example.app.provider/table1");
    
  3. 利用Uri對象查詢數據

     Cursor cursor = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);
     if (cursor != null) {
       while (cursor.moveToNext()) {
         String column1 = cursor.getString(cursor.getColumnIndex("column1"));
         int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
       } 
       cursor.close();
     }
    

填加:

ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);

修改:

ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", newString[] {"text", "1"});

刪除:

getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

實例:用內容提供器讀取系統聯系人

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {

    ArrayAdapter<String> adapter;
    List<String> contactList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //獲取ListView實例
        ListView contactsView = (ListView)findViewById(R.id.contacts_view);
        //生成適配器,提供參數 上下文、ListView子項Item、ListView中數據
        adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactList);
        //給ListView設置適配器
        contactsView.setAdapter(adapter);

        //判斷用戶是否已經授權執行讀取通訊錄的操作,借助ContextCompat.checkSelfPermission()方法
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
        /*調用運行時權限的處理邏輯,即在程序運行時請求用戶授權,這里請求READ_CONTACTS的權限
        參數1:Activity實例; 參數2:請求權限的數組; 參數3:請求碼(要唯一值)*/
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
           
        }else {
        //若已授權,則直接讀取
            readContacts();
        }
    }

    private void readContacts(){
        Cursor cursor = null;
        try{
            //查詢聯系人數據
            cursor = getContentResolver().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));

                    contactList.add(displayName + "\n" + number);
                }
                //刷新ListView
                adapter.notifyDataSetChanged();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //一定要close cursor
            if(cursor != null)
                cursor.close();
        }
    }

//運行時請求權限彈出選擇框 選擇的回調方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    //requestCode就是請求時傳入的唯一值請求碼
        switch (requestCode){
            case 1:
                    //請求權限數組對應的請求結果返回數組,一一對應
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    readContacts();
                }else {
                    Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show();
                }
        }
    }
}

Manifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.contactstest">
    //最重要的是在Manifest文件中,聲明讀取系統聯系人的權限!!!
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <application
        ...
    </application>
</manifest>

創建自己的內容提供器

可以通過新建一個類去繼承ContentProvider的方式來創建一個自己的內容提供器。
在項目中右擊程序包->New->Other->Content Provider可新建內容提供器。
需要重寫以下6個方法:

  1. onCreate():初始化內容提供器時調用,用于數據庫的創建和升級等操作返回true則初始化成功,false則失敗.只有ContentResolver嘗試訪問程序中的數據時,內容提供器才會被初始化.
  2. query():使用 uri 參數來確定查詢哪張表, projection 參數用于確定查詢哪些列,selection 和 selectionArgs 參數用于約束查詢哪些行,sortOrder 參數用于對結果進行排序, 查詢的結果存放在 Cursor 對象中返回.
  3. insert():向內容提供器中添加一條數據,使用 uri 參數來確定要添加到的表,待添加的數據保存在 values 參數中,添加完成后,返回一個用于表示這條新記錄的 URI.
  4. update():更新內容提供器中已有的數據.使用 uri 參數來確定更新哪一張表中的數據,新數據保存在 values 參數中,,selection 和 selectionArgs 參數用于約束更新哪些行,,受影響的行數將作為返回值返回.
  5. delete():從內容提供器中刪除數據.使用 uri 參數來確定刪除哪一張表中的數據,和 selectionArgs 參數用于約束刪除哪些行, 被刪除的行數將作為返回值返回.
  6. getType():根據傳入的內容 URI 來返回相應的 MIME 類型.

URI寫法:

訪問com.example.app這個app中的table1表
content://com.example.app.provider/table1
訪問com.example.app這個app中table1表中id為1的數據
content://com.example.app.provider/table1/1

通配符匹配:

*: 表示匹配任意長度的任意字符串
#: 表示匹配任意長度的數據
content://com.example.app.provider/* 能匹配任意表的內容
content://com.example.app.provider/table1/#  能匹配table1 表中任意一行數據的內容

需要借助UriMatcher類來 實現匹配URI的功能
UriMatcher提供了addURI(),接受三個參數(權限、路徑、自定義代碼).
UriMatcher還提供了match()方法,返回一個匹配這個Uri對象所對應的自定義代碼。

public class DatabaseProvider extends ContentProvider {
    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.example.a123.databasetest.provider";
    private static UriMatcher uriMatcher;
    private  MyDatabaseHelper dbHelper;

    static {
        //獲得UriMatcher實例
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
        uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
    }
    
            @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        //match()方法,返回對應的自定義代碼
        switch(uriMatcher.match(uri)){
            case BOOK_DIR:
                cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
                break;

            case BOOK_ITEM:
                //getPathSegments(),將內容URI 權限之后的部分以"/"符號進行分割,得到字符串列表
                String bookID = uri.getPathSegments().get(1);
                cursor = db.query("Book",projection,"id = ?",new String[]{bookID},null,null,sortOrder);
                break;

            case CATEGORY_DIR:
                cursor = db.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
                break;

            case CATEGORY_ITEM:
                String categoryID = uri.getPathSegments().get(1);
                cursor = db.query("Category",projection,"id = ?",new String[]{categoryID},null,null,sortOrder);
        }
        return cursor;
    }
    
}

getType()方法,用于獲取uri對象所對應的MIME類型。一個內容URI所對應的MIME字符串主要由3部分組成

  1. 必須以vnd開頭

  2. 如果內容 URI 以路徑結尾,則后接 android.cursor.dir/,如果內容 URI 以 id 結尾,則后接 android.cursor.item/

  3. 最后接上 vnd.<authority>.<path>

     @Override
     public String getType(Uri uri) {
         switch (uriMatcher.match(uri)){
             case BOOK_DIR:
                 return "vnd.android.cursor.dir/vnd.com.example.a123.databasetest.provider.book";
             case BOOK_ITEM:
                 return "vnd.android.cursor.item/vnd.com.example.a123.databasetest.provider.book";
             case CATEGORY_DIR:
                 return "vnd.android.cursor.dir/vnd.com.example.a123.databasetest.provider.category";
             case CATEGORY_ITEM:
                 return "vnd.android.cursor.item/vnd.com.example.a123.databasetest.provider.category";
         }
         return null;
     }
    

然后在項目中Manifest.xml注冊

<provider
    android:name=".DatabaseProvider"
    android:authorities="com.example.a123.databasetest.provider"
    android:enabled="true"
    android:exported="true">
</provider>

這樣別的程序就能通過Uri來共享數據了!

     Uri uri = Uri.parse("content://com.example.a123.databasetest.provider/book");
    ContentValues values = new ContentValues();
    values.put("name","A Clash of Kings");
    values.put("author","George Martin");
    values.put("pages",1040);
    values.put("price",55.55);
    Uri newUri = getContentResolver().insert(uri,values);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容