Android neden takılıp kalmıyor? Koreograf

Önsöz

Android kullanıcı arayüzünün sorunsuz olmaması sorununa yanıt olarak Google, Android ekran sistemini yeniden düzenlemek için Project Butter'ı önerdi. Bu yeniden yapılanmanın üç kilit noktası

  • VSynch dikey senkronizasyonu
  • Üçlü Tampon
  • Koreograf Koreograf

Bu yazıda ağırlıklı olarak Koreograf hakkında konuşuyoruz ve devamında başkaları hakkında yazacağız.

koreograf

Arayüzün görüntüsü genellikle CPU tarafından hesaplanacaktır. > GPU sentetik pikselleştirme > Cihaz ekranını görüntüleyin. Android cihazların yenileme hızının genelde 60HZ yani saniyede 60 defa olduğunu biliyoruz.Yaklaşık 16 milimetre içerisinde bir çizim tamamlanırsa sorun yok. Ancak bir kez koordinasyon olmadığında, aşağıda gösterildiği gibi sorunlar olacaktır.

  • İlk döngüde cpu hesaplaması, GPU çalışması ve görüntüleme cihazı ekranı ile ilgili bir sorun yok
  • İkinci döngüde bir önceki döngüde hazırlanan görüntü sorunsuz görüntülenir. CPU hesaplaması, döngü bitmek üzereyken başlar
  • Üçüncü döngüde, CPU ikinci döngüye girdiğinde, hesaplama ertelendi ve bu da takibe neden oldu Üçüncü döngüye girerken, görüntülenmesi gereken görüntü hazır değildi ve üçüncü döngü tamamlandı. Periyodik görüntüler, böylece çerçeveler sıkışmış ve atlanmış görünüyorlar! Google mühendisleri tüm Jank gecikmiş askeri uçağı aradı.

Bu mesele küçük değil, nasıl çözülür? Dikey senkronizasyon Basitçe söylemek gerekirse, CPU'nun planlama ve düzensizlikler olmadan hesaplamasına izin vermek, ancak hesaplamayı her döngünün başlangıcında başlatmak ve ardından düzenli ve düzenli bir şekilde ilerlemek (aşağıda gösterildiği gibi). Koreograf sınıfı, android4.1 ve sonraki sürümlere eklendi, nasıl uygulandığına bir göz atalım.

1. Giriş

1.1 postCallbackDelayedInternal

ViewRoot'un doTravle () yönteminde böyle bir kod satırı var

mChoreographer.postCallback ( Koreograf.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

Tüm Görünüm ağacı için bir ölçüm düzeni çizim işlemini başlatmak anlamına gelir. ViewRootImpl hakkında daha fazla içerik burada tanıtılmayacaktır.

Aşağıdaki yöntem

  • Kamu geçersiz geri arama (...);
  • Kamu geçersiz postCallbackDelayed (...);
  • Kamu geçersiz postFrameCallback (FrameCallback geri araması);
  • Kamu geçersiz postFrameCallbackDelayed (FrameCallback geri araması, uzun gecikmeMillis)

Sonunda postCallbackDelayedInternal (); çağrılacak, bu yüzden bu yöntemin işlevine bir göz atalım.

private void postCallbackDelayedInternal (int callbackType, Object action, Object token, long delayMillis) { senkronize (mLock) { final long now = SystemClock.uptimeMillis (); // Geçerli saati al son uzun dueTime = şimdi + delayMillis; // Bitiş zamanı // Yürütme eylemini mCallback dizisine yerleştirin mCallbackQueues.addCallbackLocked (dueTime, action, token); // Süresi dolmuşsa, dikey senkronizasyon sinyali istemek için kaydolun eğer (dueTime < = şimdi) { programFrameLocked (şimdi); } Başka { // Süresi dolmadıysa, gecikmeli bir mesaj göndermek için işleyiciyi kullanın. Bu gecikmiş mesaj, süresi dolduğunda çalıştırılacaktır. Mesaj msg = mHandler.obtainMessage (MSG_DO_SCHEDULE_CALLBACK, eylem); msg.arg1 = callbackType; msg.setAsynchronous (doğru); mHandler.sendMessageAtTime (msg, dueTime); } } }

1.2 ÇerçeveHandler

Önceki bölümde, süresi dolmuşsa, schedFrameLocked () yöntemini doğrudan çalıştırın, çalıştırılmazsa, hangi değeri göndermek için mHandler (FrameHandler türü) kullanın MSG_DO_SCHEDULE_CALLBACK İleti. Son kullanma tarihinden sonra nasıl çalıştırılır. FrameHandler'ın bunu nasıl ele aldığına bağlıdır.

özel son sınıf FrameHandler Handler'ı genişletir { public FrameHandler (Looper döngüleyici) { süper (ilmek yapıcı); } @Override public void handleMessage (Mesaj mesajı) { anahtarı (msg.what) { MSG_DO_FRAME vakası: doFrame (System.nanoTime (), 0); kırmak; vaka MSG_DO_SCHEDULE_VSYNC: doScheduleVsync (); kırmak; durum MSG_DO_SCHEDULE_CALLBACK: // Süresi gelmediğinde PostCallbackDelayedInternal () yöntemi gönderilir doScheduleCallback (msg.arg1); kırmak; } } }

Bunu yukarıdaki kodda görebiliriz, FramHandler whate öznitelik değerini alır MSG_DO_SCHEDULE_CALLBACK DoScheduleCallback (msg.arg1) yöntemini çalıştıracak; yöntemi takip edin ve görün

1.3 Koreografi # doScheduleCallback

void doScheduleCallback (int callbackType) { senkronize (mLock) { eğer (! mFrameScheduled) { son uzun şimdi = SystemClock.uptimeMillis (); if (mCallbackQueues.hasDueCallbacksLocked (şimdi)) { programFrameLocked (şimdi); } } } }

Bu yöntemde önce bazı değerlendirmeler yapılır, mFrameSceduled yanlıştır ve hasDueCallbacksLocked () yönteminin dönüş değeri doğrudur. Yöntem adına bakarak geri aramanın süresinin dolup dolmadığını tahmin edebilirsiniz. Bunu tekrar analiz edelim. Sonunda, koşullar karşılanırsa, schedFrameLocked () yöntemini çağıracaktır.Bu yöntem tanıdık geliyor mu? Evet, evet, postCallbackDelayedInternal () yönteminde süresi dolarsa doğrudan çalıştırılacak yöntem. Bu yöntemde neler olduğunu görme zamanı.

1.4 SchedFrameLocked ()

private void schedFrameLocked (uzun süredir) { eğer (! mFrameScheduled) { mFrameScheduled = true; // Sonraki karenin işlenmek üzere programlandığını belirtmek için bayrak bitini ayarlayın. eğer (USE_VSYNC) { // Looper iş parçacığında çalışıyorsa, vsync'i hemen planlayın, // aksi takdirde vsync'i UI iş parçacığından planlamak için bir mesaj gönderin // en kısa sürede. / ** Çevirmek için, ana ileti dizisindeyseniz, hemen dikey senkronizasyonu düzenlemek için doğrudan arayın, aksi takdirde ana iş parçacığı bir mesaj gönderecek ve ana iş parçacığında mümkün olan en kısa sürede dikey bir senkronizasyon ayarlayacaktır. * / if (isRunningOnLooperThreadLocked ()) { schedVsyncLocked (); } Başka { Mesaj msg = mHandler.obtainMessage (MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous (doğru); mHandler.sendMessageAtFrontOfQueue (msg); } } Başka { son uzun nextFrameTime = Math.max ( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, şimdi); eğer (DEBUG_FRAMES) { Log.d (TAG, "Sonraki çerçeveyi planlama" + (nextFrameTime-now) + "ms."); } Mesaj msg = mHandler.obtainMessage (MSG_DO_FRAME); msg.setAsynchronous (doğru); mHandler.sendMessageAtTime (msg, nextFrameTime); } } }
  • Bu yöntemin amacı çok nettir; dikey senkronizasyonu düzenlemek, düzenlemek ve mümkün olan en kısa sürede. Dikey senkronizasyonu düzenlemenin koşulu, USE_VSYNC'nin doğru olması, yani cihazın dikey senkronizasyonu desteklemesidir.
  • Dikey senkronizasyon değilse, dikey senkronizasyonu düzenlemek için işleyiciden bir döngü gecikmeli bir mesaj gönderin. Bu Mesajın değeri nedir? MSG_DO_FRAME , MSG_DO_FRAME olan ileti için doFrame () yöntemini yürütmek için 1.2 kod bloğuna bakın.
  • Bir ayrıntı, bu mFrameScheduled değeri true olduğunda, bir sonraki çerçeve oluşturma isteğini planlamadan doğrudan geri dönecektir. Bu yanlışsa, yürütmeye devam etmek ve true olarak ayarlamak için schedFrameLocked () yöntemini yürütün; bu ne zaman false olarak ayarlanır? ? Ayrıntılar için ek 2'ye bakın

Dikey senkronizasyon düzenlemenin özel uygulaması, dikey sinyalleri almak için DisplayEventReceiver olan FrameDisplayEventReceiver sınıfıdır.

özel son sınıf FrameDisplayEventReceiver, DisplayEventReceiver'ı genişletir Runnable'ı uygular { özel boole mHavePendingVsync; özel uzun mTimestampNanos; özel int mFrame; public FrameDisplayEventReceiver (Looper döngüleyici, int vsyncSource) { süper (döngüleyici, vsyncSource); } @Override public void onVsync (long timestampNanos, int builtInDisplayId, int frame) { mTimestampNanos = timestampNanos; mFrame = çerçeve; Mesaj msg = Message.obtain (mHandler, this); msg.setAsynchronous (true); // Mesaj asenkron olarak ayarlandı mHandler.sendMessageAtTime (msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run () { mHavePendingVsync = yanlış; doFrame (mTimestampNanos, mFrame); } }

Dikey senkronizasyon sinyalini aldıktan sonra, onVsync yöntemini geri çağırın.Bu yöntem işleyiciyi geri çağrı ile bir mesaj göndermek için kullanır (kendisi tarafından miras alınan Runnable türü) Son olarak, doFrame () run () içinde çağrılır; Mesajı dağıtmak için aşağıdaki makale ek bir işleyiciye bakın

Bu mesaj asenkron olacak şekilde ayarlanmıştır (msg.setAsynchronous (true);), bu onun ilk yürütme hakkına sahip olduğu anlamına gelir.İlk önce nasıl çalıştırılır? Ek 3 Eşzamansız mesaj modu'na bakın

Özet olarak, geri arama süreci ekleyin

2. Yürütme

doFrame

void doFrame (long frameTimeNanos, int frame) { son uzun başlangıçNanos; senkronize (mLock) { eğer (! mFrameScheduled) { return; // yapacak iş yok } //şimdiki zaman startNanos = System.nanoTime (); // Geçerli saat ve dikey senkronizasyon zamanı final uzun jitterNanos = startNanos-frameTimeNanos; // Dikey senkronizasyon zamanı ile mevcut saat arasındaki fark bir döngüden büyükse düzeltin eğer (jitterNanos > = mFrameIntervalNanos) { // Enterpolasyonun kalanını alın ve daima nokta son uzun lastFrameOffset = jitterNanos% mFrameIntervalNanos; // Mevcut zamanın önceki adımdan çıkarılmasıyla elde edilen kalan, en son her zaman sinyal zamanı olarak kullanılır frameTimeNanos = startNanos-lastFrameOffset; } // Son seferki dikey senkronizasyon süresi hala kısa, sonraki dikey senkronizasyonu ayarlayın ve doğrudan geri dönün eğer (frameTimeNanos < mLastFrameTimeNanos) { schedVsyncLocked (); dönüş; } mFrameInfo.setVsync (amaçlananFrameTimeNanos, frameTimeNanos); mFrameScheduled = yanlış; mLastFrameTimeNanos = frameTimeNanos; } Deneyin { Trace.traceBegin (Trace.TRACE_TAG_VIEW, "Koreograf # doFrame"); AnimationUtils.lockAnimationClock (frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart (); doCallbacks (Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart (); doCallbacks (Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart (); doCallbacks (Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks (Choreographer.CALLBACK_COMMIT, frameTimeNanos); } en sonunda { AnimationUtils.unlockAnimationClock (); Trace.traceEnd (Trace.TRACE_TAG_VIEW); } eğer (DEBUG_FRAMES) { final long endNanos = System.nanoTime (); Log.d (TAG, "Çerçeve" + çerçeve + ": Bitti, alındı" + (endNanos-startNanos) * 0.000001f + "ms, gecikme" + (startNanos-frameTimeNanos) * 0.000001f + "ms."); } }
  • İlk adım, yargıyı düzeltmektir
    • Geçerli saat startNanos = System.nanoTime ();
    • Geçerli saat ile dikey senkronizasyon zamanı arasındaki farkı bulun: jitterNanos = startNanos-frameTimeNanos;
    • Dikey senkronizasyon süresi ile mevcut saat arasındaki fark bir döngüden büyükse (jitterNanos > = mFrameIntervalNanos) sadece düzeltin
    • Enterpolasyonun kalanını ve periyodu alın: lastFrameOffset = jitterNanos% mFrameIntervalNanos;
    • Önceki adımdan geçerli zamanın çıkarılmasıyla elde edilen kalan, her zaman en son sinyal zamanı olarak kabul edilir: frameTimeNanos = startNanos-lastFrameOffset;
    • Dikey senkronizasyon süresi geçen sefer hala kısadır, bu nedenle bir sonraki işlemeyi ayarlayın: frameTimeNanos < mLastFrameTimeNanos, doğrudan dön
  • Adım 2: Geri aramayı yürütün Geri aramanın yürütme sırası şöyledir:
    • CALLBACK_INPUT giriş zamanı en yüksek önceliğe sahiptir
    • CALLBACK_ANIMATION animasyonu ikinci
    • CALLBACK_TRAVERSAL UI çizim düzeni yeniden
    • CALLBACK_COMMIT animasyon düzeltmesiyle ilgili en son.

    2.2 doCallbacks ();

    • İçinde CallbackQueue mCallbackQueues Belirli bir tipin (girdi, animasyon, düzen, Commit) tekil bağlantılı bir listesinin alınması
    • Ardından süresi dolan Geri Arama / Çalıştırılabilir yürütmeyi çıkarın

    Yürütülmesi gereken Eylemleri çıkarın

    Eylem, zamana göre düzenlenmiş tek yönlü bir liste olan CallbackRecord'a sarılır. CallBackQueue'nun extractDueCallbacksLocked () yöntemi aracılığıyla yürütülecek Eylemleri çıkarın.CallBackQueue, bir Anction hasDueCallbacksLocked () olup olmadığına bakılmaksızın Action addCallbackLocked () eklemeyi, Action removeCallbacksLocked () öğesini kaldırmayı da içeren CallBack'in yönetim sınıfı olarak kabul edilebilir. yöntem.

    özel son sınıf CallbackQueue { // Bağlantı listesi başlığı özel CallbackRecord mHead; // Süresi dolan bir Eylem var mı public boolean hasDueCallbacksLocked (uzun süredir) { return mHead! = null mHead.dueTime < = şimdi; } // Süresi dolan eylemi alın public CallbackRecord extractDueCallbacksLocked (uzun süredir) { ... geri aramaları döndürmek; } // Eylem Ekle public void addCallbackLocked (long dueTime, Object action, Object token) { ... } // Eylemi Kaldır public void removeCallbacksLocked (Nesne eylemi, Nesne simgesi) { ... } }

    Eylemi Yürüt

    for (CallbackRecord c = callbacks; c! = null; c = c.next) { c.run (frameTimeNanos); }

    Geri aramadan CallBcakRecord'u geçin ve bunları birer birer yürütün.

    Üç, özet

    • Koreograf, postCallback ve diğer yöntemleri dış dünyaya sağlar.Sonunda, postCallbackDelayedInternal () 'ı çağırarak bu yöntemi dahili olarak uygularlar. Esas olarak iki şey yaparlar.
    • Mağaza İşlemi
    • Dikey senkronizasyon isteyin
    • Dikey senkronizasyon geri araması derhal Eylemi (CallBack / Runnable) yürütür.
    • Eylem Eylem içeriği türü olabilir
    • CALLBACK_INPUT giriş zamanı en yüksek önceliğe sahiptir
    • CALLBACK_ANIMATION animasyonu ikinci
    • CALLBACK_TRAVERSAL UI çizim düzeni yeniden
    • CALLBACK_COMMIT animasyon düzeltmesiyle ilgili en son.
    • Hanlder mekanizmasını gözden geçirdiğimde, bunun Android sistemini çalıştıran büyük motorun son noktası, işleyicinin mesaj dağıtımı ve yürütmesi ve "asenkron mod" olduğunu düşünüyorum.

    Ekli

    Ek 1. İşleyici yürütme hakkında Mesaj

    İşleyici dağıtım mantığı şu şekildedir: MessageQueue yürütülecek mesajı aldıktan sonra, Looper msg.target.dispatchMessage (msg); 'yi işlemek için mesajın hedef (İşleyici türü) özniteliğine teslim edecektir;

    public void dispatchMessage (Mesaj mesajı) { // msg'nin geri araması boş olmadığında, bir Runnable nesnesi olan msg'nin geri aramasını doğrudan yürütün eğer (msg.callback! = null) { handleCallback (msg); } Başka { // Sonra onu işleyicinin bir niteliği olan mCallBack'e verin, // Bir İşleyici oluştururken bir CallBack nesnesi geçirmeyi seçebilirsiniz // callBack'teki handleMessage true döndürdüğünde, bu şu anlama gelir: Daha fazla işlem istenmiyorsa true (daha fazla işlem gerekmez) eğer (mCallback! = null) { eğer (mCallback.handleMessage (msg)) { dönüş; } } // mCallback işlemi false değerine döndüğünde İşleyicinin handleMessage () yöntemini yürütün handleMessage (msg); } }

    İşleyicinin Mesaj mantığının yürütülmesini ve dağıtımını özetlemek için anahtar mantık zaten açıklanmıştır.

  • Mesajın callback (runnable) özelliği boş değilse, çalıştırmak için bu çalıştırılabilirin run () yöntemini çağırın
  • private static void handleCallback (Mesaj mesajı) { message.callback.run (); }

    Handler.post (Runnable r) yöntemini kullandığımızda, r'yi mesajın geri çağrısı olarak ayarlıyoruz

  • Yukarıdaki koşullar karşılanmazsa, işleyicinin kendi mCallback'i boş değilse, mesajı işlenmek üzere mCallback'e gönderir ve handlerMessage () sona erer. İşleyici oluşturulduğunda bu öznitelik aktarılır. mCallback, işleyicinin dahili bir arabirimi olan bir CallBack türüdür.
  • genel arayüz Geri arama { boolean handleMessage (Mesaj mesajı); }

    3. messaga'nın callBak'i boş olduğunda ve işleyicinin mCallBack'i boş olduğunda, kendi handlerMessage () yöntemi tarafından çalıştırılacaktır. İşleyiciyi özelleştirdiğimizde, mesaj üzerinde karşılık gelen işlemleri gerçekleştirmek için bu yöntemi geçersiz kılabiliriz.

    Ek II. MFrameScheduled özniteliğinin rolü

    • Callcack yürütülürken, mFrameScheduled özniteliğinin yanlış olması durumunda, sonraki çerçevenin işlenmek üzere programlanmadığı ve sonraki çerçevenin doğrudan döndürüleceği anlamına gelecektir.
    void doFrame (long frameTimeNanos, int frame) { son uzun başlangıçNanos; senkronize (mLock) { eğer (! mFrameScheduled) { return; // yapacak iş yok } ... ... mFrameScheduled = yanlış; ... }
    • SchedFrameLocked () yönteminde, mFrameScheduled değerinin true olarak ayarlanması, sonraki karenin işlenmek üzere programlandığı anlamına gelir. MFrameScheduled şu anda doğruysa bu, sonraki çerçevenin planlandığı ve ardından geri dönüldüğü anlamına gelir, karışıklık yok!

    Ek III: İşleyici mekanizmasının zaman uyumsuz modu

    etki

    "Eşzamansız mod" işlevi önceliklidir ve eşzamansız mesajların eşzamansız modda çalıştırma önceliği vardır.

    kullanım

    MessageQueue, bir bariyer eklemek için postSyncBarrier () yöntemini kullanır ve removeSyncBarrier () yöntemi engeli kaldırır. Bu iki yöntem çiftler halinde kullanılır.

    Uygulama prensibi

    • MessageQueued için postSyncBarrier yöntemi, messagequeue'nun başına hedef niteliği boş olan bir mesaj ekler.
    • MessageQueue'nin next () hedefi boş olan bir mesajla karşılaştığında, mesaj listesinden sadece "asenkron mesajı" kaldıracak, sıradan mesajı yok sayacak ve daha sonraki dağıtım işlemleri için Looper'a teslim edecektir.
    Sonraki mesaj () { ... için (;;) { eğer (nextPollTimeoutMillis! = 0) { Binder.flushPendingCommands (); } nativePollOnce (ptr, nextPollTimeoutMillis); senkronize edildi (bu) { // Bir sonraki mesajı almayı deneyin. Bulunursa geri dönün. son uzun şimdi = SystemClock.uptimeMillis (); Mesaj prevMsg = null; Mesaj msg = mMessages; eğer (msg! = null msg.target == null) { // Bir engelle durduruldu. Sıradaki bir sonraki eşzamansız mesajı bulun. yapmak { prevMsg = msg; msg = msg.next; } while (msg! = null! msg.isAsynchronous ()); } ... mesaj döndür; } ...

    Nihai ilgili çerçeve ve bilgiler

    toplama yöntemi:

    İzle + özel mesajı ücretsiz "Android verileri" yanıtlayın!

    Önceki Android gelişmiş mimari materyallerine, kaynak koduna, notlarına ve videolarına erişim sağlayın. Gelişmiş kullanıcı arayüzü, performans optimizasyonu, mimar kursları, NDK, hibrit geliştirme (ReactNative + Weex) WeChat uygulamaları, Android gelişmiş uygulama teknolojisinin tüm yönlerini Flutter.

    Model oyun kontrolü: Rafine boyama sınırlı çift başlı ejderha Gundam
    önceki
    "Human Beidou" karakter bilgisi açıklandı, orijinal karakterler piyasaya çıktı
    Sonraki
    Mod Çalma Kontrolü: Black Fine Evolution Edition 78
    Zhao Liying veya Gong Li'nin kızları ülkenin kralı olmasına bakılmaksızın, en güzeli her zaman sadece kendisidir.
    Yüksek potansiyelli pazarlama bağlantıları oluşturmak için verileri kullanmanın yeni yolları
    Model oyun kontrolü: gto Zaguki Celia birlikleri
    Japon oyuncuların zihninde hangi oyun dünyanın en iyisi?
    Size tanıtayım, Lu Han ve Guan Xiaotong'dan Ka Ka'nın yararlanıcısı kimdir?
    Zhang Yimou üvey annesi Chen Ting ile nadiren kırmızı halıda yürüyor Xiao Hua'yı kim hatırlıyor?
    Model oyun kontrolü: HGUC MS-14JG White Wolf özel gövde
    Eski çalışan, Tencent tarafından intihal nedeniyle dava edildi ve oyun sonu pazarı taklitçi bir çıkmaza girdi.
    Güçlü düşmanlar yoksa büyük kahramanlar da olmayacak. DC'nin en iyi kötü adamlarından biri
    2017 Celoran Cup China 4A Member Badminton Turnuvası Beijing Station Başarıyla Gerçekleştirildi
    AndroidLibrary'yi JCenter'a yükleyin: Kodunuzun başkaları tarafından zarif bir şekilde alıntılanması nasıl sağlanır
    To Top