Android App 優化之提升你的 App 啟動速度之例項挑戰

anly_jun發表於2016-11-16

為了便於閱讀, 應邀將Android App效能優化系列, 轉移到掘金原創上來.
掘金的新出的"收藏集"功能可以用來做系列文集了.

下面例項分析下App啟動優化怎麼做.

1, 程式碼分析

之前寫的Github App為例.

因為這個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);
            }
        });
    }
}複製程式碼

當前冷啟動效果:

Android App 優化之提升你的 App 啟動速度之例項挑戰

可以看到啟動時白屏了很長時間.

2, Traceview上場

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

在onCreate開始和結尾打上trace.

Debug.startMethodTracing("GithubApp");
...
Debug.stopMethodTracing();複製程式碼

執行程式, 會在sdcard上生成一個"GithubApp.trace"的檔案.

注意: 需要給程式加上寫儲存的許可權:

複製程式碼

通過adb pull將其匯出到本地

adb pull /sdcard/GithubApp.trace ~/temp複製程式碼

廣告: adb的眾多用法, 可以參考我的另一篇文

開啟DDMS分析trace檔案

Android App 優化之提升你的 App 啟動速度之例項挑戰

分析trace檔案

Android App 優化之提升你的 App 啟動速度之例項挑戰

  1. 在下方的方法區點選"Real Time/Call", 按照方法每次呼叫耗時降序排.
  2. 耗時超過500ms都是值得注意的.
  3. 看左邊的方法名, 可以看到耗時大戶就是我們用的幾大平臺的初始化方法, 特別是Bugly, 還載入native的lib, 用ZipFile操作等.
  4. 點選每個方法, 可以看到其父方法(呼叫它的)和它的所有子方法(它呼叫的).
  5. 點選方法時, 上方的該方法執行時間軸會閃動, 可以看該方法的執行執行緒及相對時長.

3, 調整Application onCreate再試

既然已經知道了哪些地方耗時長, 我們不妨調整下Application的onCreate實現, 一般來說我們可以將這些初始化放在一個單獨的執行緒中處理, 為了方便今後管理, 這裡我用了一個InitializeService的IntentService來做初始化工作.

明確一點, IntentService不同於Service, 它是工作在後臺執行緒的.

InitializeService.java程式碼如下:

package com.anly.githubapp.compz.service;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;

import com.anly.githubapp.common.wrapper.AppLog;
import com.anly.githubapp.common.wrapper.CrashHelper;
import com.anly.githubapp.common.wrapper.FeedbackPlatform;
import com.anly.githubapp.common.wrapper.ImageLoader;
import com.anly.githubapp.common.wrapper.PushPlatform;
import com.anly.githubapp.common.wrapper.SharePlatform;
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader;

/**
 * Created by mingjun on 16/8/25.
 */
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);
    }
}複製程式碼

看看現在的效果:

Android App 優化之提升你的 App 啟動速度之例項挑戰

可以看到提升了很多, 然後還有一點瑕疵, 就是起來的時候會有一個白屏, 如果手機較慢的話, 這個白屏就會持續一段時間, 不太友好.

那麼還有沒有什麼辦法優化呢?

4, 給我們的應用視窗弄一個PlaceHolder

Android最新的Material Design有這麼個建議的. 建議我們使用一個placeholder UI來展示給使用者直至App載入完畢.

怎麼做呢?

給Window加上背景

如第3節所言, 當App沒有完全起來時, 螢幕會一直顯示一塊空白的視窗(一般來說是黑屏或者白屏, 根據App主題).

前文理論基礎有說到, 這個空白的視窗展示跟主題相關, 那麼我們是不是可以從首屏的主題入手呢? 恰好有一個windowBackground的主題屬性, 我們來給Splash介面加上一個主題, 帶上我們想要展示的背景.

做一個logo_splash的背景:



    
    

    
    
        
    
複製程式碼

弄一個主題:


    @drawable/logo_splash
複製程式碼

將一個什麼不渲染布局的Activity作為啟動屏

寫一個什麼都不做的LogoSplashActivity.

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();
    }
}複製程式碼

在AndroidManifest.xml中設定其為啟動屏, 並加上主題:


  
      
      
  
複製程式碼

5, 最終的效果

讓我們來看下最終的效果:

Android App 優化之提升你的 App 啟動速度之例項挑戰

相比之前, 呈現給使用者的不再是一個白屏了, 帶上了logo, 當然這個背景要顯示什麼, 我們可以根據實際情況來自定義.

這種優化, 對於有些Application內的初始化工作不能移到子執行緒做的情況, 是非常友好的. 可以避免我們的App長時間的呈現給使用者一個空白的視窗.

6, 結語

照例, 總結下.
這次關於App啟動時間的優化, 寫了兩篇. 寫這麼多, 還是想傳達下個人做技術的思想, 也算是個人的經驗回顧, 拋磚引玉.

實際場景可能遠比這個複雜,在此更多的提供一種分析思路~歡迎擴充套件

矯情了, 還是總結下本文相關的吧:

  1. Application的onCreate中不要做太多事情.
  2. 首屏Activity儘量簡化.
  3. 善用工具分析.
  4. 多閱讀官方文件, 很多地方貌似無關, 實際有關聯, 例如這次就用了Material Design文件中的解決方案.

本文完整原始碼, 請移步Github

相關文章