第一行代码学习笔记第七章——探究内容提供器
知識點目錄
- 7.1 內容提供器簡介
- 7.2 運行權限
* 7.2.1 Android權限機制詳解
* 7.2.2 在程序運行時申請權限
- 7.3 訪問其他程序中的數據
* 7.3.1 ContentResolver的基本用法
* 7.3.2 讀取系統聯系人
- 7.4 創建自己的內容提供器
* 7.4.1 創建內容提供器的步驟
* 7.4.2 實現跨程序數據共享
- 7.5 Git時間
- 7.6 小結點評
知識點回顧
7.1 內容提供器簡介
內容提供器(Content Provider)主要用于在不同的應用程序之間實現數據共享的功能。它提供了一套完整的機制,允許一個程序訪問另一個程序中的數據,同時還能保證被訪問數據的安全性。
內容提供器可以選擇只對哪一部分數據進行共享,從而保證我們程序中的隱私數據不會有泄漏的風險。
7.2 運行權限
Android 6.0引用了運行時權限功能,很好地保護了用戶的安全和隱私。
7.2.1 Android權限機制詳解
Android要求我們在訪問用戶涉及到安全性的時候,需要在AndroidManifest.xml中加入相應的權限。這樣用戶就在如下兩個方面得到了保護:
-
在低于6.0的系統設備上安裝程序時,就會在安裝界面提醒用戶該apk需要的權限
-
用戶可以在應用程序管理界面查看任意一個程序的權限申請情況
但很多常用的軟件存在“店大欺客”的情況,濫用很多權限。因此Android研發團隊在6.0時就加入了運行時權限功能。即用戶不需要在安裝軟件的時候一次性授權所有申請的權限,而是可以在軟件的使用過程中再對某一項權限申請進行授權。
當然并不是所有的權限都是在運行時申請。Android將所有的權限分為了兩類:
-
普通權限
-
危險權限
普通權限:是指那些不會威脅到用戶的安全和隱私的權限,系統會自動幫我們進行授權。
危險權限:是指那些可能會觸及用戶隱私或者對設備安全性造成影響的權限,必須要由用戶手動點擊授權才行,否則程序就無法使用相應的功能。
Android中的危險權限主要有如下幾種:
注意:表中每一個權限都屬于一個權限組,在處理運行時權限時使用權限名,如果用戶一旦同意授權了,那么該權限所對應的權限組中所有的其他權限也會同時被授權。
Android系統中完整的權限列表如下:
點擊進入查看Android系統中的所有權限
7.2.2 在程序運行時申請權限
下面我們來使用CALL_PHONE來演示。
示例代碼:
定義一個button觸發點
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/make_call"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Make Call"android:textAllCaps="false"/></LinearLayout>在AndroidManifest.xml中聲明權限
<uses-permission android:name="android.permission.CALL_PHONE"/>運行時權限
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button makeCall = (Button) findViewById(R.id.make_call);makeCall.setOnClickListener(new View.OnClickListener() {@Overridepublic 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();}}});}private void call() {try {Intent intent = new Intent(Intent.ACTION_CALL);intent.setData(Uri.parse("tel:10086"));if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {return;}startActivity(intent);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {switch (requestCode) {case 1://如果授權成功則調用call()方法if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {call();} else {Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();}break;default:}}}解釋說明:
第一步:判斷用戶是不是已經給我們授權過
用ContextCompat.checkSelfPermission()方法去判斷用戶是否授過權。共接收兩個參數:
-
參數一:上下文
-
參數二:具體的權限名
第二步:將返回值與PackageManager.PERMISSION_GRANTED做比較,如果相等就說明用戶已經授權,如果不相等就表示用戶沒有授權。
第三步:如果授權了,則直接調用call()方法,如果沒有授權,則調用ActivityCompat.requestPermissions()方法向用戶申請授權。requestPermissions()共接收三個參數:
-
參數一:Activity實例
-
參數二:String數組,把要申請的權限名放在數組中
-
參數三:請求碼,只要確保是唯一值就行
調用完requestPermissions()方法后,系統會彈出一個權限申請的對話框,然后用戶可以選擇同意或拒絕,無論哪種最終都會調用onRequestPermissionsResult()方法,授權結果封裝在grantResults參數中,我們只需要判斷一下最后的授權結果。
效果圖:
備注:可以在Settings—>Apps—>RunntimePermissionTest—>Permissions中去關閉已經授予程序的危險權限。
7.3 訪問其他程序中的數據
內容提供器的用法主要有兩種:
-
使用現有的內容提供器來讀取和操作相應程序中的數據
-
創建自己的內容提供器給我們程序的數據提供外部訪問接口
7.3.1 ContentResolver的基本用法
Android中想要訪問內容提供器中共享的數據,需要借助ContentResolver類,可以通過Context中的getContentResolver()方法獲取到類的實例。它提供了一系列的方法對數據進行CRUD操作。
ContentResolver不接受表名,是接收內容URI,內容URI給內容提供器中的數據建立了唯一標識符,主要由authority和path組成:
-
authority:是用于對不同的應用程序做區分,一般都會采用程序包名
-
path:是用于對同一個程序中的不同表做區分
不過我們需要在頭部加上協議聲明,因此,內容URI最標準的格式寫法如下:
content://com.example.app.provider/table1在得到內容URI字符串后,還需要將它解析成Uri對象。
Uri uri = Uri.parse("content://com.example.app.provider/table1");查詢數據
Uri uri = Uri.parse("content://com.example.app.provider/table1"); Cursor cursor = getContentResolver().query(uri, //指定查詢哪個應用程序下哪張表projection, //指定查詢的列名selection, //指定where的約束條件selectionArgs, //為where中的占位符提供具體的值sortOrder); //指定查詢結果的排序方式if (cursor != null) {while (cursor.moveToNext()) {String column1 = cursor.getString(cursor.getColumnIndex("column1"));int column2 = cursor.getInt(cursor.getColumnIndex("column2"));} }添加數據
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 = ?", new String[]{"text", "1"});刪除數據
getContentResolver().delete(uri, "column2 = ?", new String[]{"1"});7.3.2 讀取系統聯系人
下面我們來讀取電話簿中的聯系人。
示例代碼
寫一個ListView布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><ListViewandroid:id="@+id/contacts_view"android:layout_width="match_parent"android:layout_height="match_parent"></ListView></LinearLayout>讀取聯系人邏輯
public class MainActivity extends AppCompatActivity {List<String> contactsList = new ArrayList<>();private ArrayAdapter<String> mAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ListView contactsView = (ListView) findViewById(R.id.contacts_view);mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, contactsList);contactsView.setAdapter(mAdapter);if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {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));contactsList.add(displayName + "\n" + number);}mAdapter.notifyDataSetChanged();}} catch (Exception e) {e.printStackTrace();} finally {if (cursor != null) {cursor.close();}}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {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();}break;default:}}}添加權限
<uses-permission android:name="android.permission.READ_CONTACTS"/>效果圖:
7.4 創建自己的內容提供器
7.4.1 創建內容提供器的步驟
新建一個類去繼承ContentProvider的方式來創建一個自己的內容提供器。
ContentProvider類中有6個抽象方法,子類繼承它的時候,需要將這6個方法全部重寫。
public class MyProvider extends ContentProvider {@Overridepublic boolean onCreate() {return false;}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {return null;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {return null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;} }onCreate()
初始化內容提供器時調用。主要完成對數據庫的創建和升級等操作。
返回true表示內容提供器初始化成功;返回false表示失敗。
備注:只有當存在ContentResolver嘗試訪問我們程序中的數據時,內容提供器才會被初始化。
query()
從內容提供器中查詢數據,查詢的結果存放在Cursor對象中。共接收5個參數:
Uri:確定查詢哪張表
projection:確定查詢哪些列
selection和selectionArgs:約束查詢哪些行
sortOrder:對查詢結果進行排序
insert()
向內容提供器中添加一條數據。共接收2個參數:
Uri:確定要添加到哪張表
values:待添加的數據保存到ContentValues中
返回一個用于表示這條新紀錄的URI。
update()
更新內容提供器中已有的數據。共接收4個參數:
Uri:確定要更新哪張表
values:保存要更新的數據
selection和selectionArgs:約束更新哪些行
受到影響的行數將作為返回值返回。
delete()
從內容提供器中刪除數據。共接收3個參數:
Uri:確定要刪除哪張表中的數據
selection和selectionArgs:約束刪除哪些行
被刪除的行數將作為返回值返回。
getType()
根據傳入內容的URI來返回相應的MIME類型
內容URI主要的兩種格式:
格式一:
content://com.example.app.provider/table1表示訪問com.example.app.provider這個應用的table1表中的數據。
格式二:
content://com.example.app.provider/table2/1表示訪問com.example.app.provider這個應用的table2表中id為1的數據
我們可以使用通配符的方式來分別匹配這兩種格式的內容URI,規則如下:
-
*:表示匹配任意長度的任意字符
-
#:表示匹配任意長度的數字
所以,一個能匹配任意表的內容URI格式就可以寫成:
content://com.example.app.provider/*一個能匹配table1表中任意一行數據的內容URI格式就可以寫成:
content://com.example.app.provider/table1/#緊接著,我們可以借助UriMatcher這個類實現匹配內容URI的功能,UriMatcher提供了一個addURI()方法,該方法共接收3個參數:
-
參數一:authority (一般是應用包名+provider)
-
參數二: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);}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {switch (uriMatcher.match(uri)) {case TABLE1_DIR://查詢table1表中的所有數據break;case TABLE1_ITEM://查詢table1表中的單條數據break;case TABLE2_DIR://查詢table2表中的所有數據break;case TABLE2_ITEM://查詢table2表中的單條數據break;default:break;}return null;}@Overridepublic boolean onCreate() {return false;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {return null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;} }上面只演示了query()方法,其他的insert()、update()、delete()方法實現起來也一樣,都通過UriMatcher的match()方法去判斷訪問方期望訪問哪張表。
MIME類型
getType()方法會根據Uri返回一個MIME類型,一個URI所對應的MIME字符串主要由3個部分組成:
-
必須以vnd開頭
-
如果內容URI以路徑結尾,則后接android.cursor.dir/;如果內容URI已id結尾,則后接android.cursor.item/。
-
最后接上vnd..
所以,對于:
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 String getType(@NonNull 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";default:break;}return null; }7.4.2 實現跨程序數據共享
在上一章DatabaseTest項目上繼續開發,新建一個內容提供器,右擊com.example.broadcasttest包—>New—>Other—>Content Provider。
自定義內容提供器代碼如下:
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 UriMatcher mUriMatcher;public static final String AUTHORITY = "com.example.databasetest.provider";static {mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);mUriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);mUriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);mUriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);mUriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);}private MyDatabaseHelper mDbHelper;public DatabaseProvider() {}@Overridepublic boolean onCreate() {mDbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {//查詢數據SQLiteDatabase db = mDbHelper.getReadableDatabase();Cursor cursor = null;switch (mUriMatcher.match(uri)) {case BOOK_DIR:cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);break;case BOOK_ITEM://getPathSegments()會將內容URI權限之后的部分以"/"符號進行分割,分割后的結果放入到一個//字符串列表中,列表的第0個位置存放的是路徑,第1個位置存放的是idString 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;}@Overridepublic Uri insert(Uri uri, ContentValues values) {//添加數據SQLiteDatabase db = mDbHelper.getWritableDatabase();Uri uriReturn = null;switch (mUriMatcher.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 newCategoryId = db.insert("Category", null, values);uriReturn = Uri.parse("content://" + AUTHORITY + "/category" + newCategoryId);break;default:break;}return uriReturn;}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {//更新數據SQLiteDatabase db = mDbHelper.getWritableDatabase();int updateRows = 0;switch (mUriMatcher.match(uri)) {case BOOK_DIR:updateRows = db.update("Book", values, selection, selectionArgs);break;case BOOK_ITEM:String bookId = uri.getPathSegments().get(1);updateRows = db.update("Book", values, "id = ?", new String[]{bookId});break;case CATEGORY_DIR:updateRows = db.update("Category", values, selection, selectionArgs);break;case CATEGORY_ITEM:String categoryId = uri.getPathSegments().get(1);updateRows = db.update("Category", values, "id = ?", new String[]{categoryId});break;default:break;}return updateRows;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {// 刪除數據SQLiteDatabase db = mDbHelper.getWritableDatabase();int deleteRows = 0;switch (mUriMatcher.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 categoryId = uri.getPathSegments().get(1);deleteRows = db.delete("Category", "id = ?", new String[]{categoryId});break;default:break;}return deleteRows;}@Overridepublic String getType(Uri uri) {switch (mUriMatcher.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";default:break;}return null;} }DatabaseTest項目的完整代碼我已上傳我到gitHub,有需要的朋友可以進入查看:
點擊查看DatabaseTest項目
訪問內容提供器中的數據:
public class MainActivity extends AppCompatActivity {private String mNewId;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//添加數據Button addData = (Button) findViewById(R.id.add_data);addData.setOnClickListener(new View.OnClickListener() {@Overridepublic 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", "George Martin");values.put("pages", 1040);values.put("price",22.85);Uri newUri = getContentResolver().insert(uri, values);mNewId = newUri.getPathSegments().get(1);}});//查詢數據Button queryData = (Button) findViewById(R.id.query_data);queryData.setOnClickListener(new View.OnClickListener() {@Overridepublic 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("MainActivity", "book name is " + name);Log.d("MainActivity", "book author is " + author);Log.d("MainActivity", "book pages is " + pages);Log.d("MainActivity", "book price is " + price);}}}});//更新數據Button updateData = (Button) findViewById(R.id.update_data);updateData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + mNewId);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 deleteData = (Button) findViewById(R.id.delete_data);deleteData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + mNewId);getContentResolver().delete(uri, null, null);}});//清空表中的數據Button clearData = (Button) findViewById(R.id.clear_data);clearData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Uri uri = Uri.parse("content://com.example.databasetest.provider/book/");int delete = getContentResolver().delete(uri, null, null);Log.d("MainActivity", "delete = " + delete);}});} }ProvideTest程序是去訪問DatabaseTest中的數據,完整代碼我已上傳我到gitHub,有需要的朋友可以進入查看:
點擊查看ProvideTest項目
7.5 Git時間
這里推薦一個Git詳細教程。
Git使用教程
7.6 小結點評
本章了解了Android的權限機制,并且學會了如何在6.0以上的系統中使用運行時的權限,然后又重點學習了內容提供器的相關內容,以實現跨程序共享的功能。
非常感謝您的耐心閱讀,希望我的文章對您有幫助。歡迎點評、轉發或分享給您的朋友或技術群。
總結
以上是生活随笔為你收集整理的第一行代码学习笔记第七章——探究内容提供器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一行代码学习笔记第六章——详解持久化技
- 下一篇: 第一行代码学习笔记第八章——运用手机多媒