
原文参考链接:原文链接:https://blog.csdn.net/qq_34211365/article/details/105206088
一、View向SurfaceFlinger注册 Vysnc信号
1. Choreographer注册DisplayEventReceiver
private Choreographer(Looper looper, int vsyncSource) {//UI线程的loopermLooper = looper;mHandler = new FrameHandler(looper);//USE_VSYNC为true,使用VSYNC//注册DisplayEventReceiver 入口mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;mLastFrameTimeNanos = Long.MIN_VALUE;//Android默认60FPS,mFrameIntervalNanos为16.6mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());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));}//父类DisplayEventReceiverpublic DisplayEventReceiver(Looper looper, int vsyncSource) {if (looper == null) {throw new IllegalArgumentException("looper must not be null");}mMessageQueue = looper.getQueue();//JNImReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue,vsyncSource);mCloseGuard.open("dispose");}
FrameDisplayEventReceiver 继承 DisplayEventReceiver ,在初始化的时候构造方法中显式调用了父类DisplayEventReceiver 的构造方法。
VSYNC有两种:APP和SurfaceFlinger。
NativeDisplayEventReceiver构造方法中调用了父类DisplayEventDispatcher的构造方法,并且将java层传递下来的VSYNC_SOURCE_APP强转为了ISurfaceComposer::VsyncSource传递给了DisplayEventDispatcher,VsyncSource是枚举类型,VSYNC_SOURCE_APP 为0。
2.进入JNI nativeInit 主要有两步
//nativeInit 步骤1sp receiver = new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource);//nativeInit 步骤2status_t status = receiver->initialize();
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject messageQueueObj, jint vsyncSource) {//根据java层messageQueue的到native层messageQueuesp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == NULL) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}//2.1 步骤1 nativeInit->step 1sp receiver = new NativeDisplayEventReceiver(env,receiverWeak, messageQueue, vsyncSource);//2.2 步骤2 nativeInit->step 2status_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(receiver.get());}
//nativeInit->step 1:创建 NativeDisplayEventReceiverNativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,jobject receiverWeak, const sp& messageQueue, jint vsyncSource) :DisplayEventDispatcher(messageQueue->getLooper(),static_cast<:vsyncsource>(vsyncSource)),</:vsyncsource>mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),mMessageQueue(messageQueue) {ALOGV("receiver %p ~ Initializing display event receiver.", this);}
//nativeInit->step 1:DisplayEventDispatcher构造方法中将vsyncSource赋值给了mReceiverDisplayEventDispatcher::DisplayEventDispatcher(const sp& looper,ISurfaceComposer::VsyncSource vsyncSource) :mLooper(looper), mReceiver(vsyncSource), mWaitingForVsync(false) {ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);}
//nativeInit->step 1:调用DisplayEventReceiver的构造函数进行初始化DisplayEventReceiver::DisplayEventReceiver(ISurfaceComposer::VsyncSource vsyncSource) {//步骤1 通过binder获取SurfaceFlinger服务//获取的是SurfaceFlinger的Bp端BpSurfaceComposer,//SurfaceFlinger的是它的Bn端BnSurfaceComposer的子类sp sf(ComposerService::getComposerService());if (sf != nullptr) {//步骤2 调用SurfaceFlinger创建 BitTube 用于监听VSYNCmEventConnection = sf->createDisplayEventConnection(vsyncSource);if (mEventConnection != nullptr) {//步骤3 返回创建BitTubemDataChannel = std::make_unique<:bittube>();</:bittube>//步骤4 在surfaceFlinger进程中创建的mReceiveFd传递到app进程中去mEventConnection->stealReceiveChannel(mDataChannel.get());}}}
//nativeInit->step 1:Binder实现跨进程调用//SurfaceFlinger返回的也是EventThreadConnection的Bp端BpDisplayEventConnection,EventThreadConnection继承自BnDisplayEventConnection//mEventConnection = sf->createDisplayEventConnection(vsyncSource);//调用到 SurfaceFlinger::createDisplayEventConnectionvirtual sp createDisplayEventConnection(VsyncSource vsyncSource){.....err = remote()->transact(BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION,data, &reply);......lt = interface_cast(reply.readStrongBinder());return result;}//nativeInit->step 1:SurfaceFlinger::createDisplayEventConnectionsp SurfaceFlinger::createDisplayEventConnection(ISurfaceComposer::VsyncSource vsyncSource) {auto resyncCallback = mScheduler->makeResyncCallback([this] {Mutex::Autolock lock(mStateLock);return getVsyncPeriod();});const auto& handle =vsyncSource == eVsyncSourceSurfaceFlinger ? mSfConnectionHandle : mAppConnectionHandle;//Scheduler::createDisplayEventConnectionreturn mScheduler->createDisplayEventConnection(handle, std::move(resyncCallback));}
//createConnection函数根据connectionName创建EventThread,//根据EventThread创建EventThreadConnection,然后创建ConnectionHandle,//根据ConnectionHandle,EventThreadConnection,//EventThread最终创建Connection,并以id为key,Connection为value加入到mConnections的map中,//最终返回”app“的ConnectionHandle//nativeInit->step 1:mScheduler 在 SurfaceFlinger::init() 中创建void SurfaceFlinger::init() {......mScheduler =getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },mRefreshRateConfigs);auto resyncCallback =mScheduler->makeResyncCallback(std::bind(&SurfaceFlinger::getVsyncPeriod, this));mAppConnectionHandle =mScheduler->createConnection("app", mPhaseOffsets->getCurrentAppOffset(),resyncCallback,impl::EventThread::InterceptVSyncsCallback());......}//Scheduler::createConnection//一次是创建"app"的Connection,一次是创建"sf"的Connection//两套连接的用connectionName以及id进行标识,(id为0,connectionName等于”app“的)与(id为1,connectionName等于”sf“的)//createConnection函数根据connectionName创建EventThread,//根据EventThread创建EventThreadConnection,//然后创建ConnectionHandle,根据ConnectionHandle,EventThreadConnection,//EventThread最终创建Connection,并以id为key,//Connection为value加入到mConnections的map中,//最终返回”app“的ConnectionHandle//原文链接:https://blog.csdn.net/qq_34211365/article/details/105123790sp<:connectionhandle> Scheduler::createConnection(</:connectionhandle>const char* connectionName, int64_t phaseOffsetNs, ResyncCallback resyncCallback,impl::EventThread::InterceptVSyncsCallback interceptCallback) {//id从0累加const int64_t id = sNextId++;ALOGV("Creating a connection handle with ID: %" PRId64 "n", id);//创建对应connectionName名字的EventThreadstd::unique_ptr eventThread =makeEventThread(connectionName, mPrimaryDispSync.get(), phaseOffsetNs,std::move(interceptCallback));//创建EventThreadConnectionauto eventThreadConnection =createConnectionInternal(eventThread.get(), std::move(resyncCallback));//创建ConnectionHandle,与id对应mConnections.emplace(id,std::make_unique(new ConnectionHandle(id),eventThreadConnection,std::move(eventThread)));//返回”app“的ConnectionHandlereturn mConnections[id]->handle;}
//nativeInit->step 1:Scheduler::createDisplayEventConnectionsp Scheduler::createDisplayEventConnection(const sp<:connectionhandle>& handle, ResyncCallback resyncCallback) {</:connectionhandle>RETURN_VALUE_IF_INVALID(nullptr);//handle->id用于标识这套连接是app还是surfaceFlingerreturn createConnectionInternal(mConnections[handle->id]->thread.get(),std::move(resyncCallback));}sp Scheduler::createDisplayEventConnection(const sp<:connectionhandle>& handle, ResyncCallback resyncCallback) {</:connectionhandle>RETURN_VALUE_IF_INVALID(nullptr);return createConnectionInternal(mConnections[handle->id]->thread.get(),std::move(resyncCallback));}sp Scheduler::createConnectionInternal(EventThread* eventThread,ResyncCallback&& resyncCallback) {return eventThread->createEventConnection(std::move(resyncCallback));}sp EventThread::createEventConnection(ResyncCallback resyncCallback) const {return new EventThreadConnection(const_cast(this), std::move(resyncCallback));}EventThreadConnection::EventThreadConnection(EventThread* eventThread,ResyncCallback resyncCallback): resyncCallback(std::move(resyncCallback)),mEventThread(eventThread),mChannel(gui::BitTube::DefaultSize)//构造函数中最重要的就是创建了mChannel,//mChannel是gui::BitTube类型接着看gui::BitTube的构造函数{}
DisplayEventReceiver 步骤4 BitTube的构造函数
BitTube::BitTube(size_t bufsize) {init(bufsize, bufsize);}//mReceiveFd.reset(sockets[0]),mSendFd.reset(sockets[1]);建立Fd与socket的关联,//一对sockets一个用来接受消息,一个用来发送消息,//Vsync到来的时候通过mSendFd写入消息,然后app监听mReceiveFd接受消息,就完成了app对Vsync的接收//原文链接:https://blog.csdn.net/qq_34211365/article/details/105123790void BitTube::init(size_t rcvbuf, size_t sndbuf) {int sockets[2];if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) {size_t size = DEFAULT_SOCKET_BUFFER_SIZE;setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));// since we don't use the "return channel", we keep it small...setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));fcntl(sockets[0], F_SETFL, O_NONBLOCK);fcntl(sockets[1], F_SETFL, O_NONBLOCK);mReceiveFd.reset(sockets[0]);mSendFd.reset(sockets[1]);} else {mReceiveFd.reset();ALOGE("BitTube: pipe creation failed (%s)", strerror(errno));}}
DisplayEventReceiver 步骤5 传递mReceiveFd到app进程
class BpDisplayEventConnection : public SafeBpInterface<IDisplayEventConnection> {public:......status_t stealReceiveChannel(gui::BitTube* outChannel) override {return callRemote<decltype(&IDisplayEventConnection::stealReceiveChannel)>(Tag::STEAL_RECEIVE_CHANNEL,outChannel);}}status_t EventThreadConnection::stealReceiveChannel(gui::BitTube* outChannel) {outChannel->setReceiveFd(mChannel.moveReceiveFd());return NO_ERROR;}
base::unique_fd BitTube::moveReceiveFd() {return std::move(mReceiveFd);}void BitTube::setReceiveFd(base::unique_fd&& receiveFd) {mReceiveFd = std::move(receiveFd);}
DisplayEventReceiver::DisplayEventReceiver(ISurfaceComposer::VsyncSource vsyncSource) {//步骤1sp sf(ComposerService::getComposerService());if (sf != nullptr) {//步骤2mEventConnection = sf->createDisplayEventConnection(vsyncSource);if (mEventConnection != nullptr) {//步骤3mDataChannel = std::make_unique<:bittube>();</:bittube>//步骤4mEventConnection->stealReceiveChannel(mDataChannel.get());}}}
2.2 步骤1
DisplayEventDispatcher::initialize 方法,调用Looper的addFd将我们前面得到的mReceiveFd添加到Handler进行监听。
status_t DisplayEventDispatcher::initialize() {//检查mReceiver是否异常status_t result = mReceiver.initCheck();...//调用Looper的addFd将我们前面得到的mReceiveFd添加到Handler进行监听int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,this, NULL);if (rc < 0) {return UNKNOWN_ERROR;}return OK;}
二、View向SurfaceFlinger申请下一次Vsync信号
如调用invalidate()方法主动请求重绘,ViewRoot接收到刷新请求,通过scheduleTraversals()调度traversal,就会调用到 nativeScheduleVsync 用于应用层向native层注册监听下一次Vsync信号,在UI需要刷新时调用。
也就是调用到:mReceiver.requestNextVsync。
//android_view_DisplayEventReceiver.cppstatic void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {sp receiver =reinterpret_cast(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());}}
//NativeDisplayEventReceiver继承DisplayEventReceiver,scheduleVsync这个函数是在父类实现的status_t DisplayEventDispatcher::scheduleVsync() {if (!mWaitingForVsync) {ALOGV("dispatcher %p ~ Scheduling vsync.", this);// Drain all pending events.nsecs_t vsyncTimestamp;PhysicalDisplayId vsyncDisplayId;uint32_t vsyncCount;//步骤1if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "",this, ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));}//步骤2status_t status = mReceiver.requestNextVsync();if (status) {ALOGW("Failed to request next vsync, status=%d", status);return status;}mWaitingForVsync = true;}return OK;}
步骤1. processPendingEvents获取最新到来的Vsync
//通过mReceiver.getEvents获取事件,//mReceiver类型为DisplayEventReceiver,然后处理事件然后根据不同类型的事件,调用不同的处理方式,事件有三种,定义在DisplayEventReceiver.h中//DisplayEventReceiver.henum {DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),DISPLAY_EVENT_CONFIG_CHANGED = fourcc('c', 'o', 'n', 'f'),};bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, uint32_t* outCount) {bool gotVsync = false;DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];ssize_t n;//mReceiver.getEventswhile ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {ALOGV("dispatcher %p ~ Read %d events.", this, int(n));for (ssize_t i = 0; i < n; i++) {const DisplayEventReceiver::Event& ev = buf[i];switch (ev.header.type) {case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:// Later vsync events will just overwrite the info from earlier// ones. That's fine, we only care about the most recent.gotVsync = true;*outTimestamp = ev.header.timestamp;*outDisplayId = ev.header.displayId;*outCount = ev.vsync.count;break;case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:......break;case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:.....break;default:break;}}}...return gotVsync;}
1.1 mReceiver.getEvents
//mDataChannel是在nativeInit过程中创建的,//这个过程创建了一套”app“的连接用来接收Vsync信号,而gui::BitTube则作为这套连接的数据收发的管理类ssize_t DisplayEventReceiver::getEvents(DisplayEventReceiver::Event* events,size_t count) {return DisplayEventReceiver::getEvents(mDataChannel.get(), events, count);}ssize_t DisplayEventReceiver::getEvents(gui::BitTube* dataChannel,Event* events, size_t count){return gui::BitTube::recvObjects(dataChannel, events, count);}
1.2 gui::BitTube::recvObjects
//BitTube.htemplate <typename T>static ssize_t recvObjects(BitTube* tube, T* events, size_t count) {return recvObjects(tube, events, count, sizeof(T));}ssize_t BitTube::recvObjects(BitTube* tube, void* events, size_t count, size_t objSize) {char* vaddr = reinterpret_cast<char*>(events);ssize_t size = tube->read(vaddr, count * objSize);.....return size < 0 ? size : size / static_cast<ssize_t>(objSize);}ssize_t BitTube::read(void* vaddr, size_t size) {ssize_t err, len;do {len = ::recv(mReceiveFd, vaddr, size, MSG_DONTWAIT);err = len < 0 ? errno : 0;} while (err == EINTR);if (err == EAGAIN || err == EWOULDBLOCK) {return 0;}return err == 0 ? len : -err;}
步骤2. requestNextVsync()函数
//mEventConnection是在nativeInit过程中创建的,类型为BpDisplayEventConnection,//这里最终通过Binder调到surfaceFlinger进程中BnDisplayEventConnection的实现类EventThreadConnection中,status_t DisplayEventReceiver::requestNextVsync() {if (mEventConnection != nullptr) {//调到surfaceFlinger进程中BnDisplayEventConnection的实现类EventThreadConnection中,mEventConnection->requestNextVsync();return NO_ERROR;}return NO_INIT;}//EventThread.cppvoid EventThreadConnection::requestNextVsync() {ATRACE_NAME("requestNextVsync");mEventThread->requestNextVsync(this);}void EventThread::requestNextVsync(const sp& connection) {if (connection->resyncCallback) {connection->resyncCallback();}std::lock_guard<std::mutex> lock(mMutex);if (connection->vsyncRequest == VSyncRequest::None) {connection->vsyncRequest = VSyncRequest::Single;//条件变量使用notify_all会唤醒所有调用了wait的线程,//wait在哪里调用的呢?是在EventThread创建的时候,//它的构造函数中创建了一个线程,这个线程一启动就调用了threadMain函数,//threadMain中通过条件变量调用wait使线程陷入等待mCondition.notify_all();}}
2.1 EventThread 构造函数
EventThread::EventThread(VSyncSource* src, std::unique_ptr uniqueSrc,InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName): mVSyncSource(src),mVSyncSourceUnique(std::move(uniqueSrc)),mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)),mThreadName(threadName) {...mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {std::unique_lock<std::mutex> lock(mMutex);//threadMain函数threadMain(lock);});...}
2.2 threadMain调用dispatchEvent分发事件
void EventThread::threadMain(std::unique_lock<:mutex>& lock) {</:mutex>DisplayEventConsumers consumers;while (mState != State::Quit) {...if (!mPendingEvents.empty()) {event = mPendingEvents.front();....if (!consumers.empty()) {//调用dispatchEvent分发事件dispatchEvent(*event, consumers);consumers.clear();}...// Wait for event or client registration/request.if (mState == State::Idle) {//如果mState为Idle,就让此线程陷入等待,唤醒条件就是调用notify函数mCondition.wait(lock);} else {...}}}
2.3 dispatchEvent分发VSync的流程
dispatchEvent分发VSync的流程其实也很简单,最终就是调用gui::BitTube::sendObjects函数,通过socket 通知View 进行刷新。通过scheduleTraversals()调度traversal,performTraversals()调用performMeasure()、performLayout()、performDraw()进行刷新。
mSendFd和mReceiveFd对应一对可连接的socket,sendObjects内部write函数,write函数就是向mSendFd中写数据。当mSendFd写入数据之后,对应的mReceiveFd就能收到,并且会调用DisplayEventDispatcher::
handleEvent回调函数。
//sendObjects和recvObjects对应的,sendObjects函数再调用内部write函数,write函数就是向mSendFd中写数据,//mSendFd和mReceiveFd对应一对可连接的socket//调用gui::BitTube::sendObjects函数ssize_t BitTube::sendObjects(BitTube* tube, void const* events, size_t count, size_t objSize) {const char* vaddr = reinterpret_cast<const char*>(events);ssize_t size = tube->write(vaddr, count * objSize);......return size < 0 ? size : size / static_cast<ssize_t>(objSize);}ssize_t BitTube::write(void const* vaddr, size_t size) {ssize_t err, len;do {len = ::send(mSendFd, vaddr, size, MSG_DONTWAIT | MSG_NOSIGNAL);// cannot return less than size, since we're using SOCK_SEQPACKETerr = len < 0 ? errno : 0;} while (err == EINTR);return err == 0 ? len : -err;}
//nativeInit过程中mReceiveFd会被添加到handler的epoll中进行监听//当mSendFd写入数据之后,对应的mReceiveFd就能收到,//并且会调用handleEvent回调函数,这个回调是在添加mReceiveFd时一并注册的int DisplayEventDispatcher::handleEvent(int, int events, void*) {...if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", displayId=%"ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", count=%d",this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);mWaitingForVsync = false;dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);}return 1; // keep the callback}
2.4 NativeDisplayEventReceiver::dispatchVsync
dispatchVsync的实现在DisplayEventDispatcher子类NativeDisplayEventReceiver 中,通过JAVA反射将Vsync信号相关信息发送到应用层。分发的实质就是通过gui::BitTube::sendObjects向mSendFd写入数据,监听的mReceiveFd就能收到数据并调用回调函数handleEvent,将Vsync信息分发到了应用层,开始处理View的绘制工作。
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,uint32_t count) {......//通过 反射 DisplayEventReceiver的dispatchVsync方法就将Vsync信号相关信息发送到了应用层env->CallVoidMethod(receiverObj.get(),gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, displayId, count);......}
2.5 dispatchVsync 应用层:
//frameworks/base/core/java/android/view/DisplayEventReceiver.java// Called from native code.@SuppressWarnings("unused")private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,VsyncEventData vsyncEventData//开始处理View的绘制工作,也即是调用 scheduleTraversals()调度traversalonVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);}// Called from native code.@SuppressWarnings("unused")@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)private void dispatchHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {onHotplug(timestampNanos, physicalDisplayId, connected);}// Called from native code.@SuppressWarnings("unused")private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {onModeChanged(timestampNanos, physicalDisplayId, modeId);}// Called from native code.@SuppressWarnings("unused")private void dispatchFrameRateOverrides(long timestampNanos, long physicalDisplayId,FrameRateOverride[] overrides) {onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);}
自此完成了VIEW 注册与接受 SurfaceFlinger VSYNC 信号完成。
觉得写的不错就点个赞吧!关注我下文更精彩!
原文参考链接:原文链接:https://blog.csdn.net/qq_34211365/article/details/105206088
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/21406,转载请注明出处。


评论0