精华内容
下载资源
问答
  • Android系统内存不足时,会回收后台运行应用的内存
    2021-06-04 17:00:44

    1、前言

    当Android系统的运行内存不足时,会把运行在后台的应用杀掉,将所有已经存在的Activity都杀掉,当用户重新返回应用的时候,所有被杀掉的Activity都会被重建,而且应用会运行在一个全新的进程当中,所有的数据都被重置为初始状态。那么这个时候往往都会出现错误数据。

    2、案例:

    XX应用,在登录页初始化所有数据

    --> 登录成功进入主页面

    --> 此时登陆页已经完成任务,finish登陆页

    -->从主页面进入到A页面

    --> A页面通过网络加载数据

    --> 点击Home键,打开其他耗内存的应用,此时如果内存不够,会把XX应用的主页面和A页面都干掉,当再返回XX应用时,主页面和A页面都会重建,经历onCreate方法等。

    3、如何触发以上案例:

    XX应用现在的手机基本上运行内存都足够了,但是还是会有开太多应用导致内存不够的时候,如果使用真机来重现以上案例,需要开的应用就多了,那么如何轻易的就实现以上案例呢?很简单,可以通过虚拟机,下载Genymotion,建一个虚拟机,将虚拟机的运行内存改为150M-200M左右,基本上就足够一个应用运行但不够两个应用一起运行了。

    重现案例的条件已经具备了,接下来就是如何通过代码Log来实验了。

    4、代码分析:

    @Override

    protected void onCreate(Bundle savedInstanceState)

    {

    super.onCreate(savedInstanceState);

    Log.e("YYQ", "onCreate");

    }

    @Override

    protected void onStart()

    {

    super.onStart();

    Log.e("YYQ", "onStart");

    }

    @Override

    protected void onResume()

    {

    super.onResume();

    Log.e("YYQ", "onResume" + android.os.Process.myPid());

    }

    @Override

    protected void onPause()

    {

    super.onPause();

    Log.e("YYQ", "onPause");

    }

    @Override

    protected void onStop()

    {

    super.onStop();

    Log.e("YYQ", "onStop");

    }

    @Override

    protected void onDestroy()

    {

    super.onDestroy();

    Log.e("YYQ", "onDestroy");

    }

    @Override

    protected void onRestart()

    {

    super.onRestart();

    Log.e("YYQ", "onRestart");

    }

    // 这个方法不知道是什么时候触发的

    @Override

    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)

    {

    super.onSaveInstanceState(outState, outPersistentState);

    Log.e("YYQ", "onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)");

    }

    // 当系统未经你的允许就想要把Activity干掉的时候,这个方法会被触发

    @Override

    protected void onSaveInstanceState(Bundle outState)

    {

    super.onSaveInstanceState(outState);

    Log.e("YYQ", "onSaveInstanceState(Bundle outState)");

    }

    @Override

    protected void onRestoreInstanceState(Bundle savedInstanceState)

    {

    super.onRestoreInstanceState(savedInstanceState);

    Log.e("YYQ", "onRestoreInstanceState");

    }

    在Activity中加入如上方法,就可以在Log中查看Activity的状态了。

    正常的Activity状态就不说了,

    当你按下Home键,会触发

    onPause

    onSaveInstanceState(Bundle outState)

    onStop

    比较重要的是onSaveInstanceState(Bundle outState),当系统未经你的允许就想要把Activity干掉的时候,这个方法会被触发,你可以在这里进行保存的操作。

    此时应用已经在后台运行了,当你没有打开其他应用,也就是内存足够的时候,重新返回的时候不会触发onRestoreInstanceState方法,只有应用被干掉了,才会触发onRestoreInstanceState方法,还有onCreate方法。

    onCreate

    onStart

    onRestoreInstanceState

    onResume

    在onCreate方法加入:

    @Override

    protected void onResume()

    {

    super.onResume();

    Log.e("YYQ", "onResume:" + android.os.Process.myPid());

    }

    会发现你刚启动应用时的pid和应用被杀掉后重建的pid不一样,说明应用在新进程中了。

    你可以在onRestoreInstanceState方法中恢复数据。

    5、解决方式:

    虽然可以在onSaveInstanceState(Bundle outState)和onRestoreInstanceState方法中进行保存和恢复数据,但是对于一些全局性的数据最好能够一次性恢复,这样对于所有的Activity都有好处。

    可以在基类Activity中一次性恢复。(一般应用都会有个自己的基类BaseActivity,用于抽取Activity的共性)

    在onCreate方法中加入判断:

    if (savedInstanceState != null && TextUtils.isEmpty(

    BaseApplication.getInstance().getCurrentUser().getSystemUserID()))

    {

    BaseApplication.getInstance().initUserAllData();

    }

    其中第二个判断条件和if语句块是根据个人情况来的,我在这里的判断是当用户的id为null时,就说明Activity已经被重建了,数据被清除了,那么我就再次初始化用户的数据。保证了用户数据的完整性。

    第一个判断条件只有当调用了onSaveInstanceState(Bundle outState)方法,并且应用被杀掉后,才不为null。

    如此一来,就不怕内存不够的时候了,而且初始化用户数据也不会被多次初始化。

    更多相关内容
  • Android 中图片占用内存分析

    千次阅读 2022-01-13 18:11:10
    Android 在加载图片的时候一定会考虑到的一个点就是如何防止 OOM,那么一张图片在加载的时候到底会占用多少内存呢?会有哪些因素影响占用内存呢?知道了这些,我们才能知道可以从哪些点去优化,从而避免 OOM。 一...

    Android 在加载图片的时候一定会考虑到的一个点就是如何防止 OOM,那么一张图片在加载的时候到底会占用多少内存呢?有哪些因素会影响占用的内存呢?知道了这些,我们才能知道可以从哪些点去优化,从而避免 OOM。

     

    一、图片占用内存与宽、高、色彩模式的关系

    首先我们准备一张 1920*1080 的图片:

    然后我使用的测试机是 Redmi Note 9 Pro,分辨率是 2400*1080,将这张图片放到对应分辨率的目录下,也就是 drawable-xxhdpi 目录下,然后使用不同的配置去加载图片:

        override fun initData() {
            val options = BitmapFactory.Options()
            val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test_xxhdpi, options)
            ShowLogUtil.info("width: ${options.outWidth},height: ${options.outHeight},config: ${options.inPreferredConfig},占用内存: ${bitmap.allocationByteCount}")
            options.inSampleSize = 2
            val bitmap2 = BitmapFactory.decodeResource(resources, R.drawable.test_xxhdpi, options)
            ShowLogUtil.info("width: ${options.outWidth},height: ${options.outHeight},config: ${options.inPreferredConfig},占用内存: ${bitmap2.allocationByteCount}")
            options.inPreferredConfig = Bitmap.Config.RGB_565
            val bitmap3 = BitmapFactory.decodeResource(resources, R.drawable.test_xxhdpi, options)
            ShowLogUtil.info("width: ${options.outWidth},height: ${options.outHeight},config: ${options.inPreferredConfig},占用内存: ${bitmap3.allocationByteCount}")
        }

    在上面的代码中,第一次加载图片时使用的是默认配置,第二次加载图片的时候修改了采样率,采样率必须设置为 2 的 n(n≥0) 次幂;第三次加载图片的时候在修改采样率的基础上再修改了色彩模式。观察 log:

    可以看到第二次由于我们设置采样率为 2,相当于设置图片压缩比,然后加载时的宽和高都变成了第一次的 1/2,占用内存为第一次的 1/4;第三次在第二次的基础上再设置了色彩模式为 RGB_565,占用内存为第二次的 1/2,第一次的 1/8。

    其实还有其他的色彩模式,最后再解释几种色彩模式有什么区别,现在只需要知道 ARGB_8888 是 ARGB 分量都是 8 位,所以一个像素点占 32 位,也就是 4 字节,它是最能保证图片效果的一种模式。而 RGB_565 中 RGB 分量分别使用5位、6位、5位,没有透明度,所以一个像素点占 16 位,也就是 2 字节。

    那么我们可以简单的看出,占位内存是与加载宽高,与像素点大小成正比,且倍数关系,而且暂时认为它们的关系为:

    占用内存=宽*高*像素点大小

    二、图片占用内存与存放文件夹的关系

    在日常开发中,UI 在切图的时候通常会切不同分辨率的图片,2 倍图对应 Android 的 xhdpi 目录,3 倍图对应 Android 的 xxhdpi 目录,那么为什么不同资源文件夹需要不同分辨率的图片呢?只用一套图片可不可以呢?我们来看看将同一张图片放到不同目录下,在加载的时候内存占用情况分别如何。

    我将上图放在不同目录下,分别命名为不同的名字:

     这里图片的分辨率都是一样的,都是 1920*1080,只是放到了不同目录下,然后我们再分别加载这三张图片。

        override fun initData() {
            val options1 = BitmapFactory.Options()
            val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test_xxhdpi, options1)
            ShowLogUtil.info(
                "width: ${options1.outWidth},height: ${options1.outHeight},config: ${options1.inPreferredConfig},占用内存: ${bitmap.allocationByteCount},inDensity: ${options1.inDensity},inTargetDensity: ${options1.inTargetDensity}"
            )
            val options2 = BitmapFactory.Options()
            val bitmap2 = BitmapFactory.decodeResource(resources, R.drawable.test_xhdpi, options2)
            ShowLogUtil.info(
                "width: ${options2.outWidth},height: ${options2.outHeight},config: ${options2.inPreferredConfig},占用内存: ${bitmap2.allocationByteCount},inDensity: ${options2.inDensity},inTargetDensity: ${options2.inTargetDensity}"
            )
            val options3 = BitmapFactory.Options()
            val bitmap3 = BitmapFactory.decodeResource(resources, R.drawable.test_hdpi, options3)
            ShowLogUtil.info(
                "width: ${options3.outWidth},height: ${options3.outHeight},config: ${options3.inPreferredConfig},占用内存: ${bitmap3.allocationByteCount},inDensity: ${options3.inDensity},inTargetDensity: ${options3.inTargetDensity}"
            )
        }

    在加载三张图的时候分别传入了一个默认的 Options 对象,并在加载图片完成后增加了 Options 的 inDensity 和 inTargetDensity 属性的 log,观察 log:

     可以看到在加载 xhdpi 的图片时内存占用大于 xxhdpi 的内存占用,为 xxhdpi 的 2.25 倍,在加载 hdpi 的图片时内存占用为 xxhdpi 的 4 倍,那这个倍数关系是怎么来的呢?别着急,一会儿通过源码可以找到答案。

    我们先修改一下图片,将 xhdpi 目录下的图片分辨率改为 1280*720,hdpi 目录下的图片分辨率改为 960*540,再次运行,观察 log:

    这时候我们发现加载不同分辨率下目录的图片的内存占用都是一样的了。

    现在我们来查看一下 BitmapFactory.decodeResource() 方法的源码了:

        public static Bitmap decodeResource(Resources res, int id, Options opts) {
            validate(opts);
            Bitmap bm = null;
            InputStream is = null; 
            
            try {
                final TypedValue value = new TypedValue();
                // 打开资源流,并初始化一些属性。
                is = res.openRawResource(id, value);
    
                // 解析图片资源。
                bm = decodeResourceStream(res, value, is, null, opts);
            } catch (Exception e) {
                /*  do nothing.
                    If the exception happened on open, bm will be null.
                    If it happened on close, bm is still valid.
                */
            } finally {
                try {
                    if (is != null) is.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
    
            if (bm == null && opts != null && opts.inBitmap != null) {
                throw new IllegalArgumentException("Problem decoding into existing bitmap");
            }
    
            return bm;
        }

    decodeResource() 方法中 bitmap 对象是通过 decodeResourceStream() 方法去加载的,继续查看 decodeResourceStream() 方法:

        @Nullable
        public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
                @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
            validate(opts);
            // 检查 Options 对象是否为空,为空则创建一个默认对象。
            if (opts == null) {
                opts = new Options();
            }
            // 查看 Options 对象是否设置了 inDensity,这里是默认的,所以没有设置,TypedValue 对象在上面的 decodeResource() 方法中也是创建的一个默认对象,所以不为 null,必然进这个 if 代码块。
            if (opts.inDensity == 0 && value != null) {
                final int density = value.density;
                if (density == TypedValue.DENSITY_DEFAULT) {
                    opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
                } else if (density != TypedValue.DENSITY_NONE) {
                    // TypedValue 对象在上面的 decodeResource() 方法中调用 Resources.openRawResource() 方法的时候 density 会赋值成对应的资源文件所在目录的 density 值,所以会走到这里,给 Options 的 inDensity 属性赋值。
                    opts.inDensity = density;
                }
            }
            
            // Options 的 inTargetDensity 默认没有赋值,所以会进 if 代码块,赋值为手机屏幕的 densityDpi。
            if (opts.inTargetDensity == 0 && res != null) {
                opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
            }
            
            return decodeStream(is, pad, opts);
        }

    该方法最后调用的是 decodeStream() 方法,继续查看 decodeStream() 方法,这里我只解释关键代码,省略部分代码:

        @Nullable
        public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
                @Nullable Options opts) {
            ...
            try {
                if (is instanceof AssetManager.AssetInputStream) {
                    final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
                    bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeInBitmap(opts),
                        Options.nativeColorSpace(opts));
                } else {
                    // 这里的资源并非从 assets 目录中加载,所以进入 else 代码块。
                    bm = decodeStreamInternal(is, outPadding, opts);
                }
            ...
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
            }
            return bm;
        }

    继续查看 decodeStreamInternal() 方法:

        private static Bitmap decodeStreamInternal(@NonNull InputStream is,
                @Nullable Rect outPadding, @Nullable Options opts) {
            // ASSERT(is != null);
            byte [] tempStorage = null;
            if (opts != null) tempStorage = opts.inTempStorage;
            if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
            return nativeDecodeStream(is, tempStorage, outPadding, opts,
                    Options.nativeInBitmap(opts),
                    Options.nativeColorSpace(opts));
        }

    可以看到该方法中最终调用了 native 层中的 nativeDecodeStream() 方法,所以我们需要继续追到 c++ 层,调用的是 /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp,查看其 nativeDecodeStream 方法:

    static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
            jobject padding, jobject options) {
    
        jobject bitmap = NULL;
        std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
    
        if (stream.get()) {
            std::unique_ptr<SkStreamRewindable> bufferedStream(
                    SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded()));
            SkASSERT(bufferedStream.get() != NULL);
            bitmap = doDecode(env, std::move(bufferedStream), padding, options);
        }
        return bitmap;
    }

    可以看到图片的解码是通过 doDecode() 方法完成,查看该方法,这里我只解释关键代码,省略部分代码:

    static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
                            jobject padding, jobject options) {
        // Set default values for the options parameters.
        ...
        // 初始化缩放比为 1.0
        float scale = 1.0f;
        ...
        if (options != NULL) {
            ...
            // 获取 java 中的 Options 对象中的 density,targetDensity,计算出缩放比,两者都是在 java 代码中的 decodeResourceStream() 方法赋值的。
            if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
                const int density = env->GetIntField(options, gOptions_densityFieldID);
                const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
                const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
                // 如加载的是 xhdpi 中的图片则 inDensity 为 320,使用的测试机分辨率为 1920*1080,则 targetDensity 为 480,所以 scale 为 480/320=1.5
                if (density != 0 && targetDensity != 0 && density != screenDensity) {
                    scale = (float) targetDensity / density;
                }
            }
        }
        ...
        int scaledWidth = size.width();
        int scaledHeight = size.height();
        ...
        // Scale is necessary due to density differences.
        if (scale != 1.0f) {
    		// 需要缩放的话,计算缩放后的宽高。宽高分别乘以缩放比 scale。
            willScale = true;
            scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
            scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
        }
        ...
    	if (willScale) {
            ...
            outputBitmap.setInfo(
                    bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
    		...
        } else {
            outputBitmap.swap(decodingBitmap);
        }
    	...
        // now create the java bitmap
        return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
                bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
    }

    通过对源码的简单分析,我们可以得出的结论是,如果加载的图片所处的分辨率与手机屏幕分辨率不一样的话,会对图片进行缩放,宽高分别会乘以缩放比重新计算,所以它们的关系为:占用内存=(宽*缩放比)*(高*缩放比)*像素点大小,即

    占用内存=宽*高*(手机屏幕密度/资源文件夹密度)²*像素点大小

    这也就可以解释为什么不同的资源目录下需要不同分辨率的图片了,主要是为了节省内存。但是如果每一种分辨率都要去适配的话,那势必会增加图片,增加包体积,所以在做图片适配的时候,要根据图片使用频率以及市场手机分辨率分布情况做好利弊权衡。

    三、从文件中加载图片和从网络加载图片占用内存

    这两种加载图片的方式的话,其实通过刚才的源码分析,并不需要再通过实际运行我们就可以知道,由于没有设置 inDensity 和 inTargetDensity,所以占用内存就是宽*高*像素点大小。由于它不会进行缩放,所以我们在从文件中加载图片和从网络加载图片的时候,尤其需要注意它的内存占用情况,避免 OOM。

    并且通过刚才的分析,我们可以知道除了可以设置 inSampleSize 来优化占用内存,也可以通过设置 inDensity 和 inTargetDensity 来通过缩放比间接地优化占用内存。

    四、色彩模式

    色彩模式在 Android 中主要有四种,介绍这个的文章很多,简单提一下:

    Bitmap.Config.ARGB_8888:ARGB 分量都是 8 位,总共占 32 位,还原度最高。

    Bitmap.Config.ARGB_4444:ARGB 分量都是 4 位,总共占 16 位,保留透明度,但还原度较低。

    Bitmap.Config.RGB_565:没有透明度,RGB 分量分别占 5、6、5 位,总共占 16 位。

    Bitmap.Config.ALPHA_8:只保留透明度,总共占 8 位。

    这里重点不是为了介绍它们的区别,是要提一点,并不是你设置了那个色彩模式就一定会按照这个色彩模式去加载,需要看图片解码器是否支持,比如我们如果有一个灰度加载图片的需求,那么这时候设置色彩模式为 Bitmap.Config.ALPHA_8 看起来是最简单也最高效的方法,但实际可能并不是这样,如果图片解码器不支持,那么还是会使用 Bitmap.Config.ARGB_8888 去加载,这里是一个坑,还希望出现这种情况的时候,小伙伴们能想到可能有这个原因。

    四、总结

    1.图片占用内存=宽*高*(手机屏幕密度/资源文件夹密度)²*像素点大小,所以我们在优化图片占用内存的时候主要考虑两个方面:

    1)控制图像加载尺寸,这有两种方式:

    • 设置采样率来控制加载的宽高;
    • 通过设置 inDensity 和 inTargetDensity 控制缩放比。

    2)设置色彩模式来控制像素点大小,如果不需要透明度的图片,可以设置色彩模式为 Bitmap.Config.RGB_565 直接减少一半内存。

    2.不同分辨率的文件夹下放不同分辨率的图片是为了保证内存开销,但相应的会增加包体积,所以需要根据实际情况权衡。

    展开全文
  • 问题Android开发时可以通过AndroidStudio提供的一些系列工具查看应用的内存占用,十分的方便。但是如果是对一个成品的已安装App快速查看内存占用呢,下面简单讲两种方式。方案一:top 命令top 命令是一个linux下的...

    问题

    Android开发时可以通过AndroidStudio提供的一些系列工具查看应用的内存占用,十分的方便。

    但是如果是对一个成品的已安装App快速查看内存占用呢,下面简单讲两种方式。

    方案一:top 命令

    top 命令是一个linux下的基础命令,相信熟悉 linux 的同学都会使用。android 作为 linux 内核的系统,也具备很多 linux 下的常用指令。

    首先,通过 adb shell 进入到 android 设备的内置终端。

    $adb shell

    gemini:/ $

    复制代码

    然后,直接执行 top 命令即可看到系统中运行的进程所占用的内存

    gemini:/ $ top

    User 11%, System 47%, IOW 0%, IRQ 2%

    User 4 + Nice 0 + Sys 16 + Idle 13 + IOW 0 + IRQ 1 + SIRQ 0 = 34

    PID USER PR NI CPU% S #THR VSS RSS PCY Name

    27123 shell 20 0 20% R 1 9112K 2148K fg top

    578 system 12 -8 8% D 12 179908K 16532K unk /system/bin/surfaceflinger

    1483 system 10 -10 2% S 205 2767360K 363172K ta system_server

    2457 root RT 0 2% S 1 0K 0K fg irq/22-408000.q

    3 root 20 0 2% S 1 0K 0K fg ksoftirqd/0

    25889 root 20 0 2% S 1 0K 0K fg kworker/u8:6

    13326 u0_a1179 10 -10 2% S 81 2473172K 326764K ta com.xxx.xxx.xxx.xxx

    12 root 20 0 0% S 1 0K 0K fg ksoftirqd/1

    15 root RT 0 0% S 1 0K 0K fg migration/2

    16 root 20 0 0% S 1 0K 0K fg ksoftirqd/2

    19 root RT 0 0% S 1 0K 0K fg migration/3

    20 root 20 0 0% S 1 0K 0K fg ksoftirqd/3

    23 root 0 -20 0% S 1 0K 0K fg khelper

    24 root 0 -20 0% S 1 0K 0K fg netns

    25 root 0 -20 0% S 1 0K 0K fg perf

    26 root 0 -20 0% S 1 0K 0K fg smd_channel_clo

    27 root 20 0 0% S 1 0K 0K fg dsps_smd_trans_

    28 root 20 0 0% S 1 0K 0K fg lpass_smd_trans

    复制代码

    以上 pid=u0_a1179 就是我们的游戏占用内存的情况, 简单解释下几个关键参数:

    PID : 进程的id

    VSS : Virtual Set Size 虚拟耗用内存(包括共享库占用的内存),即单个进程全部可访问的地址空间,器大小可能包括还尚未在内存中驻留的部分。对于确定单个进程实际内内存使用大小,VSS用处不大。

    RSS Resident Set Size 实际使用物理存储(包括共享库占用的内存),即单个进程实际占用的内存大小,RSS不太准确的地方在于它包括该进程所使用的共享库全部内存大小。对于一个共享库,可能被多个进程使用,实际该共享库只会被装入内存一次。

    通过上述解释,我们可以看出,游戏占用的内存大约是 326M 左右。

    以上是查看所有内存,查看指定包名还可以使用 grep 来过滤

    top | grep 'com.xxx.xxx.xxx.xxx'

    复制代码

    方案二(推荐):adb dumpsys meminfo

    top 命令非常好用,但是在Android手机上,由于国产机的各种定制原因,这个命令不一定在每个设备上都会存在,所以我们还可以使用 adb dumpsys meminfo 命令来查看,这是个adb的通用命令。

    adb shell dumpsys meminfo com.xxx.xxx

    $adb shell dumpsys meminfo com.xxx.xxx -d

    Applications Memory Usage (in Kilobytes):

    Uptime: 7921887 Realtime: 7921887

    ** MEMINFO in pid 20533 [com.game.win.wallet.demo] **

    Pss Private Private SwapPss Heap Heap Heap

    Total Dirty Clean Dirty Size Alloc Free

    ------ ------ ------ ------ ------ ------ ------

    Native Heap 33297 33236 0 0 69632 42993 26638

    Dalvik Heap 32595 32416 0 0 39529 23726 15803

    Dalvik Other 1290 1288 0 0

    Stack 2176 2176 0 0

    Ashmem 134 132 0 0

    Gfx dev 149106 147676 16 0

    Other dev 25 0 24 0

    .so mmap 34269 988 31444 0

    .jar mmap 0 0 0 0

    .apk mmap 435 0 156 0

    .ttf mmap 907 0 788 0

    .dex mmap 8601 8 8592 0

    .oat mmap 2154 0 148 0

    .art mmap 3655 2532 236 0

    Other mmap 1235 8 1168 0

    EGL mtrack 13824 13824 0 0

    GL mtrack 30520 30520 0 0

    Unknown 27758 27756 0 0

    TOTAL 341981 292560 42572 0 109161 66719 42441

    App Summary

    Pss(KB)

    ------

    Java Heap: 35184

    Native Heap: 33236

    Code: 42124

    Stack: 2176

    Graphics: 192036

    Private Other: 30376

    System: 6849

    TOTAL: 341981 TOTAL SWAP PSS: 0

    Objects

    Views: 13 ViewRootImpl: 1

    AppContexts: 4 Activities: 1

    Assets: 7 AssetManagers: 3

    Local Binders: 28 Proxy Binders: 29

    Parcel memory: 20 Parcel count: 82

    Death Recipients: 1 OpenSSL Sockets: 5

    SQL

    MEMORY_USED: 682

    PAGECACHE_OVERFLOW: 202 MALLOC_SIZE: 62

    DATABASES

    pgsz dbsz Lookaside(b) cache Dbname

    4 20 12 0/15/1 /data/user/0/com.xxx.xxx/databases/jsb.sqlite

    25/40/16 /data/user/0/com.xxx.xxx/databases/bd_embed_tea_agent.db

    4 92 65 10/25/11 /data/user/0/com.xxx.xxx/databases/ttopensdk.db

    4 20 12 0/15/1 /data/user/0/com.xxx.xxx/databases/npth_log.db

    4 20 87 1/18/2 /data/user/0/com.xxx.xxx/databases/bytedance_downloader.db

    复制代码

    下面解读一下这份数据里的关键数据。

    首先了解两个概念:

    私有内存(Dirty and Clean) RAM:

    进程独占内存,也就是进程销毁时可以回收的内存容量。

    通常 Private Dirty 内存是最重要的部分,因为只被自己进程使用。

    Dirty 内存是已经被修改的内存页,因此必须常驻内存(因为没有swap)。

    Clean内存是已经映射持久文件使用的内存页(例如正在被执行的代码),因此一段时间不使用的话就可以置换出去。

    所有的 Dalvik Heap 和 Native Heap 都属于 Private Dirty,

    与 Zygote 进程共享的 Dalvik Heap和 NativeHeap 则是共享 Dirty.

    实际使用内存(PSS)

    将跨进程共享页也加入进来,进行按比例计算 PSS。这样能够比较准确的表示进程占用的实际物理内存。

    PASS衡量的一个优点是,你可以将所有进程的 PSS 加起来,确定所有进程占用的实际内存。这表示 PSS 是一种理想的方式,来衡量进程的 实际 RAM 占用比重,以及相对于其他进程和可用的总 RAM 而言,对 RAM 的占用情况。

    通常我们需要关注的是 PASS TOTAL 和 Private Dirty . 在某些情况下,Private Clean 和 Heap Alloc 列提供的数据也值得关注。下面列出了关于不同的内存分配(各行)需要关注的其他信息:

    Dalvik Heap

    Dalvik 虚拟机分配的内存。PSS Total 包含所有 Zygote 分配使用的内存,共享跨进程加权。

    Private Dirty 是应用独占内存大小,包含独自分配的部分和引用进程从 Zygote 复制时被修改的 Zygote 分配的内存页。

    Heap Alloc 是 Dalvik Heap 和 Native Heap 分配使用的大小,它的值比 Pss Total 和 Private Dirty 大,因为进程是从 Zygote 中复制分裂出来的,包含了进程共享的分配部分。

    .so mmap & .dex mmap ...mmap

    这些 mmap 概括一句话就是:映射本地或虚拟机代码到使用的内存中。

    .so mmap 和 .dex mmap

    映射的 .so(原生) 和 .dex(Dalvik 或 ART) 代码占用的 RAM 。 Pss Total 值包括应用之间共享的平台代码。

    Private Clean 是应用自己的代码,通常实际映射的内存容量要大的多。此处的 RAM 只是应用已执行的代码当前需要占用的 RAM。

    不过,.so mmap 具有较大的 Private Dirty RAM,这是因为在将其加载到最终地址时,对原生代码进行了修复。

    .oat mmap

    这是Heap 映像(Image)占用的 RAM 容量,根据多个应用共用的预加载类计算。此映像(Image)在所有应用之间共享,不受特定应用影响。

    .art mmap

    这是 Heap 映像(Image) 占用的 RAM 容量,根据由多个应用共用的预加载类计算,此映像(Image)在所有应用之间共享,不受特定应用影响。尽管 ART 映像(Image)包含 Object 实例,但它不会计入您的堆(Heap)占用空间。

    Unknown

    系统无法将其分类到其他更具体的一个项目中的 任何 RAM 页。当前,此类 RAM 页主要包含原生分配,由于地址空间布局随机化(ASLR),工具在收集此数据时无法识别这些分配。与 Dalvik Heap(堆) 相同,Unknown 的 Pass Total 考虑了与 Zygote 共享的容量,且 Private Dirty 是仅由您的应用占用的未知的 RAM.

    TOTAL

    您的进程占用的按比例分摊的内存大小(PSS)RAM 总容量,等于上述所有 PSS 字段的综合。该值表示了您的进程占用的内存容量占总体内存容量的币种,可以直接与其他进程和可用的总 RAM 进行比较。

    Private Dirty 和 Private Clean 合起来就是您进程中的总分配,这些分配未与其他进程共享。这些分配(尤其是 Private Dirty)的容量等于进程销毁后将释放到系统中的 RAM 容量。Private RAM 页由于已经被修改过,因此必须保留在 RAM 中(因为没有swap)。

    Clean RAM 页是从某个持久性文件(例如正在执行的代码)映射而来的,因此如果暂时不适用,可以将其置换出 RAM.

    ViewRootImpl

    您的进程中当前处于活动状态的根视图数量。每个根视图斗鱼一个窗口关联,因此该值有助于您确定与对话框或其他窗口有关的内存泄漏。

    AppContexts 和 Activities

    您的进程中当前处于活动状态的应用 Context 和 Activity 对象数量,该值可以帮助您快速确定发生泄漏的 Activity 对象,这些对象由于存在对其的静态引用(比较常见)而无法进行垃圾回收。这些对象往往关联了许多其他分配,因此是查找大型内存泄露的理想工具。

    通过 Pss Total 我们可以看出游戏当前占用内存大约是 341M 左右(跟top有浮动是因为游戏阶段不同)。

    综上所述,推荐使用 adb dumpsys mem 的方式查看内存占用,熟悉Linux但对Android不太熟悉的,可以尝试用 top 命令。

    参考资料:

    展开全文
  • 图片占内存多少的计算原理

    前置概念-屏幕密度

    搞清楚 DisplayMetrics 的两个变量,
    density 是显示的逻辑密度,是密度与独立像素单元的比例因子,
    densityDpi 是屏幕每英寸对应多少个点

    关于DisplayMetrics更多细节点击这里

    图片占内存多少的计算原理

    找到每个像素占用的字节数*总像素数即可

    Android API 有个方便的方法可以获取到占用的内存大小

    public final int getByteCount() {
        // int result permits bitmaps up to 46,340 x 46,340
        return getRowBytes() * getHeight();
    } 
    

    getHeight 就是图片的高度(单位:px)
    那么getrowBytes()呢

    public final int getrowBytes() {
       if (mRecycled) {
              Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
       }
       return nativeRowBytes(mFinalizer.mNativeBitmap);
    }
    
    #Bitmap.cpp
    static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) {
         SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle)
         return static_cast<jint>(bitmap->rowBytes());
    }
    
    #SkBitmap.h
    /** Return the number of bytes between subsequent rows of the bitmap. */
    size_t rowBytes() const { return fRowBytes; }
    
    # SkBitmap.cpp
    size_t SkBitmap::ComputeRowBytes(Config c, int width) {
        return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
     }
     
    # SkImageInfo.h
    static int SkColorTypeBytesPerPixel(SkColorType ct) {
       static const uint8_t gSize[] = {
        0,  // Unknown
        1,  // Alpha_8
        2,  // RGB_565
        2,  // ARGB_4444
        4,  // RGBA_8888
        4,  // BGRA_8888
        1,  // kIndex_8
      };
      SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1),
                    size_mismatch_with_SkColorType_enum);
    
       SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize));
       return gSize[ct];
    }
    
    static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
        return width * SkColorTypeBytesPerPixel(ct);
    }
    
    

    ARGB_8888(也就是我们最常用的 Bitmap 的格式)的一个像素占用 4byte,rowBytes 实际上就是 4*width bytes.

    ARGB_8888 的 Bitmap 占用内存的计算方式为 b i t m a p I n R a m = b i t m a p W i d t h ∗ b i t m a p H e i g h t ∗ 4 b y t e s bitmapInRam = bitmapWidth*bitmapHeight *4 bytes bitmapInRam=bitmapWidthbitmapHeight4bytes

    一张522*686的 PNG 图片,把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B,就可以用这个方法获取到。

    然而公式计算出来1432368B

    density影响内存占用

    Bitmap占用空间的大小不止和图片的宽高有关,还与密度因子有关。

    读取的是 drawable 目录下面的图片,用的是 decodeResource 方法,该方法本质上就两步:

    • 读取原始资源,这个调用了 Resource.openRawResource 方法,这个方法调用完成之后会对 TypedValue 进行赋值,其中包含了原始资源的 density 等信息;

    • 调用 decodeResourceStream 对原始资源进行解码和适配。这个过程实际上就是原始资源的 density 到屏幕 density 的一个映射。

    原始资源的 density 其实取决于资源存放的目录(比如 xxhdpi 对应的是480),而屏幕 density 的赋值,

    ### BitmapFactory.java
    
    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) {
    
    //实际上,我们这里的opts是null的,所以在这里初始化。
    if (opts == null) {
        opts = new Options();
    }
    
    if (opts.inDensity == 0 && value != null) {
    	//密度等于TypedValue.DENSITY_NONE,那么就没有与资源相关的密度,它不应该被缩放
        final int density = value.density;
        if (density == TypedValue.DENSITY_DEFAULT) {
        	//密度等于这个值,那么这个密度应该被视为系统的默认密度值
            opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;//默认密度160
        } else if (density != TypedValue.DENSITY_NONE) {//不等于此值需要缩放
            opts.inDensity = density; //这里density的值如果对应资源目录为hdpi的话,就是240
        }
    }
    
    if (opts.inTargetDensity == 0 && res != null) {
    	//inTargetDensity就是当前的手机的密度,比如三星s6时就是640
        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
    }
    
    return decodeStream(is, pad, opts);
    }
    
    

    我们重点关注两个值 inDensity 和 inTargetDensity,他们与BitmapFactory.cpp文件里面的 density 和 targetDensity相对应
    inDensity 就是原始资源的 density,inTargetDensity 就是屏幕的 density。
    接着,用到了 nativeDecodeStream 方法,其中最关键的 doDecode 函数的代码:

    static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    
    ......
        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
            const int density = env->GetIntField(options, gOptions_densityFieldID);//对应hdpi的时候,是240
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的为640
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density;
            }
        }
    }
    
    const bool willScale = scale != 1.0f;
    ......
    SkBitmap decodingBitmap;
    if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
       return nullObjectReturn("decoder->decode returned false");
    }
    //这里这个decodingBitmap就是解码出来的bitmap,大小是图片原始的大小
    int scaledWidth = decodingBitmap.width();
    int scaledHeight = decodingBitmap.height();
    if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
        scaledWidth = int(scaledWidth * scale + 0.5f);
        scaledHeight = int(scaledHeight * scale + 0.5f);
    }
    if (willScale) {
        const float sx = scaledWidth / float(decodingBitmap.width());
        const float sy = scaledHeight / float(decodingBitmap.height());
    
        // TODO: avoid copying when scaled size equals decodingBitmap size
        SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
        // FIXME: If the alphaType is kUnpremul and the image has alpha, the
        // colors may not be correct, since Skia does not yet support drawing
        // to/from unpremultiplied bitmaps.
        outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
                colorType, decodingBitmap.alphaType()));
        if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
            return nullObjectReturn("allocation failed for scaled bitmap");
        }
    
        // If outputBitmap's pixels are newly allocated by Java, there is no need
        // to erase to 0, since the pixels were initialized to 0.
        if (outputAllocator != &javaAllocator) {
            outputBitmap->eraseColor(0);
        }
    
        SkPaint paint;
        paint.setFilterLevel(SkPaint::kLow_FilterLevel);
    
        SkCanvas canvas(*outputBitmap);
        canvas.scale(sx, sy);
        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
    }
    ......
    }
    

    density 其实是decodingBitmap的 densityDpi ,跟这张图片的放置的目录有关(比如 hdpi 是240,xxhdpi 是480),
    targetDensity 实际上是我们加载图片的目标 densityDpi,三星s6为640。sx 和sy 实际上是约等于 scale 的,因为 scaledWidth 和 scaledHeight 是由 width 和 height 乘以 scale 得到的。我们看到 Canvas 放大了 scale 倍,然后又把读到内存的这张 bitmap 画上去,相当于把这张 bitmap 放大了 scale 倍。

    所以回到上面
    一张522*686的PNG 图片,我把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B,其中 density 对应 xxhdpi 为480,targetDensity 对应三星s6的密度为640:

    522/480 * 640 * 686/480 *640 * 4 = 2546432B

    值还是不一样

    精度影响内存占用

    outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
                colorType, decodingBitmap.alphaType()));
    

    最终输出的 outputBitmap 的大小是scaledWidth*scaledHeight,

    if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
        scaledWidth = int(scaledWidth * scale + 0.5f);
        scaledHeight = int(scaledHeight * scale + 0.5f);
    }
    

    在我们的例子中,

    scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696

    scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915

    915 * 696 * 4 = 2547360

    Bitmap 在内存当中占用的大小的影响因素

    • 色彩格式,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节

    • 原始文件存放的资源目录

    • 目标屏幕的密度

    如何优化

    知道了原因,那么据此即可优化内存使用。
    详情可以查看优化Bitmap内存占用

    展开全文
  • 1.内存占用对于智能手机而言,内存大小是固定的;因此,如果单个app的内存占用越小,手机上可以安装运行的app就越多;或者说app的内存占用越小,在手机上运行就会越流畅。所以说,内存占用的大小,也是考量app性能的...
  • 对于一张图片,你知道应该存放在那个资源目录下面吗,或者说,放在那个资源目录下加载起来更省内存呢?在日常开发中我们可能不太注意这些东西,但是这些却是基础,是必不可少的一环,所以这几天重新温习了一下并整理...
  • Android中图片占用内存大小

    千次阅读 2021-11-11 15:40:31
    同一张图片,会出现许多文件大小值。这是为什么? 图片如下,是一张jpg图片: Android studio显示:304.6875KB 使用Glide获取Bitmap,打印图片占内存的大小: public class MainActivity extends AppCompatActivity...
  • Bitmap的内存占用检测
  • 一、Bitmap 内存占用、 二、Bitmap 内存占用计算示例、 三、Bitmap 内存占用与像素密度、 四、Bitmap 内存占用与像素密度示例、
  • 这里讨论的是占用内存大小,跟存储文件大小不是一个概念。 以下是Bitmap提供的两个获取图片占用内存大小的方法, public final int getByteCount(); public final int getAllocationByteCount(); (1)getByteCount...
  • Android OOM 分析

    2021-05-26 16:33:49
    前言在内存使用过程中使用不当或者...为了整个Android系统的内存控制需要,Android系统为每一个应用程序都设置了一个硬性的Dalvik Heap Size最大限制阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如...
  • 研究内容本篇内容主要探讨以下场景:同一张图片,放置在不同的drawable文件夹,在同一设备上运行,对图片大小及内存占用有什么影响。研究方法控制变量法分析法测试环境采用锤子T1手机(1080*1960,x...
  • Android ANR log trace 日志文件分析Trace 文件怎么获取?traces.txt 只保留最后一次 ANR 的信息, Android 系统有个 DropBox 功能功能, 它能记录系统出现的 crash 错误. 因此保留有发生过的 ANR 的信息.(log 路径:/...
  • Android中一张图片占用内存大小

    千次阅读 2020-02-23 12:11:47
    解释:此处说的占据内存,是APP加载图片用的内存,即APP运行时图片占用内存,不考虑图片显示到界面占用内存(即只加载图片,不显示。因为显示图片时,系统有可能根据控件的大小或其他因素对内存进行了优化,不...
  • 图片在内存中的存储基于位图模式(通常...Android的应用运行在JVM虚拟机上,每个JVM虚拟机进程分配的内存有限,这样才能保证多个应用同时运行时每个程序都有机会执行。系统究竟为每个应用分配的内存值大小是多少,Ja...
  • 一张图片的文件可能只有几KB、十几KB或几十KB,但在内存中解码后,所占用内存空间是多大呢?本篇文章将提供图片内存占用大小的计算与验证方法。文章内容:Android中一张图片(Bitmap)占用内存由以下三个因素决定...
  • mysql占用内存过多

    2021-01-18 18:28:12
    一、计算mysql所需的内存https://www.cnblogs.com/cheyunhua/p/9045057.html 理论有待学习...
  • android 图片占用内存大小及加载解析

    万次阅读 多人点赞 2017-04-01 16:59:24
    在讲解图片占用内存前,我们先问自己几个问题: 我们在对手机进行屏幕适时,常想可不可以只切一套图适配所有的手机呢? 一张图片加载到手机中,占用内存到底有多少? 图片占用内存跟哪些东西有关?跟手机有关系么?...
  • 1.内存信息在proc/meminfo下有详细的内存使用情况,我这里获取的内存信息就是从这个文件里获取的.获取到详细的内存信息后根据我自己的需求,从bufferdreader中单独抽取出来了剩余的内存容量.Runtime runtime = Runtime...
  • android 内存泄漏问题

    2021-05-26 13:52:31
    内存泄露问题在一些压力测试的场景很容易暴露,...②文件泄露,导致进程空间文件句柄数达到最大值。③线程泄露,导致进程空间虚拟地址被分配完,进程内保留很多线程栈(stack)。Android Native层中大部分的泄露问题都...
  • Android如何优化Bitmap内存占用

    千次阅读 2021-03-19 15:36:05
    如何优化bitmap的内存占用
  • 一、作用知其然二、概念1. 几个小概念像素:组成画面的基本单位,像素没有物理尺寸。可以任意缩放去适配显示屏的像素点大小。...px=dp*dpi/160dpi/160得到基准比例求以dp布局的 TextView 占用的实际像...
  • 毫无疑问,太尼玛吃内存了这玩意,Android手机本来内存就这么点,而且还是多个应用共享,稍微不注意给你送个惊喜,来个OutofMemoryError,不要太酸爽哦,所以作为移动软件开发者,解决好Bitmap是一个必修课,今天...
  • 图片放在assests和磁盘里加载的话占用内存大小计算方式为1024 768 4 图片放在不同密度对应的文件夹时占用内存大小为1024 密度值/260 768 密度值/260 4 我们看到在不同密度值代表的文件夹的图片会经过一次分辨率...
  • Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.weixin, options); //Bitmap bitmap = BitmapFactory.decodeFile(“mnt/...Log.i("!!!", “bitmap:ByteCount = " + bitmap.getByteCou
  • 最近在学习Binder的东西,发现Binder对跨进程传输文件的大小有要求,系统的Binder传输文件大小的时候限制在1M左右,太大的文件会导致内存溢出,导致跨进程传输失败,当然实现大文件传输的时候我们也可以使用广播,当别人发...
  • 1.内存占用对于智能手机而言,内存大小是固定的;因此,如果单个 app 的内存占用越小,手机上可以安装运行的 app 就越多;或者说 app 的内存占用越小,在手机上运行就会越流畅。所以说,内存占用的大小,也是考量 ...
  • 1.内存信息在proc/meminfo下有详细的内存使用情况,我这里获取的内存信息就是从这个文件里获取的.获取到详细的内存信息后根据我自己的需求,从bufferdreader中单独抽取出来了剩余的内存容量.Runtime runtime = Runtime...
  • Android内存裁剪

    2021-06-10 12:42:33
    底层内存裁剪的一些思路:主要思路是针对功能需求,裁剪冗余...USB的多余外设支持,FS的多余支持2、缩减reserved的内存占用从dts中声明 reserved 或代码中申请reserved的部分下手3、缩减未进入内存管理的内存占用找出 ...
  • 功能:获取android设备中某一个app的cpu和内存 环境:python和adb 使用方法:使用adb连接android设备,打开将要测试的app,执行cpu/内存代码 cpu获取代码如下:(输入参数为脚本执行时间) # coding:utf-8 ''' ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,212
精华内容 7,684
关键字:

安卓log文件占用内存