Android-使用profiler让App的内存泄漏原形毕露

教育部强调十项严禁:严禁宣传中高考状元和升学率
《通知》强调,严格落实“十项严禁”纪律:严禁无计划、超计划组织招生;严禁自行组织或与社会培训机构联合组织以选拔生源为目的的各类考试,或采用社会培训机构自行组织的各类考试结果;严禁提前组织招生,变相“掐尖”选生源;严禁公办学校与民办学校混合招生、混合编班;严禁以高额物质奖励、虚假宣传等不正当手段招揽生源;严禁任何学校收取或变相收取与入学挂钩的“捐资助学款”;严禁义务教育阶段学校以各类竞赛证书、学科竞赛成绩或考级证明等作为招生依据;严禁义务教育阶段学校设立任何名义的重点班、快慢班;严禁初高中学校对学生进行中高考成绩排名、宣传中高考状元和升学率;严禁出现人籍分离、空挂学籍、学籍造假等现象,不得为违规跨区域招收的学生和违规转学学生办理学籍转接。

本篇文章主要介绍内存泄漏以及Android Studio中profile的使用,会举栗子介绍profile使用方法,让内存泄漏原形毕露。在本人之后的学习,发现profile还需要借用mat工具来检查对象的引用类型,才能判断某个界面是否存在内存泄漏的情况,下面资料以学习了解一下就可以了。

  • 什么是内存泄漏
  • 了解内存的分配
  • 成员变量和局部变量

1. 什么是内存泄漏

当一个对象已经不再需要使用,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。

2. 了解内存的分配

  • 静态的存储区:内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间一直存在
  • 栈:在执行函数(方法),函数的内部变量的都在栈内存中存储,当函数执行完毕自动释放栈内存
  • 堆:动态分配内存,使用new来申请分配一个内存,依赖GC回收机制,有的时候也需要自己释放内存。

3. 成员变量和局部变量

成员变量 --成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)---因为他们属于类,类对象最终还是要被new出来的。
1. 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
2. 当一个对象被实例化之后,每个实例变量的值就跟着确定;
3. 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
4. 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
5. 实例变量可以声明在使用前或者使用后;
6. 访问修饰符可以修饰实例变量;
7. 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
8. 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以 在声明时指定,也可以在构造方法中指定;
9. 实例变量可以直接通过变量名访问

局部变量--局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。
1.局部变量声明在方法、构造方法或者语句块中;
2.局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
3.访问修饰符不能用于局部变量;
4.局部变量只在声明它的方法、构造方法或者语句块中可见;
5.局部变量是在栈上分配的。
6.局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。


  • Android Studio profile基本使用说明
  • 用Mvp例子来展示profiler使用方法,快速定位内存泄漏class类

4. 关于profiler工具说明百度一下一大推,这边就随便复制一些单词解释吧,主要还是说之后例子中使用到数据说明。

image.png

如上图所示
Java表示Java代码或Kotlin代码分配的内存;
Native表示C或C++代码分配的内存(即使App没有native层,调用framework代码时,也有可能触发分配native内存);
Graphics表示图像相关缓存队列占用的内存;
Stack表示native和java占用的栈内存;
Code表示代码、资源文件、库文件等占用的内存;
Others表示无法明确分类的内存;
Allocated表示Java或Kotlin分配对象的数量(Android8.0以下时,仅统计Memory Profiler启动后,进程再分配的对象数量; 8.0以上时,由于系统内置了统计工具,Memory Profiler可以得到整个app启动后分配对象的数量)。

image.png
image.png

图中两个列表说明---等下会用到 App heapArrange by package

  • App heap: 应用程序分配内存的主堆。
  • Image heap: 系统引导映像,包含在引导期间预加载的类。这里的分配保证永远不会移动或离开。
  • Zygote heap: Android系统中分发应用程序进程的写时复制堆
  • Arrange by class: 基于类名称对所有分配进行分组
  • Arrange by package:基于软件包名称对所有分配进行分组。
  • Arrange by callstack: 根据调用堆栈排序

大概看一下下面的图,了解生成内存文件的结构 补充一下说明

  • Allocations: 对象实例个数
  • Shallow Size:表示对象使用Java内存的大小,单位为byte;
  • Retained Size:表示对象占用的实际内存大小,大于等于Shallow Size
image.png

5.基本差不多了,下面通过Mvp项目的例子来展示ProFiler的用法以及定位内存泄漏

我的视频3 [320i].gif
public class LoginActivity extends AppCompatActivity implements LoginContract.LoginView, View.OnClickListener {

    private LoginPresenterImpl loginPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_activity);
        Button btnLogin = findViewById(R.id.btn_login);
        Button btnFinish = findViewById(R.id.btn_finish);
        btnLogin.setOnClickListener(this);
        btnFinish.setOnClickListener(this);
        loginPresenter = new LoginPresenterImpl();
        loginPresenter.attachView(this);
    }
    @Override
    public void loginSuc() {
        Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void LoginFail() {
        Toast.makeText(this, "登陆失败", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:
                loginPresenter.login("", "");
                break;
            case R.id.btn_finish:
                finish();
                break;
        }
    }
}
public class LoginPresenterImpl extends LoginContract.LoginPresenter {

    @Override
    public void login(String name, String psw) {
        RxManage.getInstance().registered(Observable
                .just("1")
                .compose(new ObservableTransformer<String, String>() {
                    @Override
                    public ObservableSource<String> apply(Observable<String> upstream) {
                        return upstream.subscribeOn(Schedulers.io())
                                .observeOn(AndroidSchedulers.mainThread());
                    }
                })
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        mView.loginSuc();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        mView.loginSuc();
                    }
                })
        );
    }
}

现在我们就检查一下程序的有没有内存泄漏--答案肯定有啦。直接使用profiler工具分析看看内存泄漏的地方。


image.png
image.png

先看android的app这个包的Api,为什么呢?
这个包里面包含了App的启动流程使用到的类,还有Activity和Fragment类。那样对于查看Activity/Fragment 被销毁之后是否还存在实例判断这个Activity/Fragment 是否有内存泄漏的情况,下面直接看图了

image.png

从图中可以看出,Activity存在两个实例,说明LoginActivity被Presenter持有,右键点击this$0={loginActivity} 选择点击go to instance

image.png

图中红框是表示这个App在内存中存在的实例对象本应该LoginActivity销毁之后LoginActivity和LoginPresenterImpl应该要被回收,结果没有。那样就分析出来内存泄漏的类,然后根据自己的经验去处理这个内存泄漏。在LoginActivity的onDestroy()告诉gc回收LoginPresenterImpl,之后再用profile跑下

  @Override
    protected void onDestroy() {
        super.onDestroy();
        loginPresenter.detachView();
        loginPresenter = null;
    }
image.png

结果很明细,LoginActivity被回收了。是不是LoginActivity被回收了已经没有了内存泄漏了呢?下面进行下个检查了,从App程序包(当前App的包名)中查看存在的对象着手。


image.png
  • Depth:从任意 GC 根到所选实例的最短 hop 数。
  • Shallow Size:此实例的大小。
  • Retained Size:此实例支配的内存大小

再次查看App的实例对象,很遗憾LoginPresenterImpl没有被回收,看到mView=null已经回收了,再看下面的references可以判断出应该是Rxjava没有回收。在RxManage的生命周期比LoginPresenterImpl长,LoginPresenterImpl在login()创建了Disposable对象并存放在RxManage的compositeDisposable中,在LoginPresenterImpl销毁时,RxManage还持有LoginPresenterImpl的Disposable对象的引用,所以没有能回收

public class RxManage {

    private static volatile RxManage rxManage;
    private CompositeDisposable compositeDisposable = new CompositeDisposable();

    public static RxManage getInstance() {
        if (rxManage == null) {
            synchronized (RxManage.class) {
                if (rxManage == null) {
                    rxManage = new RxManage();
                }
            }
        }
        return rxManage;
    }

    public void registered(Disposable disposable) {
        compositeDisposable.add(disposable);
    }

    public void unSubscribe(){
        compositeDisposable.clear();
    }
}

在LoginPresenterImpl类中重写detachView()处理一下就可以了

 @Override
    public void detachView() {
        super.detachView();
        RxManage.getInstance().unSubscribe();
    }

为了验证再profile一下,要走一遍流程哦


我的视频3 [320i].gif
image.png

image.png

从图中看到LoginActivityLoginPresenterImpl两个类已经没有存在堆中了,这一个内存泄漏的Mvp项目已经处理好了,接下来可以看一段动画休息一下,看完了记得动手操作哦。以及有什么地方描述有错误或者其他问题希望大家可以反馈,谢谢

在ProFiler生成的Heap Dump保存hprof,通过Android\Sdk\platform-tools\hprof-conv文件转行之后可以使用mat工具来分析内存

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

推荐阅读更多精彩内容