在Android應用開發中咱們經常須要和其餘應用進行交互,以前對這些問題沒有仔細瞭解過,如今來作一下總結。java
Android應用之間數據的交互方式:android
下面介紹獲取系統應用的數據數據庫
以獲取聯繫人數據爲例,代碼以下:緩存
Cursor cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if (cursor!=null){
while(cursor.moveToNext()){
//獲取聯繫人的名字
String name=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//獲取聯繫人的電話號碼
String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d("============"," name: "+name+" number="+number);
}
cursor.close();
}
複製代碼
ContentResolver
類的query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)
方法參數的介紹:
uri
用來指明要訪問的數據的位置
projection
訪問的列,若是爲null,則表示訪問全部列
selection
用於指定查詢條件
selectionArgs
查詢參數
sortOrder
排序順序
app
那麼ContentResolver
是怎麼經過query
方法來獲取聯繫人的數據的呢?下面咱們來分析ContentResolver
的源碼:異步
//1
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}
//2
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
//把查詢參數封裝在Bundle中
Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
return query(uri, projection, queryArgs, cancellationSignal);
}
//3
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
//獲取不穩定的提供者 IContentProvider是IPC接口用來與內容提供者進行數據交互
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
...//省略些不重要的源碼
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let's try!!!!
// This is exciting!!1!!1!!!!1
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);//第一次建立的是不穩定的,若是失敗就重試
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(
mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();//調用這個方法判斷是否查詢出錯
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);//CursorWrapperInner是內部類
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {//釋放資源
...
}
}
public final IContentProvider acquireUnstableProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {//SCHEME_CONTENT爲content,若是uri的scheme爲null則返回null
return null;
}
String auth = uri.getAuthority();
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());//這裏調用的是ApplicationContentResolver的acquireUnstableProvider方法
}
return null;
}
複製代碼
在ContentResolver
的源碼中query
有三個重載方法1,2,3,在實例中咱們調用重載方法1,方法1中則直接調用方法2。在方法2中,封裝了數據到Bundle
中,並調用了方法3,真正的實如今方法3中。方法3中的核心方法是acquireUnstableProvider(Uri uri)
,它獲取IContentProvider
(一個IPC 接口),並經過IContentProvider
來獲取聯繫人的數據。而acquireUnstableProvider(Uri uri)
調用的是acquireUnstableProvider(Context c, String name)
方法,它是個抽象方法,具體的實現類是ApplicationContentResolver
。ide
ApplicationContentResolver
的acquireUnstableProvider
方法源碼以下:post
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
複製代碼
在ApplicationContentResolver
的acquireUnstableProvider
方法調用了ActivityThread
的acquireProvider
方法,源碼以下:ui
public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) {
// 判斷緩存中是否有對應的IContentProvider,有則直接返回
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
ContentProviderHolder holder = null;
try {
//經過AMS獲取ContentProviderHolder, 處理在不一樣進程的狀況,
//若是在同一進程就讓`holder.provider = null;`
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
//失敗
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
// Install provider will increment the reference count for us, and break
// any ties in the race.
//若是在同一進程,則進行相應的處理
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
複製代碼
ApplicationContentResolver
經過調用ActivityThread的acquireProvider
方法來獲取ContentProvider
;ActivityThread
先會判斷緩存中是否存在要求的IContentProvider
, 若是存在就直接返回,若是不存在就調用AMS的getContentProviderImpl()
和installProvider
來獲取。this
關於AMS獲取IContentProvider的詳細流程,能夠看ContentProvider啓動過程分析
ContactsContract
是存儲聯繫人的數據表的常量字段,它有幾個經常使用的內部類以下:
聯繫人數據是存儲在數據庫中(具體位置在data/data/com.android.providers.contacts/databases中),根據其MIME類型來判斷其位置所表明的意義。如
ContactsContract.CommonDataKinds.Phone.NUMBER
和ContactsContract.CommonDataKinds.Email.ADDRESS
都表示data1
,可是在數據庫中不一樣的MIME類型的data1
表示不一樣的數據。
數據庫中data
表的字段和數據以下(字段太多,只截取了部分)
MIME類型以下
經過MIME
類型來獲取所須要的數據
Uri p = ContactsContract.Data.CONTENT_URI;
Cursor id = getActivity().getContentResolver().query(p,new String[]{ContactsContract.Data.CONTACT_ID},null,null,null);
while (id.moveToNext()){
Cursor cursor=getActivity().getContentResolver().query(p, null,ContactsContract.Data.CONTACT_ID+"= ?", new String[]{id.getString(id.getColumnIndex(ContactsContract.Data.CONTACT_ID))}, null);
Person person = new Person();
byte[] image = null;
StringBuilder builder = new StringBuilder();
while (cursor.moveToNext()){
String mime = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE));
Log.d("===========","mime = "+mime);
if (mime.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)){
builder.append("name = "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
builder.append("number = "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
}else if (mime.equals(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)){
//二進制數據通常放在data15中
image=cursor.getBlob(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.DATA15));
}else if (mime.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)){
builder.append("email = "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)));
}else if (mime.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)){
builder.append("address = "+cursor.getString(cursor.getColumnIndex(ContactsContract.
CommonDataKinds.StructuredPostal.STREET)));
}
}
String con=builder.toString();
Log.d("===========",con);
cursor.close();
}
id.close();
複製代碼
ContentResolver()
還有insert
、delete
、update
方法,其用法與query
相似,這裏再也不介紹。
CalendarContract
是存儲日曆和事件相關信息字段表,與ContactsContract
相似。CalendarContract
也有幾個內部類,經常使用的是CalendarContract.Events
,包含諸如事件標題,位置,開始時間,結束時間等信息。CalendarContract.Events
的使用以下:
Uri p = CalendarContract.Events.CONTENT_URI;
Cursor cursor=getActivity().getContentResolver().query(p, null,null, null, null);
while (cursor.moveToNext()){
StringBuilder builder = new StringBuilder();
builder.append("標題 : "+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.TITLE))+"\n")
.append("起始時間 :"+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DTSTART))+"\n")
.append("結束時間 :"+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DTEND))+"\n")
.append("描述 : "+cursor.getString(cursor.getColumnIndex(CalendarContract.Events.DESCRIPTION))+"\n");
contents.add(builder.toString());
}
cursor.close();
複製代碼
更多關於CalendarContract
的使用,能夠查看官方文檔。
MediaStore
包含內部和外部存儲設備上全部可用媒體的元數據。其內部類以下:
List<String> contents = new ArrayList<>();
Uri p1 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor1=getActivity().getContentResolver().query(p1, null,null, null, null);
while (cursor1.moveToNext()){
StringBuilder builder = new StringBuilder();
builder.append("文件名 : "+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.TITLE))+"\n")
.append("描述 :"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DESCRIPTION))+"\n")
.append("大小 :"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.SIZE))+"\n")
.append("位置 : "+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DATA))+"\n")
.append("文件修改時間:"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED))+"\n")
.append("DISPLAY_NAME :"+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))+"\n")
.append("時間 : "+cursor1.getString(cursor1.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN))+"\n");
contents.add(builder.toString());
}
cursor1.close();
複製代碼
Settings
與XXXContract
不一樣,它是經過xml文件來存儲數據,在文件/data/system/users/0/
目錄下,獲取設置的方式以下:
StringBuilder builder = new StringBuilder();
ContentResolver contentResolver=getActivity().getContentResolver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
builder.append("wifi是否開啓:"+Settings.Global.getString(contentResolver,Settings.Global.WIFI_ON)+"\n")
.append("數據流量是否開啓:"+Settings.Global.getString(contentResolver,Settings.Global.DATA_ROAMING)+"\n")
.append(Settings.System.NOTIFICATION_SOUND+" "+Settings.System.getString(contentResolver,Settings.System.NOTIFICATION_SOUND)).append("\n")
.append(Settings.System.SCREEN_BRIGHTNESS+" "+Settings.System.getString(contentResolver,Settings.System.SCREEN_BRIGHTNESS)).append("\n")
.append(Settings.System.TEXT_SHOW_PASSWORD+" "+Settings.System.getString(contentResolver,Settings.System.TEXT_SHOW_PASSWORD)).append("\n");
}
Log.d("==============",builder.toString());
複製代碼
更多關於Setting
的內容能夠看Android系統APP之SettingsProvider
注意:以上的操做都是須要申請權限的
若是請求數據的操做太過耗時,可能形成ANR,所以須要異步處理。異步處理的方式有:
參考文章: