欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android7.0中关于ContentProvider组件详解

程序员文章站 2022-04-30 12:27:40
作为android的四大组件之一,contentprovider作为进程之间静态数据传递的重要手段,其在系统级别的应用中起了重大的作用。毫无疑问,contentprovid...

作为android的四大组件之一,contentprovider作为进程之间静态数据传递的重要手段,其在系统级别的应用中起了重大的作用。毫无疑问,contentprovider核心机制之一也是binder,但是和其它3大组件又有区别。因为contentprovider涉及数据的增删查改,当数据量比较大的时候,继续用parcel做容器效率会比较低,因此它还使用了匿名共享内存的方式。

但是有一个问题是,contentprovider的提供者进程不再存活时,其他进程通过provider读一个非常简单的数据时,都需要先把提供者进程启动起来(除非指定multiprocess=true),这对用户是相当不友好的。又因为其是间接通过db进行数据操作,所以效率也远不如直接操作db。因此在用户app中,不是很建议经常使用contentprovider。不过对于系统级的app,它统一了数据操作的规范,利是远大于弊的。

contentprovider发布

当进程第一次启动时候会调用handlebindapplication

if (!data.restrictedbackupmode) {
        if (!arrayutils.isempty(data.providers)) {
          installcontentproviders(app, data.providers);
        }
      }

当xml中有provider时,进行provider的发布

final arraylist<iactivitymanager.contentproviderholder> results =
      new arraylist<iactivitymanager.contentproviderholder>();
    for (providerinfo cpi : providers) {
      iactivitymanager.contentproviderholder cph = installprovider(context, null, cpi,
          false /*noisy*/, true /*noreleaseneeded*/, true /*stable*/);
      if (cph != null) {
        cph.noreleaseneeded = true;
        results.add(cph);
      }
    }
    try {
      activitymanagernative.getdefault().publishcontentproviders(
        getapplicationthread(), results);
    } catch (remoteexception ex) {
    }

@installprovider(这个方法先简单过一下,后面会继续说)

final java.lang.classloader cl = c.getclassloader();
        localprovider = (contentprovider)cl.
          loadclass(info.name).newinstance();
        provider = localprovider.geticontentprovider();

@installproviderauthoritieslocked

for (string auth : auths) {
      final providerkey key = new providerkey(auth, userid);
      final providerclientrecord existing = mprovidermap.get(key);
      if (existing != null) {
      } else {
        mprovidermap.put(key, pcr);
      }
    }

这里两步把providerinfo通过installprovider转换成contentprovider的binder对象icontentprovider,并放于contentproviderholder中。并根据auth的不同,把发布进程的providerclientrecord保存在一个叫mprovidermap的成员变量中,方便第二次调用同一个contentprovider时,无需重新到ams中去查询。

ams @publishcontentproviders

final int n = providers.size();
      for (int i = 0; i < n; i++) {
        contentproviderholder src = providers.get(i);
        ...
        contentproviderrecord dst = r.pubproviders.get(src.info.name);
        if (dst != null) {
          componentname comp = new componentname(dst.info.packagename, dst.info.name);
          mprovidermap.putproviderbyclass(comp, dst);
          string names[] = dst.info.authority.split(";");
          for (int j = 0; j < names.length; j++) {
            mprovidermap.putproviderbyname(names[j], dst);
          }
          int launchingcount = mlaunchingproviders.size();
          int j;
          boolean wasinlaunchingproviders = false;
          for (j = 0; j < launchingcount; j++) {
            if (mlaunchingproviders.get(j) == dst) {
              mlaunchingproviders.remove(j);
              wasinlaunchingproviders = true;
              j--;
              launchingcount--;
            }
          }
          if (wasinlaunchingproviders) {
            mhandler.removemessages(content_provider_publish_timeout_msg, r);
          }
          ...
        }
      }

可以看到,ams会遍历所有的contentproviderholder,然后调用mprovidermap把信息保存起来,这块接下来说。保存好之后,先去看看之前是不是已经有launch过的,如果已经有launch过的,不再重复launch。再说说这个mprovidermap,这个和activitythread中的mprovidermap不太一样,这个是一个成员实例,非真正的map。看看putproviderbyclass和putproviderbyname。

providermap@putproviderbyclass

if (record.singleton) {
      msingletonbyclass.put(name, record);
    } else {
      final int userid = userhandle.getuserid(record.appinfo.uid);
      getprovidersbyclass(userid).put(name, record);
    }

providermap@putproviderbyname

if (record.singleton) {
      msingletonbyname.put(name, record);
    } else {
      final int userid = userhandle.getuserid(record.appinfo.uid);
      getprovidersbyname(userid).put(name, record);
    }

可以看到,发布的provider实际会根据class或authority存在不同的map中。如果是单例,则分别存到相应的msingleton map中,否则就根据userid存到相应的map中。这样发布的过程就完成了,其他进程需要使用的时候将会在ams按需读取。

contentreslover跨进程数据操作

当我们跨进程调用数据时候,会先调用获取用户进程的contentresolver

context.getcontentresolver().query(uri, ...);
 public contentresolver getcontentresolver() {
    return mcontentresolver;
  }

而这个contentresolver在每个进程中都存在有且唯一的实例,其在contextimpl构造函数中就已经初始化了,其初始化的实际对象是applicationcontentresolver。

mcontentresolver = new applicationcontentresolver(this, mainthread, user);

这个contentresolver是活在调用者进程中的,它是作为一个类似桥梁的作用。以插入为例:

contentresolver@insert

icontentprovider provider = acquireprovider(url);
    if (provider == null) {
      throw new illegalargumentexception("unknown url " + url);
    }
    try {
      long starttime = systemclock.uptimemillis();
      uri createdrow = provider.insert(mpackagename, url, values);
      ...
      return createdrow;
    } catch (remoteexception e) {
      return null;
    } finally {
      releaseprovider(provider);
    }

问题就转化成了,拿到其他进程的contentprovider的binder对象,有了binder对象就可以跨进程调用其方法了。

contentresolver@acquireprovider

if (!scheme_content.equals(uri.getscheme())) {
      return null;
    }
    final string auth = uri.getauthority();
    if (auth != null) {
      return acquireprovider(mcontext, auth);
    }

校验其uri,其scheme必须为content。

applicationcontentresolver@acquireprovider

protected icontentprovider acquireprovider(context context, string auth) {
      return mmainthread.acquireprovider(context,
          contentprovider.getauthoritywithoutuserid(auth),
          resolveuseridfromauthority(auth), true);
    }

这里面有个特别的函数会传递一个true的参数给activitythread,这意味本次连接是stable的。那stable和非stable的区别是什么呢?这么说吧:

stable provider:若使用过程中,provider要是挂了,你的进程也必挂。

unstable provider:若使用过程中,provider要是挂了,你的进程不会挂。但你会收到一个deadobjectexception的异常,可进行容错处理。

继续往下。

activitythread@acquireprovider

 final icontentprovider provider = acquireexistingprovider(c, auth, userid, stable);
    if (provider != null) {
      return provider;
    }

    iactivitymanager.contentproviderholder holder = null;
    try {
      holder = activitymanagernative.getdefault().getcontentprovider(
          getapplicationthread(), auth, userid, stable);
    } catch (remoteexception ex) {
    }
    if (holder == null) {
      return null;
    }

    holder = installprovider(c, holder, holder.info,
        true /*noisy*/, holder.noreleaseneeded, stable);
    return holder.provider;

这里面分了三步,1、寻找自身进程的缓存,有直接返回。 2、缓存没有的话,寻找ams中的provider。3、installprovider,又到了这个方法。怎么个install法?还是一会儿再说。

@acquireexistingprovider (寻找自身缓存)

synchronized (mprovidermap) {
      final providerkey key = new providerkey(auth, userid);
      final providerclientrecord pr = mprovidermap.get(key);
      if (pr == null) {
        return null;
      }
      icontentprovider provider = pr.mprovider;
      ibinder jbinder = provider.asbinder();
      ...
      providerrefcount prc = mproviderrefcountmap.get(jbinder);
      if (prc != null) {
        incproviderreflocked(prc, stable);
      }
      return provider;

这一步就是读取我们发布时提到的mprovidermap中的缓存。当provider记录存在,且进程存活的情况下,则在provider引用计数不为空时则继续增加引用计数。

缓存不存在,则去ams中找

ams@getcontentproviderimpl

contentproviderrecord cpr;
cpr = mprovidermap.getproviderbyname(name, userid);
if (providerrunning){
  if (r != null && cpr.canrunhere(r)) {
          contentproviderholder holder = cpr.newholder(null);
          holder.provider = null;
          return holder;
        }
}
 public boolean canrunhere(processrecord app) {
    return (info.multiprocess || info.processname.equals(app.processname))
        && uid == app.info.uid;
  }

provider是提供保护数据的接入访问的。一般情况下,不同进程的访问只能通过ipc来进行,但那是有些情况是可以允许访问者在自己的进程中创建本地provider来进行访问的。

这种情况是在uid必须相同的前提下,要么同一进程,要么provider设定了multiprocess为true。

if (!providerrunning) {
      cpi = appglobals.getpackagemanager().resolvecontentprovider(name,
          stock_pm_flags | packagemanager.get_uri_permission_patterns, userid);
      ...
      componentname comp = new componentname(cpi.packagename, cpi.name);
      cpr = mprovidermap.getproviderbyclass(comp, userid);
      if (r != null && cpr.canrunhere(r)) {
        return cpr.newholder(null);
      }
      processrecord proc = getprocessrecordlocked(
              cpi.processname, cpr.appinfo.uid, false);

          if (proc != null && proc.thread != null) {
            if (!proc.pubproviders.containskey(cpi.name)) {
              proc.pubproviders.put(cpi.name, cpr);
              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);
          }
        } 
      }
      mprovidermap.putproviderbyname(name, cpr);
    }

这块步骤比较多,挑重点就是,先从ams的providermap对象中获取ams缓存。获得后如果provider没有launch,则ams通知其进程install其provider。如果进程不存在,则新孵化一个进程。

@installprovider

回到第三步中的installprovider

private iactivitymanager.contentproviderholder installprovider(context context,
      iactivitymanager.contentproviderholder holder, providerinfo info,
      boolean noisy, boolean noreleaseneeded, boolean stable)

可以看到,这个方法里面有6个参数,其中包含contentproviderholder、providerinfo、noreleaseneeded,这几个很重要的参数。

contentproviderholder:当参数为空的时候,说明缓存为空,也就意味着是进程启动的时候调用发布provider。当缓存不为空的时候,还得做一些处理。

providerinfo:包含provider的一些信息,不能为空。

noreleaseneeded:为true的时候provider对于自身进程来说或系统的provider,是永久install的,也就是不会被destory的。

contentprovider localprovider = null;
    icontentprovider provider;
    if (holder == null || holder.provider == null) {
      try {
        final java.lang.classloader cl = c.getclassloader();
        localprovider = (contentprovider)cl.
          loadclass(info.name).newinstance();
        provider = localprovider.geticontentprovider();
        if (provider == null) {
          return null;
        }
        localprovider.attachinfo(c, info);
      } catch (java.lang.exception e) {
      }
    } else {
      provider = holder.provider;
    }

这部分在发布的时候已经说了,缓存holder为null的时候,new一个实例。

iactivitymanager.contentproviderholder retholder;
    synchronized (mprovidermap) {
      ibinder jbinder = provider.asbinder();
      if (localprovider != null) {
        componentname cname = new componentname(info.packagename, info.name);
        providerclientrecord pr = mlocalprovidersbyname.get(cname);
        if (pr != null) {
          provider = pr.mprovider;
        } else {
          holder = new iactivitymanager.contentproviderholder(info);
          holder.provider = provider;
          holder.noreleaseneeded = true;
          pr = installproviderauthoritieslocked(provider, localprovider, holder);
          mlocalproviders.put(jbinder, pr);
          mlocalprovidersbyname.put(cname, pr);
        }
        retholder = pr.mholder;
      } else {
        ...
      }

如果localprovider不等于null,则意味着是new一个实例的情况,这时候还是先去获取缓存,没有的话再真正地new一个contentproviderholder实例,并把通过installproviderauthoritieslocked方法把相关信息存入mprovidermap中,这个就是对应发布provider提的那个方法。

iactivitymanager.contentproviderholder retholder;
    synchronized (mprovidermap) {
      ...
      } else {
        providerrefcount prc = mproviderrefcountmap.get(jbinder);
        if (prc != null) {
          if (!noreleaseneeded) {
            incproviderreflocked(prc, stable);
            try {
              activitymanagernative.getdefault().removecontentprovider(
                  holder.connection, stable);
            } 
          }
        } else {
          providerclientrecord client = installproviderauthoritieslocked(
              provider, localprovider, holder);
          if (noreleaseneeded) {
            prc = new providerrefcount(holder, client, 1000, 1000);
          } else {
            prc = stable
                ? new providerrefcount(holder, client, 1, 0)
                : new providerrefcount(holder, client, 0, 1);
          }
          mproviderrefcountmap.put(jbinder, prc);
        }
        retholder = prc.holder;
      }

如果localprovider等于空,也就意味着有holder缓存或者new时候出现的异常。那先从计数map中取缓存,如果缓存不为空(之前有过计数了),这时候如果设置了noreleaseneeded,那就说明不需要计数。如果noreleaseneeded为false,则把计数器数据转移到一个新引用上,同时销毁旧的。

如果缓存为空,说明之前没有计数过。那还是先通过installproviderauthoritieslocked把信息保存到mprovidermap中。这时候如果noreleaseneeded为true,把stable和非stable的数据都瞎设置了一个1000,反正用不到。。。否则就相应的+1,并把计数器放入相应的缓存中。最后再把holder返回。

再回到contentresolver方法中,我们拿到了provider的binder引用,就可以执行相应的方法了。