2024年最全深入研究源码:DispSync详解(1),字节跳动校招hr面

最后

分享一份NDK基础开发资料

详解:Linux网络虚拟化技术

分享内容包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// dispatch events to listeners…
const size_t count = signalConnections.size();
for (size_t i = 0; i < count; i++) {
const sp& conn(signalConnections[i]);
// now see if we still need to report this event
status_t err = conn->postEvent(event);

}
}

threadMain 的主要工作是调用 waitForEventLocked 等待一个 Event,然后在一个个地通知 signalConnections。至于这个 EventsignalConnections 分别是什么,后面会具体描述。现在先来看一下 waitForEventLocked 的逻辑:

// This will return when (1) a vsync event has been received, and (2) there was
// at least one connection interested in receiving it when we started waiting.
Vector<spEventThread::Connection > EventThread::waitForEventLocked(
std::unique_lockstd::mutex* lock, DisplayEventReceiver::Event* event) {
Vector<spEventThread::Connection > signalConnections;

while (signalConnections.isEmpty() && mKeepRunning) {
bool eventPending = false;
bool waitForVSync = false;

size_t vsyncCount = 0;
nsecs_t timestamp = 0;
// 在前面 EventThread 的构造函数里面已经把 mVSyncEvent 数组内的所有 timestamp 都置为 0
// 因此在第一次初始化的时候,这个循环会直接退出
for (int32_t i = 0; i < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES; i++) {

}

// 第一次初始化的时候 mDisplayEventConnections 的数组也为空,count 为 0
size_t count = mDisplayEventConnections.size();
if (!timestamp && count) {

}

// 第一次初始化不执行这个循环
for (size_t i = 0; i < count;) {

}

// timestamp 为 0, waitForVSync 为 false
if (timestamp && !waitForVSync) {

} else if (!timestamp && waitForVSync) {

}

// eventPending 为 false,符合条件
if (!timestamp && !eventPending) {
if (waitForVSync) {

// waitForVSync 为 false,进入 else
} else {
// 最终,在第一次初始化的时候,EventThread 就阻塞在这里了
mCondition.wait(*lock);
}
}
}


}

好,到这里,SurfaceFlinger 创建的两个 EventThread 都会阻塞在上面代码提到的地方,SurfaceFlinger 的初始化继续执行。

补充:SurfaceFlinger 的启动

首先说明一下 mEventQueue 是在哪里被初始化的。是在 SurfaceFlinger 的另一个方法:

提到这里就需要 SurfaceFlinger 是怎么启动和初始化的。SurfaceFlinger 作为系统最基本最核心的服务之一,是通过 init.rc 的方式进行启动的(内容在 frameworks/native/services/surfaceflinger/surfaceflinger.rc):

service surfaceflinger /system/bin/surfaceflinger
class core animation
user system
group graphics drmrpc readproc input
onrestart restart zygote

然后就需要提到 SurfaceFlinger 的组成部分,init.rc 里面提到的 /system/bin/surfaceflinger 这个二进制文件,由 main_surfaceflinger.cpp 这个文件编译得到;而上面提到 DispSync,EventThread 等,都被编译到了 libsurfaceflinger.so 这个库。这也给了我们一个启示:当我们在自己调试 SurfaceFlinger 的时候,大部分时间都只需要重新编译 libsurfaceflinger.so 这个文件即可

回来简单看一下 SurfaceFlinger 是如何启动的,来看看 main_surfaceflinger.cpp

int main(int, char **) {

sp flinger = DisplayUtils::getInstance()->getSFInstance();

flinger->init();

这里的重点就是这个 sp<SurfaceFlinger>,当被 sp 指针引用的时候,会触发 onFirstRef() 函数:

void SurfaceFlinger::onFirstRef()
{
mEventQueue->init(this);
}

这样,就走到了 MessageQueue 部分了:

MessageQueue

接着 EventThread,然后就执行到这里:

void SurfaceFlinger::init() {

mEventQueue->setEventThread(mSFEventThread.get());

mEventQueue 在前面的 SurfaceFlinger::onFirstRef() 中完成了初始化:

void MessageQueue::init(const sp& flinger) {
mFlinger = flinger;
mLooper = new Looper(true);
mHandler = new Handler(*this);
}

接着来看一下很重要的 setEventThread()

void MessageQueue::setEventThread(android::EventThread* eventThread) {
if (mEventThread == eventThread) {
return;
}

if (mEventTube.getFd() >= 0) {
mLooper->removeFd(mEventTube.getFd());
}

mEventThread = eventThread;
mEvents = eventThread->createEventConnection();
mEvents->stealReceiveChannel(&mEventTube);
mLooper->addFd(mEventTube.getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver,
this);
}

重点来了,前面创建的 SurfaceFlinger 的 EventThread 被作为参数传给了 setEventThread,并且执行了 EventThread 的 createEventConnection()。(注意,需要时时刻刻地记住,现在处理的 SurfaceFlinger 的 EventThread

(后面为了方便,将使用 sfEventThread 指代 SurfaceFlinger 的 EventThread;使用 appEventThread 指代 app 的 EventThread)

EventThread::Connection

sp EventThread::createEventConnection() const {
return new Connection(const_cast<EventThread*>(this));
}

在这里,sfEventThread 迎来了第一个(同时也是唯一的) Connection:

EventThread::Connection::Connection(EventThread* eventThread)
count(-1), mEventThread(eventThread), mChannel(gui::BitTube::DefaultSize) {}

void EventThread::Connection::onFirstRef() {
// NOTE: mEventThread doesn’t hold a strong reference on us
mEventThread->registerDisplayEventConnection(this);
}

status_t EventThread::registerDisplayEventConnection(
const spEventThread::Connection& connection) {
std::lock_guardstd::mutex lock(mMutex);
mDisplayEventConnections.add(connection);
mCondition.notify_all();
return NO_ERROR;
}

MessageQueue 调用 sfEventThread 的 createEventConnection 创建一个 Connection。由于 sp 指针的作用,将会调用 Connection::onFirstRef,最终这个 Connection 会被添加到 mDisplayEventConnections 并且唤醒在 EventThread - 01 中阻塞的线程。

EventThread-02

在前面把 EventThread 唤醒后,由于 signalConnections 为空,继续循环。然后由于新加入的 Connection count 为 -1,所以这个 EventThread 会继续阻塞,不过此时 mDisplayEventConnections 里面已经有一个 Connection 了。接着看下去。

EventControlThread-01

SurfaceFlinger::init() 接着运行到这里:

void SurfaceFlinger::init() {

mEventControlThread = std::make_uniqueimpl::EventControlThread(
[this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });

主要提一下的是,这个传进来的参数是一个 Lambda 表达式,具体的语法不讲。稍微解释一下这里传进来的 Lambda 表达式的意义就是,捕获列表为 SurfaceFlinger 本身,接受一个布尔参数,当这个 Lamda 表达式被调用的时候,会调用 SurfaceFlinger::setVsyncEnabled() 这个函数,这个函数后面会提到,也是一个很重要的函数。

EventControlThread 的构造函数的主要内容也是启动一个线程:

EventControlThread::EventControlThread(EventControlThread::SetVSyncEnabledFunction function)
mSetVSyncEnabled(function) {
pthread_setname_np(mThread.native_handle(), “EventControlThread”);

pid_t tid = pthread_gettid_np(mThread.native_handle());
setpriority(PRIO_PROCESS, tid, ANDROID_PRIORITY_URGENT_DISPLAY);
set_sched_policy(tid, SP_FOREGROUND);
}

void EventControlThread::threadMain() NO_THREAD_SAFETY_ANALYSIS {
auto keepRunning = true;
auto currentVsyncEnabled = false;

while (keepRunning) {
mSetVSyncEnabled(currentVsyncEnabled);

std::unique_lockstd::mutex lock(mMutex);
// keepRunning 为 true,currentVsyncEnabled 为 false,mVsyncEnabled 默认值为 false,mKeepRunning 默认值为 true,因此 Lambda 表达式为 false,线程阻塞
mCondition.wait(lock, this, currentVsyncEnabled, keepRunning NO_THREAD_SAFETY_ANALYSIS {
return currentVsyncEnabled != mVsyncEnabled || keepRunning != mKeepRunning;
});
currentVsyncEnabled = mVsyncEnabled;
keepRunning = mKeepRunning;
}
}

此时,EventControlThread 也会陷入阻塞之中。而 SurfaceFlinger 也将迎来初始化中最为复杂的一步。

唤醒所有线程

至此,SurfaceFlinger 总共起了四个线程 —— DispSyncThread,两个 EvenThread 和 EventControlThread,并且这四个线程全都处于阻塞状态。导致这些线程处于阻塞状态的原因是:

  • DispSyncThread: mPeriod 为 0
  • EventThread: Connection->count 为 -1
  • EventControlThread: mVsyncEnabled 为 false

然后让我们一个个将其唤醒。

EventThread-03

接下来的 SurfaceFlinger 会进行非常复杂的初始化操作,EventThread 唤醒相关的调用流程如下(这里借用了这位大佬《Android SurfaceFlinger SW Vsync模型》的内容,写得非常棒,在学习的过程中能够得到了很大的启发):

initializeDisplays();
flinger->onInitializeDisplays();
setTransactionState(state, displays, 0);
setTransactionFlags(transactionFlags);
signalTransaction();
mEventQueue->invalidate();
mEvents->requestNextVsync() //mEvents是Connection实例
EventThread->requestNextVsync(this);

void EventThread::requestNextVsync(const spEventThread::Connection& connection) {

if (connection->count < 0) {
connection->count = 0;
mCondition.notify_all();
}
}

在这里把前面创建的那个 Connection 的 count 置为 0,并且唤醒阻塞的 EventThread,这个时候,mDisplayEventConnections 不为空并且 count 不为 -1,可以正常地运行了,EventThread::waitForEventLocked() 走到了这里:

} else if (!timestamp && waitForVSync) {
// we have at least one client, so we want vsync enabled
// (TODO: this function is called right after we finish
// notifying clients of a vsync, so this call will be made
// at the vsync rate, e.g. 60fps. If we can accurately
// track the current state we could avoid making this call
// so often.)
enableVSyncLocked();
}

void EventThread::enableVSyncLocked() {
// 一般都为 false
if (!mUseSoftwareVSync) {
// never enable h/w VSYNC when screen is off
if (!mVsyncEnabled) {
mVsyncEnabled = true;
mVSyncSource->setCallback(this);
mVSyncSource->setVSyncEnabled(true);
}
}
mDebugVsyncEnabled = true;
}

调用了 DispSyncSource::setCallback()将 EventThread 和 DispSyncSource 联系在了一起

void setCallback(VSyncSource::Callback* callback) override{
Mutex::Autolock lock(mCallbackMutex);
mCallback = callback;
}

接着调用 DispSyncSource::setVSyncEnabled

void setVSyncEnabled(bool enable) override {
Mutex::Autolock lock(mVsyncMutex);
// true
if (enable) {
status_t err = mDispSync->addEventListener(mName, mPhaseOffset,
static_castDispSync::Callback*(this));

}

最终调用了 DispSync::addEventListener

status_t addEventListener(const char* name, nsecs_t phase, DispSync::Callback* callback) {
if (kTraceDetailedInfo) ATRACE_CALL();
Mutex::Autolock lock(mMutex);

// 保证了 mEventListeners 的唯一性
for (size_t i = 0; i < mEventListeners.size(); i++) {
if (mEventListeners[i].mCallback == callback) {
return BAD_VALUE;
}
}

EventListener listener;
listener.mName = name;
listener.mPhase = phase;
listener.mCallback = callback;

listener.mLastEventTime = systemTime() - mPeriod / 2 + mPhase - mWakeupLatency;

mEventListeners.push(listener);

// 唤醒 DispSyncThread
mCond.signal();

return NO_ERROR;
}

把 DispSyncSource 加到 mEventListeners,将 DispSync 和 DispSyncSource 联系在了一起,并且把前面阻塞的 DispSyncThread 唤醒,但是由于 mPeriod 还是为 0,因此 DispSyncThread 还是会继续阻塞。

不过此时从调用关系已经初步可以看到前面我说的那句 DispSyncSource 是 DispSync 和 EventThread 的中间人 是正确的了。

接着来看 DispSyncThread。

DispSync 和 DispSyncThread-02

设置 mPeriod 的流程如下(依旧引用了这位大佬的《Android SurfaceFlinger SW Vsync模型》的内容,再次感谢):

initializeDisplays();
flinger->onInitializeDisplays();
setPowerModeInternal()
resyncToHardwareVsync(true);
repaintEverything();

这里把 SurfaceFlinger::resyncToHardwareVsync() 分为两部分,先看上部分:

void SurfaceFlinger::resyncToHardwareVsync(bool makeAvailable) {
Mutex::Autolock _l(mHWVsyncLock);

if (makeAvailable) {
mHWVsyncAvailable = true;
} else if (!mHWVsyncAvailable) {
// Hardware vsync is not currently available, so abort the resync
// attempt for now
return;
}

const auto& activeConfig = getBE().mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY);
const nsecs_t period = activeConfig->getVsyncPeriod();

mPrimaryDispSync.reset();
// 设置 mPeriod
mPrimaryDispSync.setPeriod(period);

// 默认为 false
if (!mPrimaryHWVsyncEnabled) {
mPrimaryDispSync.beginResync();
// 上部分结束

DispSync::setPeriod() 里面给 mPeriod 赋值,并且把 DispSyncThread 唤醒:

void DispSync::setPeriod(nsecs_t period) {
Mutex::Autolock lock(mMutex);
mPeriod = period;
mPhase = 0;
mReferenceTime = 0;
mThread->updateModel(mPeriod, mPhase, mReferenceTime);
}

void updateModel(nsecs_t period, nsecs_t phase, nsecs_t referenceTime) {
if (kTraceDetailedInfo) ATRACE_CALL();
Mutex::Autolock lock(mMutex);
mPeriod = period;
mPhase = phase;
mReferenceTime = referenceTime;
ALOGV(“[%s] updateModel: mPeriod = %” PRId64 “, mPhase = %” PRId64
" mReferenceTime = %" PRId64,
mName, ns2us(mPeriod), ns2us(mPhase), ns2us(mReferenceTime));
// 这里把 DispSyncThread 唤醒
mCond.signal();
}

至此,DispSyncThread 也开始运转。

EventControlThread-02

接着看 SurfaceFlinger::resyncToHardwareVsync() 的下半部分:


mEventControlThread->setVsyncEnabled(true);
mPrimaryHWVsyncEnabled = true;
}
}

void EventControlThread::setVsyncEnabled(bool enabled) {
std::lock_guardstd::mutex lock(mMutex);
mVsyncEnabled = enabled;
// 把 EventControlThread 唤醒
mCondition.notify_all();
}

把 EventControlThread 唤醒以后,会重新把 SurfaceFlinger 传进来的那个被 Lambda 表达式包裹的 SurfaceFlinger::setVsyncEnabled() 重新执行一下:

void SurfaceFlinger::setVsyncEnabled(int disp, int enabled) {
ATRACE_CALL();
Mutex::Autolock lock(mStateLock);
getHwComposer().setVsyncEnabled(disp,
enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable);
}

void HWComposer::setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled) {
if (displayId < 0 || displayId >= HWC_DISPLAY_VIRTUAL) {
ALOGD(“setVsyncEnabled: Ignoring for virtual display %d”, displayId);
return;
}

RETURN_IF_INVALID_DISPLAY(displayId);

// NOTE: we use our own internal lock here because we have to call
// into the HWC with the lock held, and we want to make sure
// that even if HWC blocks (which it shouldn’t), it won’t
// affect other threads.
Mutex::Autolock _l(mVsyncLock);
auto& displayData = mDisplayData[displayId];
if (enabled != displayData.vsyncEnabled) {
ATRACE_CALL();
auto error = displayData.hwcDisplay->setVsyncEnabled(enabled);
RETURN_IF_HWC_ERROR(error, displayId);

displayData.vsyncEnabled = enabled;

char tag[16];
snprintf(tag, sizeof(tag), “HW_VSYNC_ON_%1u”, displayId);
// 在 systrace 看到的就是在这里
ATRACE_INT(tag, enabled == HWC2::Vsync::Enable ? 1 : 0);
}
}

在这里,真正地去开启 HW-VSync。然后由于 SurfaceFlinger 接收了 HW-VSync,然后辗转发给 DispSync,DispSync 接收,校正 SW-VSYNC。而整个 DispSync SurfaceFlinger 部分的初始化的流程也最终完成。

注意,上面说的是 SurfaceFlinger 部分。前面提到,总共有两个 EventThread,而上面分析的都是 sfEventThread,下面简单地描述一下 appEventThread 的流程,其实 EventThread 到 DispSync 这部分都是一致的,只是 EventThread 的 Connection 的注册流程不一样。sfEventThread 是 MessageQueue 去注册 Connection,而 appEventThread 则是另一种方法。

appEventThread

SurfaceFlinger 接收 VSYNC 是为了合成,因此 sfEventThread 的 Connection 只有一个,就是 SurfaceFlinger 本身;而 app 接收 VSYNC 是为了画帧,appEventThread 会有很多很多个 Connection。

app 本身是如何在 appEventThread 注册一个 Connection 的,与这篇文章的主体有点偏移,这个可以另开一篇文章来详细说明,流程也是非常复杂,这里只简单地描述:核心就是 libgui 下面的 DisplayEventReceiver,它在初始化的时候会调用 SurfaceFlinger::createEventConnection

sp SurfaceFlinger::createDisplayEventConnection(
ISurfaceComposer::VsyncSource vsyncSource) {
if (vsyncSource == eVsyncSourceSurfaceFlinger) {
return mSFEventThread->createEventConnection();
} else {
return mEventThread->createEventConnection();
}
}

然后后面的流程就跟前面的一致了。

小结

通过上面的描述,依据各个类的依赖关系,其实可以总结出这么一个图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意箭头方向。

运作流程

前面提到,引入 DispSync 的目的是为了通过 SF-VSYNC 来模拟 HW-VSYNC 的行为并且通过加入 offset 来让通知时机变得灵活。因此理解整个 DispSync 的流程就可以归结为下面几个部分:SF-VSYNC 通知周期 mPeriod 的计算;SF-VSYNC 的模拟方式以及 SF-VSYNC 传递流程,分别来看。

mPeriod 计算逻辑

前面提到,DispSync 通过接收 HW-VSYNC 并且更新计算出 SW-VSYNC 间隔—— mPeriod,首先看一下 DispSync 是如何收到 HW-VSYNC。

先看一下 SurfaceFlinger 这个类:

class SurfaceFlinger : public BnSurfaceComposer,
public PriorityDumper,
private IBinder::DeathRecipient,
private HWC2::ComposerCallback

SurfaceFlinger 实现了 HW2::ComposerCallback 的接口,然后当 HW-VSYNC 到来的时候,HWC 会将 HW-VSYNC 发生的时间戳发给 SurfaceFlinger,然后 SurfaceFlinger 会转发给 DispSync:

class ComposerCallbackBridge : public Hwc2::IComposerCallback {
public:

Return onVsync(Hwc2::Display display, int64_t timestamp) override
{
mCallback->onVsyncReceived(mSequenceId, display, timestamp);
return Void();
}

};

void SurfaceFlinger::onVsyncReceived(int32_t sequenceId,
hwc2_display_t displayId, int64_t timestamp) {

{ // Scope for the lock
Mutex::Autolock _l(mHWVsyncLock);
if (type == DisplayDevice::DISPLAY_PRIMARY && mPrimaryHWVsyncEnabled) {
needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
}
}

// 这个很重要,后面会提到
if (needsHwVsync) {
enableHardwareVsync();
} else {
disableHardwareVsync(false);
}
}

重点看 DispSync 怎么处理这些 HW-VSYNC,是在 addResyncSample() 这个函数:

bool DispSync::addResyncSample(nsecs_t timestamp) {
Mutex::Autolock lock(mMutex);

size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
mResyncSamples[idx] = timestamp;
if (mNumResyncSamples == 0) {
mPhase = 0;
mReferenceTime = timestamp;
mThread->updateModel(mPeriod, mPhase, mReferenceTime);
}

if (mNumResyncSamples < MAX_RESYNC_SAMPLES) {
mNumResyncSamples++;
} else {
mFirstResyncSample = (mFirstResyncSample + 1) % MAX_RESYNC_SAMPLES;
}

updateModelLocked();

if (mNumResyncSamplesSincePresent++ > MAX_RESYNC_SAMPLES_WITHOUT_PRESENT) {
resetErrorLocked();
}

bool modelLocked = mModelUpdated && mError < (kErrorThreshold / 2);
return !modelLocked;
}

这里需要重点说明这里面几个变量的意义(在 DispSync.h 这个头文件里面有说明):

  • mPeriod 这个就是 DispSync 根据 HW-VSYNC,计算出来的 SW-VSYNC 的时间间隔,单位是纳秒。 这里有人可能会有疑问,这个值的意义在哪?硬件是以一个固定的时间间隔去发 HW-VSYNC,为什么还需要去计算一个新的时间间隔?直接跟 HW-VSYNC 的时间间隔一致不行吗? 这个当做作业留给大家思考。
  • mPhase 这个说实话我看了好久一直都看不懂这个值的意义
  • mReferenceTime 这个是第一次收到 HW-VSYNC 的时间戳,用来当做 DispSync 的参考标准
  • mWakeupLatency
  • mResyncSample 长度 32,用来记录收到硬件 VSYNC 的时间戳的数组,不过被解释为一个 ring buffer,新的会覆盖旧的
  • mFirstResyncSample 记录了 mResyncSample 这个 ring buffer 的开头
  • mNumResyncSamples 接收到硬件 VSYNC 的个数

DispSync 将从 SurfaceFlinger 发来的 HW-VSYNC 的时间戳都给记录到一个 ring buffer,当有了足够多的 HW-VSYNC 了以后(目前是 6 个即以上),就可以开始来拟合 SF-VSYNC 的间隔 mPeriod 了,是在 DispSync::updateModelLocked() 里面计算的,核心算法就在这里了。分为两部分,一部分是 mPeriod 的计算:

void DispSync::updateModelLocked() {
if (mNumResyncSamples >= MIN_RESYNC_SAMPLES_FOR_UPDATE) {
nsecs_t durationSum = 0;
nsecs_t minDuration = INT64_MAX;
nsecs_t maxDuration = 0;
for (size_t i = 1; i < mNumResyncSamples; i++) {
size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
size_t prev = (idx + MAX_RESYNC_SAMPLES - 1) % MAX_RESYNC_SAMPLES;
nsecs_t duration = mResyncSamples[idx] - mResyncSamples[prev];
durationSum += duration;
minDuration = min(minDuration, duration);
maxDuration = max(maxDuration, duration);
}

durationSum -= minDuration + maxDuration;
mPeriod = durationSum / (mNumResyncSamples - 3);

mPeriod 的计算十分简单,把所有的 HW-VSYNC 前后相减算出 HW-VSYNC 的时间间隔,然后去掉一个最小值和最大值,然后所有 HW-VSYNC 的时间戳之和除以总个数就是 mPeriod 了。这里有一个问题就是为什么在最后除的时候是除数是 3?其实很简单,因为前面的 for 循环是从 1 开始算起的,所以循环结束一下 durationSum 其实是 mNumResyncSamples - 1 个 HW-VSYNC 的总和,然后再去掉一个最大和最小,所以总数是 mNumResyncSamples - 3。

另一部分是 mPhase 的计算,这一块看上去好像挺复杂的,甚至还有三角函数:


double sampleAvgX = 0;
double sampleAvgY = 0;
// scale 的意义是,每 ms 代表了多少度。(总量除以总个数等于每个的值)
double scale = 2.0 * M_PI / double(mPeriod);

文末

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的相关的几十套腾讯、头条、阿里、美团等公司21年的面试专题,其中把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【延伸Android必备知识点】

这里只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢在关注一下~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

由于篇幅有限,这里以图片的形式给大家展示一部分免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-rBc8qpYF-1715899838503)]

【延伸Android必备知识点】

[外链图片转存中…(img-kqnTc8q3-1715899838503)]

这里只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢在关注一下~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值