技術共有

Android パフォーマンスの最適化メモリの最適化

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Android パフォーマンスの最適化メモリの最適化

記憶力の問題

  • メモリスラッシング
  • メモリーリーク
  • メモリオーバーフロー

メモリスラッシング

メモリ スラッシングとは、短期間に大量のオブジェクトが作成および破棄されることを指し、その結果、ガベージ コレクション (ガベージ コレクション、GC) アクティビティが頻繁に発生します。この頻繁な GC アクティビティは大量の CPU リソースを消費し、アプリケーションの遅延やパフォーマンスの低下を引き起こす可能性があります。

パフォーマンス: メモリの曲線がギザギザになっています。

メモリーリーク

メモリ リークは、アプリケーションが不要になったオブジェクトへの参照を保持しているときに発生します。その結果、これらのオブジェクトがガベージ コレクタによってリサイクルされなくなり、解放されるはずのメモリ領域が占有されてしまいます。時間の経過とともにメモリ リークが発生すると、利用可能なメモリがどんどん減り、最終的にはアプリケーションのクラッシュやパフォーマンスの低下につながる可能性があります。

メモリオーバーフロー

メモリ オーバーフローは、アプリケーションが追加のメモリ領域を割り当てようとしたが、割り当てるのに十分なメモリ領域がなくなったため、システムが要求を満たすことができないときに発生します。これにより通常、アプリケーションは OutOfMemoryError 例外をスローします。

検出ツール

  • メモリプロファイラ
  • メモリアナライザー
  • リークカナリア

メモリプロファイラ

  • Memory Profiler は、Android Studio に付属するメモリ分析ツールです。
  • プログラムのメモリ使用量を示すリアルタイムのグラフ。
  • メモリ リーク、スラッシングなどを特定します。
  • ヒープ ダンプをキャプチャし、GC を強制し、メモリ割り当てを追跡する機能を提供します。

メモリアナライザー

  • メモリ リークとメモリ使用量を見つけるための強力な Java ヒープ分析ツール。
  • 全体的なレポートの生成、問題の分析などを行います。

リークカナリア

  • 自動メモリリーク検出。
    • LeakCanary は、アクティビティ、フラグメント、ビュー、ビューモデル、サービスのオブジェクトのリークを自動的に検出します。
  • 公式サイト:https://github.com/square/leakcanary

メモリ管理メカニズム

ジャワ

Java メモリ構造: ヒープ、仮想マシン スタック、メソッド領域、プログラム カウンター、ローカル メソッド スタック。

Java メモリ リサイクル アルゴリズム:

  • マークアンドスイープアルゴリズム:
    1. リサイクルする必要があるオブジェクトにマークを付ける
    2. マークされたすべてのオブジェクトを均一にリサイクルします。
  • コピーアルゴリズム:
    1. メモリを 2 つの同じサイズのチャンクに分割します。
    2. メモリの 1 つのブロックが使い果たされると、残ったオブジェクトが別のブロックにコピーされます。
  • マーキング照合アルゴリズム:
    1. マーキング プロセスは、「マーク アンド スイープ」アルゴリズムと同じです。
    2. 生存オブジェクトは一方の端に移動します。
    3. 残っているメモリをクリアします。
  • 世代別収集アルゴリズム:
    • 複数の収集アルゴリズムの利点を組み合わせます。
    • 新しい世代のオブジェクトの生存率は低く、コピー アルゴリズムが使用されます。
    • 旧世代のオブジェクトの生存率は高く、マークソートアルゴリズムが使用されます。

マーククリア アルゴリズムの欠点: マークとクリアは効率的ではなく、大量の不連続なメモリ フラグメントが生成されます。

レプリケーション アルゴリズム: 実装が簡単で、効率的に実行できます。デメリット:半分のスペースを無駄にしてしまう。

マークコンパクト アルゴリズム: マーククリーンによって引き起こされるメモリの断片化を回避し、コピー アルゴリズムのスペースの無駄を回避します。

アンドロイド

Android のメモリの柔軟な割り当て、割り当て値、および最大値は、特定のデバイスの影響を受けます。

Dalvik リサイクル アルゴリズムと ART リサイクル アルゴリズムは、どちらも Android オペレーティング システムのメモリ管理に使用されるガベージ コレクション メカニズムです。

Dalvik リサイクル アルゴリズム:

  • マークアンドスイープアルゴリズム。
  • 利点は実装が簡単なことです。欠点は、マーキングフェーズとクリアフェーズ中にアプリケーションの実行が一時停止されるため、アプリケーションが一時的にフリーズし、ユーザーエクスペリエンスに影響を与えることです。

アートリサイクルアルゴリズム:

  • 圧縮ガベージ コレクションのアルゴリズム。ガベージ コレクション中の一時停止時間を短縮するために、マークスイープ アルゴリズムに基づいて改良が加えられました。
  • 同時マーキング: ART では同時マーキング フェーズが導入されています。これは、アプリケーションの実行と同時に実行できることを意味します。これにより、ガベージ コレクションによる一時停止時間が短縮されます。
  • クリーンアップと圧縮: クリーンアップ フェーズでは、ART はマークされていないオブジェクトをクリアするだけでなく、メモリを圧縮します。つまり、残っているオブジェクトを一緒に移動してメモリの断片化を軽減します。これにより、メモリ管理がより効率的になり、メモリ割り当てエラーの可能性が減ります。
  • アダプティブ コレクション: ART にはアダプティブ コレクションの概念も導入されています。これは、アプリケーションの動作とメモリ使用パターンに基づいてガベージ コレクションの頻度と方法が自動的に調整されることを意味します。これにより、ART はさまざまなアプリケーション要件にさらに適応できるようになります。

LMK メカニズム:

  • 低メモリキラーメカニズム。
  • その主な機能は、システム メモリが不足してメモリを解放し、システムの安定性と応答性を確保できない場合に、特定の優先ポリシーに従って一部のバックグラウンド プロセスを終了することです。

解決する

メモリのスラッシング問題

シミュレーション問題のコード
public class ShakeActivity extends AppCompatActivity {

    private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            String str = "";
            for (int i = 0; i < 10000000; i++) {
                str += i;
            }
            mHandler.sendEmptyMessageDelayed(1, 30);
        }
    };

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

    public void startClick(View view) {
        mHandler.sendEmptyMessage(1);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
メモリ プロファイラ ツールを使用して検出します

メモリ プロファイラーでメモリ割り当てを表示するには、[Java/Kotlin 割り当ての記録] をクリックします。

ここに画像の説明を挿入します

上記の意味:

  • Java: Java または Kotlin コードによって割り当てられたメモリ。
  • ネイティブ: C または C++ コードによって割り当てられたメモリ。
  • グラフィックス: ピクセルを画面に表示するためにグラフィックス バッファー キューによって使用されるメモリ (GL サーフェス、GL テクスチャなどを含む)。 CPU 共有メモリ。
  • スタック: アプリケーション内のネイティブ スタックと Java スタックによって使用されるメモリ。これは通常、アプリが実行しているスレッドの数に関係します。
  • dex バイトコード、最適化またはコンパイルされた dex コード、.so ライブラリ、フォントなどのコードとリソースを処理するためにアプリケーションによって使用されるメモリ。
  • アプリケーションは、システムが分類方法を不明なメモリを使用します。
  • アプリケーションによって割り当てられた Java/Kotlin オブジェクトの数。この数値には、C または C++ で割り当てられたオブジェクトは考慮されていません。

以下の意味:

  • 割り当て: パス malloc() またはnew オペレーターによって割り当てられたオブジェクトの数。
  • 割り当て解除: 経由 free() またはdelete オペレーターによって割り当てが解除されたオブジェクトの数。
  • 割り当てサイズ: 選択した期間中のすべての割り当ての合計サイズ (バイト単位)。
  • 割り当て解除サイズ: 選択した期間内に解放されたメモリの合計サイズ (バイト単位)。
  • 合計数: 割り当てから割り当て解除を差し引いた数。
  • 残りのサイズ: 割り当てサイズから割り当て解除サイズを引いたもの。
  • Shallow Size: ヒープ内のすべてのインスタンスの合計サイズ (バイト単位)。

上の写真の分析:

ここに画像の説明を挿入します

この領域の割り当てと割り当て解除の値は比較的似ており、浅いサイズは比較的大きく、オブジェクトが頻繁に作成および破棄される可能性があることを示しています。

ここに画像の説明を挿入します

クリックするとコール スタック情報が表示され、コードと組み合わせると、ハンドラーにメモリ ジッターの問題があると推測できます。

最適化のヒント
  • オブジェクトの作成と破棄を頻繁に行わないようにします。

メモリリークの問題

シミュレーション問題のコード
public class CallbackManager {
    public static ArrayList<Callback> sCallbacks = new ArrayList<>();

    public static void addCallback(Callback callback) {
        sCallbacks.add(callback);
    }

    public static void removeCallback(Callback callback) {
        sCallbacks.remove(callback);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public class LeakActivity extends AppCompatActivity implements Callback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        ImageView imageView = findViewById(R.id.imageView);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
        imageView.setImageBitmap(bitmap);
        CallbackManager.addCallback(this);
    }

    @Override
    public void onOperate() {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
LeakCanary ツールを使用して検出します

依存ライブラリを追加します。

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
  • 1

メモリ リークが発生すると、LeakCanary は関連情報を生成し、自動的にダンプします。

ここに画像の説明を挿入します

上の図からわかるように、LeakActivity にはメモリ リークがあり、参照チェーン関係が表示されます。

もちろん、hprof ファイルを生成し、プロファイラー ツールを使用して特定の情報を表示することもできます。

ここに画像の説明を挿入します

上の図からわかるように、LeakActivity を含む 10 個のリーク ポイントが発生しています。LeakActivity をクリックしてメモリ リーク オブジェクトを表示し、それが ArrayList によって保持されていることがわかります。

最適化のヒント
  • 収集要素のタイムリーなリサイクル。
  • あまりにも多くのインスタンスへの静的参照を避けてください。
  • 静的内部クラスを使用します。
  • リソース オブジェクトをすぐに閉じてください。

ビットマップの最適化

Bitmap を使用した後、画像リソースを解放しないと、次のような問題が発生しやすくなります。メモリリークによるメモリオーバーフロー

ビットマップメモリ​​モデル
  • api10 (Android 2.3.3) より前: ビットマップ オブジェクトはヒープ メモリに配置され、ピクセル データはローカル メモリに配置されます。
  • api10 以降: すべてヒープ メモリ内にあります。
  • api26 以降 (Android8.0): ピクセル データはローカル メモリに配置されます。これにより、ネイティブ層のビットマップ ピクセル データを Java 層のオブジェクトとともに迅速に解放できるようになります。

メモリのリサイクル:

  • Android 3.0 より前では、手動で呼び出す必要がありますBitmap.recycle()ビットマップのリサイクルを実行します。
  • Android 3.0 以降、システムはよりインテリジェントなメモリ管理を提供し、ほとんどの場合、ビットマップを手動でリサイクルする必要がありません。

ビットマップピクセル構成:

設定占有バイトサイズ(バイト)説明する
アルファ_81単一の透明チャネル
565色2シンプルなRGB色合い
ARGB_8888424ビットトゥルーカラー
RGBA_F168Android 8.0 新 (HDR)

Btimap が占有するメモリを計算します。

  • ビットマップ#getByteCount()
  • getWidth() * getHeight() * 1 ピクセルがメモリを占有します
リソースファイルディレクトリ

リソースファイルの問題:

  • mdpi (中密度): 約 160dpi、1x リソース。
  • hdpi (高密度): 約 240dpi、1.5 倍のリソース。
  • xhdpi (超高密度): 約 320dpi、2 倍のリソース。
  • xxhdpi (超超高密度): 約 480dpi、3 倍のリソース。
  • xxxhdpi (超超高密度): 約 640dpi、4 倍のリソース。

テストコード:

private void printBitmap(Bitmap bitmap, String drawable) {
    String builder = drawable +
            " Bitmap占用内存:" +
            bitmap.getByteCount() +
            " width:" +
            bitmap.getWidth() +
            " height:" +
            bitmap.getHeight() +
            " 1像素占用大小:" +
            getByteBy1px(bitmap.getConfig());
    Log.e("TAG", builder);
}

private int getByteBy1px(Bitmap.Config config) {
    if (Bitmap.Config.ALPHA_8.equals(config)) {
        return 1;
    } else if (Bitmap.Config.RGB_565.equals(config)) {
        return 2;
    } else if (Bitmap.Config.ARGB_8888.equals(config)) {
        return 4;
    }
    return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
// 逻辑密度
float density = metrics.density;
// 物理密度
int densityDpi = metrics.densityDpi;
Log.e("TAG", density + "-" + densityDpi);

// 1倍图
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.splash1);
printBitmap(bitmap1, "drawable-mdpi");

// 2倍图
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.splash2);
printBitmap(bitmap2, "drawable-xhdpi");

// 3倍图
Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.splash3);
printBitmap(bitmap3, "drawable-xxhdpi");

// 4倍图
Bitmap bitmap4 = BitmapFactory.decodeResource(getResources(), R.drawable.splash4);
printBitmap(bitmap4, "drawable-xxxhdpi");

// drawable
Bitmap bitmap5 = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
printBitmap(bitmap5, "drawable");
/*
        3.0-480
        drawable-mdpi Bitmap占用内存:37127376 width:2574 height:3606 1像素占用大小:4
        drawable-xhdpi Bitmap占用内存:9281844 width:1287 height:1803 1像素占用大小:4
        drawable-xxhdpi Bitmap占用内存:4125264 width:858 height:1202 1像素占用大小:4
        drawable-xxxhdpi Bitmap占用内存:2323552 width:644 height:902 1像素占用大小:4
        drawable Bitmap占用内存:37127376 width:2574 height:3606 1像素占用大小:4
         */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

例証します:

mdpi デバイスでは 1dpxhdpi デバイスでは 1px、1dpxxhdpi デバイスでは 2px、1dp==3px。

したがって、現在のデバイスは xxhdpi であるため、xxhdpi リソースの下では同じ画像の幅は 858、mdpi リソースの下では 3 倍に拡大され、幅は 2574、xhdpi リソースの下では 1.5 倍に拡大され、幅は 2574 になります。幅は1287です。

最適化のヒント
  • 複数の画像リソースのセットを設定します。
  • 適切なデコード方法を選択してください。
  • 画像キャッシュを設定します。

ソースコードのダウンロード