【Android】20.0 許可權處理(二)——訪問其他程式資料:讀取聯絡人資訊
1.0 在我的上一篇,相當於執行時許可權的初體驗,這一篇主要是怎麼訪問其他程式的資料,比如接下來的這個例子,將手機聯絡人應用中的聯絡人資料,讀取出來,在APP中展示。
2.0 一個應用程式通過內容提供器可以對其資料提供外部訪問介面,其他任何應用程式都可以對這部分資料進行訪問。
Android系統自帶的電話簿、簡訊、媒體庫等程式都提供了類似的訪問介面,這使得第三方應用程式可以充分地利用這部分資料來實現更好的功能。
3.0對於每一個應用程式來說,如果想訪問內容提供器中共享的資料,就一定要藉助ContentResolver類,可以通過Context中的getContentResolve方法獲得該類的例項。
ContentResolver中提供了一系列的方法用於增刪查改操作。不錯,和SQLiteDatabase很相似。
不同於SQLiteDatabase,ContentResolver使用的是內容URI引數。
標準格式為:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
內容URI主要由2部分組成: authority和path 。
-
authority:用於對不同應用程式做區分的,一般為了避免衝突,都會採用程式包名的方式來命名。
比如某個程式包名為
com.example.app
,該程式的authority就可以命名為com.example.app.provider
-
path:用於對同一應用程式中不同的表做以區分,通常新增在authority後面。
4.0 閒話放最後說,先上車直接幹程式碼,新建專案ContactsTest,目錄如下:

2019-02-19_222711.png
5.0 我們現在模擬器聯絡人中新增兩位聯絡人資料:

2019-02-19_214113.png

2019-02-19_214103.png
6.0 在佈局檔案activity_main.xml新增一個ListView,這裡就不用RecyclerView,雖好但也增加了程式碼量,這裡重點也不在這個。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ListView android:id="@+id/contacts_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"></ListView> </android.support.constraint.ConstraintLayout>
7.0 接著修改MainActivity.java中的程式碼:
package com.example.contactstest; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.provider.ContactsContract; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { ArrayAdapter<String> adapter; List<String> contactsList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //老老實實3步佈置好一個簡單的ListView列表介面卡。 ListView contactsView = (ListView) findViewById(R.id.contacts_view); adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList); contactsView.setAdapter(adapter); if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { //注意,換了許可權,READ_CONTACTS危險許可權,獲取聯絡人資訊 ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1); } else { readContacts(); } } private void readContacts() { Cursor cursor = null; try { //查詢聯絡人資料 //這裡沒有傳入一個URI引數,沒有呼叫Uri.parse()方法去解析一個內容URI字串 //ContactsContract.CommonDataKinds.Phone類已經做好了封裝 //.CONTENT_URI常量就是Uri.parse()解析出來的結果 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 { if (cursor != null) { cursor.close(); } } } //呼叫完requestPermissions()方法後,系統會彈出一個許可權申請的對話方塊 // 無論結果如何,最終都會回撥onRequestPermissionsResult()方法 //授權的結果,會封裝在grantResults中。 // 判斷一下,如果同意了授權就打電話,沒有就涼涼了…… @Override public void onRequestPermissionsResult(int requestCode,String[] permissions, int[] grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { readContacts(); } else { Toast.makeText(this, "抱歉,沒有該許可權!", Toast.LENGTH_SHORT).show(); } } } }
這裡沒有傳入一個URI引數,沒有呼叫Uri.parse()方法去解析一個內容URI字串,ContactsContract.CommonDataKinds.Phone類已經做好了封裝,CONTENT_URI常量就是Uri.parse()解析出來的結果。
最後千萬不要忘記將Cursor物件關閉掉。
9.0 到這裡還差最後一步,讀取系統聯絡人的許可權千萬不能忘記宣告。修改AndroidManifest.xml中的程式碼,增加 <uses-permission android:name="android.permission.READ_CONTACTS"/>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.contactstest"> <uses-permission android:name="android.permission.READ_CONTACTS"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
10.0 執行,如下:

2019-02-19_220635.png
點選“拒絕”:

2019-02-19_220640.png
重新執行該程式,點選“允許”:

2019-02-19_220711.png
11.0 這裡繼續補上枯燥的理論知識。
既然已經知道內容URI字串的格式,那麼載入解析成Uri物件可以作為引數傳入的程式碼格式如下:
Uri uri = Uri.parse("content://com.example.app.provider/table1")
只需要呼叫Uri.parse()方法,就可以將內容URI字串解析成Uri物件。
12.0 查詢table1表中的資料:
Cursor cursor =getContentResolver().query( uri, projection, selection, selectionArgs, sortOrder);
query()方法引數 | 對應的SQL語句部分 | 描述 |
---|---|---|
uri | from table_name | 指定查詢某個應用程式下的某一張表 |
projection | select column1,column2 | 指定查詢的列名 |
selection | where column= value | 指定where的約束條件 |
selectionArgs | —— | 為where中的佔位符提供具體的值 |
sortOrder | order by column1,column2 | 指定查詢結果的排序方式 |
查詢完後返回的也是一個Cursor物件,逐個讀出即可,通過移動遊標位置來遍歷Cursor所有行,然後再去除每一行相應列的資料:
if (cursor != null) { while (cursor.moveToNext()) { String column1= cursor.getString(cursor.getColumnIndex("column1")); int column2 = cursor.getInt(cursor.getColumnIndex("column2")); } cursor.close(); }
13.0 講完最難的查詢操作,增加、修改、刪除操作更不在話下。
向table1表中新增一條資料:
ContentValues values = new ContentValues(); values.put("column1","text"); values.put("column2",1); getContentResolver().insert(uri,values);
可以看到,將待新增的資料封裝到ContentValues 中,然後呼叫getContentResolver的insert()方法,將Uri和ContentValues 作為引數傳入即可。
14.0 向table1表中更新一條資料:
比如把 13.0 中新增的資料中column1的值清空:
ContentValues values = new ContentValues(); values.put("column1",""); getContentResolver().update(uri,values,"column1 = ? and column2 = ?",new String[] {"text","1"});
15.0 最後,把table1表中這條資料刪除掉:
getContentResolver().delete(uri,"column2 = ? ",new String[] {"1"});