Android面試之性能優(yōu)化

前言

本文是為了面試而寫的性能優(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)存溢出主要是由于加載大的圖片引起的。解決方式:

  1. 及時(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)釋放)喂很。
  2. 使用lru 最近最少使用
    LruCache來存儲對象put(key,value),,使用的使用LinkHashMap()惜颇。
  3. 計(jì)算inSampleSize
    官方提供的方法,使用BitmapFactory.Options來計(jì)算inSampleSize(圖片的縮略比)
  4. 縮略圖
    使用Options的inJustDecodeBounds屬性來處理加載縮略圖
  5. 三級緩存
    內(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)存泄漏:

  1. 單例導(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卡頓

  1. 在UI線程中做輕微耗時(shí)操作扼菠,會(huì)導(dǎo)致UI線程卡頓
  2. 布局Layout過于復(fù)雜,無法再16ms內(nèi)完成渲染
    60fps-->16ms
    60ms一幀 每過16ms就會(huì)更新一下ui坝咐,要達(dá)到60ms一幀循榆,否則可能會(huì)卡頓
  3. 同一時(shí)間動(dòng)畫執(zhí)行的次數(shù)過多,導(dǎo)致cpu或gpu負(fù)載過重墨坚。
  4. View過度繪制秧饮,導(dǎo)致某些像素在同一時(shí)間內(nèi)被繪制多次,從而導(dǎo)致cpu泽篮,gpu負(fù)載過重盗尸。
    overdraw
    過度繪制,
  5. view頻繁的觸發(fā)measure帽撑。layout泼各,導(dǎo)致measure。layout累計(jì)耗時(shí)過多以及整個(gè)view頻繁的重新渲染
  6. 內(nèi)存頻繁觸發(fā)Gc過多亏拉,導(dǎo)致展示阻塞渲染操作
  7. 屯余資源及邏輯導(dǎo)致加載和執(zhí)行緩慢

解決ui卡頓:
1.布局優(yōu)化 include扣蜻,merge,viewsuble
2.背景和圖片等內(nèi)存分配優(yōu)化

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

內(nèi)存管理

  1. 分配機(jī)制
    為每一個(gè)進(jìn)程分配一個(gè)小額的內(nèi)存及塘,然后根據(jù)需要分配更多內(nèi)存莽使。
  2. 回收機(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)化方法:

  1. 當(dāng)Service完成任務(wù)后庇勃,盡量停止它檬嘀。
  2. 在UI不可見的時(shí)候槽驶,釋放掉一些只有UI使用的資源
  3. 在系統(tǒng)內(nèi)存緊張的時(shí)候,盡可能多的釋放掉非重要的資源鸳兽。
  4. 避免濫用Bitmap導(dǎo)致的內(nèi)存浪費(fèi)掂铐。
  5. 盡量使用少的依賴注入框架

冷啟動(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)流程:

  1. Zygote進(jìn)程中fork創(chuàng)建一個(gè)新的進(jìn)程雨涛。
  2. 創(chuàng)建和初始化Application類枢舶,創(chuàng)建MainActivity類
  3. inflate布局,當(dāng)onCreate/onStart/onResume方法都走完替久。
  4. 調(diào)用setContetView方法后凉泄,將view添加到DecorView中,調(diào)用view的measuer/layotu/draw顯示到界面上蚯根。

減少冷啟動(dòng)的時(shí)間進(jìn)行優(yōu)化:

  1. 減少onCreate方法的工作量
    第三方sdk的使用最好使用懶加載方式后众,當(dāng)前有些困難
  2. 不用讓Application參與業(yè)務(wù)的操作。
  3. 不用再Application進(jìn)行耗時(shí)操作颅拦。
  4. 不要以靜態(tài)變量的方式在Application中保存數(shù)據(jù)蒂誉。
  5. 減少布局的深度

性能優(yōu)化工具

android Studio 中 Android Monitor

更多詳細(xì)的參考 http://www.reibang.com/p/797395731747

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市矩距,隨后出現(xiàn)的幾起案子拗盒,更是在濱河造成了極大的恐慌,老刑警劉巖锥债,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陡蝇,死亡現(xiàn)場離奇詭異,居然都是意外死亡哮肚,警方通過查閱死者的電腦和手機(jī)登夫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允趟,“玉大人恼策,你說我怎么就攤上這事〕奔簦” “怎么了涣楷?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抗碰。 經(jīng)常有香客問我狮斗,道長,這世上最難降的妖魔是什么弧蝇? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任碳褒,我火速辦了婚禮,結(jié)果婚禮上看疗,老公的妹妹穿的比我還像新娘沙峻。我一直安慰自己,他們只是感情好两芳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布摔寨。 她就那樣靜靜地躺著,像睡著了一般怖辆。 火紅的嫁衣襯著肌膚如雪是复。 梳的紋絲不亂的頭發(fā)上沉填,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機(jī)與錄音佑笋,去河邊找鬼翼闹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蒋纬,可吹牛的內(nèi)容都是我干的猎荠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蜀备,長吁一口氣:“原來是場噩夢啊……” “哼关摇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碾阁,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤输虱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后脂凶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宪睹,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年蚕钦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亭病。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘶居,死狀恐怖罪帖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邮屁,我是刑警寧澤整袁,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站佑吝,受9級特大地震影響坐昙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜迹蛤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一民珍、第九天 我趴在偏房一處隱蔽的房頂上張望襟士。 院中可真熱鬧盗飒,春花似錦、人聲如沸陋桂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗜历。三九已至宣渗,卻和暖如春抖所,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痕囱。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工田轧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鞍恢。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓傻粘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帮掉。 傳聞我的和親對象是個(gè)殘疾皇子弦悉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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

  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料蟆炊? 從這篇文章中你...
    hw1212閱讀 12,723評論 2 59
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,110評論 25 707
  • 面試必背 會(huì)舍棄稽莉、總結(jié)概括——根據(jù)我這些年面試和看面試題搜集過來的知識點(diǎn)匯總而來 建議根據(jù)我的寫的面試應(yīng)對思路中的...
    luoyangzk閱讀 6,755評論 6 173
  • 我享受著藍(lán)天暖陽 腳下踏著自由的想象 我感到快活安然 突然迎面走來熟人三倆 在歡聲笑語地交談 擦肩而過后我的身影 ...
    喜樂心記閱讀 186評論 0 1
  • 我現(xiàn)在發(fā)朋友圈的內(nèi)容真的是越來越少了污秆,除了曬曬家庭主婦的吃喝生活,偶爾轉(zhuǎn)轉(zhuǎn)驚艷到自己的圖文外昧甘,就很少發(fā)一些碎碎念和...
    玄月之佑閱讀 5,623評論 0 93