美文网首页性能优化
Android-使用profiler让App的内存泄漏原形毕露

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

作者: Czppp | 来源:发表于2019-04-26 12:04 被阅读20次

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

    本篇文章主要介绍内存泄漏以及Android Studio中profile的使用,会举栗子介绍profile使用方法,让内存泄漏原形毕露。。。

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

    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项目已经处理好了,接下来可以看一段动画休息一下,看完了记得动手操作哦。以及有什么地方描述有错误或者其他问题希望大家可以反馈,谢谢

    16932f9c69005dfb.gif

    相关文章

      网友评论

        本文标题:Android-使用profiler让App的内存泄漏原形毕露

        本文链接:https://www.haomeiwen.com/subject/lghfnqtx.html