国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏)

這篇具有很好參考價(jià)值的文章主要介紹了Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

public FrameHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
// 執(zhí)行doFrame
// 如果啟用VSYNC機(jī)制,當(dāng)VSYNC信號(hào)到來時(shí)觸發(fā)
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
// 申請(qǐng)VSYNC信號(hào),例如當(dāng)前需要繪制任務(wù)時(shí)
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
// 需要延遲的任務(wù),最終還是執(zhí)行上述兩個(gè)事件
doScheduleCallback(msg.arg1);
break;
}
}
}

  1. 注意 USE_VSYNC,用于判斷當(dāng)前是否啟用 VSYNC 機(jī)制,Android 在 4.1 之后默認(rèn)開啟該機(jī)制。

private static final boolean USE_VSYNC = SystemProperties.getBoolean(
“debug.choreographer.vsync”, true);

FrameDisplayEventReceiver 是 DisplayEventReceiver 的子類,DisplayEventReceiver 是一個(gè) abstract class。在 DisplayEventReceiver 的構(gòu)造方法會(huì)通過 JNI 創(chuàng)建一個(gè) IDisplayEventConnection 的 VSYNC 的監(jiān)聽者。

public DisplayEventReceiver(Looper looper, int vsyncSource) {
if (looper == null) {
throw new IllegalArgumentException(“l(fā)ooper must not be null”);
}

mMessageQueue = looper.getQueue();
// 注冊(cè)VSYNC信號(hào)監(jiān)聽者
mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue,
vsyncSource);

mCloseGuard.open(“dispose”);
}

另外 DisplayEventReceiver 內(nèi)還包括用于申請(qǐng) VSYNC 信號(hào)的 scheduledVsync 方法,

public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "

  • “receiver has already been disposed.”);
    } else {
    // 申請(qǐng)VSYNC中斷信號(hào)
    // 會(huì)回調(diào)onVsync方法
    nativeScheduleVsync(mReceiverPtr);
    }
    }

和用于接收 VSYNC 信號(hào)的 onVsync 方法。這樣,當(dāng)應(yīng)用需要繪制時(shí),通過 scheduledVsync 方法申請(qǐng) VSYNC 中斷,來自 EventThread 的 VSYNC 信號(hào)就可以傳遞到 Choreographer:

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// 該方法在其子類FrameDisplayEventReceiver中被重寫
// 目的是通知Choreographer
}

  1. CallbackQueue,用于保存通過 postCallback 添加的任務(wù)。目前一共定義了四種任務(wù)類型,它們分別是:
  • CALLBACK_INPUT:優(yōu)先級(jí)最高,和輸入事件處理有關(guān)。
  • CALLBACK_ANIMATION:優(yōu)先級(jí)其次,和 Animation 的處理有關(guān)
  • CALLBACK_TRAVERSAL:優(yōu)先級(jí)最低,和 UI 繪制任務(wù)有關(guān)
  • CALLBACK_COMMIT:最后執(zhí)行,和提交任務(wù)有關(guān)(在 API Level 23 添加)

優(yōu)先級(jí)的高低和處理順序有關(guān),每當(dāng)收到 VSYNC 信號(hào)時(shí),Choreographer 將首先處理 INPUT 類型的任務(wù),然后是 ANIMATION 類型,最后才是 TRAVERSAL 類型。

通過 Choreographer 添加的任務(wù)最后都被封裝成 CallbackRecord,同種任務(wù)之間按照時(shí)間順序以鏈表的形式保存在 CallbackQueue 內(nèi)。

private static final class CallbackRecord {
// 鏈表,指向下一個(gè)
public CallbackRecord next;
// 到期時(shí)間
public long dueTime;
// Runnable or FrameCallback
public Object action;

public Object token;

public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 通過postFrameCallback 或 postFrameCallbackDelayed
// 會(huì)執(zhí)行這里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}

CallbackQueue 是一個(gè)容量為 4 的數(shù)組,分別對(duì)應(yīng)不同的任務(wù)類型。


接下來,以 View 的繪制流程為例,從 ViewRootImpl 的 scheduleTraversals 方法開始,其內(nèi)部通過 Choreographer 的 postCallback 將繪制任務(wù)添加到 Chorographer。關(guān)于 View 繪制流程的詳細(xì)分析,可以參考《View 繪制流程之 DecorView 添加至窗口的過程》和《深入 Activity 三部曲(3)之 View 繪制流程》。

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 同步屏障,阻塞所有的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 注意mTraversaRunnable是一個(gè)Runnable對(duì)象
// 通過 Choreographer 發(fā)送繪制任務(wù)
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// …
}
}

Choreographer 是線程單例的,大家是否還記得 Android 系統(tǒng)的 Looper 對(duì)象也是 線程單例。主線程 Looper 是在 ActivityThread 的 main 方法被創(chuàng)建。如果要在子線程使用 Handler,必須先為其創(chuàng)建一個(gè) Looper 實(shí)例。

Choreographer 提供了兩種添加任務(wù)的方式,postCallback() 和 postFrameCallback(),當(dāng)然還有對(duì)應(yīng)的 delay 方法。

  • postCallback 對(duì)應(yīng)調(diào)用 postCallbackDelayed

public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException(“action must not be null”);
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException(“callbackType is invalid”);
}
// 最終都會(huì)調(diào)用到postCallbackDelayedInternal
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

  • postFrameCallback 對(duì)應(yīng)調(diào)用 postFrameCallbackDelayed

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException(“callback must not be null”);
}

//最終調(diào)用postCallbackDelayedInternal
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

postCallback 相比 postFrameCallback 更加靈活一些。

它們最終都會(huì)調(diào)用到 postCallbackDelayedInternal 方法:

private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {

synchronized (mLock) {
// 當(dāng)前時(shí)間
final long now = SystemClock.uptimeMillis();
// 加上延遲時(shí)間
final long dueTime = now + delayMillis;
// 根據(jù)任務(wù)類型添加到mCallbackQueues中
// VSYNC信號(hào)處理任務(wù)具有優(yōu)先級(jí)
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
//表示立即執(zhí)行,立即申請(qǐng)VSYNC信號(hào)
scheduleFrameLocked(now);
} else {
// 在指定時(shí)間運(yùn)行,最終仍然會(huì)調(diào)用scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
// 到時(shí)根據(jù)callbackType在mCallbackQueues中查找執(zhí)行
msg.arg1 = callbackType;
// 消息設(shè)置為異步
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

根據(jù)任務(wù)類型 callbackType 添加到對(duì)應(yīng)的 CallbackQueue 內(nèi),然后判斷任務(wù)是否有延遲,無延遲則立即執(zhí)行 scheduleFrameLocked 方法,否則發(fā)送定時(shí)消息到 FrameHandler,不過其最終還是調(diào)用到 scheduleFrameLocked 方法:

private void scheduleFrameLocked(long now) {
//mFrameScheduled默認(rèn)為false
if (!mFrameScheduled) {
mFrameScheduled = true;
// 判斷是否開啟VSYNC
if (USE_VSYNC) {
// 判斷是否在原線程
if (isRunningOnLooperThreadLocked()) {
//默認(rèn)會(huì)走這里
scheduleVsyncLocked();
} else {
// 否則不在原線程,發(fā)送消息到原線程
// 最后還是調(diào)用scheduleVsyncLocked方法
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 如果未開啟VSYNC則直接doFrame方法
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

注意 USE_VSYNC,如果系統(tǒng)未開啟 VSYNC 機(jī)制,此時(shí)直接發(fā)送 MSG_DO_FRAME 消息到 FrameHandler。注意查看上面貼出的 FrameHandler 代碼,此時(shí)直接執(zhí)行 doFrame 方法。

不過 Android 4.1 之后系統(tǒng)默認(rèn)開啟 VSYNC,還記得在 Choreographer 的構(gòu)造方法會(huì)創(chuàng)建一個(gè) FrameDisplayEventReceiver,scheduleVsyncLocked 方法將會(huì)通過它申請(qǐng) VSYNC 信號(hào)。

  • 這里注意 isRunningOnLooperThreadLocked 方法,其內(nèi)部根據(jù) Looper 判斷是否在原線程,否則發(fā)送消息到 FrameHandler。最終還是會(huì)調(diào)用 scheduleVsyncLocked 方法申請(qǐng) VSYNC 信號(hào)。

通過 FrameDisplayEventReceiver 申請(qǐng) VSYNC 信號(hào)的過程如下:

private void scheduleVsyncLocked() {
// 調(diào)用 FrameDisplayEventReceiver 的scheduleVsync
// 實(shí)際調(diào)用到其父類DisplayEventReceiver
mDisplayEventReceiver.scheduleVsync();
}

前面我們也有說過,申請(qǐng) VSYNC 信號(hào)實(shí)際是在其父類 DisplayEventReceiver。

public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "

  • “receiver has already been disposed.”);
    } else {
    // 申請(qǐng)VSYNC信號(hào)
    nativeScheduleVsync(mReceiverPtr);
    }
    }

接著看下 VSYNC 信號(hào)的接收方法 onVsync,該方法在其子類 FrameDisplayEventReceiver 中重寫:

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;

public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}

@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
// 忽略來自非主屏的VSYNC信號(hào)
scheduleVsync();
return;
}

// … 省略

if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "

  • “one at a time.”);
    } else {
    mHavePendingVsync = true;
    }

mTimestampNanos = timestampNanos;
mFrame = frame;
// 發(fā)送消息執(zhí)行doFrame
// 注意this,表示當(dāng)前Runnable
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

@Override
public void run() {
mHavePendingVsync = false;
// 回調(diào)這里,執(zhí)行doFrame方法
doFrame(mTimestampNanos, mFrame);
}
}

FrameDisplayEventReceiver 實(shí)現(xiàn)了 Runnable,將其作為 callback 發(fā)送到 FrameHandler,此時(shí) run 方法便得到執(zhí)行并且執(zhí)行 doFrame 方法:

void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
// 不是在執(zhí)行Frame任務(wù)直接return
return;
}

// … 省略

// 預(yù)期執(zhí)行時(shí)間
long intendedFrameTimeNanos = frameTimeNanos;
// 當(dāng)前時(shí)間
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
// 超時(shí)時(shí)間是否超過一幀的時(shí)間
if (jitterNanos >= mFrameIntervalNanos) {
// 計(jì)算掉幀數(shù)
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// 掉幀超過30幀打印Log提示
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
// 著名的掉幀Log
Log.i(TAG, "Skipped " + skippedFrames + " frames! "

  • “The application may be doing too much work on its main thread.”);
    }
    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;

frameTimeNanos = startNanos - lastFrameOffset;
}

if (frameTimeNanos < mLastFrameTimeNanos) {
// 未知原因,居然小于最后一幀的時(shí)間
// 重新申請(qǐng)VSYNC信號(hào)
scheduleVsyncLocked();
return;
}

if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}

mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// Frame標(biāo)志位恢復(fù)
mFrameScheduled = false;
// 記錄最后一幀時(shí)間
mLastFrameTimeNanos = frameTimeNanos;
}

try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, “Choreographer#doFrame”);
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

mFrameInfo.markInputHandlingStart();
// 先執(zhí)行CALLBACK_INPUT任務(wù)
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

mFrameInfo.markAnimationsStart();
// 再執(zhí)行CALLBACK_ANIMATION
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

mFrameInfo.markPerformTraversalsStart();
// 其次執(zhí)行CALLBACK_TRAVERSAL
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// API Level 23 之后加入,
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

  1. 注意第一個(gè) if 語句,不知道大家是否在自己項(xiàng)目的 Logcat 臺(tái)遇到過這樣一條日志:

Skipped (該值>=30) frames! The application may be doing too much work on its main thread

該 Log 用于提示開發(fā)人員當(dāng)前存在耗時(shí)的任務(wù)導(dǎo)致 UI 繪制掉幀超過 30 幀(≈ 16ms * 30 >= 480ms)。

  1. 注意看方法的最后,按照類型順序觸發(fā) doCallbacks 回調(diào)相關(guān)任務(wù)。

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

doCallbacks 方法將根據(jù)不同的任務(wù)類型依次執(zhí)行其 run 方法:

void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 根據(jù)指定的類型CallbackkQueue中查找到達(dá)執(zhí)行時(shí)間的CallbackRecord
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;

if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, “jitterNanos”, (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos

  • mFrameIntervalNanos;
    if (DEBUG_JANK) {
    Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
  • " ms which is more than twice the frame interval of "
  • (mFrameIntervalNanos * 0.000001f) + " ms! "
  • "Setting frame time to " + (lastFrameOffset * 0.000001f)
  • " ms in the past.");
    mDebugPrintNextFrameTimeDelta = true;
    }
    frameTimeNanos = now - lastFrameOffset;
    mLastFrameTimeNanos = frameTimeNanos;
    }
    }
    }
    try {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
    // 迭代執(zhí)行所有任務(wù)
    for (CallbackRecord c = callbacks; c != null; c = c.next) {
    // 回調(diào)CallbackRecord的run
    // 其內(nèi)部回調(diào)Callback的run
    c.run(frameTimeNanos);
    }
    } finally {
    synchronized (mLock) {
    mCallbacksRunning = false;
    do {
    final CallbackRecord next = callbacks.next;
    recycleCallbackLocked(callbacks);
    callbacks = next;
    } while (callbacks != null);
    }
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    }

注意遍歷 CallbackRecord 鏈表調(diào)用其 run 方法:

public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 通過postFrameCallback 或 postFrameCallbackDelayed
// 會(huì)執(zhí)行這里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}

注意 token == FRAME_CALLBACK_TOKEN 表示通過 postFrameCallback 添加的任務(wù)。這里就是按照 Callback 類型回調(diào)其 run 方法。

回到 ViewRootImpl 發(fā)起的繪制任務(wù),此時(shí) View 的繪制流程便開始了。

final class TraversalRunnable implements Runnable{

@Override
public void run(){
// View 的繪制任務(wù)開始
doTraversal();
}
}

至此 Choreographer 的工作流程就已經(jīng)分析清楚了,Choreographer 支持四種類型任務(wù):輸入、動(dòng)畫、繪制和提交,并配合系統(tǒng)的 VSYNC 進(jìn)行刷新、繪制等流程。確實(shí)做到了統(tǒng)一協(xié)調(diào)管理。

下面,再通過一張圖來加深對(duì) Choreographer 的工作流程的理解。


正如文章開頭介紹 Choreographer 可以配合系統(tǒng)的 VSYNC 信號(hào)完成 UI 的繪制任務(wù)。那我們便可以通過它來監(jiān)控應(yīng)用的幀率,雖然 Choreographer 內(nèi)部也實(shí)現(xiàn)了對(duì)掉幀的監(jiān)控,但是默認(rèn)只能監(jiān)控超過 30 幀及以上。

不過通過今天的分析,你是否也可以實(shí)現(xiàn)一個(gè)任意掉幀數(shù)的監(jiān)控呢?并且可以將其用于線上統(tǒng)計(jì),更好的幫助我們優(yōu)化應(yīng)用的渲染性能。


關(guān)于 UI 渲染所涉及的內(nèi)容非常多,文章最后也會(huì)附上一些擴(kuò)展資料,便于更好的學(xué)習(xí)理解。

文中如有不妥或有更好的分析結(jié)果,歡迎您的分享留言或指正。

文章如果對(duì)你有幫助,請(qǐng)留個(gè)贊吧。


擴(kuò)展閱讀
  • 關(guān)于 UI 渲染,你需要了解什么?
  • Android 之如何優(yōu)化 UI 渲染(上)
  • Android 之理解 VSYNC
  • Android 之 LayoutInflater 全面解析
  • Android 之你真的了解 View.post() 原理嗎?
  • Android 之 Choreographer 詳細(xì)分析

其他系列專題

  • Android 存儲(chǔ)優(yōu)化系列專題
  • Android 之權(quán)限管理只防君子不防
  • Android 之不要濫用 SharedPreferences(上)
  • Android 存儲(chǔ)選項(xiàng)之 SQLite 優(yōu)化那些事兒

作者:godliness
鏈接:https://www.jianshu.com/p/86d00bbdaf60
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

UI 優(yōu)化系列專題,來聊一聊 Android 渲染相關(guān)知識(shí),主要涉及 UI 渲染背景知識(shí)、如何優(yōu)化 UI 渲染兩部分內(nèi)容。


UI 優(yōu)化系列專題
  • UI 渲染背景知識(shí)

《View 繪制流程之 setContentView() 到底做了什么?》
《View 繪制流程之 DecorView 添加至窗口的過程》
《深入 Activity 三部曲(3)View 繪制流程》
《Android 之 LayoutInflater 全面解析》
《關(guān)于渲染,你需要了解什么?》
《Android 之 Choreographer 詳細(xì)分析》

  • 如何優(yōu)化 UI 渲染

《Android 之如何優(yōu)化 UI 渲染(上)》
《Android 之如何優(yōu)化 UI 渲染(下)》


Android 的 UI 渲染性能是 Google 長期以來非常重視的,基本每次 Google I/O 都會(huì)花很多篇幅講這一塊。不過隨著 Android 系統(tǒng)的不斷演進(jìn)和完善,時(shí)至今日,關(guān)于 Android UI 卡頓的話題也越來越少。

Google 在 2012 年的 I/O 大會(huì)上宣布了 Project Butter 計(jì)劃,那個(gè)曾經(jīng)嚴(yán)重影響 Android 口碑的 UI 流程性問題,首先在這得到有效的控制。并且在 Android 4.1 中正式開啟了這個(gè)機(jī)制。

Project Butter 主要包含三個(gè)組成部分:VSYNC、Triple Buffering 和今天要重點(diǎn)分析的 Choreographer。關(guān)于 Project Butter 的詳細(xì)介紹,你可以參考這里。

Choreographer

Choreographer 是 Android 4.1 新增的機(jī)制,用于配合系統(tǒng)的 VSYNC 中斷信號(hào)。它本質(zhì)是一個(gè) Java 類,如果直譯的話為舞蹈指導(dǎo),看到這個(gè)詞不得不贊嘆設(shè)計(jì)者除了 Coding 之外的廣泛視野。舞蹈是有節(jié)奏的,節(jié)奏使舞蹈的每個(gè)動(dòng)作更加協(xié)調(diào)和連貫;視圖刷新也是如此,Choreographer 可以接收系統(tǒng)的 VSYNC 信號(hào),統(tǒng)一管理應(yīng)用的輸入、動(dòng)畫和繪制等任務(wù)的執(zhí)行時(shí)機(jī)。業(yè)界一般通過它來監(jiān)控應(yīng)用的幀率。

我們先從 Choreographer 的構(gòu)造方法入手,看看 Choreographer 是如何協(xié)調(diào)任務(wù)的執(zhí)行。

private Choreographer(Looper looper, int vsyncSource) {
// 當(dāng)前線程的Looper
mLooper = looper;
// 使用該Looper創(chuàng)建FrameHandler
mHandler = new FrameHandler(looper);
// 是否開啟VSYNC,開啟VSYNC后將通過FrameDisplayEventReceiver接收VSYNC脈沖信號(hào)
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;

// 計(jì)算一幀的時(shí)間
// Android手機(jī)屏幕采用60Hz的刷新頻率
// 這里是納秒 ≈16000000ns 還是16ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 創(chuàng)建一個(gè)CallbackQueu的數(shù)組,默認(rèn)為4
// CallbackQueue中存放要執(zhí)行的輸入、動(dòng)畫、遍歷繪制等任務(wù)
// 也就是 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}

  1. Choreographer 的構(gòu)造方法被設(shè)計(jì)成私有,并且是線程單例的。只能通過其內(nèi)部的 getInstance 方法獲取當(dāng)前線程的 Choreographer 實(shí)例:

public static Choreographer getInstance() {
// Choreographer線程單例的實(shí)現(xiàn)方式
return sThreadInstance.get();
}

通過 ThreadLocal 實(shí)現(xiàn) Choreographer 的線程單例。

private static final ThreadLocal sThreadInstance =
new ThreadLocal() {
@Override
protected Choreographer initialValue() {
// 獲取當(dāng)前線程的Looper對(duì)象
Looper looper = Looper.myLooper();
if (looper == null) {
// 如果當(dāng)前線程未創(chuàng)建Looper對(duì)象則拋出異常
// 主線程(UI線程)的Looper默認(rèn)在ActivityThread的main方法被創(chuàng)建
throw new IllegalStateException(“The current thread must have a looper!”);
}
// 為當(dāng)前線程創(chuàng)建一個(gè)Choreographer對(duì)象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
// 如果是UI線程賦值給成員mMainInstance
mMainInstance = choreographer;
}
return choreographer;
}
};

  1. Choreographer 的構(gòu)造必須傳遞一個(gè) Looper 對(duì)象,其內(nèi)部會(huì)根據(jù)該 Looper 創(chuàng)建一個(gè) FrameHandler。Choreographer 的所有任務(wù)最終都會(huì)發(fā)送到該 Looper 所在的線程。

private final class FrameHandler extends Handler {

public FrameHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
// 執(zhí)行doFrame
// 如果啟用VSYNC機(jī)制,當(dāng)VSYNC信號(hào)到來時(shí)觸發(fā)
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
// 申請(qǐng)VSYNC信號(hào),例如當(dāng)前需要繪制任務(wù)時(shí)
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
// 需要延遲的任務(wù),最終還是執(zhí)行上述兩個(gè)事件
doScheduleCallback(msg.arg1);
break;
}
}
}

  1. 注意 USE_VSYNC,用于判斷當(dāng)前是否啟用 VSYNC 機(jī)制,Android 在 4.1 之后默認(rèn)開啟該機(jī)制。

private static final boolean USE_VSYNC = SystemProperties.getBoolean(
“debug.choreographer.vsync”, true);

FrameDisplayEventReceiver 是 DisplayEventReceiver 的子類,DisplayEventReceiver 是一個(gè) abstract class。在 DisplayEventReceiver 的構(gòu)造方法會(huì)通過 JNI 創(chuàng)建一個(gè) IDisplayEventConnection 的 VSYNC 的監(jiān)聽者。

public DisplayEventReceiver(Looper looper, int vsyncSource) {
if (looper == null) {
throw new IllegalArgumentException(“l(fā)ooper must not be null”);
}

mMessageQueue = looper.getQueue();
// 注冊(cè)VSYNC信號(hào)監(jiān)聽者
mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue,
vsyncSource);

mCloseGuard.open(“dispose”);
}

另外 DisplayEventReceiver 內(nèi)還包括用于申請(qǐng) VSYNC 信號(hào)的 scheduledVsync 方法,

public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "

  • “receiver has already been disposed.”);
    } else {
    // 申請(qǐng)VSYNC中斷信號(hào)
    // 會(huì)回調(diào)onVsync方法
    nativeScheduleVsync(mReceiverPtr);
    }
    }

和用于接收 VSYNC 信號(hào)的 onVsync 方法。這樣,當(dāng)應(yīng)用需要繪制時(shí),通過 scheduledVsync 方法申請(qǐng) VSYNC 中斷,來自 EventThread 的 VSYNC 信號(hào)就可以傳遞到 Choreographer:

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// 該方法在其子類FrameDisplayEventReceiver中被重寫
// 目的是通知Choreographer
}

  1. CallbackQueue,用于保存通過 postCallback 添加的任務(wù)。目前一共定義了四種任務(wù)類型,它們分別是:
  • CALLBACK_INPUT:優(yōu)先級(jí)最高,和輸入事件處理有關(guān)。
  • CALLBACK_ANIMATION:優(yōu)先級(jí)其次,和 Animation 的處理有關(guān)
  • CALLBACK_TRAVERSAL:優(yōu)先級(jí)最低,和 UI 繪制任務(wù)有關(guān)
  • CALLBACK_COMMIT:最后執(zhí)行,和提交任務(wù)有關(guān)(在 API Level 23 添加)

優(yōu)先級(jí)的高低和處理順序有關(guān),每當(dāng)收到 VSYNC 信號(hào)時(shí),Choreographer 將首先處理 INPUT 類型的任務(wù),然后是 ANIMATION 類型,最后才是 TRAVERSAL 類型。

通過 Choreographer 添加的任務(wù)最后都被封裝成 CallbackRecord,同種任務(wù)之間按照時(shí)間順序以鏈表的形式保存在 CallbackQueue 內(nèi)。

private static final class CallbackRecord {
// 鏈表,指向下一個(gè)
public CallbackRecord next;
// 到期時(shí)間
public long dueTime;
// Runnable or FrameCallback
public Object action;

public Object token;

public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 通過postFrameCallback 或 postFrameCallbackDelayed
// 會(huì)執(zhí)行這里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}

CallbackQueue 是一個(gè)容量為 4 的數(shù)組,分別對(duì)應(yīng)不同的任務(wù)類型。


接下來,以 View 的繪制流程為例,從 ViewRootImpl 的 scheduleTraversals 方法開始,其內(nèi)部通過 Choreographer 的 postCallback 將繪制任務(wù)添加到 Chorographer。關(guān)于 View 繪制流程的詳細(xì)分析,可以參考《View 繪制流程之 DecorView 添加至窗口的過程》和《深入 Activity 三部曲(3)之 View 繪制流程》。

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 同步屏障,阻塞所有的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 注意mTraversaRunnable是一個(gè)Runnable對(duì)象
// 通過 Choreographer 發(fā)送繪制任務(wù)
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// …
}
}

Choreographer 是線程單例的,大家是否還記得 Android 系統(tǒng)的 Looper 對(duì)象也是 線程單例。主線程 Looper 是在 ActivityThread 的 main 方法被創(chuàng)建。如果要在子線程使用 Handler,必須先為其創(chuàng)建一個(gè) Looper 實(shí)例。

Choreographer 提供了兩種添加任務(wù)的方式,postCallback() 和 postFrameCallback(),當(dāng)然還有對(duì)應(yīng)的 delay 方法。

  • postCallback 對(duì)應(yīng)調(diào)用 postCallbackDelayed

public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException(“action must not be null”);
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException(“callbackType is invalid”);
}
// 最終都會(huì)調(diào)用到postCallbackDelayedInternal
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

  • postFrameCallback 對(duì)應(yīng)調(diào)用 postFrameCallbackDelayed

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException(“callback must not be null”);
}

//最終調(diào)用postCallbackDelayedInternal
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

postCallback 相比 postFrameCallback 更加靈活一些。

它們最終都會(huì)調(diào)用到 postCallbackDelayedInternal 方法:

private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {

synchronized (mLock) {
// 當(dāng)前時(shí)間
final long now = SystemClock.uptimeMillis();
// 加上延遲時(shí)間
final long dueTime = now + delayMillis;
// 根據(jù)任務(wù)類型添加到mCallbackQueues中
// VSYNC信號(hào)處理任務(wù)具有優(yōu)先級(jí)
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
//表示立即執(zhí)行,立即申請(qǐng)VSYNC信號(hào)
scheduleFrameLocked(now);
} else {
// 在指定時(shí)間運(yùn)行,最終仍然會(huì)調(diào)用scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
// 到時(shí)根據(jù)callbackType在mCallbackQueues中查找執(zhí)行
msg.arg1 = callbackType;
// 消息設(shè)置為異步
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

根據(jù)任務(wù)類型 callbackType 添加到對(duì)應(yīng)的 CallbackQueue 內(nèi),然后判斷任務(wù)是否有延遲,無延遲則立即執(zhí)行 scheduleFrameLocked 方法,否則發(fā)送定時(shí)消息到 FrameHandler,不過其最終還是調(diào)用到 scheduleFrameLocked 方法:

private void scheduleFrameLocked(long now) {
//mFrameScheduled默認(rèn)為false
if (!mFrameScheduled) {
mFrameScheduled = true;
// 判斷是否開啟VSYNC
if (USE_VSYNC) {
// 判斷是否在原線程
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。

深知大多數(shù)初中級(jí)Android工程師,想要提升技能,往往是自己摸索成長或者是報(bào)班學(xué)習(xí),但對(duì)于培訓(xùn)機(jī)構(gòu)動(dòng)則近萬的學(xué)費(fèi),著實(shí)壓力不小。自己不成體系的自學(xué)效果低效又漫長,而且極易碰到天花板技術(shù)停滯不前!

因此收集整理了一份《2024年Android移動(dòng)開發(fā)全套學(xué)習(xí)資料》,初衷也很簡單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時(shí)減輕大家的負(fù)擔(dān)。

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上Android開發(fā)知識(shí)點(diǎn),真正體系化!

由于文件比較大,這里只是將部分目錄截圖出來,每個(gè)節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且會(huì)持續(xù)更新!

如果你覺得這些內(nèi)容對(duì)你有幫助,可以掃碼獲取?。。▊渥ⅲ篈ndroid)

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

總結(jié)

最后為了幫助大家深刻理解Android相關(guān)知識(shí)點(diǎn)的原理以及面試相關(guān)知識(shí),這里放上相關(guān)的我搜集整理的24套騰訊、字節(jié)跳動(dòng)、阿里、百度2019-2021面試真題解析,我把技術(shù)點(diǎn)整理成了視頻和PDF(實(shí)際上比預(yù)期多花了不少精力),包知識(shí)脈絡(luò) + 諸多細(xì)節(jié)。

還有?高級(jí)架構(gòu)技術(shù)進(jìn)階腦圖、Android開發(fā)面試專題資料?幫助大家學(xué)習(xí)提升進(jìn)階,也節(jié)省大家在網(wǎng)上搜索資料的時(shí)間來學(xué)習(xí),也可以分享給身邊好友一起學(xué)習(xí)。

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

網(wǎng)上學(xué)習(xí) Android的資料一大堆,但如果學(xué)到的知識(shí)不成體系,遇到問題時(shí)只是淺嘗輒止,不再深入研究,那么很難做到真正的技術(shù)提升。希望這份系統(tǒng)化的技術(shù)體系對(duì)大家有一個(gè)方向參考。

2021年雖然路途坎坷,都在說Android要沒落,但是,不要慌,做自己的計(jì)劃,學(xué)自己的習(xí),競爭無處不在,每個(gè)行業(yè)都是如此。相信自己,沒有做不到的,只有想不到的。祝大家2021年萬事大吉。

《互聯(lián)網(wǎng)大廠面試真題解析、進(jìn)階開發(fā)核心學(xué)習(xí)筆記、全套講解視頻、實(shí)戰(zhàn)項(xiàng)目源碼講義》點(diǎn)擊傳送門即可獲??!文章來源地址http://www.zghlxwxcb.cn/news/detail-850998.html

經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上Android開發(fā)知識(shí)點(diǎn),真正體系化!**

由于文件比較大,這里只是將部分目錄截圖出來,每個(gè)節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且會(huì)持續(xù)更新!

如果你覺得這些內(nèi)容對(duì)你有幫助,可以掃碼獲?。。。▊渥ⅲ篈ndroid)

Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏),程序員,android,架構(gòu),ui

總結(jié)

最后為了幫助大家深刻理解Android相關(guān)知識(shí)點(diǎn)的原理以及面試相關(guān)知識(shí),這里放上相關(guān)的我搜集整理的24套騰訊、字節(jié)跳動(dòng)、阿里、百度2019-2021面試真題解析,我把技術(shù)點(diǎn)整理成了視頻和PDF(實(shí)際上比預(yù)期多花了不少精力),包知識(shí)脈絡(luò) + 諸多細(xì)節(jié)。

還有?高級(jí)架構(gòu)技術(shù)進(jìn)階腦圖、Android開發(fā)面試專題資料?幫助大家學(xué)習(xí)提升進(jìn)階,也節(jié)省大家在網(wǎng)上搜索資料的時(shí)間來學(xué)習(xí),也可以分享給身邊好友一起學(xué)習(xí)。

[外鏈圖片轉(zhuǎn)存中…(img-ibNLraAZ-1712722856800)]

[外鏈圖片轉(zhuǎn)存中…(img-hTwUsn4i-1712722856800)]

[外鏈圖片轉(zhuǎn)存中…(img-phhUy3vr-1712722856800)]

網(wǎng)上學(xué)習(xí) Android的資料一大堆,但如果學(xué)到的知識(shí)不成體系,遇到問題時(shí)只是淺嘗輒止,不再深入研究,那么很難做到真正的技術(shù)提升。希望這份系統(tǒng)化的技術(shù)體系對(duì)大家有一個(gè)方向參考。

2021年雖然路途坎坷,都在說Android要沒落,但是,不要慌,做自己的計(jì)劃,學(xué)自己的習(xí),競爭無處不在,每個(gè)行業(yè)都是如此。相信自己,沒有做不到的,只有想不到的。祝大家2021年萬事大吉。

《互聯(lián)網(wǎng)大廠面試真題解析、進(jìn)階開發(fā)核心學(xué)習(xí)筆記、全套講解視頻、實(shí)戰(zhàn)項(xiàng)目源碼講義》點(diǎn)擊傳送門即可獲??!

到了這里,關(guān)于Android架構(gòu)進(jìn)階之高級(jí)UI系列(精編解析,值得收藏)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Android-高級(jí)-UI-進(jìn)階之路-(二)-深入理解-Android-8-0-View-觸摸事件分發(fā)機(jī)制,查漏補(bǔ)缺

    Android-高級(jí)-UI-進(jìn)階之路-(二)-深入理解-Android-8-0-View-觸摸事件分發(fā)機(jī)制,查漏補(bǔ)缺

    我們看到內(nèi)部又調(diào)用了父類 dispatchTouchEvent 方法, 所以最終是交給 ViewGroup 頂級(jí) View 來處理分發(fā)了。 頂級(jí) View 對(duì)點(diǎn)擊事件的分發(fā)過程 在上一小節(jié)中我們知道了一個(gè)事件的傳遞流程,這里我們就大致在回顧一下。首先點(diǎn)擊事件到達(dá)頂級(jí) ViewGroup 之后,會(huì)調(diào)用自身的 dispatchTouchE

    2024年04月14日
    瀏覽(39)
  • Canvas中的裁剪師講解與實(shí)戰(zhàn)——Android高級(jí)UI(1),Android體系化進(jìn)階學(xué)習(xí)圖譜

    Canvas中的裁剪師講解與實(shí)戰(zhàn)——Android高級(jí)UI(1),Android體系化進(jìn)階學(xué)習(xí)圖譜

    從今天開始我們聊一聊 Canvas 的API,因?yàn)镃anvas的API較多,所以我們分為幾次分享,首先分享的是裁剪類型的API使用。話不多說,先上實(shí)戰(zhàn)圖。 老夫的少女心 源碼地址文末會(huì)給出,了解原理才能更好地駕馭。 分享前,我們先來聊聊,在我們生活中如何繪制一張如下的圖。 我們

    2024年04月13日
    瀏覽(41)
  • Android-高級(jí)-UI-進(jìn)階之路-(五)-看完該篇文章-Canvas-你應(yīng)該會(huì)了

    Android-高級(jí)-UI-進(jìn)階之路-(五)-看完該篇文章-Canvas-你應(yīng)該會(huì)了

    /** 1. 繪制橢圓 */ canvas.drawOval(RectF(100f,500f,600f,800f),mPaint) /** 2. 繪制圓 */ mPaint.setColor(Color.YELLOW) mPaint.alpha = 100 canvas.drawCircle(400f,400f,200f,mPaint) 繪制 Bitmap // val bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.gild_3) //第二個(gè),第三個(gè)參數(shù)代表起點(diǎn)位置 canvas.drawBitmap(bitmap,100f,100

    2024年03月28日
    瀏覽(11)
  • Android UI—仿微信底部導(dǎo)航欄布局,吃透這份Android高級(jí)工程師面試497題解析

    Android UI—仿微信底部導(dǎo)航欄布局,吃透這份Android高級(jí)工程師面試497題解析

    android:layout_width=``\\\"match_parent\\\" android:layout_height=``\\\"match_parent\\\" android:orientation=``\\\"vertical\\\" TextView android:layout_height=``\\\"wrap_content\\\" android:layout_width=``\\\"wrap_content\\\" android:text=``\\\"微信\\\" android:textSize=``\\\"20sp\\\" / TextView android:layout_height=``\\\"wrap_content\\\" android:layout_width=``\\\"wrap_content\\\" android:text=``\\\"http://www

    2024年04月16日
    瀏覽(22)
  • Android-高級(jí)-UI-進(jìn)階之路-(七)-SVG-基礎(chǔ)使用-+-繪制中國地圖,Android面試中常問的MMAP到底是啥東東

    Android-高級(jí)-UI-進(jìn)階之路-(七)-SVG-基礎(chǔ)使用-+-繪制中國地圖,Android面試中常問的MMAP到底是啥東東

    iv.setImageDrawable(animatedVectorDrawable) val animatable = iv.drawable as Animatable animatable.start() } } 輸入搜索動(dòng)畫 利用在線繪制 SVG 圖標(biāo)網(wǎng)站 制作搜索圖標(biāo) 可以自己隨意搗鼓繪制,繪制好了之后點(diǎn)擊視圖-源代碼,將 SVG 代碼復(fù)制出來保存成 search_svg.xml 在線轉(zhuǎn)換 svg2vector 點(diǎn)擊空白或者直接將

    2024年04月25日
    瀏覽(28)
  • 22個(gè)值得收藏的android開源代碼-UI篇

    22個(gè)值得收藏的android開源代碼-UI篇

    http://jcodecraeer.com/a/opensource/2014/1016/1791.html FloatingActionButton =============================================================================== 一個(gè)類似Android版Google+浮動(dòng)功能按鈕的控件,可以響應(yīng)ListView的滾動(dòng)事件。當(dāng)列表向上滾動(dòng)的時(shí)候會(huì)自動(dòng)顯示,向下滾動(dòng)的時(shí)候自動(dòng)隱藏。 [外鏈圖片轉(zhuǎn)存中…

    2024年04月15日
    瀏覽(25)
  • 22個(gè)值得收藏的android開源代碼-UI篇(1)

    22個(gè)值得收藏的android開源代碼-UI篇(1)

    http://jcodecraeer.com/a/opensource/2014/1016/1791.html FloatingActionButton =============================================================================== 一個(gè)類似Android版Google+浮動(dòng)功能按鈕的控件,可以響應(yīng)ListView的滾動(dòng)事件。當(dāng)列表向上滾動(dòng)的時(shí)候會(huì)自動(dòng)顯示,向下滾動(dòng)的時(shí)候自動(dòng)隱藏。 http://jcodecraeer.co

    2024年04月10日
    瀏覽(23)
  • 面試跳槽原因怎么說,高級(jí)UI強(qiáng)行進(jìn)階,秀出天際

    面試跳槽原因怎么說,高級(jí)UI強(qiáng)行進(jìn)階,秀出天際

    lateinit var rotateAnimator: ObjectAnimator override fun onCreate(savedInstanceState: Bundle?) { … setContentView(demoBinding.root) rotateAnimator = ObjectAnimator.ofFloat(demoBinding.musicAvatar, View.ROTATION, 0f, 360f) rotateAnimator.duration = 6000 rotateAnimator.repeatCount = -1 rotateAnimator.interpolator = LinearInterpolator() lifecycleScope.launch(

    2024年04月11日
    瀏覽(24)
  • 靈魂畫師,Android繪制流程——Android高級(jí)UI(1)

    靈魂畫師,Android繪制流程——Android高級(jí)UI(1)

    繪制流程從何而起 Activity 的界面結(jié)構(gòu)在哪里開始形成 繪制流程如何運(yùn)轉(zhuǎn)起來 接下來我們就一個(gè)個(gè)目標(biāo)來 conquer。 我們一說到 繪制流程 ,就會(huì)想到或是聽過 onMeasure 、 onLayout 、 onDraw 這三個(gè)方法,但是有沒想過為什么我們開啟一個(gè)App或是點(diǎn)開一個(gè)Activity,就會(huì)觸發(fā)這一系列流

    2024年04月17日
    瀏覽(31)
  • Android 高級(jí)UI,面試高頻考點(diǎn)素材

    Android 高級(jí)UI,面試高頻考點(diǎn)素材

    標(biāo)簽可以刪減多余的層級(jí),它多用于替換FrameLayout或者當(dāng)一個(gè)布局包含另一個(gè)時(shí),消除視圖層次結(jié)構(gòu)中多余的視圖組 [html] ? view plain copy merge?xmlns:android=“http://schemas.android.com/apk/res/android” Button android:layout_width=“fill_parent” android:layout_height=“wrap_content” android:text=“@string/ad

    2024年04月12日
    瀏覽(36)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包