提高應(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í)間要減少。不要在onCreate、onStartonResume當(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),這就給用戶感覺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í)SplashFragment,SplashFragment里面非常之簡(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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