Android の面接ではパフォーマンスの最適化を尋ねる必要があります

Android 開発者にとって、基本的なアプリケーション開発スキルを知っているだけでは十分ではないことがよくあります。仕事であれ面接であれ、開発者はアプリケーションのエクスペリエンスを向上させるために非常に重要なパフォーマンスの最適化について多くのことを知る必要があるからです。Android 開発の場合、パフォーマンスの最適化は主に次の側面に焦点を当てます: 起動の最適化、レンダリングの最適化、メモリの最適化、ネットワークの最適化、スタックの検出と最適化、消費電力の最適化、インストール パッケージのボリュームの最適化、セキュリティの問題など。

1. 最適化を開始する

アプリケーションの起動速度はユーザー エクスペリエンスに直接影響する可能性があり、起動が遅いとユーザーがアプリケーションをアンインストールして放棄する可能性があります。

1.1 コールドスタート、ホットスタート、ウォームスタートの最適化

1.1.1 コンセプト

Androidアプリの場合、起動方法によりコールドスタートアップ、ホットスタートアップ、ウォームスタートアップの3種類に分けられます。

  • コールド スタート: システム内にアプリ プロセスが存在しないとき (アプリが初めて起動されたとき、またはアプリが完全に終了したときなど) にアプリを起動することは、コールド スタートと呼ばれます。
  • ウォーム スタート: ホーム ボタンが押されたとき、または他の状況でアプリがバックグラウンドに切り替わったときに、アプリを再度起動するプロセス。
  • ウォーム スタート: ウォーム スタートにはコールド スタートの一部の操作が含まれますが、アプリ プロセスはまだ存在するため、ホット スタートよりもオーバーヘッドが高くなります。

ホット スタートが最も高速な起動であるのに対し、ウォーム スタートはコールド スタートとホット スタートの間の起動方法であることがわかります。コールド スタートは、多くのプロセスの作成が必要なため、最も時間がかかります。コールド スタートに関連するタスク フローは次のとおりです。

1.1.2 視覚的な最適化

コールド スタート モードでは、システムは 3 つのタスクを開始します。

  • アプリケーションをロードして起動します。
  • 起動するとすぐに、アプリケーションの空の起動ウィンドウが表示されます。
  • 申請プロセスを作成します。

システムが申請プロセスを作成すると、申請プロセスは次の段階に入り、次のようにいくつかの作業を完了します。

  • アプリオブジェクトの作成
  • メインスレッド(メインスレッド)を開始します。
  • アプリケーションエントリ用のアクティビティオブジェクトを作成する
  • ロードレイアウトビューを設定します
  • 画面上でViewの描画処理を実行 計測→レイアウト→描画

アプリケーション プロセスが最初の描画を完了すると、システム プロセスは現在表示されている背景ウィンドウを交換し、メイン アクティビティに置き換えます。この時点で、ユーザーはアプリケーションの使用を開始できます。アプリ アプリケーション プロセスの作成プロセスは携帯電話のソフトウェアとハ​​ードウェアによって決定されるため、この作成プロセス中には視覚的な最適化しか実行できません。

1.1.3 テーマの最適化を開始する

コールド スタート中に、アプリケーション プロセスが作成された後、起動ウィンドウのテーマを設定する必要があります。現在、ほとんどのアプリケーションは、起動会議中に最初にスプラッシュ スクリーン ページ (LaunchActivity) に入り、アプリケーション情報を表示しますが、アプリケーション内で他のサードパーティ サービスが初期化されている場合、起動時に白い画面の問題が発生します。

スプラッシュ スクリーン ページをよりスムーズかつシームレスに接続するために、起動アクティビティのテーマでスプラッシュ スクリーン ページの画像を設定し、起動ウィンドウの画像が白い画面ではなくスプラッシュ スクリーン ページの画像になるようにすることができます。

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/lunch</item>  //闪屏页图片
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
    </style>

1.2 コードの最適化

テーマを設定する方法は、要求がそれほど要求されないシナリオでのみ使用でき、この種の最適化は症状を治療しますが、根本原因は治療しません。鍵はコードの最適化にあります。最適化するには、いくつかの基本データが必要です。

1.2.1 コールドスタートに時間がかかる統計

ADBコマンドによる方法: Android Studioのターミナルで以下のコマンドを入力し、ページの起動時間を表示します。

adb shell am start  -W packagename/[packagename].首屏Activity

実行が完了すると、次の情報がコンソールに出力されます。

Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.optimize.performance/.MainActivity }
Status: ok
Activity: com.optimize.performance/.MainActivity
ThisTime: 563
TotalTime: 563
WaitTime: 575
Complete

上記のログには、ThisTime、TotalTime、WaitTime という 3 つのフィールド情報があります。

  • ThisTime : 最後のアクティビティの開始にかかる時間
  • TotalTime : すべてのアクティビティの開始にかかる時間
  • WaitTime : AMS がアクティビティを開始するまでにかかった合計時間

ログ方式:埋め込み時間方式は、オンライン時間をカウントするもう 1 つの方法であり、開始時刻と終了時刻を記録し、その差を取る方式です。まず、時間をカウントするためのツール クラスを定義する必要があります。

class LaunchRecord {

    companion object {

        private var sStart: Long = 0

        fun startRecord() {
            sStart = System.currentTimeMillis()
        }

        fun endRecord() {
            endRecord("")
        }

        fun endRecord(postion: String) {
            val cost = System.currentTimeMillis() - sStart
            println("===$postion===$cost")
        }
    }
}

起動時はApplicationのattachBaseContextで直接管理します。では、どこから始めてどこで終わらせればいいのでしょうか?ポイントの埋め込みを終了するには、ページ データが表示されているときにポイントを埋め込むことをお勧めします。次の方法を使用できます。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mTextView.viewTreeObserver.addOnDrawListener {
            LaunchRecord.endRecord("onDraw")
        }

    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        LaunchRecord.endRecord("onWindowFocusChanged")
    }
}

1.2.2 検出ツールの最適化

起動の最適化を行う場合、サードパーティのツールを使用すると、各段階のメソッドやスレッド、CPU の実行時間などを明確にすることができます。ここでは主に TraceView と SysTrace の 2 つのツールを紹介します。

トレースビュー

TraceView は、実行時間、コール スタック、その他の情報をグラフィカル形式で表示します。情報は比較的包括的であり、次の図に示すように、すべてのスレッドが含まれます。

TraceView 検出を使用して生成された結果は、Andrid/data/packagename/files パスに配置されます。Traceview によって収集される情報は比較的包括的なため、重大な操作オーバーヘッドが発生し、アプリ全体の実行速度が低下するため、Traceview が起動時間に影響を与えるかどうかを区別することはできません。

SysTrace Systrace は、Android カーネル データを結合して HTML レポートを生成し、各スレッドの実行時間、メソッドの消費時間、CPU 実行時間などを確認できます。

API バージョン 18 以降の場合、これは互換性のある API であるため、TraceCompat を直接使用してデータをキャプチャできます。

开始:TraceCompat.beginSection("tag ")
结束:TraceCompat.endSection()

次に、次のスクリプトを実行します。

python systrace.py -b 32768 -t 10 -a packagename -o outputfile.html sched gfx view wm am app

ここで、各接頭辞の意味を普及させることができます。

  • b: 収集されたデータのサイズ
  • t: 時間
  • a: 監視対象アプリケーションのパッケージ名
  • o: 生成されるファイルの名前

Systrace はオーバーヘッドが低く、軽量なツールであり、CPU 使用率を直感的に反映できます。

2. UIレンダリングの最適化

Android システムは 16 ミリ秒ごとにアクティビティを再描画するため、アプリケーションは画面更新のすべての論理操作を 16 ミリ秒以内に完了する必要があり、各フレームは 16 ミリ秒しか滞在できません。そうしないとフレーム ドロップが発生します。Android アプリケーションがスタックするかどうかは、UI レンダリングに直接関係します。

2.1CPU、GPU

ほとんどの携帯電話の画面更新周波数は 60hz です。つまり、このフレームのタスクが 1000/60=16.67ms 以内に完了しない場合、フレーム損失が発生します。フレーム損失はインターフェイスの遅延の直接の原因であり、通常はレンダリング操作が行われます。 CPU と GPU という 2 つのコア コンポーネントに依存します。CPU は測定、レイアウトなどの計算操作を担当し、GPU はラスター化操作を担当します。

いわゆるラスタライズとは、ベクトル グラフィックスをビットマップに変換するプロセスです。携帯電話上の表示はピクセルごとに表示されます。たとえば、Button、TextView およびその他のコンポーネントは、携帯電話の画面上にピクセルに分割されて表示されます。UI レンダリングの最適化の目的は、CPU と GPU の負荷を軽減し、不必要な操作を削除し、すべての CPU と GPU の計算、描画、レンダリングなどの操作が 1 フレームあたり 16 ミリ秒以内に処理されるようにすることです。これにより、UI はスムーズにスムーズに表示されます。

2.2 オーバードロー

UI レンダリングの最適化の最初のステップは、同じフレーム内で複数回描画される画面上のピクセルを表すオーバードローを見つけることです。オーバーラップ UI レイアウトでは、非表示の UI も描画操作を実行している場合、または後者のコントロールが前のコントロールをブロックしている場合、一部のピクセル領域が複数回描画されるため、CPU と GPU への負荷が増加します。

では、オーバードローがレイアウト内のどこにあるかを確認するにはどうすればよいでしょうか? これは非常に簡単です。携帯電話で開発者向けオプションを開き、スイッチをオンにして GPU オーバードローをデバッグすると、次の図に示すように、アプリケーションのレイアウトがオーバードローされているかどうかを確認できます。

青、薄緑、薄赤、濃い赤は 4 つの異なる度合いのオーバードロー状況を表します。1x、2x、3x、4x はそれぞれ、同じピクセルが同じフレーム内で複数回描画されていることを示し、1x は 1 回 (最後の描画) を示します。理想的には)、4x は 4 倍(最悪の場合)を意味し、排除する必要があるのは 3x と 4x です。

2.3 カスタムビューのオーバードローを解決する

View をカスタマイズするときに、onDraw メソッドがオーバーライドされる場合があることはわかっていますが、Android システムは onDraw で実行される特定の操作を検出できないため、システムはいくつかの最適化を行うことができません。これにより、プログラマに高い要求が課せられます。ビューに大量の重複がある場合、CPU と GPU リソースの無駄が発生します。現時点では、canvas.clipRect() を使用して、システムがそれらの表示領域を識別できるようにすることができます。

このメソッドは長方形の領域を指定することができ、その領域のみが描画され、他の領域は無視されます。以下では、Google が提供する小さなデモを通じて OverDraw の使用法をさらに詳しく説明します。

以下のコードでは、DroidCard クラスがカード情報をカプセル化します。

public class DroidCard {

public int x;//左侧绘制起点
public int width;
public int height;
public Bitmap bitmap;

public DroidCard(Resources res,int resId,int x){
this.bitmap = BitmapFactory.decodeResource(res,resId);
this.x = x;
this.width = this.bitmap.getWidth();
this.height = this.bitmap.getHeight();
 }
}

カスタム ビューのコードは次のとおりです。

public class DroidCardsView extends View {
//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10;

private List<DroidCard> mDroidCards = new ArrayList<DroidCard>();

private Paint paint = new Paint();

public DroidCardsView(Context context) {
super(context);
initCards();
}

public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**
* 初始化卡片集合
*/
protected void initCards(){
Resources res = getResources();
mDroidCards.add(new DroidCard(res,R.drawable.alex,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.claire,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.kathryn,mCardLeft));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (DroidCard c : mDroidCards){
drawDroidCard(canvas, c);
}
invalidate();
}

/**
* 绘制DroidCard
*/
private void drawDroidCard(Canvas canvas, DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
}
}

次に、コードを実行して電話機のオーバードロー スイッチをオンにすると、次のような効果が得られます。

薄い赤色の部分は明らかに3回描画されていることが分かりますが、これは絵が重なっているためです。では、この問題をどうやって解決すればいいのでしょうか?実際、分析の結果、上の図を完全に描画するには、下の図の 3 分の 1 だけを描画する必要があり、下の 2 つの図の 3 分の 1 だけを描画すればよいことがわかります。最適化されたコードは次のとおりです。

public class DroidCardsView extends View {

//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10;

private List<DroidCard> mDroidCards = new ArrayList<DroidCard>();

private Paint paint = new Paint();

public DroidCardsView(Context context) {
super(context);
initCards();
}

public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**
* 初始化卡片集合
*/
protected void initCards(){
Resources res = getResources();
mDroidCards.add(new DroidCard(res, R.drawable.alex,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.claire,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.kathryn,mCardLeft));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mDroidCards.size() - 1; i++){
drawDroidCard(canvas, mDroidCards,i);
}
drawLastDroidCard(canvas,mDroidCards.get(mDroidCards.size()-1));
invalidate();
}

/**
* 绘制最后一个DroidCard
* @param canvas
* @param c
*/
private void drawLastDroidCard(Canvas canvas,DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
}

/**
* 绘制DroidCard
* @param canvas
* @param mDroidCards
* @param i
*/
private void drawDroidCard(Canvas canvas,List<DroidCard> mDroidCards,int i) {
DroidCard c = mDroidCards.get(i);
canvas.save();
canvas.clipRect((float)c.x,0f,(float)(mDroidCards.get(i+1).x),(float)c.height);
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
canvas.restore();
 }
}

上記のコードでは、CanvasのclipRectメソッドを使用して描画前に領域を切り取っており、描画する際にはその領域内のみを描画し、余分な部分は描画されません。上記のコードを再実行すると、結果は次のようになります。

2.4 階層ビューア

Hierarchy Viewer は、Android Device Monitor に組み込まれたツールで、開発者がレイアウト階層内の各ビューのレイアウト速度を測定できるようにし、ビュー階層によって引き起こされるパフォーマンスのボトルネックを見つけるのに役立ちます。Hierarchy Viewer は、レイアウトの Measure、Layout、および Executive の相対的なパフォーマンスを、赤、黄、緑の 3 つの異なる色で区別できます。

開ける

  1. デバイスをコンピュータに接続します。USB デバッグを許可するように求めるダイアログ ボックスがデバイスに表示された場合は? をクリックし、「OK」をクリックします。
  2. Android Studio でプロジェクトを開き、デバイス上でプロジェクトをビルドして実行します。
  3. Androidデバイスモニターを起動します。adb を介してデバイスに接続できるのは一度に 1 つのプロセスのみであり、Android Device Monitor が接続を要求しているため、Android Studio では [adb 統合を無効にする] ダイアログ ボックスが表示される場合があります。したがって、「はい」をクリックしてください。
  4. メニュー バーで、[ウィンドウ] > [パースペクティブを開く] を選択し、[階層ビュー] をクリックします。
  5. 左側の [Windows] タブでアプリケーションのパッケージ名をダブルクリックします。これにより、関連するペインにアプリのビュー階層が設定されます。

レイアウトのパフォーマンスを向上させる鍵は、レイアウト階層を可能な限りフラットに保ち、ネストされたレイアウトの繰り返しを避けることです。作成するレイアウト レベルが比較的深い場合、CPU への負荷が大幅に増加し、重大なパフォーマンス ラグが発生します。階層ビューアの使用方法については、「階層ビューアを使用してレイアウトを分析する」を参照してください

2.5 メモリジッター

ツリー構造とビューのオーバードローを最適化した後でも、アプリにフリーズ、フレームのドロップ、またはスライドの遅さなどの問題があると感じる場合があるため、メモリ ジッターがあるかどうかを確認する必要があります。いわゆるメモリ ジッターとは、頻繁なメモリ作成と GC により UI スレッドが頻繁にブロックされる現象を指します。

Android にはメモリを自動的に管理するメカニズムがありますが、メモリを不適切に使用すると、深刻なパフォーマンスの問題が発生しやすくなります。同じフレーム内であまりにも多くのオブジェクトを作成することには特別な注意が必要です。同じフレーム内で多数のオブジェクトを作成すると、継続的な GC 操作が発生する可能性があります。GC 操作を実行するときは、GC が実行されるまですべてのスレッドの操作を一時停止する必要があります。完了です。多数のノンストップ GC 操作がフレーム間隔時間を大幅に占有します。フレーム間隔中に実行される GC 操作が多すぎると、ページがフリーズします。

Android 開発では、GC 操作が頻繁に発生する主な理由が 2 つあります。

  • メモリ ジッター: いわゆるメモリ ジッターとは、多数のオブジェクトが短期間に生成され、その後短期間にすぐに解放されることを意味します。
  • 短期間に大量のオブジェクトが生成されてしきい値を超え、メモリが不足している場合にも、GC 操作がトリガーされます。

Android のメモリ ジッターは、Android Studio のプロファイラーを使用して検出できます。

次に、「記録」をクリックしてメモリ情報を記録し、メモリジッターが発生している場所を見つけます。もちろん、「ソースへジャンプ」を使用してコードの場所を直接見つけることもできます。

メモリジッターを避けるためには、for ループ内でオブジェクトを割り当ててメモリを占有することを避ける必要があります。オブジェクトの作成をループ本体の外側に移動するようにする必要があります。カスタム View の onDraw メソッドにも注意が必要です。実行プロセス中に、onDraw メソッドで複雑な操作が実行されたり、オブジェクトが作成されたりすることを避けるために、onDraw メソッドが呼び出されます。オブジェクトの作成が避けられない場合には、オブジェクトプールによる頻繁な作成と破棄の問題を解決するオブジェクトプールモデルが考えられますが、ここで注意しなければならないのは、オブジェクト内のオブジェクトは使用完了後は消えてしまうということです。プールは手動で解放する必要があります。

3. メモリの最適化

3.1 メモリ管理

前回の Java の基礎セクションでは、Java のメモリ管理モデルについても基本的に説明しました 参考リンク: Android の高頻度面接で必ず聞かれる Java の基礎

3.1.1 メモリ領域

Java のメモリ モデルでは、次の図に示すように、メモリ領域はメソッド領域、ヒープ、プログラム カウンタ、ローカル メソッド スタック、仮想マシン スタックの 5 つの領域に分割されます。

メソッド領域

  • スレッド共有領域は、クラス情報、静的変数、定数、ジャストインタイムコンパイラによってコンパイルされたコードデータを格納するために使用されます。
  • OOM は、メモリ割り当て要件を満たせない場合に発生します。

ヒープ

  • スレッド共有領域は、JAVA 仮想マシンによって管理される最大のメモリ部分であり、仮想マシンの起動時に作成されます。
  • オブジェクト インスタンスの保存: ほとんどすべてのオブジェクト インスタンスは、GC によって管理されるメイン領域であるヒープ上に割り当てられます。

仮想マシンスタック

  • スレッドプライベート領域では、各 Java メソッドはスタック フレームを作成し、ローカル変数テーブル、オペランド スタック、ダイナミック リンク、メソッド出口などの情報を格納します。メソッド実行の開始から終了までのプロセスは、スタック フレームを仮想マシン スタックにプッシュおよびポップするプロセスです。
  • ローカル変数テーブルには、コンパイル時に既知の基本データ型、オブジェクト参照、および returnAddress 型が格納されます。必要なメモリ空間はコンパイル時に割り当てられ、メソッドの入力時にフレーム内のローカル変数テーブルの空間が完全に決定されるため、実行時に変更する必要はありません。
  • スレッドによって要求されたスタックの深さが、仮想マシンによって許可される最大の深さよりも大きい場合、SatckOverFlowError エラーがスローされます。
  • 仮想マシンが動的に拡張されるときに、十分なメモリを適用できない場合は、OutOfMemoryError がスローされます。

ネイティブメソッドスタック

  • 仮想マシン内でネイティブ メソッドを提供するため、ローカル メソッド スタックで使用される言語、データ構造、および使用方法に関する強制的な規制はなく、仮想マシン自体で実装できます。
  • 占有メモリ領域のサイズは固定されず、必要に応じて動的に拡張できます。

プログラムカウンター

  • 現在のスレッドによって実行されるバイトコードの行番号インジケーターを格納する、スレッド専用の小さなメモリ空間。
  • バイトコード インタプリタは、このカウンタの値を変更して、次に実行するバイトコード命令 (分岐、ループ、ジャンプなど) を選択します。
  • 各スレッドには独立したプログラムカウンターがあります
  • Java 仮想マシン内で OOM が適用されない唯一の領域

3.1.2 ガベージコレクション

マークとクリアのアルゴリズムマークとクリアのアルゴリズムは主に 2 つの段階に分かれており、最初にリサイクルする必要があるオブジェクトにマークが付けられ、次にマーキングが完了した後、すべてのマークされたオブジェクトが均一にリサイクルされます。

  • 効率の問題: マーキングとクリアの両方のプロセスが非効率的です。
  • スペースの問題: マークとクリアにより、大量の不連続なメモリ フラグメントが発生し、大きなオブジェクトを割り当てる必要がある場合に、十分な連続スペースを見つけることができず、GC をトリガーする必要があるという問題が発生します。

コピー アルゴリズムは、使用可能なメモリをスペースに応じて同じサイズの 2 つの小さなブロックに分割し、一度に 1 つのみを使用します。このメモリ ブロックが使い果たされると、残ったオブジェクトが別のメモリ ブロックにコピーされます。その場合、メモリ領域オブジェクト全体が消去されます。毎回半分の領域全体のメモリをリサイクルしても断片化の問題は発生せず、実装は簡単かつ効率的です。短所: メモリを元のサイズの半分に減らす必要があり、スペース コストが高すぎます。

マーキングおよび照合アルゴリズム マーキングおよび照合アルゴリズム マーキング プロセスはマークおよびクリア アルゴリズムと同じですが、クリア プロセスはリサイクル可能なオブジェクトを直接クリーンアップするのではなく、生き残ったすべてのオブジェクトを一方の端に移動してから、端の境界の外側のメモリをクリーンアップします。集中的な方法で。

世代別コレクション アルゴリズム現代の仮想マシンのガベージ コレクション アルゴリズムはすべて、世代別コレクション アルゴリズムを使用して収集します。メモリは、オブジェクトの異なるライフ サイクルに従って新世代と旧世代に分割され、その後、最適なリサイクル アルゴリズムが使用されます。各世代の特徴。

  • 新しい世代では生き残るオブジェクトが少なくなり、各ガベージ コレクション中に多数のオブジェクトが消滅します。一般に、コピー アルゴリズムが使用され、少数の生き残ったオブジェクトをコピーするコストを支払うだけでガベージ コレクションを実現できます。
  • 旧世代には多くのオブジェクトが残っており、割り当てを保証するための追加スペースがないため、リサイクルにはマーク クリア アルゴリズムとマーク ソート アルゴリズムを使用する必要があります。

3.2 メモリリーク

いわゆるメモリ リークとは、メモリ内で役に立たず、再利用できないオブジェクトを指します。この現象は、メモリ ジッターと使用可能なメモリの減少を引き起こし、頻繁な GC、フリーズ、OOM につながります。

以下はメモリ リークをシミュレートするコードです。

/**
 * 模拟内存泄露的Activity
 */
public class MemoryLeakActivity extends AppCompatActivity implements CallBack{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memoryleak);
        ImageView imageView = findViewById(R.id.iv_memoryleak);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.splash);
        imageView.setImageBitmap(bitmap);

        // 添加静态类引用
        CallBackManager.addCallBack(this);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
//        CallBackManager.removeCallBack(this);
    }
    @Override
    public void dpOperate() {
        // do sth
    }

メモリ プロファイラ ツールを使用してメモリ曲線を表示すると、次の図に示すように、メモリが常に増加していることがわかります。

メモリ リークの特定の場所を分析して特定したい場合は、MAT ツールを使用できます。まず、MAT ツールを使用して hprof ファイルを生成し、[ダンプ] をクリックして現在のメモリ情報を hprof ファイルに変換し、生成されたファイルを MAT 読み取り可能なファイルに変換します。変換コマンドを実行すると変換が完了し、生成されたファイルは Android/sdk/platorm-tools パスに配置されます。

hprof-conv 刚刚生成的hprof文件 memory-mat.hprof

以下の図に示すように、mat を使用して変換したばかりの hprof ファイルを開き、次に Android Studio を使用して hprof ファイルを開きます。

次に、パネル上の [Historygram] をクリックし、MemoryLeakActivity を検索して、対応するリークされたファイルに関する関連情報を表示します。

次に、以下に示すように、すべての参照オブジェクトを表示し、関連する参照チェーンを取得します。

GC Roots が CallBackManager であることがわかります。

したがって、Activity が破棄されたときに CallBackManager 参照を削除できます。

@Override
protected void onDestroy() {
    super.onDestroy();
    CallBackManager.removeCallBack(this);
}

もちろん、上記は MAT 解析ツールの使用例にすぎず、他のメモリ リークも MAT 解析ツールを使用して解決できます。

3.3 大容量画像メモリの最適化

Android の開発では、大きな画像の読み込みによるメモリ リークの問題がよく発生しますが、このシナリオでは、ARTHook を使用して不当な画像を検出するという一般的な解決策があります。ビットマップが占有するメモリを取得するには、主に 2 つの方法があることがわかっています。

  • getByteCount メソッドを使用しますが、実行時に取得する必要があります
  • *高さ*1 ピクセルが占有するメモリ 画像が配置されている*リソース ディレクトリの圧縮率

ARTHook 方式は、不正な画像をエレガントに取得でき、煩わしさが少ないですが、互換性の問題により、通常はオフラインで使用されます。ARTHook を使用するには、次の依存関係をインストールする必要があります。

implementation 'me.weishu:epic:0.3.6'

次に、以下に示すようにフック メソッドをカスタマイズします。

public class CheckBitmapHook extends XC_MethodHook {
    @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        ImageView imageView = (ImageView)param.thisObject;
        checkBitmap(imageView,imageView.getDrawable());
    }
    private static void checkBitmap(Object o,Drawable drawable) {
        if(drawable instanceof BitmapDrawable && o instanceof View) {
            final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            if(bitmap != null) {
                final View view = (View)o;
                int width = view.getWidth();
                int height = view.getHeight();
                if(width > 0 && height > 0) {
                    if(bitmap.getWidth() > (width <<1) && bitmap.getHeight() > (height << 1)) {
                        warn(bitmap.getWidth(),bitmap.getHeight(),width,height,
                                new RuntimeException("Bitmap size is too large"));
                    }
                } else {
                    final Throwable stacktrace = new RuntimeException();
                    view.getViewTreeObserver().addOnPreDrawListener(
                            new ViewTreeObserver.OnPreDrawListener() {
                                @Override public boolean onPreDraw() {
                                    int w = view.getWidth();
                                    int h = view.getHeight();
                                    if(w > 0 && h > 0) {
                                        if (bitmap.getWidth() >= (w << 1)
                                                && bitmap.getHeight() >= (h << 1)) {
                                            warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stacktrace);
                                        }
                                        view.getViewTreeObserver().removeOnPreDrawListener(this);
                                    }
                                    return true;
                                }
                            });
                }
            }
        }
    }
    private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
        String warnInfo = new StringBuilder("Bitmap size too large: ")
                .append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')')
                .append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')')
                .append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
                .toString();
        LogUtils.i(warnInfo);

最後に、アプリケーションの初期化時にフックを挿入します。

DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
    @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        DexposedBridge.findAndHookMethod(ImageView.class,"setImageBitmap", Bitmap.class,
                new CheckBitmapHook());
    }
});

3.4 オンライン監視

3.4.1 従来方式

オプション 1:特定のシナリオで現在占有されているメモリ サイズを取得します。現在のメモリ サイズがシステムの最大メモリの 80% を超えている場合は、現在のメモリに対してダンプ (Debug.dumpHprofData()) を実行し、適切な時間を選択してファイルをアップロードします。 hprof ファイルを開き、MAT ツールを使用してファイルを手動で分析します。

欠点:

  • ダンプ ファイルは比較的大きく、ユーザーの使用時間とオブジェクト ツリーに直接関係します。
  • ファイルが大きくなると、アップロードの失敗率が高くなり、分析が困難になります。

オプション 2 : LeakCannary をオンラインにし、事前設定された疑わしいポイントを追加し、疑わしいポイントに対してメモリ リークの監視を実行し、メモリ リークが見つかった場合はサーバーにレポートします。

欠点:

  • 汎用性が低く、事前に不審点を設定する必要があり、不審点が設定されていない場所は監視できません。
  • LeakCanary 分析は時間とメモリを消費し、OOM が発生する可能性があります。
3.4.2 LeakCannary 変換

変換には主に次の点が含まれます。

  • 疑わしいポイントを事前に設定する必要を変更して、疑わしいポイントを自動的に検索し、大量のメモリを占有するオブジェクト クラスに疑わしいポイントを自動的に設定します。
  • LeakCanary は、リークされたリンクの分析が遅く、保持サイズの大きいオブジェクトのみを分析するように変更されています。
  • LeakCannary は分析中にすべての分析オブジェクトをメモリにロードするため、分析プロセスは OOM になります。分析オブジェクトの数と占有サイズを記録し、分析オブジェクトを切り出し、すべてをメモリにロードしないようにすることができます。

完了した変換手順は次のとおりです。

  1. 一般的な指標を監視します: スタンバイ メモリ、主要モジュールによって占有されているメモリ、OOM 率
  2. APP のライフ サイクルおよび主要なモジュール インターフェイスのライフ サイクル内の GC の数、GC 時間などを監視します。
  3. カスタマイズされた LeakCanary をオンラインにして、オンライン メモリ リークを自動的に分析する

4. ネットワークの最適化

4.1 ネットワーク最適化の影響

アプリのネットワーク接続はユーザーに多大な影響を与え、ほとんどの場合、非常に直感的であり、ユーザーのアプリ使用体験に直接影響します。最も重要な点は次のとおりです: トラフィック: アプリのトラフィック消費量は次のとおりです。ユーザーに対して比較的敏感です。はい、結局のところ、トラフィックにはお金がかかります。現在、ほとんどの人は、アプリのトラフィック使用状況を監視するために、携帯電話にトラフィック監視ツール アプリをインストールしています。アプリがこの側面を適切に制御しない場合、アプリはユーザーに問題を引き起こす可能性があるため、経験を活用してください。電力: 電力はユーザーにとってそれほど明白ではありません。平均的なユーザーはあまり気にしないかもしれませんが、電力の最適化で述べたように、ネットワーク接続 (無線) は電力に大きな影響を与える要素です。それに注意してください。ユーザー待機: つまり、ユーザー エクスペリエンスです。優れたユーザー エクスペリエンスは、ユーザーを維持するための最初のステップです。アプリのリクエストの待ち時間が長いと、ユーザーはネットワークの遅延やアプリケーションの応答の遅さを感じます。比較すれば、代替手段はあります。私たちのアプリはユーザーによって容赦なく放棄される可能性があります。

4.2 ネットワーク分析ツール

ネットワーク分析に使用できるツールには、モニター、プロキシ ツールなどが含まれます。

4.2.1 ネットワークモニター

Android Studio の組み込みモニター ツールは、開発者がネットワーク分析を実行するのに役立つネットワーク モニターを提供します。次は、一般的なネットワーク モニターの図です。

  • Rx — R(ecive) はダウンストリーム トラフィック、つまりダウンロード受信を表します。
  • Tx — T(ransmit) はアップストリーム トラフィック、つまりアップロードと送信を表します。

ネットワーク モニターは、選択したアプリケーションのデータ要求をリアルタイムで追跡します。携帯電話に接続し、デバッグ アプリケーション プロセスを選択し、分析する必要があるページ リクエストをアプリ上で操作できます。

4.2.2 エージェントツール

ネットワーク プロキシ ツールには 2 つの機能があります。1 つはネットワーク リクエストの応答パケットをインターセプトし、ネットワーク リクエストを分析することです。もう 1 つはプロキシ ネットワークを設定することです。モバイル アプリ開発では、通常、Wifi/Wifi などのさまざまなネットワーク環境をテストするために使用されます。 4G/3G/弱いネットワークなど。

現在では、Wireshark、Fiddler、Charles など、多くのプロキシ ツールが利用可能です。

4.3 ネットワーク最適化計画

ネットワークの最適化では、主に次の 2 つの側面から最適化が実行されます。

  1. アクティブ時間を減らす: ネットワーク データ取得の頻度を減らすことで、無線電力消費を削減し、電力使用量を制御します。
  2. 圧縮されたデータ パケット サイズ: 圧縮されたデータ パケットにより、トラフィック消費が削減され、各リクエストが高速化されます。

上記の解決策に基づいて、次の一般的な解決策が得られます。

4.3.1 インターフェース設計

1. API設計アプリとサーバー間のAPI設計は、ネットワークリクエストの頻度やリソースの状態などを考慮する必要があります。これにより、アプリはより少ないリクエストでビジネス要件とインターフェイス表示を完了できます。

たとえば、登録とログインです。通常、登録とログインの 2 つの API がありますが、API を設計するときは、登録インターフェイスに暗黙的なログインを含める必要があります。これにより、アプリが登録後にログイン インターフェイスを要求する必要がなくなります。

2. Gzip 圧縮を使用する

Gzip を使用してリクエストとレスポンスを圧縮し、送信されるデータ量を削減し、トラフィック消費を削減します。Retrofit などのネットワーク リクエスト フレームワークを使用してネットワーク リクエストを行う場合、デフォルトで Gzip 圧縮が実行されます。

3.プロトコル バッファーを使用する前は、XML を使用してデータを送信していましたが、その後、主に読みやすさとデータ量の削減を目的として、XML の代わりに JSON を使用しました。ゲーム開発において、データの正確性と適時性を確保するために、Google はプロトコル バッファー データ交換フォーマットを開始しました。

4. ネットワーク状況に応じてさまざまな解像度の画像を取得するタオバオや JD.com を使用すると、トラフィックの無駄を避け、ユーザー エクスペリエンスを向上させるために、アプリケーションがネットワーク状況に応じてさまざまな解像度の画像を取得することがわかります。

4.3.2 ネットワークキャッシュの適切な使用

キャッシュを適切に使用すると、アプリケーションが高速になるだけでなく、不必要なトラフィックの消費が回避され、より良いユーザー エクスペリエンスがもたらされます。

1. ネットワークリクエストのパッケージ化

インターフェースのデザインがビジネスニーズを満たせない場合。たとえば、1 つのインターフェイスが複数のインターフェイスをリクエストする必要がある場合や、ネットワークが良好で Wifi 状態のときにより多くのデータを取得したい場合などが考えられます。現時点では、リストの要求や、ヘッダーのクリック率が高いアイテムの詳細データの取得など、いくつかのネットワーク リクエストをパッケージ化できます。

2. デバイスのステータスを監視するユーザー エクスペリエンスを向上させるために、デバイスの使用状況を監視し、それを JobScheduler と組み合わせてネットワーク リクエストを実行できます。たとえば、Splash の広告画像については、Wifi に接続しているときにローカルにダウンロードしてキャッシュできます。ニュース アプリは、充電中や Wifi 上でオフラインでキャッシュできます。

4.3.3 弱いネットワークのテストと最適化

1. 弱いネットワークのテスト テストのために弱いネットワークをシミュレートするには、いくつかの方法があります。

Android エミュレータ通常、次の図に示すように、Android エミュレータを作成して起動し、ネットワーク速度と遅延を設定します。

そこで、起動時に使用するエミュレータコマンドは以下の通りです。

$emulator -netdelay gprs -netspeed gsm -avd Nexus_5_API_22

2. ネットワーク プロキシ ツールネットワーク プロキシ ツールを使用して、ネットワーク状態をシミュレートすることもできます。Charles を例に挙げると、携帯電話と PC を同じ LAN 内に保ち、モバイル Wi-Fi 設定の詳細設定でプロキシ モードを手動に設定し、プロキシ IP に PC の IP アドレスを入力します。デフォルトのポート番号は次のとおりです。 8888。

5. 消費電力の最適化

実際、アプリケーションがビデオの再生、GPS 情報の取得、またはゲーム アプリケーションの場合、電力消費は深刻になります。どの消費電力を回避できるか、または最適化する必要があるかをどのように判断すればよいでしょうか? 携帯電話に付属の消費電力ランキング表を開くと、「Honor of Kings」が7時間以上使用されていることがわかり、この時点でユーザーは「Honor of Kings」の消費電力に期待を抱いています。

5.1 最適化の方向性

このとき、あまり使っていないのに電力を大量に消費するアプリケーションを見つけたとします。それはシステムによって容赦なく強制終了されてしまいます。したがって、消費電力最適化の最初の方向は、アプリケーションのバックグラウンド消費電力を最適化することです。

システムが消費電力を計算する方法を知ることで、WakeLock の長期取得、WiFi、Bluetooth スキャンなど、バックグラウンド サービスだけでなく、アプリケーションがバックグラウンドで実行すべきでないことも知ることができます。消費電力最適化の第一の方向は、アプリケーションのバックグラウンド消費電力を最適化することであると言われているのはなぜですか? ほとんどのメーカーのプリインストール プロジェクトで最も厳しい要件が、アプリケーションのバックグラウンド待機電力消費であるためです。

もちろんフロントの消費電力を完全に無視するわけではありませんが、基準は大幅に緩和されます。下の図を見てみましょう。アプリケーションに対してシステムがこのダイアログ ボックスを表示した場合、ユーザーは WeChat については許容できるかもしれませんが、他のほとんどのアプリケーションでは、多くのユーザーがあなたを制限リストの背景に直接追加する可能性があります。

消費電力最適化の 2 番目の方向は、システムのルールに従い、消費電力が正常であるとシステムに認識させることです。

Android P 以降のバージョンでは、Android Vitals を通じてバックグラウンド消費電力を監視するため、Android Vitals の規則に従う必要がありますが、現時点での具体的な規則は次のとおりです。

Android システムは現在、バックグラウンドでのアラーム ウェイクアップ、バックグラウンド ネットワーク、バックグラウンド WiFi スキャン、およびシステムがバックグラウンドでスリープするのを防ぐための長期的な WakeLock をより重視していることがわかります。これらは電力消費の問題を引き起こす可能性があるためです。

5.2 消費電力の監視

5.2.1 Android の重要性

Android Vitals には、電力消費の監視に役立つ電力監視ソリューションとルールがいくつかあります。

しばらく使ってみると、あまり使いにくいことが分かりました。アラームウェイクアップを例に挙げると、Vitals では原則として 1 時間あたり 10 回以上使用します。このルールは変更できないため、さまざまなシステム バージョンをより詳細に区別したい場合がよくあります。次に、Battery Historian と同様に、ウェイクアップによってマークされたコンポーネントのみを取得できますが、適用されたスタックを取得することはできません。また、その時点で電話機が充電されているかどうかやバッテリー残量などの情報も取得できません。下の写真はwakeupで得られる情報です。

ネットワーク、WiFi スキャン、および WakeLock についても同様です。Vitals のおかげで調査範囲を絞り込むことができましたが、問題の具体的な原因はまだ確認できませんでした。

5.3 消費電力の監視方法

前述したようにAndroid Vitalsはそれほど使いやすいものではなく、国内アプリでは実は全く使えません。では、消費電力監視システムは何を監視し、どのように実行すればよいのでしょうか? まず、消費電力を具体的に監視する方法を見てみましょう。

  • 監視情報: 簡単に言うと、システムが関心を持っているものを監視します。主な焦点はバックグラウンドの消費電力の監視です。アラームウェイクアップ、WakeLock、WiFiスキャン、ネットワークなどはすべて必要ですが、その他はアプリケーションの実際の状況に基づいて行うことができます。地図アプリであればバックグラウンドでGPSを取得しても問題ありませんし、歩数計アプリであればバックグラウンドでSensorを取得しても大きな問題はありません。
  • オンサイト情報: 監視システムは、どのコード行が WiFi スキャンを開始したか、どのコード行が WakeLock に適用したかなど、完全なスタック情報を取得することを望んでいます。また、その時点で電話機が充電されているかどうか、電話機の電力レベル、アプリケーションのフォアグラウンド時間とバックグラウンド時間、CPU ステータスなどの情報もあり、特定の問題のトラブルシューティングにも役立ちます。
  • ルールの調整: 最後に、監視対象のコンテンツをルールに抽象化する必要がありますが、当然のことながら、アプリケーションごとに監視される項目やパラメータは異なります。それぞれのアプリケーションの仕様が異なるため、参考として使用できる簡単なルールがあります。

5.3.2 フックソリューション

何を監視する必要があるのか​​、具体的なルールを明確にした後、電力監視の技術的ソリューションを見てみましょう。まずはフックのソリューションを見てみましょう。フック ソリューションの利点は、ユーザー アクセスが非常に簡単で、コードを変更する必要がなく、アクセスのコストが比較的低いことです。以下では、Java フックを使用して監視の目的を達成する方法を説明するために、一般的に使用されるいくつかのルールを例として取り上げます。

1. WakeLock WakeLock は、CPU、画面、さらにはキーボードのスリープを防ぐために使用されます。Alarm や JobService と同様に、バックグラウンドの CPU 操作を完了するために WakeLock も適用されます。WakeLock の中核となる制御コードは PowerManagerService にあり、実装方法は以下に示すように非常に簡単です。

// 代理 PowerManagerService
ProxyHook().proxyHook(context.getSystemService(Context.POWER_SERVICE), "mService", this);

@Override
public void beforeInvoke(Method method, Object[] args) {
    // 申请 Wakelock
    if (method.getName().equals("acquireWakeLock")) {
        if (isAppBackground()) {
            // 应用后台逻辑,获取应用堆栈等等     
         } else {
            // 应用前台逻辑,获取应用堆栈等等
         }
    // 释放 Wakelock
    } else if (method.getName().equals("releaseWakeLock")) {
       // 释放的逻辑    
    }
}

2. アラームアラームは、スケジュールされた反復タスクを実行するために使用され、合計 4 つのタイプがあり、そのうち ELAPSED_REALTIME_WAKEUP および RTC_WAKEUP タイプはデバイスをウェイクアップします。同様に、アラームのコア制御ロジックは AlarmManagerService にあり、次のように実装されます。

// 代理 AlarmManagerService
new ProxyHook().proxyHook(context.getSystemService
(Context.ALARM_SERVICE), "mService", this);

public void beforeInvoke(Method method, Object[] args) {
    // 设置 Alarm
    if (method.getName().equals("set")) {
        // 不同版本参数类型的适配,获取应用堆栈等等
    // 清除 Alarm
    } else if (method.getName().equals("remove")) {
        // 清除的逻辑
    }
}

WakeLock と Alarm に加えて、バックグラウンド CPU の場合はラグ監視に関連するメソッドを使用できます。バックグラウンド ネットワークの場合はネットワーク監視関連のメソッドも使用できます。GPS モニタリングの場合は LOCATION_SERVICE をプロキシするためのフックを使用できます。センサーの場合は、フックで「mSensorListeners」を使用すると、SENSOR_SERVICE で情報を取得できます。

最後に、リソースを申請するためのスタック情報を保存します。問題を報告するルールをトリガーすると、収集したスタック情報、バッテリーが充電されているかどうか、CPU 情報、アプリケーションのフロント時間とバック時間、その他の補助情報をバックグラウンドにアップロードできます。

5.3.3 計測方法

フックの使用は簡単ですが、特定のルールに適したフック ポイントを見つけるのは簡単ではない場合があり、Android P 以降、多くのフック ポイントはサポートされなくなりました。互換性の理由から、私が最初に考えたのはインストルメンテーションでした。WakeLock を例に挙げます。

public class WakelockMetrics {
    // Wakelock 申请
    public void acquire(PowerManager.WakeLock wakelock) {
        wakeLock.acquire();
        // 在这里增加 Wakelock 申请监控逻辑
    }
    // Wakelock 释放
    public void release(PowerManager.WakeLock wakelock, int flags) {
        wakelock.release();
        // 在这里增加 Wakelock 释放监控逻辑
    }
}

電力消費について調査したことがある場合は、Facebook の電力消費監視用のオープンソース ライブラリである Battery-Metrics を知っているはずです。このライブラリは、アラーム、ウェイクロック、カメラ、CPU、ネットワークなどを含む非常に包括的なデータを監視し、電力も収集します。充電ステータス、電力レベル情報。残念ながら、Battery-Metrics は一連の基本クラスしか提供していないため、実際に使用するには開発者が依然として大量のソース コードを変更する必要があります。

6. インストールパッケージの最適化

現在、市場に出ているアプリのサイズは数十メガバイトから数百メガバイトまでさまざまです。インストール パッケージが小さいほど、ダウンロード中のデータ トラフィックが少なくなり、ユーザー エクスペリエンスが向上し、ダウンロードとインストールが速くなります。では、インストール パッケージの最適化にはどのような点から始めればよいでしょうか?

6.1 一般的に使用される最適化戦略

1. 無駄なリソースをクリーンアップします。Androidのパッケージ化プロセス中に、コードにリソースやコードへの参照が含まれている場合、そのコードはアプリにパッケージ化されます。これらの放棄されたコードやリソースがアプリにパッケージ化されるのを防ぐには、次のことを行う必要があります。これらの無駄なリソースのコードとリソースをタイムリーにクリーンアップして、アプリのサイズを削減します。クリーンアップの方法は、下図のようにAndroid Studioの[リファクタリング]→[未使用リソースの削除]の順にクリックします。

2. lint ツールを使用する

Lint ツールは依然として非常に便利で、最適化する必要があるポイントを提供します。

  • 使用されていないレイアウトを検出して削除します
  • 使用されていないリソースを削除する
  • String.xml 内の未使用の文字も削除することをお勧めします。

3. 無駄なリソースを削除するには、shrinkResources をオンにします。build.gradle で shrinResources true を設定します。無駄なリソースはパッケージ化時に自動的に削除されます。ただし、実験の結果、パッケージは削除されず、一部の無駄なリソースに対してより小さなリソースが使用されることが判明しました。 . 物を交換する。ここでの「役に立たない」とは、イメージを呼び出すすべての親関数が最終的にコードを破棄することを意味し、shrinkResources true は親関数呼び出しがない状況を取り除くことしかできないことに注意してください。

    android {
        buildTypes {
            release {
                shrinkResources true
            }
        }
    }

さらに、ほとんどのアプリケーションは実際には数十の言語に対する国際サポートを必要とせず、言語サポート ファイルも削除できます。

6.2 リソースの圧縮

Android 開発では、組み込みのピクチャが多数あり、それらのピクチャが多くの容量を占めるため、パッケージのサイズを削減するためにリソースを圧縮することができます。一般的に使用される方法は次のとおりです。

  1. 圧縮画像を使用する: 圧縮画像を使用すると、アプリのサイズを効果的に削減できます。
  2. 1 セットの画像のみを使用する: ほとんどのアプリでは、1 セットのデザイン図面だけで十分です。
  3. アルファ値のない jpg 画像を使用する: 大きな不透明な画像の場合、jpg の方が png よりもサイズが大きく有利です。絶対的ではありませんが、通常は半分以下に縮小されます。
  4. tinypng 非可逆圧縮を使用: PNG 画像を公式 Web サイトにアップロードして圧縮し、ダウンロードして保存することができます。PNG の圧縮は、アルファ チャネルを維持しながら 1/3 以内に抑えることができ、圧縮損失は基本的に肉眼では見えません。
  5. webp 形式を使用する: webp は透明度、圧縮率をサポートし、JPG 画像よりも小さいサイズを占めます。Android 4.0 以降からネイティブでサポートされていますが、透明度には対応していません。透明度のある webp の表示がサポートされるのは Android 4.2.1 以降です。使用する場合は特に注意してください。
  6. svg を使用する: ベクター グラフィックスは点と線で構成されます。ビットマップとは異なり、拡大しても鮮明さを維持できます。さらに、ベクター グラフィックスを使用すると、ビットマップ デザインと比較して 30 ~ 40% のスペースを節約できます。
  7. パッケージ化された画像を圧縮する: 7zip 圧縮方式を使用して画像を圧縮します。WeChat のオープンソース AndResGuard 圧縮スキームを直接使用できます。
    apply plugin: 'AndResGuard'
    buildscript {
        dependencies {
            classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.1.7'
        }
    }
    andResGuard {
        mappingFile = null
        use7zip = true
        useSign = true
        keepRoot = false
        // add <your_application_id>.R.drawable.icon into whitelist.
        // because the launcher will get thgge icon with his name
        def packageName = <your_application_id>
                whiteList = [
        //for your icon
        packageName + ".R.drawable.icon",
                //for fabric
                packageName + ".R.string.com.crashlytics.*",
                //for umeng update
                packageName + ".R.string.umeng*",
                packageName + ".R.string.UM*",
                packageName + ".R.string.tb_*",
                packageName + ".R.layout.umeng*",
                packageName + ".R.layout.tb_*",
                packageName + ".R.drawable.umeng*",
                packageName + ".R.drawable.tb_*",
                packageName + ".R.anim.umeng*",
                packageName + ".R.color.umeng*",
                packageName + ".R.color.tb_*",
                packageName + ".R.style.*UM*",
                packageName + ".R.style.umeng*",
                packageName + ".R.id.umeng*"
        ]
        compressFilePattern = [
        "*.png",
                "*.jpg",
                "*.jpeg",
                "*.gif",
                "resources.arsc"
        ]
        sevenzip {
            artifact = 'com.tencent.mm:SevenZip:1.1.7'
            //path = "/usr/local/bin/7za"
        }
    }

6.3 リソースの動的ロード

フロントエンド開発では、リソースを動的に読み込むことで、APK のサイズを効果的に削減できます。さらに、arm などの主流のアーキテクチャのみのサポートを提供しており、APK のサイズを大幅に削減できる mips および x86 アーキテクチャのサポートを検討しない可能性があります。

もちろん、上記の最適化シナリオに加えて、Android アプリの最適化には、ストレージの最適化、マルチスレッドの最適化、クラッシュ処理も含まれます。

誰もがパフォーマンスの最適化をより包括的かつ明確に理解できるように、関連するコア ノート (基礎となるロジックも含む) を用意しました。https://qr18.cn/FVlo89

パフォーマンスの最適化に関する重要な注意事項:https://qr18.cn/FVlo89

起動の最適化

、メモリの最適化、

UIの最適化、

ネットワークの最適化、

ビットマップの最適化、画像圧縮の最適化マルチスレッド同時実行の最適化、データ伝送効率の最適化、ボリュームパッケージの最適化https://qr18.cn/FVlo89




「Android パフォーマンス監視フレームワーク」:https://qr18.cn/FVlo89

「Androidフレームワーク学習マニュアル」:https://qr18.cn/AQpN4J

  1. ブート初期化プロセス
  2. 起動時に Zygote プロセスを開始する
  3. 起動時に SystemServer プロセスを開始する
  4. バインダードライバー
  5. AMSの起動プロセス
  6. PMSの起動プロセス
  7. ランチャーの起動プロセス
  8. Android の 4 つの主要コンポーネント
  9. Androidシステムサービス - 入力イベント配信処理
  10. Android の基盤となるレンダリング - 画面更新メカニズムのソース コード分析
  11. Android のソースコード解析の実践

おすすめ

転載: blog.csdn.net/maniuT/article/details/132700957