关注【搜狐技术产品】公众号,第一时间获取技术干货

导读

在Android中,Handler一直是一个热点,在开发过程中,它的使用频率很高,而且在Android源码中Handler都是常客。那么Handler是如何工作的呢,并且在使用时为何有一些特殊规定需要遵守呢,异步传递消息时消息是如何保存与分发的呢?今天我们从源码角度看一下Handler的设计与实现。

需求背景

在Android开发中,经常会遇到需要在不同线程之间切换的需要,比如网络请求(Android为了防止出现ANR异常,所以规定在Main Thread中不能进行网络请求,且最好不要进行耗时操作),子线程通信等等。此外,Android在设计之初,为了安全和用户体验考虑,规定了只允许在Main Thread里面进行UI更新,而不能在子线程里面进行,否则会抛出异常。这个时候,就需要用到Android的消息传递机制 — Handler。在Android中,Handler消息传递机制主要依赖于Handler,Message,MessageQueue,Looper。其中:

•Handler负责消息的发送与接收处理。•Message负责消息的封装,他本身可以看做消息的载体。•MessageQueue:是一个消息队列,所有需要发送的消息用类似于链表的形式进行存储,并且依据于消息消费的时间为标志确定存储位置。•Looper:进行消息循环与消息分发。简单使用如下:

 1Handler handler = new Handler() {2      @Override3      public void handleMessage(Message msg) {4          super.handleMessage(msg);5          LogUtils.d("腾讯云Imi  handler out --");6          if (TLSService.getInstance() != null && TLSService.getInstance().getLastUserIdentifier() != null) {7              isIMNotinit = false;8              presenter.getUnreadNum(getContext());9              presenter.getConversation(getContext(), true);
10              LogUtils.d("腾讯云Imi  handler");
11          }
12      }
13  };

在使用Handler时,创建一个Handler并重写其handleMessage 方法,参数Message中包含了传递的信息,信息来源等等,可以根据msg.what判断消息来源并做相应处理。但不建议这样直接创建,如果这样在Activity中创建的话,当activity被finish之后,可能有消息还在继续发送,而此时的message中保留有activity中handler的引用,而这个handler里面有这个被finish的activity的隐式引用,导致此activity无法被销毁,这样就会有内存泄漏的风险。解决办法一般是不让Handler内部类不持有外部activity的强引用,如下所示:

 1private static class MyHandler extends Handler {2      private WeakReference<Context> mContextReference;34      public MyHandler(Context c) {5          mContextReference = new WeakReference<Context>(c);6      }78      @Override9      public void handleMessage(Message msg) {
10          final Context c = mContextReference.get();
11          if (c != null) {
12              if (CommonUtils.notEmpty(msg.obj.toString())) {
13                  Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
14                  Uri uri = Uri.fromFile(new File(msg.obj.toString()));
15                  intent.setData(uri);
16                  c.sendBroadcast(intent); // 发送广播通知相册
17              }
18              Toast.makeText(c, "图片下载完成", Toast.LENGTH_SHORT).show();
19          }
20      }
21  }
22private static class MyHandler extends Handler {
23      private WeakReference<Context> mContextReference;
24
25      public MyHandler(Context c) {
26          mContextReference = new WeakReference<Context>(c);
27      }
28
29      @Override
30      public void handleMessage(Message msg) {
31          final Context c = mContextReference.get();
32          if (c != null) {
33              if (CommonUtils.notEmpty(msg.obj.toString())) {
34                  Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
35                  Uri uri = Uri.fromFile(new File(msg.obj.toString()));
36                  intent.setData(uri);
37                  c.sendBroadcast(intent); // 发送广播通知相册
38              }
39              Toast.makeText(c, "图片下载完成", Toast.LENGTH_SHORT).show();
40          }
41      }
42  }
43private static class MyHandler extends Handler {
44      private WeakReference<Context> mContextReference;
45
46      public MyHandler(Context c) {
47          mContextReference = new WeakReference<Context>(c);
48      }
49
50      @Override
51      public void handleMessage(Message msg) {
52          final Context c = mContextReference.get();
53          if (c != null) {
54              if (CommonUtils.notEmpty(msg.obj.toString())) {
55                  Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
56                  Uri uri = Uri.fromFile(new File(msg.obj.toString()));
57                  intent.setData(uri);
58                  c.sendBroadcast(intent); // 发送广播通知相册
59              }
60              Toast.makeText(c, "图片下载完成", Toast.LENGTH_SHORT).show();
61          }
62      }
63  }

把Handler设置为静态类,静态类不持有外部类的对象,所以activity销毁的时候不会受到Handler的限制,并且在Handler里面用到activity是持有的弱引用,不影响外部activity的销毁。

消息发送

上面是Handler接收到消息之后的最终处理,接下来看看消息发送的情况。Handler有多个方法用来应对不同情景的数据发送,以下为几个例子:

1.sendEmptyMessageDelayed(int what, long delayMillis) ,带有消息来源的延时消息发送。

1public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
2  Message msg = Message.obtain();
3  msg.what = what;
4  return sendMessageDelayed(msg, delayMillis);
5}

2.sendEmptyMessage(int what) 只包含来源身份信息的空消息发送。

1public final boolean sendEmptyMessage(int what)
2{
3  return sendEmptyMessageDelayed(what, 0);
4}

3.sendEmptyMessageDelayed(int what, long delayMillis)延时空消息发送

1public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
2  Message msg = Message.obtain();
3  msg.what = what;
4  return sendMessageDelayed(msg, delayMillis);
5}

4.sendMessageAtFrontOfQueue(Message msg)把消息放入队列头部,其实实现是把延时发送消息设置为0。

 1public final boolean sendMessageAtFrontOfQueue(Message msg) {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, 0);
10  }

可以看到,所有方法都调用到了sendMessageAtTime方法,只是参数不同而已。

 1public 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}

方法参数Message信息与消息消费时间。判断了是否存在MessageQueue ,为空则会报错,存在则把消息根据时间放入queue中,再看看这个MessageQueue 是什么时候初始化的。

 1public Handler(Callback callback, boolean async) {2      if (FIND_POTENTIAL_LEAKS) {3          final Class<? extends Handler> klass = getClass();4          if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&5                  (klass.getModifiers() & Modifier.STATIC) == 0) {6              Log.w(TAG, "The following Handler class should be static or leaks might occur: " +7                  klass.getCanonicalName());8          }9      }
10
11      mLooper = Looper.myLooper();
12      if (mLooper == null) {
13          throw new RuntimeException(
14              "Can't create handler inside thread that has not called Looper.prepare()");
15      }
16      mQueue = mLooper.mQueue;
17      mCallback = callback;
18      mAsynchronous = async;
19  }

通过跟踪我们可以发现,用来存储消息的MessageQueue 是在Handler初始化的时候初始化的,mQueue = mLooper.mQueue ; 且这个mLooper也是这个时候通过Looper.myLooper();初始化的,我们现在去Looper里面看看这个初始化过程:

1public static @Nullable Looper myLooper() {
2   return sThreadLocal.get();
3}

可以发现,是从一个ThreadLocal里面拿到的Looper对象,继续查看是在哪里进行Looper对象的设置的。

 1private 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}78private Looper(boolean quitAllowed) {9     mQueue = new MessageQueue(quitAllowed);
10     mThread = Thread.currentThread();
11 }

通过跟踪观察可以发现,Looper是在通过Looper.prepare() 方法把Looper对象放入ThreadLocal对象里面的,并且在此时创建了MessageQueue对象。所以当我们在子线程使用Handler的时候,在创建Handler前必须要先调用Looper.prepare() 方法的原因了,因为在创建Handler的时候会进行Looper的初始化。如果没有先调用Looper.prepare() 的话,sThreadLocal.get() 的值就为空,就会抛出RuntimeException。此外有个特例就是,如果我们在Main Thread里面使用Handler的话则不需要使用Looper.prepare() 就可以直接创建Handler使用了,其实这是因为在Android程序创建的时候已经调用过这个方法了。Android程序刚开始的入口是ActivityThread的main方法,在里面可以看到相关的设定:

 1public static void main(String[] args) {2      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");3      SamplingProfilerIntegration.start();45      // CloseGuard defaults to true and can be quite spammy.  We6      // disable it here, but selectively enable it later (via7      // StrictMode) on debug builds, but using DropBox, not logs.8      CloseGuard.setEnabled(false);9
10      Environment.initForCurrentUser();
11
12      // Set the reporter for event logging in libcore
13      EventLogger.setReporter(new EventLoggingReporter());
14
15      // Make sure TrustedCertificateStore looks in the right place for CA certificates
16      final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
17      TrustedCertificateStore.setDefaultUserDirectory(configDir);
18
19      Process.setArgV0("<pre-initialized>");
20
21      Looper.prepareMainLooper();
22
23      ActivityThread thread = new ActivityThread();
24      thread.attach(false);
25
26      if (sMainThreadHandler == null) {
27          sMainThreadHandler = thread.getHandler();
28      }
29
30      if (false) {
31          Looper.myLooper().setMessageLogging(new
32                  LogPrinter(Log.DEBUG, "ActivityThread"));
33      }
34
35      // End of event ActivityThreadMain.
36      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
37      Looper.loop();
38
39      throw new RuntimeException("Main thread loop unexpectedly exited");
40  }

我们可以看到,在ActivityThread的main方法里面,调用了Looper.prepareMainLooper();方法创建Looper对象,并且最后还调用了Looper.loop(); 方法进行消息循环,这也是在Main Thread里面调用为何不需要提前用Looper.prepare()就可以直接使用的原因了。我们再看看Looper.prepareMainLooper() 是怎么创建Looper对象的:

1public static void prepareMainLooper() {
2  prepare(false);
3  synchronized (Looper.class) {
4      if (sMainLooper != null) {
5          throw new IllegalStateException("The main Looper has already been prepared.");
6      }
7      sMainLooper = myLooper();
8  }
9}

在prepareMainLooper方法里面调用了prepare方法,并且Main Thread里面和其他子线程里面都保证了只能创建一次Looper对象,即一个线程只能有一个Looper对象,否则会抛出IllegalStateException 异常。

到此对Handler,Looper的创建做一个总结:

•在子线程创建使用Handler的时候必须先调用Looper.prepare() 方法创建Looper,Looper里面创建的对象由一个ThreadLocal对象进行存储,主线程创建使用Handler的时候不需要提前调用Looper.prepare()方法,因为在程序开始的时候在ActivityThread的main方法里面已经调用过Looper.prepareMainThread() 方法了。每个线程里面只能调用Looper.prepare() 方法一次,只能创建一个Looper对象,否则会抛出异常。•Handler创建的时候,会调用Looper的方法去实例化Looper和MessageQueue对象。

消息存储与消息循环

几乎所有的发送消息的方法最后都调用了sendMessageAtTime 方法,在这个发放中判断了是否存在MessageQueue对象之后调用了enqueueMessage 方法,在这个方法中,把msg.targt 设置为此Handler对象,注意,这里是用于之后在MessageQueue里面消费消息时找到对应的处理此消息的Handler,然后调用MessageQueue的enqueueMessage() 方法,此方法主要是用于把消息放入MessageQueue里面,即入队操作。

 1boolean 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      }89      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  }

Message消息通过MessageQueue的enqueueMessage 方法进行入队操作,在此方法中先判断msg.target 是否存在,当前消息是否在使用中,是否收到退出信息等的检测,全部正常才进行入队,从上面代码我们可以看到,MessageQueue是通过类似于链表的形式存储Message消息的,且是通过消费时间进行排序的,通过前面我们可以知道,入队用于排序的时间是Message的消费时间,即System.currentTime()+delayMillis ;循环遍历消息队列,找到msg.when 应该插入的地方,插入链表,完成消息的入队操作。这里还需要注意的是nativeWake(mPtr) ;方法,这个方法是用通过JNI实现的,即在底层通过C实现的,然后通过JNI调用,在底层中维持了一个mWakeEventFd 文件,这个文件是专门用于进程和线程之间通信的,并且通过epoll 监控。这里在入队完成之后会调用此方法,通过此方法会向MwakeEventFD 文件写入一个uint64_t ,这个是用于唤醒消息循环线程的,在后面消息取出的时候进行详细说明。

通过上面的方法就完成了消息发送过程,接下来则是消息循环过程,当消息的消费时间到了之后取出相应的消息进行消费。通过上面的时候我们知道在线程中使用完Handler之后需要调用Looper.loop()完成整个过程,在主线程不需要我们操作是因为ActivityThread里面已经调用过了,在loop() 里面做的就是消息的阻塞等待。

 1public 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;78      // 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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
28
29          final long traceTag = me.mTraceTag;
30          if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
31              Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
32          }
33          final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
34          final long end;
35          try {
36              msg.target.dispatchMessage(msg);
37              end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
38          } finally {
39              if (traceTag != 0) {
40                  Trace.traceEnd(traceTag);
41              }
42          }
43          if (slowDispatchThresholdMs > 0) {
44              final long time = end - start;
45              if (time > slowDispatchThresholdMs) {
46                  Slog.w(TAG, "Dispatch took " + time + "ms on "
47                          + Thread.currentThread().getName() + ", h=" +
48                          msg.target + " cb=" + msg.callback + " msg=" + msg.what);
49              }
50          }
51
52          if (logging != null) {
53              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
54          }
55
56          // Make sure that during the course of dispatching the
57          // identity of the thread wasn't corrupted.
58          final long newIdent = Binder.clearCallingIdentity();
59          if (ident != newIdent) {
60              Log.wtf(TAG, "Thread identity changed from 0x"
61                      + Long.toHexString(ident) + " to 0x"
62                      + Long.toHexString(newIdent) + " while dispatching to "
63                      + msg.target.getClass().getName() + " "
64                      + msg.callback + " what=" + msg.what);
65          }
66
67          msg.recycleUnchecked();
68      }
69  }

•从代码中我们可以看出,这是个无限循环方法等待获取可以处理的消息,在循环里面,通过do-while 找到一个不为空且是asynchronous的消息,找到之后检测这个消息是否到了执行时间,如果不到的话,通过Math.min(msg.when - now, Integer.MAX_VALUE) ;设置等待时间,如果这个消息找到了,且到了执行时间了,那么就取出此消息,并把链表中此消息删除,把这个消息通过msg.markInUse();标志为使用中,把这个找到的消息返回给Looper的loop方法,进行消息分发。如果到了链表尾部还是没有符合的消息,则进入阻塞等待过程。

•另外在此方法中还得注意nativePollOnce(ptr, nextPollTimeoutMillis) ;这个方法也是JNI调用C++实现的。之前说到在通过Handler发送消息的时候,会向mWakeEventFd文件写一个uint_64 ,而nativePollOnce 方法则阻塞监听mWakeEventFd文件以及唤醒消息循环线程的。当有消息发送的时候就会写入一个uint_64 ,而nativePollOnce 里面则是一直监听这个文件,当有写入操作发生时,就会唤醒epoll_wait即消息循环线程,线程就会去取出队列里面的消息去执行操作,当队列里面没有消息的时候,又会继续等待,当下次有信息写入的时候则再次唤醒线程去取出队列消息。这样就完成一次消息循环。

•继续看上面的取出消息之后的处理,上面说到取出消息之后loop()方法是调用了msg.target.dispatchMessage方法进行消息处理,而msg.target是之前设置的目标Handler,现在去Handler里面看看dispatchMessage的处理:

 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的话则直接message.callback.run() ;处理消息,如果有设置Handler的callback ,也进行分发;如果都没有的话,那就直接调用handleMessage(msg);还记得我们最开是的时候写的简单使用Handler的例子么,里面重写了一个方法,就是handleMessage(msg);所以到这里就清楚了,前面发出的消息就是在这里进行处理的。另外还有很多经常用的方法都是通过包装的Handler来进行的,比如:

1.Activity.runOnUiThread(Runnable action)

1public final void runOnUiThread(Runnable action) {
2    if (Thread.currentThread() != mUiThread) {
3        mHandler.post(action);
4    } else {
5        action.run();
6    }
7}

这个是Activity的方法,用于在切换到主线程运行,可以看到内部实现是判断当前线程是否是主线程,如果是的话直接运行,不是的话用mHandler.post发送出去,而mHandler是申明在主线程的Handler,经过发送之后再主线程的Handler里面完成调用。

2.View.post()

 1public boolean post(Runnable action) {2  final AttachInfo attachInfo = mAttachInfo;3  if (attachInfo != null) {4      return attachInfo.mHandler.post(action);5  }67  // Postpone the runnable until we know on which thread it needs to run.8  // Assume that the runnable will be successfully placed after attach.9  getRunQueue().post(action);
10  return true;
11}

View.post()用于异步更新view,也是找到主线程Handler,然后通过Handler发送消息,在主线程更新Handler。另外还有很多经典方法都是用Handler更新数据的,最典型的是Android官方提供的异步工具AsyncTask,用它可以进行异步请求或者异步下载数据,下载图片等等,AsyncTask就是用Handler实现的多线程异步工具,开启子线程进行数据请求,然后通过Handler发送数据消息,最好在主线程里面处理收到的消息,完成异步请求。

总结

•在使用Handler的时候,主线程不需要调用Looper.prepare()创建Looper,因为在程序开始的时候在ActivityTask里面的main方法里面调用了Looper.prepareMainLooper()创建了Looper对象,子线程中需要先调用Looper.prepare()创建Looper对象。每个线程只能有一个Looper对象。在创建Looper的时候会初始化MessageQueue对象,因为在创建Handler的时候需要是用到Looper对象以及MessageQueue对象,并且在Looper中保存示例是放在ThreadLocal里面的。

•初始完成之后用Handler发送信息,包括信息数据Message以及delayMillis,之后调用MessageQueue的enqueueMessage()把消息按类似于链表结构并按照时间顺序排列,此时还会调用nativeWake(mPtr)方法向底层的mWakeEventFd文件写入uint64_t,唤醒消息循环线程。子线程发送完数据之后调用Looper.loop(),死循环等待到时间处理的消息出队,Looper取到消息之后,就进行消息分发,根据设置的callback进行处理消息,如果没有设置则调用Handler的handleMessage处理消息。MessageQueue的next方法负责进行消息出队操作,无限循环检查是否有到时间的消息,如果有则把他出队,如果没有则循环阻塞。

•出队消息然后交给Looper的loop方法进行消息分发。在消息出队的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)方法,这个方法是用来通过epoll监听mWakeEventFd文件的,当监听到有uint64写入文件的时候唤醒消息循环线程取出消息并处理,没有消息的时候沉睡等待下次uint64的写入。这样就完成了一次消息的收发。


搜狐新闻推荐算法 | 呈现给你的,都是你所关心的​mp.weixin.qq.com新闻推荐系统的CTR预估模型​mp.weixin.qq.com互联网架构演进之路​mp.weixin.qq.comEmbedding 模型在推荐系统的应用​mp.weixin.qq.com深入理解Flutter多线程​mp.weixin.qq.com


加入搜狐技术作者天团,千元稿费等你来!

获取更多资讯请关注微信公众号【搜狐技术产品】,微信后台联系搜狐技术产品小助手。

android 手机内存uri_Android消息机制Handler原理解析相关推荐

  1. android 手机内存uri_android 的各种目录详解

    App独立文件 app独立文件就是那些不依赖于某特定app的文件 这类文件当我们删除应用之后,还应该保留在手机上的,例如拍照的照片,不应该随着删除应用而被删除掉. 这类文件应该是随着app删除而一起删 ...

  2. android handler的机制和原理_一文搞懂handler:彻底明白Android消息机制的原理及源码

    提起Android消息机制,想必都不陌生.其中包含三个部分:Handler,MessageQueue以及Looper,三者共同协作,完成消息机制的运行.本篇文章将由浅入深解析Android消息机制的运 ...

  3. Android消息机制Handler用法

    这篇文章介绍了Android消息机制Handler用法总结,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 1.简述 Handler消息机制主要包括: Messa ...

  4. Android消息机制(Handler机制) - 线程的等待和唤醒

    我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息. 通常在消息队列中(MessageQueue)中没有消 ...

  5. android手机内存使用情况分析

    android手机内存使用情况分析         通常客户经常纠结手机内存的使用率不合理,占有的内存太大,可用内存很少,客户往往需要给出解决方案或在给出原因,那么你首先需要知道手机的内存都被什么应用 ...

  6. android 将图片储存到手机内存不足,Android手机内存不足的解决方法

    Android手机内存不足的解决方法 如果你的安卓手机内存不是很大,安装较多软件,使用一段时间后,安卓手机和容易引起内存不足,会提示手机内存不足,然后速度变慢,无法安装新软件等等.很多对安卓手机不太了 ...

  7. Android手机内存管理与性能优化

    Android手机内存管理与性能优化&JNI.NDK高级编程(JNI.Dalvik.内存监测) 课程分类:Android 适合人群:中级 课时数量:34小节课时 用到技术:Dalvik,DDM ...

  8. Android 消息机制 Handler总结

    老久就想着写一篇 关于消息机制的文章来总结一下. Android的消息机制主要是指Handler 的运行机制.我们在开发时有的时候需要在子线程进行耗时的I/o 操作,可能是读取文件或者 访问网络等,有 ...

  9. android 消息轮训,Android消息机制Handler,有必要再讲一次

    我们在日常开发中,总是不可避免的会用到 Handler,虽说 Handler 机制并不等同于 Android 的消息机制,但 Handler 的消息机制在 Android 开发中早已谙熟于心,非常重要 ...

最新文章

  1. CSS3背景线性渐变
  2. 前端学习笔记2017.6.21-引入JS文件的方法
  3. Css框架and公共Css文件
  4. 软件编程思想的些许感想
  5. Form表单中的元素 控件集
  6. 为ASP.NET MVC应用添加自定义路由
  7. Spring Security Oauth2 (三) 密码码模式
  8. FIR滤波器FPGA实现
  9. Ubuntu16.04下Hadoop+Hive+HBase安装部署实践
  10. vuex实现及简略解析
  11. 指定Pycharm在py3.6环境下启动
  12. DarkMode 设置关闭 iOS 13, 适用于 XCode 11, XCode 10
  13. 【FPGA】 Altera FPGA 入门篇(1)
  14. lm opencv 算法_LM算法
  15. 背景图片轮播html代码,一款常见的背景图片轮播动效
  16. ubuntu 降级linux内核,ubuntu16内核降级
  17. Garbled Circuits介绍 - 4 混淆电路的优化
  18. 双非年薪40W,从字节裸辞了
  19. 装的机械硬盘计算机里没有反应,固态硬盘和机械硬盘运行打开我的计算机图标,有时候读取没有响应,单独用固态硬盘是没问题的 ,机械硬盘也测试了,没有坏道什么的,始终找不到原因...
  20. Ubuntu恶意文件分析环境搭建--安装Cuckoo Sandbox记录

热门文章

  1. string wstring
  2. Windows下误删文件解决办法
  3. DenyHosts清除黑名单IP地址方法
  4. 2010年11月8日,早会资料(日本的文化节)。CSDN博客系统出现问题了,这篇我就不改了,留着,在写一个。
  5. 20-21-2网络管理quiz5
  6. 用R3写卡成PRL302,解决新电信卡PRL301只能1X不能上3G问题
  7. 后端CORS解决跨域问题
  8. windows运行xcopy计划任务 结果是0x4解决方案
  9. socket编程 TCP 粘包和半包 的问题及解决办法
  10. [解决方案]WebAPI+SwaggerUI部署服务器后,访问一直报错的问题