概述
SurfaceFlinger是一项系统底层的服务,是负责UI方面的渲染的,与应用Application实时交互,实时刷新Surface,是与用户最直接打交道的一项服务。
下面我们将分析SurfaceFlinger服务的启动过程。
在systemcorerootdirinit.rc文件中,有这么段代码:
……
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
……
service surfaceflinger /system/bin/surfaceflinger
class main
user system
group graphics drmrpc
onrestart restart zygote
……
SurfaceFlinger服务是在init过程中通过执行/system/bin/surfaceflinger这个bin文件启动,而在编译过程中通过编译frameworks/native/services/surfaceflinger该目录下的代码最终生成了surfaceflinger这个bin文件。
好,下面来开始跟踪一下其中最终要的surfaceflinger服务的main函数。
Step 1、main()
在Android 4.4中,SurfaceFlinger的启动由frameworks/native/services/surfaceflinger/Main_surfaceflinger.cpp中的main函数启动。
int main(int argc, char** argv) {
// When SF is launched in its own process, limit the number of
// binder threads to 4.
ProcessState::self()->setThreadPoolMaxThreadCount(4);
// start the thread pool
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
// instantiate surfaceflinger
sp<SurfaceFlinger> flinger = new SurfaceFlinger(); //创建一个SurfaceFlinger对象
#if defined(HAVE_PTHREADS)
setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);
#endif
set_sched_policy(0, SP_FOREGROUND);
// initialize before clients can connect
flinger->init(); //初始化SurfaceFlinger
// publish surface flinger
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);//将SrufaceFlinger加入服务
// run in this thread
flinger->run();//运行SurfaceFlinger服务
return 0;
}
这里创建了SurfaceFlinger对象,随后初始化该对象,并且以该对象作为参数,将SurfaceFlinger服务加入ServiceManager中。
接下来,来分步解析。
Step 2、new SurfaceFlinger()
该代码在frameworks/native/services/surfaceflinger.cpp中。
SurfaceFlinger::SurfaceFlinger()
:
BnSurfaceComposer(),//创建SurfaceComposer本地对象
mTransactionFlags(0),
mTransactionPending(false),
mAnimTransactionPending(false),
mLayersRemoved(false),
mRepaintEverything(0),
mRenderEngine(NULL),
mBootTime(systemTime()),
mVisibleRegionsDirty(false),
mHwWorkListDirty(false),
mAnimCompositionPending(false),
mDebugRegion(0),
mDebugDDMS(0),
mDebugDisableHWC(0),
mDebugDisableTransformHint(0),
mDebugInSwapBuffers(0),
mLastSwapBufferTime(0),
mDebugInTransaction(0),
mLastTransactionTime(0),
mBootFinished(false),
mGpuTileRenderEnable(false),
mPrimaryHWVsyncEnabled(false),
mHWVsyncAvailable(false),
mDaltonize(false)
{
ALOGI("SurfaceFlinger is starting");
// debugging stuff...
char value[PROPERTY_VALUE_MAX];
property_get("ro.bq.gpu_to_cpu_unsupported", value, "0");
mGpuToCpuSupported = !atoi(value);
property_get("debug.sf.showupdates", value, "0");
mDebugRegion = atoi(value);
property_get("debug.sf.ddms", value, "0");
mDebugDDMS = atoi(value);
if (mDebugDDMS) {
if (!startDdmConnection()) {
// start failed, and DDMS debugging not enabled
mDebugDDMS = 0;
}
}
}
Step 2、SurfaceFlinger::OnFirstRef()
由于flinger为sp<SurfaceFlinger>强指针类型,当第一次被一个强指针引用时,就会执行SurfaceFlinger::OnFirstRef()函数。
void SurfaceFlinger::onFirstRef()
{
mEventQueue.init(this);
}
这里,mEventQueue是一个MessageQueue对象,是一个消息队列。定义的地方在SurfaceFlinger.h文件中。
mutable MessageQueue mEventQueue;
将SurfaceFlinger对象作为参数传入ini函数中,对MessageQueue初始化。将surfaceFlinger对象本神赋值给MessageQueue.mFlinger,也对MessageQueue中mHandler和mLooper进行赋值。
Step 3、MessageQueue::init
代码位于frameworks/native/services/surfaceflinger/MessageQueue.cpp中。
void MessageQueue::init(const sp<SurfaceFlinger>& flinger)
{
mFlinger = flinger; <pre name="code" class="java">
//创建主线程1
mLooper = new Looper(true);
mHandler = new Handler(*this);
}
将之前传入的SurfaceFlinger对象赋给MessageQueue的成员变量mFlinger,并且创建了消息队列的循环体Looper和事件处理器Handler。关于Looper和Handler,可以参考:http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html这个博客,里面讲的很是详细。
MessageQueue::init函数创建了下图中的主线程1,Looper配合着Message、Handle、Thread、MessageQueue完成了太多的事情,在这里简单说就是它搞了一个睡眠等待事件,然后等着被唤醒。结合到SF中就是说,这里开了一个线程等待surface的刷新,而做出相关的操作。至于是怎么唤醒的可以参考这个连接的博文http://blog.csdn.net/broadview2006/article/details/8552148。
Step 4、SurfaceFlinger::init()
在前面几步后,完成了SurfaceFlinger对象的创建过程后,需要对SurfaceFlinger对象进行初始化。
本部分的代码位于frameworks/native/services/wurfaceflinger.cpp中。
void SurfaceFlinger::init() {
status_t err;
Mutex::Autolock _l(mStateLock);
// initialize EGL for the default display
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);
// Initialize the H/W composer object.
There may or may not be an
// actual hardware composer underneath.
mHwc = new HWComposer(this,
*static_cast<HWComposer::EventHandler *>(this)); //创建HWComposer对象,通过HWComposer对象可以产生VSync同步信号。
// First try to get an ES2 config
err = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), EGL_OPENGL_ES2_BIT,
&mEGLConfig);
if (err != NO_ERROR) {
// If ES2 fails, try ES1
err = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(),
EGL_OPENGL_ES_BIT, &mEGLConfig);
}
// print some debugging info
EGLint r,g,b,a;
eglGetConfigAttrib(mEGLDisplay, mEGLConfig, EGL_RED_SIZE,
&r);
eglGetConfigAttrib(mEGLDisplay, mEGLConfig, EGL_GREEN_SIZE, &g);
eglGetConfigAttrib(mEGLDisplay, mEGLConfig, EGL_BLUE_SIZE,
&b);
eglGetConfigAttrib(mEGLDisplay, mEGLConfig, EGL_ALPHA_SIZE, &a);
// get a RenderEngine for the given display / config (can't fail)
mRenderEngine = RenderEngine::create(mEGLDisplay, mEGLConfig);
// retrieve the EGL context that was selected/created
mEGLContext = mRenderEngine->getEGLContext();
// figure out which format we got
eglGetConfigAttrib(mEGLDisplay, mEGLConfig,
EGL_NATIVE_VISUAL_ID, &mEGLNativeVisualId);
LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT,
"couldn't create EGLContext");
// initialize our non-virtual displays
for (size_t i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) {
DisplayDevice::DisplayType type((DisplayDevice::DisplayType)i);
// set-up the displays that are already connected
if (mHwc->isConnected(i) || type==DisplayDevice::DISPLAY_PRIMARY) {
// All non-virtual displays are currently considered secure.
bool isSecure = true;
createBuiltinDisplayLocked(type);
wp<IBinder> token = mBuiltinDisplays[i];
sp<BufferQueue> bq = new BufferQueue(new GraphicBufferAlloc());
sp<FramebufferSurface> fbs = new FramebufferSurface(*mHwc, i, bq);
sp<DisplayDevice> hw = new DisplayDevice(this,
type, allocateHwcDisplayId(type), isSecure, token,
fbs, bq,
mEGLConfig);
if (i > DisplayDevice::DISPLAY_PRIMARY) {
// FIXME: currently we don't get blank/unblank requests
// for displays other than the main display, so we always
// assume a connected display is unblanked.
ALOGD("marking display %d as acquired/unblanked", i);
hw->acquireScreen();
}
mDisplays.add(token, hw);
}
}
// make the GLContext current so that we can create textures when creating Layers
// (which may happens before we render something)
getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);
// start the EventThread
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true);
mEventThread = new EventThread(vsyncSrc);
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
sfVsyncPhaseOffsetNs, true);
mSFEventThread = new EventThread(sfVsyncSrc);
mEventQueue.setEventThread(mSFEventThread);
mEventControlThread = new EventControlThread(this);
mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
// set a fake vsync period if there is no HWComposer
if (mHwc->initCheck() != NO_ERROR) {
mPrimaryDispSync.setPeriod(16666667);
}
// initialize our drawing state
mDrawingState = mCurrentState;
// set initial conditions (e.g. unblank default device)
initializeDisplays();
// start boot animation
startBootAnim();
}
在init的过程中,创建了下图中的线程3,其中的关键代码如下:
// start the EventThread
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true);
mEventThread = new EventThread(vsyncSrc);
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
sfVsyncPhaseOffsetNs, true);
mSFEventThread = new EventThread(sfVsyncSrc);
mEventQueue.setEventThread(mSFEventThread);
mEventControlThread = new EventControlThread(this);
mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
这里分两个线路来说:
1、EventThread继承自Thread线程类,Thread的基类中有RefBase,如下:
class VSyncSource : public virtual RefBase {
public:
class Callback: public virtual RefBase {
public:
virtual ~Callback() {}
virtual void onVSyncEvent(nsecs_t when) = 0;
};
virtual ~VSyncSource() {}
virtual void setVSyncEnabled(bool enable) = 0;
virtual void setCallback(const sp<Callback>& callback) = 0;
};
class EventThread : public Thread, private VSyncSource::Callback {
class Connection : public BnDisplayEventConnection {
public:
Connection(const sp<EventThread>& eventThread);
status_t postEvent(const DisplayEventReceiver::Event& event);
// count >= 1 : continuous event. count is the vsync rate
// count == 0 : one-shot event that has not fired
// count ==-1 : one-shot event that fired this round / disabled
int32_t count;
private:
virtual ~Connection();
virtual void onFirstRef();
virtual sp<BitTube> getDataChannel() const;
virtual void setVsyncRate(uint32_t count);
virtual void requestNextVsync();
// asynchronous
sp<EventThread> const mEventThread;
sp<BitTube> const mChannel;
};
…
};
所以当EventThread定义的时候就会调用OnFirstRef()函数,这个函数中只有run()函数。
void EventThread::onFirstRef() {
run("EventThread", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
}
由于EventThread继承自Thread,所以在执行run函数之后,会执行EventThread::threadLoop()函数。
bool EventThread::threadLoop() {
DisplayEventReceiver::Event event;
Vector< sp<EventThread::Connection> > signalConnections;
signalConnections = waitForEvent(&event);//等待Vsync事件
// dispatch events to listeners...
const size_t count = signalConnections.size();
for (size_t i=0 ; i<count ; i++) {
const sp<Connection>& conn(signalConnections[i]);
// now see if we still need to report this event
status_t err = conn->postEvent(event);//事件信息写入BitTuBe,也就是已建立连接的套接字队的一个套接字中,注册的接收端通过另一个套接字接收数据
if (err == -EAGAIN || err == -EWOULDBLOCK) {
// The destination doesn't accept events anymore, it's probably
// full. For now, we just drop the events on the floor.
// FIXME: Note that some events cannot be dropped and would have
// to be re-sent later.
// Right-now we don't have the ability to do this.
ALOGW("EventThread: dropping event (%08x) for connection %p",
event.header.type, conn.get());
} else if (err < 0) {
// handle any other error on the pipe as fatal. the only
// reasonable thing to do is to clean-up this connection.
// The most common error we'll get here is -EPIPE.
removeDisplayEventConnection(signalConnections[i]);
}
}
return true;
}
当客户端请求渲染Surface的时候,这里就会接收到Vsync事件,则waitForEvent函数将返回用于连接客户端的signalConnections的Connection列表。(以后研究)
如果收到一个Vsync事件,就会执行到conn->postEvent(event),该函数作用请看上面的注释。至此,EventThread线程发送了一个套接字信息。下面继续讲讲上面提到的注册的接收端。
2、到底谁来接收套接字?
在之前SurfaceFlinger::init函数中有这样一句代码:
mEventQueue.setEventThread(mSFEventThread);
这个与pollOnce函数有关。
setEventThread函数的具体实现如下。
void MessageQueue::setEventThread(const sp<EventThread>& eventThread)
{
mEventThread = eventThread;
mEvents = eventThread->createEventConnection();//创建一个到EventThread的连接
mEventTube = mEvents->getDataChannel();//得到发送Vsync事件通知的BitTuBe
mLooper->addFd(mEventTube->getFd(), 0, ALOOPER_EVENT_INPUT,
MessageQueue::cb_eventReceiver, this);
}
上面addFd函数的第一个参数是获取套接字队列中的信息,一旦有东西就会调用MessageQueue::cb_eventReceiver这个回调函数。
int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {
MessageQueue* queue = reinterpret_cast<MessageQueue *>(data);
return queue->eventReceiver(fd, events);//返回事件接收者
}
根据套接字队列中的信息,得到事件队列,最后返回事件接收者。
int MessageQueue::eventReceiver(int fd, int events) {
ssize_t n;
DisplayEventReceiver::Event buffer[8];
while ((n = DisplayEventReceiver::getEvents(mEventTube, buffer, 8)) > 0) {
for (int i=0 ; i<n ; i++) {
if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
#if INVALIDATE_ON_VSYNC
mHandler->dispatchInvalidate();
#else
mHandler->dispatchRefresh();
#endif
break;
}
}
}
return 1;
}
这里主线程一直在获取事件,一旦事件类型为DISPLAY_EVENT_VSYNC,就会执行dispatchInvalidate或者dispatchRefresh函数。有关Vsync,之后再详细研究,先看看这篇博客:
http://blog.csdn.net/broadview2006/article/details/8541727。
dispatchInvalidate请看看这篇博客:http://blog.csdn.net/w401229755/article/details/38224481的后半段。这里只看dispatchRefresh函数。
void MessageQueue::Handler::dispatchRefresh() {
if ((android_atomic_or(eventMaskRefresh, &mEventMask) & eventMaskRefresh) == 0) {
mQueue.mLooper->sendMessage(this, Message(MessageQueue::REFRESH));
}
}
这里发送了REFRESH消息,在SurfaceFlinger::run函数中创建了下图中的线程2后在一直睡眠等待消息,其中的pollInner函数的handleMessage是具体处理消息的地方,具体分析请参照Step 8和Step 9。
Step 5、SurfaceFlinger::run()
在创建初始化SurfaceFlinger对象后,随后调用SurfaceFlinger的成员函数run()运行SurfaceFlinger服务。
本代码位于frameworks/native/services/surfaceflinger.cpp中。
void SurfaceFlinger::run() {
do {
waitForEvent();
} while (true);
}
这里创建了下图中的线程2。
由于SurfaceFlinger服务需要时刻运行,实时绘制,所以不能理解使用do...while循环无限调用SurfaceFlinger成员函数waitForEvent。
Step 6 、SurfaceFlinger::waitForEvent()
本代码位于frameworks/native/services/surfaceflinger.cpp中。
void SurfaceFlinger::waitForEvent() {
mEventQueue.waitMessage();
}
Step 7、MessageQueue::waitMessage()
本代码位于frameworks/native/services/MessageQueue.cpp中。
void MessageQueue::waitMessage() {
do {
IPCThreadState::self()->flushCommands();
int32_t ret = mLooper->pollOnce(-1);
switch (ret) {
case ALOOPER_POLL_WAKE:
case ALOOPER_POLL_CALLBACK:
continue;
case ALOOPER_POLL_ERROR:
ALOGE("ALOOPER_POLL_ERROR");
case ALOOPER_POLL_TIMEOUT:
// timeout (should not happen)
continue;
default:
// should not happen
ALOGE("Looper::pollOnce() returned unknown status %d", ret);
continue;
}
} while (true);
}
程序进入了一个死循环,即使mLooper->pollOnce的返回结果为ALOOPER_POLL_TIMEOUT也不会退出。Android对于处理一些严重的系统错误,采取的是一种"Let it be"的态度。
Step 8、Looper::pollOnce()
mLooper->pollOnce函数的代码位于frameworks/base/libs/utils/Looper.cpp中。
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
if (result != 0) {
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
}
result = pollInner(timeoutMillis);
}
}
Step 9、Looper::pollInner
int Looper::pollInner(int timeoutMillis) {
// Adjust the timeout based on when the next message is due.
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
}
// Poll.
int result = ALOOPER_POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
// We are about to idle.
mIdling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mIdling = false;
// Acquire lock.
mLock.lock();
// Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
result = ALOOPER_POLL_ERROR;
goto Done;
}
// Check for poll timeout.
if (eventCount == 0) {
result = ALOOPER_POLL_TIMEOUT;
goto Done;
}
// Handle all events.
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
// Invoke pending message callbacks.
mNextMessageUptime = LLONG_MAX;
<strong>while (mMessageEnvelopes.size() != 0)</strong> {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
// Remove the envelope from the list.
// We keep a strong reference to the handler until the call to handleMessage
// finishes.
Then we drop it so that the handler can be deleted *before*
// we reacquire our lock.
{ // obtain handler
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock();
handler->handleMessage(message);
} // release handler
mLock.lock();
mSendingMessage = false;
result = ALOOPER_POLL_CALLBACK;
} else {
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
// Release lock.
mLock.unlock();
// Invoke all response callbacks.
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == ALOOPER_POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = ALOOPER_POLL_CALLBACK;
}
}
return result;
}
pollOnce与pollInner的关系,请看看这篇博客:http://blog.csdn.net/broadview2006/article/details/8541727。
在Looper::pollInner函数中内部调用MessageQueue::mHandler处理消息?????
PS:(下面一段摘自大侠林学森的博客文章——Android显示系统之SurfaceFlinger(一))
这样子就构建了一个简洁而又完整的循环消息处理框架,SurfaceFlinger就是基于这个框架完成来自系统中各个程序的显示请求的。大家可能会有疑问,mHandler是由MessageQueue直接通过new Handler()生成的,这样的话如何能处理特定的SurfaceFlinger消息请求呢?个人感觉有这个困惑是由于Handler类取名不当引起的。实际上此Handler并非我们经常看到的那个Handler,这里的Handler是MessageQueue中自定义的一个事件处理器,也就是说它是专门为SurfaceFlinger设计的。
Step 10、MessageQueue::Handler::handleMessage()
代码位于frameworks/native/services/surfaceflinger/MessageQueue.cpp中。
void MessageQueue::Handler::handleMessage(const Message& message) {
switch (message.what) {
case INVALIDATE:
android_atomic_and(~eventMaskInvalidate, &mEventMask);
mQueue.mFlinger->onMessageReceived(message.what);
break;
case REFRESH:
android_atomic_and(~eventMaskRefresh, &mEventMask);
mQueue.mFlinger->onMessageReceived(message.what);
break;
case TRANSACTION:
android_atomic_and(~eventMaskTransaction, &mEventMask);
mQueue.mFlinger->onMessageReceived(message.what);
break;
}
}
当mHandler收到INVALIDATE、REFRESH和TRANSACTION消息后,又会回调SurfaceFlinger::onMessageReceived函数。这样绕了一圈友重新返回到了SurfaceFlinger中。
附上一张网上大牛画的一张有关SurfaceFlinger的图,逻辑很清晰。
最后
以上就是标致荷花为你收集整理的Android4.4——SurfaceFlinger启动的全部内容,希望文章能够帮你解决Android4.4——SurfaceFlinger启动所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复