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,记得释放。

相关推荐
每次的天空7 小时前
Android学习总结之算法篇四(字符串)
android·学习·算法
x-cmd8 小时前
[250331] Paozhu 发布 1.9.0:C++ Web 框架,比肩脚本语言 | DeaDBeeF 播放器发布 1.10.0
android·linux·开发语言·c++·web·音乐播放器·脚本语言
tangweiguo0305198711 小时前
Android BottomNavigationView 完全自定义指南:图标、文字颜色与选中状态
android
遥不可及zzz12 小时前
Android 应用程序包的 adb 命令
android·adb
无知的前端12 小时前
Flutter 一文精通Isolate,使用场景以及示例
android·flutter·性能优化
_一条咸鱼_12 小时前
Android Compose 入门之字符串与本地化深入剖析(五十三)
android
ModestCoder_13 小时前
将一个新的机器人模型导入最新版isaacLab进行训练(以unitree H1_2为例)
android·java·机器人
robin_suli14 小时前
Spring事务的传播机制
android·java·spring
鸿蒙布道师14 小时前
鸿蒙NEXT开发对象工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
Harrison_zhu16 小时前
Ubuntu18.04 编译 Android7.1代码报错
android