內容提供器(Content-Provider)完整使用指南

[TOC]java

1. 什麼是內容提供器?

​ 內容提供器(Content Provider) 主要用於在不一樣的應用程序之間實現數據的共享功能,他提供了一套完整的機制,容許一個程序訪問另外一個程序中的數據,同時還能夠保證被訪問的數據的安全性,目前使用內容提供器十Android實現跨程序共享數據的標準方式. 內容提供器能夠選擇只對哪一部分的數據進行共享,這樣就能夠保證咱們數據的安全性.android

2. 如何使用內容提供器獲取其餘應用的數據

2.1 權限聲明

​ 運行時權限:6.0系統中引入的新功能,爲了能更好的保護用戶的隱私.數據庫

​ 一般的權限聲明只須要在MainFest中添加要使用的權限就能夠了,6.0及之後對於某些權限還須要在運行的時候在代碼中檢測是否有這個權限不然彈出對話框申請,拒絕的話是沒法使用的.數組

​ **並非全部的權限都須要用到運行時權限,只有關係到用戶隱私的才須要.除了在ManiFest中聲明之外,還要在代碼中從新請求一 遍,若是沒有使用運行時權限則會致使這個應用拋出異常SecurityException() **安全

public class MainActivity extends AppCompatActivity {
    private String TAG = "TAG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkSelf();//檢查權限
    }

    private void checkSelf() {
        //若是檢查多個權限的話,能夠將要檢查的權限放入數組或者集合當中,遍歷檢查便可
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            /** * 經過checkSelfPermission能夠檢查當前這個應用有沒有獲取到指定的那個權限,沒有的話就調用請求權限的那個方法. * 根據返回值來判斷狀態 0表示權限已給予,-1表示沒有獲取到權限. */
            Log.d(TAG, "checkSelf: 權限容許");
        } else {
            /** * 經過ActivityCompat.requestPermissions動態的申請權限. * 第一個參數:當前的上下文 * 第二個參數:須要申請的權限的字符串,保存在數組中. * 第三個參數:查詢碼,請求權限的最終結果會經過回調的方式->onRequestPermissionsResult();在這個方法中,告訴你最後請求成功了沒有 */
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CALENDAR,Manifest.permission.CAMERA}, 1);

        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        /** * 經過請求碼來判斷具體是申請了什麼權限,以及結果; * grantResult[] 保存着申請權限後,根據申請權限的前後順序保存 */
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "onRequestPermissionsResult: 已受權");
                    for (int i = 0; i < grantResults.length; i++) {
                        Log.d(TAG, "onRequestPermissionsResult: 受權狀況:"+grantResults[i]);
                    }
                } else {
                    Log.d(TAG, "onRequestPermissionsResult: 您拒絕了受權");
                }
                break;
            case 2:
                break;
            default:
                break;
        }
    }
}

複製代碼

以上就是運行時權限的基本用法dom

2.2 內容提供器的基本用法及原理

​ 內容提供器的用法通常有兩種,一是使用現有的內容提供器來讀取和操做相應程序中的數據,二是本身建立一個內容提供器給咱們的程序的數據提供外部訪問接口 (就是經過別人(程序)提供的內容提供器來獲取別人(程序)想要給咱們使用的數據),系統自帶的短信,電話簿,媒體庫等程序都提供了相似的訪問接口,咱們就能夠利用這個來進行再次開發和使用了.ide

​ 若是想要獲取內容提供器中的數據,那麼就須要藉助Content-Resolver類,Context中的GetContentResolver方法能夠獲取到該類的實例. Content-Resolver提供了相似SqLiteDatabase類的方法,能夠對共享的數據進行CRUD 操做,只是參數略微有點不一樣罷了.this

​ 內容提供器是經過Uri來尋找數據源的 Uri由authority和path組成;authority用於區分不一樣的程序,一般使用包名,path是對同一程序不一樣的表作區分用的.spa

​ 好比某個包名爲 com.example.test 有兩張表table1,table2;code

​ 則標準的Uri格式爲(://前面的內容爲協議):

​ content://com.example.test/table1

​ content://com.example.test/table2

​ 這樣就能夠明確的表達出咱們想要訪問哪一個程序的哪一個表裏面的數據了. 因此內容提供器的CRUD都只接受Uri參數來肯定位置.

查找:

Uri uri=Uri.parse("content://com.example.test/table2");

Cursor cursor=getContentResolver().quert(uri,projection,selection,selectionArgs,sortOrder);

quert()方法的參數 對應SQL部分 描述
uri from table_name 指定查詢某個應用程序下的某一張表
projection select column1,column2 指定查詢的列名
selection where column =value 指定where的約束條件
selectionArgs - 爲where中的佔位符提供具體的值
sortOrder order by column1,column2 指定查詢結果的排序方式

從cursor中取出值也和數據庫的操做同樣,選擇想要獲取的數據類型,再選擇列名便可獲得

...

	String data=cursor.getString(cursor.getColumnIndex("columnName"));

	...

複製代碼

插入:

​ 和數據庫的操做差很少,都是講數據組裝到ContentValues中

ContentValues values=new ContentValues();
 values.put("columnName1","value1");
 values.put("columnName2","value2");
  getContentResolver().insert(uri,values);
複製代碼

更新:

ContentValues values=new ContentValues();
values.put("columnName1","value1");
getContentResolver().update(Uri.parse(""),contentValues,"where column1=?",new String[]{"1"});
複製代碼

update()中的參數解釋:第一個參數指定數據的位置;第二個參數指定要更新成什麼值;第三個參數指定條件;第四個參數指定where條件語句中的缺省值;

刪除:

getContentResolver().delete(Uri.parse(""),"column=?",new String[]{"1"});
複製代碼

​ 參數的意思和上面幾個差很少.就不過多的解釋了.

2.3 使用內容提供器獲取數據

​ 下面使用系統已經給咱們提供好了的內容提供器來獲取通信簿中的姓名的電話號碼吧,先確保的確保存了幾個電話號碼在通信簿中.

1.首先須要獲取READ_CANTACTS權限不然是不能讀取數據的 而且會報錯.

2.查詢語句和數據庫的用法類似

public class MainActivity extends AppCompatActivity {
    private String TAG = "TAG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        } else {
            
            getData();
        }
    }

    private void getData() {
        Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        	//傳入的Uri是ContactsContract.CommonData-Kinds.Phone類已經幫咱們封裝好了的一個常量
        //點開源代碼能夠看到 : 
        //public static final Uri CONTENT_URI = Uri.withAppendedPath(Data.CONTENT_URI,
                    "phones");
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        //ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME 也是一個常量,爲保存該數據的類名.下同
                String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                Log.d(TAG, "getData: " + name + "-" + phone);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    getData();
                } else {
                    Log.d(TAG, "onRequestPermissionsResult: 你點擊了拒絕!");
                }
                break;
            default:
                break;
        }
    }
}

複製代碼

運行程序就能夠看到姓名和電話一塊兒打印出來了.

主要的內容都在getData();方法中,具體的解釋都寫在註釋中,只要知道原理 那麼使用起來就變得十分的方便了.

3. 建立本身的內容提供器

基礎知識:

經過新建一個類去繼承ContentProvider的方式來建立一個內容提供器,可是必須重寫裏面的六個方法;

重寫的方法: 返回值 用途
onCreate(); boolean 初始化內容提供器的時候調用.一般會在這裏完成對數據庫的建立和升級操做.返回true代表內容提供器初始化成功,false失敗.
query(uri,projection, selection, selectionArgs, sortOrder); Cursor 從內容提供器中查找數據,使用uri參數來肯定要查找哪一張表格,projection用於肯定查找哪些列,selection用於肯定查找的條件,selectionArgs用於填充selection中的條件的缺省值,sortOrder用於對查詢結果排序 ; 查詢結果放入Cursor中返回;
insert(uri, values); Uri 向內容提供器中添加一條數據,使用uri來肯定要添加到的表,待添加的數據放在values參數中; 添加成功之後 返回一個用於表示這條新紀錄的Cursor;
update(uri, values, selection, selectionArgs); int 更新內容提供器中已有的數據,使用Uri參數來肯定更新哪一張表中的數據,新的數據保存在values中(只更新這裏有寫出來的值),selection和selectIonArgs參數用於約束更新哪些行;受影響的行數將做爲返回值 返回
delete(uri, selection, selectionArgs) int 刪除內容提供器中的數據,uri參數用來肯定刪除哪張表中的數據,selection和selectionArgs參數用於約束刪除哪些行;被刪除的行數將做爲返回值返回;
getType(uri)(); String 根據傳入的Uri來返回相對應的MIME類型字符串

Uri兩種格式的說明:

​ content://com.example.test/table1/1;

表示調用方指望訪問的是com.exampke.test應用裏面表table1中id爲1的數據

​ content://com.example.test/table1;

表示調用方指望訪問的是com.exampke.test應用裏面表table1的全部數據

不過一般都使用通配符的方式來匹配這兩種格式:

:表示匹配任意長度的數字

#* :表示匹配任意長度的字符

上面的內容就能夠寫成: content://com.example.test/* 或 content://com.example.test/table1/#

getType();返回數聽說明

​ content://com.example.test/table1/1;

​ content://com.example.test/table1;

仍是以上面這兩個爲例子.

MIME字符串: 以vnd開頭 + . + android.cursor.dir/(或android.cursor.item/) + vnd. + AUTHORITY + . + PATH

​ vnd.android.cursor.dir/vnd.com.example.test.table1;

​ vnd.android.cursor.item/vnd.com.example.test.table1

3.1 建立的基本步驟

建立一個繼承自ContentProvider的類,並重寫裏面的六個方法

其中內容提供器必須在ManiFest.xml中進行註冊,不然沒法使用

<provider android:name=".MyProvider" android:authorities="com.example.h.content_demo_provider" android:enabled="true" android:exported="true" />
複製代碼

第一個參數:類名

第二個參數:一般使用包名來使用, 能夠區分 不一樣的程序之間的內容提供器

第三個參數:啓用

第四個參數:表示容許被其餘的應用程序進行訪問

新建一個內容提供器:

public class MyProvider extends ContentProvider {
    public static final int BOOK_DIT = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORT_ITEM = 3;
    public static UriMatcher sUriMatcher;
    public static final String AUTHORITY = "com.example.h.content_demo_provider";
    private MyDatabaseHelper mMyDatabaseHelper;
    private SQLiteDatabase db;

    {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIT);
        sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        sUriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        sUriMatcher.addURI(AUTHORITY, "category/#", CATEGORT_ITEM);
        //UriMatcher 能夠匹配uri 經過調用他的match()方法 匹配到就會返回咱們在上面添加uri時填入的第三個參數


    }

    @Override
    /** * 初始化內容提供器的時候調用,一般會在這裏完成對數據庫的建立和升級等操做 * 返回true表示內容提供器初始化成功,返回false則表示失敗. */
    public boolean onCreate() {
        //對當前內容提供器須要的資源進行初始化
        mMyDatabaseHelper = new MyDatabaseHelper(getContext(), "info.db", null, 1);
        db = mMyDatabaseHelper.getWritableDatabase();
        Log.d(TAG, "onCreate: 內容提供器初始化完成");
        return true;
        
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        //查詢方法,經過解析uri來判斷想要查詢哪一個程序的哪一個表.經過UriMatchder進行匹配 若是有就返回前面addUri()中填入的code
        
        Cursor cursor = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                cursor = db.query("book", projection, selection, selectionArgs, null, null,
                        sortOrder);
                Log.d(TAG, "query: 查詢整個表" + cursor);
                return cursor;
            case BOOK_ITEM:
                String itemId = uri.getPathSegments().get(1);
                cursor = db.query("book", projection, "id=?", new String[]{itemId}, null, null,
                        sortOrder);
                /** * .getPathSegments()它會將內容URI權限以後的部分以 / 進行分割,並把分割後的結果放入到一個字符串列表中, * 返回的列表[0]存放的就是路徑,[1]存放的就是id */
                return cursor;
            case CATEGORT_ITEM:
                String itemId2 = uri.getPathSegments().get(1);
                cursor = db.query("category", projection, "id=?", new String[]{itemId2}, null, null,
                        sortOrder);
                return cursor;
            case CATEGORY_DIR:
                cursor = db.query("category", projection, selection, selectionArgs, null, null,
                        sortOrder);
                return cursor;
            default:
                return cursor;
        }

    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                return "vnd.android.cursor.dir/vnd.com.example.h.content_demo_provider.book";

            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.h.content_demo_provider.book";
            case CATEGORT_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.h.content_demo_provider.category";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.h.content_demo_provider.category";
            default:
                return null;
        }
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Uri uriReturn = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
            case BOOK_ITEM:
                long value = db.insert("book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + value);
                return uriReturn;
            //返回新插入行的行id,若是發生錯誤則返回-1
            case CATEGORT_ITEM:
            case CATEGORY_DIR:
                long value2 = db.insert("category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + value2);
                return uriReturn;
            default:
                return uriReturn;

        }
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int deleteRows = 0;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                deleteRows = db.delete("book", selection, selectionArgs);
                return deleteRows;
            case BOOK_ITEM:
                String itemId1 = uri.getPathSegments().get(1);
                deleteRows = db.delete("book", "id=?", new String[]{itemId1});
                return deleteRows;
            case CATEGORT_ITEM:
                String itemId2 = uri.getPathSegments().get(1);
                deleteRows = db.delete("category", "id=?", new String[]{itemId2});
                return deleteRows;
            case CATEGORY_DIR:
                deleteRows = db.delete("category", selection, selectionArgs);
                return deleteRows;
            default:
                return deleteRows;
        }

    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        int updateRows = 0;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                updateRows = db.update("book", values, selection, selectionArgs);
                return updateRows;
            case BOOK_ITEM:
                String itemId1 = uri.getPathSegments().get(1);
                updateRows = db.update("book", values, "id=?", new String[]{itemId1});
                return updateRows;
            case CATEGORT_ITEM:
                String itemId2 = uri.getPathSegments().get(1);
                updateRows = db.update("category", values, "id=?", new String[]{itemId2});
                return updateRows;
            case CATEGORY_DIR:
                updateRows = db.update("category", values, selection, selectionArgs);
                return updateRows;
            default:
                return updateRows;
        }
    }
}

複製代碼

新出現的方法:

uri.getPathSegments().get(1); //getPathSegments()返回一個集合,它將uri中authority後面的內容進行分割 即get(0)表名和id的值get(1) 這樣取出來的id就能夠看成條件對數據庫進行條件查詢了 .

3.2 跨程序獲取數據

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1, btn2, btn3, btn4;
    private Uri mUriBook = Uri.parse("content://com.example.h.content_demo_provider/book");
    private Uri mUriCategory = Uri.parse("content://com.example.h.content_demo_provider/category");

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

    }

    private void initView() {
        btn1 = findViewById(R.id.button);
        btn2 = findViewById(R.id.button2);
        btn3 = findViewById(R.id.button4);
        btn4 = findViewById(R.id.button5);
        btn4.setOnClickListener(this);
        btn3.setOnClickListener(this);
        btn2.setOnClickListener(this);
        btn1.setOnClickListener(this);
    }

    private void iQuery() {
        Cursor cursor = getContentResolver().query(mUriBook, null, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String id = cursor.getString(cursor.getColumnIndex("id"));
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String author = cursor.getString(cursor.getColumnIndex("author"));
                System.out.println(id + name + author);
            }
        } else {
            System.out.println("爲空");
        }
    }

    private void randomInsert() {
        ContentValues contentValues1 = new ContentValues();
        ContentValues contentValues2 = new ContentValues();
        for (int i = 0; i < 5; i++) {
            contentValues1.put("name", "name" + i);
            contentValues1.put("author", "author" + i);
            System.out.println(getContentResolver().insert(mUriBook, contentValues1));
            contentValues2.put("type", "type" + i);
            contentValues2.put("code", "code" + i);
            System.out.println(getContentResolver().insert(mUriCategory, contentValues2)); ;
        }
        //這個方法只是經過內容提供器對另外一個程序中的數據庫寫一點數據進去方便咱們進行後續的CRUD.
    }

    private void iDelete(String value) {
        ContentValues contentValues = new ContentValues();
        int x = 0;
        Uri uri = Uri.parse("content://com.example.h.content_demo_provider/book/" + value);
        x = getContentResolver().delete(uri, null, null);
        Log.d("TAG", "iDelete: 刪除後的返回值:" + x);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                iQuery();
                break;
            case R.id.button2:
                break;
            case R.id.button4:
                iDelete("1");
                break;
            case R.id.button5:
                break;
            default:
                break;
        }
    }
}
複製代碼

主要仍是經過定義好Uri 而後傳遞給內容提供器,告訴它你想作什麼,最後他會將執行結果經過返回值告訴你,更新功能和其餘幾個差很少就不展開解析了,

3.3 總結

通過上面的實踐,給個人感受就是, 某個程序A提供了一個內容提供器(這個內容提供器本質上就是對本程序內的數據庫進行CRUD 不過能夠對他進行限制一些權限 只給想給的數據), 而後程序B想要訪問程序A中的某些數據, 程序B能夠經過Uri對程序A容許的範圍內進行CRUD

相關文章
相關標籤/搜索