提高應(yīng)用的啟動(dòng)速度

目錄

啟動(dòng)的兩種方式

  • 冷啟動(dòng)

當(dāng)直接從桌面上直接啟動(dòng)沈跨,同時(shí)后臺(tái)沒(méi)有該進(jìn)程的緩存庆尘,這個(gè)時(shí)候系統(tǒng)就需要重新創(chuàng)建一個(gè)新的進(jìn)程并且分配各種資源宙枷。(應(yīng)用第一次啟動(dòng)或者我們按下home鍵滑動(dòng)刪除我們的應(yīng)用后再啟動(dòng))

  • 熱啟動(dòng)

該app后臺(tái)有該進(jìn)程的緩存送挑,這時(shí)候啟動(dòng)的進(jìn)程就屬于熱啟動(dòng)千元。(熱啟動(dòng)不需要重新分配進(jìn)程屯远,也就是說(shuō)不會(huì)再走Application了蔓姚,直接進(jìn)入的就是app的入口Activity,這樣就速度就會(huì)快很多)

統(tǒng)計(jì)App啟動(dòng)時(shí)間

使用命令行來(lái)啟動(dòng)app慨丐,同時(shí)進(jìn)行時(shí)間測(cè)量(單位:毫秒)

adb shell am start -W PackageName/PackageName.activity ;--注意:W一定要大些
eg:  adb am start -W com.gfd.eshop/com.gfd.eshop.feature.SplashActivity

下面通過(guò)命令行來(lái)啟動(dòng)App,分別統(tǒng)計(jì)一個(gè)冷啟動(dòng)和熱啟動(dòng)所花費(fèi)的時(shí)間坡脐,通過(guò)時(shí)間來(lái)對(duì)比一下

參數(shù)明說(shuō):
* ThisTime: 指當(dāng)前指定的Activity的啟動(dòng)時(shí)間
* TotalTime: 整個(gè)應(yīng)用的啟動(dòng)時(shí)間,Application+Activity的使用的時(shí)間房揭。
* WaitTime: 包括系統(tǒng)的影響時(shí)間
開發(fā)者一般只要關(guān)心 TotalTime 即可备闲,這個(gè)時(shí)間才是自己應(yīng)用真正啟動(dòng)的耗時(shí)。通過(guò)上面兩幅圖的比較捅暴,很明顯恬砂,冷啟動(dòng)要比熱啟動(dòng)耗時(shí)。

App啟動(dòng)流程

要想提高應(yīng)用的啟動(dòng)速度蓬痒,就要了解App的啟動(dòng)流程泻骤,從而知道時(shí)間到底花費(fèi)在哪,我們也是據(jù)此來(lái)優(yōu)化梧奢。

主要啟動(dòng)流程

  • 1.通過(guò) Launcher 啟動(dòng)應(yīng)用時(shí)狱掂,點(diǎn)擊應(yīng)用圖標(biāo)后,Launcher 調(diào)用 startActivity 啟動(dòng)應(yīng)用亲轨。
  • 2.Launcher Activity 最終調(diào)用 InstrumentationexecStartActivity 來(lái)啟動(dòng)應(yīng)用趋惨。
  • 3.Instrumentation 調(diào)用 ActivityManagerProxy (ActivityManagerService 在應(yīng)用進(jìn)程的一個(gè)代理對(duì)象) 對(duì)象的 startActivity 方法啟動(dòng) Activity。
  • 4.到目前為止所有過(guò)程都在Launcher 進(jìn)程里面執(zhí)行惦蚊,接下來(lái) ActivityManagerProxy 對(duì)象跨進(jìn)程調(diào)用 ActivityManagerService (運(yùn)行在 system_server 進(jìn)程)的 startActivity 方法啟動(dòng)應(yīng)用器虾。
  • 5.ActivityManagerServicestartActivity 方法經(jīng)過(guò)一系列調(diào)用讯嫂,最后調(diào)用 zygoteSendArgsAndGetResult通過(guò) socket 發(fā)送給 zygote 進(jìn)程,zygote 進(jìn)程會(huì)孵化出新的應(yīng)用進(jìn)程兆沙。
  • 6.zygote 進(jìn)程孵化出新的應(yīng)用進(jìn)程后欧芽,會(huì)執(zhí)行 ActivityThread 類的 main 方法。在該方法里會(huì)先準(zhǔn)備好 Looper 和消息隊(duì)列挤悉,然后調(diào)用 attach 方法將應(yīng)用進(jìn)程綁定到 ActivityManagerService渐裸,然后進(jìn)入 loop 循環(huán),不斷地讀取消息隊(duì)列里的消息装悲,并分發(fā)消息昏鹃。
  • 7.ActivityManagerService 保存應(yīng)用進(jìn)程的一個(gè)代理對(duì)象,然后 ActivityManagerService 通過(guò)代理對(duì)象通知應(yīng)用進(jìn)程創(chuàng)建入口Activity的實(shí)例诀诊,并執(zhí)行它的生命周期函數(shù)洞渤。

用戶在 Launcher 程序里點(diǎn)擊應(yīng)用圖標(biāo)時(shí),會(huì)通知 ActivityManagerService 啟動(dòng)應(yīng)用的入口 Activity属瓣, ActivityManagerService 發(fā)現(xiàn)這個(gè)應(yīng)用還未啟動(dòng)载迄,則會(huì)通知 Zygote 進(jìn)程孵化出應(yīng)用進(jìn)程,然后在這個(gè)應(yīng)用進(jìn)程里執(zhí)行 ActivityThread 的 main 方法抡蛙。應(yīng)用進(jìn)程接下來(lái)通知 ActivityManagerService 應(yīng)用進(jìn)程已啟動(dòng)护昧,ActivityManagerService 保存應(yīng)用進(jìn)程的一個(gè)代理對(duì)象,這樣 ActivityManagerService 可以通過(guò)這個(gè)代理對(duì)象控制應(yīng)用進(jìn)程粗截,然后 ActivityManagerService 通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity 的實(shí)例惋耙,并執(zhí)行它的生命周期函數(shù)。

上面的啟動(dòng)流程是 Android 提供的機(jī)制熊昌,作為開發(fā)者我們需要清楚或者至少了解其中的過(guò)程和原理绽榛,但我們并不能在這過(guò)程中做什么文章,我們能做的是從上述過(guò)程中最后一步開始婿屹,即 ActivityManagerService 通過(guò)代理對(duì)象通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity 的實(shí)例灭美,并執(zhí)行它的生命周期函數(shù)開始.

Application從構(gòu)造方法開始   --->  attachBaseContext()   --->  onCreate()
Activity構(gòu)造方法  --->  onCreate()  --->  設(shè)置顯示界面布局,設(shè)置主題昂利、背景等等屬性
---> onStart() --->  onResume()  --->  顯示里面的view(測(cè)量届腐、布局、繪制页眯,顯示到界面上)

時(shí)間到底花在哪了梯捕?

  • 1.不要在Application的構(gòu)造方法、attachBaseContext()窝撵、onCreate()里面進(jìn)行初始化耗時(shí)操作。
  • 2.MainActivity襟铭,由于用戶只關(guān)心最后的顯示的這一幀碌奉,對(duì)我們的布局的層次要求要減少短曾,自定義控件的話測(cè)量、布局赐劣、繪制的時(shí)間要減少嫉拐。不要在onCreateonStart魁兼、onResume當(dāng)中做耗時(shí)操作婉徘。
  • 3.對(duì)于SharedPreference的初始化。因?yàn)樗跏蓟臅r(shí)候是需要將數(shù)據(jù)全部讀取出來(lái)放到內(nèi)存當(dāng)中咐汞。所以盡可能減少sp文件數(shù)量(IO需要時(shí)間)盖呼;像這樣的初始化最好放到線程里面;大的數(shù)據(jù)緩存到數(shù)據(jù)庫(kù)里面化撕。

app啟動(dòng)的耗時(shí)主要是在:Application初始化 和 MainActivity的界面加載繪制時(shí)間几晤。由于MainActivity的業(yè)務(wù)和布局復(fù)雜度非常高,甚至該界面必須要有一些初始化的數(shù)據(jù)才能顯示植阴。那么這個(gè)時(shí)候MainActivity就可能半天都出不來(lái)蟹瘾,這就給用戶感覺(jué)app太卡了。我們要做的就是給用戶趕緊利落的體驗(yàn)掠手。點(diǎn)擊app就立馬彈出我們的界面憾朴。于是我們使用SplashActivity一個(gè)非常簡(jiǎn)單的一個(gè)歡迎頁(yè)面上面都不干就只顯示一個(gè)圖片∨绺耄或者邏輯相對(duì)比較簡(jiǎn)單众雷。但是SplashActivity啟動(dòng)之后,還是需要跳到MainActivity魁衙。MainActivity還是需要從頭開始加載布局和數(shù)據(jù)报腔。想到SplashActivity里面可以去做一些MainActivity的數(shù)據(jù)的預(yù)加載。然后需要通過(guò)意圖傳到MainActivity剖淀。

可不可以再做一些更好的優(yōu)化呢纯蛾?

耗時(shí)主要在ApplicationActivity的啟動(dòng)及資源加載時(shí)間;預(yù)加載的數(shù)據(jù)花的時(shí)間纵隔。如果我們能讓這兩個(gè)時(shí)間重疊在一個(gè)時(shí)間段內(nèi)并發(fā)地做這兩個(gè)事情就可以節(jié)省時(shí)間了翻诉。

解決方案

SplashActivityMainActivity合為一個(gè)。應(yīng)用一啟動(dòng)還是加載MainActivity捌刮,SplashActivity可以變成一個(gè)SplashFragment碰煌,然后放一個(gè)FrameLayout作為根布局直接現(xiàn)實(shí)SplashFragmentSplashFragment里面非常之簡(jiǎn)單绅作,就是現(xiàn)實(shí)一個(gè)圖片芦圾,或者一些簡(jiǎn)單的邏輯,啟動(dòng)非扯砣希快,當(dāng)SplashFragment顯示完畢后再將它remove个少。同時(shí)在splash的2S的友好時(shí)間內(nèi)進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)緩存洪乍。這個(gè)時(shí)候我們才看到MainActivity,就不必再去等待網(wǎng)絡(luò)數(shù)據(jù)返回了夜焦。

那么問(wèn)題又來(lái)了

SplashViewContentView加載放到一起來(lái)做了 壳澳,這可能會(huì)影響應(yīng)用的啟動(dòng)時(shí)間∶>可以使用ViewStub延遲加載MainActivity當(dāng)中的View來(lái)達(dá)到減輕這個(gè)影響巷波。ViewStub的設(shè)計(jì)就是為了防止MainActivity的啟動(dòng)加載資源太耗時(shí)了。延遲進(jìn)行加載卸伞,不影響啟動(dòng),但是ViewStub加載也需要時(shí)間抹镊。我們可以等到主界面(Splash頁(yè)面)出來(lái)以后再去加載。

如何設(shè)計(jì)延遲加載

第一時(shí)間想到的就是在onCreate里面調(diào)用handler.postDelayed()方法瞪慧。問(wèn)題是這個(gè)延遲時(shí)間如何控制髓考?真的準(zhǔn)確嗎?假設(shè)弃酌,需要在splash做一個(gè)動(dòng)畫氨菇,需要達(dá)到的效果:應(yīng)用已經(jīng)啟動(dòng)并加載完成,界面已經(jīng)顯示出來(lái)了妓湘,然后我們?cè)偃?dòng)動(dòng)畫查蓉。如果我們這樣:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
  。榜贴。豌研。。唬党。鹃共。。驶拱。霜浴。
   mHandler.postDelayed(new Runnable() {
      @Override
      public void run() {
        。蓝纲。阴孟。。税迷。永丝。。
      }
   }, 2000);

}

這個(gè)延時(shí)任務(wù)真的是在執(zhí)行到這個(gè)代碼的時(shí)候開始計(jì)時(shí)的嗎箭养?其實(shí)不是的慕嚷,在onCreate()中執(zhí)行到該代碼的時(shí)候,只是先將任務(wù)加載到消息隊(duì)列中,等測(cè)量繪制完畢后才會(huì)開始執(zhí)行闯冷。

之前在使用Handler執(zhí)行延時(shí)任務(wù)時(shí)我們都會(huì)放在onCreate()中去執(zhí)行砂心,其實(shí)這樣的延時(shí)時(shí)間是不準(zhǔn)確的懈词。我們需要在界面加載完畢后再開始執(zhí)行蛇耀。我們可以使用onwindowfocuschange或者 ViewTreeObserver

代碼實(shí)現(xiàn)

以上我們理清了應(yīng)用啟動(dòng)耗時(shí)一些原因以及各種問(wèn)題的解決方案坎弯,下面我們寫代碼實(shí)戰(zhàn)一番纺涤。

主頁(yè)面布局

使用ViewStub來(lái)延時(shí)加載我們主頁(yè)面真正的布局,FrameLayout用來(lái)替換SplashFragment來(lái)顯示開始的動(dòng)畫抠忘。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.applicationstartoptimizedemo.MainActivity" >

    <ViewStub 
      android:id="@+id/content_viewstub"
      android:layout="@layout/activity_main_viewstub"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>

    <FrameLayout
      android:id="@+id/frame"
      android:layout_width="match_parent"
      android:layout_height="match_parent" >
    </FrameLayout>

</RelativeLayout>

SplashFragment代碼

SplashFragment中我們只是簡(jiǎn)單展示一個(gè)圖片撩炊,顯示一個(gè)2秒的動(dòng)畫

public class SplashFragment extends Fragment {
  @Override
  @Nullable
  public View onCreateView(LayoutInflater inflater,@Nullable ViewGroup container,@Nullable Bundle savedInstanceState) {
     return inflater.inflate(R.layout.fragment_splash, container,false);
  }
}

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.applicationstartoptimizedemo.MainActivity" >

    <FrameLayout
      android:id="@+id/frame"
      android:background="@drawable/splash"
      android:layout_width="match_parent"
      android:layout_height="match_parent" >
    </FrameLayout>

</RelativeLayout>

主頁(yè)面代碼

public class MainActivity extends FragmentActivity {

  private Handler mHandler = new Handler();
  private SplashFragment splashFragment;
  private ViewStub viewStub;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //先顯示SplashFragment
    splashFragment = new SplashFragment();
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.frame, splashFragment);
    transaction.commit();
    viewStub = (ViewStub)findViewById(R.id.content_viewstub);
    getWindow().getDecorView().post(new Runnable() {
        //布局加載完畢
        @Override
        public void run() {
            mHandler.post(new Runnable() {
                
                @Override
                public void run() {
                    viewStub.inflate();//延時(shí)加載主頁(yè)面真正的布局
                    mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);//2秒后移除SplashFragment
                    }
                } );
            }
        });
    }

  //延時(shí)要執(zhí)行的任務(wù)
  static class DelayRunnable implements Runnable{
    private WeakReference<Context> contextRef;
    private WeakReference<SplashFragment> fragmentRef;
    
    public DelayRunnable(Context context, SplashFragment f) {
        contextRef = new WeakReference<Context>(context);
        fragmentRef = new WeakReference<SplashFragment>(f);
    }

    @Override
    public void run() {
        if(contextRef!=null){
            SplashFragment splashFragment = fragmentRef.get();
            if(splashFragment==null){
                return;
            }
            //移除SplashFragment
            FragmentActivity activity = (FragmentActivity) contextRef.get();
            FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
            transaction.remove(splashFragment);
            transaction.commit();               
            }
        }   
    }
}

總結(jié)

一開始我們先顯示SplashFragment,等布局加載完成后我們?cè)偃ゼ虞d主頁(yè)面真正的布局崎脉,因?yàn)镾plashFragment頁(yè)面需要顯示一段時(shí)間拧咳,在這個(gè)時(shí)間段里我們?nèi)ゼ虞d了主頁(yè)面真正的布局,也就是說(shuō)在顯示SplashFragment的時(shí)候同時(shí)加載主頁(yè)面真正的布局囚灼。等SplashFragment顯示完畢后立刻展示主頁(yè)面骆膝。這樣就減少了主界面加載的時(shí)間。注意灶体,這里延時(shí)加載主要是不要影響SplashFragment布局的加載阅签。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蝎抽,隨后出現(xiàn)的幾起案子政钟,更是在濱河造成了極大的恐慌,老刑警劉巖樟结,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件养交,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瓢宦,警方通過(guò)查閱死者的電腦和手機(jī)碎连,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門刁笙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)破花,“玉大人,你說(shuō)我怎么就攤上這事疲吸∽浚” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵摘悴,是天一觀的道長(zhǎng)峭梳。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么葱椭? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任捂寿,我火速辦了婚禮,結(jié)果婚禮上孵运,老公的妹妹穿的比我還像新娘秦陋。我一直安慰自己,他們只是感情好治笨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布驳概。 她就那樣靜靜地躺著,像睡著了一般旷赖。 火紅的嫁衣襯著肌膚如雪顺又。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天等孵,我揣著相機(jī)與錄音稚照,去河邊找鬼。 笑死俯萌,一個(gè)胖子當(dāng)著我的面吹牛果录,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绳瘟,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雕憔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了糖声?” 一聲冷哼從身側(cè)響起斤彼,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蘸泻,沒(méi)想到半個(gè)月后琉苇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悦施,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年并扇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡诞。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昼汗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顷窒,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布励烦,位于F島的核電站坛掠,受9級(jí)特大地震影響坷檩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一句灌、第九天 我趴在偏房一處隱蔽的房頂上張望欠拾。 院中可真熱鬧,春花似錦资昧、人聲如沸荆忍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)棺亭。三九已至蟋软,卻和暖如春岳守,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棺耍。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工俊卤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岂昭。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓狠怨,卻偏偏與公主長(zhǎng)得像佣赖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子外傅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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