Android性能優(yōu)化

Android的性能優(yōu)化,主要是從以下幾個方面進(jìn)行優(yōu)化的: 穩(wěn)定(內(nèi)存溢出、崩潰) 流暢(卡頓) 耗損(耗電、流量) 安裝包(APK瘦身) 影響穩(wěn)定性的原因很多,比如內(nèi)存使用不合理、代碼異常場景考慮不周全、代碼邏輯不合理等,都會對應(yīng)用的穩(wěn)定性造成影響。其中最常見的兩個場景是:Crash 和 ANR,這兩個錯誤將會使得程序無法使用。所以做好Crash全局監(jiān)控,處理閃退同時把崩潰信息、異常信息收集記錄起來,以便后續(xù)分析;合理使用主線程處理業(yè)務(wù),不要在主線程中做耗時操作,防止ANR程序無響應(yīng)發(fā)生。

內(nèi)存是Android運行性能至關(guān)重要的一項指標(biāo),每個進(jìn)程能使用的內(nèi)存是有限的。不合理的使用內(nèi)存會導(dǎo)致頻繁的GC、甚至發(fā)生OOM,過多GC會導(dǎo)致App卡頓,而內(nèi)存泄漏或者內(nèi)存抖動都可以導(dǎo)致OOM,這是無法接受的。

image.png
總結(jié)一下幾點

......

  1. 如果父控件有顏色,也是自己需要的顏色,那么就不必在子控件加背景顏色
  2. 如果每個自控件的顏色不太一樣,而且可以完全覆蓋父控件,那么就不需要再父控件上加背景顏色
  3. 盡量減少不必要的嵌套
  4. 能用LinearLayout和FrameLayout,就不要用RelativeLayout,因為RelativeLayout控件相對比較復(fù)雜,測繪也想要耗時。
  5. 使用include和merge增加復(fù)用,減少層級
  6. ViewStub按需加載,更加輕便
  7. 復(fù)雜界面可選擇ConstraintLayout,可有效減少層級
  8. onDraw中不要創(chuàng)建新的局部對象
  9. onDraw方法中不要做耗時的任務(wù)
  10. 解決各個情況下的內(nèi)存泄漏,注意平時代碼的規(guī)范。
  11. 利用提前展示出來的Window,快速展示出來一個界面,給用戶快速反饋的體驗;
  12. 避免在啟動時做密集沉重的初始化(Heavy app initialization);
  13. 避免I/O操作、反序列化、網(wǎng)絡(luò)操作、布局嵌套等。
  14. 代碼混淆
  15. 插件化
  16. 資源優(yōu)化
  17. 使用JobScheduler調(diào)度任務(wù)
  18. 使用懶惰法則
  19. ListView使用ViewHolder,分段,分頁加載
  20. 壓縮Bitmap
  21. 使用線程池優(yōu)化線程
  22. 避免在主線程中做耗時操作

穩(wěn)定(內(nèi)存溢出,崩潰)

內(nèi)存泄露

通俗來講,內(nèi)存泄露不僅僅會造成應(yīng)用內(nèi)存占用過大,還會導(dǎo)致應(yīng)用卡頓,造成不好的用戶體驗。

image.png

這就是Android開發(fā)童鞋需要了解的Generational Heap Memory模型,這里我們只關(guān)心當(dāng)對象在Young Generation中存活了一段時間之后,如果沒被干掉,那么會被移動到Old Generation中,同理,最后會移動到Permanent Generation中。那么用腳想一想就知道,如果內(nèi)存泄露了,那么,抱歉,你那塊內(nèi)存隨時間推移自然而然將進(jìn)入Permanent Generation中,然鵝,內(nèi)存不是白菜,想要多少就有多少,這里,因為沙盒機制的原因,分配給你應(yīng)用的內(nèi)存當(dāng)然是有那么一個極限值的,你不能逾越(有人笑了,不是有l(wèi)arge heap么,當(dāng)然我也笑了,我并沒有看到這貨被宗師android玩家青睞過),好了,你那塊造成泄露內(nèi)存的對象占著茅坑不拉屎,剩下來可以供其他對象發(fā)揮的內(nèi)存空間就少了;打個比方,舞臺小了,演員要登臺表演,沒有多余空間,他就只能等待其他演員下來他才能表演啊,這等待的時間,是沒法連續(xù)表演的,所以就卡了嘛。

頻繁GC

什么時候會導(dǎo)致頻繁GC
  • 內(nèi)存抖動
    短時間內(nèi)創(chuàng)建了大量對象同時又被快速釋放。比如在一個大循環(huán)里去不斷創(chuàng)建對象,會導(dǎo)致頻繁gc;

  • 內(nèi)存泄漏
    內(nèi)存泄漏會導(dǎo)致可用內(nèi)存逐漸變少,而且內(nèi)存碎片加多,這也會增多gc次數(shù),甚至可能發(fā)生OOM

  • 一次申請?zhí)髢?nèi)存空間
    由于內(nèi)存碎片的存在,就算內(nèi)存本身足夠,但由于碎片導(dǎo)致無法找到一塊大空間,這也會觸發(fā)gc;

OOM問題

  • 內(nèi)存泄漏了

  • 大量不可見的對象占據(jù)內(nèi)存,這個其實,很常見,只是大家可能一直不太關(guān)心罷了,比如,請求接口返回了列表有100項數(shù)據(jù),每項數(shù)據(jù)比如有100個字段,其中你用戶展示數(shù)據(jù)的只有10幾個而已,但是,你解析的時候,剩下的99個不知不覺吃了你的內(nèi)存,當(dāng),有個胖子要內(nèi)存時,呵呵,嗝屁了

  • 還有一種很常見的場景是一個頁面多圖的場景,明明每個圖只需要加載一個100100的,你卻使用原始尺寸(10801980)or更大,而且你一下子還加載個幾十張,扛得住么?所以了解一下inSampleSize,或者,如果圖片歸你們上傳管理,你可以借助萬象優(yōu)圖,他為你做了剪切好不同尺寸的圖片,這樣省得你在客戶端做圖片縮放了

內(nèi)存泄露

內(nèi)存泄漏指的是那些程序不再使用的對象無法被GC識別,這樣就導(dǎo)致這個對象一直留在內(nèi)存當(dāng)中,占用了沒來就不多的內(nèi)存空間。

那么什么情況下會出現(xiàn)這樣的對象呢? 基本可以分為以下四大類:
1、集合類泄漏
2、單例/靜態(tài)變量造成的內(nèi)存泄漏
3、匿名內(nèi)部類/非靜態(tài)內(nèi)部類
4、資源未關(guān)閉造成的內(nèi)存泄漏

集合類泄漏

集合類添加元素后,仍引用著集合元素對象,導(dǎo)致該集合中的元素對象無法被回收,從而導(dǎo)致內(nèi)存泄露。

static List<Object> mList = new ArrayList<>();
   for (int i = 0; i < 100; i++) {
       Object obj = new Object();
      mList.add(obj);
       obj = null;
    }

當(dāng)mList沒用的時候,我們?nèi)绻蛔鎏幚淼脑?,這就是典型的占著茅坑不拉屎,mList內(nèi)部持有者眾多集合元素的對象,不泄露天理難容啊。解決這個問題也超級簡單。把mList清理掉,然后把它的引用也給釋放掉。

  mList.clear();
  mList = null;

單例模式具有其 靜態(tài)特性,它的生命周期 等于應(yīng)用程序的生命周期,正是因為這一點,往往很容易造成內(nèi)存泄漏。 先來一個小栗子:

public class SingleInstance {

    private static SingleInstance mInstance;
    private Context mContext;

    private SingleInstance(Context context){
        this.mContext = context;
    }

    public static SingleInstance newInstance(Context context){
        if(mInstance == null){
            mInstance = new SingleInstance(context);
        }
        return sInstance;
    }
}

單例/靜態(tài)變量造成的內(nèi)存泄漏

當(dāng)我們在Activity里面使用這個的時候,把我們Acitivty的context傳進(jìn)去,那么,這個單例就持有這個Activity的引用,當(dāng)這個Activity沒有用了,需要銷毀的時候,因為這個單例還持有Activity的引用,所以無法GC回收,所以就出現(xiàn)了內(nèi)存泄漏,也就是生命周期長的持有了生命周期短的引用,造成了內(nèi)存泄漏。

public class SingleInstance {

    private static SingleInstance mInstance;
    private Context mContext;

    private SingleInstance(Context context){
        this.mContext = context.getApplicationContext();
    }

    public static SingleInstance newInstance(Context context){
        if(mInstance == null){
            mInstance = new SingleInstance(context);
        }
        return sInstance;
    }
}

匿名內(nèi)部類/非靜態(tài)內(nèi)部類
image.png

非靜態(tài)內(nèi)部類他會持有他外部類的引用,從圖我們可以看到非靜態(tài)內(nèi)部類的生命周期可能比外部類更長,這就是二樓的情況一致了,如果非靜態(tài)內(nèi)部類的周明周期長于外部類,在加上自動持有外部類的強引用,我的乖乖,想不泄漏都難啊。

public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        new MyAscnyTask().execute();
    }

    class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

我們經(jīng)常會用這個方法去異步加載,然后更新數(shù)據(jù)。貌似很平常,我們開始學(xué)這個的時候就是這么寫的,沒發(fā)現(xiàn)有問題啊,但是你這么想一想,MyAscnyTask是一個非靜態(tài)內(nèi)部類,如果他處理數(shù)據(jù)的時間很長,極端點我們用sleep 100秒,在這期間Activity可能早就關(guān)閉了,本來Activity的內(nèi)存應(yīng)該被回收的,但是我們知道非靜態(tài)內(nèi)部類會持有外部類的引用,所以Activity也需要陪著非靜態(tài)內(nèi)部類MyAscnyTask一起天荒地老。好了,內(nèi)存泄漏就形成了。

既然MyAscnyTask的生命周期可能比較長,那就把它變成靜態(tài),和Application玩去吧,這樣MyAscnyTask就不會再持有外部類的引用了。兩者也相互獨立了。

public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        new MyAscnyTask().execute();
    }
//改了這里 注意一下 static
   static  class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

說完非靜態(tài)內(nèi)部類,我再來看看匿名內(nèi)部類,這個問題很常見,匿名內(nèi)部類和非靜態(tài)內(nèi)部類有一個共同的地方,就是會只有外部類的強引用,所以這哥倆本質(zhì)是一樣的。但是處理方法有些不一樣。但是思路絕對一樣。換湯不換藥。

public class TestActivity extends Activity {
private TextView mText;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
//do something
mText.setText(" do someThing");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
mText = findVIewById(R.id.mText);
        //  匿名線程持有 Activity 的引用,進(jìn)行耗時操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    
        mHandler. sendEmptyMessageDelayed(0, 100000);
    }

想必這兩個方法是我們經(jīng)常用的吧,很熟悉,也是這么學(xué)的,沒感覺不對啊,老師就是這么教的,通過我們上面的分析,還這么想嗎?關(guān)鍵是 耗時時間過長,造成內(nèi)部類的生命周期大于外部類,對弈非靜態(tài)內(nèi)部類,我們可以靜態(tài)化,至于匿名內(nèi)部類怎么辦呢?一樣把它變成靜態(tài)內(nèi)部類,也就是說盡量不要用匿名內(nèi)部類。完事了嗎?很多人不注意這么一件事,如果我們在handleMessage方法里進(jìn)行UI的更新,這個Handler靜態(tài)化了和Activity沒啥關(guān)系了,但是比如這個mText,怎么說?全寫是activity.mText,看到了吧,持有了Activity的引用,也就是說Handler費勁心思變成靜態(tài)類,自認(rèn)為不持有Activity的引用了,準(zhǔn)確的說是不自動持有Activity的引用了,但是我們要做UI更新的時候勢必會持有Activity的引用,靜態(tài)類持有非靜態(tài)類的引用,我們發(fā)現(xiàn)怎么又開始內(nèi)存泄漏了呢?處處是坑啊,怎么辦呢?我們這里就要引出弱引用的概念了。

引用分為強引用,軟引用,弱引用,虛引用,強度依次遞減。

  • 強引用
    我們平時不做特殊處理的一般都是強引用,如果一個對象具有強引用,GC寧可OOM也絕不會回收它??闯龆鄰娪擦税?。

  • 軟引用(SoftReference)
    如果內(nèi)存空間足夠,GC就不會回收它,如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。

  • 弱引用(WeakReference)
    弱引用要比軟引用,更弱一個級別,內(nèi)存不夠要回收他,GC的時候不管內(nèi)存夠不夠也要回收他,簡直是弱的一匹。不過GC是一個優(yōu)先級很低的線程,也不是太頻繁進(jìn)行,所以弱引用的生活還過得去,沒那么提心吊膽。

  • 虛引用
    用的甚少,我沒有用過,如果想了解的朋友,可以自行谷歌百度。

所以我們用弱引用來修飾Activity,這樣GC的時候,該回收的也就回收了,不會再有內(nèi)存泄漏了。很完美。

public class TestActivity extends Activity {
    private TextView mText;
    private MyHandler myHandler = new MyHandler(TestActivity.this);
    private MyThread myThread = new MyThread();

    private static class MyHandler extends Handler {

        WeakReference<TestActivity> weakReference;

        MyHandler(TestActivity testActivity) {
            this.weakReference = new WeakReference<TestActivity>(testActivity);

        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            weakReference.get().mText.setText("do someThing");

        }
    }

    private static class MyThread extends Thread {

        @Override
        public void run() {
            super.run();

            try {
                sleep(100000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        mText = findViewById(R.id.mText);
        myHandler.sendEmptyMessageDelayed(0, 100000);
        myThread.start();
    }
//最后清空這些回調(diào) 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);
    }

資源未關(guān)閉造成的內(nèi)存泄漏
  • 網(wǎng)絡(luò)、文件等流忘記關(guān)閉

  • 手動注冊廣播時,退出時忘記 unregisterReceiver()

  • Service 執(zhí)行完后忘記 stopSelf()

  • EventBus 等觀察者模式的框架忘記手動解除注冊

工具

1、leakcanary傻瓜式操作,哪里有泄漏自動給你顯示出來,很直接很暴力。

2、我們平時也要多使用Memory Monitor進(jìn)行內(nèi)存監(jiān)控,這個分析就有些難度了,可以上網(wǎng)搜一下具體怎么使用。

3、Android Lint 它可以幫助我們發(fā)現(xiàn)代碼機構(gòu) / 質(zhì)量問題,同時提供一些解決方案,內(nèi)存泄露的會飄黃,用起來很方便,具體使用方法上網(wǎng)學(xué)習(xí),這里不多做說明了。

LeakCanary

LeakCanary提供了一種很方便的方式,讓我們在開發(fā)階段測試內(nèi)存泄露,我們不需要自己根據(jù)內(nèi)存塊來分析內(nèi)存泄露的原因,我們只需要在項目中集成他,然后他就會幫我們檢測內(nèi)存泄露,并給出內(nèi)存泄露的引用鏈

通過監(jiān)聽Activity的onDestory,手動調(diào)用GC,然后通過ReferenceQueue+WeakReference,來判斷Activity對象是否被回收,然后結(jié)合dump Heap的hpof文件,通過Haha開源庫分析泄露的位置

LeakCanary主要的知識點

注冊Activity的生命周期的監(jiān)聽器

通過Application.registerActivityLifecycleCallbacks()方法注冊Activity的生命周期的監(jiān)聽器,每一個Activity的生命周期都會回調(diào)到這個ActivityLifecycleCallbacks上,如果一個Activity走到了onDestory,那么就意味著他就不再存在,然后檢測這個Activity是否是真的被銷毀

通過ReferenceQueue+WeakReference,來判斷對象是否被回收

WeakReference創(chuàng)建時,可以傳入一個ReferenceQueue對象,假如WeakReference中引用對象被回收,那么就會把WeakReference對象添加到ReferenceQueue中,可以通過ReferenceQueue中是否為空來判斷,被引用對象是否被回收

快(流暢、卡頓)

image.png
布局優(yōu)化

屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次。在多層次的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制的操作,這就會導(dǎo)致某些像素區(qū)域被繪制了多次。這就浪費大量的CPU以及GPU資源。

  • 如果父控件有顏色,也是自己需要的顏色,那么就不必在子控件加背景顏色

  • 如果每個自控件的顏色不太一樣,而且可以完全覆蓋父控件,那么就不需要再父控件上加背景顏色

  • 盡量減少不必要的嵌套

  • 能用LinearLayout和FrameLayout,就不要用RelativeLayout,因為RelativeLayout控件相對比較復(fù)雜,測繪也想要耗時。

  • 使用include和merge增加復(fù)用,減少層級

  • ViewStub按需加載,更加輕便

  • 復(fù)雜界面可選擇ConstraintLayout,可有效減少層級

繪制優(yōu)化

我們平時感覺的卡頓問題最主要的原因之一是因為渲染性能,因為越來越復(fù)雜的界面交互,其中可能添加了動畫,或者圖片等等。我們希望創(chuàng)造出越來越炫的交互界面,同時也希望他可以流暢顯示,但是往往卡頓就發(fā)生在這里。

這個是Android的渲染機制造成的,Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進(jìn)行渲染,但是渲染未必成功,如果成功了那么代表一切順利,但是失敗了可能就要延誤時間,或者直接跳過去,給人視覺上的表現(xiàn),就是要么卡了一會,要么跳幀。

View的繪制頻率保證60fps是最佳的,這就要求每幀繪制時間不超過16ms(16ms = 1000/60),雖然程序很難保證16ms這個時間,但是盡量降低onDraw方法中的復(fù)雜度總是切實有效的。

這個正常情況下,每隔16ms draw()一下,很整齊,很流暢,很完美。

image.png

往往會發(fā)生如下圖的情況,有個便秘的家伙霸占著,一幀畫面拉的時間那么長,這一下可不就卡頓了嘛。把后面的時間給占用了,后面只能延后,或者直接略過了。

image.png
  • onDraw中不要創(chuàng)建新的局部對象

  • onDraw方法中不要做耗時的任務(wù)

啟動速度優(yōu)化

app啟動分為冷啟動(Cold start)、熱啟動(Hot start)和溫啟動(Warm start)三種。

冷啟動

冷啟動是指應(yīng)用程序從頭開始:系統(tǒng)的進(jìn)程在此開始之前沒有創(chuàng)建應(yīng)用程序。冷啟動發(fā)生在諸如自設(shè)備啟動以來首次啟動應(yīng)用程序或自系統(tǒng)終止應(yīng)用程序以來。

在冷啟動開始時,系統(tǒng)有三個任務(wù)。這些任務(wù)是: 1、加載并啟動應(yīng)用程序 2、啟動后立即顯示應(yīng)用程序的空白啟動窗口 3、創(chuàng)建應(yīng)用程序進(jìn)程

當(dāng)系統(tǒng)為我們創(chuàng)建了應(yīng)用進(jìn)程之后,開始創(chuàng)建應(yīng)用程序?qū)ο蟆?/p>

1、啟動主線程
2、創(chuàng)建主Activity
3、加載布局
4、屏幕布局
5、執(zhí)行初始繪制

應(yīng)用程序進(jìn)程完成第一次繪制后,系統(tǒng)進(jìn)程會交換當(dāng)前顯示的背景窗口,將其替換為主活動。此時,用戶可以開始使用該應(yīng)用程序。至此啟動完成。

image.png

Application創(chuàng)建

當(dāng)Application啟動時,空白的啟動窗口將保留在屏幕上,直到系統(tǒng)首次完成繪制應(yīng)用程序。此時,系統(tǒng)進(jìn)程會交換應(yīng)用程序的啟動窗口,允許用戶開始與應(yīng)用程序進(jìn)行交互。這就是為什么我們的程序啟動時會先出現(xiàn)一段時間的黑屏(白屏)。

如果我們有自己的Application,系統(tǒng)會onCreate()在我們的Application對象上調(diào)用該方法。之后,應(yīng)用程序會生成主線程(也稱為UI線程),并通過創(chuàng)建主要活動來執(zhí)行任務(wù)。

從這一點開始,App就按照他的 應(yīng)用程序生命周期階段進(jìn)行

Activity創(chuàng)建

應(yīng)用程序進(jìn)程創(chuàng)建活動后,活動將執(zhí)行以下操作:

  1. 初始化值。
  2. 調(diào)用構(gòu)造函數(shù)。
  3. 調(diào)用回調(diào)方法,例如 Activity.onCreate(),對應(yīng)Activity的當(dāng)前生命周期狀態(tài)。

通常,該 onCreate()方法對加載時間的影響最大,因為它以最高的開銷執(zhí)行工作:加載和膨脹視圖,以及初始化活動運行所需的對象。

熱啟動

應(yīng)用程序的熱啟動比冷啟動要簡單得多,開銷也更低。在一個熱啟動中,系統(tǒng)都會把你的Activity帶到前臺。如果應(yīng)用程序的Activity仍然駐留在內(nèi)存中,那么應(yīng)用程序可以避免重復(fù)對象初始化、布局加載和渲染。

熱啟動顯示與冷啟動方案相同的屏幕行為:系統(tǒng)進(jìn)程顯示空白屏幕,直到應(yīng)用程序完成呈現(xiàn)活動。

溫啟動

溫啟動包含了冷啟動時發(fā)生的一些操作,與此同時,它表示的開銷比熱啟動少,有許多潛在的狀態(tài)可以被認(rèn)為是溫暖的開始。

場景:
  • 用戶退出您的應(yīng)用,但隨后重新啟動它。該過程可能已繼續(xù)運行,但應(yīng)用程序必須通過調(diào)用從頭開始重新創(chuàng)建Activity 的onCreate()。
  • 系統(tǒng)將您的應(yīng)用程序從內(nèi)存中逐出,然后用戶重新啟動它。需要重新啟動進(jìn)程和活動,但是在調(diào)用onCreate()的時候可以從Bundle(savedInstanceState)獲取數(shù)據(jù)。

了解完啟動過程,我們就知道哪里會影響我們啟動的速度了。在創(chuàng)建應(yīng)用程序和創(chuàng)建Activity期間都可能會出現(xiàn)性能問題。

這里是慢的定義:

  • 啟動需要5秒或更長時間。
  • 啟動需要2秒或更長時間。
  • 啟動需要1.5秒或更長時間。

無論何種啟動,我們的優(yōu)化點都是: Application、Activity創(chuàng)建以及回調(diào)等過程

谷歌官方給的建議是:
1、利用提前展示出來的Window,快速展示出來一個界面,給用戶快速反饋的體驗;
2、避免在啟動時做密集沉重的初始化(Heavy app initialization);
3、避免I/O操作、反序列化、網(wǎng)絡(luò)操作、布局嵌套等。

具體做法:

針對1:利用提前展示出來的Window,快速展示出來一個界面

使用Activity的windowBackground主題屬性來為啟動的Activity提供一個簡單的drawable。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>

<activity ...
android:theme="@style/AppTheme.Launcher" />

這樣在啟動的時候,會先展示一個界面,這個界面就是Manifest中設(shè)置的Style,等Activity加載完畢后,再去加載Activity的界面,而在Activity的界面中,我們將主題重新設(shè)置為正常的主題,從而產(chǎn)生一種快的感覺。其實就是個障眼法而已,提前讓你看到了假的界面。也算是一種不錯的方法,但是治標(biāo)不治本。

針對2:避免在啟動時做密集沉重的初始化

我們審視一下我們的MyApplication里面的操作。初始化操作有友盟,百度,bugly,數(shù)據(jù)庫,IM,神策,圖片加載庫,網(wǎng)絡(luò)請求庫,廣告sdk,地圖,推送,等等,這么多需要初始化,Application的任務(wù)太重了,啟動不慢才怪呢。

怎么辦呢?這些還都是必要的,不能不去初始化啊,那就只能異步加載了。但是并不是所有的都可以進(jìn)行異步處理。這里分情況給出一些建議:

1、比如像友盟,bugly這樣的業(yè)務(wù)非必要的可以的異步加載。

2、比如地圖,推送等,非第一時間需要的可以在主線程做延時啟動。當(dāng)程序已經(jīng)啟動起來之后,在進(jìn)行初始化。

3、對于圖片,網(wǎng)絡(luò)請求框架必須在主線程里初始化了。

同時因為我們一般會有閃屏頁面,也可以把延時啟動的地圖,推動的啟動在這個時間段里,這樣合理安排時間片的使用。極大的提高了啟動速度。

針對3:避免I/O操作、反序列化、網(wǎng)絡(luò)操作、布局嵌套等。

1. GPU過度繪制,定位過度繪制區(qū)域

這里直接在開發(fā)者選項,打開Show GPU Overdraw,就可以看到效果,輕松發(fā)現(xiàn)哪塊需要優(yōu)化。

  • 減少布局層級
    使用ConstraintLayout替換傳統(tǒng)的布局方式。

  • 檢查是否有多余的背景色設(shè)置

我們通常會犯一些低級錯誤--對被覆蓋的父view設(shè)置背景,多數(shù)情況下這些背景是沒有必要的。

2. 主線程耗時操作排查

  • 開啟strictmode,這樣一來,主線程的耗時操作都將以告警的形式呈現(xiàn)到logcat當(dāng)中

StrictMode,嚴(yán)苛模式,是Android提供的一種運行時檢測機制,用于檢測代碼運行時的一些不規(guī)范的操作,最常見的場景是用于發(fā)現(xiàn)主線程的IO操作和網(wǎng)絡(luò)讀寫等耗時的操作。

3. 對于measure,layout耗時過多的問題

一般這類問題是由于布局過于復(fù)雜的原因?qū)е?,現(xiàn)在因為有ConstraintLayout,所以,強烈建議使用ConstraintLayout減少布局層級,問題一般得以解決,如果發(fā)現(xiàn)還存在性能問題,可以使用traceView觀察方法耗時,來定位下具體原因。

TraceView 是 Android SDK 內(nèi)置的一個工具,它可以加載 trace 文件,用圖形的形式展示代碼的執(zhí)行時間、次數(shù)及調(diào)用棧,便于我們分析。

4.

5. onDraw里面寫代碼需要注意

onDraw由于大概每16ms都會被執(zhí)行一次,因此本身就相當(dāng)于一個forloop,如果你在里面new對象的話,不知不覺中就滿足了短時間內(nèi)大量對象創(chuàng)建并釋放,于是頻繁GC就發(fā)生了,嗯,內(nèi)存抖動,于是,卡了。因此,正確的做法是將對象放在外面new出來。

第一點: onDraw方法中不要做耗時的任務(wù),也不做過多的循環(huán)操作,特別是嵌套循環(huán),雖然每次循環(huán)耗時很小,但是大量的循環(huán)勢必霸占CPU的時間片,從而造成View的繪制過程不流暢。

第二點: 除了循環(huán)之外,onDraw()中不要創(chuàng)建新的局部對象,因為onDraw()方法一般都會頻繁大量調(diào)用,就意味著會產(chǎn)生大量的零時對象,不進(jìn)占用過的內(nèi)存,而且會導(dǎo)致系統(tǒng)更加頻繁的GC,大大降低程序的執(zhí)行速度和效率。

6. json反序列化問題

json反序列化是指將json字符串轉(zhuǎn)變?yōu)閷ο?,這里如果數(shù)據(jù)量比較多,特別是有相當(dāng)多的string的時候,解析起來不僅耗時,而且還很吃內(nèi)存。解決的方式是:

  • 精簡字段,與后臺協(xié)商,相關(guān)接口剔除不必要的字段。保證最小可用原則。

  • 使用流解析,之前我考慮過json解析優(yōu)化,在Stack Overflow上搜索到這個。于是了解到Gson.fromJson是可以這樣玩的,可以提升25%的解析效率。

image.png

7.viewStub&merge&ViewStub的使用

這里merge和viewStub想必是大家非常了解的兩個布局組件了,對于只有在某些條件下才展示出來的組件,建議使用viewStub包裹起來,同樣的道理,include 某布局如果其根布局和引入他的父布局一致,建議使用merge包裹起來,如果你擔(dān)心preview效果問題,這里完全沒有必要,因為你可以tools:showIn=""屬性,這樣就可以正常展示preview了。

ViewStub它可以按需加載,什么意思?用到他的時候喊他一下,再來加載,不需要的時候像空氣一樣,在一邊靜靜的呆著,不吃你的米,也不花你家的錢。等需要的時候ViewStub中的布局才加載到內(nèi)存,多節(jié)儉持家啊。對于一些進(jìn)度條,提示信息等等八百年才用一次的功能,使用ViewStub是極其合適的。這就是不用不知道,一用戒不了。

8.加載優(yōu)化

這里并沒有過多的技術(shù)點在里面,無非就是將耗時的操作封裝到異步中去了,但是,有一點不得不提的是,要注意多進(jìn)程的問題,如果你的應(yīng)用是多進(jìn)程,你應(yīng)該認(rèn)識到你的application的oncreate方法會被執(zhí)行多次,你一定不希望資源加載多次吧,于是你只在主進(jìn)程加載,如是有些坑就出現(xiàn)了,有可能其他進(jìn)程需要那某份資源,然后他這個進(jìn)程缺沒有加載相應(yīng)的資源,然后就嗝屁了。

9.刷新優(yōu)化。

  • item 刷新
    對于列表的中的item的操作,比如對item點贊,此時不應(yīng)該讓整個列表刷新,而是應(yīng)該只刷新這個item.

  • 復(fù)雜Activity刷新
    對于較為復(fù)雜的頁面,個人建議不要寫在一個activity中,建議使用幾個fragment進(jìn)行組裝,這樣一來,module的變更可以只刷新某一個具體的fragment,而不用整個頁面都走刷新邏輯。但是問題來了,fragment之間如何共享數(shù)據(jù)呢?好,看我怎么操作。

image.png

Activity將數(shù)據(jù)這部分抽象成一個LiveData,交個LiveDataManger數(shù)據(jù)進(jìn)行管理,然后各個Fragment通過Activity的這個context從LiveDataManger中拿到LiveData,進(jìn)行操作,通知activity數(shù)據(jù)變更等等。哈哈,你沒有看錯,這個確實和Google的那個LiveData有點像,當(dāng)然,如果你想使用Google的那個,也自然沒問題,只不過,這個是簡化版的。項目的引入
'com.tencent.tip:simple_live_data:1.0.1-SNAPSHOT'

10. 動畫優(yōu)化

耗損(耗電、流量)

耗電優(yōu)化

  • 在定位精度要求不高的情況下,使用wifi或移動網(wǎng)絡(luò)進(jìn)行定位,沒有必要開啟GPS定位。

  • 先驗證網(wǎng)絡(luò)的可用性,在發(fā)送網(wǎng)絡(luò)請求,比如,當(dāng)用戶處于2G狀態(tài)下,而此時的操作是查看一張大圖,下載下來可能都200多K甚至更大,我們沒必要去發(fā)送這個請求,讓用戶一直等待那個菊花吧。

  • 適當(dāng)?shù)淖霰镜鼐彺?,避免頻繁請求網(wǎng)絡(luò)數(shù)據(jù),這里,說起來容易,做起來并非三刀兩斧就能搞定,要配合良好的緩存策略,區(qū)分哪些是一段時間不會變更的,哪些是絕對不能緩存的很重要。

安裝包(Apk瘦身)

既然要瘦身,那么我們必須知道APK的文件構(gòu)成,解壓apk:

image.png

-assets文件夾
存放一些配置文件、資源文件,assets不會自動生成對應(yīng)的 ID,而是通過 AssetManager 類的接口獲取。

  • res目錄
    res 是 resource 的縮寫,這個目錄存放資源文件,會自動生成對應(yīng)的 ID 并映射到 .R 文件中,訪問直接使用資源 ID。

  • META-INF
    保存應(yīng)用的簽名信息,簽名信息可以驗證 APK 文件的完整性。

  • AndroidManifest.xml
    這個文件用來描述 Android 應(yīng)用的配置信息,一些組件的注冊信息、可使用權(quán)限等。

  • classes.dex
    Dalvik 字節(jié)碼程序,讓 Dalvik 虛擬機可執(zhí)行,一般情況下,Android 應(yīng)用在打包時通過 Android SDK 中的 dx 工具將 Java 字節(jié)碼轉(zhuǎn)換為 Dalvik 字節(jié)碼。

  • resources.arsc
    記錄著資源文件和資源 ID 之間的映射關(guān)系,用來根據(jù)資源 ID 尋找資源。

我們需要從代碼和資源兩個方面去減少響應(yīng)的大小。

1、首先我們可以使用lint工具,如果有沒有使用過的資源就會打印如下的信息(不會使用的朋友可以上網(wǎng)看一下)

同時我們可以開啟資源壓縮,自動刪除無用的資源

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.pro'
        }
    }

2、能自己用XML寫Drawable,就自己寫,能不用公司的UI切圖,就別和他們說話,咱們自己造,做自己的UI,美滋滋。而且這種圖片占用空間會很小。

3、重用資源,比如一個三角按鈕,點擊前三角朝上代表收起的意思,點擊后三角朝下,代表展開,一般情況下,我們會用兩張圖來切換,我們完全可以用旋轉(zhuǎn)的形式去改變

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_thumb_up"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fromDegrees="180" />

4、壓縮PNG和JPEG文件 您可以減少PNG文件的大小,而不會丟失使用工具如圖像質(zhì)量 pngcrush,pngquant,或zopflipng。所有這些工具都可以減少PNG文件的大小,同時保持感知的圖像質(zhì)量。

5、使用WebP文件格式 可以使用圖像的WebP文件格式,而不是使用PNG或JPEG文件。WebP格式提供有損壓縮(如JPEG)以及透明度(如PNG),但可以提供比JPEG或PNG更好的壓縮。

可以使用Android Studio將現(xiàn)有的BMP,JPG,PNG或靜態(tài)GIF圖像轉(zhuǎn)換為WebP格式。

6、使用矢量圖形 可以使用矢量圖形來創(chuàng)建與分辨率無關(guān)的圖標(biāo)和其他可伸縮Image。使用這些圖形可以大大減少APK大小。一個100字節(jié)的文件可以生成與屏幕大小相關(guān)的清晰圖像。

但是,系統(tǒng)渲染每個VectorDrawable對象需要花費大量時間 ,而較大的圖像需要更長的時間才能顯示在屏幕上。因此,請考慮僅在顯示小圖像時使用這些矢量圖形。

不要把AnimationDrawable用于創(chuàng)建逐幀動畫,因為這樣做需要為動畫的每個幀包含一個單獨的位圖文件,這會大大增加APK的大小。

7、代碼混淆 使用proGuard 代碼混淆器工具,它包括壓縮、優(yōu)化、混淆等功能。這個大家太熟悉了。不多說了。

8、插件化。 比如功能模塊放在服務(wù)器上,按需下載,可以減少安裝包大小。

減少安裝包大小

代碼混淆。使用IDE 自帶的 proGuard 代碼混淆器工具 ,它包括壓縮、優(yōu)化、混淆等功能。 資源優(yōu)化。比如使用 Android Lint 刪除冗余資源,資源文件最少化等。 圖片優(yōu)化。比如利用 PNG優(yōu)化工具 對圖片做壓縮處理。推薦目前最先進(jìn)的壓縮工具Googlek開源庫zopfli。如果應(yīng)用在0版本以上,推薦使用 WebP圖片格式。 避免重復(fù)或無用功能的第三方庫。例如,百度地圖接入基礎(chǔ)地圖即可、訊飛語音無需接入離線、圖片庫Glide\Picasso等。 插件化開發(fā)。比如功能模塊放在服務(wù)器上,按需下載,可以減少安裝包大小。 可以使用微信開源資源文件混淆工具——AndResGuard。一般可以壓縮apk的1M左右大。

耗電優(yōu)化

我們可能對耗電優(yōu)化不怎么感冒,沒事,谷歌這方面做得也不咋地,5.0之后才有像樣的方案,講實話這個優(yōu)化的優(yōu)先級沒有前面幾個那么高,但是我們也要了解一些避免耗電的坑,至于更細(xì)的耗電分析可以使用這個Battery Historian。

Battery Historian 是由Google提供的Android系統(tǒng)電量分析工具,從手機中導(dǎo)出bugreport文件上傳至頁面,在網(wǎng)頁中生成詳細(xì)的圖表數(shù)據(jù)來展示手機上各模塊電量消耗過程,最后通過App數(shù)據(jù)的分析制定出相關(guān)的電量優(yōu)化的方法。

谷歌推薦使用JobScheduler,來調(diào)整任務(wù)優(yōu)先級等策略來達(dá)到降低損耗的目的。JobScheduler可以避免頻繁的喚醒硬件模塊,造成不必要的電量消耗。避免在不合適的時間(例如低電量情況下、弱網(wǎng)絡(luò)或者移動網(wǎng)絡(luò)情況下的)執(zhí)行過多的任務(wù)消耗電量。

具體功能:
1、可以推遲的非面向用戶的任務(wù)(如定期數(shù)據(jù)庫數(shù)據(jù)更新);
2、當(dāng)充電時才希望執(zhí)行的工作(如備份數(shù)據(jù));
3、需要訪問網(wǎng)絡(luò)或 Wi-Fi 連接的任務(wù)(如向服務(wù)器拉取配置數(shù)據(jù));
4、零散任務(wù)合并到一個批次去定期運行;
5、當(dāng)設(shè)備空閑時啟動某些任務(wù);
6、只有當(dāng)條件得到滿足, 系統(tǒng)才會啟動計劃中的任務(wù)(充電、WIFI...);

同時谷歌針對耗電優(yōu)化也提出了一個懶惰第一的法則:

減少 你的應(yīng)用程序可以刪除冗余操作嗎?例如,它是否可以緩存下載的數(shù)據(jù)而不是重復(fù)喚醒無線電以重新下載數(shù)據(jù)?

推遲 應(yīng)用是否需要立即執(zhí)行操作?例如,它可以等到設(shè)備充電才能將數(shù)據(jù)備份到云端嗎?

合并 可以批處理工作,而不是多次將設(shè)備置于活動狀態(tài)嗎?例如,幾十個應(yīng)用程序是否真的有必要在不同時間打開收音機發(fā)送郵件?在一次喚醒收音機期間,是否可以傳輸消息?

谷歌在耗電優(yōu)化這方面確實顯得有些無力,希望以后可以退出更好的工具和解決方案,不然這方面的優(yōu)化優(yōu)先級還是很低。付出和回報所差太大。

內(nèi)存優(yōu)化準(zhǔn)則

  1. 能不創(chuàng)建的對象就不創(chuàng)建
    比如字符串拼接,可以手動使用StringBuilder,而不是使用"+","+"被編譯器優(yōu)化后會每次創(chuàng)建StringBuilder對象,造成浪費;
    而且,尤其注意在主線程里不要過多創(chuàng)建對象。因為在GC時會鎖住堆內(nèi)存,此時請求分配的線程也會被掛起,這顯然會導(dǎo)致主線程的卡頓。所以在一些主線程高頻函數(shù),如onDraw,onTouchEvent里不要去創(chuàng)建對象。

  2. 盡可能復(fù)用已經(jīng)創(chuàng)建的對象
    還是StringBuilder的例子,基于一個StringBuilder可以通過SetLength(0)支持很多次的字符串拼接。
    多使用系統(tǒng)提供的對象池,比如線程池,Long、Integer、Short等包類型里的緩存值(通過valueOf取),列表view的復(fù)用等

  3. 防止內(nèi)存泄漏
    內(nèi)存一旦發(fā)生泄漏,意味著堆里有一塊區(qū)域持續(xù)被不再使用的變量占據(jù),這自然會導(dǎo)致可用內(nèi)存減少而發(fā)生gc,甚至OOM。

代碼建議

1. SparseArray代替HashMap

建議使用SparseArray代替HashMap,這里是Google建議的,因為SparseArray比HashMap更省內(nèi)存,在某些條件下性能更好,主要是因為它避免了對key的自動裝箱比如(int轉(zhuǎn)為Integer類型),它內(nèi)部則是通過兩個數(shù)組來進(jìn)行數(shù)據(jù)存儲的,一個存儲key,另外一個存儲value,為了優(yōu)化性能,它內(nèi)部對數(shù)據(jù)還采取了壓縮的方式來表示稀疏數(shù)組的數(shù)據(jù),從而節(jié)約內(nèi)存空間。

推薦使用match_parent,或者固定尺寸

不到不得已,不要使用wrap_content,,推薦使用match_parent,或者固定尺寸,配合gravity="center"。
因為 在測量過程中,match_parent和固定寬高度對應(yīng)EXACTLY ,而wrap_content對應(yīng)AT_MOST,這兩者對比AT_MOST耗時較多。

使用Link優(yōu)化項目

Lint 是Android Studio 提供的 代碼掃描分析工具,它可以幫助我們發(fā)現(xiàn)代碼結(jié)構(gòu)/質(zhì)量問題,同時提供一些解決方案,而且這個過程不需要我們手寫測試用例。

ListView和 Bitmap優(yōu)化

針對ListView優(yōu)化,主要是合理使用ViewHolder。創(chuàng)建一個內(nèi)部類ViewHolder,里面的成員變量和view中所包含的組件個數(shù)、類型相同,在convertview為null的時候,把findviewbyId找到的控件賦給ViewHolder中對應(yīng)的變量,就相當(dāng)于先把它們裝進(jìn)一個容器,下次要用的時候,直接從容器中獲取。

現(xiàn)在我們現(xiàn)在一般使用RecyclerView,自帶這個優(yōu)化,不過還是要理解一下原理的好。 然后可以對接受來的數(shù)據(jù)進(jìn)行分段或者分頁加載,也可以優(yōu)化性能。

對于Bitmap,這個我們使用的就比較多了,很容易出現(xiàn)OOM的問題,圖片內(nèi)存的問題可以看一下我之前寫的這篇文章一張圖片占用多少內(nèi)存。

Bitmap的優(yōu)化套路很簡單,粗暴,就是讓壓縮。 三種壓縮方式:
1.對圖片質(zhì)量進(jìn)行壓縮
2.對圖片尺寸進(jìn)行壓縮
3.使用libjpeg.so庫進(jìn)行壓縮

對圖片質(zhì)量進(jìn)行壓縮
  public static Bitmap compressImage(Bitmap bitmap){  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            //質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中  
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
            int options = 100;  
            //循環(huán)判斷如果壓縮后圖片是否大于50kb,大于繼續(xù)壓縮  
            while ( baos.toByteArray().length / 1024>50) {  
                //清空baos  
                baos.reset();  
                bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);  
                options -= 10;//每次都減少10  
            }  
            //把壓縮后的數(shù)據(jù)baos存放到ByteArrayInputStream中  
            ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
            //把ByteArrayInputStream數(shù)據(jù)生成圖片  
            Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);  
            return newBitmap;  
        }  
對圖片尺寸進(jìn)行壓縮
  /**
     * 按圖片尺寸壓縮 參數(shù)是bitmap
     * @param bitmap
     * @param pixelW
     * @param pixelH
     * @return
     */
    public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
        if( os.toByteArray().length / 1024>512) {//判斷如果圖片大于0.5M,進(jìn)行壓縮避免在生成圖片(BitmapFactory.decodeStream)時溢出
            os.reset();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//這里壓縮50%,把壓縮后的數(shù)據(jù)存放到baos中
        }
        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        BitmapFactory.decodeStream(is, null, options);
        options.inJustDecodeBounds = false;
        options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
        is = new ByteArrayInputStream(os.toByteArray());
        Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
        return newBitmap;
    }


    /**
     * 動態(tài)計算出圖片的inSampleSize
     * @param options
     * @param minSideLength
     * @param maxNumOfPixels
     * @return
     */
    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

使用libjpeg.so庫進(jìn)行壓縮

可以參考這篇Android性能優(yōu)化系列之Bitmap圖片優(yōu)化: https://blog.csdn.net/u012124438/article/details/66087785)

線程優(yōu)化

線程優(yōu)化的思想是采用線程池,避免在程序中存在大量的Thread。線程池可以重用內(nèi)部的線程,從而避免了現(xiàn)場的創(chuàng)建和銷毀所帶來的性能開銷,同時線程池還能有效地控制線程池的最大并發(fā)數(shù),避免大量的線程因互相搶占系統(tǒng)資源從而導(dǎo)致阻塞現(xiàn)象發(fā)生。

《Android開發(fā)藝術(shù)探索》對線程池的講解很詳細(xì),不熟悉線程池的可以去了解一下。

  • 優(yōu)點:
    1、減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷。
    2、如不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及”過度切換”。
  • 需要注意的是:
    1、如果線程池中的數(shù)量為達(dá)到核心線程的數(shù)量,則直接會啟動一個核心線程來執(zhí)行任務(wù)。
    2、如果線程池中的數(shù)量已經(jīng)達(dá)到或超過核心線程的數(shù)量,則任務(wù)會被插入到任務(wù)隊列中標(biāo)等待執(zhí)行。
    3、如果(2)中的任務(wù)無法插入到任務(wù)隊列中,由于任務(wù)隊列已滿,這時候如果線程數(shù)量未達(dá)到線程池規(guī)定最大值,則會啟動一個非核心線程來執(zhí)行任務(wù)。
    4、如果(3)中線程數(shù)量已經(jīng)達(dá)到線程池最大值,則會拒絕執(zhí)行此任務(wù),ThreadPoolExecutor會調(diào)用RejectedExecutionHandler的rejectedExecution方法通知調(diào)用者。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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