引入:

提到Android中的消息机制,大家应该都不陌生,我们在开发中不可避免的要和它打交道。从我们开发的角度来看,Handler是Android消息机制的上层接口。我们在平时的开发中只需要和Handler交互即可。通过Handler我们可以将一个任务切换到Handler所在的线程中执行。日常中我们使用Handler的场景可能大多是在子线程中进行了耗时操作,比如网络请求数据,拿到数据后我们可能会更新UI控件,因为Android规定只能在主线程进行UI操作,这时候我们通常会在主线程中创建一个Handler,然后通过在子线程中使用Handler发送消息发送到主线程,然后在主线程中消息回调的handleMessage()中处理我们的消息。但是本质来说,Handler并不是专门用来更新UI的,这是它的一个用途而已。

为什么需要这样的一个消息机制呢?

我们知道每一个应用程序都有一个主线程,如果我们直接与主线程交互,访问他的一些变量,对其进行一些修改操作,可能会带来线程安全问题,并且不利于Android系统的整体运作。通过Android系统提供的一条消息处理机制,我们通过在子线程发送消息形式,让主线程进行处理,然后我们的逻辑代码就可以执行了。
具体的消息有两种:我们自己定义的Handler和系统的Hander,其实我们Android中四大组件的运作也都是系统Handler进行着消息的处理,从而实现各个生命周期的回调,当然这里的消息种类很多,就不一一列举了。这里注意一点,我们应用退出程序得到过程,应用程序退出应用其实就是让主线程结束,换句话说也就是让我们这里的Looper循环结束,这样我们的四大组件生命周期就不会执行了,应为四大组件生命周期的回调依赖于Handler处理,Looper循环都没了,四大组件还玩毛线哦。所以有时候我们的onDestroy方法不一定会回调。

Android消息机制概述:

Android的消息机制其实主要是指Handler的运行机制,而Handler的运行又需要底层MessageQueue和Looper的支撑。MessageQueue中文翻译是指消息队列,顾名思义,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。这也可以想到,因为链表的结构对于插入删除操作执行的效率比较高。Looper中文翻译为循环,由于MessageQueue只是提供了一个消息的存储,它并不能去处理消息,Looper就填补了这个功能,Looper会以无限循环的方式去查找是否有新消息,如果有的话就处理否则就会一直等待着。

其次Looper中还要一个特殊的概念ThreadLocal,用它可以在不同的线程中存储数据,互不干扰。Handler创建的时候使用当前的Looper来构造消息循环系统,那么在Handler如何获取到当前线程的Looper呢。就是通过这个ThreadLocal,ThreadLocal可以在不同线程中互不干扰的存储和读取数据,通过ThreadLocal就可以轻松获取到每个线程的Looper。需要注意的是线程默认是没有Looper的,我们在子线程使用Handler必须先创建Looper否则会发生异常。至于主线程为什么可以直接使用Handler呢?那是因为主线程在入口的main方法中就已经帮我们创建了一个Looper对象,并开启了一个Looper循环,帮我构建好了这样一个消息消息循环的环境。这里了解一下Android规定访问UI只能在主线程中进行,如果在子线程中访问UI就会发生异常,这是因为ViewRootImpl对UI的操作做了验证,这个验证是在ViewRootImpl的checkThread()方法中完成的。

  1. void checkThread() {
  2. if (mThread != Thread.currentThread()) {
  3. throw new CalledFromWrongThreadException(
  4. "Only the original thread that created a view hierarchy can touch its views.");
  5. }
  6. }

因此由于这个的限制对AndroidUI的操作必须在主线程,但是Android又不建议在主线程中执行耗时的操作,因为会阻塞主线程导致ANR异常。因此Handler就应运而生了,系统提供Handler主要原因是解决在子线程中无法更新UI的矛盾。那么刚才提到为什么不允许子线程中访问UI呢?那是因为Android的UI控件并不是线程安全的。如果存在多线程的并发访问可能会导致UI控件处于不可预期的状态。那么系统为什么不对UI控件加锁呢。一方面加锁使逻辑更复杂,二来要进行锁判断,影响效率,阻塞了其它线程,所以简单高效的模型就是单线程。对于我们来说也不麻烦一个Handler就哦了。

消息队列MessageQueue的工作原理:

MessageQueue主要包含两个操作:插入和读取。读取本身伴随着删除操作,对应着两个方法分别是enqueueMessage()和next()。enqueueMessage是插入一条消息到消息队列中,next是取出一条信息并将其从队列移除。看他的方法源码可以看到enqueueMessage主要是进行单链表的插入操作。next是一个无线循环,如果么有消息,next会一直阻塞在这里,如果有消息到来,next会返回这个消息并将其从队列移除。

enqueueMessage方法:

  1. boolean enqueueMessage(Message msg, long when) {
  2. if (msg.target == null) {
  3. throw new IllegalArgumentException("Message must have a target.");
  4. }
  5. if (msg.isInUse()) {
  6. throw new IllegalStateException(msg + " This message is already in use.");
  7. }
  8.  
  9. synchronized (this) {
  10. if (mQuitting) {
  11. IllegalStateException e = new IllegalStateException(
  12. msg.target + " sending message to a Handler on a dead thread");
  13. Log.w(TAG, e.getMessage(), e);
  14. msg.recycle();
  15. return false;
  16. }
  17.  
  18. msg.markInUse();
  19. msg.when = when;
  20. Message p = mMessages;
  21. boolean needWake;
  22. if (p == null || when == 0 || when < p.when) {
  23. // New head, wake up the event queue if blocked.
  24. msg.next = p;
  25. mMessages = msg;
  26. needWake = mBlocked;
  27. } else {
  28. // Inserted within the middle of the queue. Usually we don't have to wake
  29. // up the event queue unless there is a barrier at the head of the queue
  30. // and the message is the earliest asynchronous message in the queue.
  31. needWake = mBlocked && p.target == null && msg.isAsynchronous();
  32. Message prev;
  33. for (;;) {
  34. prev = p;
  35. p = p.next;
  36. if (p == null || when < p.when) {
  37. break;
  38. }
  39. if (needWake && p.isAsynchronous()) {
  40. needWake = false;
  41. }
  42. }
  43. msg.next = p; // invariant: p == prev.next
  44. prev.next = msg;
  45. }
  46.  
  47. // We can assume mPtr != 0 because mQuitting is false.
  48. if (needWake) {
  49. nativeWake(mPtr);
  50. }
  51. }
  52. return true;
  53. }

next方法:

  1. Message next() {
  2. // Return here if the message loop has already quit and been disposed.
  3. // This can happen if the application tries to restart a looper after quit
  4. // which is not supported.
  5. final long ptr = mPtr;
  6. if (ptr == 0) {
  7. return null;
  8. }
  9.  
  10. int pendingIdleHandlerCount = -1; // -1 only during first iteration
  11. int nextPollTimeoutMillis = 0;
  12. for (;;) {
  13. if (nextPollTimeoutMillis != 0) {
  14. Binder.flushPendingCommands();
  15. }
  16.  
  17. nativePollOnce(ptr, nextPollTimeoutMillis);
  18.  
  19. synchronized (this) {
  20. // Try to retrieve the next message. Return if found.
  21. final long now = SystemClock.uptimeMillis();
  22. Message prevMsg = null;
  23. Message msg = mMessages;
  24. if (msg != null && msg.target == null) {
  25. // Stalled by a barrier. Find the next asynchronous message in the queue.
  26. do {
  27. prevMsg = msg;
  28. msg = msg.next;
  29. } while (msg != null && !msg.isAsynchronous());
  30. }
  31. if (msg != null) {
  32. if (now < msg.when) {
  33. // Next message is not ready. Set a timeout to wake up when it is ready.
  34. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
  35. } else {
  36. // Got a message.
  37. mBlocked = false;
  38. if (prevMsg != null) {
  39. prevMsg.next = msg.next;
  40. } else {
  41. mMessages = msg.next;
  42. }
  43. msg.next = null;
  44. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
  45. msg.markInUse();
  46. return msg;
  47. }
  48. } else {
  49. // No more messages.
  50. nextPollTimeoutMillis = -1;
  51. }
  52.  
  53. // Process the quit message now that all pending messages have been handled.
  54. if (mQuitting) {
  55. dispose();
  56. return null;
  57. }
  58.  
  59. // If first time idle, then get the number of idlers to run.
  60. // Idle handles only run if the queue is empty or if the first message
  61. // in the queue (possibly a barrier) is due to be handled in the future.
  62. if (pendingIdleHandlerCount < 0
  63. && (mMessages == null || now < mMessages.when)) {
  64. pendingIdleHandlerCount = mIdleHandlers.size();
  65. }
  66. if (pendingIdleHandlerCount <= 0) {
  67. // No idle handlers to run. Loop and wait some more.
  68. mBlocked = true;
  69. continue;
  70. }
  71.  
  72. if (mPendingIdleHandlers == null) {
  73. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
  74. }
  75. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
  76. }
  77.  
  78. // Run the idle handlers.
  79. // We only ever reach this code block during the first iteration.
  80. for (int i = 0; i < pendingIdleHandlerCount; i++) {
  81. final IdleHandler idler = mPendingIdleHandlers[i];
  82. mPendingIdleHandlers[i] = null; // release the reference to the handler
  83.  
  84. boolean keep = false;
  85. try {
  86. keep = idler.queueIdle();
  87. } catch (Throwable t) {
  88. Log.wtf(TAG, "IdleHandler threw exception", t);
  89. }
  90.  
  91. if (!keep) {
  92. synchronized (this) {
  93. mIdleHandlers.remove(idler);
  94. }
  95. }
  96. }
  97.  
  98. // Reset the idle handler count to 0 so we do not run them again.
  99. pendingIdleHandlerCount = 0;
  100.  
  101. // While calling an idle handler, a new message could have been delivered
  102. // so go back and look again for a pending message without waiting.
  103. nextPollTimeoutMillis = 0;
  104. }
  105. }

Looper的工作原理:

Looper会不停的从MessageQueue中查看是否有新消息,有的话立刻处理,没有就会一直阻塞,它的构造函数初始化了一个MessageQueue,并获取到当前线程。

  1. private Looper(boolean quitAllowed) {
  2. mQueue = new MessageQueue(quitAllowed);
  3. mThread = Thread.currentThread();
  4. }

Handler的运行需要Looper的支持,那么怎么创建Looper呢,可以调用Looper.prepare()方法,

  1. private static void prepare(boolean quitAllowed) {
  2. if (sThreadLocal.get() != null) {
  3. throw new RuntimeException("Only one Looper may be created per thread");
  4. }
  5. sThreadLocal.set(new Looper(quitAllowed));
  6. }

紧接着通过Looper.loop()方法开启消息循环。

  1. public static void loop() {
  2. final Looper me = myLooper();
  3. if (me == null) {
  4. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  5. }
  6. final MessageQueue queue = me.mQueue;
  7.  
  8. // Make sure the identity of this thread is that of the local process,
  9. // and keep track of what that identity token actually is.
  10. Binder.clearCallingIdentity();
  11. final long ident = Binder.clearCallingIdentity();
  12.  
  13. for (;;) {
  14. Message msg = queue.next(); // might block
  15. if (msg == null) {
  16. // No message indicates that the message queue is quitting.
  17. return;
  18. }
  19.  
  20. // This must be in a local variable, in case a UI event sets the logger
  21. final Printer logging = me.mLogging;
  22. if (logging != null) {
  23. logging.println(">>>>> Dispatching to " + msg.target + " " +
  24. msg.callback + ": " + msg.what);
  25. }
  26.  
  27. final long traceTag = me.mTraceTag;
  28. if (traceTag != 0) {
  29. Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
  30. }
  31. try {
  32. msg.target.dispatchMessage(msg);
  33. } finally {
  34. if (traceTag != 0) {
  35. Trace.traceEnd(traceTag);
  36. }
  37. }
  38.  
  39. if (logging != null) {
  40. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  41. }
  42.  
  43. // Make sure that during the course of dispatching the
  44. // identity of the thread wasn't corrupted.
  45. final long newIdent = Binder.clearCallingIdentity();
  46. if (ident != newIdent) {
  47. Log.wtf(TAG, "Thread identity changed from 0x"
  48. + Long.toHexString(ident) + " to 0x"
  49. + Long.toHexString(newIdent) + " while dispatching to "
  50. + msg.target.getClass().getName() + " "
  51. + msg.callback + " what=" + msg.what);
  52. }
  53.  
  54. msg.recycleUnchecked();
  55. }
  56. }

Looper还提供了一个getMainLooper()这个是用来便捷的获取主线程的Looper的。Looper也是可以退出的,他提供了quit和quitSafely方法来退出Looper,quit是直接退出Looper,而quitSafely会先处理完毕消息队列的消息再退出。

  1. public void quit() {
  2. mQueue.quit(false);
  3. }
  4. public void quitSafely() {
  5. mQueue.quit(true);
  6. }

Looper退出后,通过Handler发送的消息会失败,如果在子线程中创建Looper我们应该在不需要的时候终止Looper。Looper的loop方法工作流程:loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。当Looper执行quit方法时,会调用MessageQueue的quit或者quitSafely来通知消息队列的退出,当队列被标记退出状态时,它的next会返回null.通过MessageQueue.next来获取新消息,否则会一直阻塞,MessageQueue的next返回新消息,Looper就会处理这条消息调用msg.target.dispatchMessage()方法。其实这个target就是Message持有的Handler引用。所以最终是交给Handler调用dispatchMessage()来处理这个消息。因为dispatchMessage是在创建Handler时所使用Looper中执行的,这样就把代码逻辑切换到了指定线程中执行了。

Handler的工作原理:

其实Handler的工作就是消息的发送和接收,发送消息可以通过一系列的post方法和send方法,而post方法最终也是通过send来发送的。那么最终都是走下面这个方法。

  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  2. MessageQueue queue = mQueue;
  3. if (queue == null) {
  4. RuntimeException e = new RuntimeException(
  5. this + " sendMessageAtTime() called with no mQueue");
  6. Log.w("Looper", e.getMessage(), e);
  7. return false;
  8. }
  9. return enqueueMessage(queue, msg, uptimeMillis);
  10. }

Handler发送消息的过程就是仅仅是向消息队列中插入了一条消息,MessageQueue的next方法返回了这个消息交给Looper,Looper接收到消息就开始处理了。最终的消息是交由Handler处理,即Handler的disaptchMessage会被调用,这时候Handler就进入了消息处理的过程,Handler处理消息的过程如

  1. public void dispatchMessage(Message msg) {
  2. if (msg.callback != null) {
  3. handleCallback(msg);
  4. } else {
  5. if (mCallback != null) {
  6. if (mCallback.handleMessage(msg)) {
  7. return;
  8. }
  9. }
  10. handleMessage(msg);
  11. }
  12. }

首先检查Message的callBack是否为空,不为空就通过handleCallback来处理消息,Message的CallBack就是一个Runnable对象,实际上就是通过post方法传递的Runnable参数,其次是检查mCallBack是否为null,不为空就调用mCallBack的handleMessage()方法,通过CallBack我们可以如下创建Handler。 Handler handler=new Handler(callback).这里不需要派生一个子类对象,在日常中我们就是创建Handler子类对象,并重写handleMesaage方法。不过注意callback的handleMessage方法的返回值boolean会影响到Handler自己的handleMessage的调用,用到的时候要注意。

程序的入口分析

  1. public static void main(String[] args) {
  2. SamplingProfilerIntegration.start();
  3. // CloseGuard defaults to true and can be quite spammy. We
  4. // disable it here, but selectively enable it later (via
  5. // StrictMode) on debug builds, but using DropBox, not logs.
  6. CloseGuard.setEnabled(false);
  7. Environment.initForCurrentUser();
  8. // Set the reporter for event logging in libcore
  9. EventLogger.setReporter(new EventLoggingReporter());
  10. Security.addProvider(new AndroidKeyStoreProvider());
  11. // Make sure TrustedCertificateStore looks in the right place for CA certificates
  12. final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
  13. TrustedCertificateStore.setDefaultUserDirectory(configDir);
  14. Process.setArgV0("<pre-initialized>");
  15. Looper.prepareMainLooper();
  16. ActivityThread thread = new ActivityThread();
  17. thread.attach(false);
  18. if (sMainThreadHandler == null) {
  19. sMainThreadHandler = thread.getHandler();
  20. }
  21. if (false) {
  22. Looper.myLooper().setMessageLogging(new
  23. LogPrinter(Log.DEBUG, "ActivityThread"));
  24. }
  25. Looper.loop();
  26. throw new RuntimeException("Main thread loop unexpectedly exited");
  27. }

这是Android应用程序启动的入口main方法。注意到这里面比较关键的几行代码

  • Looper.prepareMainLooper();点进方法里可以看到在该方发中首先从ThreadLocal中获取到looper对象,如果存在则抛出异常(只能创建一次),然后new Looper()创建一个Looper对象并与当前的线程绑定,具体的绑定方式请随我看。首先获取到当前所在的线程,然后通过该线程对象获取到该线程的 ThreadLocalMap集合,然后以当前线程对象做为key,所创建的Looper对象作为value进行map结合的存储。这样Looper与线程之间通过ThreadLocal就进行绑定了起来。
  1. sThreadLocal.set(new Looper(quitAllowed));
  2. public void set(T value) {
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. }
  • ActivityThread thread = new ActivityThread();这句话创建了ActivityThread对象, 大家如果看源码的话可以看到该类定义了一个H mH的成员变量,没错这个mH就是系统的Handler,很特别,该变量在定义的时候就进行了初始化,这行代码走完之后,mH就相应的有值了。
  • Looper.loop();这个方法就比较牛了,可以说我们应用程序的运行就依赖于它,点进该方法可以看到,它里面是一个for死循环。不断的进行消息的获取处理,从而使我们程序一直运行下去。

补充说明消息机制中涉及的几个要素

  • Looper:Looper其实是充当着一个循环器的作用,它的内部持有MesageQueue、Thread、和ThreadLocal(其内部的map集合用于存储Looper和线程,实现两者的绑定)其中prepare()方法用于创建Looper对象并进行存储(存储方式前面说过),loope方法即是一个消息处理的循环器。,不断的从MessageQueue中取出消息交由Handler处理。
  • Message消息对象,它的内部有持有Handler和Runnable的引用以及消息类型。其中有一个比较重要的方法obtain(),取消息,该方法的内部是先通过消息池来获取池中的消息,没有则创建,实现了Message对象的复用。其内部有一个target引用,该引用是一个Handler对象的引用,在Looper中提到的消息处理就是通过Message持中的target引用来调用Handler的dispatchMessage()方法来实现消息的处理。还有这个Runnable引用。这个引用会在消息的处理中看到,在dispatchMessage()方法中会先判断这个Message得callBack是否为空,如果不为空则走handleCallBack方法最终将走到Runnable的run方法

这个我们经常遇到,比如我们通过Handler.post().....等一系列post方法,该方法实现消息发送的原理就是将线程任务Runnable封装到消息对象Message中,最终会走到这个Runnable的run方法中执行,所以这个过程中并没有开启一个线程,仍然是在主线程中运行的。因此不要有耗时的操作,否则会阻塞主线程。

  • MessageQueue:消息队列,其实不应该这样称呼,应为他的结构并不是一个队列,而是一个链表(这样说方便理解)它实现了对消息Message的存储,以及消息在链表中的排列以取出消息的顺序。
  • Handler:Handler主要是扮演者消息发送和消息处理的角色,这里我将对他的一些方法进行介绍:

(1)构造方法:Handler有一系列的构造方法,这个自行查阅,他也可以有自己的回调处理CallBack,在消息处理dispatchMessage中我们可以看到有这样一个判断,如果mCallback!=null会执行它自己的handleMessage方法,该方法的返回值直接决定了下面handleMessage的执行与否。

(2)一系列的Post()方法(原理上面说过啦)其实最终都是将任务进行了消息的封装插入到MessageQueue中,最终Runnable不为空,处理消息时会回调自己的run()方法。

(3)sendMessage()....等方法也是对消息就行了封装最后插入到了消息对列中

(4)removeCallbacks(Runnabler)改方法是对通过post发送方式进行消息的清除,还有removeMessage(int what)通过消息类型进行移除,还removeAllbacksAndAMessages()将会移除所有的callbacks和messages

总结

在主程序的入口main方法中进行了主线程Looper的创建以及Handler的创建,以及将改Looper与主线程绑定。然后通过Looper.loop方法进行消息的循环,不断的从消息队列(在初始化Looper的构造函数中进行了MessageQueue的初始化)取出消息,然后交给Message所持有的Handler来处理,Handler通过调用dispatchMessage()方法来处理消息。从而形成了整个消息系统机制。注意:因为我们一般使用Handler都是在主线程中,不用考虑Looper的创建,因为刚才说了,启动程序时候默认给我们创建了一个Looper对象,所在在这个环境下我们可以自由使用Handler,但是如果我们要在子线程中使用Handler就必须先通过Looper.prepare()方法创建一个Looper对象,然后创建handler对象然后通过Looper.loop()方法实Loop循环,不断的处理消息。

重温Android中的消息机制的更多相关文章

  1. 浅析Android中的消息机制(转)

    原博客地址:http://blog.csdn.net/liuhe688/article/details/6407225 在分析Android消息机制之前,我们先来看一段代码: public class ...

  2. 浅析Android中的消息机制(转)

    在分析Android消息机制之前,我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListen ...

  3. 浅析Android中的消息机制-解决:Only the original thread that created a view hierarchy can touch its views.

    在分析Android消息机制之前,我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListen ...

  4. 浅析Android中的消息机制

    在分析Android消息机制之前,我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListen ...

  5. 谈谈对Android中的消息机制的理解

    Android中的消息机制主要由Handler.MessageQueue.Looper三个类组成,他们的主要作用是 Handler负责发送.处理Message MessageQueue负责维护Mess ...

  6. Android中的消息机制

    在分析Android消息机制之前.我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListen ...

  7. Android中对消息机制(Handler)的再次解读

    今天遇到一些关于在子线程中操作Handler的问题,感觉又要研究源代码了,但是关于Handler的话,我之前研究过,可以参考这篇文章:http://blog.csdn.net/jiangwei0910 ...

  8. Android中的消息机制:Handler消息传递机制

    参考<疯狂android讲义>第2版3.5 P214 一.背景 出于性能优化考虑,Android的UI操作并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为 ...

  9. Android中的消息机制:Handler消息传递机制 分类: H1_ANDROID 2013-10-27 22:54 1755人阅读 评论(0) 收藏

    参考<疯狂android讲义>第2版3.5 P214 一.背景 出于性能优化考虑,Android的UI操作并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为 ...

随机推荐

  1. 腾讯QQAndroid API调用实例(QQ分享无需登录)

    腾讯QQAndroid API调用实例(QQ分享无需登录)   主要分为两个步骤: 配置Androidmanifest.xml 修改activity里边代码 具体修改如下:   1.Activity代 ...

  2. Docker - 导出导入容器

    导出和导入容器 使用docker export命令可以将本地容器导出为容器快照文件. 使用docker import命令可以将容器快照文件导入到本地镜像库,也可以通过指定URL或者某个目录来导入. 特 ...

  3. apache代理转发

    打开apache安装目录的conf文件夹下的httpd.conf1.将以下两行前的注释字符 # 去掉:#LoadModule proxy_module modules/mod_proxy.so#Loa ...

  4. UI基础控件—UIView

    1. 什么是UIView?     UIView :代表屏幕上的一个矩形区域,管理界面上的内容; 2. 创建UIview a.开辟空间并初始化视图(初始化时,给出视图位置和大小) b.对视图做一些设置 ...

  5. 基于本地文件系统的LocalDB

    零.前言 之前写一些小工具的时候,需要用到数据存储方面的技术,但是用数据库又觉得太大了,本地文件存储txt文件存储又不是很规范,于是乎想到了去编写一个简单的基于本地文件系统的数据存储库,暂且叫它loc ...

  6. 当一个JavaScripter初次进入PHP的世界,他将看到这样的风景

     本文将从以下11点介绍javascript和PHP在基础语法和基本操作上的异同: 1.数据类型的异同 2.常量和变量的定义的不同,字符串连接运算符不同 3.对象的创建方法的不同 4.PHP与JS在变 ...

  7. SSM框架中常用的注解

    @Controller:在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model , ...

  8. 《算法4》1.5 - Union-Find 算法解决动态连通性问题,Python实现

    Union-Find 算法(中文称并查集算法)是解决动态连通性(Dynamic Conectivity)问题的一种算法,作者以此为实例,讲述了如何分析和改进算法,本节涉及三个算法实现,分别是Quick ...

  9. php原生curl接口的请求

    /** * @desc 接口请求处理 * @date 2017/5/19 11:39 * @param [$url请求的接口地址,$way为false为get请求,true为post请求] * @au ...

  10. 快速找到ARP病毒源

    第一招:使用Sniffer抓包 在网络内任意一台主机上运行抓包软件,捕获所有到达本机的数据包.如果发现有某个IP不断发送请求包,那么这台电脑一般就是病毒源.原理:无论何种ARP病毒变种,行为方式有两种 ...