Android应用之间数据的交互(一)获取系统应用的数据

在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排序顺序

基本流程

那么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

ApplicationContentResolveracquireUnstableProvider方法源码如下:

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

ApplicationContentResolveracquireUnstableProvider方法调用了ActivityThreadacquireProvider方法,源码如下:

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来获取。

关于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

参考文章:

转载于:https://juejin.im/post/5ce406896fb9a07ea6484876

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值