内存优化(二) ---- Android APP性能优化

由于Android应用的沙箱机制,每个应用所分配的内存大小是有限度的,因此内存会显得非常珍贵,如果我们的内存占用超过了一定的水平就会出现OutOfMemory错误

内存概述

RAM(random access memory)随机存取存储器.(通俗的说就是内存)

  • Java的内存分配策略:
Java内存分配时会涉及到以下区域:
栈(Stack):一些基本类型的变量和对象的引用都是在栈内存中分配,当超过变量的作用域后,java会自动释放该变量分配的内存(对象本身不存放在栈中,而是存放在堆中)
堆(Heap): 通常用来存放new出来的对象和数组,由java垃圾回收器回收.
静态存储区(static field): 编译时就分配好,在程序整个运行期间都存在.它主要存放静态数据和常量

还一个CPU存储区:
寄存器(Registers): 速度最快的存储场所,因为寄存器位于处理器内部,我们在程序中无法控制
  • 堆栈的特点:

栈:

定义一个变量时,Java在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放为该变量所分配的内存空间.

栈的存取速度比堆要快,仅次于寄存器.但是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性

栈中的数据可以共享,它是由编译器完成的,有利于节省空间

例如:需要定义两个变量int a = 3;int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没有,就将3存放进来再将a指向3.
接着处理int b = 3,创建完b的引用变量后在栈中已经有3这个值,便将b直接指向3.这样,就出现了a与b同时均指向3的情况.
这时,如果再让a=4,那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并让a指向4.
如果已经有了,则直接将a指向这个地址.因此a值的改变不会影响到b的值。

堆:

当堆中通过new产生数组和对象超出其作用域后,它们不会被释放,只有在没有引用变量指向它们的时候才变成垃圾,不能再被使用,并且只有等被垃圾回收器回收才回释放内存.这也是Java比较占内存的原因.

堆是一个运行时数据区,可以动态地分配内存大小,因此存取速度较慢.

如上例子,栈中a的修改并不会影响到b,而在堆中一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量

  • APP内存占用信息查询
    float max = Runtime.getRuntime().maxMemory() * 1.0f / (1024 * 1024);
    float total = Runtime.getRuntime().totalMemory() * 1.0f / (1024 * 1024);
    float free = Runtime.getRuntime().freeMemory() * 1.0f / (1024 * 1024);
查看系统设置单个进程的内存上限
C:\Users\Administrator>adb shell
sagit:/ $ getprop|grep heapgrowthlimit
[dalvik.vm.heapgrowthlimit]: [256m]
  • java中四种引用类型:
强引用(StrongReference):强引用是使用最普遍的引用(如:Object object=new Object(),object就是一个强引用了),
如果一个对象具有强引用内存不足时,宁抛异常OOM导致程序终止也不回收,也就是JVM停止时才终止

软引用(SoftReference):如果内存空间不足时,才会被回收(当内存达到一个阀值,GC就会去回收它)

弱引用(WeakReference):不管当前内存空间是否足够,在GC 时都会回收

虚引用(PhantomReference):顾名思义,就是形同虚设,任何时候都可能被GC回收(已经不用)
image

软引用实例:

    // 例子1:
    private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
    
    public void add(String path) {
        Bitmap bitmap = BitmapFactory.decodeFile(path); // 这里的bitmap属于强引用
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);   // 软引用的Bitmap对象
        imageCache.put(path, softBitmap);
    }
    
    public Bitmap get(String path) {
        SoftReference<Bitmap> softBitmap = imageCache.get(path);
        if (softBitmap == null) {
            return null;
        }
        return softBitmap.get();    // 取出软引用的Bitmap,如果内存不足被回收,获取为NUll  
    }
    
    public static Bitmap readBitmap(Context context, int resId) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
        InputStream is = context.getResources().openRawResource(resId);
        return BitmapFactory.decodeStream(is, null, opt);
    }
    
    // 例子2:
    static class MyHandler extends Handler {
        private SoftReference<Activity> reference;
    
        public MyHandler(Activity activity) {
            // 持有 Activity 的软引用
            reference = new SoftReference<Activity>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            Activity activity = reference.get();
            if (activity != null && !activity.isFinishing()) {
                switch (msg.what) {
                    // 处理消息
                }
            }
        }
    }
  • 垃圾回收机制:
垃圾回收是指清理在内存中不再需要的数据对象,以便大块内存可以重新分配给新的对
象。一般来说,一旦某个对象在 App 中没有一个活动的引用,就可以作为垃圾被回收了。
垃圾回收器会先从根部的对象开始(它知道这些对象是活动的并且正被进程所使用),并
且沿着每个引用去查找它们的关联。如果一个对象不在这个有效引用的列表中,那么它肯
定不会再被使用,就可以被回收了。此时,分配给这个对象的内存空间也可以回收了

内存优化

  • 内存泄漏

    内存泄漏是内存优化中最重要的部分

    Android内存泄露OOM的原因及解决方案

  • IntentService的使用

    IntentService是一种特殊的Service,继承自Service;用于在后台执行耗时的异步任务,当任务完成后会自动停止

    为什么使用IntentService?

    我们通常Service用法如下,也是标准用法:

    public class MyService extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread() {
                @Override
                public void run() {
                    // 处理耗时逻辑
                    stopSelf(); // 如需实现处理完自动停止功能,可这样做
                }
            }.start();
            return super.onStartCommand(intent, flags, startId);
        }
    }
如上写法并没有什么错误,但是需要写如上额外代码,同时 当业务逻辑复杂后会有Service停止失败导致内存泄漏的风险,Android官方推荐的最佳解决方案就是使用IntentService
    public class MyIntentService extends IntentService {
    
        public MyIntentService(String name) {
            super(name);
        }
    
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            // 处理耗时逻辑,处理完自动停止
        }
    }
内部通过HandlerThread和Handler实现异步操作,创建IntentService时,只需实现onHandleIntent和构造方法.onHandleIntent为异步方法,可执行耗时操作.
  • Bitmap优化

    Bitmap是内存消耗大户,是导致OMM最常见的原因之一

图片显示:

我们可以根据场景需求去加载图片的大小,例如列表中的小图我们可以只加载缩略图(thumbnails)

等比例压缩图片:

直接使用图片(bitmap)会占用较多资源,特别是图片较大的时候,可能导致崩溃,这时,我们可以使用BitmapFactory.Options设置inSampleSize.inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则获取图片的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4.
    BitmapFactory.Options options = new BitmapFactory.Options();
    // 该值设为true后将不返回实际的bitmap,也不给其分配内存空间.但允许我们查询图片的信息,计算出原始图片的长和宽
    options.inJustDecodeBounds = true; 
    //缩放的倍数,图片宽高都为原来的二分之一,即图片为原来的四分之一,SDK中建议该值为2,值越大会导致图片不清晰
    options.inSampleSize = 2;  
    options.inJustDecodeBounds = false;  
    Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);  

图片像素:

Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存 
ARGB_4444:每个像素占用2byte内存 
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存 

Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高.同时占用的内存也最大 所以对图片效果不是特别高的情况下可以使用RGB_565(565没有透明度属性)
    public static Bitmap readBitmap(Context context, int resId) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
        InputStream is = context.getResources().openRawResource(resId);
        return BitmapFactory.decodeStream(is, null, opt);
    }

图片回收:

使用Bitmap过后及时的调用Bitmap.recycle()方法来释放内存,不要等Android系统来进行释放
    if (bitmap != null && !bitmap.isRecycled()) {
        // 回收并且置为null
        bitmap.recycle();
        bitmap = null;
    }
    System.gc();

对图片采用软引用

SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);

捕获异常:

最坏的情况下不能导致程序崩溃,捕获OOM异常
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeFile(path);
    } catch (OutOfMemoryError e) {
        e.printStackTrace();
    }
    if (bitmap == null) {
        return defaultBitmap;
    }

相关链接直达:

Android APP性能优化之 ---- 布局优化(一)

Android APP性能优化之 ---- 内存优化(二)

Android APP性能优化之 ---- 代码优化(三)

Android APP性能优化之 ---- 优化监测工具(四)

Android APP性能优化之 ---- APK瘦身 App启动优化

Android内存泄露OOM异常处理优化

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容