Androidのメインスレッドと子スレッドの関係の詳細な説明

Androidのメインスレッドと子スレッドの関係の詳細な説明

メインスレッドと画面のレンダリング

ユーザーがアプリケーションを起動すると、Androidは新しいLinuxプロセスと実行スレッドを作成します。このメインスレッドは、インターフェイススレッド(UIスレッド)とも呼ばれ、画面上で発生するすべてのアクティビティを担当します。

Androidでは、メインスレッドの設計は非常に単純です。その唯一の仕事は、スレッドセーフなワークキューからタスク(ワークブロック)を取得し、アプリケーションが終了するまでそれらを実行することです。

メインスレッドによって実行されるこれらのタスクは、ライフサイクル情報に関連するコールバック、ユーザーイベント(入力など)、または他のアプリケーションやプロセスからのイベントから取得されます。もちろん、アプリケーション開発でフレームワークを使用せずに、自分でタスクキューを実装することもできます。

アプリケーションによって実行されるほとんどすべてのコードブロックは、イベントコールバック(入力、レイアウト拡張、描画など)に関連付けられています。操作がイベントをトリガーすると、イベントを持つ子スレッドは、子スレッドからメインスレッドのメッセージキューにイベントをプッシュします。次に、メインスレッドはイベントにサービスを提供できます。

アニメーションや画面の更新が行われているとき、システムは毎秒60フレームのスムーズな速度でレンダリングするように、16ミリ秒ごとにタスク(画面の描画を担当)を実行しようとします。システムがこの目標を達成するには、メインスレッドでインターフェイス/ビュー階層を更新する必要があります。ただし、メインスレッドのメッセージキューにタスクが多すぎたり長すぎたりすると、メインスレッドは更新を十分な速度で完了できません。メインスレッドが16ミリ秒以内にタスクを完了できない場合、ユーザーはフリーズ、遅延、またはインターフェイスが入力に応答しないことを認識する可能性があります。メインスレッドが約5秒間ブロックされると、システムは「アプリケーションが応答していません」(ANR)ダイアログボックスを表示し、ユーザーがアプリケーションを直接閉じることができるようにします。

メインスレッドが多数の時間のかかるタスクを実行してストールなどの問題を引き起こすのを防ぐために、処理のために多数のまたは時間のかかるタスクをメインスレッドからサブスレッドに移動する必要があります。スムーズなレンダリングには影響せず、ユーザー入力にすばやく応答します。

子スレッドとUIオブジェクト間の参照関係

Androidの設計によると、AndroidのUIオブジェクトはスレッドセーフではありません。UIオブジェクトを作成、使用、または破棄するかどうかに関係なく、アプリケーションはメインスレッドで実行する必要があります。メインスレッド以外のスレッドでUIオブジェクトを変更または参照しようとすると、例外、サイレントエラー、クラッシュ、およびその他の未定義の異常な動作が発生する可能性があります。

したがって、メインスレッドでUIオブジェクトを操作する必要があります。上記のように、子スレッドで時間のかかるタスクを実行する必要がありますが、実行する時間のかかるタスクがUIオブジェクトに関連している場合はどうなりますか?たとえば、一般的なデータリクエスト操作は、最初にデータをリクエストしてからUIオブジェクトを更新することです。データのリクエストは通常​​、時間のかかる操作です。メインスレッドで操作すると、必然的にメインスレッドがブロックされて紙詰まりの原因になります。では、子スレッドを使用して問題を解決するにはどうすればよいでしょうか。

説明する前に、子スレッドからの2種類のUIオブジェクト参照(明示的参照と暗黙的参照)について説明しましょう。

参照を表示する

非メインスレッドでの多くのタスクの最終的な目標は、UIオブジェクトを更新することです。ただし、スレッドの1つがビュー階層内のオブジェクトにアクセスすると、アプリケーションが不安定になる可能性があります。ワーカースレッドがオブジェクトのプロパティを変更し、他のスレッドが同時にオブジェクトを参照している場合、結果を判別できません。

たとえば、アプリケーションがワーカースレッド上のUIオブジェクトを直接参照するとします。ワーカースレッドのオブジェクトにはビューへの参照が含まれている場合がありますが、作業が完了する前に、ビューはビュー階層から削除されます。これらの2つの操作が同時に発生すると、参照はViewオブジェクトをメモリに保持し、プロパティを設定します。ただし、現時点ではユーザーにオブジェクトが表示されることはなく、オブジェクト参照が消えた後、アプリケーションはオブジェクトを削除します。

別の例を示すために、Viewオブジェクトにそれが属するアクティビティへの参照が含まれていると仮定します。アクティビティが破棄されても、それを直接または間接的に参照するスレッド処理タスクがまだある場合、ガベージコレクターは、処理タスクが完了するまで待機してからアクティビティを収集します。

スレッド処理作業の実行中にアクティビティライフサイクルイベント(画面回転など)が発生した場合、問題が発生する可能性があります。進行中の作業が完了するまで、システムはガベージコレクションを実行できません。したがって、ガベージコレクションが使用可能な場合、メモリ内に2つのActivityオブジェクトが存在する可能性があります。

このような場合、アプリケーションのスレッド処理タスクにインターフェイスオブジェクトへの明示的な参照を含めないでください。このような参照を回避すると、スレッド処理の競合を回避しながら、これらのタイプのメモリリークを防ぐことができます。

いずれの場合も、アプリケーションはメインスレッドのインターフェイスオブジェクトのみを更新する必要があります。子スレッドとメインスレッドはどのように連携する必要がありますか?

メインスレッドからサブスレッドに切り替え、サブスレッドを使用して時間のかかるタスク(この例のデータ要求操作など)を処理できます。タスクの処理が完了したら、メインスレッドに戻ってUIの更新を実行します。このようにして、作業タスクは子スレッドで処理され、最終的なUIビューの更新は、統合処理のためにメインスレッドに転送され、参照UIオブジェクトを表示する子スレッドによって引き起こされるさまざまな問題を回避します。

暗黙の参照

Java内部クラス(匿名内部クラスと名前付き内部クラスを含む)では、それらのオブジェクトは外部クラスオブジェクトへの参照を暗黙的に保持するため、外部クラスをガベージコレクションできないという問題が発生します。

Androidの一般的な例を挙げてください。

    public class MainActivity extends Activity {
      // ...
      public class MyAsyncTask extends AsyncTask<Void, Void, String>   {
        @Override protected String doInBackground(Void... params) {...}
        @Override protected void onPostExecute(String result) {...}
      }
    }

サンプルコードでは、スレッド処理オブジェクトMyAsyncTaskは、Activityの非静的内部クラス(またはKotlinの内部クラス)として宣言されています。この宣言により、内部クラスMyAsyncTaskはActivityインスタンスへの暗黙的な参照を保持します。したがって、スレッド処理作業が完了する前に、オブジェクトには常に対応するアクティビティへの参照が含まれます。これにより、参照されるアクティビティの破棄が遅延し、メモリリークの問題が発生します。

それを解決する方法は?

実際、それは簡単です。暗黙の参照を削除するには、内部クラスMyAsyncTaskを静的クラスとして定義するだけです。

AsyncTaskオブジェクトを静的なネストされたクラスとして宣言します(またはKotlinの内部修飾子を削除します)。静的なネストされたクラスは内部クラスとは異なるため、そうすることで暗黙的な参照の問題を排除できます。内部クラスのインスタンスは外部クラスのインスタンスのインスタンス化を必要とし、カプセル化されたインスタンスのメソッドとフィールドに直接アクセスできます。対照的に、静的にネストされたクラスは、カプセル化クラスのインスタンスを参照する必要がないため、外部クラスのメンバーへの参照は含まれません。

    public class MainActivity extends Activity {
      // ...
      static public class MyAsyncTask extends AsyncTask<Void, Void, String>   {
        @Override protected String doInBackground(Void... params) {...}
        @Override protected void onPostExecute(String result) {...}
      }
    }

スレッドとアプリケーションアクティビティのライフサイクル

アプリケーションのライフサイクルは、子スレッドがAPPでどのように機能するかに影響します。アクティビティが破棄された後も子スレッドを保持する必要があるかどうかを判断する必要がある場合があります。また、スレッドの優先度と、アクティビティがフォアグラウンドで実行されているかバックグラウンドで実行されているかとの関係にも注意を払う必要があります。

予約スレッド

UIインターフェイスが破棄された場合、元のタスクを実行し続けるために子スレッドを保持し続ける必要があるかどうかを確認する必要があります。

以下はその一例です。

アクティビティが作業タスクを処理するためのスレッドのセットを生成し、作業スレッドが作業タスクを実行する前に破棄されると仮定すると、アプリケーションは実行中の作業タスクをどのように処理する必要がありますか?

作業タスクが破棄されたインターフェイスを更新する場合、作業を続行する必要はありません。たとえば、タスクがデータベースからユーザー情報をロードしてからビューを更新することである場合、スレッドは不要になります。

対照的に、作業タスクは、インターフェースの更新に完全には関連していないデータ操作を処理し、データは後で使用する必要がある場合があります。この場合、スレッドを保持する必要があります。たとえば、データパケットは、イメージのダウンロード、ディスクへのキャッシュ、および関連付けられたViewオブジェクトの更新を待機している場合があります。オブジェクトはもう存在しませんが、ユーザーが破棄されたアクティビティに戻った場合に備えて、イメージをダウンロードしてキャッシュすると便利な場合があります。

スレッドの優先度

アプリケーションでスレッドを作成および管理するときは、スレッドが正しい優先順位を取得するように、必ずスレッドの優先順位を設定してください。サブスレッドの優先度を高く設定しすぎると、メインスレッドとレンダリングスレッドに干渉し、アプリケーションがフレームをドロップして紙詰まりを引き起こす可能性があります。子スレッドの優先度の設定が低すぎると、非同期タスク(画像の読み込みなど)が必要な速度に到達しない場合があります。

システムのスレッドスケジューラは、優先度の高いスレッドを優先し、これらの優先度と最終的にすべての作業を完了する必要性との間でトレードオフを行います。一般的に、フォアグラウンドグループはデバイスの合計実行時間の約95%を占め、バックグラウンドグループは約5%を占めます。

スレッドが作成されるたびに、setThreadPriority()を呼び出して、スレッドの優先度を設定する必要があります。

スレッド優先度の設定については、「Androidでスレッド優先度を設定する正しい方法(2つの方法)」を参照してください

さらに、通常、スレッドプール内のスレッドの優先度を設定する必要がありますが、どのように設定しますか?「スレッドプールでのスレッド優先度のAndroidコード実装」を参照してください

また、システムはProcessクラスを使用して、各スレッドに独自の優先度値を割り当てます。

デフォルトでは、システムはスレッドの優先度を、それを生成したスレッドと同じ優先度とグループメンバーシップを持つように設定します。つまり、デフォルトでは、子スレッドの優先度は、スレッドを作成した親スレッドの優先度を継承します。ただし、setThreadPriority()を使用して、スレッドの優先度を明示的に調整する必要があります。

Processクラスは、スレッドの優先度の定数のセットを提供して、優先度の値の割り当てを簡素化するのに役立ちます。たとえば、THREAD_PRIORITY_DEFAULTは、スレッドのデフォルト値を表します。スレッドによって実行される作業がそれほど緊急ではない場合、アプリケーションはスレッドの優先度をTHREAD_PRIORITY_BACKGROUNDに設定する必要があります。

THREAD_PRIORITY_LESS_FAVORABLE定数とTHREAD_PRIORITY_MORE_FAVORABLE定数をインクリメンターとして使用して、相対的な優先順位を設定することもできます。

非同期スレッドを作成して使用する方法

Androidは、Thread、Runnable、Executorsクラスなど、スレッド処理を容易にするために同じJavaクラスとプリミティブを提供します。また、Androidのスレッド処理を容易にし、開発するために、Androidには、AsyncTaskLoaderやAsyncTaskなどの開発を支援できる一連の補助プログラムが用意されています。各補助クラスには、少数の特定のスレッド処理の問題を解決するための特定のパフォーマンスのニュアンスがあります。間違った状況で間違ったクラスを使用すると、パフォーマンスの問題が発生する可能性があります。

Androidでスレッドを使用する方法については、前の記事「Android非同期タスクの6つの実装方法の詳細な説明」を参照してください

いくつのスレッドを作成する必要がありますか?

上記のように、サブスレッドを使用すると、時間のかかるタスクが原因でメインスレッドがストールするのを防ぐことができます。それで、より多くのスレッドを作成する方が良いですか?

技術レベルではありますが、コード内に数百のスレッドを作成できますが、そうするとパフォーマンスの問題が発生する可能性があります。私たちのアプリケーションは、限られたCPUリソースをバックグラウンドサービス、レンダリングプログラム、オーディオエンジン、ネットワークなどと共有します。CPUは、実際には少数のスレッドのみを並列処理できます(これはデバイスのコア数に関連しています)。制限を超えると、優先順位とスケジューリングの問題が発生します。したがって、ワークロード要件に基づいて適切な数のスレッドを作成することが重要です。さらに、子スレッドの優先度も同時に制御する必要があります。

子スレッドとメインスレッドはCPUリソースをめぐって競合します。

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-Tl4v5SpC-1608193450595)(evernotecid:// 6FE75482-54A0-433A-9625- A01F7FEE92EC / appyinxiangcom / 9896050 / ENResource / p2888)]

実際、作成するスレッドの数は多くの変数によって異なりますが、値を選択して(たとえば、最初に4を選択)、テストにSystraceを使用できます。この戦略は他の戦略と同じくらい信頼性があります。試行錯誤を繰り返して、問題を回避するために少なくともいくつのスレッドを減らす必要があるかを見つけることができます。

作成するスレッドの数を決定するときは、スレッドが空いていないことも考慮する必要があります。スレッドはメモリを消費します。Androidのスレッドは、最終的にネイティブレイヤーを介して作成されます。FixStackSizeは、スレッドスタックサイズを設定するために使用されます。デフォルトでは、スレッドスタックに必要な合計メモリサイズ= 1M + 8k + 8k、つまり1040kです。デバイスにインストールされている多数のアプリケーションは、特にコールスタックが大幅に拡張されている場合、すぐにこの数になります。

実際、通常の状況では、リソースの最適化のためにスレッドを再利用する必要があります。既存のスレッドプールを再利用できるため、メモリと処理リソースの競合を減らすことができ、パフォーマンスの向上に役立ちます。


** PS:よりエキサイティングなコンテンツについてはチェックしてください-> 「Android開発」
** PS:よりエキサイティングなコンテンツについてはチェックしてください-> 「Android開発」
** PS:よりエキサイティングなコンテンツについてはチェックしてください-> 「Android開発」

おすすめ

転載: blog.csdn.net/u011578734/article/details/111318450