我们启动一个空壳 app,所谓空壳 app 就是里面实际上啥都没有,一张图片都没有的 app,在我的小米 Note 4x 上看看内存占用多少

我曹 怎么会有 36mb? wtf? 这个 graphics 里面几乎都是 bitmap,为何这里 bitmap 占用内存这么多呀?

我明明一张图都没加载啊。

打开内存 dump,导出以后 用 hprof 工具转换成 mat 可以解析的格式:

最后用 mat 打开,搜下 bitmap 看看是啥?

嗯?怎么有这么多 bitmap 对象?我没加载过任何图片啊?

查查看都是谁引用的?

去源码里面查查这是个sPreloadedDrawables啥东西?

既然是跟资源有关的东西 肯定绕不开 ResourcesImpl 这个类了,很快就能搜到

实际上解释起来,就是 android 系统启动的时候肯定是先启动 zygote 进程,有了这个进程以后,其他 app 进程启动 就只要 fork 他即可。所以实际上当 zygote 进程启动的时候会把一些系统的资源优先加载到内存里,注意 这个 framework 本身包含的资源是很多的,但是只有很有限的资源才有资格到这预加载内存里面

所以当我们 zygote 进程启动完毕以后,这个进程的内存里面就有这些 系统资源在里面了,我们这里只关注 bitmap 资源 也就是 drawable,然后其他 app 进程启动的时候既然是 fork 了 zygote 进程,自然而然的这里 也会包含这些图片资源。

这就是为什么我们一个空壳 app 也会加载那么多 bitmap 在内存里的原因,这样做的好处当然是如果你的 app 用到了这些预加载 的资源,那么可以省略 decode 的过程,直接从内存里面取,速度快,缺点就是这个东西占用的内存着实有一些大了。。。 动不动 20mb 左右的空间。

我们来看下系统 loadDrawable 的过程就可以加深这个理解了

@Nullable
    Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
            boolean useCache) throws NotFoundException {
        try {
            if (TRACE_FOR_PRELOAD) {
                // Log only framework resources
                if ((id >>> 24) == 0x1) {
                    final String name = getResourceName(id);
                    if (name != null) {
                        Log.d("PreloadDrawable", name);
                    }
                }
            }
        final boolean isColorDrawable;
        final DrawableCache caches;
        final long key;
        <span class="hljs-keyword">if</span> (value.type &gt;= TypedValue.TYPE_FIRST_COLOR_INT
                &amp;&amp; value.type &lt;= TypedValue.TYPE_LAST_COLOR_INT) {
            isColorDrawable = <span class="hljs-literal">true</span>;
            caches = mColorDrawableCache;
            key = value.data;
        } <span class="hljs-keyword">else</span> {
            isColorDrawable = <span class="hljs-literal">false</span>;
            caches = mDrawableCache;
            key = (((long) value.assetCookie) &lt;&lt; 32) | value.data;
        }

        // First, check whether we have a cached version of this drawable
        // that was inflated against the specified theme. Skip the cache <span class="hljs-keyword">if</span>
        // we<span class="hljs-string">'re currently preloading or we'</span>re not using the cache.
        <span class="hljs-keyword">if</span> (!mPreloading &amp;&amp; useCache) {
            final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
            <span class="hljs-keyword">if</span> (cachedDrawable != null) {
                <span class="hljs-built_in">return</span> cachedDrawable;
            }
        }

        // Next, check preloaded drawables. Preloaded drawables may contain
        // unresolved theme attributes.
        final Drawable.ConstantState cs;
        <span class="hljs-keyword">if</span> (isColorDrawable) {
            cs = sPreloadedColorDrawables.get(key);
        } <span class="hljs-keyword">else</span> {
            //这里看到没有 取系统缓存了
            cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
        }
        //取不到就重新decode
        Drawable dr;
        <span class="hljs-keyword">if</span> (cs != null) {
            dr = cs.newDrawable(wrapper);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (isColorDrawable) {
            dr = new ColorDrawable(value.data);
        } <span class="hljs-keyword">else</span> {
            dr = loadDrawableForCookie(wrapper, value, id, null);
        }

        // Determine <span class="hljs-keyword">if</span> the drawable has unresolved theme attributes. If it
        // does, we<span class="hljs-string">'ll need to apply a theme and store it in a theme-specific
        // cache.
        final boolean canApplyTheme = dr != null &amp;&amp; dr.canApplyTheme();
        if (canApplyTheme &amp;&amp; theme != null) {
            dr = dr.mutate();
            dr.applyTheme(theme);
            dr.clearMutated();
        }

        // If we were able to obtain a drawable, store it in the appropriate
        // cache: preload, not themed, null theme, or theme-specific. Don'</span>t
        // pollute the cache with drawables loaded from a foreign density.
        <span class="hljs-keyword">if</span> (dr != null &amp;&amp; useCache) {
            dr.setChangingConfigurations(value.changingConfigurations);
            cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
        }

        <span class="hljs-built_in">return</span> dr;
    } catch (Exception e) {
        String name;
        try {
            name = getResourceName(id);
        } catch (NotFoundException e2) {
            name = <span class="hljs-string">"(missing name)"</span>;
        }

        // The target drawable might fail to load <span class="hljs-keyword">for</span> any number of
        // reasons, but we always want to include the resource name.
        // Since the client already expects this method to throw a
        // NotFoundException, just throw one of those.
        final NotFoundException nfe = new NotFoundException(<span class="hljs-string">"Drawable "</span> + name
                + <span class="hljs-string">" with resource ID #0x"</span> + Integer.toHexString(id), e);
        nfe.setStackTrace(new StackTraceElement[0]);
        throw nfe;
    }
}
复制代码

所以这里就给我们提供了一种优化思路:

当我们的 app 刚开始运行时,内存富余空间很大,所以我们可以不关心这个系统缓存带来的问题,但是如果当我们发现 内存压力变大的时候,就可以考虑在合适的时机,手动的释放掉这 20mb 左右的系统缓存了,释放的方法也很简单 反射 clear 一下即可:

Resources resource = getApplicationContext().getResources();
        try {
            //注意这个地方 有些rom会有独特的名字,需要你们线上监控到不同rom以后 遍历下他们的field看看这个变量
            //的名字是什么 才能hook成功
            Field field = Resources.class.getDeclaredField("sPreloadedDrawables");
            field.setAccessible(true);
            LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables = (LongSparseArray<Drawable.ConstantState>[]) field
                    .get(resource);
            for (LongSparseArray<Drawable.ConstantState> s : sPreloadedDrawables) {
                s.clear();
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
复制代码

当然这样的开销也有弊病,就是如果你释放掉这部分内存以后,如果要加载这些资源,那么会多一步 decode 的过程。

当然现在插件框架很多了,很多人都对 resources 这部分有了解,其实这个地方我们完全可以利用动态代理的方法 hook 掉 系统的这个取缓存的 sPreloadedDrawables 过程,让他们取 我们自己图片加载框架里的缓存即可。 但是注意这个 hook 的过程要考虑不同 rom 的兼容性的地方就更多了,有兴趣的同学可以自行研究下。

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...