# Handler Looper Message Thread

首先要阐述几者之间的关系。 Thread 可以拥有多个handler对象; Thread 只能拥有一个Looper 和一个MessageQueue。

Looper 只能属于一个Thread, 并且只能和MessageQueue 一一对应。 looper的在几者中的作用是什么呢! Looper的作用就是起到 发动机的原理,当然它不是让车跑起来,而是让MessageQueue里的message被执行。 那么 Message被谁执行呢? 后文即会提到。

MessageQueue 也仅是和一个looper绑定,在出生的时候即决定了这件事,后面在代码中会解释为什么! MessageQueue里面存放就是 Message。

Looper

首先需要关注的是该方法。

public static void prepare() {prepare(true);
}
复制代码

参数为是否允许退出,答案是肯定的 true; 只有一种情况即主线程调用prepare时传递false,因为主线程不允许退出。 该方法即为 预热发动机的入口。让 Looper这台机器进行启动之前的准备工作。

private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

分析一下 是如何判断已经prepare的呢? sThreadLocal.get() != null 那就需要看一下set是什么东东。就是准备的是什么呢?

/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.*/
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
复制代码

这个value就是上文提到的 new Looper(quitAllowed) createMap创建的是一个ThreadLocalMap。 每一个线程仅有一个ThreadLocalMap, 在该map中存储内容为该线程本地变量的副本。ThreadLocalMap使用及注意事项以后单独开讲。

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码

当第一次sThreadLocal.get()时,会返回setInitialValue=null;

private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}
复制代码

当一个线程只能有一个Looper之后也就意味着只能有一个MessageQueue.class

Looper.loop即为启动发动机的入口,启动之后开始进行消息轮询,并且注释说明一定要调用quit()退出轮询。 Looper一直把MesageQueue所有的message执行完。 每执行完一个后即通过next拿到下一个message.

public static void loop() {
---
for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;} //当队列中没有消息之后 即退出。// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}try {msg.target.dispatchMessage(msg); //msg.target即为执行message工具。} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}
}
复制代码

MessageQueue MessageQueue和Looper之间有个紧密的联系就是通过 MessageQueue.next()方法。以next方法为切入点介绍MessageQueue.class

Message next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//natvie层进行阻塞,后文在Looper.c中介绍nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// Stalled by a barrier.  Find the next asynchronous message in the queue.// 当因为有 "同步分隔栏" 引起停滞后, 将要找到下一个异步消息, // 同步分隔栏后面的同步消息并不会执行do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// Next message is not ready.  Set a timeout to wake up when it is ready.//如果当前的msg没有准备好,那么就下次轮询进入到等待。//计算等待时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.//标记当次轮询不会被wait,不需要被唤醒mBlocked = false;//当在链表队列中找到可执行msg,把当前message调出,并修复原链接if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);//标记当前msg被使用状态msg.markInUse();return msg;}} else {// No more messages.nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.//如果looper调用了quit, messagequeue也进行退出操作。if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.// 引入了另外一个messagequeue的功能, idle handles的处理,// 当队列为空的时候或没有任务可执行的时候,执行idle handles内容。if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.// 既没有idle handlers 和message可以处理那么就需要阻塞,入队时候就需要唤醒。mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {//执行idler中的回调,并且有返回值,true意味着想要保持这个idle下次继续执行,//false则会从队列中移除keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}
}
复制代码

下面继续介绍enqueueMessage,入队操作由Handler.class执行。后文提到其中几种入队操作。

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {//如果looper已经条用quit,那么就放弃入队。if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.// 如果messagequeue中没有message或者需要立即执行或者插入message时间优于对头// message所需要执行时间,那么就把msg插入到对头。msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.//通常情况下将目标message插入到队里中间时,是不需要唤醒队列的,//除非有一个"同步分隔栏"在对头或者目标message是最早需要执行的异步message。needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {//找到最后一个位置,或者时间排序上晚于目标message的位置break;}//当需要唤醒,但是 要插入目标message的前面所有位置的message//只要有异步消息的话既不需要唤醒。if (needWake && p.isAsynchronous()) {needWake = false;}}// 将目标message插入到理想位置,修复整个链接msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {//此处为唤醒 LoopernativeWake(mPtr);}}return true;
}
复制代码

上面介绍了MessageQueue的两个主要方法next()和enqueueMessage(),其中涉及到了两个native层的本地方法分别为: nativePollOnce(ptr, nextPollTimeoutMillis); nativeWake(mPtr); 那么下面介绍一下这两个方法。 方法在/frameworks/base/core/jni/android_os_MessageQueue.cpp中进行了定义。

static JNINativeMethod gMessageQueueMethods[] = {/* name, signature, funcPtr */{ "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit },{ "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy },{ "nativePollOnce", "(II)V", (void*)android_os_MessageQueue_nativePollOnce },{ "nativeWake", "(I)V", (void*)android_os_MessageQueue_nativeWake }
};
复制代码
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jint ptr, jint timeoutMillis) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->pollOnce(timeoutMillis);
}
复制代码

最终调用到Looper::pollOnce====>Looper::pollInner

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
int Looper::pollInner(int timeoutMillis);
复制代码
int Looper::pollInner(int timeoutMillis) {---
#ifdef LOOPER_USES_EPOLLstruct epoll_event eventItems[EPOLL_MAX_EVENTS];//通过Epoll进行阻塞int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
#else// Wait for wakeAndLock() waiters to run then set mPolling to true.mLock.lock();while (mWaiters != 0) {mResume.wait(mLock);}mPolling = true;mLock.unlock();size_t requestedCount = mRequestedFds.size();int eventCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
#endif---
}复制代码

其中最终运用epoll进行控制(epoll不再本文讨论感兴趣读者可自行查询! )。下面引入《深入理解Android:卷II》对pollOnce解释:

其中四个参数: timeoutMillis参数为超时等待时间。如果值为–1,则表示无限等待,直到有事件发生为止。如果值为0,则无须等待立即返回。 outFd用来存储发生事件的那个文件描述符。 outEvents用来存储在该文件描述符上发生了哪些事件,目前支持可读、可写、错误和中断4个事件。这4个事件其实是从epoll事件转化而来的。后面我们会介绍大名鼎鼎的epoll。 outData用于存储上下文数据,这个上下文数据是由用户在添加监听句柄时传递的,它的作用和pthread_create函数最后一个参数param一样,用来传递用户自定义的数据。 另外,pollOnce函数的返回值也具有特殊的意义,具体如下: 当返回值为ALOOPER_POLL_WAKE时,表示这次返回是由wake函数触发的,也就是管道写端的那次写事件触发的。 返回值为ALOOPER_POLL_TIMEOUT表示等待超时。 返回值为ALOOPER_POLL_ERROR表示等待过程中发生错误。 返回值为ALOOPER_POLL_CALLBACK表示某个被监听的句柄因某种原因被触发。这时,outFd参数用于存储发生事件的文件句柄,outEvents用于存储所发生的事件。

MessageQueue还有其他公开方法:

用来添加IdleHandler,当没有message需要立即处理时就会处理IdleHandler。

void addIdleHandler(@NonNull IdleHandler handler);
void removeIdleHandler(@NonNull IdleHandler handler);
复制代码

用来添加需要监听的文件描述符fd

void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,@OnFileDescriptorEventListener.Events int events,@NonNull OnFileDescriptorEventListener listener);
void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd);
复制代码

Message.class 主要是handler要处理的信使,主要功能携带参数。下面主要介绍handler参数。

/*** User-defined message code so that the recipient can identify * what this message is about. Each {@link Handler} has its own name-space* for message codes, so you do not need to worry about yours conflicting* with other handlers.*/
public int what;
//定义在handler中要执行的事件/*** arg1 and arg2 are lower-cost alternatives to using* {@link #setData(Bundle) setData()} if you only need to store a* few integer values.*/
public int arg1;
//如果要存储简单的参数,使用arg1和arg2就可以/*** arg1 and arg2 are lower-cost alternatives to using* {@link #setData(Bundle) setData()} if you only need to store a* few integer values.*/
public int arg2;/*** An arbitrary object to send to the recipient.  When using* {@link Messenger} to send the message across processes this can only* be non-null if it contains a Parcelable of a framework class (not one* implemented by the application).   For other data transfer use* {@link #setData}.* * <p>Note that Parcelable objects here are not supported prior to* the {@link android.os.Build.VERSION_CODES#FROYO} release.*/
public Object obj;
//可存储任意类型参数/*** Optional Messenger where replies to this message can be sent.  The* semantics of exactly how this is used are up to the sender and* receiver.*/
public Messenger replyTo;
//可实现跨进程通信,后面会独立章节进行讲解。/*** Optional field indicating the uid that sent the message.  This is* only valid for messages posted by a {@link Messenger}; otherwise,* it will be -1.*/
public int sendingUid = -1;
//与Messenger 配合使用/*package*/ int flags;
//0x00 非使用, 0x01被使用:当入队和被回收的时候会设置为1
//0x10 表示为异步/*package*/ long when;
//延迟执行时间/*package*/ Bundle data;
//存储一些复杂数据/*package*/ Handler target;
//执行该message的handler/*package*/ Runnable callback;
//hanlder执行该message时,如果有callback即执行该callback// sometimes we store linked lists of these things
/*package*/ Message next;
//保存链表
复制代码

主要解析一下该函数

/**
* Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.*/
public static Message obtain() {//sPoolSync 同步锁synchronized (sPoolSync) {//sPool指向链表的头if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;//将sPool取出,并断链return m;}}//如果链中没有元素,重新分配return new Message();
}/*** Recycles a Message that may be in-use.* Used internally by the MessageQueue and Looper when disposing of queued Messages.*/
void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {//链表不会无限增长if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;//将该message插入头部}}
}
复制代码

Handler 先比较前几个Class, Handler比较简单,成员只有以下几个:

final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
复制代码

先看几个比较重要的构造方法:

//常用的为无参构造形式
public Handler() {this(null, false);
}//这是无参构造方法调用的真正构造方法,
public Handler(Callback callback, boolean async) {//FIND_POTENTIAL_LEAKS//将此标志设置为true以检测扩展的Handler类, 扩展的handler类如果不是静态的匿名,本地或成员类, //则会产生泄漏。我们常见构造时的警告说明!至于消除警告方法一般是设置成静态或弱引用。if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}//mLooper是来自于sThreadLocal中ThreadLocalMap中 通过调用线程ID存储的looper,唯一mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}//mqueue来自looper, 也唯一mQueue = mLooper.mQueue;mCallback = callback;//标示该handler发送的数据是否为异步数据。mAsynchronous = async;
}
复制代码

通过分析构造方法可验证前文提到的 handler 仅对应一个looper MessageQueue,,翻过来不成立,也就是说会有多个handler绑定在同一个Looper中。

通过调用post(Runnable r); postDelayed(Runnable r, long delayMillis);sendMessage(Message msg);等方法发送的时间,最终调用下面的方法。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;//如果为异步,则对每一个message进行设置。if (mAsynchronous) {msg.setAsynchronous(true);}//调用enqueueMessage 进行入队Messagereturn queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

还有另外一种入队方法,需要介绍:

public final boolean sendMessageAtFrontOfQueue(Message msg) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}//与enqueueMessage差别为uptimeMillis=0. 在messagequeue中当遇到when=0时,//会将该message放在对头进行处理return enqueueMessage(queue, msg, 0);
}
复制代码

在需要注意下,这个remove方法,当传入null时可将MessageQueue中的所有数据remove掉。

public final void removeCallbacksAndMessages(Object token) {mQueue.removeCallbacksAndMessages(this, token);}
复制代码

回过头来说一下上面的 同步分隔栏,

在Api 23 之, 通过MessageQueue 进行调用

/*** Posts a synchronization barrier to the Looper's message queue.** Message processing occurs as usual until the message queue encounters the* synchronization barrier that has been posted.  When the barrier is encountered,* later synchronous messages in the queue are stalled (prevented from being executed)* until the barrier is released by calling {@link #removeSyncBarrier} and specifying* the token that identifies the synchronization barrier.** This method is used to immediately postpone execution of all subsequently posted* synchronous messages until a condition is met that releases the barrier.* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier* and continue to be processed as usual.** This call must be always matched by a call to {@link #removeSyncBarrier} with* the same token to ensure that the message queue resumes normal operation.* Otherwise the application will probably hang!** @return A token that uniquely identifies the barrier.  This token must be* passed to {@link #removeSyncBarrier} to release the barrier.** @hide*/// 该方法为hide, 正常写代码是调用不到的。
public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());
}// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
// 同步分隔栏消息没有target, 并且arg1用来记录token
private int mNextBarrierToken;private int postSyncBarrier(long when) {// Enqueue a new sync barrier token.// We don't need to wake the queue because the purpose of a barrier is to stall it.synchronized (this) {final int token = mNextBarrierToken++;final Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;Message prev = null;Message p = mMessages;if (when != 0) {while (p != null && p.when <= when) {prev = p;p = p.next;}}if (prev != null) { // invariant: p == prev.nextmsg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}return token;}
}
复制代码

这个同步分隔message有什么作用呢? 对开发者没有明显的提供,那么就是在系统及别使用。在ViewRootImpl.java中进行了使用。

void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}
复制代码

为了让View能够有快速的布局和绘制,ViewRootImpl在开始measure和layout ViewTree时,会向主线程的Handler添加同步分隔message,这样后续的消息队列中的同步的消息将不会被执行,以免会影响到UI绘制,但是只有异步消息才能被执行。如果想要使用postSyncBarrier() 那么就需要使用反射进行使用。

总结 Looper、MessageQueue 和 Thread 一一对应。 Handler 需要绑定到一个Looper中, 一个Looper可以有多个Handler。

这是第一文章,以后会多多写的。欢迎各位指正问题!谢谢。 sy_dqs@163.com

转载于:https://juejin.im/post/5ae841a451882567336a84a9

小葵花妈妈课堂开课了:《Handler Looper Message 浅析》相关推荐

  1. ITextPDF填写模板,部分中文丢失,如“小葵花妈妈课堂”,剩余“小葵花堂”

    ITextPDF填写模板,部分中文中文,如"小葵花妈妈课堂",剩余"小葵花堂" 问题描述 问题描述 一开始以为是linux字体的问题,重新安装两次无效无效.后怀 ...

  2. 你真的了解AsyncTask吗?AsyncTask源码分析

    转载请注明出处:http://blog.csdn.net/yianemail/article/details/51611326 1,概述 Android UI是线程不安全的,如果想要在子线程很好的访问 ...

  3. 线程中task取消_Rust Async: async-task源码分析

    async-std是rust异步生态中的基础运行时库之一,核心理念是合理的性能 + 用户友好的api体验.经过几个月密集的开发,前些天已经发布1.0稳定版本.因此是时候来一次深入的底层源码分析.asy ...

  4. Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原因)

    (注意:本文基于API 28的源码分析,API 29上或其他平台的源码略有不同) 前言 当你调用AsyncTask对象的execute()方法时,突然发生崩溃--内心充满不解:java.lang.Il ...

  5. 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介 | 使用方法 | AsyncTask 源码分析 )

    文章目录 一.AsyncTask 参数简介 二.AsyncTask 方法简介 三.AsyncTask 基本用法 四.AsyncTask 构造函数源码解析 五.AsyncTask 构造函数相关源码注释 ...

  6. Asynctask源码分析

    ​ 首先我们使用AsyncTask时,一般是: new AsyncTask(...).execute() 复制代码 我们看new AsyncTask(),它走的是: public AsyncTask( ...

  7. android asynctask源码分析,Android通过Handler与AsyncTask两种方式动态更新ListView(附源码)...

    本文实例讲述了Android通过Handler与AsyncTask两种方式动态更新ListView的方法.分享给大家供大家参考,具体如下: 有时候我们需要修改已经生成的列表,添加或者修改数据,noti ...

  8. JavaFX源码分析实战:如何设置窗体标题小图标和任务栏图标

    JavaFX实战系列 JavaFX源码分析和实战:javaFX线程结构分析 JavaFX源码分析和实战之launcher启动器:两种启动javaFX的方式及launch(args[])参数设置和获取 ...

  9. AsyncTask使用以及源码分析

    综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看And ...

  10. Redis学习之intset整数集合源码分析

    1.整数集合:整数的集合,升序排序,无重复元素 2.整数集合intset是集合键的底层实现之一,当一个集合只包含整数值的元素,并且这个集合的元素数量不多时,redis会使用整数集合作为集合键的底层实现 ...

最新文章

  1. Boost 1.53.0 发布,可移植的C++标准库
  2. ElasticSearch + xpack 使用
  3. C#静态变量和非静态变量的区别
  4. APK反编译得工具总结(转载)
  5. vue 导入excel解析_VUE中导入excel文件
  6. plugin.super mysql_使用MySQ Clone Plugin部署MySQL Group Replication
  7. oracle监控资源管理器
  8. 8大排序算法图文解说
  9. android开发完全退出activity
  10. ps图像放大后变清晰和文字模糊变清晰
  11. 手机中.android_secure文件夹中的文件能删除吗,安卓手机里的各“文件夹”都是什么?能删吗?-手机相册在哪个文件夹...
  12. React中setState() 函数的三种用法
  13. linux设备驱动--字符设备模型
  14. PyTorch之对类别张量进行one-hot编码
  15. 制作linux包 u盘安装
  16. mnist 数据集 下载 训练 测试 pytorch
  17. 利用物联网全方位节能,降费增效明显
  18. SRM 551 ColorfulCupcakesDivTwo
  19. 408 计算机学,问一下 今年计算机考408的有哪些学校?
  20. 闪电搜索算法,Lightning search algorithm

热门文章

  1. php 401认证,给phpMyAdmin增加401加密认证
  2. HDFS的机架感知(rack aware)
  3. 【记一下1】2022年6月29日 哥和弟 双重痛苦
  4. 不在同一局域网?组建家庭局域网的方案和踩坑
  5. android usb 读写权限,Android默认给予USB读写权限,去掉受权对话框
  6. Ciprian Manolescu 解决了三角解剖猜想
  7. java 批量下载小说天堂的小说
  8. 手游服务器账号,手游服务器登录流程
  9. Windows快速访问无法取消固定
  10. 小米笔试题 风口的猪-中国牛市