Androidキュー機能を実装したいですか?ハンドラーの内なる強さの精神的な方法、あなたはそれに値する!(1)-ハンドラーのソースコードと一般的な質問への回答

ハンドラーはAndroidのメッセージ処理メカニズムであり、スレッド間の通信のためのソリューションです。同時に、メインスレッドに自然にキューを作成することも理解できます。キュー内のメッセージの順序は遅延です。 Androidでキュー機能を実装したい場合は、初めて検討することをお勧めします。この記事は3つの部分に分かれています。

ハンドラーのソースコードとよくある質問への回答

  1. スレッドには最大でいくつのハンドラー、ルーパー、メッセージキューがありますか?
  2. Looperの無限ループによってアプリケーションがフリーズしないのはなぜですか?多くのリソースを消費しますか?
  3. Dialog、Toastなどの子スレッドのUIを更新するにはどうすればよいですか?システムが子スレッドのUIの更新を推奨しないのはなぜですか?
  4. メインスレッドはどのようにネットワークにアクセスしますか?
  5. ハンドラーの不適切な使用によって引き起こされるメモリリークに対処するにはどうすればよいですか?
  6. ハンドラーのメッセージ優先度のアプリケーションシナリオは何ですか?
  7. メインスレッドのルーパーはいつ終了しますか?手動でログアウトできますか?
  8. 現在のスレッドがAndroidのメインスレッドであるかどうかを判断するにはどうすればよいですか?
  9. メッセージインスタンスを作成する正しい方法は?

ハンドラーの詳細な質問応答

  1. ThreadLocal
  2. epollメカニズム
  3. 同期バリアメカニズムを処理する
  4. ハンドラーロック関連の問題
  5. ハンドラーの同期メソッド

システムおよびサードパーティフレームワークでのHandlerの一部のアプリケーション

  1. HandlerThread
  2. IntentService
  3. クラッシュしないアプリを構築する方法
  4. グライドでのアプリケーション

ハンドラーのソースコードとよくある質問への回答

公式の定義を見てみましょう:

ハンドラーを使用すると、スレッドのMessageQueueに関連付けられたMessageオブジェクトとRunnableオブジェクトを送信および処理できます。各Handlerインスタンスは、単一のスレッドとそのスレッドのメッセージキューに関連付けられています。新しいハンドラーを作成すると、ルーパーにバインドされます。メッセージとランナブルをそのルーパーのメッセージキューに配信し、そのルーパーのスレッドで実行します。

一般的な考え方は、Handlerを使用するとMessage / Runnableをスレッドのメッセージキュー(MessageQueue)に送信でき、各Handlerインスタンスはスレッドとそのスレッドのメッセージキューに関連付けられます。ハンドラーを作成するときは、ルーパーにバインドする必要があります(メインスレッドはデフォルトですでにルーパーを作成しており、子スレッドはルーパーを自分で作成する必要があります)。メッセージ/実行可能ファイルをルーパーの対応するメッセージキューに送信し、そのルーパー内の対応するスレッド。メッセージ/実行可能。次の図は、Handlerのワークフローです。

ハンドラーの作業フローチャート

スレッドでは、ルーパーのコンベヤーベルトは実際には無限ループであり、メッセージキューMessageQueueからメッセージを継続的にフェッチし、最後にメッセージ配信のためにHandler.dispatchMessageに渡し、Handler.sendXXX、Handler.postXXXこれらのメソッドを渡します。メッセージをメッセージキュー内のMessageQueueに送信します。モード全体は、実際にはプロデューサー-コンシューマーモードであり、メッセージを継続的に生成および処理し、メッセージがない場合はスリープします。MessageQueueは、単一リンクリストで構成される優先キューです(すべてのヘッドが取得されるため、キューになります)。

前述のように、ハンドラーを作成するときは、ルーパーをバインドする必要があります(バインドは作成としても理解できます。メインスレッドはデフォルトでルーパーを既に作成しており、子スレッドはルーパーを自分で作成する必要があります)。最初にメインスレッドを見てください。どのように処理されますか。

//ActivityThread.java
public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();
        ···
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThreadのmainメソッドで、最初にLooper.prepareMainLooper()メソッドを呼び出し、次に現在のスレッドのハンドラーを取得し、最後にLooper.loop()を呼び出すことがわかります。まず、Looper.prepareMainLooper()メソッドを見てください。

//Looper.java  
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself.  See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
     prepare(false);
     synchronized (Looper.class) {
         if (sMainLooper != null) {
             throw new IllegalStateException("The main Looper has already been prepared.");
         }
         sMainLooper = myLooper();
     }
}
//prepare
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
}

現在のスレッドのルーパーはLooper.prepareMainLooper()メソッドで作成され、ルーパーインスタンスはスレッドローカル変数sThreadLocal(ThreadLocal)に格納されていることがわかります。つまり、各スレッドには独自のルーパーがあります。ルーパーが作成されると、スレッドのメッセージキューも作成されました。prepareMainLooperがsMainLooperに値があるかどうかを判断することがわかります。複数回呼び出されると、例外がスローされるため、1つだけになります。メインスレッドのルーパーとメッセージキュー。同様に、Looper.prepare()が子スレッドで呼び出されると、prepare(true)メソッドが呼び出されます。複数回呼び出されると、各スレッドが1つのルーパーしか持てないという例外もスローされます。各スレッドにはルーパーが1つだけあり、MessageQueueもあります。

//Looper.java
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

メインスレッドsMainThreadHandler = thread.getHandler()を見てみましょう。getHandlerが取得するのは実際にはmHハンドラーです。

//ActivityThread.java
final H mH = new H(); 
@UnsupportedAppUsage
    final Handler getHandler() {
      return mH;
}

mHハンドラーはActivityThreadの内部クラスです。handMessageメソッドを表示すると、このハンドラーが、サービスの作成やサービスのメッセージのバインドなど、4つの主要コンポーネントであるアプリケーションなどのメッセージを処理していることがわかります。

//ActivityThread.java
class H extends Handler {
    ···

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case RECEIVER:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                handleReceiver((ReceiverData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case CREATE_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                handleCreateService((CreateServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case BIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                handleBindService((BindServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case UNBIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
                handleUnbindService((BindServiceData)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case SERVICE_ARGS:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
                handleServiceArgs((ServiceArgsData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case STOP_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
                handleStopService((IBinder)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            ···
            case APPLICATION_INFO_CHANGED:
                mUpdatingSystemConfig = true;
                try {
                    handleApplicationInfoChanged((ApplicationInfo) msg.obj);
                } finally {
                    mUpdatingSystemConfig = false;
                }
                break;
            case RUN_ISOLATED_ENTRY_POINT:
                handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
                        (String[]) ((SomeArgs) msg.obj).arg2);
                break;
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                if (isSystem()) {
                    // Client transactions inside system process are recycled on the client side
                    // instead of ClientLifecycleManager to avoid being cleared before this
                    // message is handled.
                    transaction.recycle();
                }
                // TODO(lifecycler): Recycle locally scheduled transactions.
                break;
            case RELAUNCH_ACTIVITY:
                handleRelaunchActivityLocally((IBinder) msg.obj);
                break;
            case PURGE_RESOURCES:
                schedulePurgeIdler();
                break;
        }
        Object obj = msg.obj;
        if (obj instanceof SomeArgs) {
            ((SomeArgs) obj).recycle();
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
    }
}

最後に、Looper.loop()メソッドを見てみましょう。

//Looper.java
public static void loop() {
      //获取ThreadLocal中的Looper
    final Looper me = myLooper();
        ···
    final MessageQueue queue = me.mQueue;
    ···
    for (;;) { //死循环
          //获取消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
                ···
        msg.target.dispatchMessage(msg);
                ···
        //回收复用  
        msg.recycleUnchecked();
    }
}

ループメソッドは無限ループです。ここでは、メッセージqueue.next()がメッセージキューから継続的に取得され、メッセージがハンドラー(msg.target)を介して配信されます。実際、特定のバインディングはありません。ハンドラーがあります各スレッドに対応するルーパーとメッセージキューは1つだけであり、処理、つまりLooper.loop()メソッドの呼び出しをこれらに依存するのは自然なことです。Looper.loop()のエンドレスループでは、メッセージが継続的にフェッチされ、最終的にリサイクルされて再利用されます。

ここでは、メッセージのパラメータターゲット(ハンドラー)を強調する必要があります。各メッセージがメッセージ配信に対応するハンドラーを見つけることができるのはこの変数であり、複数のハンドラーが同時に機能できるようにします。

子スレッドがどのように処理されるかを見てみましょう。まず、子スレッドにハンドラーを作成し、Runnableを送信します。

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
                    }
                });
            }
        }).start();

    }

実行後、エラーログが表示されます。子スレッドでLooper.prepare()メソッドを呼び出す必要があることがわかります。これは、実際には、ハンドラーに「関連付ける」ルーパーを作成するためのものです。

   --------- beginning of crash
2020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.jackie.testdialog, PID: 21122
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:207)
        at android.os.Handler.<init>(Handler.java:119)
        at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)
        at java.lang.Thread.run(Thread.java:919)

Looper.prepare()を追加してLooperを作成し、Looper.loop()メソッドを呼び出してメッセージの処理を開始します。

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //创建Looper,MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
                    }
                });
                //开始处理消息
                Looper.loop();
            }
        }).start();
    }

ここで、すべてが処理された後にquitメソッドを呼び出してメッセージループを終了する必要があることに注意してください。そうしないと、子スレッドは常にループ待機状態になるため、ルーパーを終了する必要がない場合は、ルーパーを呼び出します。 myLooper()。quit()。

上記のコードを読んだ後、質問があるかもしれませんが、子スレッドのUI(トースト)を更新するのは問題ではありませんか?Androidは子スレッドのUIを更新することを許可されていませんか?実際、これはViewRootImplでは、checkThreadメソッドはmThread!= Thread.currentThread()を検証します。mThreadの初期化はViewRootImplのコンストラクターで行われます。つまり、ViewRootImplを作成するスレッドは、checkThreadが呼び出されるスレッドと一致している必要があります。 。UIの更新はメインスレッドに限定されません。続行できます。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

ここでいくつかの概念を紹介する必要があります。ウィンドウはAndroidのウィンドウです。各アクティビティ、ダイアログ、トーストは特定のウィンドウに対応します。ウィンドウは抽象的な概念です。各ウィンドウはビューとViewRootImplに対応します。ウィンドウとビューはViewRootImplをパスして確立します。したがって、接続はビューの形式で存在します。ToastでのViewRootImplの作成プロセスを見てみましょう。toastのshowメソッドを呼び出すと、最終的にそのhandleShowメソッドが呼び出されます。

//Toast.java
public void handleShow(IBinder windowToken) {
        ···
    if (mView != mNextView) {
        // Since the notification manager service cancels the token right
        // after it notifies us to cancel the toast there is an inherent
        // race and we may attempt to add a window after the token has been
        // invalidated. Let us hedge against that.
        try {
            mWM.addView(mView, mParams); //进行ViewRootImpl的创建
            trySendAccessibilityEvent();
        } catch (WindowManager.BadTokenException e) {
            /* ignore */
        }
    }
}

このmWM(WindowManager)の最後の実装者はWindowManagerGlobalです。そのaddViewメソッドで、ViewRootImplが作成され、次にroot.setView(view、wparams、panelParentView)が実行され、ViewRootImplを介してインターフェイスが更新され、Windowを追加するプロセスは次のようになります。完了しました。

//WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); //创建ViewRootImpl

    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // do this last because it fires off messages to start doing things
    try {
        //ViewRootImpl
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

SetViewは、requestLayoutを介して非同期更新要求を内部的に完了し、checkThreadメソッドを呼び出してスレッドの正当性を検証します。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

したがって、ViewRootImplの作成はサブスレッドで行われるため、mThreadの値もサブスレッドであり、更新もサブスレッドで行われるため、例外は生成されません。これも参照できます。分析のための記事と非常に詳細な書き込み。同様に、次のコードでもこの状況を確認できます。

//子线程中调用    
public void showDialog(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //创建Looper,MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        builder = new AlertDialog.Builder(HandlerActivity.this);
                        builder.setTitle("jackie");
                        alertDialog = builder.create();
                        alertDialog.show();
                        alertDialog.hide();
                    }
                });
                //开始处理消息
                Looper.loop();
            }
        }).start();
    }

子スレッドでshowDialogメソッドを呼び出し、最初にalertDialog.show()メソッドを呼び出してから、alertDialog.hide()メソッドを呼び出します。hideメソッドは、他の操作を実行せずに(ウィンドウを削除せずに)ダイアログを非表示にするだけです。次にメインでスレッドはalertDialog.show();を呼び出します。ビュー階層を作成した元のスレッドのみがビューの例外に触れることができます。

2020-11-09 18:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.jackie.testdialog, PID: 24819
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
        at android.view.View.requestLayout(View.java:24454)
        at android.view.View.setFlags(View.java:15187)
        at android.view.View.setVisibility(View.java:10836)
        at android.app.Dialog.show(Dialog.java:307)
        at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
        at android.view.View.performClick(View.java:7125)
        at android.view.View.performClickInternal(View.java:7102)

したがって、スレッドのUIを更新する焦点は、ViewRootImplとcheckThreadが同じスレッドであるかどうかです。

メインスレッドでネットワークにアクセスする方法

ネットワークリクエストの前に次のコードを追加します。

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);

Android 2.3で導入されたStrictMode(ストリクトモード)。ThreadPolicy(スレッド戦略)とVmPolicy(VM戦略)の2つの主要な問題を検出するために使用されます。ここでは、ストリクトモードのネットワーク検出がオフになっているため、メインスレッドでネットワーク操作を実行できます。これは一般的にはお勧めできません。

システムが子スレッドでUIにアクセスすることを推奨しないのはなぜですか?

これは、AndroidのUIコントロールがスレッドセーフではないためです。複数のスレッドで同時にアクセスすると、UIコントロールが予期しない状態になる可能性がある場合、システムがUIコントロールにロックメカニズムを追加しないのはなぜですか。2つの欠点があります。

  1. まず、ロックメカニズムを追加すると、UIアクセスのロジックが複雑になります
  2. ロックメカニズムは一部のスレッドの実行をブロックするため、ロックメカニズムはUIアクセスの効率を低下させます。

したがって、最も簡単で効率的な方法は、シングルスレッドモデルを使用してUI操作を処理することです。(Android開発アートを探索する)

子スレッドは、UIを更新するようにメインスレッドにどのように通知しますか(ハンドルを介してすべて、UIを操作するためにメインスレッドにメッセージを送信します)

  1. ハンドラーはメインスレッドで定義され、子スレッドはmHandlerを介してメッセージを送信し、メインスレッドハンドラーのhandleMessageはUIを更新します。
  2. ActivityオブジェクトのrunOnUiThreadメソッドを使用します。
  3. ハンドラーを作成し、getMainLooperを渡します。
  4. View.post(Runnable r)。

Looperの無限ループによってアプリケーションがフリーズしないのはなぜですか?多くのリソースを消費しますか?

メインスレッドとサブスレッドの以前の分析から、Looperがスレッド内のメッセージを継続的に取得することがわかります。サブスレッドの無限ループの場合、タスクが完了したら、ユーザーは手動で終了する必要があります。寝かせて待つ代わりに。(Gityuanからの引用)スレッドは実際には実行可能コードの一部です。実行可能コードが実行されると、スレッドのライフサイクルが終了し、スレッドが終了します。メインスレッドについては、一定期間実行されてから自分で終了することを望んでいません。それでは、常に存続できるようにするにはどうすればよいでしょうか。簡単な方法は、実行可能コードを継続的に実行でき、エンドレスループが終了しないことを保証できることです。たとえば、バインダースレッドもエンドレスループメソッドを採用し、バインダードライバーを使用して読み取りおよび書き込み操作を行います。異なるループ方法もちろん、それは単なるエンドレスループではなく、メッセージがないときにスリープします。Androidはメッセージ処理メカニズムに基づいており、ユーザーの動作はこのLooperループ内にあります。スリープ中に画面をタップすると、メインスレッドが起動して作業を続行します。

常に実行されているメインスレッドの無限ループはCPUリソースを消費しますか?実際にはそうではありません。これがLinuxのパイプ/ epollメカニズムです。簡単に言えば、メインスレッドのMessageQueueにメッセージがない場合、ループのqueue.next()のnativePollOnce()メソッドでブロックされます。このとき、メインスレッドはCPUを解放します。次のメッセージが到着するかトランザクションが発生するまでリソースは休止状態になり、パイプの書き込み側にデータを書き込むことでメインスレッドがウェイクアップされます。ここで使用されるepollメカニズムは、複数の記述子を同時に監視できるIO多重化メカニズムです。記述子の準備ができている(読み取りまたは書き込みの準備ができている)と、対応するプログラムに読み取りまたは書き込み操作(基本的に同期I / O)を即座に通知します。つまり、読み取りと書き込みがブロックされます。したがって、メインスレッドはほとんどの場合休止状態であり、多くのCPUリソースを消費しません。

メインスレッドのルーパーはいつ終了しますか

アプリが終了すると、ActivityThreadのmH(ハンドラー)はメッセージを受信した後に終了を実行します。

//ActivityThread.java
case EXIT_APPLICATION:
    if (mInitialApplication != null) {
        mInitialApplication.onTerminate();
    }
    Looper.myLooper().quit();
    break;

メインスレッドのルーパーを手動で終了しようとすると、次の例外がスローされます。

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
    at android.os.MessageQueue.quit(MessageQueue.java:428)
    at android.os.Looper.quit(Looper.java:354)
    at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)
    at android.app.Activity.performCreate(Activity.java:7802)
    at android.app.Activity.performCreate(Activity.java:7791)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
    at android.os.Handler.dispatchMessage(Handler.java:107) 
    at android.os.Looper.loop(Looper.java:214) 
    at android.app.ActivityThread.main(ActivityThread.java:7356) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

メインスレッドは終了できないため、終了できないのはなぜですか。一度終了すると、プログラムがハングアップすることを意味します。この方法で終了を使用しないでください。

ハンドラーメッセージ処理シーケンス

Looperがメッセージループloop()を実行すると、次のコード行が実行され、msg.targeがHandlerオブジェクトになります。

msg.target.dispatchMessage(msg);

dispatchMessageのソースコードを見てみましょう:

  public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

1. MessageオブジェクトにCallBackコールバックがある場合、CallBackは実際にはRunnableであり、このコールバックのみが実行されてから終了します。メッセージを作成するためのCallBackコードは次のとおりです。

Message msgCallBack = Message.obtain(handler, new Runnable() {
    @Override
    public void run() {
    }
});

handleCallbackメソッドは、Runnableのrunメソッドを呼び出します。

private static void handleCallback(Message message) {
    message.callback.run();
}

2. MessageオブジェクトにCallBackコールバックがない場合は、elseブランチを入力して、ハンドラーのCallBackが空かどうかを判断します。空でない場合は、CallBackのhandleMessageメソッドを実行してから戻り、ハンドラーのCallBackコードを次のようにビルドします。 :

Handler.Callback callback = new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
          //retrun true,就不执行下面的逻辑了,可以用于做优先级的处理
        return false;
    }
};

3.最後に、ハンドラーのhandleMessage()関数が呼び出されます。これは、私たちがよく書き直す関数であり、メッセージはこのメソッドで処理されます。

使用するシーン

Handler.Callbackがメッセージを処理する優先順位を持っていることがわかります。メッセージが処理され、Callbackによってインターセプトされると(trueを返す)、HandlerのhandleMessage(msg)メソッドは呼び出されません。Callbackがメッセージを処理する場合、インターセプトではなく、メッセージをコールバックとハンドラーで同時に処理できることを意味します。コールバックインターセプトを使用して、ハンドラーメッセージをインターセプトできます。

シナリオ:ActivityThread.mHをフックします。ActivityThreadにはメンバー変数mHがあります。これはハンドラーであり、非常に重要なクラスです。ほとんどすべてのプラグインフレームワークがこのメソッドを使用します。

Handler.post(Runnable r)メソッド実行ロジック

一般的に使用されるHandler.post(Runnable r)メソッドがどのように実行されるか、新しいスレッドが作成されるかどうか、実際には作成されないかどうかを分析する必要があります。このRunnableオブジェクトは単に実行メソッドと呼ばれ、まったく開始されません。スレッド、ソースコードは次のとおりです。

//Handler.java
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

最後に、RunnableオブジェクトはMessageオブジェクトにパッケージ化されます。つまり、RunnableオブジェクトはMessageのCallBackオブジェクトであり、実行の優先順位があります。

ハンドラーがスレッド切り替えを実行する方法

原則は非常に単純です。リソースはスレッド間で共有されます。子スレッドはhandler.sendXXX、handler.postXXX、およびその他のメソッドを介してメッセージを送信し、Looper.loop()を使用してメッセージキュー内のメッセージを継続的に取得し、最後に渡します。それをhandle.dispatchMessageメソッドに渡します。メッセージ配信処理を実行します。

ハンドラーの不適切な使用によって引き起こされるメモリリークに対処するにはどうすればよいですか?

  1. 遅延メッセージがあります。インターフェイスが閉じられた後、メッセージ/実行可能ファイルを時間内に削除してください。handler.removeCallbacksAndMessages(null)を呼び出してください。
  2. 内部クラスによって引き起こされるメモリリークは静的内部クラスに変更され、コンテキストまたはアクティビティ/フラグメントへの弱参照が使用されます。

同時に、非常に重要なポイントがあります。遅延メッセージがある場合、インターフェイスが閉じているときに、ハンドラー内のメッセージはまだ処理されていませんが、メッセージは最終的にどのように処理されますか?たとえば、テスト後、インターフェイスを開いてメッセージの送信を10秒間遅らせた後、インターフェイスを閉じ、最終的にハンドラーのhandMessageメソッド(匿名内部クラスによって作成された)でメッセージ(ログの印刷)を受信しました。 )。MessageQueue->メッセージ->ハンドラー->アクティビティの参照チェーンがあるため、ハンドラーは破棄されず、アクティビティは破棄されません。

メッセージインスタンスを正しく作成する

  1. メッセージのMessage.obtain()静的メソッドによって取得されます。
  2. ハンドラーパブリックメソッドhandler.obtainMessage()を介して

Flyweightデザインパターンを使用して、すべてのメッセージがリサイクルされ、sPoolに入れられます。

面接レビュールート

余計な言葉はお話ししませんが、次に面接のレビュールートをご紹介します。面接の準備もしているのに効率よくレビューする方法がわからない場合は、私のレビュールートを参考にしてください。ご不明な点がございましたら、お気軽にお問い合わせください。

誰もが体系的に学ぶための方向性は次のとおりです。

1.体系的な学習のためのビデオを見る

過去数年間のCrudの経験から、私は本当に新人の戦闘機であることがわかりました。また、Crudのおかげで、私の技術は比較的断片化されており、体系化するのに十分な深さではないため、もう一度勉強する必要があります。 。私に欠けているのは、システムの知識、貧弱な構造フレームワークとアイデアです。そのため、ビデオを通じて学ぶことは、より良く、より包括的です。ビデオ学習に関しては、個人がステーションBでの学習を推奨できます。ステーションBには多くの学習ビデオがあります。唯一の欠点は、無料で簡単に古くなることです。

また、私は自分でいくつかのビデオを集めました。必要に応じてそれらをあなたと共有することができます。

2.知識を体系的に整理し、準備金を改善する

クライアント開発には非常に多くの知識ポイントがあり、インタビューにはまだほとんど何もありません。したがって、これらの知識ポイントにどれだけ準備しているかを確認するためだけに、面接のための他のトリックはありません。したがって、面接に出かけるときは、レビューでどの段階に到達したかを確認することをお勧めします。

システム学習の方向性:

  • アーキテクトに不可欠なスキル:詳細なJavaジェネリック+単純な言語でのアノテーション+並行プログラミング+データ送信とシリアル化+ Java仮想マシンの原則+リフレクションとクラスローディング+動的プロキシ+効率的なIO

  • Androidの高度なUIとFrameWorkのソースコード:高度なUIプロモーション+フレームワークカーネル分析+ Androidコンポーネントカーネル+データの永続性

  • 360°の全体的なパフォーマンスチューニング:設計アイデアとコード品質の最適化+プログラムパフォーマンスの最適化+開発効率の最適化

  • オープンソースフレームワーク設計アイデアの解釈:ホットリペア設計+プラグインフレームワーク解釈+コンポーネントフレームワーク設計+画像読み込みフレームワーク+ネットワークアクセスフレームワーク設計+ RXJavaレスポンシブプログラミングフレームワーク設計+ IOCアーキテクチャ設計+ AndroidアーキテクチャコンポーネントJetpack

  • NDKモジュール開発: NDK基本知識システム+基礎となる画像処理+オーディオおよびビデオ開発

  • WeChatミニプログラム:ミニプログラムの紹介+ UI開発+ API操作+ WeChatドッキング

  • ハイブリッド開発とFlutter: Html5プロジェクトの戦闘+ Flutter Advanced

知識を整理した後、不足している点を確認する必要があるので、これらの知識点については、手元にたくさんの電子書籍やメモを用意しました。これらのメモは、各知識点の完全な要約を提供します。

3.ソースコードを読み、実際の戦闘ノートを読み、神の考えを学びます

「プログラミング言語はプログラマーが表現する方法であり、アーキテクチャーはプログラマーの世界に対する認識です。」したがって、プログラマーがアーキテクチャーをすばやく理解して学習したい場合は、ソースコードを読むことが不可欠です。ソースコードを読むことは、問題を解決し、物事を理解することであり、さらに重要なことは、ソースコードの背後にあるアイデアを参照することです。プログラマーは、何千行ものソースコードを読み、何千もの方法を練習します。

主にWeChatMMKVソースコード、AsyncTaskソースコード、Volleyソースコード、Retrofitソースコード、OkHttpソースコードなどが含まれます。

4.インタビューの前夜に、スプリントの質問

面接前1週間以内に全力疾走を開始できます。質問をブラッシングするときは、テクノロジーが最優先であり、アルゴリズムは並べ替えなどの基本であり、知的質問は、学校の新入生でない限り、通常は質問されないことに注意してください。

面接の質問に関して、私はあなたがお互いから学ぶのを助けるために一連の体系的な面接の質問を個人的に準備しました:

上記のコンテンツは、フルバージョンが必要なすべての人、友人と無料で共有できますすべてのコンテンツを表示するにはここをクリックしてください

おすすめ

転載: blog.csdn.net/weixin_44339238/article/details/111870459