杨说:ContentProvider知识点总结

586 阅读9分钟

目录

一、ContentProvider的作用
二、访问提供程序
三、内容URI
四、从提供程序检索数据
五、内容提供程序权限
六、ContentProvider、ContentResolver、ContentObserver 之间的关系
七、其他应用场景
八、源码解析

一、ContentProvider的作用

访问中央数据的一种方式,给其他应用使用。
与文件存储、SharedPreferences存储、SQLite数据库存储这几种数据存储方法不同的是,后者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。

二、访问提供程序

如需访问内容提供程序中的数据,您可以客户端的形式使用应用的 Context 中的 ContentResolver 对象与提供程序进行通信。ContentResolver 对象会与提供程序对象(即实现 ContentProvider 的类的实例)通信。提供程序对象从客户端接收数据请求、执行请求的操作并返回结果。此对象的某些方法可调用提供程序对象(ContentProvider 某个具体子类的实例)中的同名方法。ContentResolver 方法可提供持久性存储空间的基本“CRUD”(创建、检索、更新和删除)功能。

从界面访问 ContentProvider 的常用模式是使用 CursorLoader 在后台运行异步查询。界面中的 Activity 或 Fragment 会调用查询的 CursorLoader,其转而使用 ContentResolver 获取 ContentProvider。如此一来,用户便可在查询运行时继续使用界面

三、内容URL

内容 URI 用来在提供程序中标识数据。内容 URI 包括整个提供程序的符号名称(其授权)和指向表的名称(路径)。当您调用客户端方法以访问提供程序中的表时,该表的内容 URI 将是其参数之一。

在前面的代码行中,常量 CONTENT_URI 包含用户字典的“字词”表的内容 URI。ContentResolver 对象会解析出 URI 的授权,并将该授权与已知提供程序的系统表进行比较,从而“解析”提供程序。然后,ContentResolver 可以将查询参数分派给正确的提供程序。

ContentProvider 使用内容 URI 的路径部分选择需访问的表。通常,提供程序会为其公开的每个表显示一条路径。

content://user_dictionary/words

其中,user_dictionary 字符串是提供程序的授权,words 字符串是表的路径。字符串 content://(架构)始终显示,并且会将其标识为内容 URI。

四、从提供程序检索数据

如需从提供程序检索数据,请按照以下基本步骤执行操作:

4.1、请求对提供程序的读取访问权限

如需从提供程序检索数据,您的应用需具备对提供程序的“读取访问权限”。您无法在运行时请求此权限;相反,您需要使用 元素和提供程序定义的准确权限名称,在清单文件中指明您需要此权限。在清单文件中指定此元素后,您便可有效地为应用“请求”此权限。用户在安装您的应用时,他们会隐式允许此请求。

4.2、定义将查询发送至提供程序的代码

从提供程序检索数据的下一步是构建查询

五、内容提供程序权限

读写分离
权限控制-精确到表级
URL控制

提供程序的应用可以指定其他应用访问提供程序的数据所必需具有的权限。这些权限可确保用户了解应用将尝试访问的数据。根据提供程序的要求,其他应用会请求其访问该提供程序时所需的权限。最终用户会在安装应用时看到所请求的权限。

如果提供程序的应用未指定任何权限,其他应用将无法访问该提供程序的数据。但是,无论指定权限如何,提供程序的应用中的组件始终拥有完整的读取和写入访问权限。

如需获取访问提供程序所需的权限,应用需通过其清单文件中的 元素请求这些权限。Android 软件包管理器安装应用时,用户必须批准该应用请求的所有权限。如果用户批准所有权限,软件包管理器将继续安装;如果用户未批准这些权限,软件包管理器将中止安装

六、ContentProvider、ContentResolver、ContentObserver 之间的关系

6.1 ContentProvider

管理数据,提供数据的增删改查操作,数据源可以是数据库、文件、XML、网络等,ContentProvider为这些数据的访问提供了统一的接口,可以用来做进程间数据共享。

6.2 ContentResolver

ContentResolver可以为不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互

6.3 ContentObserver

观察ContentProvider中的数据变化,并将变化通知给外界

七、其他应用场景

ContentProvider还可以作为sdk提供初始化Context的方式,我们在写sdk的时候通常都是要初始化Context,sdk开发者为了方便我们使用实现一个自定义的ContentProvider来初始化sdk的Context

原理:进程未启动时,ContentProvider的onCreate的方法调用是在Application onCreate方法之前,所以依赖这种特性可以在ContentProvider的onCreate方法中做sdk的初始化操作

缺点:这种方式实现的初始化在,应用层是不能做延迟初始化操作的,会影响应用启动速度

Android现在提供了startup用于统一初始化Context,他的内部也是使用ContentProvider实现,其中有一个单例AppInitializer管理整体的初始化工作,添加了对启动依赖的初始化工作(异步非同步)

八、源码解析

8.1 ContentProvider启动

通常我们使用ContentProvider是从Context中获取,然后调用ContentProvider的insert query delete update 我们拿insert来看

ContextImpl.java

  @Override
  public ContentResolver getContentResolver() {
        return mContentResolver;
    }
    
 private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
            @NonNull LoadedApk packageInfo, @Nullable String splitName,
            @Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
            @Nullable ClassLoader classLoader) {
       ···
        mContentResolver = new ApplicationContentResolver(this, mainThread);
    }

1、mContentResolver是在ContextImpl创建的时候初始化的,它的类型是ApplicationContentResolver继承自ContentResolver, ContentResolver中insert方法

ContentResolver.java

 public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
                @Nullable ContentValues values) {
        Preconditions.checkNotNull(url, "url");
        ····
        //获取调用对象,这里获取的数据可能是夸进程的所以要获取Binder的IContentProvider
        IContentProvider provider = acquireProvider(url);
        Uri createdRow = provider.insert(mPackageName, url, values);
           
        return createdRow;
          ··
    }
    
    
  public final IContentProvider acquireProvider(Uri uri) {
       ···  //这里会调用抽象方法acquireProvider具体实现在ApplicationContentResolver中
            return acquireProvider(mContext, auth);
    }
ApplicationContentResolver.java

 @Override
 protected IContentProvider acquireUnstableProvider(Context c, String auth) {
 			//调用ActivityThread获取IContentProvider
            return mMainThread.acquireProvider(c,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), false);
        }
ActivityThread.java

   public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        //如果本地查找Provider存在直接返回
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
            return provider;
        }
        ···
        ContentProviderHolder holder = null;
        		//向AMS请求ContentProvider
                holder = ActivityManager.getService().getContentProvider(
                        getApplicationThread(), auth, userId, stable);

       ···
       //在应用端本地安装
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

1、本地查找IContentProvider对象
2、找到直接返回,不然请求AMS
3、AMS返回holder,本地安卓

8.1.1 本地查找IContentProvider对象

ActivityThread.java

 public final IContentProvider acquireExistingProvider(
            Context c, String auth, int userId, boolean stable) {
		····
            final ProviderKey key = new ProviderKey(auth, userId);
            //根据key查找
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            //检测Binder对象是否活着
            IBinder jBinder = provider.asBinder();
            if (!jBinder.isBinderAlive()) {
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }
            return provider;
        }
    }

8.1.2 AMS查找IContentProvider对象

ActivityManagerService.java

public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) {
        ····
        return getContentProviderImpl(caller, name, null, stable, userId);
    }
    
 private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
        //如果ContentProviderRecord存在就直接返回
        //如果ContentProviderRecord不存在,就创建一个
        //如若ContentProvider能跑在调用者的进程,就直接返回,否则往下走
        //如果provider所在的进程没启动,就启动进程,然后等待发布,完成的时候返回
        //如果binder对象还没发布就请求发布,然后等待,完成的时候返回
        
        //provider启动了
        if (providerRunning) {
        	  //查看是否可以跑在调用者进程上,包名或者并且UID相同
        	  if (r != null && cpr.canRunHere(r)) {
                    ContentProviderHolder holder = cpr.newHolder(null);
                    holder.provider = null;
                    return holder;
                }
              conn = incProviderCountLocked(r, cpr, token, stable);
        }
        //provider没启动
        if (!providerRunning) {
        	 cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);

             if (r != null && cpr.canRunHere(r)) {
				return cpr.newHolder(null);
             }
             //mLaunchingProviders中找到record
             if (i >= N) {
             	ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);
                //进程没启动
                if (proc != null && proc.thread != null && !proc.killed) {
                	 proc.thread.scheduleInstallProvider(cpi);
                }else {
                //启动进程
                	proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0, "content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name), false, false, false);
                }
             }
             mLaunchingProviders.add(cpr);
        }
         
        //等待provider发布
        synchronized (cpr) {
         //provider还没有发布
       	 while (cpr.provider == null) {
             ···
             cpr.wait();
         }
       }
        return cpr != null ? cpr.newHolder(conn) : null;
}

8.1.3 应用端安装Provider

ActivityThread.java

private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
	//创建一个Context
	 Context c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);
     final java.lang.ClassLoader cl = c.getClassLoader();
     LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    // System startup case.
                    packageInfo = getSystemContext().mPackageInfo;
                }
     ContentProvider localProvider = packageInfo.getAppFactory().instantiateProvider(cl, info.name);
     IContentProvider provider = localProvider.getIContentProvider();
     localProvider.attachInfo(c, info);
     
      ProviderClientRecord pr = mLocalProvidersByName.get(cname);
      ContentProviderHolder retHolder = new ContentProviderHolder(info);
      pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
      mLocalProviders.put(jBinder, pr);
      mLocalProvidersByName.put(cname, pr);
      return retHolder;
}

应用获取provider服务有两种情况
1、应用在请求provider的时候需要获取到provider的Binder对象,provider执行在提供者进程中,内容提供者需要先启动发布Binder到AMS
2、应用uid和提供者的uid相同并且进程名称一致,获取provider信息,在应用进程内创建一个Provider实例,这样就不需要跨进程通信

8.2 ContentProvider发布

8.2.1 主动发布

应用启动会调用AMS attachApplication

ActivityManagerService.java

 public final void attachApplication(IApplicationThread thread, long startSeq) {
        synchronized (this) {
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);

        }
    }
    
  private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
       ····
       //获得应用provider列表,像PMS请求
       List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
		//发布binder
        if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
            Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
            msg.obj = app;
            mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
        }
        //通知应用端
        thread.bindApplication(processName, appInfo, providers....)
 }
ActivityThread.java

 public final void bindApplication(){
  		sendMessage(H.BIND_APPLICATION, data);
 }


 private void handleBindApplication(AppBindData data) {
 	 installContentProviders(app, data.providers);
 }
 
 
   private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            //安装provider
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                results.add(cph);
            }
        }
           //通知AMS发布
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
    }
ActivityManagerService.java

 public final void publishContentProviders(IApplicationThread caller,
            List<ContentProviderHolder> providers) {
        final ProcessRecord r = getRecordForAppLocked(caller);
        for (int i = 0; i < N; i++) {
       		ContentProviderHolder src = providers.get(i);
             ContentProviderRecord dst = r.pubProviders.get(src.info.name);
             //更新provider缓存
             //更新mLaunchingProviders
             //删除超时计时的消息
        	 synchronized (dst) {
                        dst.provider = src.provider;
                        dst.proc = r;
                        //通知等待provider等待发布对象
                        dst.notifyAll();
                    }
        }
            
}


8.2.2 AMS请求发布

ActivityThread.java

public void scheduleInstallProvider(ProviderInfo provider) {
            sendMessage(H.INSTALL_PROVIDER, provider);
        }
        
public void handleInstallProvider(ProviderInfo info) {
            installContentProviders(mInitialApplication, Arrays.asList(info));
    }
    
private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

8.1.3 发布总结

provider没有启动的情况
1、应用A 向AMS请求provider
2、创建进程,启动provider,应用通知AMS attachApplication,AMS 通知provider bindApplication
3、发布provider
4、应用A可以 CRUD

provider启动了
1、应用A,向AMS请求provider
2、返回binder给应用A
3、AMS请求provider发布 scheduleInstallProvider
4、应用发布provider publishContentProvider

参考

1、Android官方文档
2、Android篇:2019初中级Android开发社招面试解答(上)