インタビュアー:Androidスタートアップの最適化について話す

アプリケーションの起動速度はAPPにとって非常に重要であり、ユーザーエクスペリエンスに直接影響します。起動速度が遅すぎると、ユーザーの損失につながります。この記事では、起動速度の最適化を分析し、いくつかのアイデアを提供します。起動速度を最適化するため。

1.アプリケーションの起動分類

アプリケーションには3つの起動状態があり、それぞれがアプリケーションがユーザーに表示されるのに必要な時間に影響します。コールド起動、ウォーム起動、およびホット起動です。コールドスタートでは、アプリケーションは最初から起動します。他の2つの状態では、システムはバックグラウンドで実行されているアプリケーションをフォアグラウンドにする必要があります。

1.1、コールドスタート

アプリのコールドスタートは2つの段階に分けることができます

第一段階

1.アプリをロードして起動します

2.起動直後に空白の起動ウィンドウが表示されます

3.アプリプロセスを作成します

第2段

1.アプリオブジェクトを作成します

2.メインスレッドを開始します

3.メインアクティビティを作成します

4.レイアウトをロードします

5.画面をレイアウトします

6.最初のフレームを描画します

アプリケーションプロセスが最初のフレームの描画を終了すると、システムプロセスは現在表示されている背景ウィンドウを置き換え、メインのアクティビティに置き換えます。この時点で、ユーザーはアプリの使用を開始できます

コールドスタートでは、開発者として介入できる主な部分は、次の図に示すように、アプリケーションのOnCreateフェーズとアクティビティのonCreateフェーズです。

1.2、ホットスタート

ウォームスタート中、システムはアクティビティをフォアグラウンドに置きます。アプリケーションのすべてのアクティビティがメモリに存在する場合、アプリケーションは、オブジェクトの初期化、レンダリング、および描画操作の繰り返しを回避できます。

1.3、ウォームスタート

ウォームスタート中、アプリプロセスがまだ存在するため、コールドスタートの第2段階が実行されます

1.アプリオブジェクトを作成します

2.メインスレッドを開始します

3.メインアクティビティを作成します

4.レイアウトをロードします

5.画面をレイアウトします

6.最初のフレームを描画します

ウォームスタートの一般的なシナリオ:

1.ユーザーは、アプリケーションを終了した後、アプリケーションを再起動します。プロセスは引き続き実行される場合があります。ただし、アプリケーションは、ActivityのonCreateメソッドを呼び出してから再び実行を開始します。

2.メモリが不足している場合、システムはアプリケーションを強制終了し、ユーザーは再起動します。プロセスとアクティビティを再起動する必要がありますが、onCreateに渡された保存済みインスタンスバンドルは起動を完了するのに役立ちます

次に、起動時間を取得する方法を見てみましょう〜

2.起動時間を取得します

2.1、adbコマンドを使用して取得します

adb shell am start -W [packageName] / [packageName.xxActivity]

adb shell am start -W com.tg.test.gp/com.test.demo.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.tg.test.gp/com.test.demo.MainActivity }
Status: ok
LaunchState: COLD
Activity: com.tg.test.gp/com.test.demo.MainActivity
ThisTime: 1344
TotalTime: 1344
WaitTime: 1346
Complete

ThisTime:最後のアクティビティの開始には時間がかかります

TotalTime:起動時に経験したすべてのアクティビティによって消費された合計時間

WaitTime:AMSがすべてのアクティビティを開始するための合計時間(ターゲットアプリケーションを開始する前の時間を含む)

2.2、コードを使用して管理する

public class LaunchTimer {

    private static long sTime;

    public static void startRecord() {
        sTime = System.currentTimeMillis();
    }
    public static void endRecord() {
        endRecord("");
    }
    public static void endRecord(String msg) {
        long cost = System.currentTimeMillis() - sTime;
        LogUtils.i(msg + "cost " + cost);
    }
}

このメソッドは通常、アプリケーション初期化attachBaseContextメソッドの開始タイムスタンプを設定し、終了タイムスタンプはアプリケーションユーザーの操作インターフェイスが完全に表示されて操作可能になった後に設定されます。2つの時間差は起動時間です。

ただし、この方法は洗練されていません。起動ロジックが複雑で、多くの方法がある場合は、最適化にaopを使用するのが最適です。

3.アプリケーション起動最適化分析ツール

3.1、TraceView

Traceviewは、Androidに付属するパフォーマンス分析ツールです。メソッドの呼び出し時間、呼び出しスタックをグラフィカルに表示し、すべてのスレッド情報を表示できます。メソッドの分析には時間がかかり、呼び出しチェーンは非常に優れたツールです。

使用方法は、コード埋め込み方法を使用することです。

1.収集位置の先頭で、を実行Debug.startMethodTracing("app_trace")します。ここで、パラメーターはカスタムファイル名です。

2.収集位置の最後で、実行しますDebug.endMethodTracing()

ファイル生成パスは/ sdcard / Android / data / package name / files / filename.traceです。ファイルはAndroidStudioを使用して開くことができます。

例えば:

testAddの時間のかかるメソッドを見てみましょう

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testAdd(3, 5);
    }

    private void testAdd(int a, int b) {
        Debug.startMethodTracing("app_trace");
        int c = a + b;
        Log.i("Restart", "c =  a + b = " + c);
        Debug.stopMethodTracing();
    }
}

プログラムの実行後、/ sdcard / Android / data / com.restart.perference / files / app_trace.traceファイルを見つけ、AndroidStudioでダブルクリックすると、ファイル内の情報を解析して、次のように開くことができます。

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-R2s1gpZz-1616421092820)(https://upload-images.jianshu.io/ upload_images / 25094154-7d9a059e870d906c.image?imageMogr2 / auto-orient / strip%7CimageView2 / 2 / w / 1240)]

CallChart、FlameChart、Top Down、およびBottomUpをそれぞれ使用する方法を見てみましょう。まず、タイムゾーンを選択します。この場合と同様に、プログラムが非常に単純であるため、分析できる領域が比較的小さくなります。選択すると、次の図が表示されます。

画像

コールチャートで得られたグラフでは、プログラムのコールスタック全体と時間のかかる方法を見ることができます。

たとえば、図のtestAddメソッドは、Debug.startMethodTracing、Log.i、およびDebug.stopMethodTracingメソッドを連続して呼び出しています。同時に、図から、startMethodTracingメソッドはstopMethodTracingメソッドよりも時間がかかることがわかります。実際の最適化では、どの方法を見つけるのに時間がかかり、ターゲットを絞った最適化が非常に役立ちます。

分析する場合、通常、サードパーティのプログラムやシステムコードを最適化することはできません。CallChartは、自分で作成したコードを区別するために、色を非常に密接に使用しています。オレンジ色のものはシステムAPI、青色のものは通常サードパーティのAPI、緑色のものは私たち自身が作成したものです。たとえば、図のtestAddメソッドでは、自分で作成したものを調整および最適化できます。

フレームチャートはコールチャートの逆チャートであり、同様の機能を備えています。グラフィックは次のとおりです。

トップダウンでは、各メソッド内で呼び出されているメソッドと、各メソッドの時間と時間の割合を確認できます。コールチャートのグラフィック検索と比較すると、トップダウンはより具体的で、特定のメソッドの時間のかかるデータがあります。 。

この図で、Totalはメソッドによって消費された合計時間を表し、selfはメソッド内の非メソッド呼び出しコードによって消費された時間を表し、Childrenはメソッドで呼び出された他のメソッドによって消費された時間を表します。

例としてtestAddメソッドを取り上げます。testAddメソッドによって消費される合計時間は3840usで、プログラム実行時間の97%を占めます。testAddメソッドのコードは342usを取り、8.65%を占め、その他のメソッドはtestAddメソッドは合計3498usを取ります。88.52%を占めています

private void testAdd(int a, int b) {
        Debug.startMethodTracing("app_trace");//算到Children中
        int c = a + b;//这一句是算在self耗时中,耗时其实很短
        Log.i("Restart", "c =  a + b = " + c);//算到Children中
        Debug.stopMethodTracing();//算到Children中
}

ボトムアップはトップダウンの逆グラフであり、メソッドによって呼び出されたメソッドを確認できます。

画像

TraceViewには注目に値する別のオプションがあります。

右上隅には、実時間スレッド時間のオプションがあります。実時間はメソッドによって消費された実際の時間を意味し、スレッド時間はCPU時間を指します。通常、最適化についてはCPU時間の最適化について詳しく説明します。IO操作がある場合は、スレッド時間を使用して時間のかかる分析を行う方が合理的です。

さらに、TraceViewの使用には、TraceViewの実行時のオーバーヘッドに注意を払う必要があります。これは、TraceView自体に時間がかかり、方向を最適化する可能性があるためです。

3.2、Systrace

SystraceはAndroidカーネルデータを組み合わせてHTMLレポートを生成します

systraceはAndroid / sdk / platform-tools / systraceディレクトリにあります。systraceはpythonを使用してhtmlを生成するため、使用する前にpythonをインストールする必要があります

報告によると、コマンドは次のとおりです。

python systrace.py -b 32768 -t 10 -a 包名 -o perference.html sched gfx view wm am app

特定のパラメータについては、developer.android.google.cn / studio / prof ...を参照してください

コマンドを実行した後、レポートを開くと、次のように表示されます

Chromeブラウザを使用して開きます。そうしないと、白い画面が表示される場合があります。chromeを使用して白い画面を表示する場合は、chromeブラウザにchrome:tracingと入力し、ファイルを読み込んで表示します。

画像を表示しているときは、Aキーが左に移動し、Dキーが右に移動し、Sキーがズームアウトし、Wキーがズームインします。

4.一般的な最適化

4.1。ブートロードの一般的な最適化戦略

アプリケーションが大きいほど、関連するモジュールが多くなり、ネットワークモジュールの初期化、低レベルのデータの初期化など、アプリケーションに含まれるサービスやプロセスも多くなります。これらの読み込みは事前に準備する必要があり、不要なものは配置しないでください。アプリケーションで。開始点は通常、次の4つの次元から並べ替えることができます。

1.必要で時間のかかる:初期化を開始し、スレッドを使用して初期化することを検討してください

2.必要で時間もかからない:対処する必要がない

3.不必要に時間がかかる、データレポート、プラグインの初期化、およびオンデマンドでの処理

4.必須ではなく、時間がかかる:必要に応じて取り外してロードするだけです

アプリケーションの起動時に実行されるコンテンツは上記のように分類され、ロードロジックはオンデマンドで実装されます。では、一般的な最適化されたロード戦略は何ですか?

非同期ロード:時間のかかるロードは子スレッドで非同期に実行されます

遅延読み込み:重要でないデータの遅延読み込み

早期読み込み:ContentProviderを使用して事前に初期化する

以下に、非同期ロードと遅延ロードの一般的な処理をいくつか紹介します。

4.2、非同期ロード

非同期ロードとは、簡単に言うと、サブスレッドを使用して非同期にロードすることです。実際のシナリオでは、多くの場合、起動時にさまざまなサードパーティライブラリを初期化する必要があります。初期化を子スレッドに配置することで、起動を大幅に高速化できます。

ただし、通常、一部のビジネスロジックは、サードパーティライブラリの初期化後にのみ正常に実行できます。現時点では、サブスレッドに配置して実行すると、初期化が行われる前にビジネスロジックが実行される可能性があります。制限なしで完了しました。異常です。

このより複雑な状況では、CountDownLatchを使用して対処するか、ランチャーのアイデアを使用して対処することができます。

CountDownLatchの使用

class MyApplication extends Application {

    // 线程等待锁
    private CountDownLatch mCountDownLatch = new CountDownLatch(1);

    // CPU核数
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // 核心线程数
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

    void onCreate() {
		ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
        service.submit(new Runnable() {
            @Override public void run() {
            	//初始化weex,因为Activity加载布局要用到需要提前初始化完成
                initWeex();
                mCountDownLatch.countDown();
            }
        });

        service.submit(new Runnable() {
            @Override public void run() {
            	//初始化Bugly,无需关心是否在界面绘制前初始化完
                initBugly();
            }
        });

        //提交其他库初始化,此处省略。。。

		try {
            //等待weex初始化完才走完onCreate
            mCountDownLatch.await();
		} catch (Exception e) {
            e.printStackTrace();
		}
    }
}

初期化ロジックが複雑でない場合は、CountDownLatchを使用することをお勧めします。ただし、初期化されたライブラリ間に相互依存関係があり、ロジックが複雑な場合は、ローダーを使用することをお勧めします。

ランチャー

ランチャーのコアは次のとおりです。

  • CPUのマルチコア機能を最大限に活用し、タスクを自動的に分類して順番に実行します。
  • コードはタスク化されており、起動タスクは各タスクに抽象化されています。
  • すべてのタスク依存関係の並べ替えに従って、有向非巡回グラフを生成します。
  • マルチスレッドはスレッド優先順に実行されます

具体的な実装については、github.com / NoEndToLF / A ...を参照してください

4.3、遅延読み込み

一部のサードパーティライブラリの初期化は実際には優先度が高くなく、オンデマンドでロードできます。または、IdleHandlerを使用して、メインスレッドがアイドル状態のときにバッチで初期化します。

オンデマンドの読み込みは特定の条件に応じて実装できるため、ここでは詳しく説明しません。IdleHandlerの使用の概要は次のとおりです

    private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            //当return true时,会移除掉该IdleHandler,不再回调,当为false,则下次主线程空闲时会再次回调
            return false;
        }
    };

IdleHandlerを使用してバッチの初期化を行いますが、なぜバッチ処理を行うのですか?メインスレッドがアイドル状態のとき、IdleHandlerが実行されますが、IdleHandlerの内容が多すぎると、フリーズが発生します。したがって、メインスレッドがアイドル状態のときに初期化操作をバッチ処理するのが最適です。

public class DelayInitDispatcher {

    private Queue<Task> mDelayTasks = new LinkedList<>();

    private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            //每次执行一个Task,实现分批进行
            if(mDelayTasks.size()>0){
                Task task = mDelayTasks.poll();
                new DispatchRunnable(task).run();
            }
            //当为空时,返回false,移除IdleHandler
            return !mDelayTasks.isEmpty();
        }
    };

    //添加初始化任务
    public DelayInitDispatcher addTask(Task task){
        mDelayTasks.add(task);
        return this;
    }

    //给主线程添加IdleHandler
    public void start(){
        Looper.myQueue().addIdleHandler(mIdleHandler);
    }

}

4.4、事前読み込み

上記のスキームで初期化する最速の時間は、アプリケーションのonCreateですが、以前の方法があります。ContentProviderのonCreateは、アプリケーションのattachBaseContextメソッドとonCreateメソッドの間で実行されます。つまり、ApplicationのonCreateメソッドよりも早く実行されます。したがって、これを使用して、サードパーティライブラリの初期化を事前にロードできます。

androidx-スタートアップの使用

如何使用:
第一步,写一个类实现Initializer,泛型为返回的实例,如果不需要的话,就写Unit
class TimberInitializer : Initializer<Unit> {

    //这里写初始化执行的内容,并返回初始化实例
    override fun create(context: Context) {
        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
            Timber.d("TimberInitializer is initialized.")
        }
    }

    //这里写初始化的东西依赖的另外的初始化器,没有的时候返回空List
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }

}

第二步,在AndroidManifest中声明provider,并配置meta-data写初始化的类
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="com.test.pokedex.androidx-startup"
    android:exported=“false"
    //这里写merge是因为其他模块可能也有同样的provider声明,做合并操作
    tools:node="merge">
    //当有相互依赖的情况下,写顶层的初始化器就可以,其依赖的会自动搜索到
    <meta-data
        android:name="com.test.pokedex.initializer.TimberInitializer"
        android:value="androidx.startup" />
</provider>

4.5。その他の最適化

1.アプリケーションで、起動時のデフォルトイメージを追加するか、テーマをカスタマイズし、最初にアクティビティでデフォルトのインターフェイスを使用して、部分的な起動時の短い白黒画面の問題を解決します。android:theme = "@ style / Theme.AppStartLoad"など

5.まとめ

1.コールドスタート、ウォームスタート、ホットスタートの主な処理とその違い

2.起動時間を取得する方法、adbコマンドとコード管理を使用する2つの方法を紹介しました

3.ツールを使用してプログラム内の時間のかかるコードを見つけ、TraceViewとSystraceの使用法を紹介する方法

4.一般的な起動最適化の方法と実装(非同期読み込み、遅延読み込み、早期読み込みなど)の概要、重要なアイデア:非同期、遅延、遅延読み込み

おすすめ

転載: blog.csdn.net/zhireshini233/article/details/115101211