今天整理一下關(guān)于內(nèi)存泄漏和優(yōu)化相關(guān)塑崖,這是個人最近心得贷腕,希望能夠幫助讀者侵俗。
下面我們便開始吧。
最近組內(nèi)在討論關(guān)于內(nèi)存泄漏與優(yōu)化的問題丰刊,每個人多多少少可能都會遇到這樣的問題隘谣,總是覺得哪里會出現(xiàn)內(nèi)存泄漏,而網(wǎng)上對內(nèi)存泄漏和優(yōu)化的文章有一大堆啄巧,每次看的總是覺得一時能夠理解寻歧,但是自己卻總是用不到或者想不透,最近頗有心得秩仆,下面來講下關(guān)于這個問題码泛,結(jié)合一些例子,優(yōu)化和泄漏放在一起澄耍。
個人理解有以下幾點(diǎn):
1.首先噪珊,關(guān)于后臺服務(wù),別總是想著要逼肓活痢站,現(xiàn)在Google已經(jīng)把a(bǔ)pp后臺活動限制的死死的,別想著要反著來选酗,人家這樣做肯定是經(jīng)過仔細(xì)考慮才發(fā)布的阵难,而且每個版本都還要加強(qiáng)限制,不懂得小伙伴可以大概了解一下Doze模式下面程序活動情況芒填,基本后臺活動會不斷延時進(jìn)行呜叫,就連用的微信最近放久了也不及時不靈光了空繁。
結(jié)論:服務(wù)Google推薦使用JobService,一般服務(wù)做前臺就行朱庆,也可以用IntentService(如果與UI無交互)
2.應(yīng)用返回為殺死盛泡,我們經(jīng)常會在主頁面重寫返回,讓用戶點(diǎn)了back之后進(jìn)入后臺頁面椎工,這是不友好的饭于。
建議:使用正常退出流程,讓應(yīng)用關(guān)閉當(dāng)前頁面维蒙,否則這部分頁面任然占用內(nèi)存掰吕,導(dǎo)致資源浪費(fèi)
3.關(guān)于Context引用,有Activity的Context颅痊,而一般建議使用ApplicationContext殖熟,這樣使對象引用的是Application,而不是頁面的Context斑响,否則存在內(nèi)存泄漏菱属,這個通俗易懂,但是什么情況是Context引用而容易導(dǎo)致內(nèi)存泄漏呢舰罚,我整理了一下纽门,用一句話來講:線程或者靜態(tài)對象直接或者間接引用了ActivityContext。
什么是直接引用营罢?
1.靜態(tài)對象引用
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;
}
}
這是一個靜態(tài)對象引用case赏陵,這里存在頁面關(guān)閉activity任然被靜態(tài)指向?qū)е聝?nèi)存泄漏,我想這樣的代碼應(yīng)該每人寫饲漾。
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蝙搔,這種情況導(dǎo)致頁面關(guān)閉但是Thread可能還沒有執(zhí)行完成導(dǎo)致Context被引用出現(xiàn)內(nèi)存泄漏。
以上是2種直接引用this對象(Map引用也差不多)考传,這2種情況都需要使用慎重吃型,直接引用很容易排查,建議使用WeakReference或者在頁面推出執(zhí)行引用置空操作并且釋放資源
3.間接引用:
關(guān)于間接引用僚楞,據(jù)我理解勤晚,其實就是 非靜態(tài)內(nèi)部類/匿名內(nèi)部類 被靜態(tài)或者線程引用,為什么這樣理解呢泉褐?
3.1鏈?zhǔn)絺鬟f引用:顧名思義运翼,就是一個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非靜態(tài)內(nèi)部類:
非靜態(tài)內(nèi)部類可以訪問外部方法(靜態(tài)內(nèi)部類則不能訪問)悠夯,也就是說癌淮,非靜態(tài)內(nèi)部類默認(rèn)可以完全訪問外部方法,這樣外部類就是默認(rèn)被非靜態(tài)內(nèi)部類完全引用了沦补,如果將這個非靜態(tài)內(nèi)部類傳給靜態(tài)變量或者線程乳蓄,那么就好比 A -> B -> T/S , 其中A是Context/Activity,B在A里面屬于非靜態(tài)內(nèi)部類夕膀, T/S是線程或靜態(tài)變量虚倒,這樣T/S就是間接引用A,在A關(guān)閉可以導(dǎo)致內(nèi)存泄漏产舞,當(dāng)然了魂奥,這里需要注意的是可能B經(jīng)過很多C或者D或者E,最后到T/S易猫,這就存在一個鏈?zhǔn)介g接引用耻煤,這條鏈上面的每個元素都不會被釋放,導(dǎo)致泄漏准颓。下面將結(jié)合3.3舉例:
3.3匿名內(nèi)部類:
匿名內(nèi)部類同3.2非靜態(tài)內(nèi)部類哈蝇,匿名內(nèi)部類是由new創(chuàng)建并重寫或者實現(xiàn)某個方法,假設(shè)在A中有個匿名內(nèi)部類攘已,那么匿名內(nèi)部類重寫或者實現(xiàn)的方法中可以調(diào)用A中所有方法炮赦,這就存在對A的引用,匿名內(nèi)部類引用了A样勃,這時候又和上面一樣的case了眼五,如果這個匿名內(nèi)部類經(jīng)過多次鏈?zhǔn)絺鬟f給了靜態(tài)變量或者線程,那么頁面關(guān)閉的時候彤灶,匿名內(nèi)部類不會被釋放,那么引用的A也不會被釋放批旺,導(dǎo)致內(nèi)存泄漏幌陕,常見的有CallBack,Handler汽煮,new構(gòu)造的所有對象重寫或?qū)崿F(xiàn)的方法會產(chǎn)生引用A內(nèi)部方法搏熄,舉個栗子:
public class MainActivity extends Activity{
private void init(){
//匿名內(nèi)部類引用
MyThread thread = new MyThread(new callBack1() {
@Override
public void onResult() {
processResult();
}
});
//非靜態(tài)內(nèi)部類引用
thread.setCallBack2(new callBack2());
}
public interface callBack1{
void onResult();
}
public class callBack2{
public void invoke(){
MainActivity.this.processResult();
}
}
public void processResult(){
//do something
}
}
此例子是一個Activity里面有一個內(nèi)部類CallBack2和一個接口,首先是非靜態(tài)內(nèi)部類暇赤,使用invoke方法調(diào)用Activity的onResult心例,其二是匿名內(nèi)部類接口callBack1,這里內(nèi)部類接口callBack1引用了this里面方法導(dǎo)致后臺操作不會及時釋放Context鞋囊。
4.資源的打開與關(guān)閉
使用IO止后、File流或者Sqlite、Cursor等資源時要及時關(guān)閉,一般都是對應(yīng)寫注冊與反注冊, Open與Close译株。
5.其他
耗時任務(wù)瓜喇,屬性動畫間接引用View里面Context,Thread(Runnable)歉糜,Timer引用This方法導(dǎo)致泄漏乘寒。
總結(jié):
關(guān)于內(nèi)存泄漏,目前個人理解就是對頁面Context(this)的引用匪补,直接或者間接引用伞辛,直接引用容易發(fā)現(xiàn),間接引用需要追蹤鏈接夯缺,不管是線程還是靜態(tài)內(nèi)部類蚤氏,引用方法或者view都會造成內(nèi)存泄漏。如果內(nèi)部類并沒有引用Context(this)里面的方法或者指向則不會造成泄漏喳逛。