ViewRootImpl管理着整个view tree。 对于ViewRootImpl.setView(),我们可以简单的把它当做一个UI渲染操作的入口。

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/WindowManagerImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//mWindowSession是一个aidl,ViewRootImpl利用它来和WindowManagerService交互
//mWindow是一个aidl,WindowManagerService可以利用这个对象与服务端交互
//mAttachInfo可以理解为是一个data bean,可以跨进程传递
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
...
}
ViewRootImpl.setView()方法会向WindowManagerService请求添加一个Window,mWindowSession.addToDisplay()跨进程最终调用到了WindowManagerService.addWindow():

http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

public int addWindow(Session session, IWindow client...) {
...
//WindowState用来描述一个Window
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
...
win.attach(); //会创建一个SurfaceSession mWindowMap.put(client.asBinder(), win); //mWindowMap是WindowManagerService用来保存当前所有Window新的的集合
...
win.mToken.addWindow(win); //一个token下会有多个win state。 其实token与PhoneWindow是一一对应的。
...
}
 void attach() {
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Attaching " + this + " token=" + mToken
+ ", list=" + mToken.windows);
mSession.windowAddedLocked();
}
WindowStateWindowManagerService用来描述应用程序的一个Window的对象。上面注释我标注了win.attach(),这个方法可以说是WindowSurfaceFlinger链接的起点,它最终会调用到Session.windowAddedLocked():

http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/wm/Session.java

void windowAddedLocked(String packageName) {
...
if (mSurfaceSession == null) {
...
mSurfaceSession = new SurfaceSession();
...
}
}

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/SurfaceSession.java

//SurfaceSession类的构造方法
public final class SurfaceSession {
private long mNativeClient; // SurfaceComposerClient* public SurfaceSession() {
mNativeClient = nativeCreate();
}

这里调用了native方法nativeCreate(),这个方法其实是返回了一个SurfaceComposerClient指针。那这个对象是怎么创建的呢?

SurfaceComposerClient的创建

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/jni/android_view_SurfaceSession.cpp

static jlong nativeCreate(JNIEnv* env, jclass clazz) {
SurfaceComposerClient* client = new SurfaceComposerClient(); //构造函数其实并没有做什么
client->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(client);
}
即构造了一个SurfaceComposerClient对象。并返回它的指针。这个对象一个应用程序就有一个,它是应用程序与SurfaceFlinger沟通的桥梁,为什么这么说呢?在SurfaceComposerClient指针第一次使用时会调用下面这个方法:
//这个方法在第一次使用SurfaceComposerClient的指针的时候会调用
void SurfaceComposerClient::onFirstRef() {
....
sp<ISurfaceComposerClient> conn;
//sf 就是SurfaceFlinger
conn = (rootProducer != nullptr) ? sf->createScopedConnection(rootProducer) :
sf->createConnection();
...
}

即通过SurfaceFlinger(它本身具有跨进程通信的能力)创建了一个ISurfaceComposerClient对象:

http://androidxref.com/6.0.1_r10/xref/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() {
return initClient(new Client(this)); //initClient这个方法其实并没有做什么,
}
即构造了一个Client对象,Client实现了ISurfaceComposerClient接口。是一个可以跨进程通信的aidl对象。即SurfaceComposerClient可以通过它来和SurfaceFlinger通信。除此之外它还可以创建Surface,并且维护一个应用程序的所有Layer(下文会分析到它是什么)它是一个十分重要的对象,我们先来看一下它的组成,它所涉及的其他东西在下文分析中都会讲到:

http://androidxref.com/6.0.1_r10/xref/frameworks/native/services/surfaceflinger/Client.h

class Client : public BnSurfaceComposerClient
{
public:
...
void attachLayer(const sp<IBinder>& handle, const sp<Layer>& layer);
void detachLayer(const Layer* layer);
...
private:
// ISurfaceComposerClient interface。 gbp很重要,它维护这一个应用程序的渲染 Buffer队列
virtual status_t createSurface(...sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp); virtual status_t destroySurface(const sp<IBinder>& handle); //跨进程通信方法
virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
... // constant
sp<SurfaceFlinger> mFlinger; // protected by mLock
DefaultKeyedVector< wp<IBinder>, wp<Layer> > mLayers; // 一个应用程序的所有Layer
...
};

经过上面这一顿源码分析,我们大概知道了ViewRootImpl.setView()所引发的主要操作:

  1. WindowManagerService创建了一个WindowState。用来表示客户端的一个Window
  2. WindowManagerService创建了一个SurfaceSession,SurfaceSession会与SurfaceFlinger构建链接,创建了一个SurfaceComposerClient对象,一个应用程序只具有一个这个对象。
  3. SurfaceComposerClient创建了一个Client, 这个对象十分重要,它维护这应用程序的渲染核心数据,并负责与SurfaceFlinger通信。

经过上面的步骤,应用程序的ViewRootImpl已经被WindowManagerService识别,并且应用程序已经与SurfaceFlinger建立连接。即创建了SurfaceComposerClientClient对象

文章开始就已经说了SurfaceWindow(ViewRootImpl)的UI载体,那Surface是在哪里创建的呢?

Surface的创建

其实一个ViewRootImpl就对应一个Surface

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/Surface.java

这点可以通过ViewRootImpl的源码看出:

ViewRootImpl在构造的时候就new 了一个 Surface。但其实这个新new的Surface并没有什么逻辑,它的构造函数是空的。

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/ViewRootImpl.java

public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
//...
public final Surface mSurface = new Surface();
//...
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout(); //susion 请求layout。先添加到待渲染队列中
...
res = mWindowSession.addToDisplay(mWindow, ...); //WindowManagerService会创建mWindow对应的WindowState
...
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
} }

即在向WindowManagerService请求创建WindowState之前,调用了requestLayout(),这个方法会引起ViewRootImpl所管理的整个view tree的重新渲染。它最终会调用到scheduleTraversals():

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/ViewRootImpl.java

void scheduleTraversals() {
...
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}

scheduleTraversals()会通过Choreographer来post一个mTraversalRunnableChoreographer接收显示系统的时间脉冲(垂直同步信号-VSync信号,16ms发出一次),在下一个frame渲染时控制执行这个mTraversalRunnable

但是mTraversalRunnable的执行至少要在应用程序与SurfaceFlinger建立连接之后。这是因为渲染操作是由SurfaceFlinger负责调度了,如果应用程序还没有与SurfaceFlinger创建连接,那SurfaceFlinger当然不会渲染这个应用程序。所以在执行完mWindowSession.addToDisplay(mWindow, ...)之后,才会执行mTraversalRunnable:

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

doTraversal()会调用到ViewRootImpl.performTraversals(),大部分同学可能知道这个方法是一个view treemeasure/layout/draw的控制方法:

private void performTraversals() {
finalView host = mView; //mView是一个Window的根View,对于Activity来说就是DecorView
...
relayoutWindow(params, viewVisibility, insetsPending);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}

Surface的具体创建就由relayoutWindow(params, viewVisibility, insetsPending)这个方法来完成的。这个方法会通过IPC调用到WindowManagerService.relayoutWindow():

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/ViewRootImpl.java

private int relayoutWindow(WindowManager.LayoutParams params, ...) throws RemoteException {
...
int relayoutResult = mWindowSession.relayout(mWindow,..., mSurface);
...
}

mWindowSession.relayout()方法的很多参数,不过有一个十分重要的参数我没有省略,就是mSurface。前面已经分析了它就是一个空的Surface对象。其实:

真正的Surface创建是由SurfaceControl完成的,应用程序ViewRootImplSurface只是一个指针,指向这个Surface

下面就来看一下SurfaceControl是如何创建Surface的:

mWindowSession.relayout()会调用到WindowManagerService.relayoutWindow():

http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

 //这里的outSurface其实就是ViewRootImpl中的那个Surface
public int relayoutWindow(Session session, IWindow client....Surface outSurface){
...
result = createSurfaceControl(outSurface, result, win, winAnimator);
...
} private int createSurfaceControl(Surface outSurface, int result, WindowState win,WindowStateAnimator winAnimator) {
...
surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
...
surfaceController.getSurface(outSurface);
}

winAnimator.createSurfaceLocked实际上是创建了一个SurfaceControl。即上面是先构造SurfaceControl,然后在构造Surface

http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java

 SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
if (mSurfaceControl == null) {
//...
mSurfaceFormat = format;
if (DEBUG_SURFACE_TRACE) {
mSurfaceControl = new SurfaceTrace(
mSession.mSurfaceSession,
attrs.getTitle().toString(),
width, height, format, flags);
} else {
mSurfaceControl = new SurfaceControl(
mSession.mSurfaceSession,
attrs.getTitle().toString(),
width, height, format, flags);
}
//...
}
return mSurfaceControl;
}

SurfaceControl的创建

winAnimator.createSurfaceLocked其实是通过SurfaceControl的构造函数创建了一个SurfaceControl对象,这个对象的作用其实就是负责维护Surface,Surface其实也是由这个对象负责创建的,我们看一下这个对象的构造方法:

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/view/SurfaceControl.java

long mNativeObject; //成员指针变量,指向native创建的SurfaceControl

private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid){
...
mNativeObject = nativeCreate(session, name, w, h, format, flags,
parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
...
}

即调用了nativeCreate()并返回一个SurfaceControl指针:

http://androidxref.com/6.0.1_r10/xref/frameworks/native/libs/gui/SurfaceControl.cpp

static jlong nativeCreate(JNIEnv* env, ...) {
//这个client其实就是前面创建的SurfaceComposerClinent
sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
sp<SurfaceControl> surface; //创建成功之后,这个指针会指向新创建的SurfaceControl
status_t err = client->createSurfaceChecked(String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid);
...
return reinterpret_cast<jlong>(surface.get()); //返回这个SurfaceControl的地址
}

即调用到SurfaceComposerClient.createSurfaceChecked():

http://androidxref.com/6.0.1_r10/xref/frameworks/native/libs/gui/SurfaceComposerClient.cpp

//outSurface会指向新创建的SurfaceControl
status_t SurfaceComposerClient::createSurfaceChecked(...sp<SurfaceControl>* outSurface..)
{
sp<IGraphicBufferProducer> gbp; //这个对象很重要
...
err = mClient->createSurface(name, w, h, format, flags, parentHandle, windowType, ownerUid, &handle, &gbp);
if (err == NO_ERROR) {
//SurfaceControl创建成功, 指针赋值
*outSurface = new SurfaceControl(this, handle, gbp, true);
}
return err;
}
上面这个方法实际上是调用Client.createSurface()来创建一个Surface。在创建时有一个很重要的参数sp<IGraphicBufferProducer> gbp,在下面源码分析中我们也要重点注意它。这是因为应用所渲染的每一帧,实际上都会添加到IGraphicBufferProducer中,来等待SurfaceFlinger的渲染。

http://androidxref.com/6.0.1_r10/xref/frameworks/native/services/surfaceflinger/Client.cpp

status_t Client::createSurface(...)
{
...
//gbp 直接透传到了SurfaceFlinger
return mFlinger->createLayer(name, this, w, h, format, flags, windowType, ownerUid, handle, gbp, &parent);
}

SurfaceSurfaceFlinger中对应的实体其实是Layer

我们继续看一下mFlinger->createLayer()

http://androidxref.com/6.0.1_r10/xref/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

status_t SurfaceFlinger::createLayer(const String8& name,const sp<Client>& client...)
{
status_t result = NO_ERROR;
sp<Layer> layer; //将要创建的layer
switch (flags & ISurfaceComposerClient::eFXSurfaceMask) {
case ISurfaceComposerClient::eFXSurfaceNormal:
result = createBufferLayer(client,
uniqueName, w, h, flags, format,
handle, gbp, &layer); // 注意gbp,这时候还没有构造呢!
break;
... //Layer 分为好几种,这里不全部列出
}
...
result = addClientLayer(client, *handle, *gbp, layer, *parent); //这个layer和client相关联, 添加到Client的mLayers集合中。
...
return result;
}

SurfaceFlinger.createLayer()方法可以看出Layer分为好几种。我们这里只对普通的BufferLayer的创建做一下分析,看createBufferLayer():

http://androidxref.com/6.0.1_r10/xref/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

status_t SurfaceFlinger::createBufferLayer(const sp<Client>& client... sp<Layer>* outLayer)
{
...
sp<BufferLayer> layer = new BufferLayer(this, client, name, w, h, flags);
status_t err = layer->setBuffers(w, h, format, flags); //设置layer的宽高
if (err == NO_ERROR) {
*handle = layer->getHandle(); //创建handle
*gbp = layer->getProducer(); //创建 gbp IGraphicBufferProducer
*outLayer = layer; //把新建的layer的指针拷贝给outLayer,这样outLayer就指向了新建的BufferLayer
}
return err;
}

前面我说过IGraphicBufferProducer(gbp)是一个很重要的对象,它涉及到SurfaceFlinger的渲染逻辑,下面我们就看一下这个对象的创建逻辑:

IGraphicBufferProducer(gbp)的创建

sp<IGraphicBufferProducer> BufferLayer::getProducer() const {
return mProducer;
}

mProducer其实是Layer的成员变量,它的创建时机是Layer第一次被使用时:

void BufferLayer::onFirstRef() {
...
BufferQueue::createBufferQueue(&producer, &consumer, true);
mProducer = new MonitoredProducer(producer, mFlinger, this);
...
}

所以mProducer的实例是MonitoredProducer,但其实它只是一个装饰类,它实际功能都委托给构造它的参数producer:

BufferQueue.cpp

void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
...
sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core, consumerIsSurfaceFlinger));
sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core)); //注意这个consumer
...
*outProducer = producer;
*outConsumer = consumer;
}

所以实际实现mProducer的工作的queueProducerBufferQueueProducer

所以构造一个SurfaceControl所做的工作就是创建了一个SurfaceControl,并让SurfaceFlinger创建了一个对应的LayerLayer中有一个IGraphicBufferProducer,它的实例是BufferQueueProducer

可以用下面这个图来描述SurfaceControl的创建过程:

从SurfaceControl中获取Surface

我们回看WindowManagerService.createSurfaceControl(), 来看一下java层的Surface对象到底是个什么:

http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private int createSurfaceControl(Surface outSurface, int result, WindowState win,WindowStateAnimator winAnimator) {
...
surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
...
surfaceController.getSurface(outSurface);
}
上面我们已经了解了winAnimator.createSurfaceLocked的整个过程,我们看一下surfaceController.getSurface(outSurface), surfaceControllerWindowSurfaceController的实例:
//WindowSurfaceController.java
void getSurface(Surface outSurface) {
outSurface.copyFrom(mSurfaceControl);
} //Surface.java
public void copyFrom(SurfaceControl other) {
...
long surfaceControlPtr = other.mNativeObject;
...
long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr);
...
mNativeObject = ptr; // mNativeObject指向native创建的Surface
}

Surface.copyFrom()方法调用nativeGetFromSurfaceControl()来获取一个指针,这个指针是根据前面创建的SurfaceControl的指针来寻找的,即传入的参数surfaceControlPtr:

android_view_Surface.cpp

static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz, jlong surfaceControlNativeObj) {
sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj)); //把java指针转化内native指针
sp<Surface> surface(ctrl->getSurface()); //直接构造一个Surface,指向 ctrl->getSurface()
if (surface != NULL) {
surface->incStrong(&sRefBaseOwner); //强引用
}
return reinterpret_cast<jlong>(surface.get());
}

这里的ctrl指向前面创建的SurfaceControl,继续追溯ctrl->getSurface():

sp<Surface> SurfaceControl::getSurface() const
{
Mutex::Autolock _l(mLock);
if (mSurfaceData == 0) {
return generateSurfaceLocked();
}
return mSurfaceData;
} sp<Surface> SurfaceControl::generateSurfaceLocked() const
{
//这个mGraphicBufferProducer其实就是上面分析的BufferQueueProducer
mSurfaceData = new Surface(mGraphicBufferProducer, false);
return mSurfaceData;
}

即直接new了一个nativie的Surface返回给java层,java层的Surface指向的就是native层的Surface

所以Surface的实际创建可以用下图表示:

经过上面这个图,也可以理解SurfaceControl为什么叫SurfaceControl了。

Android的Surface的创建的更多相关文章

  1. [转]Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

    转自:Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划 前面我们从Android应用程序与SurfaceFlinger服务的关系出发,从侧面简单学习了Surfa ...

  2. Android系统Surface机制的SurfaceFlinger服务的线程模型分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8062945 在前面两篇文章中,我们分析了Sur ...

  3. Android系统Surface机制的SurfaceFlinger服务对帧缓冲区(Frame Buffer)的管理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8046659 在前文中,我们分析了Surface ...

  4. Android系统Surface机制的SurfaceFlinger服务的启动过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8022957 在前面一篇文章中,我们简要介绍了A ...

  5. Android使用surface直接显示yuv数据(三)

    在本文中,Java创建UI和关节JNI经营层surface直接显示yuv数据(yv12).发展环境Android 4.4,驰A23平台. package com.example.myyuvviewer ...

  6. 「Android」 Surface分析

    本篇针对Surface模块进行分析,从Java层的Activity创建开始,到ViewRoot.WindowsManagerService,再到JNI层和Native层. 首先推荐一个Android源 ...

  7. Android用surface直接显示yuv数据(三)

    本文用Java创建UI并联合JNI层操作surface来直接显示yuv数据(yv12),开发环境为Android 4.4,全志A23平台. package com.example.myyuvviewe ...

  8. Android系统Surface机制的SurfaceFlinger服务渲染应用程序UI的过程分析

    参考:Android系统Surface机制的SurfaceFlinger服务渲染应用程序UI的过程分析 一句话概括一下Android应用程序显示的过程:Android应用程序调用SurfaceFlin ...

  9. [Android Pro] Android 官方推荐 : DialogFragment 创建对话框

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37815413 1. 概述 DialogFragment在android 3.0时 ...

随机推荐

  1. Docker下载镜像并创建容器运行

    在linux系统中安装完成docker后,我们开始进行docker的镜像.容器的使用. 在使用docker时,首先要明确的两个概念:image(镜像) 与  container (容器) image: ...

  2. STM32F407 正点原子按键输入实验

    库函数版本: 库函数 源文件 头文件 GPIO_Init(GPIOE, &GPIOE_initstructure) stm32f4xx_gpio.c stm32f4xx_gpio.h RCC_ ...

  3. 基于linux(CentOS7)数据库性能优化(Postgresql)

    基于CentOS7数据库性能优化(Postgresql) 1.  磁盘 a)         Barriers IO i.              通过查看linux是否加载libata,确定是否开 ...

  4. 配置阿里云SLB全站HTTPS集群(以下内容仅为流程,信息可能有些对应不上)

    1)登录阿里云购买两台实例 1.1) 按量付费购买两台实例 1.2) 配置网络可以不选择分配外网 1.3) 自定义密码 1.4) 购买完成 1.5) 实例列表 2)购买SLB实例 2.1)按量付费购买 ...

  5. MyBatis中返回List

    一般情况下,我们需要返回一个List 在Dao层定义: List<TbAddress> selectAll(); 那么在对应的mapper文件中,应该如下: <select id=& ...

  6. 牛客假日团队赛5 F 随机数 BZOJ 1662: [Usaco2006 Nov]Round Numbers 圆环数 (dfs记忆化搜索的数位DP)

    链接:https://ac.nowcoder.com/acm/contest/984/F 来源:牛客网 随机数 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6 ...

  7. Python核心技术与实战——九|面向对象

    在搞清了各种数据类型.赋值判断.循环以后如果是从C++.Java语言入手的,就会有一个深坑要过:OOP(object oriented programming):公私有保护.多重继承.多态派生.纯函数 ...

  8. PHP环境安全性能检查

    PHP环境安全性能检查 PHP在Linux环境下安全配置是一个复杂的过程,其中涉及到很多的细节设置,在这里发出来一个脚本,通过这个脚本来检测你的PHP环境是否存在安全隐患,从而针对这些对你的PHP环境 ...

  9. 【leetcode】1123. Lowest Common Ancestor of Deepest Leaves

    题目如下: Given a rooted binary tree, return the lowest common ancestor of its deepest leaves. Recall th ...

  10. linux运维、架构之路-Hadoop完全分布式集群搭建

    一.介绍 Hadoop实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS.HDFS有高容错性的特点,并且设计用来部署在低廉的(low-cost)硬件 ...