android Graphiics data output
来源:互联网 发布:软件 开发的软件环境 编辑:程序博客网 时间:2024/06/06 03:59
上篇博客分析到SurfaceFlinger收到了VSync信号后,调用了handleMessageRefresh函数,这篇博客主要就是分析这个函数,我们先看看它的代码:
void
SurfaceFlinger::handleMessageRefresh() {
ATRACE_CALL();
static
nsecs_t previousExpectedPresent =
0
;
nsecs_t expectedPresent = mPrimaryDispSync.computeNextRefresh(
0
);
static
bool previousFrameMissed =
false
;
bool frameMissed = (expectedPresent == previousExpectedPresent);
if
(frameMissed != previousFrameMissed) {
ATRACE_INT(
"FrameMissed"
, static_cast<
int
>(frameMissed));
}
previousFrameMissed = frameMissed;
if
(CC_UNLIKELY(mDropMissedFrames && frameMissed)) {
// Latch buffers, but don't send anything to HWC, then signal another
// wakeup for the next vsync
preComposition();
repaintEverything();
}
else
{
preComposition();
rebuildLayerStacks();
setUpHWComposer();
doDebugFlashRegions();
doComposition();
postComposition();
}
previousExpectedPresent = mPrimaryDispSync.computeNextRefresh(
0
);
}</
int
>
我们主要看下下面几个函数。
preComposition();
rebuildLayerStacks();
setUpHWComposer();
doDebugFlashRegions();
doComposition();
postComposition();
一、preComposition函数
我们先来看第一个函数preComposition
void
SurfaceFlinger::preComposition()
{
bool needExtraInvalidate =
false
;
const
LayerVector& layers(mDrawingState.layersSortedByZ);
const
size_t count = layers.size();
for
(size_t i=
0
; i<count -=
""
if
=
""
>onPreComposition()) {
needExtraInvalidate =
true
;
}
}
if
(needExtraInvalidate) {
signalLayerUpdate();
}
}
</count>
上面函数先是调用了mDrawingState的layersSortedByZ来得到上次绘图的Layer层列表。并不是所有的Layer都会参与屏幕图像的绘制,因此SurfaceFlinger用state对象来记录参与绘制的Layer对象。
记得在之前的博客,我们分析过createLayer函数来创建Layer,创建之后会调用addClientLayer函数。
status_t SurfaceFlinger::createLayer(
const
String8& name,
const
sp<client>& client,
uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
sp<ibinder>* handle, sp<igraphicbufferproducer>* gbp)
{
//ALOGD("createLayer for (%d x %d), name=%s", w, h, name.string());
if
(int32_t(w|h) <
0
) {
ALOGE(
"createLayer() failed, w or h is negative (w=%d, h=%d)"
,
int
(w),
int
(h));
return
BAD_VALUE;
}
status_t result = NO_ERROR;
sp<layer> layer;
switch
(flags & ISurfaceComposerClient::eFXSurfaceMask) {
case
ISurfaceComposerClient::eFXSurfaceNormal:
result = createNormalLayer(client,
name, w, h, flags, format,
handle, gbp, &layer);
break
;
case
ISurfaceComposerClient::eFXSurfaceDim:
result = createDimLayer(client,
name, w, h, flags,
handle, gbp, &layer);
break
;
default
:
result = BAD_VALUE;
break
;
}
if
(result != NO_ERROR) {
return
result;
}
result = addClientLayer(client, *handle, *gbp, layer);
if
(result != NO_ERROR) {
return
result;
}
setTransactionFlags(eTransactionNeeded);
return
result;
}</layer></igraphicbufferproducer></ibinder></client>
我们来看下addClientLayer函数,这里会把Layer对象放在mCurrentState的layersSortedByZ对象中。而mDrawingState和mCurrentState什么关系呢?在后面我们会介绍,mDrawingState代表上一次绘图时的状态,处理完之后会把mCurrentState赋给mDrawingState。
status_t SurfaceFlinger::addClientLayer(
const
sp<client>& client,
const
sp<ibinder>& handle,
const
sp<igraphicbufferproducer>& gbc,
const
sp<layer>& lbc)
{
// add this layer to the current state list
{
Mutex::Autolock _l(mStateLock);
if
(mCurrentState.layersSortedByZ.size() >= MAX_LAYERS) {
return
NO_MEMORY;
}
mCurrentState.layersSortedByZ.add(lbc);
mGraphicBufferProducerList.add(IInterface::asBinder(gbc));
}
// attach this layer to the client
client->attachLayer(handle, lbc);
return
NO_ERROR;
}</layer></igraphicbufferproducer></ibinder></client>
回到preComposition函数,遍历所有的Layer对象,调用其onPreComposition函数来检测Layer层中的图像是否有变化。
void
SurfaceFlinger::preComposition()
{
bool needExtraInvalidate =
false
;
const
LayerVector& layers(mDrawingState.layersSortedByZ);
const
size_t count = layers.size();
for
(size_t i=
0
; i<count -=
""
if
=
""
>onPreComposition()) {
needExtraInvalidate =
true
;
}
}
if
(needExtraInvalidate) {
signalLayerUpdate();
}
}</count>
1.1 每个Layer的onFrameAvailable函数
onPreComposition函数来根据mQueuedFrames来判断图像是否发生了变化,或者是mSidebandStreamChanged。
bool Layer::onPreComposition() {
mRefreshPending =
false
;
return
mQueuedFrames >
0
|| mSidebandStreamChanged;
}
当Layer所对应的Surface更新图像后,它所对应的Layer对象的onFrameAvailable函数会被调用来通知这种变化。
我们看Layer的onFirstRef函数,先调用BufferQueue::createBufferQueue来获取一个Buffer的消费者和生产者。然后新建了一个MonitoredProducer和一个SurfaceFlingerConsumer
void
Layer::onFirstRef() {
// Creates a custom BufferQueue for SurfaceFlingerConsumer to use
sp<igraphicbufferproducer> producer;
sp<igraphicbufferconsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
mProducer =
new
MonitoredProducer(producer, mFlinger);
mSurfaceFlingerConsumer =
new
SurfaceFlingerConsumer(consumer, mTextureName);
mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(
0
));
mSurfaceFlingerConsumer->setContentsChangedListener(
this
);
mSurfaceFlingerConsumer->setName(mName);</igraphicbufferconsumer></igraphicbufferproducer>
我们再来看SurfaceFlingerConsumer的setContentsChangedListener函数
void
SurfaceFlingerConsumer::setContentsChangedListener(
const
wp<contentschangedlistener>& listener) {
setFrameAvailableListener(listener);
Mutex::Autolock lock(mMutex);
mContentsChangedListener = listener;
}</contentschangedlistener>
上面函数是调用了基类ConsumerBase的setFrameAvailableListener函数,将listener赋给了mFrameAvailableListener。
void
ConsumerBase::setFrameAvailableListener(
const
wp<frameavailablelistener>& listener) {
CB_LOGV(
"setFrameAvailableListener"
);
Mutex::Autolock lock(mMutex);
mFrameAvailableListener = listener;
}</frameavailablelistener>
而最终在其onFrameAvailable函数中调用了listener->onFrameAvailable函数。
void
ConsumerBase::onFrameAvailable(
const
BufferItem& item) {
CB_LOGV(
"onFrameAvailable"
);
sp<frameavailablelistener> listener;
{
// scope for the lock
Mutex::Autolock lock(mMutex);
listener = mFrameAvailableListener.promote();
}
if
(listener != NULL) {
CB_LOGV(
"actually calling onFrameAvailable"
);
listener->onFrameAvailable(item);
}
}</frameavailablelistener>
void
Layer::onFrameAvailable(
const
BufferItem& item) {
// Add this buffer from our internal queue tracker
{
// Autolock scope
Mutex::Autolock lock(mQueueItemLock);
// Reset the frame number tracker when we receive the first buffer after
// a frame number reset
if
(item.mFrameNumber ==
1
) {
mLastFrameNumberReceived =
0
;
}
// Ensure that callbacks are handled in order
while
(item.mFrameNumber != mLastFrameNumberReceived +
1
) {
status_t result = mQueueItemCondition.waitRelative(mQueueItemLock,
ms2ns(
500
));
if
(result != NO_ERROR) {
ALOGE(
"[%s] Timed out waiting on callback"
, mName.string());
}
}
mQueueItems.push_back(item);
android_atomic_inc(&mQueuedFrames);
// Wake up any pending callbacks
mLastFrameNumberReceived = item.mFrameNumber;
mQueueItemCondition.broadcast();
}
mFlinger->signalLayerUpdate();
}
同样在SurfaceFlinger的preComposition函数中当有Layer的图像改变了,最后也会调用SurfaceFlinger的signalLayerUpdate函数。
void
SurfaceFlinger::preComposition()
{
bool needExtraInvalidate =
false
;
const
LayerVector& layers(mDrawingState.layersSortedByZ);
const
size_t count = layers.size();
for
(size_t i=
0
; i<count -=
""
if
=
""
>onPreComposition()) {
needExtraInvalidate =
true
;
}
}
if
(needExtraInvalidate) {
signalLayerUpdate();
}
}</count>
SurfaceFlinger::signalLayerUpdate是调用了MessageQueue的invalidate函数
void
SurfaceFlinger::signalLayerUpdate() {
mEventQueue.invalidate();
}
调用了handler的dispatchInvalidate函数,
void
MessageQueue::invalidate() {
#
if
INVALIDATE_ON_VSYNC
mEvents->requestNextVsync();
#
else
mHandler->dispatchInvalidate();
#endif
}
Handler::dispatchInvalidate只是发送了一个消息
void
MessageQueue::Handler::dispatchInvalidate() {
if
((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) ==
0
) {
mQueue.mLooper->sendMessage(
this
, Message(MessageQueue::INVALIDATE));
}
}
最后处理还是调用了SurfaceFlinger的onMessageReceived函数。
case
INVALIDATE:
android_atomic_and(~eventMaskInvalidate, &mEventMask);
mQueue.mFlinger->onMessageReceived(message.what);
break
;
我们再来看看SurfaceFlinger的onMessageReceived函数对NVALIDATE的处理
void
SurfaceFlinger::onMessageReceived(int32_t what) {
ATRACE_CALL();
switch
(what) {
......
case
MessageQueue::INVALIDATE: {
bool refreshNeeded = handleMessageTransaction();
refreshNeeded |= handleMessageInvalidate();
refreshNeeded |= mRepaintEverything;
if
(refreshNeeded) {
// Signal a refresh if a transaction modified the window state,
// a new buffer was latched, or if HWC has requested a full
// repaint
signalRefresh();
}
break
;
我们先来看下handleMessageTransaction和handleMessageInvalidate函数。
bool SurfaceFlinger::handleMessageTransaction() {
uint32_t transactionFlags = peekTransactionFlags(eTransactionMask);
if
(transactionFlags) {
handleTransaction(transactionFlags);
return
true
;
}
return
false
;
}
bool SurfaceFlinger::handleMessageInvalidate() {
ATRACE_CALL();
return
handlePageFlip();
}
handleMessageInvalidate函数中调用了handlePageFlip函数,这个函数将会处理Layer中的缓冲区,把更新过的图像缓冲区切换到前台,等待VSync信号更新到FrameBuffer。
1.2 绘制流程
具体完整的绘制流程如图。
用户进程更新Surface图像,将导致SurfaceFlinger中的Layer发送invalidate消息,处理该消息会调用handleTransaction函数和handlePageFilp函数来更新Layer对象。一旦VSync信号到来,再调用rebuildlayerStacks setUpHWComposer doComposition postComposition函数将所有Layer的图像混合后更新到显示设备上去。
二、handleTransaction handPageFlip更新Layer对象
在上一节中的绘图的流程中,我们看到了handleTransaction和handPageFlip这两个函数通常是在用户进程更新Surface图像时会调用,来更新Layer对象。这节就主要讲解这两个函数。
2.1handleTransaction函数
handleTransaction函数的参数是transactionFlags,不过函数中没有使用这个参数,而是通过getTransactionFlags(eTransactionMask)来重新对transactionFlags赋值,然后使用它作为参数来调用函数handleTransactionLocked。
void
SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
{
ATRACE_CALL();
State drawingState(mDrawingState);
Mutex::Autolock _l(mStateLock);
const
nsecs_t now = systemTime();
mDebugInTransaction = now;
transactionFlags = getTransactionFlags(eTransactionMask);
//产生一个新的transactionFlags变量
handleTransactionLocked(transactionFlags);
mLastTransactionTime = systemTime() - now;
mDebugInTransaction =
0
;
invalidateHwcGeometry();
}
getTransactionFlags函数的参数是eTransactionMask只是屏蔽其他位。
uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags) {
return
android_atomic_and(~flags, &mTransactionFlags) & flags;
}
handleTransactionLocked函数会调用每个Layer类的doTransaction函数,在分析handleTransactionLocked函数之前,我们先看看Layer类 的doTransaction函数。
2.2 Layer的doTransaction函数
下面是Layer的doTransaction函数代码
uint32_t Layer::doTransaction(uint32_t flags) {
ATRACE_CALL();
const
Layer::State& s(getDrawingState());
//上次绘制的State对象
const
Layer::State& c(getCurrentState());
//当前使用的State对象
const
bool sizeChanged = (c.requested.w != s.requested.w) ||
//如果两个对象的大小不相等,说明Layer的尺寸发生变化
(c.requested.h != s.requested.h);
if
(sizeChanged) {
//如果Layer的尺寸发生变化,就要改变Surface的缓冲区的尺寸
mSurfaceFlingerConsumer->setDefaultBufferSize(
c.requested.w, c.requested.h);
}
if
(!isFixedSize()) {
//如果Layer不是固定尺寸的类型,比较它的实际大小和要求的改变大小
const
bool resizePending = (c.requested.w != c.active.w) ||
(c.requested.h != c.active.h);
if
(resizePending && mSidebandStream == NULL) {
//如果两者不一样,flags加上不更新Geometry标志
flags |= eDontUpdateGeometryState;
}
}
if
(flags & eDontUpdateGeometryState) {
}
else
{
//如果没有eDontUpdateGeometryState标志,更新active的值为request
Layer::State& editCurrentState(getCurrentState());
editCurrentState.active = c.requested;
}
if
(s.active != c.active) {
// 如果当前state的active和以前的State的active不等,设置更新标志
flags |= Layer::eVisibleRegion;
}
if
(c.sequence != s.sequence) {
//如果当前state的sequence和以前state的sequence不等,设置更新标志
flags |= eVisibleRegion;
this
->contentDirty =
true
;
const
uint8_t type = c.transform.getType();
mNeedsFiltering = (!c.transform.preserveRects() ||
(type >= Transform::SCALE));
}
// Commit the transaction
commitTransaction();
//将mCurrentState的值赋给mDrawingState
return
flags;
}
Layer类中的两个类型为Layer::State的成员变量mDrawingState、mCurrentState,我们来看下Layer::State类型
struct State {
Geometry active;
//实际大小
Geometry requested;
//用户大小
uint32_t z;
//Layer的Z轴值
uint32_t layerStack;
//和显示设备的关联值
uint8_t alpha;
//Layer的透明度
uint8_t flags;
//Layer的标志
uint8_t reserved[
2
];
int32_t sequence;
//序列值,Layer的属性变化一次,这个值就加1
Transform transform;
// the transparentRegion hint is a bit special, it's latched only
// when we receive a buffer -- this is because it's "content"
// dependent.
Region activeTransparentRegion;
//实际的透明区域
Region requestedTransparentRegion;
//用户社会中的透明区域
};
这里为什么要两个对象呢?Layer对象在绘制图形时,使用的是mDrawingState变量,用户调用接口设置Layer对象属性是,设置的值保存在mCurrentState对象中,这样就不会因为用户的操作而干扰Layer对象的绘制了。
Layer的doTransaction函数据你是比较这两个变量,如果有不同的地方,说明在上次绘制以后,用户改变的Layer的设置,要把这种变化通过flags返回。
State的结构中有两个Geometry字段,active和requested。他们表示layer的尺寸,其中requested保存是用户设置的尺寸,而active保存的值通过计算后的实际尺寸。
State中的z字段的值就是Layer在显示轴的位置,值越小位置越靠下。
layerStack字段是用户指定的一个值,用户可以给DisplayDevice也指定一个layerStack值,只有Layer对象和DisplayDevice对象的layerStack相等,这个Layer才能在这个显示设备上输出,这样的好处是可以让显示设备只显示某个Surface的内容。例如,可以让HDMI显示设备只显示手机上播放视频的Surface窗口,但不显示Activity窗口。
sequence字段是个序列值,每当用户调用了Layer的接口,例如setAlpha、setSize或者setLayer等改变Layer对象属性的哈数,这个值都会加1。因此在doTransaction函数中能通过比较sequence值来判断Layer的属性值有没有变化。
doTransaction函数最后会调用commitTransaction函数,就是把mCurrentState赋值给mDrawingState
void
Layer::commitTransaction() {
mDrawingState = mCurrentState;
}
- 软件开发|web前端|Web开发|移动开发|综合编程|
- 首页 >程序开发 > 移动开发 > Android > 正文
- Android6.0 显示系统(六) 图像的输出过程
- 2016-10-14 0 个评论 来源:kc58236582的博客
- 收藏 我要投稿
2.3 handleTransactionLocked函数
下面我们来分析handleTransactionLocked函数,这个函数比较长,我们分段分析
2.3.1 处理Layer的事务
在SurfaceFlinger中也有两个类型为State的变量mCurrentState和mDrawingState,但是和Layer中的不要混起来。它的名字相同而已1234567891011121314151617181920void
SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
{
const
LayerVector& currentLayers(mCurrentState.layersSortedByZ);
const
size_t count = currentLayers.size();
/*
* Traversal of the children
* (perform the transaction for each of them if needed)
*/
if
(transactionFlags & eTraversalNeeded) {
for
(size_t i=
0
; i<count
const
=
""
layer=
""
>& layer(currentLayers[i]);
uint32_t trFlags = layer->getTransactionFlags(eTransactionNeeded);
if
(!trFlags)
continue
;
const
uint32_t flags = layer->doTransaction(
0
);
if
(flags & Layer::eVisibleRegion)
mVisibleRegionsDirty =
true
;
}
}</count>
1234struct State {
LayerVector layersSortedByZ;
DefaultKeyedVector< wp<ibinder>, DisplayDeviceState> displays;
};</ibinder>
结构layersSortedByZ字段保存所有参与绘制的Layer对象,而字段displays保存的是所有输出设备的DisplayDeviceState对象
这里用两个变量的目的是和Layer中使用两个变量是一样的。
上面代码根据eTraversalNeeded标志来决定是否要检查所有的Layer对象。如果某个Layer对象中有eTransactionNeeded标志,将调用它的doTransaction函数。Layer的doTransaction函数返回的flags如果有eVisibleRegion,说明这个Layer需要更新,就把mVisibleRegionsDirty设置为true
2.3.2 处理显示设备的变化
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364if
(transactionFlags & eDisplayTransactionNeeded) {
//得到当前显示设备列表和之前使用的显示设备列表
const
KeyedVector< wp<ibinder>, DisplayDeviceState>& curr(mCurrentState.displays);
const
KeyedVector< wp<ibinder>, DisplayDeviceState>& draw(mDrawingState.displays);
if
(!curr.isIdenticalTo(draw)) {
mVisibleRegionsDirty =
true
;
const
size_t cc = curr.size();
//现在显示设备的数量
size_t dc = draw.size();
//以前显示设备的数量
for
(size_t i=
0
; i<dc ......=
""
const
=
""
else
=
""
if
=
""
j=
""
main=
""
remove=
""
ssize_t=
""
the=
""
to=
""
trying=
""
>
0
代表这个设备两个列表中都存在,再检查有没有其他变化
// this display is in both lists. see if something changed.
const
DisplayDeviceState& state(curr[j]);
const
wp<ibinder>& display(curr.keyAt(j));
const
sp<ibinder> state_binder = IInterface::asBinder(state.surface);
const
sp<ibinder> draw_binder = IInterface::asBinder(draw[i].surface);
if
(state_binder != draw_binder) {
//设备的Surface已经发生了变化(Surface对象不是一个了),旧的设备必须先删除掉
sp<displaydevice> hw(getDisplayDevice(display));
if
(hw != NULL)
hw->disconnect(getHwComposer());
mDisplays.removeItem(display);
mDrawingState.displays.removeItemsAt(i);
dc--; i--;
// at this point we must loop to the next item
continue
;
}
const
sp<displaydevice> disp(getDisplayDevice(display));
if
(disp != NULL) {
//两个对象的layerStack不相等,使用当前对象的
if
(state.layerStack != draw[i].layerStack) {
disp->setLayerStack(state.layerStack);
}
//如果两个对象的方向、viewport、frame不相等,使用当前对象的
if
((state.orientation != draw[i].orientation)
|| (state.viewport != draw[i].viewport)
|| (state.frame != draw[i].frame))
{
disp->setProjection(state.orientation,
state.viewport, state.frame);
}
if
(state.width != draw[i].width || state.height != draw[i].height) {
disp->setDisplaySize(state.width, state.height);
}
}
}
}
// 处理显示设备增加的情况
for
(size_t i=
0
; i<cc ......=
""
const
=
""
ibinder=
""
if
=
""
>& display(curr.keyAt(i));
if
(dispSurface != NULL) {
sp<displaydevice> hw =
new
DisplayDevice(
this
,
state.type, hwcDisplayId,
mHwc->getFormat(hwcDisplayId), state.isSecure,
display, dispSurface, producer,
mRenderEngine->getEGLConfig());
......
mDisplays.add(display, hw);
......
}
}
}
}
}</displaydevice></cc></displaydevice></displaydevice></ibinder></ibinder></ibinder></dc></ibinder></ibinder>
这段代码的作用是处理显示设备的变化,分成3种情况:
1.显示设备减少了,需要把显示设备对应的DisplayDevice移除
2.显示设备发生了变化,例如用户设置了Surface、重新设置了layerStack、旋转了屏幕等,这就需要重新设置显示对象的属性
3.显示设备增加了,创建新的DisplayDevice加入系统中。
2.3.3 设置TransfromHit
1234567891011121314151617181920212223242526272829if
(transactionFlags & (eTraversalNeeded|eDisplayTransactionNeeded)) {
sp<
const
displaydevice=
""
> disp;
uint32_t currentlayerStack =
0
;
for
(size_t i=
0
; i<count; .=
""
are=
""
by=
""
const
=
""
displays=
""
every=
""
fact=
""
first=
""
for
=
""
have=
""
layer=
""
layers=
""
layerstack=
""
list=
""
note:=
""
of=
""
on=
""
rely=
""
so=
""
sorted=
""
t=
""
that=
""
the=
""
to=
""
traverse=
""
we=
""
>& layer(currentLayers[i]);
uint32_t layerStack = layer->getDrawingState().layerStack;
if
(i==
0
|| currentlayerStack != layerStack) {
currentlayerStack = layerStack;
// figure out if this layerstack is mirrored
// (more than one display) if so, pick the default display,
// if not, pick the only display it's on.
disp.clear();
//清除disp
for
(size_t dpy=
0
; dpy<mdisplays.size()
const
=
""
displaydevice=
""
> hw(mDisplays[dpy]);
if
(hw->getLayerStack() == currentlayerStack) {
if
(disp == NULL) {
disp = hw;
//找到了一个layerStacker相同的显示设备
}
else
{
disp = NULL;
//如果有两个显示设备的layerStacker相同,都不用
break
;
}
}
}
}
if
(disp == NULL) {
// 没有找到具有相同layerStack的显示设备,使用缺省设备
disp = getDefaultDisplayDevice();
}
layer->updateTransformHint(disp);
//设置Layer对象的TransformHint
}
}</mdisplays.size()></count;></
const
>
这段代码的作用是根据每种显示设备的不同,设置和显示设备关联在一起的Layer(主要看Layer的layerStack是否和DisplayDevice的layerStack)的TransformHint(主要指设备的显示方向orientation)。
2.3.4 处理Layer增加情况123456789101112131415161718192021const
LayerVector& layers(mDrawingState.layersSortedByZ);
if
(currentLayers.size() > layers.size()) {
// 如果有Layer加入,设置需要更新
mVisibleRegionsDirty =
true
;
}
// 处理有Layer删除的情况
if
(mLayersRemoved) {
mLayersRemoved =
false
;
mVisibleRegionsDirty =
true
;
const
size_t count = layers.size();
for
(size_t i=
0
; i<count
const
=
""
layer=
""
>& layer(layers[i]);
if
(currentLayers.indexOf(layer) <
0
) {
//如果这个Layer已经不存在了,把它的所在区域设置为需要更新的区域
const
Layer::State& s(layer->getDrawingState());
Region visibleReg = s.transform.transform(
Region(Rect(s.active.w, s.active.h)));
invalidateLayerStack(s.layerStack, visibleReg);
}
}
}</count>
这段代码处理Layer的增加情况,如果Layer增加了,需要重新计算设备的更新区域,因此把mVisibleRegionsDirty设为true,如果Layer删除了,需要把Layer的可见区域加入到系统需要更新的区域中。
2.3.5 设置mDrawingState
123commitTransaction();
updateCursorAsync();
调用commitTransaction和updateCursorAsync函数 commitTransaction函数作用是把mDrawingState的值设置成mCurrentState的值。而updateCursorAsync函数会更新所有显示设备中光标的位置。
2.3.6 小结
handleTransaction函数的作用的就是处理系统在两次刷新期间的各种变化。SurfaceFlinger模块中不管是SurfaceFlinger类还是Layer类,都采用了双缓冲的方式来保存他们的属性,这样的好处是刚改变SurfaceFlinger对象或者Layer类对象的属性是,不需要上锁,大大的提高了系统效率。只有在最后的图像输出是,才进行一次上锁,并进行内存的属性变化处理。正因此,应用进程必须收到VSync信号才开始改变Surface的内容。
2.4 handlePageFlip函数
handlePageFlip函数代码如下:
123456789101112131415161718192021222324252627282930313233343536bool SurfaceFlinger::handlePageFlip()
{
Region dirtyRegion;
bool visibleRegions =
false
;
const
LayerVector& layers(mDrawingState.layersSortedByZ);
bool frameQueued =
false
;
Vector<layer*> layersWithQueuedFrames;
//查找需要更新的Layer
for
(size_t i =
0
, count = layers.size(); i<count
const
=
""
layer=
""
>& layer(layers[i]);
if
(layer->hasQueuedFrame()) {
frameQueued =
true
;
if
(layer->shouldPresentNow(mPrimaryDispSync)) {
layersWithQueuedFrames.push_back(layer.get());
}
else
{
layer->useEmptyDamage();
}
}
else
{
layer->useEmptyDamage();
}
}
for
(size_t i =
0
, count = layersWithQueuedFrames.size() ; i<count
const
=
""
layer=
"layersWithQueuedFrames[i];"
layer-=
""
region=
""
>latchBuffer(visibleRegions));
layer->useSurfaceDamage();
const
Layer::State& s(layer->getDrawingState());
invalidateLayerStack(s.layerStack, dirty);
}
mVisibleRegionsDirty |= visibleRegions;
if
(frameQueued && layersWithQueuedFrames.empty()) {
signalLayerUpdate();
}
return
!layersWithQueuedFrames.empty();
}</count></count></layer*>
handlePageFlip函数先调用每个Layer对象的hasQueuedFrame函数,确定这个Layer对象是否有需要更新的图层,然后把需要更新的Layer对象放到layersWithQueuedFrames中。
我们先来看Layer的hasQueuedFrame方法就是看其mQueuedFrames是否大于0 和mSidebandStreamChanged。前面小节分析只要Surface有数据写入,就会调用Layer的onFrameAvailable函数,然后mQueuedFrames值加1.
1bool hasQueuedFrame()
const
{
return
mQueuedFrames >
0
|| mSidebandStreamChanged; }
继续看handlePageFlip函数,接着调用需要更新的Layer对象的latchBuffer函数,然后根据返回的更新区域调用invalidateLayerStack函数来设置更新设备对象的更新区域。
下面我们看看latchBuffer函数123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151Region Layer::latchBuffer(bool& recomputeVisibleRegions)
{
ATRACE_CALL();
if
(android_atomic_acquire_cas(
true
,
false
, &mSidebandStreamChanged) ==
0
) {
// mSidebandStreamChanged was true
mSidebandStream = mSurfaceFlingerConsumer->getSidebandStream();
if
(mSidebandStream != NULL) {
setTransactionFlags(eTransactionNeeded);
mFlinger->setTransactionFlags(eTraversalNeeded);
}
recomputeVisibleRegions =
true
;
const
State& s(getDrawingState());
return
s.transform.transform(Region(Rect(s.active.w, s.active.h)));
}
Region outDirtyRegion;
if
(mQueuedFrames >
0
) {
//mQueuedFrames大于0代表有Surface更新的要求
if
(mRefreshPending) {
return
outDirtyRegion;
}
// Capture the old state of the layer for comparisons later
const
State& s(getDrawingState());
const
bool oldOpacity = isOpaque(s);
sp<graphicbuffer> oldActiveBuffer = mActiveBuffer;
struct Reject :
public
SurfaceFlingerConsumer::BufferRejecter {
......
//定义Reject结构体
};
Reject r(mDrawingState, getCurrentState(), recomputeVisibleRegions,
getProducerStickyTransform() !=
0
);
uint64_t maxFrameNumber =
0
;
{
Mutex::Autolock lock(mQueueItemLock);
maxFrameNumber = mLastFrameNumberReceived;
}
status_t updateResult = mSurfaceFlingerConsumer->updateTexImage(&r,
//更新纹理
mFlinger->mPrimaryDispSync, maxFrameNumber);
if
(updateResult == BufferQueue::PRESENT_LATER) {
mFlinger->signalLayerUpdate();
//如果结果是推迟处理,发送Invalidate消息
return
outDirtyRegion;
}
else
if
(updateResult == SurfaceFlingerConsumer::BUFFER_REJECTED) {
// If the buffer has been rejected, remove it from the shadow queue
// and return early
Mutex::Autolock lock(mQueueItemLock);
mQueueItems.removeAt(
0
);
android_atomic_dec(&mQueuedFrames);
return
outDirtyRegion;
}
else
if
(updateResult != NO_ERROR || mUpdateTexImageFailed) {
// This can occur if something goes wrong when trying to create the
// EGLImage for this buffer. If this happens, the buffer has already
// been released, so we need to clean up the queue and bug out
// early.
{
Mutex::Autolock lock(mQueueItemLock);
mQueueItems.clear();
android_atomic_and(
0
, &mQueuedFrames);
}
// Once we have hit this state, the shadow queue may no longer
// correctly reflect the incoming BufferQueue's contents, so even if
// updateTexImage starts working, the only safe course of action is
// to continue to ignore updates.
mUpdateTexImageFailed =
true
;
return
outDirtyRegion;
}
{
// Autolock scope
auto currentFrameNumber = mSurfaceFlingerConsumer->getFrameNumber();
Mutex::Autolock lock(mQueueItemLock);
// Remove any stale buffers that have been dropped during
// updateTexImage
while
(mQueueItems[
0
].mFrameNumber != currentFrameNumber) {
mQueueItems.removeAt(
0
);
android_atomic_dec(&mQueuedFrames);
}
mQueueItems.removeAt(
0
);
}
// Decrement the queued-frames count. Signal another event if we
// have more frames pending.
if
(android_atomic_dec(&mQueuedFrames) >
1
) {
//减少mQueuedFrames的值
mFlinger->signalLayerUpdate();
//如果还有更多frame需要处理,要发消息
}
if
(updateResult != NO_ERROR) {
// something happened!
recomputeVisibleRegions =
true
;
return
outDirtyRegion;
}
//更新mActiveBuffer,得到现在需要输出的图像数据
mActiveBuffer = mSurfaceFlingerConsumer->getCurrentBuffer();
if
(mActiveBuffer == NULL) {
return
outDirtyRegion;
//出错
}
mRefreshPending =
true
;
mFrameLatencyNeeded =
true
;
//下面根据各种情况是否重新计算更新区域
if
(oldActiveBuffer == NULL) {
// the first time we receive a buffer, we need to trigger a
// geometry invalidation.
recomputeVisibleRegions =
true
;
}
Rect crop(mSurfaceFlingerConsumer->getCurrentCrop());
const
uint32_t transform(mSurfaceFlingerConsumer->getCurrentTransform());
const
uint32_t scalingMode(mSurfaceFlingerConsumer->getCurrentScalingMode());
if
((crop != mCurrentCrop) ||
(transform != mCurrentTransform) ||
(scalingMode != mCurrentScalingMode))
{
mCurrentCrop = crop;
mCurrentTransform = transform;
mCurrentScalingMode = scalingMode;
recomputeVisibleRegions =
true
;
}
if
(oldActiveBuffer != NULL) {
uint32_t bufWidth = mActiveBuffer->getWidth();
uint32_t bufHeight = mActiveBuffer->getHeight();
if
(bufWidth != uint32_t(oldActiveBuffer->width) ||
bufHeight != uint32_t(oldActiveBuffer->height)) {
recomputeVisibleRegions =
true
;
}
}
mCurrentOpacity = getOpacityForFormat(mActiveBuffer->format);
if
(oldOpacity != isOpaque(s)) {
recomputeVisibleRegions =
true
;
}
// FIXME: postedRegion should be dirty & bounds
Region dirtyRegion(Rect(s.active.w, s.active.h));
// transform the dirty region to window-manager space
outDirtyRegion = (s.transform.transform(dirtyRegion));
}
return
outDirtyRegion;
}</graphicbuffer>
LatchBuffer函数调用updateTextImage来得到需要的图像。这里参数r是Reject对象,其作用是判断在缓冲区的尺寸是否符合要求。调用updateTextImage函数如果得到的结果是PRESENT_LATER,表示推迟处理,然后调用signalLayerUpdate函数来发送invalidate消息,这次绘制过程就不处理这个Surface的图像了。
如果不需要推迟处理,把mQueuedFrames的值减1.
最后LatchBuffer函数调用mSurfaceFlingerConsumer的getCurrentBuffer来取回当前的图像缓冲区指针,保存在mActiveBuffer中。
2.5 小结
这样经过handleTransaction handlePageFlip两个函数处理,SurfaceFlinger中无论是Layer属性的变化还是图像的变化都处理好了,只等VSync信号到来就可以输出了。
三、rebuildLayerStacks函数前面介绍,VSync信号到来后,先是调用了rebuildLayerStacks函数
12345678910111213141516171819202122232425262728293031323334353637383940414243void
SurfaceFlinger::rebuildLayerStacks() {
// rebuild the visible layer list per screen
if
(CC_UNLIKELY(mVisibleRegionsDirty)) {
ATRACE_CALL();
mVisibleRegionsDirty =
false
;
invalidateHwcGeometry();
//计算每个显示设备上可见的Layer
const
LayerVector& layers(mDrawingState.layersSortedByZ);
for
(size_t dpy=
0
; dpy<mdisplays.size() layer=
""
region=
""
> > layersSortedByZ;
const
sp<displaydevice>& hw(mDisplays[dpy]);
const
Transform& tr(hw->getTransform());
const
Rect bounds(hw->getBounds());
if
(hw->isDisplayOn()) {
//计算每个layer的可见区域,确定设备需要重新绘制的区域
SurfaceFlinger::computeVisibleRegions(layers,
hw->getLayerStack(), dirtyRegion, opaqueRegion);
const
size_t count = layers.size();
for
(size_t i=
0
; i<count
const
=
""
layer=
""
>& layer(layers[i]);
const
Layer::State& s(layer->getDrawingState());
if
(s.layerStack == hw->getLayerStack()) {
//只需要和显示设备的LayerStack相同的layer
Region drawRegion(tr.transform(
layer->visibleNonTransparentRegion));
drawRegion.andSelf(bounds);
if
(!drawRegion.isEmpty()) {
//如果Layer的显示区域和显示设备的窗口有交集
//把Layer加入列表中
layersSortedByZ.add(layer);
}
}
}
}
//设置显示设备的可见Layer列表
hw->setVisibleLayersSortedByZ(layersSortedByZ);
hw->undefinedRegion.set(bounds);
hw->undefinedRegion.subtractSelf(tr.transform(opaqueRegion));
hw->dirtyRegion.orSelf(dirtyRegion);
}
}
}
</count></displaydevice></mdisplays.size()>
rebuildLayerStacks函数的作用是重建每个显示设备的可见layer对象列表。对于按显示轴(Z轴)排列的Layer对象,排在最前面的当然会优先显示,但是Layer图像可能有透明域,也可能有尺寸没有覆盖整个屏幕,因此下面的layer也有显示的机会。rebuildLayerStacks函数对每个显示设备,先计算和显示设备具有相同layerStack值的Layer对象在该显示设备上的可见区域。然后将可见区域和显示设备的窗口区域有交集的layer组成一个新的列表,最后把这个列表设置到显示设备对象中。
computeVisibleRegions函数首先计算每个Layer在设备上的可见区域visibleRegion。计算方法就是用整个Layer的区域减去上层所有不透明区域aboveOpaqueLayers。而上层所有不透明区域值是一个逐层累计的过程,每层都需要把自己的不透明区域累加到aboveOpaqueLayers中。
而每层的不透明区域的计算方法:如果Layer的alpha的值为255,并且layer的isOpaque函数为true,则本层的不透明区域等于Layer所在区域,否则为0.这样一层层算下来,就很容易得到每层的可见区域大小了。
其次,计算整个显示设备需要更新的区域outDirtyRegion。outDirtyRegion的值也是累计所有层的需要重回的区域得到的。如果Layer中的显示内容发生了变化,则整个可见区域visibleRegion都需要更新,同时还要包括上一次的可见区域,然后在去掉被上层覆盖后的区域得到的就是Layer需要更新的区域。如果Layer显示的内容没有变化,但是考虑到窗口大小的变化或者上层窗口的变化,因此Layer中还是有区域可以需要重绘的地方。这种情况下最简单的算法是用Layer计算出可见区域减去以前的可见区域就可以了。但是在computeVisibleRegions函数还引入了被覆盖区域,通常被覆盖区域和可见区域并不重复,因此函数中计算暴露区域是用可见区域减去被覆盖区域的。
四、setUpHWComposer函数
setUpHWComposer函数的作用是更新HWComposer对象中图层对象列表以及图层属性。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869void
SurfaceFlinger::setUpHWComposer() {
for
(size_t dpy=
0
; dpy<mdisplays.size() bool=
""
dirty=
"!mDisplays[dpy]-"
>getDirtyRegion(
false
).isEmpty();
bool empty = mDisplays[dpy]->getVisibleLayersSortedByZ().size() ==
0
;
bool wasEmpty = !mDisplays[dpy]->lastCompositionHadVisibleLayers;
bool mustRecompose = dirty && !(empty && wasEmpty);
mDisplays[dpy]->beginFrame(mustRecompose);
if
(mustRecompose) {
mDisplays[dpy]->lastCompositionHadVisibleLayers = !empty;
}
}
HWComposer& hwc(getHwComposer());
//得到系统HWComposer对象
if
(hwc.initCheck() == NO_ERROR) {
// build the h/w work list
if
(CC_UNLIKELY(mHwWorkListDirty)) {
mHwWorkListDirty =
false
;
for
(size_t dpy=
0
; dpy<mdisplays.size()
const
=
""
displaydevice=
""
> hw(mDisplays[dpy]);
const
int32_t id = hw->getHwcDisplayId();
if
(id >=
0
) {
const
Vector< sp<layer> >& currentLayers(
hw->getVisibleLayersSortedByZ());
const
size_t count = currentLayers.size();
//根据Layer数量在HWComposer中创建hwc_layer_list_t列表
if
(hwc.createWorkList(id, count) == NO_ERROR) {
......
}
}
}
}
// set the per-frame data
for
(size_t dpy=
0
; dpy<mdisplays.size()
const
=
""
displaydevice=
""
> hw(mDisplays[dpy]);
const
int32_t id = hw->getHwcDisplayId();
if
(id >=
0
) {
......
for
(size_t i=
0
; cur!=end && i<count
const
=
""
layer=
""
>& layer(currentLayers[i]);
//将Layer的mActiveBuffer设置到HWComposer中
layer->setPerFrameData(hw, *cur);
}
}
}
// If possible, attempt to use the cursor overlay on each display.
for
(size_t dpy=
0
; dpy<mdisplays.size()
const
=
""
displaydevice=
""
> hw(mDisplays[dpy]);
const
int32_t id = hw->getHwcDisplayId();
if
(id >=
0
) {
const
Vector< sp<layer> >& currentLayers(
hw->getVisibleLayersSortedByZ());
const
size_t count = currentLayers.size();
HWComposer::LayerListIterator cur = hwc.begin(id);
const
HWComposer::LayerListIterator end = hwc.end(id);
for
(size_t i=
0
; cur!=end && i<count
const
=
""
layer=
""
>& layer(currentLayers[i]);
if
(layer->isPotentialCursor()) {
cur->setIsCursorLayerHint();
break
;
}
}
}
}
status_t err = hwc.prepare();
ALOGE_IF(err,
"HWComposer::prepare failed (%s)"
, strerror(-err));
for
(size_t dpy=
0
; dpy<mdisplays.size()
const
=
""
displaydevice=
""
> hw(mDisplays[dpy]);
hw->prepareFrame(hwc);
}
}
}</mdisplays.size()></count></layer></mdisplays.size()></count></mdisplays.size()></layer></mdisplays.size()></mdisplays.size()>
HWComposer中有一个类型为DisplayData结构的数组mDisplayData,它维护着每个显示设备的信息。DisplayData结构中有一个类型为hwc_display_contents_l字段list,这个字段又有一个hwc_layer_l类型的数组hwLayers,记录该显示设备所有需要输出的Layer信息。
setUpHWComposer函数调用HWComposer的createWorkList函数就是根据每种显示设备的Layer数量,创建和初始化hwc_display_contents_l对象和hwc_layer_l数组
创建完HWComposer中的列表后,接下来是对每个Layer对象调用它的setPerFrameData函数,参数是HWComposer和HWCLayerInterface。setPerFrameData函数将Layer对象的当前图像缓冲区mActiveBuffer设置到HWCLayerInterface对象对应的hwc_layer_l对象中。
HWComposer类中除了前面介绍的Gralloc还管理着Composer模块,这个模块实现了硬件的图像合成功能。setUpHWComposer函数接下来调用HWComposer类的prepare函数,而prepare函数会调用Composer模块的prepare接口。最后到各个厂家的实现hwc_prepare函数将每种HWComposer中的所有图层的类型都设置为HWC_FRAMEBUFFER就结束了。
五、合成所有层的图像 (doComposition函数)
doComposition函数是合成所有层的图像,代码如下:
1234567891011121314151617181920void
SurfaceFlinger::doComposition() {
ATRACE_CALL();
const
bool repaintEverything = android_atomic_and(
0
, &mRepaintEverything);
for
(size_t dpy=
0
; dpy<mdisplays.size()
const
=
""
displaydevice=
""
>& hw(mDisplays[dpy]);
if
(hw->isDisplayOn()) {
// transform the dirty region into this screen's coordinate space
const
Region dirtyRegion(hw->getDirtyRegion(repaintEverything));
// 图像合成
doDisplayComposition(hw, dirtyRegion);
hw->dirtyRegion.clear();
hw->flip(hw->swapRegion);
hw->swapRegion.clear();
}
// inform the h/w that we're done compositing
hw->compositionComplete();
}
postFramebuffer();
}</mdisplays.size()>
doComposition函数针对每种显示设备调用doDisplayComposition函数来合成,合成后调用postFramebuffer函数,我们先来看看doDisplayComposition函数
123456789101112131415161718192021222324252627282930313233343536373839404142434445void
SurfaceFlinger::doDisplayComposition(
const
sp<
const
displaydevice=
""
>& hw,
const
Region& inDirtyRegion)
{
bool isHwcDisplay = hw->getHwcDisplayId() >=
0
;
if
(!isHwcDisplay && inDirtyRegion.isEmpty()) {
return
;
}
Region dirtyRegion(inDirtyRegion);
//swapRegion设置为需要更新的区域
hw->swapRegion.orSelf(dirtyRegion);
uint32_t flags = hw->getFlags();
//获得显示设备支持的更新方式标志
if
(flags & DisplayDevice::SWAP_RECTANGLE) {
//支持矩阵更新
dirtyRegion.set(hw->swapRegion.bounds());
}
else
{
if
(flags & DisplayDevice::PARTIAL_UPDATES) {
//支持部分更新
dirtyRegion.set(hw->swapRegion.bounds());
}
else
{
//将更新区域调整为整个窗口大小
dirtyRegion.set(hw->bounds());
hw->swapRegion = dirtyRegion;
}
}
if
(CC_LIKELY(!mDaltonize && !mHasColorMatrix)) {
if
(!doComposeSurfaces(hw, dirtyRegion))
return
;
//合成
}
else
{
RenderEngine& engine(getRenderEngine());
mat4 colorMatrix = mColorMatrix;
if
(mDaltonize) {
colorMatrix = colorMatrix * mDaltonizer();
}
mat4 oldMatrix = engine.setupColorTransform(colorMatrix);
doComposeSurfaces(hw, dirtyRegion);
//合成
engine.setupColorTransform(oldMatrix);
}
// update the swap region and clear the dirty region
hw->swapRegion.orSelf(dirtyRegion);
// swap buffers (presentation)
hw->swapBuffers(getHwComposer());
//没有硬件composer的情况,输出图像
}</
const
>
doDisplayComposition函数根据显示设备支持的更新方式,重新设置需要更新区域的大小。
真正的合成工作是在doComposerSurfaces函数中完成,这个函数在layer的类型为HWC_FRAMEBUFFER,或者不支持硬件的composer的情况下,调用layer的draw函数来一层一层低合成最后的图像。合成完后,doDisplayComposition函数调用了hw的swapBuffers函数,这个函数前面介绍过了,它将在系统不支持硬件的composer情况下调用eglSwapBuffers来输出图像到显示设备。
六、postFramebuffer函数
上一节的doComposition函数最后调用了postFramebuffer函数,代码如下:
1234567891011121314151617181920void
SurfaceFlinger::postFramebuffer()
{
ATRACE_CALL();
const
nsecs_t now = systemTime();
mDebugInSwapBuffers = now;
HWComposer& hwc(getHwComposer());
if
(hwc.initCheck() == NO_ERROR) {
if
(!hwc.supportsFramebufferTarget()) {
// EGL spec says:
// "surface must be bound to the calling thread's current context,
// for the current rendering API."
getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);
}
hwc.commit();
}
......
}
postFramebuffer先判断系统是否支持composer,如果不支持,我们知道图像已经在doComposition函数时调用hw->swapBuffers输出了,就返回了。如果支持硬件composer,postFramebuffer函数将调用HWComposer的commit函数继续执行。
12345678910111213141516171819202122status_t HWComposer::commit() {
int
err = NO_ERROR;
if
(mHwc) {
if
(!hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) {
// On version 1.0, the OpenGL ES target surface is communicated
// by the (dpy, sur) fields and we are guaranteed to have only
// a single display.
mLists[
0
]->dpy = eglGetCurrentDisplay();
mLists[
0
]->sur = eglGetCurrentSurface(EGL_DRAW);
}
for
(size_t i=VIRTUAL_DISPLAY_ID_BASE; i<mnumdisplays; -=
""
if
=
""
>outbuf = disp.outbufHandle;
mLists[i]->outbufAcquireFenceFd =
disp.outbufAcquireFence->dup();
}
}
err = mHwc->set(mHwc, mNumDisplays, mLists);
......
}
return
(status_t)err;
}</mnumdisplays;>
commit函数又调用了composer模块的set接口来完成工作,这就到HAL层的代码了,最后输出到显示屏上。
- android Graphiics data output
- ABAP OUTPUT DATA IN LIST
- Data input format and Data output format
- String data type: printf & cout output differently
- ArcGIS Server Open data output support GeoJSON
- android makefile message output
- android timed output vibrator driver
- Android data/data目录
- output
- Output
- output
- Output
- Error writing to data sink! Some output may get lost
- android软件 根目录 data/data
- Android audio加载output设备列表流程图
- error: Some data has already been output, can't send PDF file
- Ndomod: Could not open data sink! I'll keep trying, but some output may get lost
- 无法真机调试Installation failed with the following output:pkg: /data/local/tmp/Package.apk
- android注解与反射、ButterKnife实现
- npm本地化仓库Sinopia
- UnityAI行为------群组行为CraigReynold算法
- 抓取国家统计局区划、城乡划分代码的简易python爬虫实现
- jdbc连接mysql
- android Graphiics data output
- 使用JDK自带HttpsURLConnection发送HTTPS请求
- postgresql 结束进程
- Kotlin 踩坑日记(三) Android Studio 3.0.0 Canary 编译错误
- js ——假值和sort
- 《大型网站技术架构:核心原理与案例分析》拜读总结,第七章——随需应变:网站的可扩展架构
- 孙鑫c++教程笔记(1)
- intelij idea快捷键
- React-Native之iOS集成支付宝支付