7 跨程序共享數據-探究內容提供器

image.jpg

文件存儲,SharedPreferences存儲以及數據庫存儲,使用這些持久化技術所保存的數據都只能在當前應用程序中訪問。

內容提供器簡介

內容提供器(Content Provider)主要用于在不同的應用程序之間實現數據共享的功能,它提供了一套完整的機制,允許一個程序訪問另一個程序中的數據,同時還能保證被訪數據的安全性。目前,使用內容提供器是Android實現跨程序共享數據的標準方式。

不同于文件存儲和SharedPreferences存儲中的兩種全局可讀寫操作模式,內容提供器可以選擇只對哪一部分數據進行共享,從而保證我們程序中的隱私數據不會有泄露的風險。

運行時權限

Android的權限機制并不是什么新鮮事物,從系統的第一個版本開始就已經存在了。

用戶不需要在安裝軟件的時候一次性授權所有申請的權限,而是可以在軟件的使用過程中再對某一項權限申請進行授權。

Android現在將所有的權限歸成了兩類,一類是普通權限,一類是危險權限

普通權限指的是那些不會直接威脅到用戶的安全和隱私的權限,對于這部分的權限申請,系統會自動幫我們進行授權,而不需要用戶再去手動操作了。

危險權限 則表示那些可能會觸及用戶隱私,或者對設備安全性造成影響的權限,如獲取設備聯系人信息等,對于這部分權限申請,必須要由用戶手動點擊授權才可以,否則程序就無法使用相應的功能。

危險權限總共就那么幾個,除了危險權限之外,剩余的就都是普通權限了。

另外注意一下,表格中每個危險權限都屬于一個權限組,我們在進行運行時權限處理時使用的是權限名,但是用戶一旦同意授權了,那么該權限所對應的的權限組中所有的其他權限也會同時被授權。

在程序運行時申請權限

CALL_PHONE這個權限是編寫撥打電話功能的時候需要聲明的,因為撥打電話會涉及用戶手機的資費問題,因而被列為了危險權限。

button_make_call.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                try
                {
                    Intent intent = new Intent(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:10086"));
                    startActivity(intent);
                }catch (SecurityException e)
                {
                    e.printStackTrace();
                }
            }
        });
        
//聲明權限
<uses-permission android:name="android.permission.CALL_PHONE"/>

我們構建了一個隱式Intent,Intentaction指定為Intent.ACTION_CALL。這是一個系統內置的打電話的動作,然后在Data部分指定了協議是tel,號碼是10086。我們以前指定的actionIntent.ACTION.DIAL。表示打開撥號界面,這個是不需要聲明權限的,而Intent.ACTION_CALL則可以直接撥打電話,因此必須聲明權限。另外為了防止程序崩潰,我們將所有操作都放在了異常捕獲代碼塊當中。

public void initEvent()
    {
        button_make_call.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if (ContextCompat.checkSelfPermission(MainActivity.this,
                        Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED)
                {
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE},1);
                }
                else
                {
                    Call();
                }
            }
        });
    }

    @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)
               {
                   Toast.makeText(MainActivity.this, "你同意了相關權限!", Toast.LENGTH_SHORT).show();
                   Call();
               }
               else
               {
                   Toast.makeText(MainActivity.this, "你拒絕了相關權限!", Toast.LENGTH_SHORT).show();
               }
               break;

           default:
       }
    }

    private void Call()
    {
        try
        {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }catch (SecurityException e)
        {
            e.printStackTrace();
        }
    }

說白了,運行時權限的核心就是在程序運行過程中由用戶授權我們去執行某些危險操作,程序是不可以擅自做主去執行這些危險操作的。因此,第一步就是要先判斷用戶是不是給過我們授權了,借助的是ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接收兩個參數,第一個參數是Context,第二參數是具體的權限名,比如打電話的權限名就是Manifest.permission.CALL_PHONE。然后我們使用方法的返回值和PackageManager.PERMISSION_GRANTED作比較,相等就說明用戶已經授權,不等就表示用戶沒有授權。

如果沒有授權的話,則需要調用ActivityCompat.requestPermissions()方法來向用戶申請授權,requestPermissions()方法接收三個參數第一個參數要求是Activity的實例,第二個參數是一個String數組,我們把要申請的權限名放在數組中即可,第三個參數是請求碼,只要是唯一值就可以了。

調用完了requestPermissions()方法之后,系統會彈出一個權限申請的對話框,然后用戶可以選擇同意或拒絕我們的權限申請,不論是哪種結果,最終都會回調到onRequestPermissionsResult()方法中,而授權的結果則會封裝在grantResults參數當中。這里只需要判斷一下最后的授權結果,如果用戶同意的話,就調用Call()方法來撥打電話,如果拒絕的話,我們只能放棄操作,并且彈出一條失敗提示。

訪問其他程序中的數據

內容提供器的用法一般有兩種:一種是使用現有的內容提供器來讀取和操作相應程序中的數據,另一種是創建自己的內容提供器給我們程序的數據提供外部訪問接口。

如果一個應用程序通過內容提供器對其數據提供了外部訪問的接口,那么任何其他的應用程序就都可以對這部分數據進行訪問。

ContentResolver的基本用法

對于每一個應用程序來說,如果想要訪問內容提供器中共享的數據,就一定要借助ContentResolver類,可以通過Context中的getContentResolver方法獲取到該類的實例。ContentResolver中提供了一系列的方法用于對數據進行CRUD操作。

不同于SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數的,而是使用一個Uri參數代替,這個參數被稱為內容URI。內容URI給內容提供器中的數據建立了唯一標識符,它主要有兩部分組成:authority和path。authority是用于對不同的應用程序做區分的,一般為了避免沖突,都會采用程序包名的方式來進行命名。path則是用于對同一應用程序中不同的表做區分的,通常都會添加到authority的后面。
不過目前還很難辨認出這兩個字符串就是兩個內容URI,我們還需要在字符串的頭部加上協議聲明。因此,內容URI最標準的格式寫法如下:
content://com.example.app.provider/table1

content://com.example.app.provider/table2

在得到了內容URI字符串之后,我們還需要將它解析成Uri對象才可以作為參數傳入,解析的方法也非常簡單,
Uri uri = Uri.parse("content://com.example.app.provider/table1")

只需要調用Uri.parse()方法,就可以將內容URI字符串解析成Uri對象了。

Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
uri:指定查詢某個應用程序下的某一張表。
projection:指定查詢的列名。
selection:指定where的約束條件。
selectionArgs:為where中的占位符提供具體的值。
orderBy:指定查詢結果的排序方式。

查詢完成后,返回的仍然是一個Cursor對象,這時我們就可以將數據從Cursor對象中逐個讀取出來了。

如何向table1表中添加一條數據:
ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2",1);
getContentResolver().insert(uri,values);

將待添加的數據組裝到ContentValues中,然后調用ContentResolver的insert()方法,將Uri和ContentValues作為參數傳入即可。

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

注意上述代碼使用了selection和selectionArgs參數來對想要更新的數據進行約束,以防止所有的行都會受影響。

getContentResolver().delete(uri,"column2 = ?",new String[]{"1"});
刪除數據。

讀取系統聯系人

public class MainActivity extends AppCompatActivity
{

    ArrayAdapter<String> adapter;
    List<String> contactsList = new ArrayList<>();
    private ListView listView;

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

        initView();

        adapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1,
                contactsList);
        listView.setAdapter(adapter);

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED)
        {
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission
            .READ_CONTACTS},1);
        }
        else
        {
            readContacts();
        }
    }

    public void initView()
    {
        listView = (ListView) findViewById(R.id.contacts_view);
    }

    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));
                    contactsList.add(displayName + "\n" + number);
                }
                adapter.notifyDataSetChanged();
            }
        } catch (Exception e)
        {
            e.printStackTrace();
        } finally
        {
            cursor.close();
        }

    }

    @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)
                    readContacts();
                break;
            default:
        }
    }


}

獲取ListView控件的實例,并給它設置好了適配器,然后開始調用運行時權限的處理邏輯,因為READ_CONTACTS權限是屬于危險權限的。我們在用戶授權之后調用readContacts()方法來讀取系統聯系人信息。

這里使用了ContentResolver的query()方法是來查詢系統的聯系人數據。

ContactsContract.CommonDataKinds.Phone類已經幫我們做好了封裝,提供了一個CONTENT_URI常量,而這個常量就是使用Uri.parse()方法解析出來的結果。接著我們對Cursor對象進行遍歷,將聯系人姓名和手機號這些數據逐個取出,聯系人姓名這一列對應的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME

聯系人手機號這一列對應的常量是ContactsContract.CommonDataKinds.Phone.NUMBER。兩個數據都取出之后,將它們進行拼接,并在中間加上換行符,然后將拼接后的數據添加到ListView的數據源里,并通知刷新一下ListView,最后千萬不要忘記將Cursor對象關閉掉。

讀取系統聯系人的權限千萬不能忘記聲明。

創建內容提供器的步驟

如果想要實現跨程序共享數據的功能,官方推薦的方式就是使用內容提供器,可以通過新建一個類去繼承ContentProvider的方式來創建一個自己的內容提供器。ContentProvider類中有6個抽象方法,我們在使用子類繼承它的時候,需要將這6個方法全部重寫。

public class MyProvider extends ContentProvider
{


    @Override
    public boolean onCreate()
    {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
    {
        return null;
    }

    @Nullable
    @Override
    public String getType(Uri uri)
    {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values)
    {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs)
    {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    {
        return 0;
    }
    
}

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參數確定刪除哪一張表中的數據,selection和selectionArgs參數用于約束刪除哪些行,被刪除的行數將作為返回值返回。

6.getType()

根據傳入的內容URI來返回相應的MIME類型。

以路徑結尾就表示期望訪問該表中所有的數據,以id結尾就表示期望訪問該表中擁有相應id的數據。我們可以使用通配符的方式來分別匹配這兩種格式的內容URI,規則如下:

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

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

所以一個能夠匹配任意表的內容URI格式就可以寫成:

content://com.example.app.provider/*

而一個能夠匹配table1表中任意一行數據的內容URI格式就可以寫成:

content://com.example.app.provider/table1/#

我們再借助UriMatcher這個類就可以輕松地實現匹配內容URI的功能。UriMatcher中提供了一個addURI()方法,這個方法接受3個參數,可以分別把authority,path和一個自定義代碼傳進去。

當調用UriMatcher的match()方法時,將可以將一個Uri對象傳入,返回值是某個能夠匹配這個Uri對象所對應的自定義代碼,利用這個代碼,我們就可以判斷出調用方期望訪問的是哪張表中的數據了。

public class MyProvider extends ContentProvider
{
    public static final int TABLE1_DIR = 0;

    public static final int TABLE1_ITEM = 1;

    public static final int TABLE2_DIR = 2;

    public static final int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher;

    static
    {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
    }

    @Override
    public boolean onCreate()
    {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
    {
        switch (uriMatcher.match(uri))
        {
            case TABLE1_DIR:
                //查詢table表中的所有數據
                break;
            case TABLE1_ITEM:
                //查詢table1表中的單條數據
                break;
            case TABLE2_DIR:
                //查詢table2表中的所有數據
                break;
            case TABLE2_ITEM:
                //查詢table2表中的單條數據
                break;
        }
    }

    @Nullable
    @Override
    public String getType(Uri uri)
    {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values)
    {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs)
    {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    {
        return 0;
    }

}

MyProvider中新增了4個整形常量。其中TABLE1_DIR表示訪問table1表中的所有數據, TABLE1_ITEM表示訪問table1表中的單條數據,TABLE2_DIR表示訪問table2表中的所有數據,
TABLE2_ITEM表示訪問table2表中的單條數據。

接著在靜態代碼塊里我們創建了UriMatcher的實例,并調用addURI()方法將期望匹配的內容URI格式傳遞進去,注意這里傳入的路徑參數是可以使用通配符的。

然后當query()方法被調用的時候就會通過UriMatcher的match()方法對傳入的Uri對象進行匹配,如果發現UriMatcher中某個內容URI格式成功匹配了該Uri對象,則會返回相應的自定義代碼,然后我們就可以判斷出調用方期望訪問的到底是什么數據了。

還有一個方法你會比較陌生,即getType()方法。它是所有的內容提供器都必須提供的一個方法,用于獲取Uri對象所對應的的MIME類型。一個內容URI所對應的MIME字符串主要有3部分構成。

1.必須以vnd開頭

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

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

對于content://com.example.app.provider/table1這個內容URI,它所對應的MIME類型就可以寫成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

對于content://com.example.app.provider/table1/1這個內容URI,它所對應的MIME類型就可以寫成:

vnd.android.cursor.item/vnd.com.example.app.provider.table1

public class MyProvider extends ContentProvider
{
    public static final int TABLE1_DIR = 0;

    public static final int TABLE1_ITEM = 1;

    public static final int TABLE2_DIR = 2;

    public static final int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher;

    static
    {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
    }

    @Override
    public boolean onCreate()
    {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
    {
        switch (uriMatcher.match(uri))
        {
            case TABLE1_DIR:
                //查詢table表中的所有數據
                break;
            case TABLE1_ITEM:
                //查詢table1表中的單條數據
                break;
            case TABLE2_DIR:
                //查詢table2表中的所有數據
                break;
            case TABLE2_ITEM:
                //查詢table2表中的單條數據
                break;
        }
    }

    @Nullable
    @Override
    public String getType(Uri uri)
    {
        switch (uriMatcher.match(uri))
        {
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
            case TABLE1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
            case TABLE2_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
        }

        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values)
    {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs)
    {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    {
        return 0;
    }

}

實現跨程序數據共享

右擊com.example.databasetest包---New--Other---Content Provider,


image

我們將內容提供器命名為DatabaseProvider,authority指定為com.example.databasetest.provider,Exported屬性表示是否允許外部程序訪問我們的內容提供器,Enabled屬性表示是否啟用這個內容提供器。

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.databasetest.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;

    static
    {
        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 boolean onCreate()
    {
        dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,2);
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder)
    {
        //查詢數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri))
        {
            case BOOK_DIR:
                cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
                break;
            case BOOK_ITEM:
                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);
                break;
            default:
                break;
        }
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values)
    {
        //添加數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri))
        {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("Book",null,values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case Category_DIR:
            case Category_ITEM:
                long newCategory = db.insert("Category",null,values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategory);
                break;
            default:
                break;
        }
        return uriReturn;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs)
    {
        //更新數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri))
        {
            case BOOK_DIR:
                updatedRows = db.update("Book",values,selection,selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book",values,"id = ?",new String[]{bookId});
                break;
            case Category_DIR:
                updatedRows = db.update("Category",values,selection,selectionArgs);
                break;
            case Category_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Category",values,"id = ?",new String[]{categoryId});
                break;
            default:
                break;
        }
        return updatedRows;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs)
    {
        //刪除數據
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (uriMatcher.match(uri))
        {
            case BOOK_DIR:
                deleteRows = db.delete("Book",selection,selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deleteRows = db.delete("Book","id = ?",new String[]{bookId});
                break;
            case Category_DIR:
                deleteRows = db.delete("Category",selection,selectionArgs);
                break;
            case Category_ITEM:
                String category = uri.getPathSegments().get(1);
                deleteRows = db.delete("Category","id = ?",new String[]{category});
                break;
            default:
                break;
        }
        return deleteRows;
    }

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

}

在類的一開始,同樣是定義了4個常量,分別用于表示訪問Book表中的所有數據,訪問Book表中的單條數據,訪問Category表中的所有數據和訪問Category表中的單條數據。然后在靜態代碼塊里對UriMatcher進行了初始化操作,將期望匹配的幾種URI格式添加了進去。

接下來就是每個抽象方法的具體實現了,先來看下onCreate()方法,這個方法的代碼很短,就是創建了一個MyDatabaseHelper的實例,然后返回true表示內容提供器初始化成功,這時數據庫就已經完成了創建或升級操作。

query()方法,先獲取到了SQLiteDatabase的實例,然后根據傳入的Uri參數判斷用戶想要訪問哪張表,再調用SQLiteDatabase的query()進行查詢,并將Cursor對象返回就好了。注意訪問單條數據的時候有一個細節,這里調用了Uri對象的getPathSegments()方法,它會將內容URI權限之后的部分以"/"符號進行分割,并把分割后的結果放入到一個字符串列表中,那這個列表中第0個位置存放的就是路徑,第一個位置存放的就是id了。得到了id 之后,再通過selection和selectionArgs參數進行約束,就實現了查詢單條數據的功能

insert()方法,先獲取到了SQLiteDatabase的實例,根據傳入的Uri參數判斷用戶想要往哪張表里添加數據,再調用SQLiteDatabase的insert()進行添加,注意insert()方法要求返回一個能夠表示這條新增數據的URI,所以我們還需要調用Uri.parse()方法來將一個內容URI解析成Uri對象
update()方法,先獲取到了SQLiteDatabase的實例,然后根據傳入的Uri參數判斷用戶想要更新哪張表的數據,再調用SQLiteDatabase的update()進行更新就好了,受影響的行數將作為返回值返回。

delete()方法,先獲取到了SQLiteDatabase的實例,然后根據傳入的Uri參數判斷用戶想要刪除哪張表的數據,再調用SQLiteDatabase的delete()進行刪除就好了,被刪除的行數將作為返回值返回。

另外還有一點需要注意,內容提供器一定要在AndroidManifest.xml文件中注冊才可以使用

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

<application>標簽內出現了一個新的標簽<provider>,我們使用它來對DatabaseProvider這個內容提供器進行注冊,

訪問DatabaseTest中的數據

public class MainActivity extends AppCompatActivity
{

    Button  button_add_data,button_query_data,
            button_update_data,button_delete_data;

    private String newId;

    private static final String TAG = "MainActivity";

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

        initView();
        initEvent();
    }

    public void initView()
    {
        button_add_data = (Button) findViewById(R.id.add_data);
        button_query_data = (Button) findViewById(R.id.query_data);
        button_update_data = (Button) findViewById(R.id.update_data);
        button_delete_data = (Button) findViewById(R.id.delete_data);
    }

    public void initEvent()
    {
        //添加數據
        button_add_data.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name","A Clash of Kings");
                values.put("author","Georgr Martin");
                values.put("pages",1040);
                values.put("price",22.85);
                Uri newUri = getContentResolver().insert(uri,values);
                newId = newUri.getPathSegments().get(1);
            }
        });

        //查看數據
        button_query_data.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                Cursor cursor = getContentResolver().query(uri,null,null,null,null);
                if (cursor != null)
                {
                    while (cursor.moveToNext())
                    {
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));

                        Log.d(TAG, "book name is :" + name);
                        Log.d(TAG, "book author is " + author);
                        Log.d(TAG, "book pages is :" + pages);
                        Log.d(TAG, "book price is :" + price);

                    }
                    cursor.close();
                }
            }
        });

        //更新數據
        button_update_data.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name","A Storm of Swords");
                values.put("pages",1216);
                values.put("price",24.05);
                getContentResolver().update(uri,values,null,null);
            }
        });

        //刪除數據
        button_delete_data.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                getContentResolver().delete(uri,null,null);
            }
        });
    }
}

代碼的意思我就不解釋了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容