Android系统的编舞者Choreographer

2024-02-09 06:58

本文主要是介绍Android系统的编舞者Choreographer,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

个人博客地址 http://dandanlove.com/

前言

上一篇文章 Android的16ms和垂直同步以及三重缓存 解释了手机流畅性的问题,并在文章中提到了在Android4.1中添加的VsyncChoreographer机制,用于同Vsync机制配合,实现统一调度界面绘图。

Choreographer的构造

Choreographer是线程级别的单例,并且具有处理当前线程消息循环队列的功能。

public final class Choreographer {// Enable/disable vsync for animations and drawing.private static final boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);//单例public static Choreographer getInstance() {return sThreadInstance.get();}//每个线程一个Choreographer实例private static final ThreadLocal<Choreographer> sThreadInstance =new ThreadLocal<Choreographer>() {@Overrideprotected Choreographer initialValue() {Looper looper = Looper.myLooper();if (looper == null) {throw new IllegalStateException("The current thread must have a looper!");}return new Choreographer(looper);}};private Choreographer(Looper looper) {mLooper = looper;//创建handle对象,用于处理消息,其looper为当前的线程的消息队列mHandler = new FrameHandler(looper);//创建VSYNC的信号接受对象mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;//初始化上一次frame渲染的时间点mLastFrameTimeNanos = Long.MIN_VALUE;//计算帧率,也就是一帧所需的渲染时间,getRefreshRate是刷新率,一般是60mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());//创建消息处理队列mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];for (int i = 0; i <= CALLBACK_LAST; i++) {mCallbackQueues[i] = new CallbackQueue();}}
}

变量USE_VSYNC用于表示系统是否是用了Vsync同步机制,该值是通过读取系统属性debug.choreographer.vsync来获取的。如果系统使用了Vsync同步机制,则创建一个FrameDisplayEventReceiver对象用于请求并接收Vsync事件,最后Choreographer创建了一个大小为3的CallbackQueue队列数组,用于保存不同类型的Callback。

Choreographer的使用

注册Runnable对象

作者之前写过一篇关于ViewRootImpl的文章:ViewRootImpl的独白,我不是一个View(布局篇)里面有涉及使用Choreographer进行View的绘制,这次我们从ViewRootImpl的绘制出发来看看Choreographer的使用。

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {Choreographer mChoreographer;/***部分代码省略***/public ViewRootImpl(Context context, Display display) {/***部分代码省略***/mChoreographer = Choreographer.getInstance();/***部分代码省略***/}/***部分代码省略***/void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();}}
}

注册FrameCallback对象

无论是注册Runnable还是注册FrameCallback对象最终都会调用postCallbackDelayedInternal方法往mCallbackQueues添加回调,区别在于FrameCallback的token为FRAME_CALLBACK_TOKEN,两者在回调的时候不相同。

public final class Choreographer {// All frame callbacks posted by applications have this token.private static final Object FRAME_CALLBACK_TOKEN = new Object() {public String toString() { return "FRAME_CALLBACK_TOKEN"; }};private static final class CallbackRecord {public CallbackRecord next;public long dueTime;public Object action; // Runnable or FrameCallbackpublic Object token;public void run(long frameTimeNanos) {if (token == FRAME_CALLBACK_TOKEN) {((FrameCallback)action).doFrame(frameTimeNanos);} else {((Runnable)action).run();}}}
}

Choreographer的消息处理

Choreographer接受消息

public final class Choreographer {//Input callback.  Runs first.public static final int CALLBACK_INPUT = 0;//Animation callback.  Runs before traversals.public static final int CALLBACK_ANIMATION = 1;// Traversal callback.  Handles layout and draw.  //Runs last after all other asynchronous messages have been handled.public static final int CALLBACK_TRAVERSAL = 2;private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;//长度为3(CALLBACK_LAST+1)的CallbackQueue类型的数组private final CallbackQueue[] mCallbackQueues;//发送回调事件public void postCallback(int callbackType, Runnable action, Object token) {postCallbackDelayed(callbackType, action, token, 0);}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");}postCallbackDelayedInternal(callbackType, action, token, delayMillis);}private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {/***部分代码省略***/synchronized (mLock) {//从开机到现在的毫秒数(手机睡眠的时间不包括在内); final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;//添加类型为callbackType的CallbackQueue(将要执行的回调封装而成)mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//函数执行时间if (dueTime <= now) {//立即执行scheduleFrameLocked(now);} else {//异步回调延迟执行Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, dueTime);}}}/*** @param dueTime 任务开始时间* @param action 任务* @param token 标识*/private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {CallbackRecord callback = mCallbackPool;if (callback == null) {callback = new CallbackRecord();} else {mCallbackPool = callback.next;callback.next = null;}callback.dueTime = dueTime;callback.action = action;callback.token = token;return callback;}private final class CallbackQueue {private CallbackRecord mHead;public void addCallbackLocked(long dueTime, Object action, Object token) {CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);CallbackRecord entry = mHead;//判断当前的是否不头节点if (entry == null) {mHead = callback;return;}//判断当前任务出发起始时间是不是当前所有任务的最开始时间if (dueTime < entry.dueTime) {callback.next = entry;mHead = callback;return;}//根据任务开始时间由小到大插入到链表当中while (entry.next != null) {if (dueTime < entry.next.dueTime) {callback.next = entry.next;break;}entry = entry.next;}entry.next = callback;}}
}

CallbackQueue

public final class Choreographer {/*** Callback type: Input callback.  Runs first.* @hide*/public static final int CALLBACK_INPUT = 0;/*** Callback type: Animation callback.  Runs before traversals.* @hide*/public static final int CALLBACK_ANIMATION = 1;/*** Callback type: Traversal callback.  Handles layout and draw.  Runs* after all other asynchronous messages have been handled.* @hide*/public static final int CALLBACK_TRAVERSAL = 2;
}

三种类型不同的CallbackRecord链表,按照任务触发时间由小到大排列。

CallbackQueue.png

FrameHandler异步处理

public final class Choreographer {private static final int MSG_DO_FRAME = 0;private static final int MSG_DO_SCHEDULE_VSYNC = 1;private static final int MSG_DO_SCHEDULE_CALLBACK = 2;private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_DO_FRAME://刷新当前这一帧doFrame(System.nanoTime(), 0);break;case MSG_DO_SCHEDULE_VSYNC://做VSYNC的信号同步doScheduleVsync();break;case MSG_DO_SCHEDULE_CALLBACK://将当前任务加入执行队列doScheduleCallback(msg.arg1);break;}}}
}

doFrame

public final class Choreographer {void doFrame(long frameTimeNanos, int frame) {final long startNanos;synchronized (mLock) {if (!mFrameScheduled) {return; // no work to do}//当前时间startNanos = System.nanoTime();//抖动间隔final long jitterNanos = startNanos - frameTimeNanos;//抖动间隔大于屏幕刷新时间间隔(16ms)if (jitterNanos >= mFrameIntervalNanos) {final long skippedFrames = jitterNanos / mFrameIntervalNanos;//跳过了几帧!,也许当前应用在主线程做了太多的事情。if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {Log.i(TAG, "Skipped " + skippedFrames + " frames!  "+ "The application may be doing too much work on its main thread.");}//最后一次的屏幕刷是lastFrameOffset之前开始的final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;if (DEBUG) {Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "+ "which is more than the frame interval of "+ (mFrameIntervalNanos * 0.000001f) + " ms!  "+ "Skipping " + skippedFrames + " frames and setting frame "+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");}//最后一帧的刷新开始时间frameTimeNanos = startNanos - lastFrameOffset;}//由于跳帧可能造成了当前展现的是之前的帧,这样需要等待下一个vsync信号if (frameTimeNanos < mLastFrameTimeNanos) {if (DEBUG) {Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "+ "previously skipped frame.  Waiting for next vsync.");}scheduleVsyncLocked();return;}//当前画面刷新的状态置falsemFrameScheduled = false;//更新最后一帧的刷新时间mLastFrameTimeNanos = frameTimeNanos;}//按照优先级策略进行画面刷新时间处理doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);if (DEBUG) {final long endNanos = System.nanoTime();Log.d(TAG, "Frame " + frame + ": Finished, took "+ (endNanos - startNanos) * 0.000001f + " ms, latency "+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");}}
}

doScheduleVsync

public final class Choreographer {//等待vsync信号void doScheduleVsync() {synchronized (mLock) {if (mFrameScheduled) {scheduleVsyncLocked();}}}//当运行在Looper线程,则立刻调度vsyncprivate void scheduleVsyncLocked() {mDisplayEventReceiver.scheduleVsync();}
}

doScheduleCallback

public final class Choreographer {// Enable/disable vsync for animations and drawing.private static final boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);private final class CallbackQueue {//判断是否有能执行的任务public boolean hasDueCallbacksLocked(long now) {return mHead != null && mHead.dueTime <= now;}/***部分代码省略***/}/***部分代码省略***///执行任务回调void doScheduleCallback(int callbackType) {synchronized (mLock) {if (!mFrameScheduled) {final long now = SystemClock.uptimeMillis();//有能执行的任务if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {scheduleFrameLocked(now);}}}}private void scheduleFrameLocked(long now) {if (!mFrameScheduled) {mFrameScheduled = true;if (USE_VSYNC) {if (DEBUG) {Log.d(TAG, "Scheduling next frame on vsync.");}// If running on the Looper thread, then schedule the vsync immediately,// otherwise post a message to schedule the vsync from the UI thread// as soon as possible.if (isRunningOnLooperThreadLocked()) {//当运行在Looper线程,则立刻调度vsyncscheduleVsyncLocked();} else {//切换到主线程,调度vsyncMessage msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);msg.setAsynchronous(true);mHandler.sendMessageAtFrontOfQueue(msg);}} else {//如果没有VSYNC的同步,则发送消息刷新画面final long nextFrameTime = Math.max(mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);if (DEBUG) {Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");}Message msg = mHandler.obtainMessage(MSG_DO_FRAME);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, nextFrameTime);}}}//检测当前的Looper线程是不是主线程private boolean isRunningOnLooperThreadLocked() {return Looper.myLooper() == mLooper;}
}
public final class Choreographer {// The display event receiver can only be accessed by the looper thread to which// it is attached.  We take care to ensure that we post message to the looper// if appropriate when interacting with the display event receiver.private final FrameDisplayEventReceiver mDisplayEventReceiver;private Choreographer(Looper looper) {/***部分代码省略***///在Choreographer的构造函数中,我们使用USE_VSYNC则会有FrameDisplayEventReceiver做为与显示器时间进行交互mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;}/***部分代码省略***/private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {//构造函数需要传入当前的looper队列  public FrameDisplayEventReceiver(Looper looper) {super(looper);}/***部分代码省略***/  }
}
public abstract class DisplayEventReceiver {private static native void nativeScheduleVsync(long receiverPtr);/*** Creates a display event receiver.** @param looper The looper to use when invoking callbacks.*/public DisplayEventReceiver(Looper looper) {if (looper == null) {throw new IllegalArgumentException("looper must not be null");}mMessageQueue = looper.getQueue();//接受数量多少等于looper中消息的多少mReceiverPtr = nativeInit(this, mMessageQueue);mCloseGuard.open("dispose");}/*** Schedules a single vertical sync pulse to be delivered when the next* display frame begins.*/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 {nativeScheduleVsync(mReceiverPtr);}}
}

Choreographer流程汇总

choreographer.png

native端的消息处理

文件路径:frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp

NativeDisplayEventReceiver类结构

//NativeDisplayEventReceiver类的定义
class NativeDisplayEventReceiver : public LooperCallback {
public://对象公共方法//构造函数NativeDisplayEventReceiver(JNIEnv* env,jobject receiverObj, const sp<MessageQueue>& messageQueue);status_t initialize();  //初始化方法void dispose();status_t scheduleVsync();//获取下一个VSYNC信号protected:virtual ~NativeDisplayEventReceiver();//析构函数private:jobject mReceiverObjGlobal;//java层的DisplayEventReceiver的全局引用sp<MessageQueue> mMessageQueue;//looper的消息队列DisplayEventReceiver mReceiver;//frameworks/nivate/libs/gui/DisplayEventReceiver.cppbool mWaitingForVsync;//默认为falsevirtual int handleEvent(int receiveFd, int events, void* data);bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
};//ststem/core/include/utils/Looper.h
/*** A looper callback.*/
//NativeDisplayEventReceiver的父类,用与looper中消息的回调
class LooperCallback : public virtual RefBase {
protected:virtual ~LooperCallback() { }public:virtual int handleEvent(int fd, int events, void* data) = 0;
};

NativeDisplayEventReceiver初始化

//初始化native的消息队列
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj,jobject messageQueueObj) {sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == NULL) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}//构造NativeDisplayEventReceiver对象sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,receiverObj, messageQueue);status_t status = receiver->initialize();if (status) {String8 message;message.appendFormat("Failed to initialize display event receiver.  status=%d", status);jniThrowRuntimeException(env, message.string());return 0;}receiver->incStrong(gDisplayEventReceiverClassInfo.clazz); // retain a reference for the objectreturn reinterpret_cast<jlong>(receiver.get());
}
//NativeDisplayEventReceiver的构造函数
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,jobject receiverObj, const sp<MessageQueue>& messageQueue) :mReceiverObjGlobal(env->NewGlobalRef(receiverObj)),mMessageQueue(messageQueue), mWaitingForVsync(false) {ALOGV("receiver %p ~ Initializing input event receiver.", this);
}
//receiver内部数据的初始化
status_t NativeDisplayEventReceiver::initialize() {status_t result = mReceiver.initCheck();if (result) {ALOGW("Failed to initialize display event receiver, status=%d", result);return result;}//监听mReceiver的所获取的文件句柄。int rc = mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,this, NULL);if (rc < 0) {return UNKNOWN_ERROR;}return OK;
}

NativeDisplayEventReceiver请求VSYNC的同步

//java层调用DisplayEventReceiver的scheduleVsync请求VSYNC的同步
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {sp<NativeDisplayEventReceiver> receiver =reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);status_t status = receiver->scheduleVsync();if (status) {String8 message;message.appendFormat("Failed to schedule next vertical sync pulse.  status=%d", status);jniThrowRuntimeException(env, message.string());}
}status_t NativeDisplayEventReceiver::scheduleVsync() {if (!mWaitingForVsync) {ALOGV("receiver %p ~ Scheduling vsync.", this);// Drain all pending events.nsecs_t vsyncTimestamp;int32_t vsyncDisplayId;uint32_t vsyncCount;processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount);//请求下一次Vsync信息处理status_t status = mReceiver.requestNextVsync();if (status) {ALOGW("Failed to request next vsync, status=%d", status);return status;}mWaitingForVsync = true;}return OK;
}//frameworks/native/libs/gui/DisplayEventReceiver.cpp
//通过IDisplayEventConnection接口来请求Vsync信号,IDisplayEventConnection实现了Binder通信框架,可以跨进程调用。
//因为Vsync信号请求进程和Vsync产生进程有可能不在同一个进程空间,因此这里就借助IDisplayEventConnection接口来实现。
status_t DisplayEventReceiver::requestNextVsync() {if (mEventConnection != NULL) {mEventConnection->requestNextVsync();return NO_ERROR;}return NO_INIT;
}

NativeDisplayEventReceiver处理消息

//NativeDisplayEventReceiver处理消息
int NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, void* data) {...nsecs_t vsyncTimestamp;int32_t vsyncDisplayId;uint32_t vsyncCount;//过滤出最后一次的vsyncif (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {mWaitingForVsync = false;//分发Vsync,调用到native的android/view/DisplayEventReceiver.class的dispatchVsync方法dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);}return 1;
}

DisplayEventReceiver分发VSYNC信号

public abstract class DisplayEventReceiver {/***部分代码省略***/public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {}// Called from native code.@SuppressWarnings("unused")private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {onVsync(timestampNanos, builtInDisplayId, frame);}
}private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;/***部分代码省略***/@Overridepublic void onVsync(long timestampNanos, int builtInDisplayId, int frame) {//忽略来自第二显示屏的Vsyncif (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {scheduleVsync();return;}/***部分代码省略***/mTimestampNanos = timestampNanos;mFrame = frame;//该消息的callback为当前对象FrameDisplayEventReceiverMessage msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}@Overridepublic void run() {mHavePendingVsync = false;//DisplayEventReceiver消息处理doFrame(mTimestampNanos, mFrame);}
}

DisplayEventReceiver消息处理

参见4.2.1、doFrame介绍

Choreographer处理回调

Choreographer触发可执行任务的回调

这里为什么说可执行任务呢?因为每个任务都有自己的触发时间,Choreographer只选择它能触发的任务。

public final class Choreographer {//进行回调的标识private boolean mCallbacksRunning;/***部分代码省略***/void doCallbacks(int callbackType, long frameTimeNanos) {CallbackRecord callbacks;synchronized (mLock) {final long now = SystemClock.uptimeMillis();//找到当前能触发的回调链表callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);if (callbacks == null) {return;}mCallbacksRunning = true;}try {for (CallbackRecord c = callbacks; c != null; c = c.next) {//循环遍历,回调所有的任务c.run(frameTimeNanos);}} finally {synchronized (mLock) {mCallbacksRunning = false;do {final CallbackRecord next = callbacks.next;recycleCallbackLocked(callbacks);callbacks = next;} while (callbacks != null);}}}//回收回调任务资源private void recycleCallbackLocked(CallbackRecord callback) {callback.action = null;callback.token = null;callback.next = mCallbackPool;mCallbackPool = callback;}private final class CallbackQueue {public CallbackRecord extractDueCallbacksLocked(long now) {CallbackRecord callbacks = mHead;//当链表头部的任务触发事件都比当前时间晚,那么整个链表则没有任务需要触发if (callbacks == null || callbacks.dueTime > now) {return null;}CallbackRecord last = callbacks;CallbackRecord next = last.next;//找到当前时间之前需要触发任务链表,将该链表截断并返回while (next != null) {if (next.dueTime > now) {last.next = null;break;}last = next;next = next.next;}//mHead重置为原始链表截断的头部mHead = next;return callbacks;}}
}

处理Choreographer回调

3、Choreographer的使用部分讲述了ViewRootImpl使用Choreographer的使用,那么我们现在来看一下ViewRootImplChoreographer回调时间的处理。

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {Choreographer mChoreographer;/***部分代码省略***/public ViewRootImpl(Context context, Display display) {/***部分代码省略***/mChoreographer = Choreographer.getInstance();/***部分代码省略***/}/***部分代码省略***/void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();}}final class TraversalRunnable implements Runnable {@Overridepublic void run() {//开始View的测量、布局、绘制doTraversal();}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
}

总结

整片文章单独看起来留下的印象不是很深刻,以前阅读过 Android的16ms和垂直同步以及三重缓存 这篇文章之后就会知道本文章是对 Android的16ms和垂直同步以及三重缓存 这篇文章其中的一些疑问进行解答。从代码的角度讲述了android的屏幕绘制部分知识。

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦

想阅读作者的更多文章,可以查看我 个人博客 和公共号:
振兴书城

这篇关于Android系统的编舞者Choreographer的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/693462

相关文章

Linux查询服务器系统版本号的多种方法

《Linux查询服务器系统版本号的多种方法》在Linux系统管理和维护工作中,了解当前操作系统的版本信息是最基础也是最重要的操作之一,系统版本不仅关系到软件兼容性、安全更新策略,还直接影响到故障排查和... 目录一、引言:系统版本查询的重要性二、基础命令解析:cat /etc/Centos-release详

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

更改linux系统的默认Python版本方式

《更改linux系统的默认Python版本方式》通过删除原Python软链接并创建指向python3.6的新链接,可切换系统默认Python版本,需注意版本冲突、环境混乱及维护问题,建议使用pyenv... 目录更改系统的默认python版本软链接软链接的特点创建软链接的命令使用场景注意事项总结更改系统的默

在Linux系统上连接GitHub的方法步骤(适用2025年)

《在Linux系统上连接GitHub的方法步骤(适用2025年)》在2025年,使用Linux系统连接GitHub的推荐方式是通过SSH(SecureShell)协议进行身份验证,这种方式不仅安全,还... 目录步骤一:检查并安装 Git步骤二:生成 SSH 密钥步骤三:将 SSH 公钥添加到 github

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon