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

前言

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

  1. 及時釋放bitmap,調(diào)用.recycler(Bitmap會占用java內(nèi)存和c(native)內(nèi)存,java內(nèi)存會自動釋放,c內(nèi)存需要手動釋放)。
  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)存抖動

內(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)存泄漏:

  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; 
    } 
}

上面是一個比較簡單的單例模式用法,需要外部傳入一個 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卡頓

  1. 在UI線程中做輕微耗時操作,會導(dǎo)致UI線程卡頓
  2. 布局Layout過于復(fù)雜,無法再16ms內(nèi)完成渲染
    60fps-->16ms
    60ms一幀 每過16ms就會更新一下ui,要達(dá)到60ms一幀,否則可能會卡頓
  3. 同一時間動畫執(zhí)行的次數(shù)過多,導(dǎo)致cpu或gpu負(fù)載過重。
  4. View過度繪制,導(dǎo)致某些像素在同一時間內(nèi)被繪制多次,從而導(dǎo)致cpu,gpu負(fù)載過重。
    overdraw
    過度繪制,
  5. view頻繁的觸發(fā)measure。layout,導(dǎo)致measure。layout累計(jì)耗時過多以及整個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ī)制
    為每一個進(jìn)程分配一個小額的內(nèi)存,然后根據(jù)需要分配更多內(nèi)存。
  2. 回收機(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)化方法:

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

冷啟動的優(yōu)化

冷啟動就是在啟動應(yīng)用前,系統(tǒng)中沒有該應(yīng)用的任何進(jìn)程信息。
熱啟動就是用戶使用返回鍵退出應(yīng)用,然后馬上又重新啟動應(yīng)用。

Application只初始化一次,冷啟動會先創(chuàng)建Application,然后初始化MainActivity,熱啟動會直接初始化MainActivity。

冷啟動流程:

  1. Zygote進(jìn)程中fork創(chuàng)建一個新的進(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顯示到界面上。

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

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

性能優(yōu)化工具

android Studio 中 Android Monitor

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容