Android内存分析demo
1.前言
这段时间打算换工作,所以没有负责新的项目,主要为已有项目加注释和文档,刚好空余了点时间就想对以前项目遇到的一个问题做
一个分析,项目有一个需求是acticity保持不被销毁,结果在低配置手机上出现了问题.
刚好这几天看到android studio在1.5开始支持进行内存分析,所以对这方面知识做个梳理
2.一些思考
不知道大家有没有考虑过一个问题,在android中actiity service这种可以保持长时间运行的组件是不允许new对象的,而
广播如果你运行超过10秒就会出现anr异常,所以也就没这方面的问题.我觉得有一方面的考录就是为了使组件之间相互隔离,从而就可以
使用隐式意图启动.
结合这一点,那么我们对内存分析的意义出发点就可以确定的,我们知道,一个对象能不能被回收取决于有没有引用指向它,当然
还要考虑循环引用的问题,这一点垃圾回收算法也会认为是可回收的.所以我们队内存泄露的检测就有了一个大概的路劲了.
3.内存分析的流程
既然我们知道了activity是不允许new的,那么当我们启动一个activity的时候,这个activity的引用是应用层框架所持有的,
理论上,当我们activity的destory方法执行后,如果这个activity没有销毁,那么我们就可以认为发生了这个activity发生了内存泄露.
既然activit发生了内存泄露,那么这个activity对象就没办法被回收,里面的view、比如说mvp中presenter层所引用的对象
,model层对象也就都没办法被回收,这样的代价难免有点大.所以我们这里分析主要从activity开始。
4.开工
先卡看我们的项目目录结构
在这里我们采用的是mvp。
- M MODEL,这里我们主要进行数据的处理,比如说从网络、数据库
- V View,这里是用户界面了,这里我们专注于动作,比如说从用户界面获取用户名,获取密码,
P Presenter,在这里我们专注于逻辑,用于连接view和model我们可以看看我们presenter层的代码。
代码很少,检测用户名、密码成功就执行登录,同时回调view界面的preparLogin()方法用于显示登录前的提醒
而view层每一个方法都专注于一类事情比如说我们这里
再来看看我们实现类
public class LoginActivity extends AppCompatActivity implements ILoginActivity {private TextView userName; private TextView password; private Button loginButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); userName = (TextView) findViewById(R.id.email); password = (TextView) findViewById(R.id.password); loginButton = (Button) findViewById(R.id.email_sign_in_button); final Presenter presenter = new Presenter(this, this); loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.login(); } }); } @Override public String getUserName() { return userName.getText().toString(); } @Override public String getPassword() { return password.getText().toString(); } @Override public boolean checkUserName() { if (getUserName().length() > 0) { return true; } else { Toast.makeText(this, "用户名不合法", Toast.LENGTH_SHORT).show(); return false; } } @Override public boolean checkPassword() { if (getUserName().length() > 0) { return true; } else { Toast.makeText(this, "密码不合法", Toast.LENGTH_SHORT).show(); return false; } } @Override public void preparLogin() { Toast.makeText(this, "显示进度条", Toast.LENGTH_SHORT).show(); } @Override public void LoginFinish() { Toast.makeText(this, "关闭进度条" + "进入主页", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); } @Override public void LoginFailed(String massage) { Toast.makeText(this, "登录失败了,失败原因是" + massage, Toast.LENGTH_SHORT).show(); }
}
大概思路,用户点击登录,判断用户名密码,如果用户名或者密码不合法进行提醒,如果合法登录,登录成功进入主页,同时关闭页面
model层只有一个asyncTask延时5秒返回登录成功我们不在阐述了。
handle导致内存泄露
引起activity出现内存泄露的原因有很多,这里我们看一个我们比较容易模拟的问题
上面图中我们可以看到,代码出现警告了,提示我们使用静态的,原来我们非静态的内部类会持有外部类的引用,而handel我们发送
了一个延时消息,handle要成功执行handleMassage方法,那么必然的消息队列中会持有handle的引用,从而就造成的activity
不能被成功销毁,。
我们结合memory monitor来看看我们的代码
* 1.进入loginActivity点击登录,登录成功进入主页,同时finish当前页面。
* 2.这时候我们到下面页面,点击箭头所指向的小车,手动的启动一次垃圾回收
* 1.导出java堆的详细日志
* 2 and 3.按照包名找到我们的activity
* 4.这个loginActivity.class只有一个实例,但是我们已经finish()同时在测试前启动了垃圾回收,照理说他应该被回收
* 5.既然activity没有被回收,我们看看谁持有他的引用,android.os.messageQueue,是一个消息队列引用着他,
也就是我们的handel,当然关于handel的运行原理大家可以移步
总结
当然了,以上只是我们的一个初步探索,在我们实际生产环境中可能要结合我们的业务逻辑,以及内存分配时间轴比如说短时间内内存急剧
升高然后又急剧下降,也就是内存抖动了,那么久意味着内存在快速的分配,同时快速的被回收。或者某些情况下内存居高不下等等原因进行
分析。
代码地址。