今天整理一下关于内存泄漏和优化相关,这是个人最近心得,希望能够帮助读者。
下面我们便开始吧。
最近组内在讨论关于内存泄漏与优化的问题,每个人多多少少可能都会遇到这样的问题,总是觉得哪里会出现内存泄漏,而网上对内存泄漏和优化的文章有一大堆,每次看的总是觉得一时能够理解,但是自己却总是用不到或者想不透,最近颇有心得,下面来讲下关于这个问题,结合一些例子,优化和泄漏放在一起。
个人理解有以下几点:
1.首先,关于后台服务,别总是想着要保活,现在Google已经把app后台活动限制的死死的,别想着要反着来,人家这样做肯定是经过仔细考虑才发布的,而且每个版本都还要加强限制,不懂得小伙伴可以大概了解一下Doze模式下面程序活动情况,基本后台活动会不断延时进行,就连用的微信最近放久了也不及时不灵光了。
结论:服务Google推荐使用JobService,一般服务做前台就行,也可以用IntentService(如果与UI无交互)
2.应用返回为杀死,我们经常会在主页面重写返回,让用户点了back之后进入后台页面,这是不友好的。
建议:使用正常退出流程,让应用关闭当前页面,否则这部分页面任然占用内存,导致资源浪费
3.关于Context引用,有Activity的Context,而一般建议使用ApplicationContext,这样使对象引用的是Application,而不是页面的Context,否则存在内存泄漏,这个通俗易懂,但是什么情况是Context引用而容易导致内存泄漏呢,我整理了一下,用一句话来讲:线程或者静态对象直接或者间接引用了ActivityContext。
什么是直接引用?
1.静态对象引用
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//public static Object ref1;
RefManager.ref1 = this;
}
}
这是一个静态对象引用case,这里存在页面关闭activity任然被静态指向导致内存泄漏,我想这样的代码应该每人写。
2.线程引用
public class MyThread extends Thread{
private Context mContext;
public MyThread(Context mContext) {
this.mContext = mContext;
}
}
Thread mThread = new MyThread(this);
mThread.start();
这种case是线程直接引用Context,这种情况导致页面关闭但是Thread可能还没有执行完成导致Context被引用出现内存泄漏。
以上是2种直接引用this对象(Map引用也差不多),这2种情况都需要使用慎重,直接引用很容易排查,建议使用WeakReference或者在页面推出执行引用置空操作并且释放资源
3.间接引用:
关于间接引用,据我理解,其实就是 非静态内部类/匿名内部类 被静态或者线程引用,为什么这样理解呢?
3.1链式传递引用:顾名思义,就是一个A -> B -> T/S,那么T/S也是引用A的,举例如下:
MyObject obj = new MyObject(MainActivity.this);
Thread mThread = new MyThread(obj);
mThread.start();
这里MyObject对象对Activity引用,而该对象又被MyThread引用,这么按照传递的效果,那么Activity也是间接被MyThread引用了
3.2非静态内部类:
非静态内部类可以访问外部方法(静态内部类则不能访问),也就是说,非静态内部类默认可以完全访问外部方法,这样外部类就是默认被非静态内部类完全引用了,如果将这个非静态内部类传给静态变量或者线程,那么就好比 A -> B -> T/S , 其中A是Context/Activity,B在A里面属于非静态内部类, T/S是线程或静态变量,这样T/S就是间接引用A,在A关闭可以导致内存泄漏,当然了,这里需要注意的是可能B经过很多C或者D或者E,最后到T/S,这就存在一个链式间接引用,这条链上面的每个元素都不会被释放,导致泄漏。下面将结合3.3举例:
3.3匿名内部类:
匿名内部类同3.2非静态内部类,匿名内部类是由new创建并重写或者实现某个方法,假设在A中有个匿名内部类,那么匿名内部类重写或者实现的方法中可以调用A中所有方法,这就存在对A的引用,匿名内部类引用了A,这时候又和上面一样的case了,如果这个匿名内部类经过多次链式传递给了静态变量或者线程,那么页面关闭的时候,匿名内部类不会被释放,那么引用的A也不会被释放,导致内存泄漏,常见的有CallBack,Handler,new构造的所有对象重写或实现的方法会产生引用A内部方法,举个栗子:
public class MainActivity extends Activity{
private void init(){
//匿名内部类引用
MyThread thread = new MyThread(new callBack1() {
@Override
public void onResult() {
processResult();
}
});
//非静态内部类引用
thread.setCallBack2(new callBack2());
}
public interface callBack1{
void onResult();
}
public class callBack2{
public void invoke(){
MainActivity.this.processResult();
}
}
public void processResult(){
//do something
}
}
此例子是一个Activity里面有一个内部类CallBack2和一个接口,首先是非静态内部类,使用invoke方法调用Activity的onResult,其二是匿名内部类接口callBack1,这里内部类接口callBack1引用了this里面方法导致后台操作不会及时释放Context。
4.资源的打开与关闭
使用IO、File流或者Sqlite、Cursor等资源时要及时关闭,一般都是对应写注册与反注册, Open与Close。
5.其他
耗时任务,属性动画间接引用View里面Context,Thread(Runnable),Timer引用This方法导致泄漏。
总结:
关于内存泄漏,目前个人理解就是对页面Context(this)的引用,直接或者间接引用,直接引用容易发现,间接引用需要追踪链接,不管是线程还是静态内部类,引用方法或者view都会造成内存泄漏。如果内部类并没有引用Context(this)里面的方法或者指向则不会造成泄漏。