Android應用程式請求SurfaceFlinger服務渲染Surface的過程分析
在前面一篇文章中,我們分析了Android應用程式請求SurfaceFlinger服務建立Surface的過程。有了Surface之後,Android應用程式就可以在上面繪製自己的UI了,接著再請求SurfaceFlinger服務將這個已經繪製好了UI的Surface渲染到裝置顯示屏上去。在本文中,我們就將詳細分析Android應用程式請求SurfaceFlinger服務渲染Surface的過程。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
Android應用程式在請求SurfaceFlinger
從前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文可以知道,Android系統的開機動畫應用程式bootanim是在BootAnimation類的成員函式readyToRun中請求SurfaceFlinger服務建立Surface的。這個Surface建立完成之後,就會被設定為當前活動的繪圖上下文,如下所示:
status_t BootAnimation::readyToRun() { ...... // create the native surface sp<SurfaceControl> control = session()->createSurface( getpid(), 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); ...... sp<Surface> s = control->getSurface(); ...... // initialize opengl and egl const EGLint attribs[] = { EGL_DEPTH_SIZE, 0, EGL_NONE }; ...... EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); EGLUtils::selectConfigForNativeWindow(display, attribs, s.get(), &config); surface = eglCreateWindowSurface(display, config, s.get(), NULL); context = eglCreateContext(display, config, NULL, NULL); ...... if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) return NO_INIT; ......}
BootAnimation類的成員函式readyToRun首先呼叫eglGetDisplay和eglInitialize函式來獲得和初始化OpengGL庫的預設顯示屏,接著再呼叫EGLUtils::selectConfigForNativeWindow函式來獲得前面所建立的一個Surface(由sp<Surface>指標s來描述)的配置資訊。有了這些資訊之後,接下來就分別呼叫eglCreateWindowSurface和eglCreateContext函式來建立一個適用於OpenGL庫使用的繪圖表面surface以及繪圖上下文context,最後就可以呼叫eglMakeCurrent函式來將繪圖表面surface和繪圖上下文context設定為當前活動的繪圖表面和繪圖上下文,這就相當於是將前面請求SurfaceFlinger服務建立的一個Surface設定為當前活動的繪圖上下文了。
完成了上述操作之後,Android系統的開機動畫應用程式bootanim就可以繼續使用OpengGL庫的其它API來在當前活動的Surface上繪製UI了,不過,通過前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文的學習,我們知道,此時SurfaceFlinger服務為Android應用程式建立的Surface只有UI元資料緩衝區,而沒有UI資料緩衝區,即還沒有圖形緩衝區,換句來說,就是還沒有可以用來繪製UI的載體。那麼,這些用來繪製UI的圖形緩衝區是什麼時候建立的呢?
從前面Android應用程式與SurfaceFlinger服務的關係概述和學習計劃一文可以知道,每一個Surface都有一個對應的UI元資料緩衝區堆疊,這個UI元資料緩衝區堆疊是使用一個SharedBufferStack來描述的,如圖1所示。
圖1 SharedBufferStack的結構示意圖
從圖1就可以看出,每一個UI元資料緩衝區都可能對應有一個UI資料緩衝區,這個UI資料緩衝區又可以稱為圖形緩衝區,它使用一個GraphicBuffer物件來描述。注意,一個UI元資料緩衝區只有第一次被使用時,Android應用程式才會為它建立一個圖形緩衝區,因此,我們才說每一個UI元資料緩衝區都可能對應有一個UI資料緩衝區。例如,在圖1中,目前只使到了編號為1和2的UI元資料緩衝區,因此,只有它們才有對應的圖形緩衝區,而編號為3、4和5的UI元資料緩衝區沒有。
Android應用程式渲染一個Surface的過程大致如下所示:
1. 從UI元資料緩衝區堆疊中得到一個空閒的UI元資料緩衝區;
2. 請求SurfaceFlinger服務為這個空閒的UI元資料緩衝區分配一個圖形緩衝區;
3. 在圖形緩衝區上面繪製好UI之後,即填充好UI資料之後,就將前面得到的空閒UI元資料緩衝區新增到UI元資料緩衝區堆疊中的待渲染佇列中去;
4. 請求SurfaceFlinger服務渲染前面已經準備好了圖形緩衝區的Surface;
5. SurfaceFlinger服務從即將要渲染的Surface的UI元資料緩衝區堆疊的待渲染佇列中找到待渲染的UI元資料緩衝區;
6. SurfaceFlinger服務得到了待渲染的UI元資料緩衝區之後,接著再找到在前面第2步為它所分配的圖形緩衝區,最後就可以將這個圖形緩衝區渲染到裝置顯示屏上去。
這個過程的第1步、第3步和第5步涉到UI元資料緩衝區堆疊的一些出入棧操作,為了方便後面描述Android應用程式請求SurfaceFlinger服務渲染Surface的過程,我們首先介紹一下UI元資料緩衝區堆疊的一些出入棧操作。
在前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文中,我們分析了用來描述UI元資料緩衝區堆疊的SharedBufferServer和SharedBufferClient類的父類SharedBufferBase,它有一個成員函式waitForCondition,用來等待一個條件得到滿足,它定義在檔案frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:
class SharedBufferBase{ ......protected: ...... struct ConditionBase { SharedBufferStack& stack; inline ConditionBase(SharedBufferBase* sbc) : stack(*sbc->mSharedStack) { } virtual ~ConditionBase() { }; virtual bool operator()() const = 0; virtual const char* name() const = 0; }; status_t waitForCondition(const ConditionBase& condition); ......};
SharedBufferBase類的成員函式waitForCondition只有一個引數condition,它的型別為ConditionBase,用來描述一個需要等待滿足的條件。ConditionBase類是一個抽象類,我們需要以它來為父類,來實現一個自定義的條件,並且重寫操作符號()和成員函式name。接下來,我們分析SharedBufferBase類的成員函式waitForCondition的實現,接著再分析ConditionBase類的一個子類的實現。SharedBufferBase類的成員函式waitForCondition實現在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp檔案中,如下所示:
status_t SharedBufferBase::waitForCondition(const ConditionBase& condition){ const SharedBufferStack& stack( *mSharedStack ); SharedClient& client( *mSharedClient ); const nsecs_t TIMEOUT = s2ns(1); const int identity = mIdentity; Mutex::Autolock _l(client.lock); while ((condition()==false) && (stack.identity == identity) && (stack.status == NO_ERROR)) { status_t err = client.cv.waitRelative(client.lock, TIMEOUT); // handle errors and timeouts if (CC_UNLIKELY(err != NO_ERROR)) { if (err == TIMED_OUT) { if (condition()) { LOGE("waitForCondition(%s) timed out (identity=%d), " "but condition is true! We recovered but it " "shouldn't happen." , condition.name(), stack.identity); break; } else { LOGW("waitForCondition(%s) timed out " "(identity=%d, status=%d). " "CPU may be pegged. trying again.", condition.name(), stack.identity, stack.status); } } else { LOGE("waitForCondition(%s) error (%s) ", condition.name(), strerror(-err)); return err; } } } return (stack.identity != mIdentity) ? status_t(BAD_INDEX) : stack.status;}
SharedBufferBase類的成員變數mSharedStack指向了一個SharedBufferStack物件,即一個UI元資料緩衝區堆疊,另外一個成員變數mSharedClient指向了當前應用程式程序的一個SharedClient單例。SharedClient類有一個型別為Condition的成員變數cv,用來描述一個條件變數,同時,SharedClient類還有一個型別為Mutex的成員變數lock,用來描述一個互斥鎖。通過呼叫一個Condition物件的成員函式waitRelative,就可以在指定的時間內等待一個互斥鎖變為可用。
SharedBufferBase類的成員函式waitForCondition中的while迴圈的作用是迴圈等待一個UI元資料緩衝區堆疊滿足某一個條件,這個條件是通過引數condition來描述的。當呼叫引數condition所描述的一個CondtitionBase物件的過載操作符號()的返回值等於true的時候,就表示所要等待的條件得到滿足了,這時候函式就會停止執行中間的while迴圈語句。另一方面,當呼叫引數condition所描述的一個CondtitionBase物件的過載操作符號()的返回值等於flase的時候,就表示所要等待的條件還沒有得到滿足,這時候函式就會繼續執行中間的while迴圈,直到所要等待的條件得到滿足為止。等待的操作是通過呼叫下面這個語句來完成的:
status_t err = client.cv.waitRelative(client.lock, TIMEOUT);
即呼叫當前應用程式程序的SharedClient單例client的成員變數cv所描述的一個條件變數的成員函式waitRelative來完成,並且指定要等待的互斥鎖為當前應用程式程序的SharedClient單例client的成員變數lock所描述的一個互斥鎖,以及指定等待的時間為TIMEOUT,即1秒。如果在1秒內,當前應用程式程序的SharedClient單例client的成員變數lock所描述的一個互斥鎖還是不可用,那麼上述等待操作就會超時,然後導致重新執行外層的while迴圈,否則的話,等待操作就完成了。在SharedBufferClient類中,定義了一個ConditionBase子類DequeueCondition,用來描述一個UI元資料緩衝區堆疊是否有空閒的緩衝區可以出棧,它定義在檔案frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:
class SharedBufferClient : public SharedBufferBase{ ......private: ...... struct DequeueCondition : public ConditionBase { inline DequeueCondition(SharedBufferClient* sbc); inline bool operator()() const; inline const char* name() const { return "DequeueCondition"; } }; ......};
一個UI元資料緩衝區堆疊是否有空閒的緩衝區可以出棧是由DequeueCondition類的過載操作符號()來決定的,它實現在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp檔案中,如下所示:bool SharedBufferClient::DequeueCondition::operator()() const { return stack.available > 0;}
DequeueCondition類的成員變數stack是從父類ConditionBase繼承下來的,它指向了一個SharedBufferStack物件,即用來描述一個UI元資料緩衝區堆疊。從前面Android應用程式與SurfaceFlinger服務的關係概述和學習計劃一文可以知道,當一個SharedBufferStack物件的成員變數available的值大於0的時候,就說明它所描述的UI元資料緩衝區堆疊有空閒的緩衝區可以使用,因此,這時候DequeueCondition類的過載操作符號()的返回值就等於true,表示一個UI元資料緩衝區堆疊有空閒的緩衝區可以出棧。SharedBufferBase類還有一個成員函式updateCondition,用來操作一個UI元資料緩衝區堆疊,例如,執行一個UI元資料緩衝區的出入棧操作。這個成員函式定義和實現在檔案rameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:
class SharedBufferBase{ ......protected: ...... struct UpdateBase { SharedBufferStack& stack; inline UpdateBase(SharedBufferBase* sbb) : stack(*sbb->mSharedStack) { } }; template <typename T> status_t updateCondition(T update);};template <typename T>status_t SharedBufferBase::updateCondition(T update) { SharedClient& client( *mSharedClient ); Mutex::Autolock _l(client.lock); ssize_t result = update(); client.cv.broadcast(); return result;}
SharedBufferBase類的成員函式updateCondition是一個模板函式,它過呼叫引數T的過載操作符號()來實現一個具體的UI元資料緩衝區堆疊操作。這個引數T必須要從基類UpdateBase繼承下來,並且過載操作符號()。SharedBufferBase類的成員函式updateCondition執行完成一個UI元資料緩衝區堆疊操作之後,還會呼叫當前應用程序的SharedClient單例client的成員變數cv所描述的一個條件變數的成員函式broadcast,用來喚醒那些在當前應用程序的SharedClient單例client的成員變數lock所描述的一個互斥鎖上等待的其它執行緒,以便它們可以繼續執行自己的操作,這樣,SharedBufferBase類的成員函式updateCondition就可以和前面介紹的成員函式waitCondition對應起來。
接下來,我們就分別分析UpdateBase的三個子類QueueUpdate、DequeueUpdate和RetireUpdate。QueueUpdate和DequeueUpdate兩個子類是Android應用程式這一側使用的,前者用來向一個UI元資料緩衝區堆疊的待渲染佇列增加一個緩衝區,而後者用來從一個UI元資料緩衝區堆疊出棧一個空閒的緩衝區。RetireUpdate類是在SurfaceFlinger服務這一側使用的,用來從一個UI元資料緩衝區堆疊的待渲染隊列出棧一個緩衝區,以便可以將與它所對應的圖形緩衝區渲染到裝置顯示屏去。
QueueUpdate類定義在檔案frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:
class SharedBufferClient : public SharedBufferBase{ ......private: ...... struct QueueUpdate : public UpdateBase { inline QueueUpdate(SharedBufferBase* sbb); inline ssize_t operator()(); }; ......};
它的過載操作符號()實現在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp檔案中,如下所示:ssize_t SharedBufferClient::QueueUpdate::operator()() { android_atomic_inc(&stack.queued); return NO_ERROR;}
QueueUpdate類的成員變數stack是從父類UpdateBase繼承下來的,它指向了一個SharedBufferStack物件,用來描述當前要操作的UI元資料緩衝區堆疊。從前面的圖1可以知道,當我們將一個SharedBufferStack物件的成員變數queued的值增加1的時候,就表示這個SharedBufferStack物件所描述的UI元資料緩衝區堆疊的待渲染佇列的大小增加了1。不過,在執行這個操作之前,我們還需要將用來這個待渲染佇列頭queue_head往前移動一個位置。後面在分析Surface的渲染過程時,我們再詳細分析。DequeueUpdate類定義在檔案frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:
class SharedBufferClient : public SharedBufferBase{ ......private: ...... struct DequeueUpdate : public UpdateBase { inline DequeueUpdate(SharedBufferBase* sbb); inline ssize_t operator()(); }; ......};
它的過載操作符號()實現在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp檔案中,如下所示:ssize_t SharedBufferClient::DequeueUpdate::operator()() { if (android_atomic_dec(&stack.available) == 0) { LOGW("dequeue probably called from multiple threads!"); } return NO_ERROR;}
DequeueUpdate類的成員變數stack是從父類UpdateBase繼承下來的,它指向了一個SharedBufferStack物件,用來描述當前要操作的UI元資料緩衝區堆疊。從前面的圖1可以知道,當我們將一個SharedBufferStack物件的成員變數available的值減少1的時候,就表示這個SharedBufferStack物件所描述的UI元資料緩衝區堆疊的空閒緩衝區的大小就減少了1。不過,在執行這個操作之前,我們還需要將用來這個UI元資料緩衝區堆疊尾tail往前移動一個位置。後面在分析Surface的渲染過程時,我們再詳細分析。RetireUpdate類定義在檔案frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:
class SharedBufferServer : public SharedBufferBase, public LightRefBase<SharedBufferServer>{ ......private: ...... struct RetireUpdate : public UpdateBase { const int numBuffers; inline RetireUpdate(SharedBufferBase* sbb, int numBuffers); inline ssize_t operator()(); }; ......};
RetireUpdate類的成員變數numBuffers用來描述一個UI元資料緩衝區堆疊的大小,它的過載操作符號()實現在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp檔案中,如下所示:ssize_t SharedBufferServer::RetireUpdate::operator()() { int32_t head = stack.head; if (uint32_t(head) >= SharedBufferStack::NUM_BUFFER_MAX) return BAD_VALUE; // Decrement the number of queued buffers int32_t queued; do { queued = stack.queued; if (queued == 0) { return NOT_ENOUGH_DATA; } } while (android_atomic_cmpxchg(queued, queued-1, &stack.queued)); // lock the buffer before advancing head, which automatically unlocks // the buffer we preventively locked upon entering this function head = (head + 1) % numBuffers; const int8_t headBuf = stack.index[head]; stack.headBuf = headBuf; // head is only modified here, so we don't need to use cmpxchg android_atomic_write(head, &stack.head); // now that head has moved, we can increment the number of available buffers android_atomic_inc(&stack.available); return head;}
在前面Android應用程式與SurfaceFlinger服務的關係概述和學習計劃一文中提到,在圖1所描述的UI元資料緩衝區堆疊中,位於(head, queue_head]裡面的緩衝區組成了一個待渲染佇列,而SurfaceFlinger服務就是按照head到queue_head的順序來渲染這個佇列中的緩衝區的。理解了這一點之後,RetireUpdate類的過載操作符號()的實現就好理解了。首先,函式使用一個do...while迴圈來將queued的值減少1,即將待渲染佇列的大小減少1。當然,如果這個待渲染佇列的大小本來就等於0,那麼函式就什麼也不做就返回了。接著,函式將待渲染佇列的頭部head向前移一個位置。移動後的得到的位置所對應的緩衝區就是接下來要渲染的,因此,函式最後要將它返回給呼叫者。函式在將要渲染的緩衝區的位置返回給呼叫者之前,還會將當前正在操作的UI元資料緩衝區的空閒緩衝區的個數available增加1。
至此,DequeueCondition、QueueUpdate、DequeueUpdate和RetireUpdate這四個輔助類就介紹完成了,接下來,我們就可以繼續分析Android應用程式請求SurfaceFlinger服務渲染Surface的過程了。在分析的過程中,我們還會繼續看到這四個輔助類的使用方法。
在前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文的Step 16中,我們將在Android應用程式這一側所建立的一個Surface的父類ANativeWindow的OpenGL回撥函式dequeueBuffer和queueBuffer分別設定為Surface類的靜態成員函式dequeueBuffer和queueBuffer。OpenGL在繪圖之前,就首先會呼叫Surface類的靜態成員函式dequeueBuffer來獲得一個空閒的UI元資料緩衝區,接著請求SurfaceFlinger服務為這個UI元資料緩衝區分配一個圖形緩衝區。有了圖形緩衝區之後,OpengGL庫就可以往裡面填入UI資料。在往圖形緩衝區填入UI資料的同時,OpenGL庫也會往前面獲得的UI元資料緩衝區填入當前正在操作的Surface的裁剪區域、紋理座標和旋轉方向等資訊。再接下來,OpenGL庫就會呼叫Surface類的靜態成員函式queueBuffer來將前面已經填好了資料的UI元資料緩衝區新增到當前正在操作的Surface的UI元數緩衝區堆疊的待渲染佇列中。最後,Android應用程式就會請求SurfaceFlinger服務將當前正在操作的Surface的UI資料渲染到裝置顯示屏去。
接下來,我們就首先分析Surface類的靜態成員函式dequeueBuffer的實現,接著再分析Surface類的靜態成員函式queueBuffer的實現,最後分析SurfaceFlinger服務渲染Surface的圖形緩衝區的過程。
Surface類的靜態成員函式dequeueBuffer獲得空閒UI元資料緩衝區,以及請求SurfaceFlinger服務為這個空閒UI元資料緩衝區分配圖形緩衝區的過程如圖2所示:
圖2 分配空閒UI元資料緩衝區及其圖形緩衝區的過程
這個過程一共分為12個步驟,接下來我們就詳細分析每一個步驟。
Step 1. Surface.dequeueBuffer
int Surface::dequeueBuffer(ANativeWindow* window, android_native_buffer_t** buffer) { Surface* self = getSelf(window); return self->dequeueBuffer(buffer);}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/Surface.cpp中。 引數window雖然是一個ANativeWindow指標,但是它實際上指向的是一個Surface物件,因此,函式首先呼叫另外一個靜態成員函式getSelf來將它轉換為一個Surface物件self,接著再呼叫這個Surface物件self的成員函式dequeueBuffer來分配一個空閒UI元資料緩衝區和一個圖形緩衝區,其中,分配的圖形緩衝區就儲存在輸出引數buffer中。Surface類的非靜態成員函式dequeueBuffer的實現如下所示:
int Surface::dequeueBuffer(android_native_buffer_t** buffer){ status_t err = validate(); if (err != NO_ERROR) return err; ...... ssize_t bufIdx = mSharedBufferClient->dequeue(); ...... if (bufIdx < 0) { ...... return bufIdx; } // grow the buffer array if needed const size_t size = mBuffers.size(); const size_t needed = bufIdx+1; if (size < needed) { mBuffers.insertAt(size, needed-size); } uint32_t w, h, format, usage; if (needNewBuffer(bufIdx, &w, &h, &format, &usage)) { err = getBufferLocked(bufIdx, w, h, format, usage); ...... if (err == NO_ERROR) { // reset the width/height with the what we get from the buffer const sp<GraphicBuffer>& backBuffer(mBuffers[bufIdx]); mWidth = uint32_t(backBuffer->width); mHeight = uint32_t(backBuffer->height); } } // if we still don't have a buffer here, we probably ran out of memory const sp<GraphicBuffer>& backBuffer(mBuffers[bufIdx]); if (!err && backBuffer==0) { err = NO_MEMORY; } if (err == NO_ERROR) { mDirtyRegion.set(backBuffer->width, backBuffer->height); *buffer = backBuffer.get(); } else { mSharedBufferClient->undoDequeue(bufIdx); } return err;}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/Surface.cpp中。
函式首先呼叫Surface類的成員變數mSharedBufferClient所指向的一個SharedBufferClient物件的成員函式dequeue來從UI元資料緩衝區堆疊中獲得一個空閒的緩衝區。獲得的空閒緩衝區使用一個編號來描述,這個編號儲存在變數bufIdx中。後面我們再分析SharedBufferClient類的成員函式dequeue的實現。
獲最一個空閒UI元資料緩衝區之後,函式接下來判斷該緩衝區的編號是否大於Surface類的成員變數mBuffers所描述的一個GraphicBuffer向量的大小。如果大於,那麼就需要擴充這個向量的大小,以便後面可以用來儲存與該緩衝區對應的一個GraphicBuffer,即一個圖形緩衝區。
函式再接下來呼叫Surface類的另外一個成員函式needNewBuffer來判斷之前是否已經為編號為bufIdx的UI元資料緩衝區分配過圖形緩衝區了,它的實現如下所示:
bool Surface::needNewBuffer(int bufIdx, uint32_t *pWidth, uint32_t *pHeight, uint32_t *pFormat, uint32_t *pUsage) const{ Mutex::Autolock _l(mSurfaceLock); // Always call needNewBuffer(), since it clears the needed buffers flags bool needNewBuffer = mSharedBufferClient->needNewBuffer(bufIdx); bool validBuffer = mBufferInfo.validateBuffer(mBuffers[bufIdx]); bool newNeewBuffer = needNewBuffer || !validBuffer; if (newNeewBuffer) { mBufferInfo.get(pWidth, pHeight, pFormat, pUsage); } return newNeewBuffer;}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/Surface.cpp中。由於UI元資料緩衝區堆疊中的緩衝區是迴圈使用的。當一個UI元資料緩衝區第一次被使用的時候,應用程式就會請求SurfaceFlinger服務為它分配一個圖形緩衝區。這個圖形緩衝區使用完成之後,就會被應用程式快取起來,以便後面可以繼續使用。但是這個圖形緩衝區可能會被得無效,例如,與它對應的Surface的大小和用途等資訊發生改變之後,該圖形緩衝區就會變得無效了,因為它在分配的時候,是按照既定的大小和用途來分配的。
這個函式首先呼叫Surface類的成員變數mSharedBufferClient所指向的一個SharedBufferClient物件的成員函式needBuffer來驗證編號為bufIdx的UI元資料緩衝區所對應的圖形緩衝區資訊是否發生了變化。如果發生了變化,那麼變數needNewBuffer的值就會等於true,表示要重新為編號為bufIdx的UI元資料緩衝區分配新的圖形緩衝區。
SharedBufferClient類的成員函式needBuffer的實現如下所示:
bool SharedBufferClient::needNewBuffer(int buf) const{ SharedBufferStack& stack( *mSharedStack ); const uint32_t mask = 1<<(31-buf); return (android_atomic_and(~mask, &stack.reallocMask) & mask) != 0;}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp中。SharedBufferClient類的成員變數mSharedStack的型別為SharedBufferStack,它是從父類SharedBufferBase繼承下來的,用來描述一個UI元資料緩衝區堆疊。SharedBufferStack類的成員變數reallocMask是一個掩碼,如果它的某一位的值等於1,那麼這一位所描述的一個UI元資料緩衝區所對應的圖形緩衝區就是無效的。這一般是由SurfaceFlinger服務來裝置的。當SurfaceFlinger服務發現一個Surface的元資訊發生變化時,就會通過一個SharedBufferServer物件來設定這個Surface的UI元資料緩衝區堆疊的成員變數reallocMask的相應位等於1,以便應用程式在使用到該位所描述的UI元資料緩衝區時,請求分配一個新的圖形緩衝區。例如,假設SharedBufferStack類的成員變數reallocMask的值等於01000000 00000000 00000000 00000000,那麼就表示編號為1的UI元資料緩衝區對應的圖形緩衝區需要重新分配。
回到Surface類的成員函式needNewBuffer中,接下來該函式繼續驗證編號為bufIdx對應的UI元資料緩衝區在成員變數mBuffers中所對應的圖形緩衝區是否還有效,即圖形緩衝區mBuffers[bufIdx]是否還有效,這是通過呼叫Surface類的成員變數mBufferInfo所描述的一個BufferInfo物件的成員函式validateBuffer來驗證的。如果沒有效,那麼變數validBuffer的值就會等於false,表示要重新為編號為bufIdx的UI元資料緩衝區分配新的圖形緩衝區。
BufferInfo類的成員函式validateBuffer的實現如下所示:
bool Surface::BufferInfo::validateBuffer(const sp<GraphicBuffer>& buffer) const { // make sure we AT LEAST have the usage flags we want if (mDirty || buffer==0 || ((buffer->usage & mUsage) != mUsage)) { mDirty = 0; return false; } return true;}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/Surface.cpp中。BufferInfo類的成員變數mDirty用來描述一個Surface的元資料是否發了變化,例如,它的大小、畫素格式等是發生了變化。如果發生了變化,那麼它的值就會不等於0。
引數buffer是一個型別為GraphicBuffer的強指標,如果它的值等於null,那麼就說明它所描述的圖形緩衝是無效的。
如果引數buffer所指向的圖形緩衝區是有效的,但是它的用途發生了變化,即它的用途與它所對應的Surface的用途已經不一致了。
上述三種情況都說明需要為編號為bufIdx的UI元資料緩衝分配新的圖形緩衝區,因此,這個函式的返回值就會等於false。
回到Surface類的成員函式needNewBuffer中,接下來該函式通過變數needNewBuffer和變數validBuffer的值就可以知道是否需要為編號為bufIdx的UI元資料緩衝分配新的圖形緩衝區了。假設應用程式是第一次使用編號為bufIdx的UI元資料緩衝,那麼變數validBuffer的值就一定會等於false,因此,Surface類的成員函式needNewBuffer的返回值就會等於true,表示要為編號為bufIdx的UI元資料緩衝分配新的圖形緩衝區。該函式在返回之前,還會通過Surface類的成員變數mBufferInfo所描述的一個BufferInfo物件來得到當前正在繪製的Surface的寬度、高度、畫素格式以及用途,分別儲存在輸出引數pWidth、pHeight、pFormat和pUsage,以便應用程式接下來可以使用這些資訊來請求SurfaceFlinger服務分配一個新的圖形緩衝區。
回到Surface類的非靜態成員函式dequeueBuffer中,該函式接下來就會呼叫Surface類的另外一個成員函式getBufferLocked來請求SurfaceFlinger服務為編號為bufIdx的UI元資料緩衝區分配一個圖形緩衝區。分配完成之後,這個圖形緩衝區就會儲存在mBuffers[bufIdx]中。後面我們就詳細分析Surface類的成員函式getBufferLocked的實現。
Surface類的非靜態成員函式dequeueBuffer獲得了編號為bufIdx的圖形緩衝區之後,接下來就會得到這個圖形緩衝區的寬度和高度,並且儲存Surface類的成員變數mWidth和mHeight中,以便可以表示當前下在繪製的Surface的寬度和高度。同時,這個圖形緩衝區的寬度和高度還會被更新到用來描述當前正在繪製的Surface的裁剪區域去,因為SurfaceFlinger服務在渲染該Surface時,需要用到這個資訊。當前正在繪製的Surface的裁剪區域是由Surface類的成員變數mDirtyRegion來描述的,只要呼叫它的成員函式set,就可以重新設定它的寬度和高度。
最後,Surface類的非靜態成員函式dequeueBuffer就將得到的圖形緩衝區的地址儲存輸出引數buffer中,以便OpenGL庫可以在上面填入UI資料。另一方面,如果分配圖形緩衝區失敗,那麼Surface類的非靜態成員函式dequeueBuffer會將前面得到的一個UI元資料緩衝區返回給成員變數mSharedBufferClient所描述的一個UI元資料緩衝區堆疊去,這是通過呼叫成員變數mSharedBufferClient的成員函式undoDequeue來實現的。
接下來,我們就繼續分析SharedBufferClient類的成員函式dequeue的實現,以便了解它是如何從UI元資料緩衝區堆疊中獲得一個空閒的緩衝區的。
Step 2. SharedBufferClient.dequeue
ssize_t SharedBufferClient::dequeue(){ SharedBufferStack& stack( *mSharedStack ); if (stack.head == tail && stack.available == mNumBuffers) { LOGW("dequeue: tail=%d, head=%d, avail=%d, queued=%d", tail, stack.head, stack.available, stack.queued); } RWLock::AutoRLock _rd(mLock); const nsecs_t dequeueTime = systemTime(SYSTEM_TIME_THREAD); //LOGD("[%d] about to dequeue a buffer", // mSharedStack->identity); DequeueCondition condition(this); status_t err = waitForCondition(condition); if (err != NO_ERROR) return ssize_t(err); DequeueUpdate update(this); updateCondition( update ); int dequeued = stack.index[tail]; tail = ((tail+1 >= mNumBuffers) ? 0 : tail+1); LOGD_IF(DEBUG_ATOMICS, "dequeued=%d, tail++=%d, %s", dequeued, tail, dump("").string()); mDequeueTime[dequeued] = dequeueTime; return dequeued;}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp中。
從前面Android應用程式與SurfaceFlinger服務的關係概述和學習計劃一文可以知道,SharedBufferClient類的成員變數tail指向了一個UI元資料緩衝區堆疊的空閒緩衝區列表的尾部。當這個UI元資料緩衝區堆疊的可用空閒緩衝區的數量available的值大於0的時候,應用程式就可以從它的空閒緩衝區列表的尾部分配一個繪衝區出來使用。
函式首先建立了一個DequeueCondition物件condition,然後再呼叫SharedBufferClient從SharedBufferBase類繼承下來的成員函式waitForCondition來判斷當前正在使用的UI元資料緩衝區堆疊是否有空閒的緩衝區可以分配。如果沒有,那麼當前執行緒就會一直等待,直到可以得到一個空閒緩衝區為止。
函式接著建立了一個DequeueUpdate物件update,然後再呼叫SharedBufferClient從SharedBufferBase類繼承下來的成員函式updateCondition來減少當前正在使用的UI元資料緩衝區堆疊的空閒緩衝區的數量,因為接下來要將空閒緩衝區列表尾部的緩衝區分配出來使用。
函式最後就通過SharedBufferClient類的成員變數tail來獲得了一個編號為dequeued的空閒UI元資料緩衝區,並且將這個編號返回給呼叫者。不過,在返回之前,函式還會將SharedBufferClient類的成員變數tail向前移一個位置,以便它可以指向下一個可以用來分配的空閒UI元資料緩衝區。由於UI元資料緩衝區堆疊是迴圈使用的,因此,當SharedBufferClient類的成員變數tail向前移一個位置,即加1之後,它的值大於等於UI元資料緩衝區堆疊的大小mNumBuffers時,就需要繞回到堆疊的開頭去。
這一步執行完成之後,就返回到Step 1中,即Surface類的成員函式dequeueBuffer中,這時候應用程式就為當前正在繪製的Surface獲得了一個空閒UI元資料緩衝區,接下來就會繼續呼叫Surface類的成員函式getBufferLocked來為該空閒UI元資料緩衝區分配圖形緩衝區。
Step 3. Surface.getBufferLocked
status_t Surface::getBufferLocked(int index, uint32_t w, uint32_t h, uint32_t format, uint32_t usage){ sp<ISurface> s(mSurface); if (s == 0) return NO_INIT; status_t err = NO_MEMORY; // free the current buffer sp<GraphicBuffer>& currentBuffer(mBuffers.editItemAt(index)); if (currentBuffer != 0) { getBufferMapper().unregisterBuffer(currentBuffer->handle); currentBuffer.clear(); } sp<GraphicBuffer> buffer = s->requestBuffer(index, w, h, format, usage); ...... if (buffer != 0) { // this should never happen by construction ...... err = mSharedBufferClient->getStatus(); ...... if (!err && buffer->handle != NULL) { err = getBufferMapper().registerBuffer(buffer->handle); ...... if (err == NO_ERROR) { currentBuffer = buffer; currentBuffer->setIndex(index); } } else { err = err<0 ? err : status_t(NO_MEMORY); } } return err;}
這個函式定義在檔案frameworks/base/libs/surfaceflinger_client/Surface.cpp中。從前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文可以知道, Surface類的成員變數mSurface指向了一個型別為BpSurface的Binder代理物件,這個Binder代理物件引用了執行在SurfaceFlinger服務一側的一個型別為SurfaceLayer的Binder本地物件。函式首先將這個成員變數儲存在變數s中,後面會通過它來向SurfaceFlinger服務為編號為index的空閒UI元資料緩衝區分配一個圖形緩衝區。
在請求SurfaceFlinger服務為編號為index的空閒UI元資料緩衝區分配圖形緩衝區之前,函式還會檢查在Surface類的成員變數mBuffers中是否存在一個與編號為index的空閒UI元資料緩衝區對應的圖形緩衝區。如果存在的話,就需要將這個圖形緩衝區從應用程式程序的地址空間登出掉,因為這個圖形緩衝區已經變成無效了。Surface類的成員函式getBufferMapper的返回值是一個GraphicBufferMapper物件,通過呼叫這個GraphicBufferMapper物件的成員函式unregisterBuffer就可以登出一個指定的圖形緩衝區。GraphicBufferMapper類的成員函式unregisterBuffer最終也是通過HAL層中的Gralloc模組提供的介面gralloc_unregister_buffer來登出一個指定的圖形緩衝區,這一點可以參考前面Android幀緩衝區(Frame Buffer)硬體抽象層(HAL)模組Gralloc的實現原理分析一文。
函式接下來就請求變數s所指向的一個BpSurface物件的成員函式requestBuffer請求SurfaceFlinger服務為編號為index的空閒UI元資料緩衝區分配一個圖形緩衝區,這個緩衝區儲存在變數buffer中。應用程式得到圖形緩衝區buffer之後,還需要將它註冊到本程序的地址空間之後,才能使用,這是通過呼叫GraphicBufferMapper類的成員函式registerBuffer來實現的,後面我們再詳細分析這個註冊的過程。
接下來,函式就將圖形緩衝區buffer儲存在一個GraphicBuffer引用currentBuffer中。由於currentBuffer引用的是Surface類的成員變數mBuffers的第index個圖形緩衝區,因此,前面相當於將圖形緩衝區buffer儲存在Surface類的成員變數mBuffers的第index個位置中,以便以後可以重複利用。最後,函式還呼叫GraphicBuffer引用currentBuffer的成員函式setIndex來將前面分配到的圖形緩衝區的編號設定為index,這樣就可以將它與編號為index的UI元資料緩衝區關聯起來。
由於變數s引用的是一個型別為SurfaceLayer的Binder本地物件,因此,接下來我們就繼續分析SurfaceLayer類的成員函式requestBuffer的實現,以便可以瞭解SurfaceFlinger服務是如何為應用程式的一個Surface分配一個圖形緩衝區的。
Step 4. SurfaceLayer.requestBuffer
sp<GraphicBuffer> Layer::SurfaceLayer::requestBuffer(int index, uint32_t w, uint32_t h, uint32_t format, uint32_t usage){ sp<GraphicBuffer> buffer; sp<Layer> owner(getOwner()); if (owner != 0) { /* * requestBuffer() cannot be called from the main thread * as it could cause a dead-lock, since it may have to wait * on conditions updated my the main thread. */ buffer = owner->requestBuffer(index, w, h, format, usage); } return buffer;}
這個函式定義在檔案frameworks/base/services/surfaceflinger/Layer.cpp中。函式首先呼叫SurfaceLayer類的成員函式getOwner來獲得當前正在處理的一個SurfaceLayer物件的宿主Layer物件,接著再呼叫這個Layer物件的成員函式requestBuffer來執行分配圖形緩衝區的操作。從前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文可以知道,SurfaceFlinger服務在為Android應用程式建立一個Surface的時候,會相應地建立一個Layer物件和一個SurfaceLayer物件來描述這個Surface。
接下來,我們就繼續分析Layer類的成員函式requestBuffer的實現。
Step 5. Layer.requestBuffer
這個函式定義在frameworks/base/services/surfaceflinger/Layer.cpp檔案中,我們分段來閱讀:
sp<GraphicBuffer> Layer::requestBuffer(int index, uint32_t reqWidth, uint32_t reqHeight, uint32_t reqFormat, uint32_t usage){ sp<GraphicBuffer> buffer; if (int32_t(reqWidth | reqHeight | reqFormat) < 0) return buffer; if ((!reqWidth && reqHeight) || (reqWidth && !reqHeight)) return buffer; // this ensures our client doesn't go away while we're accessing // the shared area. ClientRef::Access sharedClient(mUserClientRef); SharedBufferServer* lcblk(sharedClient.get()); if (!lcblk) { // oops, the client is already gone return buffer; }
我們首先明確一下各個函式引數的含義。引數index用來描述一個UI元資料緩衝區的編號,引數reqWidth、reqHeight、reqFormat和usage分別表示要分配的圖形緩衝區的寬度、高度、畫素格式和用途。函式首先檢查各個引數的合法性,即引數reqWidth、reqHeight和reqFormat不能為負數,並且引數reqWidth和reqHeight不能同時等於0。從前面Android應用程式請求SurfaceFlinger服務建立Surface的過程分析一文可以知道,Layer類的成員變數mUserClientRef指向了一個ClientRef物件,通過這個ClientRef物件可以獲得一個SharedBufferServer物件lcblk。得到的SharedBufferServer物件lcblk就是用來描述正在請求SurfaceFlinger服務分配圖形緩衝區的Surface的UI元資料緩衝區堆疊的,接下來我們就會看到它的用法。
我們繼續往下看:
/* * This is called from the client's Surface::dequeue(). This can happen * at any time, especially while we're in the middle of using the * buffer 'index' as our front buffer. */ uint32_t w, h, f, bypass; { // scope for the lock Mutex::Autolock _l(mLock); bypass = mBypassState; // zero means default mFixedSize = reqWidth && reqHeight; if (!reqFormat) reqFormat = mFormat; if (!reqWidth) reqWidth = mWidth; if (!reqHeight) reqHeight = mHeight; w = reqWidth; h = reqHeight; f = reqFormat; if ((reqWidth != mReqWidth) || (reqHeight != mReqHeight) || (reqFormat != mReqFormat)) { mReqWidth = reqWidth; mReqHeight = reqHeight; mReqFormat = reqFormat; mNeedsScaling = mWidth != mReqWidth || mHeight != mReqHeight; lcblk->reallocateAllExcept(index); } }
這一段程式碼主要就是用來判斷要分配圖形緩衝區的Surface的寬度、高度和畫素格式是否發生變化。當請求分配的圖形緩衝區的寬度、高度和畫素格式與這個圖形緩衝區所描述的Surface原來的寬度、高度和畫素格式不一樣時,SurfaceFlinger服務就會認為這個Surface的元資訊發生了變化,這時候函式就會將請求分配的圖形緩衝區的寬度、高度和畫素格式設定為當前Surface的寬度、高度和畫素格式,並且呼叫前面所獲得的一個SharedBufferServer物件lcblk的成員函式reallocateAllExcept來將之前已經分配給當前Surface的圖形緩衝區設定為無效,因為之前已經分配給當前Surface的圖形緩衝區已經不適合於當前Surface使用了。在這一段程式碼中,還有一個需要注意的地方,即Layer類的成員變數mBypassState。這個成員變量表示當前正在處理的一個Layer物件所描述的一個Surface在SurfaceFlinger服務渲染UI時,是否需要參與合成。當它的值等於true的時候,就表示不需要參與合成,否則就要參考合成。一般當一個Layer物件所描述的Surface的圖形緩衝區是直接在硬體幀緩衝區fb上分配時,對應的Surface就不需要參與SurfaceFlinger服務的合成操作。
我們繼續向下看:
// here we have to reallocate a new buffer because the buffer could be // used as the front buffer, or by a client in our process // (eg: status bar), and we can't release the handle under its feet. uint32_t effectiveUsage = getEffectiveUsage(usage); status_t err = NO_MEMORY;#ifdef USE_COMPOSITION_BYPASS if (!mSecure && bypass && (effectiveUsage & GRALLOC_USAGE_HW_RENDER)) { // always allocate a buffer matching the screen size. the size // may be different from (w,h) if the buffer is rotated. const DisplayHardware&
相關推薦
Android應用程式請求SurfaceFlinger服務渲染Surface的過程分析
在前面一篇文章中,我們分析了Android應用程式請求SurfaceFlinger服務建立Surface的過程。有了Surface之後,Android應用程式就可以在上面繪製自己的UI了,接著再請求SurfaceFlinger服務將這個已經繪製好了UI的Surf
Android應用程式與SurfaceFlinger服務的關係概述
SurfaceFlinger服務負責繪製Android應用程式的UI,它的實現相當複雜,要從正面分析它的實現不是一件容易的事。既然不能從正面分析,我們就想辦法從側面分析。說到底,無論SurfaceFlinger服務有多複雜,它都是為Android應用程式服務的,因此,我們就從Android應
Android應用程式與SurfaceFlinger服務的關係概述和學習計劃
SurfaceFlinger服務負責繪製Android應用程式的UI,它的實現相當複雜,要從正面分析它的實現不是一件容易的事。既然不能從正面分析,我們就想辦法從側面分析。說到底,無論SurfaceFlinger服務有多複雜,它都是為Android應用程式服務的
轉自老羅 Android應用程式資源的編譯和打包過程分析
原文地址 http://blog.csdn.net/luoshengyang/article/details/8744683 轉載自老羅,轉載請說明
我們知道,在一個APK檔案中,除了有程式碼檔案之外,還有很多資原始檔。這些資原始檔是通過An
Android應用程式註冊廣播接收器 registerReceiver 的過程分析
前面我們介紹了Android系統的廣播機制,從本質來說,它是一種訊息訂閱/釋出機制,因此,使用這種訊息驅動模型的第一步便是訂閱訊息;而對Android應用程式來說,訂閱訊息其實就是註冊廣播接收器,本文將探討Android應用程式是如何註冊廣播接收器以及把廣播接收
Android應用程式資源的編譯和打包過程分析
我們知道,在一個APK檔案中,除了有程式碼檔案之外,還有很多資原始檔。這些資原始檔是通過Android資源打包工具aapt(Android Asset
Package Tool)打包到APK檔案裡面的。在打包之前,大部分文字格式的XML資原始檔還會被編譯
android應用程式fps meter[幀數顯示]的分析 —— SurfaceFlinger被注入統計程式碼 (1)
fps meter是常用的檢測幀率的軟體,該軟體需要root許可權才能工作,一直比較好奇它一個apk是如何知道系統當前的幀率情況的,就針對此apk分析了一下其工作原理。
Apk組成
首先看一下apk的組成,apk檔案就是一個壓縮包,可以解壓縮軟體如winrar解壓檢視,也可
Android應用程式管理服務啟動過程淺析(PackageManagerService)
我們知道安卓應用程式的安裝最終都是通過應用程式管理服務PackageManagerService來管理安裝的,系統在啟動時就會啟動該服務,在之前的 Android應用程式安裝過程淺析文章中分析了應用程式的安裝的過程,當時只是使用該服務,並沒有講到該
瀏覽器判別下載安裝/開啟Android應用程式
摘要: 通過手機瀏覽器直接開啟Android應用程式。 如果本地已經安裝了指定Android應用,就直接開啟它; 如果沒有安裝,則直接下載該應用的安裝檔案(也可以跳轉到下載頁面)。
之前寫過一篇blog,介紹如何通過點選手機瀏覽器中的連結,直接開啟本地Android App。
實現方式
Android應用程式框架講解
在我們的android的程式中會有很多的檔案,那麼這些檔案到底是有什麼作用呢?
我們編譯android專案,可以使用eclipse和AS,首先介紹一下eclipse中的框架
1、src檔案:存放的是應用程式使用到的java檔案
2、gen檔案:系統自動生成的目錄。不需要程式設計師進行
android應用程式的介面程式設計
要點
android的介面與view元件
view元件和viewgroup元件
android控制程式的三種方式
通過繼承view開發自定義view
android常見的佈局管理器
文字框元件:textview和edittext
按鈕元件:button
特殊按鈕元件:radiobut
Android應用程式進行系統簽名
有時寫一個程式需要呼叫系統的庫,如果許可權不夠,是用不了庫裡面一些方法的 。這時就需要將apk進行系統簽名。
簡單常用的方法:
1,在apk的AndroidMani
Android應用程式簽名打包(AS)
使用Android studio對Android應用簽名步驟:
第一步:
第二步:
第三步:
第四步:
數字證書建立完成後,點選OK----->點選Next------>Finish。
注意:生成後的數字證書千萬不能丟失,還有密碼也不能忘記了
怎麼獲取Android應用程式的上下文
前面我做了一個類似於騰訊QQ一樣的聊天應用,在這個應用裡面,有很多activity,而這些activity之間經常要進行互相啟動、往復跳轉、還有就是通過Notification啟動。當activity多了之後,如果設定他的模式為單例模式,或者不設定模式。在反覆啟動後會出現數據不同步、fc等各種未知的
Android 應用程式崩潰日誌捕捉
程式崩潰是應用迭代中不可避免的問題,即使有著5年或者10年經驗的程式猿也無法完全保證自己的程式碼沒有任何的bug導致崩潰,現在有一些第三方平臺可以幫助我們蒐集應用程式的崩潰,比如友盟,詳情如下圖
雖然能夠看到崩潰的日誌以及機型等,但還是不是很方便,如果需要精確定位的話需要使用者提供崩潰的時間點、機型
轉老羅 Android應用程式資源的查詢過程分析
原文地址 http://blog.csdn.net/luoshengyang/article/details/8806798 轉載請說明
我們知道,在Android系統中,每一個應用程式一般都會配置很多資源,用來適配不同密
轉自 老羅 Android應用程式資源管理器(Asset Manager)的建立過程分析
原文地址在 http://blog.csdn.net/luoshengyang/article/details/8791064 原創老羅,轉載請說明
在前面一篇文章中,我們分析了Android應用程式資源的編譯和打包過程,最終得到的應用程式資源就與
模組化惡意Android應用程式偽裝成語音工具,通過調查收集PII資料
“這些惡意應用程式和惡意軟體的觀察變體自10月份開始逐一部署,其演變包括規避技術及其感染行為分為幾個階段,”趨勢科技在他們的分析中表示。
在嘗試通過虛假調查收集使用者的個人身份資訊(PII)並提供禮品卡作為獎勵以填寫它們時,已經觀察到被稱為AndroidOS_FraudBot.OPS的惡意軟體。此外,惡意應
VS開發應用程式控制windows服務安裝、解除安裝、啟停 許可權問題
一、VS可能出現的錯誤提示:
ServiceController無法開啟計算機**上的**服務。
二、原因
編寫的應用程式許可權不夠,無法控制系統windows服務。
三、解決:
android應用程式的混淆打包(轉)
target=android-8
proguard.config=proguard.cfg
Eclipse會通過此配置在工程目錄生成proguard.cfg檔案
2 . 生成keystore (如已有可直接利用)
按照下面的命令列 在D:\Program