android源码-ContentProvider实现原理分析

news/2024/5/16 17:19:04/文章来源:https://blog.csdn.net/AA5279AA/article/details/127829456

前言:

最初的目的是想研究下ContentProvider产生ANR原因的,但是如果要讲ANR的原因,那么必须要了解ContentProvider的完整实现原理,所以本篇就先讲一下ContentProvider的实现原理,下一篇再去讲ANR的原因。

本篇主要会讲以下内容:

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引用providerprovider = 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的记录对象ProviderClientRecordProviderClientRecord 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进行处理。

@Overridepublic 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类进行处理的:

@Overridepublic 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) {...//1cpr = mProviderMap.getProviderByName(name, userId);if (cpr == null && userId != UserHandle.USER_SYSTEM) {//2cpr = 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) {//1cpr = mProviderMap.getProviderByName(name, userId);//2if (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;}//4if (!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);}}//5return 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 listContentProviderRecord record = mSingletonByName.get(name);if (record != null) {return record;}// Check the current user's listreturn 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方法。

@Overridepublic 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_36952.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Baklib知识库|为什么知识共享工具对减少内部知识缺口至关重要

你的企业是否存在知识缺口&#xff1f; 知识缺口——没有对关键知识进行研究和记录&#xff0c;以有效地传播信息&#xff0c;并教育企业内外的用户——可能是寻求生产率最大化并最终实现利润增长的公司的一个关键缺陷。知识&#xff08;或数据、关键信息等&#xff09;是你的…

网络通信基本原理

通讯的必要条件 主机之间需要有传输介质。光纤、蓝牙、wify主机上必须有网卡设备。把二进制信息转为高低电压的过程就是数据的调制过程。把电信号转为二进制信息的过程为解调制。主机之间需要协商网络速率。 网路的通讯方式 日常生活中&#xff0c;我们通讯的方式不可能只有…

现代密码学导论-14-基于伪随机发生器的EAV安全

目录 3.3.3 基于伪随机发生器的EAV安全 用伪随机发生器进行加密的图示 CONSTRUCTION 3.17 一种基于伪随机发生器的私钥加密方案 THEOREM 3.16 基于伪随机发生器的私钥加密方案的EAV安全 THEOREM 3.16 的证明 最后来理一下 3.3.3 基于伪随机发生器的EAV安全 用伪随机发生…

sql server如何卸载干净?来看这里

一、如何卸载干净 1.关闭服务 快捷键&#xff1a;windows R&#xff0c;在命令行输入&#xff1a; services.msc&#xff0c;把有关SQL都关闭 &#xff0c;下图所示&#xff1a; 2.到控制面板&#xff0c;卸载 sql server 3.删除磁盘里的文件 我的在c盘里&#xff0c;看各位…

Linux Command htpasswd 创建密码文件

文章目录Linux Command htpasswd 创建密码文件1. 简介2. 安装3. 语法4. 选项5. 示例6. 其他Linux Command htpasswd 创建密码文件 1. 简介 htpasswd是Apache的Web服务器内置的工具,用于创建和更新储存用户名和用户基本认证的密码文件。 2. 安装 centos 7、 redhat&#xff…

详解设计模式:简单工厂模式

简单工厂模式&#xff08;Smiple Factory Pattern&#xff09;&#xff1a;定义一个工厂类&#xff0c;他可以根据参数的不同返回不同类的实例&#xff0c;被创建的实例通常都具有共同的父类&#xff0c;简单工厂模式也被称为静态工厂模式。 &#xff5e; 本篇内容包括&#xf…

SpringBoot整合Mybatis方式2:使用注解方式整合Mybatis

SpringBoot整合Mybatis简介SpringBoot整合Mybatis方式2&#xff1a;使用注解方式整合Mybatis1.先用idea创建一个添加mybatis需要的相关依赖的工程。2.准备数据库和表3.创建表映射类4.创建mapper代理接口5.创建Service层和Service的实现层6.创建控制层&#xff08;也就是web层&a…

五种方法帮你解决电脑内存占用大的问题

有用户反映自己的电脑什么都没开&#xff0c;但是运行内存显示占用90%以上&#xff0c;这是什么情况&#xff1f;运行内存占用大&#xff0c;直接影响了用户的使用体验&#xff0c;下面小编就给大家分享五个解决电脑内存占用大的办法吧。 方法一&#xff1a; 1、右键【我的电脑…

Spring Boot Admin2 自定义异常监控

其他相关文章&#xff1a; Spring Boot Admin 参考指南SpringBoot Admin服务离线、不显示健康信息的问题Spring Boot Admin2 EnableAdminServer的加载Spring Boot Admin2 AdminServerAutoConfiguration详解Spring Boot Admin2 实例状态监控详解Spring Boot Admin2 自定义JVM监控…

GEO振弦式钢筋计的组装

&#xff08;1&#xff09;按钢筋直径选配相应的钢筋计&#xff0c;如果规格不符合&#xff0c;应选择尽量接近于结构钢筋直径 的钢筋计&#xff0c;例如&#xff1a;钢筋直径为 35mm&#xff0c;可使用 NZR-36 或 NZR-32 的钢筋计&#xff0c;此时仪器的最小 读数应进行修…

MapReduce

4.1 MapReduce概述 2003年和2004年&#xff0c;Google公司在国际会议上分别发表了两篇关于Google分布式文件系统和MapReduce的论文&#xff0c;公布了Google的GFS和MapReduce的基本原理和主要设计思想。 4.1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#…

【无百度智能云与实体经济“双向奔赴”: 一场1+1>2的双赢 标题】

实体经济&#xff0c;已经成为检验科技企业潜力的试金石。 在最近的财报季中&#xff0c;各家大厂的财报里“实体经济”都是关键字眼&#xff0c;已经成为各家心照不宣的共同目的地。 当然&#xff0c;条条大路通罗马。每一家的战略思路和打法都不一样。11月22日&#xff0c;…

DOTA-PEG-麦芽糖 maltose-DOTA 麦芽糖-四氮杂环十二烷四乙酸

DOTA-PEG-麦芽糖 maltose-DOTA 麦芽糖-四氮杂环十二烷四乙酸 PEG接枝修饰麦芽糖&#xff0c;麦芽糖-聚乙二醇-四氮杂环十二烷四乙酸&#xff0c;DOTA-PEG-麦芽糖 中文名称&#xff1a;麦芽糖-四氮杂环十二烷四乙酸 英文名称&#xff1a;maltose-DOTA 别称&#xff1a;DOTA修…

HCIA 访问控制列表ACL

一、前言 ACL又称访问控制列表&#xff0c;其实这个东西在很多地方都有用&#xff0c;可能名字不太一样但原理和功能都差不太多&#xff0c;比如服务器、防火墙&#xff0c;都有类似的东西&#xff0c;功能其实也就是“过滤”掉不想收到的数据包。为什么不想收到一些数据包呢&…

The Seven Tools of Causal Inference with Reflections on Machine Learning 文章解读

目录 THE THREE LAYER CAUSAL HIERARCHY. 4 THE SEVEN TOOLS OF CAUSAL INFERENCE (OR WHAT YOU CAN DO WITH A CAUSAL MODEL THAT YOU COULD NOT DO WITHOUT?) 7 Tool 1: Encoding Causal Assumptions – Transparency and Testability. 10 Tool 2: Do-calculus and the …

深入浅出基于HLS流媒体协议视频加密的解决方案

一套简单的基于HLS流媒体协议&#xff0c;使用video.js NodeJS FFmpeg等相关技术实现的m3u8tsaes128视频加密及播放的解决方案示例。 项目简介 起初是为了将工作中已有的基于Flash的视频播放器替换为不依赖Flash的HTML5视频播放器&#xff0c;主要使用了现有的video.js开源播…

【学习笔记25】JavaScript字符串的基本认识

JavaScript字符串的基本认识一、严格模式二、字符串1、字面量2、构造函数3、包装类型三、字符集&#xff08;了解&#xff09;1、ASCII&#xff1a;128个2、GBK国标码&#xff1a;前128位ASCII&#xff0c;从129开始为汉字3、unicode(万国码)四、字符串的length与下标一、严格模…

学弟:功能测试转测试开发容易吗?

最近看到后台留言问&#xff1a;功能测试转测试开发容易吗&#xff1f; 从这个问题&#xff0c;我能读出一些信息如下&#xff1a; 不知道你从事测试工作多久了&#xff0c;可以看出您特别羡慕测试开发工程师&#xff1b;你可能一直从事功能测试工作&#xff0c;工作模式或大…

排名预测系统

排名预测系统 题目链接 题目背景&#xff1a; 本题大模拟来自真实的需求&#xff0c;即&#xff1a;综合三场网络赛的名次&#xff0c;来预计一个正式队伍在所有正式参赛队伍中的名次以此来估计自己能不能拿牌。本来只有一道题&#xff0c;即为你们看到的T5&#xff0c;经过…

HTTP响应详解

目录 一.状态码 小结&#xff08;记住&#xff09; 二.认识响应正文&#xff08;body&#xff09; 三.如何构造http请求 一.状态码 是一个数字&#xff0c;这个数字描述了当前这次请求的状态&#xff08;成功&#xff0c;失败&#xff0c;失败的原因&#xff09; http的状态…