草庐IT

android四大组件之三-Provider实现原理分析

失落夏天 2023-04-20 原文

前言:

一开始的目标是解决各种各样的ANR问题的,但是我们知道,ANR总体上分有四种类型,这四种类型有三种是和四大组件相对应的,所以,如果想了解ANR发生的根因,对安卓四大组件的实现流程是必须要了解的。

所以会写一系列的文章,来分析四大组建的实现原理,同时也会写文章来讲解四种类型的ANR是如何发生的。

姐妹篇文章介绍contentProvider中的ANR是如何产生的如下:

ANR系列之四:ContentProvider类型ANR产生原理讲解_失落夏天的博客-CSDN博客

本篇主要会讲以下内容:

1.ContentProvider的一些基本概念和流程

2.ContentProvider中具体实现和注册流程。

3.使用者拿到ContentProvider的binder引用后执行具体的操作,如quert/insert等等。

4.关于ContentProvider的一些扩展性的问题。

PS:本文基于android13的源码进行讲解。

一.基本概念和流程

ContentProvider在安卓中类似于一种跨进程的数据库,A进程对外提供数据库接口,B进程拿到接口后可以进行增删改查的逻辑。对于A进程中ContentProvider的具体实现逻辑,则无需对外暴露,由A进程自己负责实现。

使用的概览流程图如下:

一些Provider流程中使用到的类讲解:

ContentProvider:Provider的基类,具体的增删改查功能由其的实现类来实现。

ContentResolver:中间类,通过其获取Provider的引用,然后再通过引用完成最终的增删改查操作。

ActivityManagerService:这个类不但负责Activity的分发处理,而且负责广播,Provider等等。所以我觉得一开始google起错了名字,叫ApplicationManagerService更为合适。该类在Provider整个流程中起到一个管理者的作用,Provider向其进行注册,使用者也向其获取Provider的引用。

ContentProviderHelper:Provider在系统侧的功能具体实现类,负责具体的查找,执行等等操作。ProviderMap理解为容器的话,那么ContentProviderHelper就是访问这个容器的勺子。

ProviderMap:所有Provider引用的注册类。和binder的ServiceManager的功能其实很相似,所有的Provider引用都注册到这里,所有的使用者也都从这里查找对应的引用。

二.Provider的注册流程

流程简介:

整个Provider的生成注册,以及使用段获取Provider引用的流程图如下:

应用在启动时就会根据传递过来的Provider配置,生成Provider,并且通过binder把自身的binder引用注册到系统当中,供其它APP查询和调用。

2.1 Provider的初始化

bindApplication初始化进程

一个应用被首次启动的时候,一定会调用ActivityThread中的bindApplication的方法,具体原因这里就不扩展了,有兴趣的可以看我另外一篇文章:android源码学习- APP启动流程

bindApplication方法我们可以主要拆解为以下几步:

//1 等待debug调试
Debug.waitForDebugger();
//2 加载dex和资源文件,并且创建ContextImpl
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
//3 创建应用代理类
mInstrumentation = new Instrumentation();
//4 创建Application对象
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
//5 生成Provider对象并注册
installContentProviders(app, data.providers);
//6 执行代理类的生命周期流程
mInstrumentation.onCreate(data.instrumentationArgs);
//7 执行Application的生命周期流程
mInstrumentation.callApplicationOnCreate(app);

这里稍微扩展一点,有上面的代码中我们可以看到,Provider的生命周期流程甚至比Application的onCreate还早,所以有一些初始化框架(比如android-startup)就是通过Provider解耦初始化任务的,当然,这是外话了。

至于其它流程这里我们就不详细介绍了,我们只看第5步,生成Provider对象并注册。然后,检查AppBindData的providers的长度是否为0,如果为0则代表manifest中没有注册Provider,自然也无需走后面的流程。这里需要值得注意的是,AppBindData对象并不是APP进程从manifest中读取的,而是来源于系统层,和Activity的注册一样,是系统层读取manifest配置传递给APP的。所以我们想在APP侧动态去注册Provider是不可行的,如果非要这么做,就得采取和Activity一样插桩的方式来进行。

注册并发布provider

installContentProviders方法中,主要就做了两件事。

首先,遍历providers,调用installProvider方法生成实例对象。

然后,通过publishContentProviders方法进行binder跨进程通信,把这些实例对象的引用注册到系统。

代码如下:

private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

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

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

installProvider方法生成Provider对象

installProvider方法中的逻辑也是很简单的,主要做了三件事情。

1.使用工厂类,通过反射生成ContentProvider对象localProvider;

2.通过attachInfo调用ContentProvider的生命周期方法onCreate;

3.通过localProvider对象生成binder引用provider,然后封装成ProviderClientRecord对象,保存到APP进程中的map中。

4.最后返回ContentProviderHolder类型对象,该类型对象持有生成的binder引用对象provider。

简化后的代码如下:

private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        //1.生成ContentProvider对象
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
        //2.生成ContentProvider对象的binder引用provider
                provider = localProvider.getIContentProvider();
        
        ContentProviderHolder retHolder;

        synchronized (mProviderMap) {
            if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
                    + " / " + info.name);
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                //3.生成ContentProvider的记录对象ProviderClientRecord
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, "
                                + "using existing local provider");
                    }
                    provider = pr.mProvider;
                } else {
                    holder = new ContentProviderHolder(info);
                    holder.provider = provider;
                    holder.noReleaseNeeded = true;
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    mLocalProviders.put(jBinder, pr);
                    mLocalProvidersByName.put(cname, pr);
                }
                retHolder = pr.mHolder;
        //4.返回ContentProviderHolder,其持有binder引用provider。
        return retHolder;
    }

至此,ContentProvider创建好了,并且持有其binder引用的ContentProviderHolder也创建好了,下一步,就是要把这个ContentProviderHolder进行对应的注册了。

2.2 Provider注册到系统服务中

生成好对应的Provider对象,下一步就是注册了。

publishContentProviders注册Provider对象到系统

ActivityThread中,通过publishContentProviders( getApplicationThread(), results);进行跨进程通信,通知到AMS中,AMS中的publishContentProviders方法进行接收。

AMS中只是记录了trace,没有做任何的业务操作,直接委托给了ContentProviderHelper进行处理。

@Override
    public final void publishContentProviders(IApplicationThread caller,
            List<ContentProviderHolder> providers) {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
            ...
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
        }
        try {
            mCpHelper.publishContentProviders(caller, providers);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
    }

ContentProviderHelper.publishContentProviders方法中,会进行如下一些操作:

1.在系统一侧,其实针对每个APP进程都会有一个对象进行记录,这个对象就是ProcessRecord,它里面记录的信息,都是从配置文件Manifest中读取的。所以方法中首先会找到对应的进程对象。

final ProcessRecord r = mService.getRecordForAppLOSP(caller);

2.依次的处理传递过来的providers集合,检查对应的配置文件中是否存在,ProcessRecord.mProviders,如果不存在,则不会进行注册。

 ContentProviderRecord dst = r.mProviders.getProvider(src.info.name);
 if (dst == null) {
    continue;
 }

3.如果存在,则会开始进行注册操作了。

首先,会把dst对象,注册到mProviderMap中。前面有介绍,ProviderMap就是完成最终provider存储的容器。

ProviderMap会存储两种类型的map,一类是注册的authorities和dst的对应,还有一类是ComponentName和dst的对应关系。

ProviderMap中进行的存储结构最终有两种:

一种是singleton类型的,这种所有的进程共用一个authority的,所以它的存储结构是HashMap<String, ContentProviderRecord>结构。

另外一种就是正常的区分包名的,不同的进程可以拥有同样的authority,所以他的存储结构是SparseArray<HashMap<String, ContentProviderRecord>>结构。而userId就是SparseArray的种的key。

代码如下:

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);
}

4.把ContentProviderRecord注册到ProviderMap后,把APP进程传递过来的binder引用赋值给dst.provider,则此时注册就算完成了。

 synchronized (dst) {
 dst.provider = src.provider;
 dst.setProcess(r);
 dst.notifyAll();
 dst.onProviderPublishStatusLocked(true);

三.Provider引用的获取取流程

3.1 provider的引用获取简介

第一章的时候我们有过介绍,使用侧,如果想进行query/insert等操作,首先要获取provider的引用,然后通过这个引用完成对实体类的操作。代码如下:

public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
        @Nullable ContentValues values, @Nullable Bundle extras) {
    //获取IContentProvider的引用
    IContentProvider provider = acquireProvider(url);    
    ...
    //执行具体的操作
    Uri createdRow = provider.insert(mContext.getAttributionSource(), url, values, extras);
    ...
    return createdRow;
}

所以,本章主要讲acquireProvider如何获取引用的流程。

主要流程如下图所示:

首先,做一层合法性校验检查;

其次,转交给ContextImpl中的内部类处理,通过binder请求系统尝试获取引用

然后,系统侧通过ProviderMap获取对应的引用记录ProviderRecord。

最后,如果Provider引用对象和进程都存在,则进行各种合法性检查;如果不存在,则进行创建并返回。

接下来,我们就一条条来细讲。

3.2 使用侧获取Provider的引用

进行合法性检查

首先,acquireProvider方法中,首先对url中的scheme做一个合法性校验,如果不行,自然也无需继续走下去。

acquireProvider然后,获取Authority,如果不为空,则通过acquireProvider方法进行请求。

public final IContentProvider acquireProvider(Uri uri) {
        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
            return null;
        }
        final String auth = uri.getAuthority();
        if (auth != null) {
            return acquireProvider(mContext, auth);
        }
        return null;
    }

交由ContentImpl中的类完成和系统侧的对接

执行到acquireProvider方法。ContentResolver本身是一个抽象类,其实现类在ContentImpl中,是ContentImpl.ApplicationContentResolver类。

我们看一下其中的acquireProvider方法,其直接交给了ActivityThread类去处理。

protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), true);
        }

ActivityThread中的处理逻辑也是很简单的,通过AMS提供的getContentProvider方法获取ContentProviderHolder,因为Holder会持有最终ContentProvider的引用。代码如下:

holder = ActivityManager.getService().getContentProvider(
                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);

AMS侧的处理逻辑

接下来,我们就看一下AMS中的实现逻辑,和注册的时候一样,获取也是直接委托给ContentProviderHelper类进行处理的:

@Override
    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String callingPackage, String name, int userId,
            boolean stable) {
       ...
       return mCpHelper.getContentProvider(caller, callingPackage, name, userId, stable);
        ...
    }

getContentProvider中做了一些检查操作后,又交给了getContentProviderImpl方法进行处理。getContentProviderImpl中,主要做了以下的几步逻辑:

1.首先会使用当前的进程userId通过getProviderByName方法进行一遍查询,看是否存在ContentProviderRecord记录。getProviderByName方法我们后面再介绍

2.如果cpr不存在,则使用系统的userId再去查询一边,看系统应用中是否存在对应的Provider记录。

3.检查Provider的引用是否存在。如果存在,则检查Privider是否是expose的,在检查Provider实例是否存在。

4.如果不存在对应的Provider的引用,则仍然检查Provider的配置。如果配置没有问题,则检查是对应的进程不存在,还是对应进程的Provider不存在,进行对应的创建。

5.进行一系列的超时判断,最终返回ContentProviderHolder,其持有Provider的引用。

 private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, int callingUid, String callingPackage, String callingTag,
            boolean stable, int userId) {
    ...
    //1
    cpr = mProviderMap.getProviderByName(name, userId);
    if (cpr == null && userId != UserHandle.USER_SYSTEM) {
        //2
        cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
    }
}

private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, int callingUid, String callingPackage, String callingTag,
            boolean stable, int userId) {
            //1
            cpr = mProviderMap.getProviderByName(name, userId);
            //2
            if (cpr == null && userId != UserHandle.USER_SYSTEM) {
                cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
                ...
            }

            ProcessRecord dyingProc = null;
            if (cpr != null && cpr.proc != null) {
                providerRunning = !cpr.proc.isKilled();
                ...
            }
            //3 如果存在
            if (providerRunning) {
                cpi = cpr.info;

                
            }
            //4
            if (!providerRunning) {

                        if (proc != null && (thread = proc.getThread()) != null
                                && !proc.isKilled()) {
                            
                            final ProcessProviderRecord pr = proc.mProviders;
                            if (!pr.hasProvider(cpi.name)) {
                                checkTime(startTime, "getContentProviderImpl: scheduling install");
                                pr.installProvider(cpi.name, cpr);
                                try {
                                    thread.scheduleInstallProvider(cpi);
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            proc = mService.startProcessLocked(...);
                            
                        }
                        cpr.launchingApp = proc;
                        mLaunchingProviders.add(cpr);
                    } finally {
                        Binder.restoreCallingIdentity(origId);
                    }
                }

         //5
        return cpr.newHolder(conn, false);
    }

查询ProviderMap中是否存在对应的引用

接下来,我们介绍下从ProviderMap中获取引用的getProviderByName方法。

ProviderMap中存在两种存储数据的结构mSingletonByName和mProvidersByNamePerUser。

第一种主要用于系统引用,是单例的,也就是说,一个authory只能对应唯一的一个Provider。

第二种就是我们正常使用的,不同的APP可以拥有同样authory的Provider。

自然,单例的优先级是要更高的。

ContentProviderRecord getProviderByName(String name, int userId) {
        if (DBG) {
            Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid());
        }
        // Try to find it in the global list
        ContentProviderRecord record = mSingletonByName.get(name);
        if (record != null) {
            return record;
        }

        // Check the current user's list
        return getProvidersByName(userId).get(name);
    }

考虑到后面还会用系统的USERID查询一次,所以我们总结一下,一共有三个优先级。

1.单例的Provider,这种优先级最高。

2.根据userId区分的Provider,这种优先级其次。

3.使用SYSTEM_USERID的Provider,这种是最低的。

不存在时,创建对应的实例或者进程

如果进程存在,但是实例不存在的话,则通过APP侧的binder对象thread,调用scheduleInstallProvider方法,通知提供者去创建。

如果进程都不存在,则直接创建进程就好了。因为进程创建后,一定会初始化Provider并注册。

if (proc != null && (thread = proc.getThread()) != null
                                && !proc.isKilled()) {
                            if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
                                Slog.d(TAG, "Installing in existing process " + proc);
                            }
                            final ProcessProviderRecord pr = proc.mProviders;
                            if (!pr.hasProvider(cpi.name)) {
                                checkTime(startTime, "getContentProviderImpl: scheduling install");
                                pr.installProvider(cpi.name, cpr);
                                try {
                                    thread.scheduleInstallProvider(cpi);
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            checkTime(startTime, "getContentProviderImpl: before start process");
                            proc = mService.startProcessLocked(
                                    cpi.processName, cpr.appInfo, false, 0,
                                    new HostingRecord(HostingRecord.HOSTING_TYPE_CONTENT_PROVIDER,
                                        new ComponentName(
                                                cpi.applicationInfo.packageName, cpi.name)),
                                    Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false);
                            checkTime(startTime, "getContentProviderImpl: after start process");
                            if (proc == null) {
                                Slog.w(TAG, "Unable to launch app "
                                        + cpi.applicationInfo.packageName + "/"
                                        + cpi.applicationInfo.uid + " for provider " + name
                                        + ": process is bad");
                                return null;
                            }
                        }

既然通知提供者的APP去创建了,那么什么时候创建好呢?这个做为system一侧是无法掌控的因此,就会通过handler启动一个延时执行的任务去检测,如果好了,则结束掉这延时任务,如果没有按时完成,则会触发这个任务的执行。

首先,我们看一下创建延时任务,这里我们看到,延时了20S去触发这个任务。

if (caller != null) {
                // The client will be waiting, and we'll notify it when the provider is ready.
                synchronized (cpr) {
                    if (cpr.provider == null) {
                       ...
                        Message msg = mService.mHandler.obtainMessage(
                                ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG);
                        msg.obj = cpr;
                        mService.mHandler.sendMessageDelayed(msg,
                                ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                    }
                }

取消这个任务的我们就不看了,我们直接看下这个任务做了什么。总结来说,这任务就是通知APP一侧,既然没有按时完成注册,那么就会通知APP进行下一步的处理。具体流程应该是APP在创建Provider是上锁的流程,既然超时了,会把这个锁释放掉。

case WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG: {
                    synchronized (ActivityManagerService.this) {
                        ((ContentProviderRecord) msg.obj).onProviderPublishStatusLocked(false);
                    }
                } 


onProviderPublishStatusLocked方法如下:

void onProviderPublishStatusLocked(boolean status) {
        final int numOfConns = connections.size();
        for (int i = 0; i < numOfConns; i++) {
            final ContentProviderConnection conn = connections.get(i);
            if (conn.waiting && conn.client != null) {
                final ProcessRecord client = conn.client;
                if (!status) {
                    if (launchingApp == null) {
                        Slog.w(TAG_AM, "Unable to launch app "
                                + appInfo.packageName + "/"
                                + appInfo.uid + " for provider "
                                + info.authority + ": launching app became null");
                        EventLogTags.writeAmProviderLostProcess(
                                UserHandle.getUserId(appInfo.uid),
                                appInfo.packageName,
                                appInfo.uid, info.authority);
                    } else {
                        Slog.wtf(TAG_AM, "Timeout waiting for provider "
                                + appInfo.packageName + "/"
                                + appInfo.uid + " for provider "
                                + info.authority
                                + " caller=" + client);
                    }
                }
                final IApplicationThread thread = client.getThread();
                if (thread != null) {
                    try {
                        thread.notifyContentProviderPublishStatus(
                                newHolder(status ? conn : null, false),
                                info.authority, conn.mExpectedUserId, status);
                    } catch (RemoteException e) {
                    }
                }
            }
            conn.waiting = false;
        }
    }

四.使用Provider引用进行操作

拿到了Provider的引用IContentProvider后,我们就可以开始利用这个引用对Provider进行操作了。

这里以insert为例。

Uri createdRow = provider.insert(mContext.getAttributionSource(), url, values, extras);

这里的IContentProvider类型对象provider,其实就是ContentProvider中的子类Transport,所以这里调用insert方法,就会通过binder调用到ContentProvider.Transport中的insert方法。

@Override
        public Uri insert(@NonNull AttributionSource attributionSource, Uri uri,
                ContentValues initialValues, Bundle extras) {
            uri = validateIncomingUri(uri);
            int userId = getUserIdFromUri(uri);
            uri = maybeGetUriWithoutUserId(uri);
            if (enforceWritePermission(attributionSource, uri)
                    != PermissionChecker.PERMISSION_GRANTED) {
                final AttributionSource original = setCallingAttributionSource(
                        attributionSource);
                try {
                    return rejectInsert(uri, initialValues);
                } finally {
                    setCallingAttributionSource(original);
                }
            }
            traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
            final AttributionSource original = setCallingAttributionSource(
                    attributionSource);
            try {
                return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            } finally {
                setCallingAttributionSource(original);
                Trace.traceEnd(TRACE_TAG_DATABASE);
            }
        }

我们可以看到,前面是检查,后面回走到mInterface.insert,而mInterface就是ContentProvider本身,所以这里的insert就会调用到ContentProvider实现类中的insert方法。

return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);

至此,insert的整个流程就会发送到接受者进程,由接受者进程进行具体业务逻辑的处理。比如这里是insert方法,那么就会调用到接收者进程的中的insert方法。 

五.总结和扩展

总结

我们这里对Provider的整个流程做一个总结,其实Provider和广播/ServiceManager等很像,由一个容器(ProviderMap)来装载所有的引用。并且这个容器是key-value的形式,key就是我们在manifest中声明的authorit,value就是Provider对外提供的bidner引用,我们可以根据key来查找其所对应的引用。

Provider提供者在启动时,向系统侧进行注册,最终会把映射关系存储到容器ProviderMap中。

使用者首先向服务侧尝试获取引用,获取成功后,则利用这个binder引用,直接向提供者发起请求进行具体的功能操作。

扩展

总结完了流程,我们这里再做一个稍微的扩展,也为以后讲解Provider类型的ANR埋一个小小的伏笔。

了解完整个流程,你也许会产生一个疑问,具体操作insert中,每次都要重新获取一个binder引用,是不是有一些臃肿,有没有更直接的使用方案呢?(当然,这里的acquireProvider其实也不是每次都去系统侧查找,本身也是有缓存的)。

public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
            @Nullable ContentValues values, @Nullable Bundle extras) {
    ...
    IContentProvider provider = acquireProvider(url);
    ...
    Uri createdRow = provider.insert(mContext.getAttributionSource(), url, values, extras);       
}

当然是有的,这个方案就是ContentProviderClient。简略的使用方式如下:

            ContentValues values = new ContentValues();
            values.put("key_main", "value_main");
            String AUTHORITY = "com.xt.client.android_startup.multiple";
            Uri uri = Uri.parse("content://" + AUTHORITY);
            ContentProviderClient client = getContentResolver().acquireContentProviderClient(uri);
            try {
                int query = client.update(uri, values, null, null);
                Log.i("lxltest", "query:" + query);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

首先获取一个client,后续通过这个client进行各种操作就好了。ANR也是只有这种使用方式才会出现,当然,具体的内容,我们就下一节再讲了。

六.关于Provider扩展性问题

1.Provider的onCreate中为什么不能有耗时操作?

答:我们从第2.1章Provider的初始化可以得知,Provider的onCreate甚至要早于Application的onCreate方法,从而导致冷启动耗时过长。

2.Provider中的insert等方法,是在什么线程执行?

答:上面第四章我们有讲到,使用方拿到提供方的binder后,会直接调用。那么提供方作为binder的server,自然会有专门的binder线程来负责具体的实行。

3.为啥我写的ContentProvider跨进程无法使用?

答:android11开始,使用者需要额外申请定向的包名。

使用者的manifest进行如下配置:

 <queries>
        <package android:name="com.xt.client" />
    </queries>

有关android四大组件之三-Provider实现原理分析的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  5. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  6. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  7. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  8. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  9. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

  10. ruby-on-rails - 使用 Ruby 正确处理 Stripe 错误和异常以实现一次性收费 - 2

    我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)

随机推荐