🎨 Android UIはどのように更新されますか?


こんにちは:)アンドロイドシステムを開発すると、tv.text=「hello」私、tv.setText("World!")UIが更新されることがありますこれらのコマンドを実行すると、内部で何が起こるか考えたことがありますか?私は本当に好奇心があります.今日、AndroidでUIの変更メカニズムについて説明します.今日も頑張ります!🌿

1▼ビューに関する用語、クラス、インタフェースの理解


下記の失効、遍歴の説明については、
  • を参照してください.
  • invalidation


  • ビュー階層(親ビューに再描画する必要がある)の処理を示します.

  • ビューを描くのではなく、自分の両親に変更があったことを伝えます.
    The process of causing things to be redrawn
  • traversal


  • フレームを描くために必要なすべてのステップを実行するプロセス.

  • ビューの寸法測定、ビューの位置と寸法配置レイアウト、および図面ビューの処理.

  • ループ後にdraw()を呼び出します.
    Performing all phases of rendering for a frame
  • ViewGroup, ViewRootImpl, ViewParent


    2朕失効()のUI変更メカニズム


    UI変更アルゴリズム


  • ( Drawn out: How Android renders (Google I/O '18) )
  • invalidate()メソッドの呼び出しスタックを確認する


  • View.invalidate()
  • ViewGroup.invalidateChild()
  • parent = parent.invalidateChildInParent() : ViewGroup.invalidateChildInParent()
  • parent = parent.invalidateChildInParent() : ViewRootImpl.invalidateChildInParent()
  • ViewRootImpl.ScheduleTraversals():スケジューリングループタスク
  • ViewRootImpl.performTraversal():ループ操作(drawメッセージ送信)
  • を実行

  • View.invalidate()を呼び出して、ビュー変更のinvalidate操作を開始します.View.invalidate()は、現在の親がinvalidateであることを内部で通知するためのView Groupです.invalidateChild()を呼び出します.

  • ViewGroup.invalidateChild()では、親タスクinvalidateに通知し続けます.do-while文でこの操作を繰り返します.このループでは、ViewGroup.親invalidate操作を通知する部分であるinvalidateChildInParent()を呼び出します.この繰り返しは、現在の親がRootでない場合にのみ繰り返します.

  • 現在の両親がRootであれば、ViewGroupです.invalidateChildInParent()ではなくViewRootImpl.invalidateChildInParent()が呼び出されます.つまりdo-whileの最後の呼び出しはViewRootImplである.invalidateChildInParent()になります.

  • ViewRootImpl.invalidateChildInParent()でループ操作を開始します.ScheduleTransversals()とperformTransversal()に大別します.
  • ViewRootImpl.invalidateChildInParent()では、遍歴操作をスケジュールするために、ViewRootImpl.ScheduleTransversals()を呼び出します.
  • ViewRootImpl.invalidateChildInParent()の場合、遍歴操作を実行するためにViewRootImplが使用されます.performTraversal()を呼び出します.

  • ViewRootImpl.scheduleTransversals()はMainLooperのMessageQueueにメッセージを直接入れますが、JellyBinからChereographerに委任します.タスクのみを委任し、MainLooperのメッセージキューにメッセージを入れます.
  • invalidateChild() , invalidateChildInParent() DEPRECATED


  • ViewParentのinvalidateChild()とinvalidateChildInParent()はすでにDEPRECATEDに登録されており、onDescentInvalidated()を使用する必要があります.上図は以下の通りです.

  • 3「invalidate()を複数回呼び出す場合(invalidate()を連続的に呼び出す場合)


    新規開発者の一般的なエラー


  • 次のコードは、画面を自動的に更新するために使用されます.0から4までの数字、つまり5回の画面切り替えの間隔が1秒になるようにコードを書きたいです.1秒ごとに寝ているので、画面の更新時間を与えているので、正常に働かなければ気持ちがいいです.このコードは本当に正常に動作しますか?
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            findViewById<Button>(R.id.button).setOnClickListener {
                Log.d("setOnClickListener", SystemClock.uptimeMillis().toString())
                onClick(findViewById<TextView>(R.id.text_view))
            }
        }
    
        fun onClick(view: TextView) {
            for (i in 0..4) {
                view.text = i.toString()
                Log.d("for 문 ${i}번째", SystemClock.uptimeMillis().toString())
                SystemClock.sleep(1000)
            }
    
        }
    }

  • 残念ながら、上記のコードは以下の通りです.Androidの開発に入る過程で、次のような間違いを犯したことがあるかもしれません.(ハハ)

    私は
  • で彼を1枚撮りました.正確に言えば、forドアは5回回りました.では、setText()も5回呼び出されたに違いありません.論理は間違っていないようですが、なぜ4万を出力したのか分かりません.
  • このコードが予想通りに実行できない理由

  • は、次のようにコード内の問題を定義します.前述したMainLooperのMessageQueueにメッセージを入れるこの操作原理を適用すると、1秒間隔の問題がない理由が理解できます.他の問題について説明しましょう
  • 1 1秒間隔なし→MessageQueueのEnqueueingに関する
  • 画面変更は1回のみ→ビュー内のmPrivateFlagsに関する
  • タスクの実行順序に関連する理由(Enqueueing)


  • 既存の理解を適用する場合は、次の手順を5回繰り返します.
    invalidate() → performTraversal() → draw Message → Sleep

  • したがって、次の図に示すように、タスクはEnqueueingに入ります.順番に並んで仕事をするすなわち、MessageQueueに0を描画したメッセージから描画4までのMを順にenqueueと理解する.
  • ここでは理由1が理解できる.
  • IのP MSプロセスを完了し、M段階でdrawメッセージはMainThreadタスクとしてMessageQueueに格納される.これは、sleepの実行時間がdrawメッセージの実行時間と異なることを意味します.そのため、画面切り替えやsleepは順番に行うことができません.
  • mPrivateFlagsの理由

  • の場合、画面変更はMessageQueue方式で5回行われますが、速度が速すぎて検出できませんか?と疑問に思うかもしれません.ただし、画面変更は1回のみ発生します.メッセージキューにはdrawメッセージが1つしか含まれていません.
  • ビューのmPrivateFlagsでは、メッセージを1つ制御するだけでMessageQueueにアクセスできます.連続した無効な()呼び出しの場合、ビュー内のmPrivateFlagsは、すべての呼び出しがViewRootImplに到達しないことを制御し、最初の呼び出しのみがViewRootImplに到達することを制御します.
  • すなわち、変換を担当するViewRootImplに1回アクセスするだけで、画面を1回切り替えるだけです.
  • #️⃣ Reference

  • 上図ソース
  • 盧載春:Androidプログラミングの次のステップ