Android應用之間數據的交互(一)獲取系統應用的數據

在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)方法,它是個抽象方法,具體的實現類是ApplicationContentResolveride

ApplicationContentResolveracquireUnstableProvider方法源碼以下:post

@Override
 protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c,
                  ContentProvider.getAuthorityWithoutUserId(auth),
                  resolveUserIdFromAuthority(auth), false);
       }
複製代碼

ApplicationContentResolveracquireUnstableProvider方法調用了ActivityThreadacquireProvider方法,源碼以下: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詳解

ContactsContract是存儲聯繫人的數據表的常量字段,它有幾個經常使用的內部類以下:

  • ContactsContract.Data:數據表的常量,包含與原始聯繫人關聯的數據點。 數據表的每一行一般用於存儲單條聯繫信息(例如電話號碼)及其相關元數據(例如,它是工做號碼仍是家庭號碼)
  • ContactsContract.CommonDataKinds:用於定義存儲在ContactsContract.Data表中的公共數據類型的容器。
  • ContactsContract.Contacts:聯繫人表的常量,包含表明同一我的的每一個原始聯繫人聚合的記錄

聯繫人數據是存儲在數據庫中(具體位置在data/data/com.android.providers.contacts/databases中),根據其MIME類型來判斷其位置所表明的意義。如 ContactsContract.CommonDataKinds.Phone.NUMBERContactsContract.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()還有insertdeleteupdate方法,其用法與query相似,這裏再也不介紹。

獲取其餘系統應用數據

CalendarContract

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

MediaStore包含內部和外部存儲設備上全部可用媒體的元數據。其內部類以下:

  • MediaStore.Audio:集裝箱全部的音頻內容。
  • MediaStore.Files:媒體提供程序表,包含媒體存儲中全部文件的索引,包括非媒體文件。
  • MediaStore.Images:包含全部可用圖像的元數據
  • interface MediaStore.MediaColumns:大多數MediaProvider表的公共字段
  • MediaStore.Video:包含全部可用視頻的元數據
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

SettingsXXXContract不一樣,它是經過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,所以須要異步處理。異步處理的方式有:

  • Thread + Handler
  • AsyncTask
  • Loader
  • RxJava

參考文章:

相關文章
相關標籤/搜索