Android パフォーマンスの最適化 (3) - 描画の最適化

携帯電話を実行する携帯電話の構成Androidは常に改善されていますが、それでもPC携帯電話と比較することはできず、PC携帯電話の大容量メモリと高性能を実現することはできませんCPUAndroidそのため、アプリケーション開発時に無制限にメモリやメモリを使用することは不可能でありCPUCPUメモリやメモリの使い方を誤ると、アプリケーションのフリーズやメモリオーバーフローなどの問題も発生します。

1 描画性能分析

Androidアプリケーションはクレンジング フェイスをユーザーに表示する必要があり、ユーザーはクレンジング フェイスと対話するため、インターフェイスの流暢さが非常に重要です。

1.1 描画原理

View3の描画プロセスには、 、measureの 3 つのステップがありlayoutdraw主にシステムのアプリケーション フレームワーク層で実行されますが、画面へのデータの実際のレンダリングはNativeシステム層のSurfaceFlingerサービスによって行われます。

描画処理は主に、ラスタライズとレンダリングを担当する 、 、 のデータ計算作業CPUによって実行MeasureLayoutますおよび(グラフィックス プロセッサ) はグラフィックス ドライバー層を介して接続されており、グラフィックス ドライバー層はキューを維持し、キューに追加れるため、キューからデータを取り出すことができます。RecordExecuteGPUCPUGPUgraphics processing unitCPUdisplay listGPU

描画性能に関して言えば、フレームの概念について言及する必要があります。フレーム数は、時間内に送信される画像の量であり、グラフィックス プロセッサが 1 秒間に何回リフレッシュできるか( ( )1sで表される) としても理解できます。各フレームは実際には静止画であり、フレームを素早く連続して表示することによって、動いているような錯覚が生まれます。最も単純な例は、ゲームをプレイしているときに、画面が にある場合は行き詰まりを感じませんが、画面が より低い場合、例えば に行き詰まりを感じることです。FPSFrames Per Second60fps60fps50fpsこれは、人間の脳が目で見た情報を継続的に受信して処理するためであり、単位時間あたりに処理されるフレーム数が多ければ多いほど、脳が認識できる最小フレーム数がロードされるためです。このとき、10fps ~ 12fps脳の イメージが静止しているのか変化しているのかは不明です。

画面を に保つためには60fps、 以内に画面を1 回1s更新する必要があります60。つまり、16.6667ms1 回更新しない (描画時間が16ms以内) 必要があります。

Androidシステムは、レンダリングをトリガーするために信号16msを送信します。すべてのレンダリングが成功すると、滑らかな画像に必要なものを達成できます(Vertical Synchronization)の略で、タイミング割り込みの一種で、信号を受信するとフレームごとのデータ処理を開始します。操作のコストが高すぎて、システムが信号を受け取ったときに正常にレンダリングできない場合、フレーム ドロップが発生します。ユーザーには、同じフレームが で表示されます。VSYNCUI60fpsVSYNCVSYNCVertical SynchronizationVSYNCCPU24msVSYNC32ms

吃音の原因はさまざまですが、主なものとしては以下のようなものがあります。

  • レイアウトLayoutが複雑すぎて16ms内でレンダリングできません。
  • 同時に実行されるアニメーションが多すぎると、過負荷が発生するCPUか、GPUオーバーロードが発生します。
  • Viewオーバードロー。同じフレーム時間内に一部のピクセルが複数回描画されます。
  • UI少し時間のかかる操作がスレッド内で実行されました
  • GC一時停止時間が長すぎるか、リサイクル中にGC大量の一時停止時間が頻繁に発生します。

1.2 ツール

1.2.1 GPU レンダリングのプロファイル

プロファイル GPU レンダリングは、Android 4.1システムによって提供される開発支援機能であり、開発者向けオプションでオンにすることができます: [設定] –> [開発者向けオプション] –> [GPU レンダリング モード分析] –> [画面上に棒グラフとして表示]:

GPUモード解析

図の横軸は時間を表し、縦軸は特定のフレームの消費時間を表します。緑色の横線は警告線です。この線を超えると継続時間を超過したことを意味します。縦方向のカラー ヒストグラムが維持されていることを確認してください16ms。緑の線の下。これらの縦方向の色付きヒストグラムはフレームを表し、異なる色の色付きヒストグラムは異なる意味を表します。

  • オレンジ色は処理時間を表し、フレームのレンダリングCPUを指示します。コマンドへの応答を待機するGPUため、これはブロック呼び出しです。オレンジ色のヒストグラムが高い場合は、非常にビジーであることを意味します。CPUGPUGPU
  • 赤は実行時間を表し、レンダリングAndroidにかかる​​時間を表します。赤のヒストグラムが高い場合は、ビューの再送信が原因である可能性があります。赤いヒストグラムが高くなる複雑なカスタマイズもあります。2DDisplay ListView
  • 青は、図面の測定時間、つまり の作成と更新にかかる時間を表しますDisplay List青色のヒストグラムが高い場合は、再描画する必要があるか、View.onDraw()メソッドが処理する処理が多すぎる可能性があります。

インターフェイスが更新されると、インターフェイスには各フレームのレンダリング時間がリアルタイム ヒストグラムで表示されます。ヒストグラムが高いほど、レンダリング時間が長くなります。各ヒストグラムには、ベンチマークを表す緑色の水平線があります。マークされた列の行には次の内容が含まれ16msます3 つの部分 (青はDisplay List描画の測定時間を表し、赤はOpenGLレンダリングDisplay Listに必要な時間を表し、黄色は処理のCPU待機時間を表しますGPU)。各フレームの合計時間がベースラインより低い限り、UIカードトンは存在しません。問題があります (実際、それらの一部がベースラインを超えていても問題はありません)。

1.2.2GPU描画

パフォーマンスを最適化するために、UI開発者向けオプションのGPUオーバードロー ツールを使用して分析することもできます。[設定] -> [開発者向けオプション] -> [デバッグGPUオーバードロー] でデバッグをオンにすると (デバイスごとに場所や名前が異なる場合があります)、次の図が表示されます (settings現在のインターフェイスのオーバードローを分析します)。

GPU描画

以下の手順:

オーバードロー

青 (1x上書き)、薄緑 (2x上書き)、薄赤 (3x上書き)、濃い赤 (上書き4x) は4さまざまなレベルのOverdraw状態を表します。目標は、赤を最小限に抑えOverdraw、より多くの青の領域を表示することです。

OverdrawUIレイアウトの重複部分が多いことが原因である場合もあれば、不必要に重複している背景が原因である場合もあります。たとえば、あるActivityオブジェクトには背景があり、その中のオブジェクトにもLayout独自の背景があり、同時にそれぞれの子にもView独自の背景があります。不要な背景画像を削除するだけで、多くの赤いOverdraw領域が減り、青い領域の割合が増加します。この対策により、プログラムのパフォーマンスが大幅に向上します。

レイアウトで両方を使用できる場合はRealtiveLayoutLinearLayoutそれを直接使用しますLinearLayout。これは、Relativelayoutのレイアウトがより複雑で、CPU描画に時間がかかるためです。複数LinearLayoutまたはFramelayout入れ子が必要な場合は、それを使用できますRelativelayoutマルチレベルのネストのため、レイアウトの描画の大部分が繰り返されるため、プログラムのパフォーマンスが低下します。

2 レイアウト最適化ツール - Layout Inspector

レイアウト インスペクター ツールとレイアウト検証ツールを使用してレイアウトをデバッグする

3 レイアウト最適化手法

レイアウトを最適化する方法は数多くありますが、主にレイアウト、includemergeの合理的な使用が挙げられますViewStub

3.1 レイアウトの合理的な使用

一般的に使用されるレイアウトには、主にLinearLayoutRelativeLayoutFrameLayoutなどが含まれます。これらを適切に使用すると、Android描画の負荷が軽減され、パフォーマンスが向上します。例えば:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="horizontal"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局优化" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Merge" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ViewStub" />

    </LinearLayout>

</LinearLayout>

レイアウトには 3 つのレイヤーがあることがわかります。

レイアウト階層

レイアウトには合計 のレイヤーがあり3、合計5が含まれていますViewRelativeLayoutで書き換えると、コードは次のようになります。

<?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=".MainActivity">

    <TextView
        android:id="@+id/tv_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局优化" />

    <TextView
        android:id="@+id/tv_text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/tv_text1"
        android:text="Merge" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/tv_text1"
        android:layout_below="@+id/tv_text2"
        android:text="ViewStub" />

</RelativeLayout>

レイアウトには 2 つのレイヤーがあることがわかります。

レイアウト階層

2レイアウトにはレイヤーがあり、合計4でがあり、このことから1レイヤーのレイアウトが削減されていることViewがわかります。RelativeLayoutレイアウトが複雑な場合、RelativeLayoutレイアウトのレベルを下げるために合理的に使用できます。の配置は相互に依存することに基づいているため、RelativeLayoutよりもパフォーマンスが低くなります。LinearLayoutRelativeLayoutView

しかし、実際の開発プロセスではさまざまな状況に直面するため、どちらのパフォーマンスが優れているとは簡単には言えません。一般に、レイアウト層が多い場合に使用することをお勧めします。RelativeLayoutまた、入れ子のレイアウトが多い場合に使用することをお勧めしますLinearLayout

3.2includeレイアウトの再利用にタグを使用する

複数のレイアウトが同じレイアウト (1 つなど) を再利用する必要がある場合TitleBar、これらの清掃面に同じレイアウトを追加する必要がある場合TitleBar、保守が非常に面倒になり、TitleBar追加する必要がある各清掃面にレイアウトをコピーする必要があります。省略が起こりやすいものです。変更する場合は、参照されているレイアウト内で変更するTitleBar必要があります。TitleBarこれらの問題を解決するには、includeラベルを使用して解決できます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp">

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:src="@drawable/ic_launcher_background"
        android:padding="3dp" />

</LinearLayout>

これはTitleBarで構成されます以前に使用したレイアウトに次のように導入してみましょう。ImageViewTextViewTitleBar

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <include layout="@layout/title_bar" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv_text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="布局优化" />

        <TextView
            android:id="@+id/tv_text2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/tv_text1"
            android:text="Merge" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_text2"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/tv_text1"
            android:text="ViewStub" />

    </RelativeLayout>
</LinearLayout>

レイアウトには 2 つのレイヤーがあることがわかります。

レイアウト階層

3.3mergeラベルを使用して冗長なレイヤーを削除する

mergeこれはマージを意味し、適切なシナリオでタグを使用すると、merge冗長なレイヤーを減らすことができます。mergeタグは通常、includeタグと組み合わせて使用​​されます。前のセクションの例では、merge代わりに タグが使用されている場合LinearLayout、コードは次のようになります。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp">

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:padding="3dp"
        android:src="@drawable/ic_launcher_background" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="绘制优化" />

</merge>

レイアウト階層:

レイアウト階層

以前のものはなくなっていることがわかりますが、 を置き換えるタグがLinearLayoutあるため、 が失敗し、レイアウトがめちゃくちゃになります。ラベルを置き換えるか、同じレイアウト方向にすることをお勧めします。たとえば、現在の親レイアウトのレイアウト方向が垂直で、含まれる子レイアウトのレイアウト防御線も垂直であるため、ラベルを使用できます。ただし、このシナリオではと のレイアウト方向は水平であり、明らかにこの要件を満たしていません。mergeLinearLayoutLinearLayoutmergeFrameLayoutLinearLayoutLinearLayoutLinearLayoutmergeTitleBarLinearLayout

3.4ViewStub読み込み速度を向上させるために使用する

一般的な開発シナリオでは、特定のレイアウト上のすべてのコントロールが表示されるのではなく、その一部が表示されることがあります。この場合、一般的な方法は と 属性を使用することです。この方法は効率的ではありませんが、非表示の目的は達成さViewGONEますVISIBLE。 、しかし、それらはまだレイアウト内にあり、システムは引き続きそれらを解析し、ViewStubこの問題を解決するために使用できます。

ViewStub軽量でView目に見えず、レイアウトスペースを取りません。ViewStubメソッドの呼び出しまたは設定が表示されるinflate、システムはViewStub指定されたレイアウトをクランプし、メソッドの呼び出しまたは設定が表示されるViewStub前にこのレイアウトを に追加します。レイアウト スペースやシステム リソースを占有しません。その主な目的は、ターゲットビューが位置を占めます。したがって、これを使用すると、クレンジング初期化のパフォーマンスが向上し、インターフェイスの読み込み速度が向上します。まず、レイアウトにタグを追加しますViewStubinflateViewStubViewStub

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout="@layout/title_bar" />

</LinearLayout>

タグ内でViewStub使用して、android:layout以前に作成したレイアウトを参照しますtitle_bar.xmlプログラムを実行すると、ViewStubラベルで参照されるレイアウトは表示できません。これは、レイアウトが にロードされておらずViewStub、コードで使用されていないためですViewStub

public class MainActivity extends AppCompatActivity {
    
    

    private ViewStub viewStub;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewStub = findViewById(R.id.view_stub);
        viewStub.inflate(); // 1
        viewStub.setVisibility(View.VISIBLE); // 2
    }
}

コメント1とコメントは、アプリケーションのレイアウトを に配置するため2に使用され、アプリケーションのレイアウトが表示されます。を使用する場合は、次の問題に注意してください。ViewStubViewStubViewStub

  • ViewStubロードできるのは 1 回のみで、ViewStubロード後はオブジェクトは空になるため、で参照されるレイアウトがロードされた後は、参照されるレイアウトを制御するためにViewStub使用することはできません。したがって、コントロールを継続的に表示および非表示にする必要がある場合でも、属性ViewStubを使用する必要がありますViewVisibility
  • ViewStubタグをネストすることはできませんmerge
  • ViewStub操作はレイアウト ファイルです。特定の を操作したいだけの場合は、属性Viewを使用する必要がありますViewVisibility

3.5 描画の最適化

描画の最適化は主に、View.onDrawメソッドが多数の操作の実行を回避する必要があることを意味します。

  • onDrawonDrawこのメソッドはリアルタイムで実行されるため、新しいローカル オブジェクトを作成する必要はありません。その結果、多数の一時オブジェクトが生成され、メモリ使用量が増加し、システムは常に実行されているため、実行効率が低下しますGC
  • onDrawこのメソッドでは時間のかかる操作を実行する必要がなく、ループには多くの時間onDrawがかかるため、メソッド内でのループの使用量は少なくなりますCPU描画が滑らかでなくなったり、カクついたりするなどの原因となります。ViewGoogle は、描画フレーム レートが で安定していることを公式に指摘しており、各フレームの描画時間が( )60dpsを超えないようにする必要があります保証するのは難しいですが、できる限り減らす必要があります。16ms1000/60

60dpsこれは、現時点で最適な画像表示速度であり、Androidほとんどのデバイスで設定されているデバッグ頻度でもあります。インターフェイスのリフレッシュ操作が正常に完了する16msと、滑らかな画像を表示できますがVSYNC、何らかの理由でリフレッシュ操作ができない場合があります。信号を受信すると完了しますので、フレームドロップが発生し、当然リフレッシュフレームレートも低下します(リフレッシュフレームレートが通常から低レベルに60fps低下する30fps

おすすめ

転載: blog.csdn.net/xingyu19911016/article/details/128815711