ContentProvider启动浅析
一、自己的理解
对于content provide的启动我是这样认为的,要用ContentResolver去获得一个contentProvider,在这的获得的过程中,
1、如果本应用之前有contentProvider的引用,则直接返回。
2、如果没用,则向AMS(ActivityManagerService)去申请,然后AMS返回一个ContentProvideHolder对象,这时又分两种情况:
2.1如果此contentprovider需要在本应用进程中创建,则返回一个Holder对象,然后在本进程中创建一个contentprovider,并保存一系列引用。
2.2 如果此contentprovider在其他进程中,这时又要分情况:
2.2.1如果此contentprovider已经存在,并且可以在运行在本应用进程中,则直接返回;如果不可以,则需要设置一个ContentProviderConnection对象 conn,然后将contentProvider添加到conn中,即为contentProvider添加了一个异地引用。
2.2.2如果此contentProvider不存在,则需要去创建一个,如果这个contentProvider所在的进程已经被创建,则直接在此进程中加载,如果没有被创建,则先启动这个应用进程,然后加载。在这个加载过程中会有一个while循环,直到contentprovider被加载完毕。
最后,会返回一个带有contentProvider的Holder对象。
ps:其实返回的ContentProvider对象并不真正是一个ContentProvider对象,因为这是两个进程之间的通信,他们不能直接获得彼此进程中的一些引用,而客户进程得到的ContentProvider对象引用,本质上是一个Binder对象。因为Android中只有BInder才能在两个进程之间通信。在这里,客户端得到的其实是Transport对象,通过它来操作服务端的ContentProvider
二、源码浅析
在分析源码前,先说明几个变量:
1、AMS的mProviderMap,它保存了整个系统中所有的ContentProviderRecord对象,而这个记录里面包含了contentProvider,mProviderMap有两种获得ContentProviderRecord的方法,一是通过类名获得,另一个是通过contentProvider的授权URI获得。
2、ProcessRecord的pubProviders和conProviders,他们是被存放在每个应用各自的进程中,以键值对的方式记录着ContentProviderRecord,pubProviders保存着进程中所有创建的ContentProvider,conProviders保存着进程中所有使用的ContentProvider。
3、ContentProviderHolder中保存了ContentProvider的相关信息,它含有ProviderInfo(包含了contentProvider的授权URI,读写权限等信息),ContentProvider的引用,IBInder(这其实是一个服务端的ContentProviderConnection对象,其继承了BInder,用来作为客户端与服务端的链接)
下面我们来看一下源码中使怎么写的:
1、ContentResolver的query
在query()中,会首先获得contentProvider对象,然后用此对象去进行查询。要获得contentProvider对象会调用acquireUnstableProvider(uri)或acquireProvider(uri)。其实这两个方法的最终调用都是一样的,现在,我们选择acquireUnstableProvider(uri)来分析。
2、ContentResolver的acquireUnstableProvider()
要分析此方法,首先要知道resolver对象是如何获得的。resolver对象是通过context对象的getContentResolver()获得的。其实是ContextImpl实现了Context,然后由ContextWrapper对其进行了修饰,即有ContextImpl对象的一个引用——mBase,然后Service,Activity又继承了ContextWrapper ,由此就可以使用上下文的资源。在ContextImpl中,有一个ApplicationContentResolver类继承了ContentResolver,并在ContextImplement初始化时,对其进行实例化,保存为mContentResolver引用。在getContentResolver()中会使用mBase的getContentResolver(),得到的就是这个mContentResolver,然后会在它的acquireUnstableProvider()去调用mMainThread(ActivityThread)的acquireProvider().
3、ActivityThread的acquireProvider()方法
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
//查看是否有已经存在的ContentProvider,若存在,则将其返回
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
try {
//向AMS请求一个含有ContentProvider的Holder
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
} // Install provider will increment the reference count for us, and break
// any ties in the race.
//如注释所说,对ContentProvider添加引用
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
关于此方法的作用,已在注释中写明,从这个方法中我们的值ContentProvider主要是从两个方面去获取的,一,从本地存在的COntentProvider集合中获取,即第4步;二,向AMS索取,即5,6,7步
4、ActivityThread的acquireExistingProvider()
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
final ProviderKey key = new ProviderKey(auth, userId);
//查看本应用中是否存有要求的ContentProvider,没有则返回null
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
//查看provider是否可用,不可用返回null
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
if (!jBinder.isBinderAlive()) {
// The hosting process of the provider has died; we can't
// use this one.
Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ ": existing object's process dead");
handleUnstableProviderDiedLocked(jBinder, true);
return null;
}
//至此,说明provider是可用的,然后对contentProvider的引用数量进行改变
// Only increment the ref count if we have one. If we don't then the
// provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable);
}
return provider;
}
}
关于incProviderRefLocked(),当请求一个新的ContentProvider时会调用此方法,它会将ContentProvider的引用计数加1,即将ContentProviderConnection对象进行更新,告诉它有某个应用需要使用新的ContentProvider,令其更新他的计数器。
5、AMS的getContentProvider()
调用此方法说明,在本应用没有已经存在的ContentProvider,需要向AMS申请一个。在此方法中会进行参数检查,最终去调用getContentProviderImpl()
5.1、AMS的getContentProviderImpl()
这个方法较长我们将它分为几个部分:
5.1.1若ContentProvider已经存在,则返回引用
5.1.2若其不存在,但是提供它的进程存在,则令此进程加载ContentProvider,然后返回新加载的对象
5.1.3若其不存,且提供它的进程也不存在,则开启此进程,并加载ContentProvider ,而后返回新加载的对象
好的下面来分析5.1.1:
先声明一些变量来保存数据
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
接下来是一个同步代码块,再此代码块中获得ContentProvider,并防止多个进程进行争抢,导致出错
ProcessRecord r = null;
if (caller != null) {
r = getRecordForAppLocked(caller);
if (r == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when getting content provider " + name);
}
}
获得客户端的进程,用来判断获得ContentProvider后,能否直接在客户端进程运行。
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
// If that didn't work, check if it exists for user 0 and then
// verify that it's a singleton provider before using it.
if (cpr == null && userId != UserHandle.USER_SYSTEM) {
cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
if (cpr != null) {
cpi = cpr.info;
if (isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
userId = UserHandle.USER_SYSTEM;
checkCrossUser = false;
} else {
cpr = null;
cpi = null;
}
}
}
在此代码块中,首先查看,授权URI对应的进程中是否有ContentProvider,若没有,则去系统进程中查找,找到则证明此ContentProvider是单实例的,即系统中只有一个。
boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;
这一句是判断是否有存在且存活的ContentProvider,若有则为true,否则为false
if (providerRunning) {
cpi = cpr.info;
......
//查看是否可以在请求contentProvider的进程中运行,如果可以或者此contentProvider就是请求,提供的
//则直接返回
if (r != null && cpr.canRunHere(r)) {
ContentProviderHolder holder = cpr.newHolder(null);
holder.provider = null;
return holder;
}
......
// In this case the provider instance already exists, so we can
// return it right away.
//为contentProvider增加引用数
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
// 更新提供contentProvider进程的位置
......
updateLruProcessLocked(cpr.proc, false, null);
}
}
//下面检查提供contentprovider的进程是否存活,若已死亡,等待新的进程启动
//它是通过oom_adj的值来检查的
final int verifiedAdj = cpr.proc.verifiedAdj;
boolean success = updateOomAdjLocked(cpr.proc);
if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) {
success = false;
}
......
if (!success) {
// 进程已为空,等待新进程的启动
appDiedLocked(cpr.proc);
......
providerRunning = false;
conn = null;
} else {
cpr.proc.verifiedAdj = cpr.proc.setAdj;
} Binder.restoreCallingIdentity(origId);
}
上面的代码是当有contentProvider时,该如何做。应在注释中写明
这两个方法的作用详见:
下面我们来分析contentProvider不存在的情况,这里我们把5.1.2和5.1.3合并到一起分析
if (!providerRunning) {
try{
//在此获得provider的信息
cpi = AppGlobals.getPackageManager().
resolveContentProvider(name,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
} catch (RemoteException ex) {
}
if (cpi == null) {
return null;
}
// 参数检查
......
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
......
//以类名的来获得provider
cpr = mProviderMap.getProviderByClass(comp, userId);
......final boolean firstClass = cpr == null;
if (firstClass) {
......try {
checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
ApplicationInfo ai =
AppGlobals.getPackageManager().
getApplicationInfo(
cpi.applicationInfo.packageName,
STOCK_PM_FLAGS, userId);
......
ai = getAppInfoForUser(ai, userId);
//因为我们需要provider,但是provider记录不存在,所以在此创建一个ContentProviderRecord来保存要获得provider
cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
} finally {
Binder.restoreCallingIdentity(ident);
}
}
......
//到这里我们已经获得了contentProviderRecord对象cpr,不管它含不含有provider
if (r != null && cpr.canRunHere(r)) {
//条件成立,表示provider可以在请求者进程运行,或在请求者进程创建,则可以直接返回一个Holder
return cpr.newHolder(null);
} if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid "
+ (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): "
+ cpr.info.name + " callers=" + Debug.getCallers(6)); //查看是否有正在启动的provider,若有等待启动完成
final int N = mLaunchingProviders.size();
int i;
for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break;
}
} // 没有正在启动的provider,去启动它
if (i >= N) {
final long origId = Binder.clearCallingIdentity(); try {
// 加载provider所在的包
try {
......
AppGlobals.getPackageManager().setPackageStoppedState(
cpr.appInfo.packageName, false, userId);
checkTime(startTime, "getContentProviderImpl: after set stopped state");
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ cpr.appInfo.packageName + ": " + e);
} // 获得provider所在的进程,因为我们要在此进程中启动provider ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null && !proc.killed) {
//条件成立,表示进程已经启动
if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,
"Installing in existing process " + proc);
if (!proc.pubProviders.containsKey(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
proc.pubProviders.put(cpi.name, cpr);
try {
//在此进程启动provider
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
//条件不成立,则需要先启动一个进程,然后等待此进程加载provider
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
......
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
} finally {
Binder.restoreCallingIdentity(origId);
}
} //此处保存这个新provider的一些引用信息
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
} mProviderMap.putProviderByName(name, cpr);
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null) {
conn.waiting = true;
}
}
完成上面的代码,表示provider已经存在,或正在启动,下面的代码用来检查
synchronized (cpr) {
while (cpr.provider == null) {
......try {
......
cpr.wait();
} catch (InterruptedException ex) {
} finally {
if (conn != null) {
conn.waiting = false;
}
}
}
}
return cpr != null ? cpr.newHolder(conn) : null;
新的provider成功获得后,把它放在一个Holder中返回
下面我们来说一下,上面的两个进程中启动provider的方法startProcessLocked()和scheduleInstallProvider(),他们最终都会调用installProvider()。现在,我们以scheduleInstallProvider()来分析。首先,会调用ApplicationThread的scheduleInstallProvider(),而在这个方法中会给消息队列发送一个消息,然后,会转到ActivityThread中的Handler对象中去处理。继而调用handleInstallProvider().
ActivityThread的handleInstallProvider():
public void handleInstallProvider(ProviderInfo info) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
installContentProviders(mInitialApplication, Lists.newArrayList(info));
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
此方法会转而去调用installContentProviders()
ActivityThread的installContentProviders()
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<IActivityManager.ContentProviderHolder> results =
new ArrayList<IActivityManager.ContentProviderHolder>(); 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());
}
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) {
throw ex.rethrowFromSystemServer();
}
}
在这个方法中,首先会获得要加载的每一个provider的信息,因为我们这里只传入了一个,所以providers的长度为1.然后调用installProvider()去启动provider,启动完成之后会去告诉AMS,此provider已经启动完毕,让AMS去更新一些信息。installProvider()这个方法我们在前面请求者请求provider时见过,所以在这我们结合着两种不同的场景来分这方法的实现
ActivityThread的installProviders()
private IActivityManager.ContentProviderHolder installProvider(Context context, I
ActivityManager.ContentProviderHolder holder, ProviderInfo inf
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
......
//在此首先要获得与要加载provider相关的Context,因为provider是null,所以我们在后面要去实例化一个provider,这是当前情况下的处理做法
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 { c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE); } catch (PackageManager.NameNotFoundException e) { // Ignore } } ......try { final java.lang.ClassLoader cl = c.getClassLoader(); localProvider = (ContentProvider)cl. loadClass(info.name).newInstance();
//实例化一个provider,并获得可以在进程间通信的Transport对象
provider = localProvider.getIContentProvider();
......
// 为新创建的provider配置一些信息,如读写权限之类的
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
......
}
} else {
//这是在请求者请求时的处理,传进来的是一个在其他进程已经启动好了的provider
provider = holder.provider;
......
} IActivityManager.ContentProviderHolder retHolder; synchronized (mProviderMap) {
......
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
//条件成立,表示此provider是新建的,需要保存一些引用
ComponentName cname = new ComponentName(info.packageName, info.name);
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
if (pr != null) {
//为null表示,在多个进程同时请求时,竞争失败,已经有其他进程先获得了provider,在此不需要在此保存
provider = pr.mProvider;
} else {
//需要将provider保存在holder中
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 {
//表示此provider是用其他进程传入的,要在此保存provider的引用数量,当Pro不为null时,是第一次传入,可以根据要求判断是否进行更新
//pro为null,则需要创建一个provider远程引用数,并进行保存
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
......
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
try {
ActivityManagerNative.getDefault().removeContentProvider(
holder.connection, stable);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
}
}
} 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;
}
} return retHolder;
}
然后,我们可以通知AMS去发布provider了
AMS的publishContentProviders()
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
.....synchronized (this) {
final ProcessRecord r = getRecordForAppLocked(caller);
......final int N = providers.size();
for (int i = 0; i < N; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
continue;
}
//dst为之前在getContentProviderImpl中创建的provider记录
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--;
}
}
//在此表示,请求的provider已经被启动,并向record中添加provider,然后打断前面的while循环。synchronized (dst) {
dst.provider = src.provider;
dst.proc = r;
dst.notifyAll();
}
updateOomAdjLocked(r);
maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
src.info.authority);
}
} Binder.restoreCallingIdentity(origId);
}
}
传入的providers 是已经加载好的provider,会与加载的provider进行比较,若相同则说明已经启动,并将其从待启动队列中移除。并向provider记录中添加provider,这样前面的while循环就可以被打断,从而AMS就可以将provider返回,给请求者。
至此contentProvider的启动就已经分析结束。
ContentProvider启动浅析的更多相关文章
- Android ContentProvider 启动分析
对于 ContentProvider 还不是很熟悉的同学,可以阅读上一篇 Android ContentProvider 基本原理和使用详解.本文主要是对 contentProvider 的源码进行分 ...
- ContentProvider 使用示例(转载)
ContentProvider 使用示例(转载) 当数据需要在应用程序间共享时,我们就可以利用ContentProvider为数据定义一个URI.之后其他应用程序对数据进行查询或者修改时,只需要从当前 ...
- ContentProvider工作过程
ContentProvider启动过程(通过query方法触发) ContentProvider.acquireProvider--> ApplicationContentResolver.ac ...
- ContentProvider的那些小事(纯结论)
一.ContentProvider背景 Android系统是基于Linux系统内核来进行开发的,在Linux中,文件具有一系列的属性,其中最重要的莫过于文件权限了.关于文件权限,其实就是文件的读写,执 ...
- Android loader 详解
装载器从android3.0开始引进.它使得在activity或fragment中异步加载数据变得简单.装载器具有如下特性: 它们对每个Activity和Fragment都有效. 他们提供了异步加载数 ...
- kernel解析dtb为节点
title: 解析dtb为节点 date: 2019/4/26 14:02:18 toc: true --- kernel解析dtb为节点 head.s入口传递 回顾 看以前的笔记 kernel(二) ...
- [Android]四大组件的运行状态
Activity的主要作用是展示一个界面并和用户交互,它扮演的是一种前台界面的角色. Service是一种计算型组件,用于在后台执行一系列计算任务.Service有两种状态:启动状态和绑定状态.启动状 ...
- Android 内存泄漏检测工具 LeakCanary(Kotlin版)的实现原理
LeakCanary 是一个简单方便的内存泄漏检测框架,做 android 的同学基本都收到过 LeakCanary 检测出来的内存泄漏.目前 LeakCanary 最新版本为 2.7 版本,并且采用 ...
- 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!
请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...
随机推荐
- Linux下命令别名配置
在~/.bashrc文件中添加相关命令别名内容,可以降低风险操作 1. vim ~/.bashrc alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' ...
- Python web前端 07 函数及作用域
Python web前端 07 函数及作用域 一.函数 1.有名函数和匿名函数 #函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块 #函数就是包裹在花括号里面的代码块,前面使用了关键字fun ...
- 石头剪刀布(2019Wannafly winter camp day3 i) 带权并查集+按秩合并 好题
题目传送门 思路: 按照题意描述,所有y挑战x的关系最后会形成一棵树的结构,n个人的总方案数是 3n 种,假设一个人被挑战(主场作战)a次,挑战别人(客场)b次,那么这个人存活到最后的方案数就是3n* ...
- vue 同一个组件的跳转, 返回时保留原来的下拉位置
1,需求分析 公司的项目有这样一个需求: 同一个list组件,根据传过来的listId渲染成多个页面,每个页面都可以下拉.在返回到不同的list页面时,要保留当时下拉的位置. 说的我自己都有点懵逼了, ...
- 通过 flume 上传数据到hive
目标: 通过接受 1084端口的http请求信息, 存储到 hive数据库中, osgi为hive中创建的数据库名称 periodic_report6 为创建的数据表, flume配置如下: a1.s ...
- [转] javascript 判断对象是否存在的10种方法总结
[From] http://www.jb51.net/article/44726.htm Javascript语言的设计不够严谨,很多地方一不小心就会出错.举例来说,请考虑以下情况.现在,我们要判断一 ...
- dcoker machine
Docker Machine是一个安装和管理 Docker 的工具, 它有自己的命令行工具:docker-machine.Docker Machine简化了Docker的安装和远程管理, 不仅可以管理 ...
- firewall-cmd --reload 防火墙
防火墙重新加载配置firewall-cmd --reload 查看开放的端口 firewall-cmd --list-ports nano /etc/sysconfig/iptables -A IN_ ...
- 常用维护SQL-数据清理
truncate某个库的表数据 show full processlist; select concat('truncate table ',table_schema,'.',table_name,' ...
- java se系列(三) 顺序语句、if...else、switch、While、do-while、for、break、continue
1 顺序语句 语句:使用分号分隔的代码称作为一个语句. 注意:没有写任何代码只是一个分号的时候,也是一条语句,称作空语句. 顺序语句就是按照从上往下的顺序执行的语句. 2 判断(if…else) 什么 ...