Android应用程序通常害怕被杀死。高内存使用率是被杀死的重要原因之一。因此,每个人都想做各种技巧,但效果是平均的。
但是有一个窍门:
WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);
几乎没有人提到它。在此期间的实际战斗中,发现效果还不错,但是要掌握此功能的用法,您需要仔细了解其原理。毕竟,此调用等效于在部分时间内清除应用程序的一系列GPU缓存,这等效于硬件加速。无效。
文章分为三个部分。第一部分以简单的方式描述了Android绘图系统的框架,第二部分解释了GPU为什么在绘图过程中生成缓存。第三部分介绍了startTrimMemory可以清理的GPU缓存以及一些误解。
([一)Android绘图系统框架简介
Android绘图系统更加复杂。互联网上的许多文章都非常详细,但是要掌握核心点并不容易。实际上,只要我们掌握了12个关键的对应关系和概念,就可以掌握清晰的基本框架,并且调试和性能优化具有价值。
1)活动对应于一个窗口。当然,可能会有一个没有活动的窗口,例如通知栏。众所周知,窗口具有各种属性,例如级别,位置等。
2)窗口对应于一个表面,并且该表面实际上是管理图形缓冲区的对象

3)surface是通过请求surfaceflinger完成创建的,实际上,它对应于一个图形缓冲区,可由gpu和cp进行访问
4)窗口上可以有很多视图,它们可以是一个视图树。对于该活动,顶视图是DecorView,并且该活动上的所有视图都对应于同一表面
5)与活动中的视图相比,表面视图(glsurfaceview)具有自己的独立表面和独立的处理线程,与活动表面不同
6)活动视图的图形(在启用了硬件加速的情况下)实际上是在表面上绘制的,最终由hwui这样完成,这是在应用程序侧而不是在SurfaceFlinger侧执行的。 hwui是用于硬件渲染的密钥库。最重要的是,hui中有一系列GPU缓存,以避免在渲染过程中重新上传图像纹理和其他与GPU渲染相关的数据
7)每个表面都有一个合成过程,该过程可以在Surfaceflinger中完成
8)每次绘制活动视图并合成表面时,它都会由vsync信号触发,并且vsync每16.6毫秒触发一次。
9)surfaceview(glsurfaceview)的图形可以在不使用vsync的情况下进行同步,并且其自己的线程独立控制节奏,但是绘制后的曲面合成由surfaceflinger统一进行
10)应用程序侧的表面,无论是与视图相对应还是与表面视图相对应,绘制后,通过eglwapbuffer方法,将图形缓冲区队列返回给surfaceflinger(在合成surfaceflinger之后,它将在屏幕上显示然后释放,以便应用程序端可以重用这些缓冲区)
在为1 1)视图设置时,如果未刷新子视图,则可能不会触发子视图的绘制。这是过程高效执行的关键。您可以使用视图的硬件层缓存整体上制作,如果在对视图进行处理时触发了子视图的重绘,则会降低绘制效率
12)当前主流的Android手机中,GPU和CPU共享内存。如果GPU占用更多内存,则为CPU保留的内存量将相应减少。 GPU在每个进程中占用的内存也将在每个进程中进行计数。在总内存中,它将影响低内存杀手级策略
另一幅图可以大致反映出上面12个关键描述的部分架构
(二)由GPU缓存引起的画布绘制位图(通常称为GPU内存泄漏)
每个人都绝对有兴趣。如何将位图绘制到屏幕上的视图将触发绘图代码中的canvas.drawBitmap。如果打开了硬件加速,则画布实际上是GLES20RecordingCanvas,而GLES20RecordingCanvas的父类是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对应的本机代码是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);
}很明显,画布的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对象,该对象将绘制的位图缓存在gpu纹理中,以便下次有重复时可以将其直接用于绘制,而无需再次上传纹理。
如果TextureCache中没有相关的位图缓存,则TextureCache将创建一个位图纹理缓存。如果缓存空间不足,则TextureCache将删除最早的位图缓存,并为新的位图腾出空间作为缓存。
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如何知道它是相同的位图。这取决于LRUCache,TextureCache中的成员变量mCache。在此LRUCache中,位图等效于键。这是什么意思?这意味着,如果不重复使用位图,则每次对象不同时,将不可避免地在gpu空间中生成一个副本。
即使您是优秀的android开发人员,也要非常注意回收位图,gpu空间仍然会被占用,因为在位图的回收功能中,没有调用来主动清除TextureCache。
当反复触发画布进行绘制时,内存监视工具仍然可以发现内存泄漏,并且GPU缓存不断上升是很可能的原因。什么时候可以发布系统?
([三)系统如何释放GPU缓存
系统何时释放这些GPU缓存?通常在ActivityManagerService(AMS)中,当切换应用程序时,AMS将触发trimApplication函数,trimApplication调用的updateOomAdjLocked将具有以下缓存清除过程:
可以看到:
1.系统会在某个时候清除hwui中请求的GPU缓存
2.在后台进行的时间越长,清理起来就越容易,并且可以深度清理最后一个。具体代码在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模式)

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时,如果执行removeView,则不会释放视图中的纹理缓存,但是将触发GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS)来清除图层缓存。在先前的工作中,团队讨论了removeView可以完全释放GPU缓存。这个结论是不正确的。最近,一个学生进行了非常深入的研究。他的演示和源代码演练证明,removeView仅释放层缓存,而不会触发纹理缓存的恢复。这是什么意思?这意味着通知系统动态addView-> display-> removeView的过程仍然会导致GPU内存逐渐增加,而剩余的系统内存将越来越少。在系统AMS触发startTrimMemory之前,不会回收内存。
总结:调用startTrimMemory的应用程序开发人员将帮助该应用程序或系统释放更多的内存并减少内存压力,但是调用的位置和时间应谨慎,因为已清除了缓存并在下次绘制时(出现vsync的下一个信号))绘制时效率不是很高。
作者
黄石柱MIG智能平台产品部终端开发组副主任具有10年移动终端软件开发经验,4年腾讯终端开发经验,并领导了tita(tos的前身)的设计和开发。 ,美拍等腾讯产品。我们正在对tos和虚拟现实技术进行深入的研究和开发。我们在Android操作系统和多媒体技术方面拥有丰富的积累。我们正在开发公司级课件“在加深Android省电方面的十大困惑”。
腾讯Bugly最的质量跟踪平台
Spirit兄弟和Little Lolita,我们将定期为您分享应用崩溃的解决方案
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-343666-1.html
是啊