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ı
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.
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.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
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); } } }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
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."); } }2.2 doCallbacks ();
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.
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.
Handler.post (Runnable r) yöntemini kullandığımızda, r'yi mesajın geri çağrısı olarak ayarlıyoruz
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ü
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
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.