感叹
其实吧,要开发一个app是很简单的事,很多人都认为,不就写几个界面,什么LinearLayout、RelativeLayout、FrameLayout、TextView、ImageView等等组合在一起,然后在Activity中从服务器获取数据显示出来嘛,就那么简单,我只想说,那只是最基本的app开发工作,而且app开发根本就不你想象的那么简单的,特别是Android开发,各种适配问题,各种崩溃问题,还有各种内存爆掉的问题,今天我们就来聊聊如何让你的app在手机运行起来如丝般丝滑,扯那么远,今天聊的就是如何写更好的代码,处理app的内存泄漏,进行内存优化。
单例
相信作为一个程序猿(不要意思,又自嘲了0.0),在你编码的过程中,肯定有使用到过单例吧?不知道你们的代码中的单例是怎么编写的呢?
public static Test mTest;
private Context mContext;
private Test(Context context) {
mContext = context;
}
public static synchronized Test getInstance(Context context) {
if (mTest == null) {
mTest = new Test(context);
}
return mTest;
}
相信绝大部分人写的单例都是这样的吧?这就是传说中的懒汉模式,我们先不去讨论此种方式是否好,我们要讨论的时候创建单例的时候如何避免内存的泄漏,仔细的同学应该发现,创建单例的时候传过来了一个Context,那么我们应该是用Activity的Context呢?还是Application的呢?我们来分析一下。
假设传入的是Activity的Context,当Activity销毁再重建后,因为是单例,所以mTest肯定部位空,所以不会走 if (mTest == null) ,不会再进行创建,而是直接返回mTest。 此时的Context 还是上一个activity实例的Context,所以,上一个activity实例并未被释放掉,所以就会造成内存泄漏了。所以我们应该把Application的Context传进来,不应是Activity的。
非静态匿名内部类
有人看到这名词会不会感觉到陌生?那我们就用实例来解析下吧。
public class Test extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
test();
}
/**
* 测试内存泄漏的代码
*/
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
虽然上面的例子非常的简单,而且在写代码的时候我们没那么蠢会写这样的代码,但是new Thread(),这个我们还是会有用到的吧?这样看起来好像没什么问题啊,执行test方法,然后就一直睡眠1秒钟嘛,应该没事啊,但当我们销毁了Test这个activity的时候,会出现什么问题呢?创建一个非静态的内部类实例如new Thread(),就会引用它的外围实例,也就是Test。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。
这类问题的解决方案为:
1.将内部类变成静态内部类。
2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用。
3.当Activity执行onDestory时,把这些耗时任务给干掉。
WebView
WebView在我之前写的文章中有讲到过,这边也就不重复了。
WebView想说爱你不容易啊
有兴趣的可以进去看看。
Handler
这个的话,我敢保证,你绝对有用过!
private Handler recordHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
};
是不是绝大部分人都是这样来操作的?给我说中了吧,其实就跟上面说到的非静态内部类一样,将 Handler 声明为静态的, 则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity, 避免直接将Activity 作为 context 传进去。
正确的使用方式是
static class TestHandler extends Handler {
private final WeakReference mActivityRef;
TestHandler(Test activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
final Test activity = mActivityRef.get();
if (activity == null || activity.isFinishing()) {
removeCallbacksAndMessages(null);
return;
}
switch (msg.what) {
}
}
}
资源
对于Bitmap,BraodcastReceiver,ContentObserver,File,Cursor,Stream等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏的。这个是也一个良好的习惯吧,随用随开,开完就要关,就像你打开冰箱门,拿了可乐出来,使用完了之后就会把冰箱门关上,防止冷气浪费了,就如同防止内存泄漏了一样。
静态
Context对象为静态的。
publicclassTestextendsActivity{
publicstaticContextmContext;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
mContext=this;
}
}
像上面的代码,那么Activity就无法正常销毁,会常驻内存。这样就会造成内存泄漏了,最好就不要把Context变成静态的变量,可以使用Application的Context。
总结
1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
4、对于不再需要使用的对象或者资源,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
5、注意activity的生命周期,要在销毁的时候把所有耗时的任务或者资源都要释放
6、合理使用单例,并且要注意其生命周期
7、推荐使用内存泄漏检测工具,
(1)代码检测工具: LeakCanary
(2)Android Studio自带工具Monitors,可以时刻监控app的Memory.
其实内存泄漏都是人为的(这不是废话么,难道代码不是人写的么?),我这里说的人为的意思是,不良的编码习惯,还有基础功不扎实造成的,所以不管怎样,正所谓代码是人写的,内存泄漏也是人为的,那么就肯定会有对应的方法去解决的,只有在不断开发中遇到困难,在困难中不断学习,在学习中不断成长,这才是我们想要的。