前言
本文是為了面試而寫的性能優(yōu)化站绪。目的不是為了具體的深入而是對于要面試的同學(xué)在面試的時(shí)候能和面試官說出的性能優(yōu)化的方面。在面試的時(shí)候基本現(xiàn)在每個(gè)面試官都會(huì)問一些關(guān)于性能優(yōu)化方法的問題莉炉。那么該怎么回答呢铐炫?面試不同于我們學(xué)習(xí)新的知識點(diǎn)澳眷,要完全學(xué)會(huì),要學(xué)精疏魏,對于面試官這個(gè)問題颁井,可以從下面幾個(gè)方面來回答,ANR,內(nèi)存溢出蠢护,內(nèi)存抖動(dòng)雅宾,內(nèi)存泄漏,UI卡頓葵硕,冷啟動(dòng)優(yōu)化等方面來回答眉抬。
ANR
ANR(Applicatino not responding)是指程序無響應(yīng),主要原因?yàn)椋?/p>
- 主線程被io操作阻塞(4.0后網(wǎng)絡(luò)io不允許主線程中)懈凹。
- 主線程做了耗時(shí)任務(wù)超過 5秒蜀变。
- Service做了耗時(shí)操作超過20秒,這是由于service默認(rèn)執(zhí)行在主線程介评,可以使用IntentService 库北。
- BroadcastReceiver的onReciver做了耗時(shí)操作超過10秒爬舰。
解決方式:
- 開一個(gè)子線程,使用Handler來處理寒瓦。
- 使用AsyncTask來處理耗時(shí)任務(wù)情屹。
內(nèi)存溢出
內(nèi)存溢出主要是由于加載大的圖片引起的。解決方式:
- 及時(shí)釋放bitmap杂腰,調(diào)用.recycler(Bitmap會(huì)占用java內(nèi)存和c(native)內(nèi)存垃你,java內(nèi)存會(huì)自動(dòng)釋放,c內(nèi)存需要手動(dòng)釋放)喂很。
- 使用lru 最近最少使用
LruCache來存儲對象put(key,value),,使用的使用LinkHashMap()惜颇。 - 計(jì)算inSampleSize
官方提供的方法,使用BitmapFactory.Options來計(jì)算inSampleSize(圖片的縮略比) - 縮略圖
使用Options的inJustDecodeBounds屬性來處理加載縮略圖 - 三級緩存
內(nèi)存少辣,本地凌摄,網(wǎng)絡(luò)。
內(nèi)存抖動(dòng)
內(nèi)存抖動(dòng)是指內(nèi)存在短時(shí)間內(nèi)頻繁地分配和回收漓帅,而頻繁的gc會(huì)導(dǎo)致卡頓锨亏,嚴(yán)重時(shí)和內(nèi)存泄漏一樣會(huì)導(dǎo)致OOM。
常見的內(nèi)存抖動(dòng)場景:
- 循環(huán)中創(chuàng)建大量臨時(shí)對象煎殷;
- onDraw中創(chuàng)建Paint或Bitmap對象等;
內(nèi)存抖動(dòng)的原因:
瞬間產(chǎn)生大量的對象會(huì)嚴(yán)重占用新生代的內(nèi)存區(qū)域腿箩,當(dāng)達(dá)到閥值豪直,剩余空間不夠的時(shí)候,就會(huì)觸發(fā)GC珠移。系統(tǒng)花費(fèi)在GC上的時(shí)間越多弓乙,進(jìn)行界面繪制或流音頻處理的時(shí)間就越短。即使每次分配的對象占用了很少的內(nèi)存钧惧,但是他們疊加在一起會(huì)增加Heap的壓力暇韧,從而觸發(fā)更多其他類型的GC。這個(gè)操作有可能會(huì)影響到幀率浓瞪,并使得用戶感知到性能問題懈玻。
內(nèi)存泄漏
內(nèi)存泄漏是指無用對象(不在使用的對象)持續(xù)占有內(nèi)存或無用對象的內(nèi)存得不到及時(shí)釋放,從而造成的內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏乾颁。
Android內(nèi)存泄漏:
- 單例導(dǎo)致內(nèi)存泄漏
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context;
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
} return sInstance;
}
}
上面是一個(gè)比較簡單的單例模式用法涂乌,需要外部傳入一個(gè) Context 來獲取該類的實(shí)例,如果此時(shí)傳入的 Context 是 Activity 的話英岭,此時(shí)單例就有持有該 Activity 的強(qiáng)引用(直到整個(gè)應(yīng)用生命周期結(jié)束)湾盒。這樣的話,即使該 Activity 退出诅妹,該 Activity 的內(nèi)存也不會(huì)被回收罚勾,這樣就造成了內(nèi)存泄露毅人,特別是一些比較大的 Activity,甚至還會(huì)導(dǎo)致 OOM(Out Of Memory)尖殃。
解決方式:
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
his.mContext = context.getApplicationContext();
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
} return sInstance;
}
}
可以看到在 SingleInstanceTest 的構(gòu)造函數(shù)中丈莺,將 context.getApplicationContext() 賦值給 mContext,此時(shí)單例引用的對象是 Application分衫,而 Application 的生命周期本來就跟應(yīng)用程序是一樣的场刑,也就不存在內(nèi)存泄露。
2.內(nèi)部類導(dǎo)致內(nèi)存泄漏
非靜態(tài)內(nèi)部類會(huì)默認(rèn)持有外部類的引用蚪战。會(huì)導(dǎo)致內(nèi)部類的生命周期過長牵现。
正確的做法就是修改成靜態(tài)內(nèi)部類。
3.Handler
看下面的代碼
public class HandlerActivity extends AppCompatActivity {
private final static int MESSAGECODE = 1 ;
private final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("mmmmmmmm" , "handler " + msg.what ) ;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
//點(diǎn)擊結(jié)束Activity
findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
//新建線程邀桑,內(nèi)部類
new Thread(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage( MESSAGECODE ) ;
try {
Thread.sleep( 8000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
//持有對象的引用
handler.sendEmptyMessage( MESSAGECODE ) ;
}
}).start() ;
}
}
這段代碼運(yùn)行起來后瞎疼,立即點(diǎn)擊 finish 按鈕,通過檢測壁畸,發(fā)現(xiàn) HandlerActivity 出現(xiàn)了內(nèi)存泄漏贼急。
當(dāng)Activity finish后,延時(shí)消息會(huì)繼續(xù)存在主線程消息隊(duì)列中8秒鐘捏萍,然后處理消息太抓。而該消息引用了Activity的Handler對象,然后這個(gè)Handler又引用了這個(gè)Activity令杈。這些引用對象會(huì)保持到該消息被處理完走敌,這樣就導(dǎo)致該Activity對象無法被回收,從而導(dǎo)致了上面說的 Activity泄露逗噩。
Handler 是個(gè)很常用也很有用的類掉丽,異步,線程安全等等异雁。如果有下面這樣的代碼捶障,會(huì)發(fā)生什么呢? handler.postDeslayed 纲刀,假設(shè) delay 時(shí)間是幾個(gè)小時(shí)… 這意味著什么项炼?意味著只要 handler 的消息還沒有被處理結(jié)束,它就一直存活著示绊,包含它的 Activity 就跟著活著芥挣。
我們來想辦法修復(fù)它,修復(fù)的方案是 WeakReference 耻台,也就是所謂的弱引用空免。垃圾回收器在回收的時(shí)候,是會(huì)忽視掉弱引用的盆耽,所以包含它的 Activity 會(huì)被正常清理掉蹋砚。
解決方式
1.靜態(tài)內(nèi)部類
2.弱引用
3.注意在onDestroy中移除消息
public class HandlerActivity extends AppCompatActivity {
private final static int MESSAGECODE = 1 ;
private static Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v) {
finish();
}
});
//創(chuàng)建Handler
handler = new MyHandler( this ) ;
//創(chuàng)建線程并且啟動(dòng)線程
new Thread( new MyRunnable() ).start();
}
private static class MyHandler extends Handler {
WeakReference<HandlerActivity> weakReference ;
public MyHandler(HandlerActivity activity ){
weakReference = new WeakReference<HandlerActivity>( activity) ;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if ( weakReference.get() != null ){
// update android ui
Log.d("mmmmmmmm" , "handler " + msg.what ) ;
}
}
}
private static class MyRunnable implements Runnable {
@Override
public void run() {
handler.sendEmptyMessage( MESSAGECODE ) ;
try {
Thread.sleep( 8000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage( MESSAGECODE ) ;
}
}
}
UI卡頓
- 在UI線程中做輕微耗時(shí)操作扼菠,會(huì)導(dǎo)致UI線程卡頓
- 布局Layout過于復(fù)雜,無法再16ms內(nèi)完成渲染
60fps-->16ms
60ms一幀 每過16ms就會(huì)更新一下ui坝咐,要達(dá)到60ms一幀循榆,否則可能會(huì)卡頓 - 同一時(shí)間動(dòng)畫執(zhí)行的次數(shù)過多,導(dǎo)致cpu或gpu負(fù)載過重墨坚。
- View過度繪制秧饮,導(dǎo)致某些像素在同一時(shí)間內(nèi)被繪制多次,從而導(dǎo)致cpu泽篮,gpu負(fù)載過重盗尸。
overdraw
過度繪制, - view頻繁的觸發(fā)measure帽撑。layout泼各,導(dǎo)致measure。layout累計(jì)耗時(shí)過多以及整個(gè)view頻繁的重新渲染
- 內(nèi)存頻繁觸發(fā)Gc過多亏拉,導(dǎo)致展示阻塞渲染操作
- 屯余資源及邏輯導(dǎo)致加載和執(zhí)行緩慢
解決ui卡頓:
1.布局優(yōu)化 include扣蜻,merge,viewsuble
2.背景和圖片等內(nèi)存分配優(yōu)化
內(nèi)存優(yōu)化
內(nèi)存管理
- 分配機(jī)制
為每一個(gè)進(jìn)程分配一個(gè)小額的內(nèi)存及塘,然后根據(jù)需要分配更多內(nèi)存莽使。 - 回收機(jī)制
Android的目的是盡可能的運(yùn)行多個(gè)進(jìn)程,這樣可以讓用戶不用每次都重新開啟笙僚,而是恢復(fù)芳肌。當(dāng)內(nèi)存緊張時(shí)會(huì)按等級殺死進(jìn)程。前臺進(jìn)程>可見進(jìn)程>服務(wù)進(jìn)程>后臺進(jìn)程(lru)>空進(jìn)程味咳。
優(yōu)化方法:
- 當(dāng)Service完成任務(wù)后庇勃,盡量停止它檬嘀。
- 在UI不可見的時(shí)候槽驶,釋放掉一些只有UI使用的資源
- 在系統(tǒng)內(nèi)存緊張的時(shí)候,盡可能多的釋放掉非重要的資源鸳兽。
- 避免濫用Bitmap導(dǎo)致的內(nèi)存浪費(fèi)掂铐。
- 盡量使用少的依賴注入框架
冷啟動(dòng)的優(yōu)化
冷啟動(dòng)就是在啟動(dòng)應(yīng)用前,系統(tǒng)中沒有該應(yīng)用的任何進(jìn)程信息揍异。
熱啟動(dòng)就是用戶使用返回鍵退出應(yīng)用全陨,然后馬上又重新啟動(dòng)應(yīng)用。
Application只初始化一次衷掷,冷啟動(dòng)會(huì)先創(chuàng)建Application辱姨,然后初始化MainActivity,熱啟動(dòng)會(huì)直接初始化MainActivity戚嗅。
冷啟動(dòng)流程:
- Zygote進(jìn)程中fork創(chuàng)建一個(gè)新的進(jìn)程雨涛。
- 創(chuàng)建和初始化Application類枢舶,創(chuàng)建MainActivity類
- inflate布局,當(dāng)onCreate/onStart/onResume方法都走完替久。
- 調(diào)用setContetView方法后凉泄,將view添加到DecorView中,調(diào)用view的measuer/layotu/draw顯示到界面上蚯根。
減少冷啟動(dòng)的時(shí)間進(jìn)行優(yōu)化:
- 減少onCreate方法的工作量
第三方sdk的使用最好使用懶加載方式后众,當(dāng)前有些困難 - 不用讓Application參與業(yè)務(wù)的操作。
- 不用再Application進(jìn)行耗時(shí)操作颅拦。
- 不要以靜態(tài)變量的方式在Application中保存數(shù)據(jù)蒂誉。
- 減少布局的深度
性能優(yōu)化工具
android Studio 中 Android Monitor
更多詳細(xì)的參考 http://www.reibang.com/p/797395731747