小葵花妈妈课堂开课了:《Handler Looper Message 浅析》
# 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 浅析》相关推荐
- ITextPDF填写模板,部分中文丢失,如“小葵花妈妈课堂”,剩余“小葵花堂”
ITextPDF填写模板,部分中文中文,如"小葵花妈妈课堂",剩余"小葵花堂" 问题描述 问题描述 一开始以为是linux字体的问题,重新安装两次无效无效.后怀 ...
- 你真的了解AsyncTask吗?AsyncTask源码分析
转载请注明出处:http://blog.csdn.net/yianemail/article/details/51611326 1,概述 Android UI是线程不安全的,如果想要在子线程很好的访问 ...
- 线程中task取消_Rust Async: async-task源码分析
async-std是rust异步生态中的基础运行时库之一,核心理念是合理的性能 + 用户友好的api体验.经过几个月密集的开发,前些天已经发布1.0稳定版本.因此是时候来一次深入的底层源码分析.asy ...
- Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原因)
(注意:本文基于API 28的源码分析,API 29上或其他平台的源码略有不同) 前言 当你调用AsyncTask对象的execute()方法时,突然发生崩溃--内心充满不解:java.lang.Il ...
- 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介 | 使用方法 | AsyncTask 源码分析 )
文章目录 一.AsyncTask 参数简介 二.AsyncTask 方法简介 三.AsyncTask 基本用法 四.AsyncTask 构造函数源码解析 五.AsyncTask 构造函数相关源码注释 ...
- Asynctask源码分析
首先我们使用AsyncTask时,一般是: new AsyncTask(...).execute() 复制代码 我们看new AsyncTask(),它走的是: public AsyncTask( ...
- android asynctask源码分析,Android通过Handler与AsyncTask两种方式动态更新ListView(附源码)...
本文实例讲述了Android通过Handler与AsyncTask两种方式动态更新ListView的方法.分享给大家供大家参考,具体如下: 有时候我们需要修改已经生成的列表,添加或者修改数据,noti ...
- JavaFX源码分析实战:如何设置窗体标题小图标和任务栏图标
JavaFX实战系列 JavaFX源码分析和实战:javaFX线程结构分析 JavaFX源码分析和实战之launcher启动器:两种启动javaFX的方式及launch(args[])参数设置和获取 ...
- AsyncTask使用以及源码分析
综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看And ...
- Redis学习之intset整数集合源码分析
1.整数集合:整数的集合,升序排序,无重复元素 2.整数集合intset是集合键的底层实现之一,当一个集合只包含整数值的元素,并且这个集合的元素数量不多时,redis会使用整数集合作为集合键的底层实现 ...
最新文章
- Boost 1.53.0 发布,可移植的C++标准库
- ElasticSearch + xpack 使用
- C#静态变量和非静态变量的区别
- APK反编译得工具总结(转载)
- vue 导入excel解析_VUE中导入excel文件
- plugin.super mysql_使用MySQ Clone Plugin部署MySQL Group Replication
- oracle监控资源管理器
- 8大排序算法图文解说
- android开发完全退出activity
- ps图像放大后变清晰和文字模糊变清晰
- 手机中.android_secure文件夹中的文件能删除吗,安卓手机里的各“文件夹”都是什么?能删吗?-手机相册在哪个文件夹...
- React中setState() 函数的三种用法
- linux设备驱动--字符设备模型
- PyTorch之对类别张量进行one-hot编码
- 制作linux包 u盘安装
- mnist 数据集 下载 训练 测试 pytorch
- 利用物联网全方位节能,降费增效明显
- SRM 551 ColorfulCupcakesDivTwo
- 408 计算机学,问一下 今年计算机考408的有哪些学校?
- 闪电搜索算法,Lightning search algorithm