日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

优化安卓应用内存的神奇方法以及背后的原理,一般人我不告诉他

發布時間:2024/4/14 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 优化安卓应用内存的神奇方法以及背后的原理,一般人我不告诉他 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

安卓應用一般都害怕自己被殺。內存占用高是被殺的重要原因之中的一個。所以大家都想盡各種招數應對,但效果都一般。


但有一招:


WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);


差點兒沒有人提及。這段時間tos的實戰,在通知欄和桌面都有嘗試,發現效果還不錯,但要掌握好這個函數的使用方法。須要細致理解背后的原理,畢竟這個調用相當于在局部時間內讓應用的一系列GPU緩存被清理。相當于硬件加速失效。


文章分三大部分,第一大部分用簡單的方式描寫敘述安卓繪制系統框架。第二大部分說明繪制過程中GPU產生緩存的原因。

第三大部分說明startTrimMemory可以清理的GPU緩存以及一些誤區。


(一)簡單介紹安卓繪制系統框架


安卓繪制系統比較復雜。網上非常多文章講得非常細,但不easy抓住核心要點,事實上我們僅僅要抓到12個關鍵的相應關系和概念,就能夠掌握清晰基本框架,對debug和性能優化都有價值。


1)一個activity相應一個window。當然。沒有activity耶能夠有window,比方通知欄,window大家都知道。有各種屬性。比方層次。位置等等


2)一個window相應一個surface。surface事實上就是一個對graphic buffer進行管理的對象


3)surface的創建是請求surfaceflinger完畢的。事實上相應的是一塊graphicbuffer,gpu和cp都能訪問到


4)window上能夠有非常多的view,能夠是一棵view的tree。對于activity來說,頂部的view就是DecorView,activity上全部的view都相應同一個surface


5)相比activity里的view。surfaceview(glsurfaceview)會有自己獨立的surface。有自己獨立的處理線程。與activity的surface不是同一個


6)activity的view的繪制(打開硬件加速的情況下),事實上就是在一個surface上的繪制,終于通過hwui這個so完畢,這是在應用端進行的。不是在surfaceflinger這一側。hwui是硬件繪制的關鍵庫。最關鍵的是hwui里有一系列GPU緩存,避免在繪制的時候又一次再上傳圖片紋理等GPU繪制相關的數據


7)各個surface另一個合成的過程。這是在surfaceflinger中完畢的


8)每一次activity的view的繪制和surface的合成,都是通過vsync信號觸發的,vsync每16.6毫秒觸發一次


9)surfaceview(glsurfaceview)的繪制能夠不通過vsync來同步,自己的線程獨立控制節奏,可是繪制之后的surface的合成。由surfaceflinger統一進行


10)應用側的surface。不管是view還是surface view相應的,繪制完成之后。通過eglwapbuffer的方法,將graphicbuffer queue回給surfaceflinger(surfaceflinger合成完成之后,會上屏,之后會釋放出來。讓應用側能夠又一次使用這些buffer)


11)view做動畫的時候,假設子view沒有刷新,子view的ondraw能夠不被觸發,這是動畫過程性能高效的一個關鍵點。以view的hardware layer緩存總體做動畫就可以,在view做動畫的時候假設觸發了子view的又一次繪制,繪制效率就會減少


12) 眼下主流安卓手機。GPU和CPU會共享內存。GPU占用內存多了。留給CPU的就會對應降低,每一個進程GPU占用的內存,也會被統計到各個進程的總內存其中,會影響到low memory killer的策略




另外一張圖大致也能夠反映出上面的12個關鍵描寫敘述的部分體系結構




(二)canvas 繪制bitmap 導致的GPU緩存(俗稱GPU內存泄漏)


大家肯定感興趣,一個bitmap。是怎樣繪制到屏幕上的view的繪制代碼里會觸發canvas.drawBitmap,硬件加速打開的話。canvas事實上就是GLES20RecordingCanvas,GLES20RecordingCanvas的父類是GLES20Canvas。

我們看看GLES20Canvas的GLES20Canvas::DrawBitmap的代碼:


@Override

public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {

throwIfCannotDraw(bitmap);

// Shaders are ignored when drawing bitmaps

int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;

try {

? ? final int nativePaint = paint == null ? 0 : paint.mNativePaint;

? ? nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);

} finally {

? ? if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);

}

}


GLES20Canvas相應的native代碼是android_view_GLES20Canvas.cpp,android_view_GLES20Canvas_drawBitmap 就是nDrawBitmap的詳細實現。


static void android_view_GLES20Canvas_drawBitmap(JNIEnv* env, jobject clazz,

OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, float left,

float top, SkPaint* paint) {

// This object allows the renderer to allocate a global JNI ref to the buffer object.

JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);

renderer->drawBitmap(bitmap, left, top, paint);

}


這里已經非常明白。canvas的drawbitmap事實上調用的就是hwui里的OpenGLRenderer的drawBitmap,我們看看里面做了什么事情。


status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {

const float right = left + bitmap->width();

const float bottom = top + bitmap->height();

if (quickReject(left, top, right, bottom)) {

return DrawGlInfo::kStatusDone;

}

mCaches.activeTexture(0);

Texture* texture = getTexture(bitmap);

if (!texture) return DrawGlInfo::kStatusDone;

const AutoTexture autoCleanup(texture);

if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {

drawAlphaBitmap(texture, left, top, paint);

} else {

drawTextureRect(left, top, right, bottom, texture, paint);

}


hwui有TextureCache對象,將繪制的bitmap緩存在gpu紋理里,這樣下次假設有反復的。就能夠直接使用來進行繪制,避免再次上傳紋理。


假設TextureCache里沒有相關bitmap的緩存,TextureCache就會創建bitmap的紋理緩存,假設緩存空間不夠了,TextureCache就會移除最老的bitmap的緩存,釋放空間給新的bitmap做緩存。


Texture* TextureCache::get(SkBitmap* bitmap) {

Texture* texture = mCache.get(bitmap);

if (!texture) {

if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {

? ? ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",

? ? ? ? ? ? bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);

? ? return NULL;

}

const uint32_t size = bitmap->rowBytes() * bitmap->height();

// Don't even try to cache a bitmap that's bigger than the cache

if (size < mMaxSize) {

? ? while (mSize + size > mMaxSize) {

? ? ? ? mCache.removeOldest();

? ? }

}

texture = new Texture();

texture->bitmapSize = size;

generateTexture(bitmap, texture, false);

if (size < mMaxSize) {

? ? mSize += size;

? ? TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",

? ? ? ? ? ? ?bitmap, texture->id, size, mSize);

? ? if (mDebugEnabled) {

? ? ? ? ALOGD("Texture created, size = %d", size);

? ? }

? ? mCache.put(bitmap, texture);

} else {

? ? texture->cleanup = true;

}

} else if (bitmap->getGenerationID() != texture->generation) {

generateTexture(bitmap, texture, true);

}

return texture;

}


有意思的是TextureCache怎樣知道是同一個bitmap,這個依賴于LRUCache,TextureCache里的成員變量mCache,這個LRUCache中,bitmap相當于是key。這意味著什么?意味著假設你的bitmap沒有復用,每次對象都不一樣的話,必定會在gpu空間產生一份拷貝。


即使你是一位優秀的android開發。很注意回收bitmap,gpu空間依舊會有占用,由于在bitmap的回收函數中。并沒有對主動清除TextureCache的調用。


當一個canvas重復被觸發繪制的時候。內存監測工具依舊能夠發現內存泄漏,GPU的緩存不斷上漲就是一個非常有可能的原因。

那系統什么時候能夠釋放?


(三)系統怎樣釋放GPU緩存


系統會在什么時候釋放這些GPU緩存呢?通常是在ActivityManagerService(AMS)里。當應用切換的時候。AMS就會觸發trimApplication函數。trimApplication調用的updateOomAdjLocked里會有例如以下的清除緩存的過程:




這個能夠看出:

  • 系統會在某個時候清除hwui里申請的GPU緩存


  • 2.在后臺時間越久的進程越easy被清理。排在最后的能夠被深度清理,詳細代碼在hardwarerender.java里:


    static void startTrimMemory(int level) {

    ? ? if (sEgl == null || sEglConfig == null) return;

    ? ? Gl20RendererEglContext managedContext =

    ? ? ? ? ? ? (Gl20RendererEglContext) sEglContextStorage.get();

    ? ? // We do not have OpenGL objects

    ? ? if (managedContext == null) {

    ? ? ? ? return;

    ? ? } else {

    ? ? ? ? usePbufferSurface(managedContext.getContext());

    ? ? }

    ? ? if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {

    ? ? ? ? GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);

    ? ? } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {

    ? ? ? ? GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);

    ? ? }

    }


    GLES20的flushCaches本質上還是調用了hwui的Caches.cpp的操作函數Caches::flush(FlushMode mode)


    void Caches::flush(FlushMode mode) {

    FLUSH_LOGD("Flushing caches (mode %d)", mode);

    // We must stop tasks before clearing caches

    if (mode > kFlushMode_Layers) {

    tasks.stop();

    }

    switch (mode) {

    case kFlushMode_Full:

    ? ? textureCache.clear();

    ? ? patchCache.clear();

    ? ? dropShadowCache.clear();

    ? ? gradientCache.clear();

    ? ? fontRenderer->clear();

    ? ? fboCache.clear();

    ? ? dither.clear();

    ? ? // fall through

    case kFlushMode_Moderate:

    ? ? fontRenderer->flush();

    ? ? textureCache.flush();

    ? ? pathCache.clear();

    ? ? // fall through

    case kFlushMode_Layers:

    ? ? layerCache.clear();

    ? ? renderBufferCache.clear();

    ? ? break;

    }

    clearGarbage();

    }



    GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL) 相應的是kFlushMode_Full,這個清理的程度最深


    GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE)相應的是kFlushMode_Moderate


    GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS)相應的是kFlushMode_Layers


    關于kFlushMode_Layers,我們要小心。


    當我們往windowmanager里addview之后,假設做了removeView。并不會釋放view里的texture cache,可是會觸發GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS),清除layer cache。在之前的工作中,團隊曾有討論,覺得removeView能夠充分釋放GPU緩存,這個結論是不準確的。近期有位同學研究的非常深入,他的demo和源代碼走讀證明了removeView僅僅會釋放layer cache,并沒有觸發紋理緩存的回收,這意味著什么?意味通知系統動態addView->顯示 ->removeView的過程依舊會導致GPU內存逐步上漲。系統剩余內存越來越少的情況,直到系統AMS觸發startTrimMemory后,內存才會被回收一些。


    總結一下:應用開發人員調用startTrimMemory會幫助app或者系統很多其它的釋放內存,降低內存壓力,可是調用的位置和時機要謹慎,由于清除了緩存。在下一次繪制(vsync的下一個信號到來)的時候繪制效率不會非常高。


    作者簡單介紹

    ?黃石柱 MIG智能平臺產品部終端開發組副總監10年的移動端軟件研發經驗,4年騰訊終端開發經驗。在騰訊主導設計研發tita(tos前身),魅拍等多款產品,眼下正在深入tos的研發以及虛擬現實技術的研發,在安卓操作系統,多媒體技術上有不錯的積累,開發公司級課件《深入安卓省電十大困惑》。


    超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

    總結

    以上是生活随笔為你收集整理的优化安卓应用内存的神奇方法以及背后的原理,一般人我不告诉他的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 美女的隐私免费看 | 欧美精品久久久久久久久 | 美女黄色小视频 | 最新av女优 | 99精品久久久久久久婷婷 | 毛片黄色片 | 黄色成年人视频 | 中国精品一区二区 | 亚洲永久视频 | 久久久无码精品亚洲无少妇 | 九色视频偷拍少妇的秘密 | 秋霞7777鲁丝伊人久久影院 | 99爱精品| 国产成人免费看 | 少妇太爽了在线观看 | 亚洲精品亚洲人成人网 | 91黄免费| 91精品国产乱码久久 | 久久国产乱子伦免费精品 | 中国黄色网页 | 午夜精品一区二区三区免费视频 | 日本人の夫妇交换 | 欧美黑人精品一区二区不卡 | 亚洲精品视频观看 | 国产又粗又长又大 | 国产成人久久精品麻豆二区 | 丁香婷婷深情五月亚洲 | 国产a级片| 日本精品人妻无码免费大全 | 日韩a级在线观看 | 公妇借种乱htp109cc | 婷婷激情视频 | 又紧又大又爽精品一区二区 | 国产一区二区在线免费观看视频 | 日本视频网站在线观看 | 91av在线视频观看 | 日日操夜夜摸 | 一级片一区二区三区 | 无码人妻精品一区二区中文 | 国产一区在线不卡 | eeuss鲁片一区二区三区在线观看 | 亚洲成人精选 | 久久夜夜操妹子 | 高清一区二区三区四区五区 | 中文字幕色哟哟 | 美国黄色一级视频 | 蜜桃在线一区二区三区 | 青青草视频在线免费观看 | 国产亚洲成av人片在线观看桃 | 免费看成年人视频 | 午夜在线播放视频 | 亚洲第一成肉网 | 日韩一区免费观看 | 青草av在线 | 国产精品天天狠天天看 | 亚洲精品久久久久久久久久久 | 99久久网站 | 国产在线视频导航 | 免费爱爱视频 | 图片区小说区视频区 | 青青草欧美 | 日本黄色性视频 | 黄色av导航| 亚洲小视频网站 | 成人看片在线观看 | 天堂在线中文资源 | 三级网站免费观看 | 波多野结衣一区二区三区中文字幕 | 清草视频| 欧美激情在线看 | 91精品国产成人 | 国产ts丝袜人妖系列视频 | 日本三级免费 | 国产专区精品 | 成人免费观看cn | 日本不卡视频一区二区三区 | 精品久久久亚洲 | 狠狠精品干练久久久无码中文字幕 | 日韩激情小说 | 婷婷俺去也 | youjizzxxxxx| 国产精品日韩一区二区三区 | 日本www在线 | 爆乳熟妇一区二区三区 | 色一情一乱一区二区三区 | 国产色在线,com | 手机在线观看av网站 | 在线免费观看亚洲视频 | 国产精品无码电影在线观看 | 脱美女衣服亲摸揉视频 | 国产粉嫩av| 免费中文字幕日韩欧美 | 99热r | 亚洲一区视频在线播放 | 久久丫精品久久丫 | 中国一级特黄真人毛片免费观看 | 五月婷婷激情视频 | 欧美三级又粗又硬 | 嫩草影院中文字幕 |