一 啓動App分爲兩種方式:
1 冷啓動:當直接從桌面上直接啓動,同時後臺進程中沒有該APP進程的緩存,這個時候系統就需要重新創建一個新的進程,並且分配各種資源。
2 熱啓動:該App在後臺有該進程的緩存(例:按back鍵、home鍵,應用雖然會退出,但是該應用的進程是依然會保留在後臺,可進入任務列表查看),這個時候啓動的進程就屬於熱啓動。熱啓動不需要重新分配進程,也不會執行Application,直接走的就是App要顯示的Activity中的代碼,這樣就速度快一些。
冷啓動會有哪些表現?
1 點擊桌面後短時間內沒有反應,沒有瞬間開打應用
2 點擊桌面圖標會顯示黑屏或者白屏,沒有及時呈現出頁面
二 應用的啓動流程
冷啓動流程:當點擊app的啓動圖標時,安卓系統會從Zygote進程中fork出一個新的進程分配給該應用,之後會依次創建和初始化Application類、創建MainActivity類、加載主題樣式Theme中的windowBackground等屬性設置給MainActivity、再inflate佈局、當onCreate/onStart/onResume方法都走完了後最後才進行contentView的measure/layout/draw顯示在界面上,所以直到這裏,應用的第一次啓動纔算完成,這時候我們看到的界面也就是所說的第一幀。
總結流程如下:啓動Application從構造方法開始 -->attachBaseContent() -->onCreate()之後啓動Activity-->啓動Acitivyt構造方法-->onCreate()->設置主題、背景等等屬性-->onStart()-->onResume()-->顯示裏面的View(測量、佈局、繪製,顯示到界面上)
三 如何減少應用的啓動時間的耗時?
1 不要在Application的構造方法、attachBaseContent()、onCreate()裏面進行初始化耗時操作。這是必然的。
2 應用的第一個顯示Activity、由於用戶只關心最後的顯示的這一幀,對我們的佈局的層次要求要減少,如果有自定義控件,注意測量、佈局、繪製的時間。不要在onCreate、onStart、onResume當中做耗時操作。
3 對於SharedPreference的初始化,因爲他初始化的時候是需要將數據全部讀取出來放到內存中。如果有大量的數據,是耗時的。如果啓動app就初始化,是消耗時間的。
優化:(1)可以儘可能減少SharedPreference文件數量 (2)像這樣的初始化最好放到線程裏面。(3)大數據放到Sqlite裏面
思考:
有很多開發者使用SplashActivity 作爲應用的第一頁,在這個頁面什麼都不幹,停留2秒鐘,就只顯示一張圖片
這樣App啓動的耗時主要是在:Application初始化,第一個顯示的界面Activity以及MainActivity的界面加載繪製時間。
但是要知道SplashActivity啓動之後,最終還是需要跳到MainActivity。MainActivity還是需要從頭開始加載佈局和數據。這樣當用戶關心的MainActivity還是不能很快的顯示給用戶。在SplashActivity顯示的2秒鐘,主要的MainActivity什麼都沒有做,我們要充分的利用每一段時間。即使有的讀者在這裏想到,在SplashActivity中加載數據,然後Intent將數據傳入到MainActivity中,但是MainActivity還是要繪製界面處理邏輯。
終極解決方案
可不可以再做一些更好的優化呢?
將MainActivity和SplashActivity和併爲一個Activity。
應用一進來就啓動MainActivity,將MainActivity作爲應用的第一個啓動的Activity,不再啓動SplashActivity,SplashActivity可以變成一個SplashFragment(SplashFragment成爲MainActivity中的一個Fragment),然後放一個FrameLayout幀佈局作爲MainActivity的根佈局,直接顯示SplashFragment界面。SplashFragment裏非常之簡單,就是實現一個圖片,啓動非常快。
當SplashFragment顯示完畢後再將它remove掉。在Splash顯示的2s友好時間內,MainActivity中已經進行網絡數據獲取,UI繪製,以及很多事情。這個時候再看到MainActivity就不必再去等待網絡數據返回了。
問題:SplashFragment和MainActivity中的內容佈局加載放到一起來做,這可能會影響應用的啓動時間。
解決:可以使用ViewStub延遲加載MainActivity中的View來達到減輕這個影響。
ViewStub的設計就是爲了防止MainActivity的啓動加載資源太耗時了,延遲進行加載,不影響啓動。但是ViewStub啓動加載也需要時間。但是我們可以等到SplashFragment顯示出來以後再講ViewStub渲染進來。
爲防止黑屏或者白屏,設置啓動頁面的theme。
<style name="LoadingThreme" parent="Threme.AppCompat.Light.NoActionBar">
<item name="android:background">@drawable/loading_bg</item>
</style>
1 AndroidManifest.xml
<application
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
2 activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.startoptdemo.MainActivity">
<!--MainActivity真正要顯示的內容,用ViewStub先暫時保留位置,並不渲染-->
<ViewStub
android:id="@+id/content_viewstub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/activity_main_viewstub"/>
<!-- 放SplashFragment -->
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
activity_main_viewstub.xml就是MainActivity的一些佈局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@mipmap/ic_launcher"
android:gravity="center_horizontal"
android:text="首頁" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@mipmap/ic_launcher"
android:gravity="center_horizontal"
android:text="XX" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@mipmap/ic_launcher"
android:gravity="center_horizontal"
android:text="我的" />
</LinearLayout>
</LinearLayout>
3 SplashFragment 繼承了Fragment。
public class SplashFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
//顯示啓動App所見到的第一頁
return inflater.inflate(R.layout.fragment_splash, container, false);
}
}
4 MainActivity類中:
onCreate方法一進來就加載顯示SplashFragment,然後當MainActivity的窗體加載完畢後,在進行耗時的邏輯處理。這樣既能快速的顯示Splash界面,與此同時又加載了MainActivity的耗時的操作。節省了時間。
package com.example.startoptdemo;
import android.content.Context;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.ViewStub;
import java.lang.ref.WeakReference;
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private ViewStub vsMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//第一步就將SplashFragment加載進來顯示
final SplashFragment splashFragment = new SplashFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame, splashFragment);
transaction.commit();
vsMain = findViewById(R.id.content_main_viewstub);
//判斷當前窗體加載完畢,等splash界面加載出來後再做耗時操作
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
//當窗體加載完畢的時候,立馬再加載MainActivity中的真正的佈局
//將viewstub加載進來,在這裏可以進行MainActivity中的邏輯
vsMain.inflate();
//開啓延遲加載,目的是讓Fragment2秒後消失。這裏只是演示。
mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment), 2000);
}
});
}
static class DelayRunnable implements Runnable {
private WeakReference<Context> contextRef;
private WeakReference<SplashFragment> fragmentRef;
public DelayRunnable(Context context, SplashFragment f) {
contextRef = new WeakReference<>(context);
fragmentRef = new WeakReference<>(f);
}
@Override
public void run() {
//移除fragment
if (contextRef != null) {
SplashFragment splashFragment = fragmentRef.get();
if (splashFragment == null) {
return;
}
FragmentActivity activity = (FragmentActivity) contextRef.get();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.remove(fragmentRef.get());
transaction.commit();
}
}
}
}