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

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识