Android性能優(yōu)化一篇文章搞定

本文由玉剛說寫作平臺(tái)提供寫作贊助,版權(quán)歸玉剛說微信公眾號(hào)所有
原作者:Mr.s(豬_隊(duì)友)
版權(quán)聲明:未經(jīng)玉剛說許可,不得以任何形式轉(zhuǎn)載

文章之前的一些話:

本文尿點(diǎn)比較多,酌情看自己想看的。

什么是性能

image.png

這張圖很好詮釋了什么性能。
快,穩(wěn),省,小,這四點(diǎn)很形象的代表了性能的四個(gè)方面,同時(shí)也讓我們知道我們App現(xiàn)在是否是款性能良好的APP,如果有一項(xiàng)不達(dá)標(biāo),那么說明我們的應(yīng)用有待優(yōu)化。
很多時(shí)候我們注重功能實(shí)現(xiàn),保證能用,但是我們會(huì)發(fā)現(xiàn),這樣的應(yīng)用很難拿的出手,里面的槽點(diǎn)太多了,性能很差,但是又不知道從哪里下手進(jìn)行優(yōu)化,那么我們就一步一步來,看看我們到底應(yīng)該怎么優(yōu)化我們的APP。

1 、布局優(yōu)化

和UI相關(guān)的首先就是布局,特別是在開發(fā)一些復(fù)雜界面的時(shí)候,通常我們都是采用布局嵌套的方法,每個(gè)人的布局思路不太一樣,寫出的也不太一樣,,所以就可能造成嵌套的層級(jí)過多。

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

  • 白話:
    顯示一個(gè)布局就好比我們蓋一個(gè)房子,首先我們要測(cè)量房子的大小,還要測(cè)量房間里面各個(gè)家具的大小,和位置,然后進(jìn)行擺放同時(shí)也要對(duì)房子進(jìn)行裝修,如果我們是一層,都在明面上,干起活來敞亮也輕松,可是有的人的房子,喜歡各種隔斷,分成一個(gè)一個(gè)的大隔斷間,每個(gè)大隔斷間里還有小隔斷間,小隔斷間里有小小隔斷間,還有小小小隔斷間。。。N層隔斷間
    看到這些頭皮發(fā)麻吧,而且是一個(gè)大隔斷間里面所有的小隔斷,小小隔斷等等都測(cè)量完擺放好,才能換另外一個(gè)大隔斷,天呢,太浪費(fèi)時(shí)間了,不能都直接都放外面嗎?也好擺放啊,這么搞我怎么擺,每個(gè)隔斷間都要裝修一遍,太浪費(fèi)時(shí)間了啊。

我們的Android虛擬機(jī)也會(huì)這么抱怨,咱們家本來就不富裕,什么都要省著用,你這么搞,肯定運(yùn)轉(zhuǎn)有問題啊,那么多嵌套的小隔斷間需要處理,都會(huì)占用cpu計(jì)算的時(shí)間和GPU渲染的時(shí)間。顯示GPU過度繪制,分層如下如所示

分層顏色.png

通過顏色我們可以知道我們應(yīng)用是否有多余層次的繪制,如果一路飄紅,那么我們就要相應(yīng)的處理了。

所以我們有了第一個(gè)優(yōu)化版本:

  • 優(yōu)化 1.0
    1、如果父控件有顏色,也是自己需要的顏色,那么就不必在子控件加背景顏色
    2、如果每個(gè)自控件的顏色不太一樣,而且可以完全覆蓋父控件,那么就不需要再父控件上加背景顏色
    3、盡量減少不必要的嵌套
    4、能用LinearLayout和FrameLayout,就不要用RelativeLayout,因?yàn)镽elativeLayout控件相對(duì)比較復(fù)雜,測(cè)繪也想要耗時(shí)。

做到了以上4點(diǎn)只能說恭喜你,入門級(jí)優(yōu)化已經(jīng)實(shí)現(xiàn)了。
針對(duì)嵌套布局,谷歌也是陸續(xù)出了一些新的方案。
對(duì)就是<include>,<merge>,<ViewStub>三兄弟

<include>可以提高布局的復(fù)用性,大大方便我們的開發(fā),有人說這個(gè)沒有減少布局的嵌套吧,對(duì),<include>確實(shí)沒有,但是<include>和<merge>聯(lián)手搭配,效果那是杠杠滴。

<merge>的布局取決于父控件是哪個(gè)布局,使用<merge>相當(dāng)于減少了自身的一層布局,直接采用父<include>的布局,當(dāng)然直接在父布局里面使用意義不大,所以會(huì)和<include>配合使用,既增加了布局的復(fù)用性,用減少了一層布局嵌套。

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

我們開始進(jìn)化我們的優(yōu)化

  • 優(yōu)化1.1
    5、使用<include>和<merge> 增加復(fù)用,減少層級(jí)
    6、<ViewStub>按需加載,更加輕便

可能又有人說了:
背景復(fù)用了,嵌套已經(jīng)很精簡(jiǎn)了,再精簡(jiǎn)就實(shí)現(xiàn)了不了復(fù)雜視圖了,可是還是一路飄紅,這個(gè)怎么辦?面對(duì)這個(gè)問題谷歌給了我們一個(gè)新的布局ConstraintLayout
ConstraintLayout可以有效地解決布局嵌套過多的問題。
ConstraintLayout使用約束的方式來指定各個(gè)控件的位置和關(guān)系的,它有點(diǎn)類似于 RelativeLayout,但遠(yuǎn)比RelativeLayout要更強(qiáng)大。(照抄隔壁IOS的約束布局)

所以簡(jiǎn)單布局簡(jiǎn)單處理,復(fù)雜布局ConstraintLayout很好使。提升性能從布局做起。

再次進(jìn)化:

  • 優(yōu)化1.2
    7、復(fù)雜界面可選擇ConstraintLayout,可有效減少層級(jí)

2、繪制優(yōu)化

我們把布局優(yōu)化了,但是和布局息息相關(guān)的還有繪制。這是直接影響顯示的兩個(gè)根本因素。

其實(shí)布局優(yōu)化了對(duì)于性能提升影響不算很大,但是是我們最容易下手,最直接接觸的優(yōu)化,所以不管能提升多少,哪怕只有百分之一的提升,我們也要做,因?yàn)橛绊懶阅艿牡胤教嗔耍總€(gè)部分都提升一點(diǎn),我們應(yīng)用就可以提升很多了。

我們平時(shí)感覺的卡頓問題最主要的原因之一是因?yàn)殇秩拘阅埽驗(yàn)樵絹碓綇?fù)雜的界面交互,其中可能添加了動(dòng)畫,或者圖片等等。我們希望創(chuàng)造出越來越炫的交互界面,同時(shí)也希望他可以流暢顯示,但是往往卡頓就發(fā)生在這里。

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

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

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


image.png

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


image.png

既然問題找到了,那么我們肯定要有相應(yīng)的解決辦法,根本做法是 減輕onDraw()的負(fù)擔(dān)。所以

第一點(diǎn):
onDraw方法中不要做耗時(shí)的任務(wù),也不做過多的循環(huán)操作,特別是嵌套循環(huán),雖然每次循環(huán)耗時(shí)很小,但是大量的循環(huán)勢(shì)必霸占CPU的時(shí)間片,從而造成View的繪制過程不流暢。
第二點(diǎn):
除了循環(huán)之外,onDraw()中不要?jiǎng)?chuàng)建新的局部對(duì)象,因?yàn)閛nDraw()方法一般都會(huì)頻繁大量調(diào)用,就意味著會(huì)產(chǎn)生大量的零時(shí)對(duì)象,不進(jìn)占用過的內(nèi)存,而且會(huì)導(dǎo)致系統(tǒng)更加頻繁的GC,大大降低程序的執(zhí)行速度和效率。

其實(shí)這兩點(diǎn)在android的UI線程中都適用。

升級(jí)進(jìn)化:

  • 優(yōu)化2.0
    8、onDraw中不要?jiǎng)?chuàng)建新的局部對(duì)象
    9、onDraw方法中不要做耗時(shí)的任務(wù)

其實(shí)從渲染優(yōu)化里我們也牽扯出了另一個(gè)優(yōu)化,那就是內(nèi)存優(yōu)化。

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

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

內(nèi)存泄漏是一個(gè)緩慢積累的過程,一點(diǎn)一點(diǎn)的給你,溫水煮青蛙一般,我們往往很難直觀的看到,只能最后內(nèi)存不夠用了,程序奔潰了,才知道里面有大量的泄漏,但是到底是那些地方?估計(jì)是狼煙遍地,千瘡百孔,都不知道如何下手。怎么辦?最讓人難受的是內(nèi)存泄漏情況那么多,記不住,理解也不容易,關(guān)鍵是老會(huì)忘記。怎么辦呢?老這么下去也不是事,總不能面試的時(shí)候突擊,做項(xiàng)目的時(shí)候不知所措吧。所以一定要記住了解GC原理,這樣才可以更準(zhǔn)確的理解內(nèi)存泄漏的場(chǎng)景和原因。不懂GC原理的可以先看一下這個(gè)JVM初探:內(nèi)存分配、GC原理與垃圾收集器

本來GC的誕生是為了讓java程序員更加輕松(這一點(diǎn)隔壁C++痛苦的一匹),,java虛擬機(jī)會(huì)自動(dòng)幫助我們回收那些不再需要的內(nèi)存空間。通過引用計(jì)數(shù)法,可達(dá)性分析法等等方法,確認(rèn)該對(duì)象是否沒有引用,是否可以被回收。

有人會(huì)說真么強(qiáng)悍的功能看起來無懈可擊啊,對(duì),理論上可以達(dá)到消除內(nèi)存泄漏,但是很多人不按常理出牌啊,往往很多時(shí)候,有的對(duì)象還保持著引用,但邏輯上已經(jīng)不會(huì)再用到。就是這一類對(duì)象,游走于GC法律的邊緣,我沒用了,但是你又不知道我沒用了,就是這么賴著不走,空耗內(nèi)存。
因?yàn)橛袃?nèi)存泄漏,所以內(nèi)存被占用越來越多,那么GC會(huì)更容易被觸發(fā),GC會(huì)越來越頻發(fā),但是當(dāng)GC的時(shí)候所有的線程都是暫停狀態(tài)的,需要處理的對(duì)象數(shù)量越多耗時(shí)越長,所以這也會(huì)造成卡頓。


image.png

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

1、集合類泄漏:

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

舉個(gè)栗子:

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沒用的時(shí)候,我們?nèi)绻蛔鎏幚淼脑挘@就是典型的占著茅坑不拉屎,mList內(nèi)部持有者眾多集合元素的對(duì)象,不泄露天理難容啊。解決這個(gè)問題也超級(jí)簡(jiǎn)單。把mList清理掉,然后把它的引用也給釋放掉。

  mList.clear();
  mList = null;

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

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

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

當(dāng)我們?cè)贏ctivity里面使用這個(gè)的時(shí)候,把我們Acitivty的context傳進(jìn)去,那么,這個(gè)單例就持有這個(gè)Activity的引用,當(dāng)這個(gè)Activity沒有用了,需要銷毀的時(shí)候,因?yàn)檫@個(gè)單例還持有Activity的引用,所以無法GC回收,所以就出現(xiàn)了內(nèi)存泄漏,也就是生命周期長的持有了生命周期短的引用,造成了內(nèi)存泄漏。
所以我們要做的就是生命周期長的和生命周期長的玩,短的和短的玩。就好比你去商場(chǎng),本來就是傳個(gè)話的,話說完就要走了,突然保安過來非要拉著你的手,說要和你天長地久。只要商場(chǎng)在一天,他就要陪你一天。天呢?太可怕了。叔叔我們不約,我有我的小伙伴,我還要上學(xué)呢,你趕緊找你的保潔阿姨去吧。你在商場(chǎng)的生命周期本來可能就是1分鐘,而保安的生命周期那是要和商場(chǎng)開關(guān)門一致的,所以不同生命周期的最好別一起玩的好。

解決方案也很簡(jiǎn)單:

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

還有一個(gè)常用的地方就是Toast。你應(yīng)該知道和誰玩了吧。

3、匿名內(nèi)部類/非靜態(tài)內(nèi)部類

這里有一張寶圖:


image.png

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

我們?cè)賮砼e個(gè)栗子:

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

怎么辦呢?

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

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)部類,這個(gè)問題很常見,匿名內(nèi)部類和非靜態(tài)內(nèi)部類有一個(gè)共同的地方,就是會(huì)只有外部類的強(qiáng)引用,所以這哥倆本質(zhì)是一樣的。但是處理方法有些不一樣。但是思路絕對(duì)一樣。換湯不換藥。

舉個(gè)灰常熟悉的栗子:

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)行耗時(shí)操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    
        mHandler. sendEmptyMessageDelayed(0, 100000);
    }

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

引用分為強(qiáng)引用,軟引用,弱引用,虛引用,強(qiáng)度一次遞減。

強(qiáng)引用:
我們平時(shí)不做特殊處理的一般都是強(qiáng)引用,如果一個(gè)對(duì)象具有強(qiáng)引用,GC寧可OOM也絕不會(huì)回收它。看出多強(qiáng)硬了吧。

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

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

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

所以我們用弱引用來修飾Activity,這樣GC的時(shí)候,該回收的也就回收了,不會(huì)再有內(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);
    }

4、資源未關(guān)閉造成的內(nèi)存泄漏

  • 網(wǎng)絡(luò)、文件等流忘記關(guān)閉
  • 手動(dòng)注冊(cè)廣播時(shí),退出時(shí)忘記 unregisterReceiver()
  • Service 執(zhí)行完后忘記 stopSelf()
  • EventBus 等觀察者模式的框架忘記手動(dòng)解除注冊(cè)

這些需要記住又開就有關(guān),具體做法也很簡(jiǎn)單就不一一贅述了。給大家介紹幾個(gè)很好用的工具:

1、leakcanary傻瓜式操作,哪里有泄漏自動(dòng)給你顯示出來。很直接很暴力。
2、我們平時(shí)也要多使用Memory Monitor進(jìn)行內(nèi)存監(jiān)控,這個(gè)分析就有些難度了,可以上網(wǎng)搜一下具體怎么使用。
3、Android Lint 它可以幫助我們發(fā)現(xiàn)代碼機(jī)構(gòu) / 質(zhì)量問題,同時(shí)提供一些解決方案,內(nèi)存泄露的會(huì)飄黃,用起來很方便,具體使用方法上網(wǎng)學(xué)習(xí),這里不多做說明了。

so

  • 優(yōu)化3.0
    10、解決各個(gè)情況下的內(nèi)存泄漏,注意平時(shí)代碼的規(guī)范。

4、啟動(dòng)速度優(yōu)化

不知道大家有沒有細(xì)心發(fā)現(xiàn),我們的應(yīng)用啟動(dòng)要比別的大廠的要慢,要花費(fèi)更多的時(shí)間,明明他們的包體更大,app更復(fù)雜,怎么啟動(dòng)時(shí)間反而比我們的短呢?
但是這塊的優(yōu)化關(guān)注的人很少,因?yàn)锳pp常常伴有閃屏頁,所以這個(gè)問題看起來就不是問題了,但是一款好的應(yīng)用是絕對(duì)不允許這樣的,我加閃屏頁是我的事,啟動(dòng)速度慢絕對(duì)不可以。

app啟動(dòng)分為冷啟動(dòng)(Cold start)和熱啟動(dòng)(Hot start),溫啟動(dòng)(Warm start)三種。

冷啟動(dòng)(Cold start):

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

在冷啟動(dòng)開始時(shí),系統(tǒng)有三個(gè)任務(wù)。這些任務(wù)是:

1、 加載并啟動(dòng)應(yīng)用程序。
2、 啟動(dòng)后立即顯示應(yīng)用程序的空白啟動(dò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、啟動(dòng)主線程。

2、創(chuàng)建主Activity。

3、加載布局

4、屏幕布局

5、執(zhí)行初始繪制。

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


image.png

Application創(chuàng)建

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

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

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

Activity創(chuàng)建

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

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

通常,該 onCreate()方法對(duì)加載時(shí)間的影響最大,因?yàn)樗宰罡叩拈_銷執(zhí)行工作:加載和膨脹視圖,以及初始化活動(dòng)運(yùn)行所需的對(duì)象。

熱啟動(dòng)(Hot start):

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

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

溫啟動(dòng)(Warm start)

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

場(chǎng)景:

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

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

這里是慢的定義:

  • 啟動(dòng)需要5秒或更長時(shí)間。
  • 啟動(dòng)需要2秒或更長時(shí)間。
  • 啟動(dòng)需要1.5秒或更長時(shí)間。

無論何種啟動(dòng),我們的優(yōu)化點(diǎn)都是:
Application、Activity創(chuàng)建以及回調(diào)等過程
谷歌官方給的建議是:
1、利用提前展示出來的Window,快速展示出來一個(gè)界面,給用戶快速反饋的體驗(yàn);
2、避免在啟動(dòng)時(shí)做密集沉重的初始化(Heavy app initialization);
3、避免I/O操作、反序列化、網(wǎng)絡(luò)操作、布局嵌套等。

具體做法:

針對(duì)1:利用提前展示出來的Window,快速展示出來一個(gè)界面

使用Activity的windowBackground主題屬性來為啟動(dòng)的Activity提供一個(gè)簡(jiǎn)單的drawable。

Layout XML file:

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

Manifest file:

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

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

針對(duì)2:避免在啟動(dòng)時(shí)做密集沉重的初始化

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

怎么辦呢?這些還都是必要的,不能不去初始化啊,那就只能異步加載了。
但是并不是所有的都可以進(jìn)行異步處理。這里分情況給出一些建議。
1、比如像友盟,bugly這樣的業(yè)務(wù)非必要的可以的異步加載。
2、比如地圖,推送等,非第一時(shí)間需要的可以在主線程做延時(shí)啟動(dòng)。當(dāng)程序已經(jīng)啟動(dòng)起來之后,在進(jìn)行初始化。
3、對(duì)于圖片,網(wǎng)絡(luò)請(qǐng)求框架必須在主線程里初始化了。

同時(shí)因?yàn)槲覀円话銜?huì)有閃屏頁面,也可以把延時(shí)啟動(dòng)的地圖,推動(dòng)的啟動(dòng)在這個(gè)時(shí)間段里,這樣合理安排時(shí)間片的使用。極大的提高了啟動(dòng)速度。

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

這個(gè)不用多說了,大家應(yīng)該知道如何去做了,有些上文也有說明。

so

  • 優(yōu)化4.0
    11、利用提前展示出來的Window,快速展示出來一個(gè)界面,給用戶快速反饋的體驗(yàn);
    12、避免在啟動(dòng)時(shí)做密集沉重的初始化(Heavy app initialization);
    13、避免I/O操作、反序列化、網(wǎng)絡(luò)操作、布局嵌套等。

5、包體優(yōu)化

我做過兩年的海外應(yīng)用產(chǎn)品,深知包體大小對(duì)于產(chǎn)品新增的影響,包體小百分之五,可能新增就增加百分之五。如果產(chǎn)品基數(shù)很大,這個(gè)提升就更可怕了。不管怎么說,我們要減肥,要六塊腹肌。不要九九歸一的大肚子。
既然要瘦身,那么我們必須知道APK的文件構(gòu)成,解壓apk。


image.png

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

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

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

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

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

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

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

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

res/layout/preferences.xml: Warning: The resource R.layout.preferences appears
    to be unused [UnusedResources]

同時(shí)我們可以開啟資源壓縮,自動(dòng)刪除無用的資源

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

無用的資源已經(jīng)被刪除了,接下來哪里可以在瘦身呢?

2、我們可以使用可繪制對(duì)象,某些圖像不需要靜態(tài)圖像資源; 框架可以在運(yùn)行時(shí)動(dòng)態(tài)繪制圖像。Drawable對(duì)象(<shape>以XML格式)可以占用APK中的少量空間。此外,XML Drawable對(duì)象產(chǎn)生符合材料設(shè)計(jì)準(zhǔn)則的單色圖像。
上面的話官方,簡(jiǎn)單說來就是,能自己用XML寫Drawable,就自己寫,能不用公司的UI切圖,就別和他們說話,咱們自己造,做自己的UI,美滋滋。而且這種圖片占用空間會(huì)很小。

3、重用資源,比如一個(gè)三角按鈕,點(diǎn)擊前三角朝上代表收起的意思,點(diǎn)擊后三角朝下,代表展開,一般情況下,我們會(huì)用兩張圖來切換,我們完全可以用旋轉(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" />

比如同一圖像的著色不同,我們可以用android:tint和tintMode屬性,低版本(5.0以下)可以使用ColorFilter

4、壓縮PNG和JPEG文件
您可以減少PNG文件的大小,而不會(huì)丟失使用工具如圖像質(zhì)量 pngcrushpngquant,或zopflipng。所有這些工具都可以減少PNG文件的大小,同時(shí)保持感知的圖像質(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大小。一個(gè)100字節(jié)的文件可以生成與屏幕大小相關(guān)的清晰圖像。

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

不要把AnimationDrawable用于創(chuàng)建逐幀動(dòng)畫,因?yàn)檫@樣做需要為動(dòng)畫的每個(gè)幀包含一個(gè)單獨(dú)的位圖文件,這會(huì)大大增加APK的大小。

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


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

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

so
優(yōu)化5.0
14、代碼混淆。
15、插件化
16、資源優(yōu)化

6、耗電優(yōu)化

我們可能對(duì)耗電優(yōu)化不怎么感冒,沒事,谷歌這方面做得也不咋地,5.0之后才有像樣的方案,講實(shí)話這個(gè)優(yōu)化的優(yōu)先級(jí)沒有前面幾個(gè)那么高,但是我們也要了解一些避免耗電的坑,至于更細(xì)的耗電分析可以使用這個(gè)Battery Historian
Battery Historian 是由Google提供的Android系統(tǒng)電量分析工具,從手機(jī)中導(dǎo)出bugreport文件上傳至頁面,在網(wǎng)頁中生成詳細(xì)的圖表數(shù)據(jù)來展示手機(jī)上各模塊電量消耗過程,最后通過App數(shù)據(jù)的分析制定出相關(guān)的電量優(yōu)化的方法。

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

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

同時(shí)谷歌針對(duì)耗電優(yōu)化也提出了一個(gè)懶惰第一的法則
1、減少:你的應(yīng)用程序可以刪除冗余操作嗎?例如,它是否可以緩存下載的數(shù)據(jù)而不是重復(fù)喚醒無線電以重新下載數(shù)據(jù)?
2、推遲:應(yīng)用是否需要立即執(zhí)行操作?例如,它可以等到設(shè)備充電才能將數(shù)據(jù)備份到云端嗎?
3、合并:可以批處理工作,而不是多次將設(shè)備置于活動(dòng)狀態(tài)嗎?例如,幾十個(gè)應(yīng)用程序是否真的有必要在不同時(shí)間打開收音機(jī)發(fā)送郵件?在一次喚醒收音機(jī)期間,是否可以傳輸消息?
谷歌在耗電優(yōu)化這方面確實(shí)顯得有些無力,希望以后可以退出更好的工具和解決方案,不然這方面的優(yōu)化優(yōu)先級(jí)還是很低。付出和回報(bào)所差太大。

so
優(yōu)化6.0
17、使用JobScheduler調(diào)度任務(wù)
18、使用懶惰法則

6、ListView和 Bitmap優(yōu)化

針對(duì)ListView優(yōu)化

ViewHolder的使用
創(chuàng)建一個(gè)內(nèi)部類ViewHolder,里面的成員變量和view中所包含的組件個(gè)數(shù)、類型相同,在convertview為null的時(shí)候,把findviewbyId找到的控件賦給ViewHolder中對(duì)應(yīng)的變量,就相當(dāng)于先把它們裝進(jìn)一個(gè)容器,下次要用的時(shí)候,直接從容器中獲取。
現(xiàn)在我們現(xiàn)在一般使用RecyclerView,自帶這個(gè)優(yōu)化,不過還是要理解一下原理的好。
然后可以對(duì)接受來的數(shù)據(jù)進(jìn)行分段或者分頁加載,也可以優(yōu)化性能。

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

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

  • 對(duì)圖片質(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;  
        }  
  • 對(duì)圖片尺寸進(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)時(shí)溢出
            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;
    }


    /**
     * 動(dòng)態(tài)計(jì)算出圖片的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;
        }
    }

so

  • 優(yōu)化7.0
    18、ListView使用ViewHolder,分段,分頁加載
    19、壓縮Bitmap

8、響應(yīng)速度優(yōu)化

影響響應(yīng)速度的主要因素是主線程有耗時(shí)操作,影響了響應(yīng)速度。所以響應(yīng)速度優(yōu)化的核心思想是避免在主線程中做耗時(shí)操作,把耗時(shí)操作異步處理。

9、線程優(yōu)化

線程優(yōu)化的思想是采用線程池,避免在程序中存在大量的Thread。線程池可以重用內(nèi)部的線程,從而避免了現(xiàn)場(chǎng)的創(chuàng)建和銷毀所帶來的性能開銷,同時(shí)線程池還能有效地控制線程池的最大并發(fā)數(shù),避免大量的線程因互相搶占系統(tǒng)資源從而導(dǎo)致阻塞現(xiàn)象發(fā)生。
《Android開發(fā)藝術(shù)探索》對(duì)線程池的講解很詳細(xì),不熟悉線程池的可以去了解一下。

  • 優(yōu)點(diǎn):
    1、減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開銷。
    2、如不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及”過度切換”。

  • 需要注意的是:
    1、如果線程池中的數(shù)量為達(dá)到核心線程的數(shù)量,則直接會(huì)啟動(dòng)一個(gè)核心線程來執(zhí)行任務(wù)。
    2、如果線程池中的數(shù)量已經(jīng)達(dá)到或超過核心線程的數(shù)量,則任務(wù)會(huì)被插入到任務(wù)隊(duì)列中標(biāo)等待執(zhí)行。
    3、如果(2)中的任務(wù)無法插入到任務(wù)隊(duì)列中,由于任務(wù)隊(duì)列已滿,這時(shí)候如果線程數(shù)量未達(dá)到線程池規(guī)定最大值,則會(huì)啟動(dòng)一個(gè)非核心線程來執(zhí)行任務(wù)。
    4、如果(3)中線程數(shù)量已經(jīng)達(dá)到線程池最大值,則會(huì)拒絕執(zhí)行此任務(wù),ThreadPoolExecutor會(huì)調(diào)用RejectedExecutionHandler的rejectedExecution方法通知調(diào)用者。

10、微優(yōu)化

這些微優(yōu)化可以在組合時(shí)提高整體應(yīng)用程序性能,但這些更改不太可能導(dǎo)致顯著的性能影響。選擇正確的算法和數(shù)據(jù)結(jié)構(gòu)應(yīng)始終是我們的首要任務(wù),以提高代碼效率。

  • 編寫高效代碼有兩個(gè)基本規(guī)則:
    1、不要做你不需要做的工作。
    2、如果可以避免,請(qǐng)不要分配內(nèi)存。

1、避免創(chuàng)建不必要的對(duì)象
對(duì)象創(chuàng)建永遠(yuǎn)不是免費(fèi)的,雖然每一個(gè)的代價(jià)不是很大,但是總歸是代價(jià)的不是嗎?能不創(chuàng)建何必要浪費(fèi)資源呢?

2、首選靜態(tài)(這里說的是特定情景)
如果您不需要訪問對(duì)象的字段,請(qǐng)使您的方法保持靜態(tài)。調(diào)用速度將提高約15%-20%。這也是很好的做法,因?yàn)槟憧梢詮姆椒ê灻锌闯觯{(diào)用方法不能改變對(duì)象的狀態(tài)

3、對(duì)常量使用static final
此優(yōu)化僅適用于基本類型和String常量,而不適用于 任意引用類型。盡管如此,static final盡可能聲明常量是一種好習(xí)慣。

4、使用增強(qiáng)的for循環(huán)語法
增強(qiáng)for循環(huán)(for-each)可用于實(shí)現(xiàn)Iterable接口和數(shù)組的集合。對(duì)于集合,分配一個(gè)迭代器來對(duì)hasNext()和進(jìn)行接口調(diào)用next()。使用一個(gè) ArrayList,手寫計(jì)數(shù)循環(huán)快約3倍,但對(duì)于其他集合,增強(qiáng)的for循環(huán)語法將完全等效于顯式迭代器用法。

5、避免使用浮點(diǎn)數(shù)
根據(jù)經(jīng)驗(yàn),浮點(diǎn)數(shù)比Android設(shè)備上的整數(shù)慢約2倍

結(jié)尾

本文篇幅有限,性能優(yōu)化的方面很多,每一項(xiàng)深入下去,不寫個(gè)幾十萬字是結(jié)束不了,所以很多都是淺嘗輒止,希望可以拋磚引玉,用我的拙劣的文章,給大家一些幫助。性能優(yōu)化需要走的路還很遠(yuǎn),希望能和各位同學(xué)一同前行、一起進(jìn)步。

大家可以點(diǎn)個(gè)關(guān)注,告訴我大家想要深入探究哪些問題,希望看到哪方面的文章,我可以免費(fèi)給你寫專題文章。。哈哈。。。
希望大家多多支持。。你的一個(gè)關(guān)注,是我堅(jiān)持的最大動(dòng)力。。

參考:
Android APP 性能優(yōu)化的一些思考
https://www.cnblogs.com/cr330326/p/8011523.html
谷歌官方
http://developer.android.com/topic/performance/
Android性能優(yōu)化系列之Bitmap圖片優(yōu)化
https://blog.csdn.net/u012124438/article/details/66087785
Android 內(nèi)存泄漏總結(jié)
https://yq.aliyun.com/articles/3009

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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