內(nèi)存優(yōu)化

內(nèi)存泄露的原因
內(nèi)存優(yōu)化中非常關(guān)鍵的一點(diǎn)瓷胧,就是避免內(nèi)存泄露玄渗,因?yàn)閮?nèi)存泄露會(huì)嚴(yán)重的導(dǎo)致內(nèi)存浪費(fèi)翔曲。

無用的對(duì)象引用一直未被釋放败玉,就會(huì)導(dǎo)致內(nèi)存泄露。
如果代碼中疫铜,無用的對(duì)象的引用一直沒有被清除掉茂浮,就會(huì)造成內(nèi)存泄露。

當(dāng)按Back鍵關(guān)掉一個(gè)Activity時(shí)壳咕,此Activity就暫時(shí)沒用了席揽。
但是,如果某個(gè)后臺(tái)任務(wù)一直持有著該Activity對(duì)象的引用谓厘,此時(shí)就會(huì)導(dǎo)致內(nèi)存泄露幌羞。
四種引用類型
1.  強(qiáng)引用
    對(duì)象類型 對(duì)象名 = new 對(duì)象構(gòu)造方法();
    如:String str = new String("Test");

    清除強(qiáng)引用對(duì)象中的引用如下:
    str = null;

強(qiáng)引用對(duì)象一般不會(huì)被GC回收,它寧愿被拋出OOM異常竟稳,想要回收就要置空引用属桦。
2.  軟應(yīng)用 SoftReference

    private void demo() {
        String str = new String("");
        SoftReference<String> softReference = new SoftReference<>(str);
    }

當(dāng)系統(tǒng)內(nèi)存不足時(shí),軟引用對(duì)象會(huì)被GC回收他爸。
清除軟引用對(duì)象中的引用鏈聂宾,可以通過模擬系統(tǒng)內(nèi)存不足來清除,也可以手動(dòng)清除诊笤,手動(dòng)清除如下:
    SoftReference<String> softReference = new SoftReference<String>(str);
    softReference.clear();
3.  弱引用 WeakReference
    private void demo() {
        String str = new String("");
        WeakReference<String> softReference = new WeakReference<>(str);
    }

當(dāng)每次GC時(shí)系谐,弱可及對(duì)象就會(huì)被回收。
清除弱引用對(duì)象中的引用鏈可以通過手動(dòng)調(diào)用gc代碼來清除讨跟,如下:
    private void demo() {
        String str = new String("");
        WeakReference<String> softReference = new WeakReference<>(str);
        softReference.get();//獲取引用
        softReference.clear();
        System.gc();
    }
4.  虛引用 PhantomReference
虛引用在代碼中出現(xiàn)的頻率極低蔚鸥,主要目的是為了檢測(cè)對(duì)象是否已經(jīng)被系統(tǒng)回收。

PhantomReference phantomReference = new PhantomReference<>(arg0, arg1);
補(bǔ)充
一個(gè)對(duì)象的可及性由最強(qiáng)的那個(gè)來決定许赃。 
System.gc()只會(huì)回收堆內(nèi)存中存放的對(duì)象。

String str = "demo";
WeakReference<String> weakReference = new WeakReference<>(str);
System.gc();
"demo"被存放在常量池里馆类,所以gc()不會(huì)去回收混聊。

常見的內(nèi)存泄露
1.內(nèi)部類導(dǎo)致內(nèi)存泄露
1.  內(nèi)部類導(dǎo)致內(nèi)存泄漏的原因
內(nèi)部類實(shí)例會(huì) 隱式引用 外部類的引用。
內(nèi)部類如果沒有持有外部類的引用乾巧,是沒辦法去調(diào)用外部類的屬性和方法的句喜。
然而,內(nèi)部類沒有明顯的去指定和聲明引用沟于,所以稱之為 隱式引用咳胃。


泄露原因:
比如在Activity中創(chuàng)建一個(gè)內(nèi)部類鞠柄,然后在內(nèi)部類中去執(zhí)行一些耗時(shí)操作伍掀。
操作在執(zhí)行過程中仅父,Activity被關(guān)掉,此時(shí)Activity對(duì)象也不會(huì)被釋放
因?yàn)槟莻€(gè)內(nèi)部類還持有著對(duì)Activity的引用咆槽。
在Activity中創(chuàng)建一個(gè) 內(nèi)部類 去繼承Thread
然后讓該Thread執(zhí)行一些后臺(tái)任務(wù),未執(zhí)行完静暂,關(guān)閉Activity搏熄,就造成會(huì)內(nèi)存泄露。

public class MainActivity extends AppCompatActivity {
?
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startThread();
            }
        });
    }
?
    private void startThread() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    SystemClock.sleep(1000);
                }
            }
        };
        thread.start();
    }
}

當(dāng)點(diǎn)擊頁面按鈕執(zhí)行startThread()后来惧,再按下back鍵關(guān)閉Activity
幾秒后LeakCanary就會(huì)提示內(nèi)存泄露了冗栗。
2.  內(nèi)部類內(nèi)存泄漏解決方案
聲明這個(gè)內(nèi)部類為靜態(tài)類,避免這個(gè)內(nèi)部類去隱式引用外部類Activity即可供搀。

public class MainActivity extends AppCompatActivity {
?
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...與先前相比未做變化隅居,不再描述
    }
?
    private void startThread() {
        Thread thread = new MyStaticThread();
        thread.start();
    }
?
    將內(nèi)部類聲明為靜態(tài)類
    private static class MyStaticThread extends Thread {
?
        @Override
        public void run() {
            for (int i = 0; i < 200; i++) {
                SystemClock.sleep(1000);
            }
        }
    }
}


2.1  控制后臺(tái)任務(wù)
為了效率和優(yōu)化,建議通過一個(gè)boolean類型的標(biāo)志位來控制后臺(tái)任務(wù)葛虐。
在外部類Activity的onDestory()中胎源,將boolean值進(jìn)行修改,使后臺(tái)任務(wù)退出循環(huán)挡闰。
public class MainActivity extends AppCompatActivity {
    ...
    //Activity頁面是否已經(jīng)destroy
    private static boolean isDestroy = false;
?
    private static class MyStaticThread extends Thread {
?
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if(!isDestroy){
                    SystemClock.sleep(1000);
                }
            }
        }
    }
?
    @Override
    protected void onDestroy() {
        super.onDestroy();
        isDestroy = true;
    }
}


2.3  通過聲明 引用 調(diào)用外部類屬性乒融,方法。
當(dāng)該內(nèi)部類聲明為靜態(tài)時(shí)摄悯,將不再持有外部類Activity的引用
此時(shí)也不能再直接使用外部類中的方法赞季、變量。

此時(shí)就需要通過引用調(diào)用

public class MainActivity extends AppCompatActivity {
    private  boolean isDestroy = false;

    private void startThread() {
        Thread thread = new MyStaticThread(MainActivity.this);
        thread.start();
    }
?
    private static class MyStaticThread extends Thread {
?
        private WeakReference<MainActivity> softReference = null;
?
        MyStaticThread(MainActivity mainActivity){
            this.softReference = new WeakReference<>(mainActivity);
        }
?
        @Override
        public void run() {

            MainActivity mainActivity = softReference.get();

            for (int i = 0; i < 200; i++) {
                //使用前最好對(duì)MainActivity對(duì)象做非空判斷
                //如果它已經(jīng)被回收奢驯,就不再執(zhí)行后臺(tái)任務(wù)
                if(mainActivity != null && !mainActivity.isDestroy){
                    SystemClock.sleep(1000);
                }
            }
        }
    }
?
    @Override
    protected void onDestroy() {
        super.onDestroy();
        isDestroy = true;
    }
}
Handler
public class MainActivity extends AppCompatActivity {
?
    private static final int MESSAGE_DELAY = 0;
    private Button mButton;
?
    private void startDelayTask() {
        //發(fā)送一條消息申钩,該消息會(huì)被延時(shí)10秒后才處理
        ...
    }
?
    private Handler mHandler = new InsideHandler(MainActivity.this);
?
    private static class InsideHandler extends Handler {
        private WeakReference<MainActivity> mSoftReference;
?
        InsideHandler(MainActivity activity) {
            mSoftReference = new WeakReference<MainActivity>(activity);
        }
?
        @Override
        public void handleMessage(Message msg) {

            MainActivity mainActivity = mSoftReference.get();

            if (mainActivity != null) {
                switch (msg.what) {
                    case MESSAGE_DELAY:
                        //通過軟引用中的mainActivity可以拿到那個(gè)非靜態(tài)的button對(duì)象
                        mainActivity.mButton.setText("延時(shí)修改了按鈕的文本");
                        break;
                }
            }
        }
    }
}


當(dāng)Activity頁面退出時(shí),將handler中的所有消息進(jìn)行移除瘪阁,做到滴水不漏撒遣。
其實(shí)就是在onDestroy中寫上:
@Override
protected void onDestroy() {
    super.onDestroy();
    //參數(shù)為null時(shí),handler中所有消息和回調(diào)都會(huì)被移除
    mHandler.removeCallbacksAndMessages(null);
}
總結(jié)

1.  將內(nèi)部類聲明為靜態(tài)的
2.  通過引用獲取外部屬性管跺,方法
3.  注意判斷引用獲取是否為空
4.  在Activity銷毀方法內(nèi)注銷Handler等
2.Context導(dǎo)致內(nèi)存泄露
有時(shí)候我們會(huì)創(chuàng)建一個(gè)靜態(tài)類义黎,比如說AppManager、XXXManager豁跑。
這些靜態(tài)類可能還會(huì)以單例模式存在廉涕。
當(dāng)需要做關(guān)于UI的處理,所以傳遞了一個(gè)Context進(jìn)來艇拍。

public class ToastManager {
    private Context mContext;
    ToastManager(Context context){
        mContext = context;
    }
?
    private static ToastManager mManager = null;
?
    public void showToast(String str){
        if(str == null){
            return;
        }
        Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show();
    }
?
    public static ToastManager getInstance(Context context){
        if(mManager == null){
            synchronized (ToastManager.class){
                if(mManager == null){
                    mManager = new ToastManager(context);
                }
            }
        }
        return mManager;
    }
}



使用時(shí)
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        ToastManager instance = ToastManager.getInstance(MainActivity.this);
    }
}

此時(shí)會(huì)發(fā)生內(nèi)存泄露
因?yàn)殪o態(tài)實(shí)例比Activity生命周期長(zhǎng)狐蜕,在使用靜態(tài)類時(shí)將Activity作為context參數(shù)傳了進(jìn)來
當(dāng)Activity被關(guān)掉,但是靜態(tài)實(shí)例中還保有對(duì)它的引用
會(huì)導(dǎo)致Activity沒法被及時(shí)回收卸夕,造成內(nèi)存泄露


解決方案:
在傳Context上下文參數(shù)時(shí)层释,盡量傳跟Application應(yīng)用相同生命周期的Context。
比如getApplicationContext()快集,因?yàn)殪o態(tài)實(shí)例的生命周期跟應(yīng)用Application一致贡羔。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ToastManager instance = ToastManager.getInstance(getApplicationContext());
    }
}

Context的作用域
Context的具體實(shí)現(xiàn)子類
Activity廉白、Application、Service

1.Activity
出于安全原因的考慮治力,Android是不允許Activity或Dialog憑空出現(xiàn)的蒙秒。
一個(gè)Activity的啟動(dòng),必須要建立在另一個(gè)Activity的基礎(chǔ)上宵统,以此形成的返回棧晕讲。

Dialog則必須在一個(gè)Activity上面彈出。
在這種場(chǎng)景下马澈,只能使用Activity類型的Context瓢省,否則將會(huì)出錯(cuò)。
總結(jié)

1.  凡是跟UI相關(guān)的痊班,都建議使用Activity做為Context來處理.

2.  注意Context引用的持有勤婚,防止內(nèi)存泄漏。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涤伐,一起剝皮案震驚了整個(gè)濱河市馒胆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凝果,老刑警劉巖祝迂,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異器净,居然都是意外死亡型雳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門山害,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纠俭,“玉大人,你說我怎么就攤上這事浪慌≡┚#” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵权纤,是天一觀的道長(zhǎng)钓简。 經(jīng)常有香客問我,道長(zhǎng)妖碉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任芥被,我火速辦了婚禮欧宜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拴魄。我一直安慰自己冗茸,他們只是感情好席镀,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夏漱,像睡著了一般豪诲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挂绰,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天屎篱,我揣著相機(jī)與錄音,去河邊找鬼葵蒂。 笑死交播,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的践付。 我是一名探鬼主播秦士,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼永高!你這毒婦竟也來了隧土?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤命爬,失蹤者是張志新(化名)和其女友劉穎曹傀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遇骑,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卖毁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了落萎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亥啦。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖练链,靈堂內(nèi)的尸體忽然破棺而出翔脱,到底是詐尸還是另有隱情,我是刑警寧澤媒鼓,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布届吁,位于F島的核電站,受9級(jí)特大地震影響绿鸣,放射性物質(zhì)發(fā)生泄漏疚沐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一潮模、第九天 我趴在偏房一處隱蔽的房頂上張望亮蛔。 院中可真熱鬧,春花似錦擎厢、人聲如沸究流。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芬探。三九已至神得,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間偷仿,已是汗流浹背哩簿。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炎疆,地道東北人卡骂。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像形入,于是被迫代替她去往敵國(guó)和親全跨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容