前言
本文是為了面試而寫的性能優(yōu)化。目的不是為了具體的深入而是對于要面試的同學(xué)在面試的時候能和面試官說出的性能優(yōu)化的方面。在面試的時候基本現(xiàn)在每個面試官都會問一些關(guān)于性能優(yōu)化方法的問題。那么該怎么回答呢?面試不同于我們學(xué)習(xí)新的知識點(diǎn),要完全學(xué)會,要學(xué)精,對于面試官這個問題,可以從下面幾個方面來回答,ANR,內(nèi)存溢出,內(nèi)存抖動,內(nèi)存泄漏,UI卡頓,冷啟動優(yōu)化等方面來回答。
ANR
ANR(Applicatino not responding)是指程序無響應(yīng),主要原因?yàn)椋?/p>
- 主線程被io操作阻塞(4.0后網(wǎng)絡(luò)io不允許主線程中)。
- 主線程做了耗時任務(wù)超過 5秒。
- Service做了耗時操作超過20秒,這是由于service默認(rèn)執(zhí)行在主線程,可以使用IntentService 。
- BroadcastReceiver的onReciver做了耗時操作超過10秒。
解決方式:
- 開一個子線程,使用Handler來處理。
- 使用AsyncTask來處理耗時任務(wù)。
內(nèi)存溢出
內(nèi)存溢出主要是由于加載大的圖片引起的。解決方式:
- 及時釋放bitmap,調(diào)用.recycler(Bitmap會占用java內(nèi)存和c(native)內(nèi)存,java內(nèi)存會自動釋放,c內(nèi)存需要手動釋放)。
- 使用lru 最近最少使用
LruCache來存儲對象put(key,value),,使用的使用LinkHashMap()。 - 計(jì)算inSampleSize
官方提供的方法,使用BitmapFactory.Options來計(jì)算inSampleSize(圖片的縮略比) - 縮略圖
使用Options的inJustDecodeBounds屬性來處理加載縮略圖 - 三級緩存
內(nèi)存,本地,網(wǎng)絡(luò)。
內(nèi)存抖動
內(nèi)存抖動是指內(nèi)存在短時間內(nèi)頻繁地分配和回收,而頻繁的gc會導(dǎo)致卡頓,嚴(yán)重時和內(nèi)存泄漏一樣會導(dǎo)致OOM。
常見的內(nèi)存抖動場景:
- 循環(huán)中創(chuàng)建大量臨時對象;
- onDraw中創(chuàng)建Paint或Bitmap對象等;
內(nèi)存抖動的原因:
瞬間產(chǎn)生大量的對象會嚴(yán)重占用新生代的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值,剩余空間不夠的時候,就會觸發(fā)GC。系統(tǒng)花費(fèi)在GC上的時間越多,進(jìn)行界面繪制或流音頻處理的時間就越短。即使每次分配的對象占用了很少的內(nèi)存,但是他們疊加在一起會增加Heap的壓力,從而觸發(fā)更多其他類型的GC。這個操作有可能會影響到幀率,并使得用戶感知到性能問題。
內(nèi)存泄漏
內(nèi)存泄漏是指無用對象(不在使用的對象)持續(xù)占有內(nèi)存或無用對象的內(nèi)存得不到及時釋放,從而造成的內(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;
}
}
上面是一個比較簡單的單例模式用法,需要外部傳入一個 Context 來獲取該類的實(shí)例,如果此時傳入的 Context 是 Activity 的話,此時單例就有持有該 Activity 的強(qiáng)引用(直到整個應(yīng)用生命周期結(jié)束)。這樣的話,即使該 Activity 退出,該 Activity 的內(nèi)存也不會被回收,這樣就造成了內(nèi)存泄露,特別是一些比較大的 Activity,甚至還會導(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,此時單例引用的對象是 Application,而 Application 的生命周期本來就跟應(yīng)用程序是一樣的,也就不存在內(nèi)存泄露。
2.內(nèi)部類導(dǎo)致內(nèi)存泄漏
非靜態(tài)內(nèi)部類會默認(rèn)持有外部類的引用。會導(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后,延時消息會繼續(xù)存在主線程消息隊(duì)列中8秒鐘,然后處理消息。而該消息引用了Activity的Handler對象,然后這個Handler又引用了這個Activity。這些引用對象會保持到該消息被處理完,這樣就導(dǎo)致該Activity對象無法被回收,從而導(dǎo)致了上面說的 Activity泄露。
Handler 是個很常用也很有用的類,異步,線程安全等等。如果有下面這樣的代碼,會發(fā)生什么呢? handler.postDeslayed ,假設(shè) delay 時間是幾個小時… 這意味著什么?意味著只要 handler 的消息還沒有被處理結(jié)束,它就一直存活著,包含它的 Activity 就跟著活著。
我們來想辦法修復(fù)它,修復(fù)的方案是 WeakReference ,也就是所謂的弱引用。垃圾回收器在回收的時候,是會忽視掉弱引用的,所以包含它的 Activity 會被正常清理掉。
解決方式
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)建線程并且啟動線程
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線程中做輕微耗時操作,會導(dǎo)致UI線程卡頓
- 布局Layout過于復(fù)雜,無法再16ms內(nèi)完成渲染
60fps-->16ms
60ms一幀 每過16ms就會更新一下ui,要達(dá)到60ms一幀,否則可能會卡頓 - 同一時間動畫執(zhí)行的次數(shù)過多,導(dǎo)致cpu或gpu負(fù)載過重。
- View過度繪制,導(dǎo)致某些像素在同一時間內(nèi)被繪制多次,從而導(dǎo)致cpu,gpu負(fù)載過重。
overdraw
過度繪制, - view頻繁的觸發(fā)measure。layout,導(dǎo)致measure。layout累計(jì)耗時過多以及整個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ī)制
為每一個進(jìn)程分配一個小額的內(nèi)存,然后根據(jù)需要分配更多內(nèi)存。 - 回收機(jī)制
Android的目的是盡可能的運(yùn)行多個進(jìn)程,這樣可以讓用戶不用每次都重新開啟,而是恢復(fù)。當(dāng)內(nèi)存緊張時會按等級殺死進(jìn)程。前臺進(jìn)程>可見進(jìn)程>服務(wù)進(jìn)程>后臺進(jìn)程(lru)>空進(jìn)程。
優(yōu)化方法:
- 當(dāng)Service完成任務(wù)后,盡量停止它。
- 在UI不可見的時候,釋放掉一些只有UI使用的資源
- 在系統(tǒng)內(nèi)存緊張的時候,盡可能多的釋放掉非重要的資源。
- 避免濫用Bitmap導(dǎo)致的內(nèi)存浪費(fèi)。
- 盡量使用少的依賴注入框架
冷啟動的優(yōu)化
冷啟動就是在啟動應(yīng)用前,系統(tǒng)中沒有該應(yīng)用的任何進(jìn)程信息。
熱啟動就是用戶使用返回鍵退出應(yīng)用,然后馬上又重新啟動應(yīng)用。
Application只初始化一次,冷啟動會先創(chuàng)建Application,然后初始化MainActivity,熱啟動會直接初始化MainActivity。
冷啟動流程:
- Zygote進(jìn)程中fork創(chuàng)建一個新的進(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顯示到界面上。
減少冷啟動的時間進(jìn)行優(yōu)化:
- 減少onCreate方法的工作量
第三方sdk的使用最好使用懶加載方式,當(dāng)前有些困難 - 不用讓Application參與業(yè)務(wù)的操作。
- 不用再Application進(jìn)行耗時操作。
- 不要以靜態(tài)變量的方式在Application中保存數(shù)據(jù)。
- 減少布局的深度
性能優(yōu)化工具
android Studio 中 Android Monitor
更多詳細(xì)的參考 http://www.itdecent.cn/p/797395731747