ContentProvider 总结

ContentProvider 总结

时序图:

白色:client,发起provider的进程

红色:systemserver进程

蓝色:server,提供provider的进程

1.流程1-4在client进程(进程A)中,一般来说APP在进程启动之后的初始化时,会installProvider(流程7-10),如果APP请求的provider在当前进程,那么到4就能获取到

2.如果请求的provider在另外一个进程(进程B)中,则会走到流程5-6

3.查询到provider信息之后,如果需要跨进程调用,则通过ContentProviderProxy发起binder call到对方进程执行query。在这中,AMS充当中间管理员,

每个进程在启动之后需要把自己应该install的provider install之后publish给AMS。后面其他进程请求这个provider的话,AMS可以直接告诉你所请求的对方的信息

进程存在但provider未publish,provider进程存在但provide的记录对象cpr ==null,需要向AMS publish:

  • Client进程在获取provider的过程,发现cpr为空,则调用scheduleInstallProvider来向provider所在进程发出一个oneway的binder请求,并进入wait()状态.
  • provider进程安装完provider信息,则notifyAll()处于等待状态的进程/线程;

4.如果进程B存在且已经publish ,则AMS.getContentProviderImpl,进入installProvider过程,直接返回给进程A 对方的provider信息,不进入到wait()流程

5.进程不存在,需要先创建provider的进程,再publish他的provider:

源码

ActivityThread. installContentProviders

在APP的进程启动的时候,handleBindApplication中会触发installContentProviders:

markdown 复制代码
private void installContentProviders(

        Context context, List<ProviderInfo> providers) {

    // 此处的provider信息是在AMS启动进程时

    // 从manifest收集到的需要install的provider信息

    final ArrayList<ContentProviderHolder> results = new ArrayList<>();

    for (ProviderInfo cpi : providers) {

        if (DEBUG_PROVIDER) {

            StringBuilder buf = new StringBuilder(128);

            buf.append("Pub ");

            buf.append(cpi.authority);

            buf.append(": ");

            buf.append(cpi.name);

            Log.i(TAG, buf.toString());

        }

        // 执行installProvider,注意此处的stable参数默认为true

        ContentProviderHolder cph = installProvider(context, null, cpi,

                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);

        if (cph != null) {

            cph.noReleaseNeeded = true;

            results.add(cph);

        }

    }



    try {

        // install完成之后,要告诉AMS

        ActivityManager.getService().publishContentProviders(

            getApplicationThread(), results);

    } catch (RemoteException ex) {

        throw ex.rethrowFromSystemServer();

    }

}

ActivityThread.installProvider的具体实现

markdown 复制代码
    private ContentProviderHolder installProvider(Context context,

            ContentProviderHolder holder, ProviderInfo info,

            boolean noisy, boolean noReleaseNeeded, boolean stable) {

        ContentProvider localProvider = null;

        IContentProvider provider;//Transport

        // holder为null表示还没有install过

        if (holder == null || holder.provider == null) {

            Context c = null;

            ApplicationInfo ai = info.applicationInfo;

            if (context.getPackageName().equals(ai.packageName)) {

                c = context;

            } else if (mInitialApplication != null &&

                    mInitialApplication.getPackageName().equals(ai.packageName)) {

                c = mInitialApplication;

            } else {

                try {

                // 创建context

                    c = context.createPackageContext(ai.packageName,

                            Context.CONTEXT_INCLUDE_CODE);

                } catch (PackageManager.NameNotFoundException e) {

                    // Ignore

                }

            }

            ...

            try {

                final java.lang.ClassLoader cl = c.getClassLoader();

                // 通过反射创建provider对象

                localProvider = (ContentProvider)cl.

                    loadClass(info.name).newInstance();

                // 获取IContentProvider对象,用于跨进程binder call

                //返回Transport对象,是ContentProviderNative子类,用来binder通信

                provider = localProvider.getIContentProvider();

                if (provider == null) {

                    Slog.e(TAG, "Failed to instantiate class " +

                          info.name + " from sourceDir " +

                          info.applicationInfo.sourceDir);

                    return null;

                }

                // provider的attach,其中最重要的是会执行provider的onCreate

                localProvider.attachInfo(c, info);

            } catch (java.lang.Exception e) {

                if (!mInstrumentation.onException(null, e)) {

                    throw new RuntimeException(

                            "Unable to get provider " + info.name

                            + ": " + e.toString(), e);

                }

                return null;

            }

        } else {

            provider = holder.provider;

        }



        // 到这里,provider的对象创建好了,那么接下来需要做的就是数据结构的封装

        // 把provider相关信息保存起来

        ContentProviderHolder retHolder;

        // mProviderMap的key时providerKey,value是ProviderClientReocrd

        // 这两个类主要是封装了一些provider的基本信息

        synchronized (mProviderMap) {

            IBinder jBinder = provider.asBinder();

            if (localProvider != null) {

                ComponentName cname = new ComponentName(info.packageName, info.name);

                // mLocalProvidersByName的key是component信息,value是对应的ProviderClientReocrd

                ProviderClientRecord pr = mLocalProvidersByName.get(cname);

                if (pr != null) {

                    // 不为空代表install过

                    provider = pr.mProvider;

                } else {

                    // 对于新创建的provider,创建其对应的ContentProviderHolder对象

                    holder = new ContentProviderHolder(info);

                    holder.provider = provider;

                    holder.noReleaseNeeded = true;

                    // install Authorities

                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);

                    // mLocalProviders的key是IContentProvider的binder对象,value是ProviderClientRecord

                    // 将封装好的provider放入map中

                    mLocalProviders.put(jBinder, pr);

                    mLocalProvidersByName.put(cname, pr);

                }

                retHolder = pr.mHolder;

            } else {

                // mProviderRefCountMap的key是binder对象,value是ProviderRefCount

 // ProviderRefCount中记录了这个provider的stable和unstable的数量

                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);

                if (prc != null) {

                    if (!noReleaseNeeded) {

                        incProviderRefLocked(prc, stable);

                        try {

                            ActivityManager.getService().removeContentProvider(

                                    holder.connection, stable);

                        } catch (RemoteException e) {

                        }

                    }

                } else {

                    ProviderClientRecord client = installProviderAuthoritiesLocked(

                            provider, localProvider, holder);

                    if (noReleaseNeeded) {

                        // 创建ProviderRefCount

                        prc = new ProviderRefCount(holder, client, 1000, 1000);

                    } else {

                        // 根据参数中的stable和unstable创建对象

                        prc = stable

                                ? new ProviderRefCount(holder, client, 1, 0)

                                : new ProviderRefCount(holder, client, 0, 1);

                    }

                    // 放入map

                    mProviderRefCountMap.put(jBinder, prc);

                }

                retHolder = prc.holder;

            }

        }

        return retHolder;

    }
scala 复制代码
public abstract class ContentProvider implements ComponentCallbacks2 {

    //mTransport在ContentProvider中,表示ContentProvider对象创建时它就创建了

    private Transport mTransport = new Transport();

    class Transport extends ContentProviderNative {

        ContentProvider getContentProvider() {

            return ContentProvider.this;

        }

    }

    //获取Transport

    public IContentProvider getIContentProvider() {

        return mTransport;

    }

}

ActivityThread.installProviderAuthoritiesLocked

markdown 复制代码
    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,

            ContentProvider localProvider, ContentProviderHolder holder) {

        final String auths[] = holder.info.authority.split(";");

        final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);



        if (provider != null) {

            for (String auth : auths) {

                // 对于一些特殊的auth,允许跨进程binder call

                // Binder.allowBlocking代表允许执行同步的binder call

                switch (auth) {

                    case ContactsContract.AUTHORITY:

                    case CallLog.AUTHORITY:

                    case CallLog.SHADOW_AUTHORITY:

                    case BlockedNumberContract.AUTHORITY:

                    case CalendarContract.AUTHORITY:

                    case Downloads.Impl.AUTHORITY:

                    case "telephony":

                        Binder.allowBlocking(provider.asBinder());

                }

            }

        }

        // 创建ProviderClientRecord

        final ProviderClientRecord pcr = new ProviderClientRecord(

                auths, provider, localProvider, holder);

        for (String auth : auths) {

            // 根据auth和userId创建ProviderKey,放入mProviderMap

            final ProviderKey key = new ProviderKey(auth, userId);

            final ProviderClientRecord existing = mProviderMap.get(key);

            if (existing != null) {

                Slog.w(TAG, "Content provider " + pcr.mHolder.info.name

                        + " already published as " + auth);

            } else {

                mProviderMap.put(key, pcr);

            }

        }

        return pcr;

    }

小结:

  1. 创建了provider对象,其中也创建了IContentProvider对象(Transport)

  1. 创建ContentProviderHolder
  2. 创建ProviderKey(根据auth和userId创建,放入mProviderMap)
  3. 创建ProviderClientRecord,这是一个provider在client进程中对应的对象,分别放入mProviderMap/mLocalProviders/mLocalProvidersByName
  4. 创建ProviderRefCount,放入mProviderRefCountMap( mProviderRefCountMap的key是binder对象,value是ProviderRefCount, ProviderRefCount中记录了这个provider的stable和unstable的数量)

stable和unstable:

代表的是client和server的链接,主要取决于获取provider的传参,默认情况下,insert/update/delete建立的链接都是stable,而query则是unstable,不过在query的时候如果失败,还会重新创建stable

stable和unstable最重大的差别在于unstable的情况下,即使对端挂掉了,client也没关系,但是stable的话,如果对端进程挂掉了,client也会被跟着级联kill掉。

一个问题

XX进程 作为securitycenter的client端 ,因为低内存导致securitycenter died ,而被杀掉了。

07-16 08:15:55.683 2523 6648 I XXX.securitycenter/1000(26179): invisible->died(168916ms) R(process died ) adj=900.

07-16 08:15:55.683 2523 6648 I ActivityManager: Process XXX.securitycenter (pid 26179) has died: cch CRE

07-16 08:15:55.688 2523 6648 I ActivityManager: Killing 16348:com.android.camera/u0a84 (adj 900): depends on providerXXX

解决办法

自己实现ContentProvider的调用,用完后释放。这里采用UnstableContentProviderClient替换ContentResolver,对象判空,我们再决定是否继续调用call()方法。用完将ContentProviderClient close。

markdown 复制代码
private final boolean removeDyingProviderLocked(ProcessRecord proc,

        ContentProviderRecord cpr, boolean always) {

    ...

    for (int i = cpr.connections.size() - 1; i >= 0; i--) {

        ...

        ProcessRecord capp = conn.client;

        conn.dead = true;

        // 关键就在于conn.stableCount > 0 这个条件

        if (conn.stableCount > 0) {

            if (!capp.isPersistent() && capp.thread != null

                    && capp.pid != 0

                    && capp.pid != MY_PID) {

                capp.kill("depends on provider "

                        + cpr.name.flattenToShortString()

                        + " in dying proc " + (proc != null ? proc.processName : "??")

                        + " (adj " + (proc != null ? proc.setAdj : "??") + ")",

                        ApplicationExitInfo.REASON_DEPENDENCY_DIED,

                        ApplicationExitInfo.SUBREASON_UNKNOWN,

                        true);

            }

            ...

    }

    ...

}

从方法名来看,removeDyingProviderLocked应该是AMS用来移除将死进程的Provider信息的。并且在移除这些Provider信息的时候会根据一些条件来判断是否要杀死调用方。接下去我们可以分两个方向来分析,一个是removeDyingProviderLocked (ProcessRecord proc, ContentProviderRecord cpr, boolean always)这个方法何时会被调用,另一个则是conn.stableCount在满足怎样的条件时会大于0。

方法 stableCount unstableCount 条件
acquireProvider 1 0 removePending=false
acquireProvider 1 -1 removePending=true
acquireUnstableProvider 0 1 removePending=false
acquireUnstableProvider 0 0 removePending=true

导致XXX闪退的关键就在system_server到provider process的交互过程

AMS首先会调用getContentProviderImp()方法尝试获取target provider。如果ContentProvider还未被注册(即所在进程还未启动),则会调用startProcessLocked()方法来启动server process

server进程在启动时会调用attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq)方法,关键代码如下:

java 复制代码
    static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;

    

    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,

            int pid, int callingUid, long startSeq) {

        // ...

        if (providers != null && checkAppInLaunchingProvidersLocked(app)) {

            Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);

            msg.obj = app;

            mHandler.sendMessageDelayed(msg,

                    ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);

        }

        // ...

    }

server进程会判断当前AndroidManifest.xml文件中是否存在需要注册的ContentProvider,如果存在就给Handler发送一个延时消息。这个消息的处理逻辑如下:

java 复制代码
            case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {

                ProcessRecord app = (ProcessRecord)msg.obj;

                synchronized (ActivityManagerService.this) {

                    processContentProviderPublishTimedOutLocked(app);

                }

            } break;

//

private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {

        cleanupAppInLaunchingProvidersLocked(app, true);

        mProcessList.removeProcessLocked(app, false, true,

                ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,

                ApplicationExitInfo.SUBREASON_UNKNOWN,

                "timeout publishing content providers");

    }

//

final boolean cleanUpApplicationRecordLocked(ProcessRecord app,

            boolean restarting, boolean allowRestart, int index, boolean replacingPid) {

        ...

        // Remove published content providers.

        for (int i = app.pubProviders.size() - 1; i >= 0; i--) {

            ContentProviderRecord cpr = app.pubProviders.valueAt(i);

            if (cpr.proc != app) {

                // If the hosting process record isn't really us, bail out

                continue;

            }

            final boolean alwaysRemove = app.bad || !allowRestart;

            final boolean inLaunching = removeDyingProviderLocked(app, cpr, alwaysRemove);

            ...

        }

        ...

    }

在全局范围内搜索CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG的时候会发现,还有removeMessage的方法。总共有两个调用地方

  • 重启server进程
arduino 复制代码
    final boolean cleanUpApplicationRecordLocked(ProcessRecord app,

            boolean restarting, boolean allowRestart, int index, boolean replacingPid) {

        ...

        if (restart && allowRestart && !app.isolated) {

            // We have components that still need to be running in the

            // process, so re-launch it.

            if (index < 0) {

                ProcessList.remove(app.pid);

            }



            // Remove provider publish timeout because we will start a new timeout when the

            // restarted process is attaching (if the process contains launching providers).

            mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);



            mProcessList.addProcessNameLocked(app);

            app.pendingStart = false;

            mProcessList.startProcessLocked(app,

                    new HostingRecord("restart", app.processName),

                    ZYGOTE_POLICY_FLAG_EMPTY);

            return true;

        }

        ...

    }
  • ContentProvider注册成功
markdown 复制代码
    public final void publishContentProviders(IApplicationThread caller,

            List<ContentProviderHolder> providers) {

            ...

                    if (wasInLaunchingProviders) {

                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);

                    }

            ...

    }

system_server在启动进程时如果目标进程有需要注册的ContentProvider,就会发送一个10s的超时信息;如果目标进程的ContentProvider在十秒内加载完成,system_server就会移除这个超时信息;如果没有注册完成,system_server就会处理这个信息,最终就会调用到removeDyingProviderLocked()方法。

根据ContentResolver中各个方法的实现逻辑,大致列出以下几个有可能导致调用方进程闪退的方法。包括:acquireProvider()、getStreamTypes()、canonicalize()、uncanonicalize()、refresh()、insert()、bulkInsert()、delete()、update()、call()、acquireContentProviderClient()

ContentProviderClient VS ContentResolver

Android官方文档中提到:如需访问内容提供程序中的数据,您可以客户端的形式使用应用的 Context 中的 ContentResolver 对象与提供程序进行通信。

实际上还可以通过getContentResolver().acquireContentProviderClient(authority)获取

ContentProviderClient对象进行通信。

那两者有何区别呢?

Android通过content://uri区分数据库内容。

ContentResolver通过URI找到对应的ContentProvider,从而实现数据库存取。

1.调用ContentResolver.query() ContentResolver.update()等方法时,uri作为其一部分参数被解析。ContentResolver通过该内容标识找到对应的ContentProvider。每一次的查询对应ContentProvider的操作都是非常耗费资源的操作,因为每次的URI都可能是不同的,对应的内容标识也可能是不同的。另外,连接和断开连接对应的ContentProvider的操作也是非常耗费资源的。

2.调用acquireContentProviderClient(authority)时,查询对应的ContentProvider动作已经完成了,同时获得了一个ContentProviderClient对象。这个对象与其对应的ContentProvider建立了连接。因此,当使用ContentProviderClient时,会直接与对应的ContentProvider通信,大大减少了查询对应ContentProvider带来的消耗。

(warning: acquireContentProviderClient方法的ContentProvider对象必须在使用结束后调用ContentProviderClient.release()释放)

小结:

不同ContentProvider的多次调用:使用ContentResolver;

相同ContentProvider的多次调用:使用ContentProviderClient,记得释放。

相关推荐
命运之手5 小时前
【Android】自定义换肤框架01之皮肤包制作
android·skin·skinner·换肤框架·不重启换肤
练习本5 小时前
android perfetto使用技巧梳理
android
GitLqr6 小时前
Android - 云游戏本地悬浮输入框实现
android·开源·jitpack
周周的Unity小屋6 小时前
Unity实现安卓App预览图片、Pdf文件和视频的一种解决方案
android·unity·pdf·游戏引擎·webview·3dwebview
单丽尔9 小时前
Gemini for China 大更新,现已上架 Android APP!
android
JerryHe10 小时前
Android Camera API发展历程
android·数码相机·camera·camera api
Synaric11 小时前
Android与Java后端联调RSA加密的注意事项
android·java·开发语言
程序员老刘·12 小时前
如何评价Flutter?
android·flutter·ios
JoyceMill13 小时前
Android 图像效果的奥秘
android
想要打 Acm 的小周同学呀14 小时前
ThreadLocal学习
android·java·学习