Android优化(瘦身+代码+内存)

原文链接:https://yuchao.wang/article?id=37

应用瘦身

  • 首先打包 xxx.apk
  • Build -> Analyze APK 导入 xxx.apk 后会发现如下图
  • 然后我们会大致分析一下应用的文件大小是否正常,将未用到的大文件清理
  • 点击DEX文件,还可以查看类个数和方法个数,这里如果应用达到了64k的限制,那么我们则需要将应用拆分,插件化处理。
  • 点击DEX文件,我们会看到包名和类名,分析靠前的包是否需要。(有时候我们会引用到相似功能的第三方库,但是我们只用了一个,删除不必要的类库,当然也可以直接在gradle里面进行分析)
  • 想知道跟老版本的区别,可以点击右上角的 Compare with
  • 去掉无用资源以及类库后,最后重新打包即可
应用瘦身
应用瘦身

代码分析

  • Analyze -> Inspect Code 我们会发现下图
  • 不要被问题数吓到,可能有很多是类库自己的问题,我们只需要分析自己应用的问题即可。
  • 大部分都是比较简单的容易处理的小问题,然后有些提示其实也没必要改动,一般就是用来优化代码执行速率以及减少内存开销的一些问题。
  • 最方便的一点就是,点开每一个问题,右侧都给出了问题的定位、描述、解决办法。我们能从这些提示中学到不少关于代码优化的小技巧呢。
代码分析
代码分析

内存优化

内存优化是Android应用优化最重要的一部分

内存分配

  • 静态区:主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
  • 栈区 :当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 堆区 :又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

内存泄露

  • 直观表述:只要长的生命周期引用一个比他生命同期短的对象就可能内存泄漏
  • 根本原因:内存中有些对象的引用还存在,但是却没有什么用处,GC没有办法释放,因此还占用内存。
  • 表现形式:A对象引用B对象,A对象的生命周期(t1-t4)比B对象的生命周期(t2-t3)长的多。当B对象没有被应用程序使用之后,A对象仍然在引用着B对象。这样,垃圾回收器就没办法将B对象从内存中移除,从而导致内存问题。

常见情景

  • 集合类:Map or List
    public static void main(String[] args) {
        ArrayList<Student> arrayList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Student student = new Student();
            student.name = i + "";
            arrayList.add(student);
            if (i == 2) {
                student = null;//没有释放内存,造成内存泄露
            }
        }
        System.out.println(arrayList.toString());
        arrayList.set(3, null);// 正确释放内存
        System.out.println(arrayList.toString());
    }
    
    // 结果如下
    [Student{name='0'}, Student{name='1'}, Student{name='2'}, Student{name='3'}, Student{name='4'}]
    [Student{name='0'}, Student{name='1'}, Student{name='2'}, null, Student{name='4'}]
  • 非静态内部类创建静态实例:因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确做法是将Test内部类抽取出来或者设置为静态内部类。
public class Activity {
    private static Test test = null;

    class Test {
    }

    public void onCreate() {
        if (test == null) {
            test = new Test();
        }
    }
}
  • 匿名内部类:匿名内部类会持有外部类的一个引用,因此不要再将这个引用,传入异步线程。否则一定会造成内存泄露。

  • static:跟应用的生命周期是一样长。永远不要创建静态的 Context 或 View 对象,或者将二者存储于静态变量中。

  • Context:能传Context尽量不要传 Activity 。避免在 Activity 或 Fragment 之外传递 Context 对象。尽量使用 ApplicationContext 而不是Activity;

  • 异步任务:Handler+Thread 或者 AsyncTask 。

  • 不要在 AsyncTasks(异步任务)或后台线程中存储指向 activities 的强引用。Activity 可能会关闭,但是 AsyncTask 会继续执行,一直保存着对该 activity 的引用。
  • 如果我们一定需要传入 Activity (Dialog,Progress 之类)来进更改UI,可以使用 WeakReference 持有 Activity 的引用,每次使用前注意判空即可。
  • Handler 造成内存泄露很常见,推荐使用静态内部类 + WeakReference 这种方式。具体请看WeakHandler
  • Handler 还可以使用 removeCallbacks 和 removeMessages 移除
  • 资源未关闭:对于使用了 Receiver Stream Observer File Cursor 等资源,应该在Activity销毁时及时关闭或者注销,回收内存。

Android Monitors

Monitors
Monitors

点击Dump Java Heap 会自动打开 .hprof文件,可以查看如下图所示

hprof文件分析
hprof文件分析
hprof文件分析
hprof文件分析
hprof文件分析
hprof文件分析

那么我们怎么才能发现内存泄露呢?这只是显示了一些基础的保留堆栈、总栈、层次、深度之类的参数,我们只能分析比较浅显容易看出来的内存泄露,比如某一个Bitmap占用了大量的内存,然后就可以 jump to source 。如果想要详细分析应用内存,这时就需要用到MAT了。

MAT 分析跟踪

  • 下载地址
  • 首先将 .hprof 文件导出为标准文件 ,然后使用 MAT 打开,选择 leak suspect report ,
hprof文件分析
hprof文件分析
MAT
MAT
MAT
MAT
MAT
MAT

可以根据上图中引用个数判断是否内存泄露,如果引用个数过多,可能就会造成内存泄露。我们点击Histogram 查看一下 ArrayList

MAT
MAT
MAT
MAT

通过对包名weiboyi的搜索发现,util 包中 GsonUtil 可能会有内存泄露,因为引用数量太多了。

下面我们再搜索一下activity,看是否存在Activity的泄露。可能我这运行次数比较少,目前还没有发现。

MAT
MAT

以上都仅仅是比较简单的用法,MAT的更多功能只能慢慢探索。

LeakCanary

如果你厌倦了上述的检测内存泄露的繁琐方式,那么现在有个开源类库LeakCanary,可以直接让内存泄露无所遁形。

具体的说明请看官网吧,LeakCanary中文说明在这

参考

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,295评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 6,939评论 0 62
  • 被文同时发布在CSDN上,欢迎查看。 APP内存的使用,是评价一款应用性能高低的一个重要指标。虽然现在智能手机的内...
    大圣代阅读 4,782评论 2 54
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 心若年轻,天地不老;心若老去,人生荒年。岁月,不是因经历而遗憾,而是因沧桑而丰盈! 岁月匆匆,不得不叹息时间如流水...
    司空绝枫阅读 261评论 0 0