第八章 性能優化 之 App啓動優化(二)

第八章 性能優化 之 App啓動優化(二)

(一)啓動頁白屏/黑屏解決

1、現象

打開app,往往會先白屏停頓一下後再進入啓動頁面(Splash)

2、原因

在啓動Acitivty的onCreate()方法裏面,系統先繪製窗體,再執行setContentView(R.layout.activity_splash),窗體繪製後佈局資源還沒加載,於是就使用默認背景色。
如果主題使用Theme.AppCompat.Light(亮色系)則顯示白色閃屏,若使用ThemeOverlay.AppCompat.Dark(暗色系)則顯示黑色閃屏

3、解決

步驟1:把啓動圖bg_splash設置爲窗體背景,避免剛剛啓動App的時候出現,黑/白屏
步驟2 :設置爲背景bg_splash顯示的時候,後臺負責加載資源,同時去下載廣告圖,廣告圖下載成功或者超時的時候顯示SplashActivity的真實樣子
步驟3:隨後進入MainAcitivity

       <style name="ThemeSplash" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:background">@mipmap/bg_splash</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        </style>

(二)啓動速度優化

1、Android Application啓動流程分析

(1)App基礎理論

每個Android App都在一個獨立空間裏, 意味着其運行在一個單獨的進程中, 擁有自己的VM, 被系統分配一個唯一的user ID.
Android App由很多不同組件組成, 這些組件還可以啓動其他App的組件. 因此, Android App並沒有一個類似程序入口的main()方法.
Android進程與Linux進程一樣. 默認情況下, 每個apk運行在自己的Linux進程中. 另外, 默認一個進程裏面只有一個線程—主線程. 這個主線程中有一個Looper實例, 通過調用Looper.loop()從Message隊列裏面取出Message來做相應的處理.
進程在其需要的時候被啓動. 任意時候, 當用戶或者其他組件調取你的apk中的任意組件時, 如果你的apk沒有運行, 系統會爲其創建一個新的進程並啓動. 通常, 這個進程會持續運行直到被系統殺死.

(2)App啓動流程

在這裏插入圖片描述
用戶點擊Home上的一個App圖標, 啓動一個應用時:
Click事件會調用startActivity(Intent), Launcer會通過Binder IPC機制, 最終通知ActivityManagerService(AMS是Android系統的一個進程,用於管理系統四大組件運行狀態)去啓動Activity。
該Service會執行如下操作:
第一步:通過PackageManager的resolveIntent()收集這個intent對象的指向信息.指向信息被存儲在一個intent對象中
第二步:通過grantUriPermissionLocked()方法來驗證用戶是否有足夠的權限去調用該intent對象指向的Activity.
如果有權限, ActivityManagerService會檢查並在新的task中啓動目標activity
第三步:檢查這個進程的ProcessRecord是否存在了.若存在,直接啓動activity,如果ProcessRecord是null, ActivityManagerService會創建新的進程來實例化目標activity
第四步:ActivityManagerService調用startProcessLocked()方法來創建新的進程, 該方法會通過前面講到的socket通道傳遞參數給Zygote進程. Zygote孵化自身, 並調用ZygoteInit.main()方法來實例化ActivityThread對象並最終返回新進程的pid
ActivityThread隨後依次調用Looper.prepareLoop()和Looper.loop()來開啓消息循環
第五步:將進程和指定的Application綁定起來
第六步:在該存在的進程中調用realStartActivity()來啓動Activity

2、App啓動方式

(1)冷啓動

App沒有啓動過或App進程被killed, 系統中不存在該App進程, 此時啓動App即爲冷啓動.
冷啓動的流程即爲第2節所描述的App啓動流程的全過程, 需要創建App進程, 加載相關資源, 啓動Main Thread, 初始化首屏Activity等.
在這個過程中, 屏幕會顯示一個空白的窗口(顏色基於主題), 直至首屏Activity完全啓動.
冷啓動時間線:
在這裏插入圖片描述

(2)熱啓動

熱啓動意味着你的App進程只是處於後臺, 系統只是將其從後臺帶到前臺, 展示給用戶.
類同與冷啓動, 在這個過程中, 屏幕會顯示一個空白的窗口(顏色基於主題), 直至activity渲染完畢.

(3)溫啓動

介於冷啓動和熱啓動之間, 一般來說在以下兩種情況下發生:
a.用戶back退出了App, 然後又啓動. App進程可能還在運行, 但是activity需要重建.
b.用戶退出App後, 系統可能由於內存原因將App殺死, 進程和activity都需要重啓, 但是可以在onCreate中將被動殺死鎖保存的狀態(saved instance state)恢復.

通過三種啓動狀態的相關描述, 可以看出我們要做的啓動優化其實就是針對冷啓動. 熱啓動和溫啓動都相對較快.

3、導致App啓動慢原因

根據冷啓動的時間圖, 可以看出, 對於App來說, 我們可以控制的啓動時間線的點無外乎:
(3.1)Application的onCreate
(3.2)首屏Activity的渲染
而我們現在的App動不動集成了很多第三方服務, 啓動時需要檢查廣告, 註冊狀態等等一系列接口都是在Application的onCreate或是首屏的onCreate中做的.

4、實例分析

(1)代碼分析

因爲這個App集成了Bugly, Push, Feedback等服務, 所以Application的onCreate有很多第三方平臺的初始化工作:

public class GithubApplication extends MultiDexApplication {

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

        // init logger.
        AppLog.init();

        // init crash helper
        CrashHelper.init(this);

        // init Push
        PushPlatform.init(this);

        // init Feedback
        FeedbackPlatform.init(this);

        // init Share
        SharePlatform.init(this);

        // init Drawer image loader
        DrawerImageLoader.init(new AbstractDrawerImageLoader() {
            @Override
            public void set(ImageView imageView, Uri uri, Drawable placeholder) {
                ImageLoader.loadWithCircle(GithubApplication.this, uri, imageView);
            }
        });
    }
}

(2)利用Traceview分析application的onCreate耗時

接下來我們結合我們上文的理論知識, 和介紹的Traceview工具, 來分析下Application的onCreate耗時.
在onCreate開始和結尾打上trace.

Debug.startMethodTracing("GithubApp");
...
Debug.stopMethodTracing();

運行程序, 會在sdcard上生成一個"GithubApp.trace"的文件.

注意: 需要給程序加上寫存儲的權限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

通過adb pull將其導出到本地

adb pull /sdcard/GithubApp.trace ~/temp

打開DDMS分析trace文件
ddms_open_trace
分析trace文件
在這裏插入圖片描述
在下方的方法區點擊"Real Time/Call", 按照方法每次調用耗時降序排.
耗時超過500ms都是值得注意的.
看左邊的方法名, 可以看到耗時大戶就是我們用的幾大平臺的初始化方法, 特別是Bugly, 還加載native的lib, 用ZipFile操作等.
點擊每個方法, 可以看到其父方法(調用它的)和它的所有子方法(它調用的).
點擊方法時, 上方的該方法執行時間軸會閃動, 可以看該方法的執行線程及相對時長.

(3)對Application的onCreate優化

將第三方SDK初始化放在一個單獨的線程中處理。這裏用了一個InitializeService的IntentService來做初始化工作.(IntentService不同於Service, 它是工作在後臺線程的.)
InitializeService.java代碼如下:

public class InitializeService extends IntentService {

    private static final String ACTION_INIT_WHEN_APP_CREATE = "com.anly.githubapp.service.action.INIT";

    public InitializeService() {
        super("InitializeService");
    }

    public static void start(Context context) {
        Intent intent = new Intent(context, InitializeService.class);
        intent.setAction(ACTION_INIT_WHEN_APP_CREATE);
        context.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {
                performInit();
            }
        }
    }

    private void performInit() {
        AppLog.d("performInit begin:" + System.currentTimeMillis());

        // init Drawer image loader
        DrawerImageLoader.init(new AbstractDrawerImageLoader() {
            @Override
            public void set(ImageView imageView, Uri uri, Drawable placeholder) {
                ImageLoader.loadWithCircle(getApplicationContext(), uri, imageView);
            }
        });

        // init crash helper
        CrashHelper.init(this.getApplicationContext());

        // init Push
        PushPlatform.init(this.getApplicationContext());

        // init Feedback
        FeedbackPlatform.init(this.getApplication());

        // init Share
        SharePlatform.init(this.getApplicationContext());

        AppLog.d("performInit end:" + System.currentTimeMillis());
    }
}

GithubApplication的onCreate改成:

public class GithubApplication extends MultiDexApplication {

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

        // init logger.
        AppLog.init();

        InitializeService.start(this);
    }
}

(4)啓動界面優化

步驟1:製作一個主題,帶上背景

<style name="SplashTheme" parent="AppTheme">
    <item name="android:windowBackground">@drawable/logo_splash</item>
</style>

步驟2:將一個不渲染布局的Activity作爲啓動屏,並加上主題

public class LogoSplashActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 注意, 這裏並沒有setContentView, 單純只是用來跳轉到相應的Activity.
        // 目的是減少首屏渲染
        
        if (AppPref.isFirstRunning(this)) {
            IntroduceActivity.launch(this);
        }
        else {
            MainActivity.launch(this);
        }
        finish();
    }
}
<activity
  android:name=".ui.module.main.LogoSplashActivity"
  android:screenOrientation="portrait"
  android:theme="@style/SplashTheme">
  <intent-filter>
      <action android:name="android.intent.action.MAIN"/>
      <category android:name="android.intent.category.LAUNCHER"/>
  </intent-filter>
</activity>

5、總結

(1)Application的onCreate中不要做太多事情.
(2)首屏Activity儘量簡化.
(3)善用性能分析工具分析.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章