今天整理一下關(guān)于內(nèi)存泄漏和優(yōu)化相關(guān),這是個(gè)人最近心得,希望能夠幫助讀者。
下面我們便開(kāi)始吧。
最近組內(nèi)在討論關(guān)于內(nèi)存泄漏與優(yōu)化的問(wèn)題,每個(gè)人多多少少可能都會(huì)遇到這樣的問(wèn)題,總是覺(jué)得哪里會(huì)出現(xiàn)內(nèi)存泄漏,而網(wǎng)上對(duì)內(nèi)存泄漏和優(yōu)化的文章有一大堆,每次看的總是覺(jué)得一時(shí)能夠理解,但是自己卻總是用不到或者想不透,最近頗有心得,下面來(lái)講下關(guān)于這個(gè)問(wèn)題,結(jié)合一些例子,優(yōu)化和泄漏放在一起。
個(gè)人理解有以下幾點(diǎn):
1.首先,關(guān)于后臺(tái)服務(wù),別總是想著要?;?,現(xiàn)在Google已經(jīng)把a(bǔ)pp后臺(tái)活動(dòng)限制的死死的,別想著要反著來(lái),人家這樣做肯定是經(jīng)過(guò)仔細(xì)考慮才發(fā)布的,而且每個(gè)版本都還要加強(qiáng)限制,不懂得小伙伴可以大概了解一下Doze模式下面程序活動(dòng)情況,基本后臺(tái)活動(dòng)會(huì)不斷延時(shí)進(jìn)行,就連用的微信最近放久了也不及時(shí)不靈光了。
結(jié)論:服務(wù)Google推薦使用JobService,一般服務(wù)做前臺(tái)就行,也可以用IntentService(如果與UI無(wú)交互)
2.應(yīng)用返回為殺死,我們經(jīng)常會(huì)在主頁(yè)面重寫返回,讓用戶點(diǎn)了back之后進(jìn)入后臺(tái)頁(yè)面,這是不友好的。
建議:使用正常退出流程,讓應(yīng)用關(guān)閉當(dāng)前頁(yè)面,否則這部分頁(yè)面任然占用內(nèi)存,導(dǎo)致資源浪費(fèi)
3.關(guān)于Context引用,有Activity的Context,而一般建議使用ApplicationContext,這樣使對(duì)象引用的是Application,而不是頁(yè)面的Context,否則存在內(nèi)存泄漏,這個(gè)通俗易懂,但是什么情況是Context引用而容易導(dǎo)致內(nèi)存泄漏呢,我整理了一下,用一句話來(lái)講:線程或者靜態(tài)對(duì)象直接或者間接引用了ActivityContext。
什么是直接引用?
1.靜態(tài)對(duì)象引用
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;
}
}
這是一個(gè)靜態(tài)對(duì)象引用case,這里存在頁(yè)面關(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)致頁(yè)面關(guān)閉但是Thread可能還沒(méi)有執(zhí)行完成導(dǎo)致Context被引用出現(xiàn)內(nèi)存泄漏。
以上是2種直接引用this對(duì)象(Map引用也差不多),這2種情況都需要使用慎重,直接引用很容易排查,建議使用WeakReference或者在頁(yè)面推出執(zhí)行引用置空操作并且釋放資源
3.間接引用:
關(guān)于間接引用,據(jù)我理解,其實(shí)就是 非靜態(tài)內(nèi)部類/匿名內(nèi)部類 被靜態(tài)或者線程引用,為什么這樣理解呢?
3.1鏈?zhǔn)絺鬟f引用:顧名思義,就是一個(gè)A -> B -> T/S,那么T/S也是引用A的,舉例如下:
MyObject obj = new MyObject(MainActivity.this);
Thread mThread = new MyThread(obj);
mThread.start();
這里MyObject對(duì)象對(duì)Activity引用,而該對(duì)象又被MyThread引用,這么按照傳遞的效果,那么Activity也是間接被MyThread引用了
3.2非靜態(tài)內(nèi)部類:
非靜態(tài)內(nèi)部類可以訪問(wèn)外部方法(靜態(tài)內(nèi)部類則不能訪問(wèn)),也就是說(shuō),非靜態(tài)內(nèi)部類默認(rèn)可以完全訪問(wèn)外部方法,這樣外部類就是默認(rèn)被非靜態(tài)內(nèi)部類完全引用了,如果將這個(gè)非靜態(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)過(guò)很多C或者D或者E,最后到T/S,這就存在一個(gè)鏈?zhǔn)介g接引用,這條鏈上面的每個(gè)元素都不會(huì)被釋放,導(dǎo)致泄漏。下面將結(jié)合3.3舉例:
3.3匿名內(nèi)部類:
匿名內(nèi)部類同3.2非靜態(tài)內(nèi)部類,匿名內(nèi)部類是由new創(chuàng)建并重寫或者實(shí)現(xiàn)某個(gè)方法,假設(shè)在A中有個(gè)匿名內(nèi)部類,那么匿名內(nèi)部類重寫或者實(shí)現(xiàn)的方法中可以調(diào)用A中所有方法,這就存在對(duì)A的引用,匿名內(nèi)部類引用了A,這時(shí)候又和上面一樣的case了,如果這個(gè)匿名內(nèi)部類經(jīng)過(guò)多次鏈?zhǔn)絺鬟f給了靜態(tài)變量或者線程,那么頁(yè)面關(guān)閉的時(shí)候,匿名內(nèi)部類不會(huì)被釋放,那么引用的A也不會(huì)被釋放,導(dǎo)致內(nèi)存泄漏,常見(jiàn)的有CallBack,Handler,new構(gòu)造的所有對(duì)象重寫或?qū)崿F(xiàn)的方法會(huì)產(chǎn)生引用A內(nèi)部方法,舉個(gè)栗子:
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
}
}
此例子是一個(gè)Activity里面有一個(gè)內(nèi)部類CallBack2和一個(gè)接口,首先是非靜態(tài)內(nèi)部類,使用invoke方法調(diào)用Activity的onResult,其二是匿名內(nèi)部類接口callBack1,這里內(nèi)部類接口callBack1引用了this里面方法導(dǎo)致后臺(tái)操作不會(huì)及時(shí)釋放Context。
4.資源的打開(kāi)與關(guān)閉
使用IO、File流或者Sqlite、Cursor等資源時(shí)要及時(shí)關(guān)閉,一般都是對(duì)應(yīng)寫注冊(cè)與反注冊(cè), Open與Close。
5.其他
耗時(shí)任務(wù),屬性動(dòng)畫間接引用View里面Context,Thread(Runnable),Timer引用This方法導(dǎo)致泄漏。
總結(jié):
關(guān)于內(nèi)存泄漏,目前個(gè)人理解就是對(duì)頁(yè)面Context(this)的引用,直接或者間接引用,直接引用容易發(fā)現(xiàn),間接引用需要追蹤鏈接,不管是線程還是靜態(tài)內(nèi)部類,引用方法或者view都會(huì)造成內(nèi)存泄漏。如果內(nèi)部類并沒(méi)有引用Context(this)里面的方法或者指向則不會(huì)造成泄漏。