感嘆
其實(shí)吧蟹肘,要開(kāi)發(fā)一個(gè)app是很簡(jiǎn)單的事,很多人都認(rèn)為俯树,不就寫(xiě)幾個(gè)界面帘腹,什么LinearLayout、RelativeLayout许饿、FrameLayout阳欲、TextView、ImageView等等組合在一起陋率,然后在Activity中從服務(wù)器獲取數(shù)據(jù)顯示出來(lái)嘛球化,就那么簡(jiǎn)單,我只想說(shuō)瓦糟,那只是最基本的app開(kāi)發(fā)工作筒愚,而且app開(kāi)發(fā)根本就不你想象的那么簡(jiǎn)單的,特別是Android開(kāi)發(fā)菩浙,各種適配問(wèn)題巢掺,各種崩潰問(wèn)題,還有各種內(nèi)存爆掉的問(wèn)題劲蜻,今天我們就來(lái)聊聊如何讓你的app在手機(jī)運(yùn)行起來(lái)如絲般絲滑陆淀,扯那么遠(yuǎn),今天聊的就是如何寫(xiě)更好的代碼斋竞,處理app的內(nèi)存泄漏倔约,進(jìn)行內(nèi)存優(yōu)化。
單例
相信作為一個(gè)程序猿(不要意思坝初,又自嘲了0.0)浸剩,在你編碼的過(guò)程中,肯定有使用到過(guò)單例吧鳄袍?不知道你們的代碼中的單例是怎么編寫(xiě)的呢绢要?
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;
}
相信絕大部分人寫(xiě)的單例都是這樣的吧?這就是傳說(shuō)中的懶漢模式拗小,我們先不去討論此種方式是否好重罪,我們要討論的時(shí)候創(chuàng)建單例的時(shí)候如何避免內(nèi)存的泄漏,仔細(xì)的同學(xué)應(yīng)該發(fā)現(xiàn),創(chuàng)建單例的時(shí)候傳過(guò)來(lái)了一個(gè)Context剿配,那么我們應(yīng)該是用Activity的Context呢搅幅?還是Application的呢?我們來(lái)分析一下呼胚。
假設(shè)傳入的是Activity的Context茄唐,當(dāng)Activity銷毀再重建后,因?yàn)槭菃卫詍Test肯定部位空沪编,所以不會(huì)走 if (mTest == null) ,不會(huì)再進(jìn)行創(chuàng)建年扩,而是直接返回mTest蚁廓。 此時(shí)的Context 還是上一個(gè)activity實(shí)例的Context,所以,上一個(gè)activity實(shí)例并未被釋放掉,所以就會(huì)造成內(nèi)存泄漏了厨幻。所以我們應(yīng)該把Application的Context傳進(jìn)來(lái)相嵌,不應(yīng)是Activity的。
非靜態(tài)匿名內(nèi)部類
有人看到這名詞會(huì)不會(huì)感覺(jué)到陌生克胳?那我們就用實(shí)例來(lái)解析下吧平绩。
public class Test extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
test();
}
/**
* 測(cè)試內(nèi)存泄漏的代碼
*/
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
雖然上面的例子非常的簡(jiǎn)單,而且在寫(xiě)代碼的時(shí)候我們沒(méi)那么蠢會(huì)寫(xiě)這樣的代碼漠另,但是new Thread()捏雌,這個(gè)我們還是會(huì)有用到的吧?這樣看起來(lái)好像沒(méi)什么問(wèn)題啊笆搓,執(zhí)行test方法性湿,然后就一直睡眠1秒鐘嘛,應(yīng)該沒(méi)事啊满败,但當(dāng)我們銷毀了Test這個(gè)activity的時(shí)候肤频,會(huì)出現(xiàn)什么問(wèn)題呢?創(chuàng)建一個(gè)非靜態(tài)的內(nèi)部類實(shí)例如new Thread()算墨,就會(huì)引用它的外圍實(shí)例宵荒,也就是Test。如果這個(gè)非靜態(tài)內(nèi)部類實(shí)例做了一些耗時(shí)的操作净嘀,就會(huì)造成外圍對(duì)象不會(huì)被回收报咳,從而導(dǎo)致內(nèi)存泄漏。
這類問(wèn)題的解決方案為:
1.將內(nèi)部類變成靜態(tài)內(nèi)部類挖藏。
2.如果有強(qiáng)引用Activity中的屬性暑刃,則將該屬性的引用方式改為弱引用。
3.當(dāng)Activity執(zhí)行onDestory時(shí)膜眠,把這些耗時(shí)任務(wù)給干掉岩臣。
WebView
WebView在我之前寫(xiě)的文章中有講到過(guò)溜嗜,這邊也就不重復(fù)了。
有興趣的可以進(jìn)去看看架谎。
Handler
這個(gè)的話炸宵,我敢保證,你絕對(duì)有用過(guò)狐树!
private Handler recordHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
};
是不是絕大部分人都是這樣來(lái)操作的焙压?給我說(shuō)中了吧鸿脓,其實(shí)就跟上面說(shuō)到的非靜態(tài)內(nèi)部類一樣抑钟,將 Handler 聲明為靜態(tài)的, 則其存活期跟 Activity 的生命周期就無(wú)關(guān)了野哭。同時(shí)通過(guò)弱引用的方式引入 Activity在塔, 避免直接將Activity 作為 context 傳進(jìn)去。
正確的使用方式是
static class TestHandler extends Handler {
private final WeakReference<Test> 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) {
}
}
}
資源
對(duì)于Bitmap拨黔,BraodcastReceiver蛔溃,ContentObserver,F(xiàn)ile篱蝇,Cursor贺待,Stream等資源的使用,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷零截,否則這些資源將不會(huì)被回收麸塞,造成內(nèi)存泄漏的。這個(gè)是也一個(gè)良好的習(xí)慣吧涧衙,隨用隨開(kāi)哪工,開(kāi)完就要關(guān),就像你打開(kāi)冰箱門(mén)弧哎,拿了可樂(lè)出來(lái)雁比,使用完了之后就會(huì)把冰箱門(mén)關(guān)上,防止冷氣浪費(fèi)了撤嫩,就如同防止內(nèi)存泄漏了一樣偎捎。
靜態(tài)
Context對(duì)象為靜態(tài)的。
publicclassTestextendsActivity{
publicstaticContextmContext;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
mContext=this;
}
}
像上面的代碼序攘,那么Activity就無(wú)法正常銷毀茴她,會(huì)常駐內(nèi)存。這樣就會(huì)造成內(nèi)存泄漏了两踏,最好就不要把Context變成靜態(tài)的變量败京,可以使用Application的Context。
總結(jié)
1梦染、對(duì)于生命周期比Activity長(zhǎng)的對(duì)象如果需要應(yīng)該使用ApplicationContext
3赡麦、對(duì)于需要在靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(如:Context朴皆、View ),可以在靜態(tài)內(nèi)部類中使用弱引用來(lái)引用外部類的變量來(lái)避免內(nèi)存泄漏
4泛粹、對(duì)于不再需要使用的對(duì)象或者資源遂铡,顯示的將其賦值為null,比如使用完Bitmap后先調(diào)用recycle()晶姊,再賦為null
5扒接、注意activity的生命周期,要在銷毀的時(shí)候把所有耗時(shí)的任務(wù)或者資源都要釋放
6们衙、合理使用單例钾怔,并且要注意其生命周期
7、推薦使用內(nèi)存泄漏檢測(cè)工具蒙挑,
(1)代碼檢測(cè)工具:LeakCanary
(2)Android Studio自帶工具M(jìn)onitors宗侦,可以時(shí)刻監(jiān)控app的Memory.
其實(shí)內(nèi)存泄漏都是人為的(這不是廢話么,難道代碼不是人寫(xiě)的么忆蚀?)矾利,我這里說(shuō)的人為的意思是,不良的編碼習(xí)慣馋袜,還有基礎(chǔ)功不扎實(shí)造成的男旗,所以不管怎樣,正所謂代碼是人寫(xiě)的欣鳖,內(nèi)存泄漏也是人為的察皇,那么就肯定會(huì)有對(duì)應(yīng)的方法去解決的,只有在不斷開(kāi)發(fā)中遇到困難观堂,在困難中不斷學(xué)習(xí)让网,在學(xué)習(xí)中不斷成長(zhǎng),這才是我們想要的师痕。