Android Handler 分析学习
一、Handler简介
Handler 是 Android 中用于线程间交互的机制。与其相关的概念有 Thread、Looper、Runnable、Message、MessageQueue 等。
Google 的 Android Developer 文档中对 Handler 的描述如下:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
本文首先将介绍 Handler 的使用方法,在了解了 Handler 使用方法的基础上深入分析其内部机制。
二、Handler使用方法
Handler 是两个线程之间交互的机制,所以要使用 Handler 首先需要准备两个线程。
在 AndroidStudio 中创建一个简单的示例来演示Handler的使用方法,示例尽量简化了其他无关元素,以突出 Handler 使用方法的演示。
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler; import android.util.Log; import android.widget.TextView; public class MainActivity extends Activity {
private static final String TAG = "ActivityDemo";
private TextView mTextView = null;
// 创建一个 Handler, 该 Handler 将用于设置 MainActivity 中的 TextView
private Handler uiHandler = new Handler(); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textView);
Log.d(TAG, "Create a HandlerDemoThread in main thread."); // 创建并启动一个 HandlerDemoThread
HandlerDemoThread mHandlerDemoThread = new HandlerDemoThread();
mHandlerDemoThread.start();
} class HandlerDemoThread extends Thread {
@Override
public void run() {
try {
Log.d(TAG, "HandlerDemoThread is running!");
Thread.sleep(1000); // 通过 Runnable 设置 MainActivity 中的 mTextView
Runnable mRunnable = new Runnable() {
@Override
public void run() {
MainActivity.this.mTextView
.setText("HandlerDemoThread set text in MainActivity.");
}
};
uiHandler.post(mRunnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述代码中 MainActivity 在 onCreate() 函数中创建并启动了一个新的 HandlerDemoThread,HandlerDemoThread 设置了 MainActivity 的 mTextView 的显示内容。
注意到 HandlerDemoThread 中并没有直接调用 MainActivity.this.mTextView.setText() 函数,而是使用 Runnable 完成对 TextView 的设置。
实际上如果 HandlerDemoThread 直接调用 MainActivity.this.mTextView.setText() 会发生运行时错误。
在这个过程中涉及到两个线程:主线程和 HandlerDemoThread 线程。
在 Android 应用启动时,会自动创建一个程序的主线程(主线程负责 UI 的展示、UI事件消息的派发处理等等,也被称作 UI线程)。
主线程中的 TextView 控件不是线程安全的,因此 HandlerDemoThread 线程需要通过 Handler 机制来访问主线程中的 TextView 控件。
三、 Handler 详解
上述示例演示了 Hanlder 的使用,HandlerDemoThread 使用主线程的 Handler 与主线程交互。但是通过此例我们并不能够一探 Handler 的究竟,这主要是因为主线程已经帮我们把该做的事情都做好了,我们看不到其内部实现。
要想弄清楚 Handler 的机制,首先必须搞清楚 Handler 的实现目的。 Handler 是 Android 中引入的一种让开发者参与处理线程中消息循环的机制,在 Google 的开发者文档中描述为:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue.
该表述中涉及到几个关键词: Handler,Message,Runnable, Thread,MessageQueue。 要搞清楚 Handler 机制,必须搞清楚上述概念,我们首先从最基本的 Thread 入手分析。
3.1 普通线程示例
Thread 不是 Android 中引入的概念,而是从 Java 中继承的概念。关于Thread 我们先不深入分析,只通过一个简单的示例来了解 Thread。
MainActivity.java 的代码:
1 package name.gaoce.looperdemo;
2 import android.app.Activity;
3 import android.os.Bundle;
4 import android.util.Log;
5
6 public class MainActivity extends Activity {
7 private static final String TAG = "ActivityDemo";
8 private Thread mNormalThread;
9
10 @Override
11 protected void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.activity_main);
14 Log.d(TAG, "Start a normal thread in main thread.");
15 mNormalThread = new NormalThreadDemo();
16 mNormalThread.start();
17 }
18 }
MainActivity 在 onCreate 函数中创建了一个 NormalThreadDemo 线程,并调用其 start() 函数启动该线程。
NormalThreadDemo.java 的代码:
1 package name.gaoce.looperdemo;
2
3 import android.util.Log;
4
5 public class NormalThreadDemo extends Thread {
6 private static final String TAG = "NormalThreadDemo";
7
8 public void run() {
9 while (true) {
10 Log.d(TAG, "NormalThread is running.");
11 try {
12 Thread.sleep(1000);
13 } catch (InterruptedException e) {
14 Log.e(TAG, "Thread sleeping is interrupted.");
15 }
16 }
17 }
18 }
这个线程非常简单,其 run() 函数就是一个无穷循环打印一行 Log,然后 sleep 1秒钟。 这个例子中有两个线程,一个是主线程,另一个就是 NormalThreadDemo 线程,两个线程互不干扰。
那么如果现在需要主线程与 NormalThreadDemo 线程交互,该如何做呢?
我们尝试用 Handler 机制可以解决这个问题。
3.2 LooperThread 线程示例
在 3.1 的示例的基础上添加 LooperThread 的类来演示 Handler 的使用,如以下代码所示:
MainActivity.java 的代码:
1 package name.gaoce.looperdemo;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Message;
6 import android.util.Log;
7
8 public class MainActivity extends Activity {
9 private static final String TAG = "MainActivity";
10 private LooperThreadDemo mLooperThread;
11
12 @Override
13 protected void onCreate(Bundle savedInstanceState) {
14 super.onCreate(savedInstanceState);
15 setContentView(R.layout.activity_main);
16 Log.d(TAG, "Start a normal thread in main thread.");
17 mLooperThread = new LooperThreadDemo();
18 mLooperThread.start();
19 /* 等待LooperThreadDemo启动完成 */
20 try{
21 Thread.sleep(1000);
22 }catch(InterruptedException e){
23 Log.e(TAG, "onCreate sleep is interrupted.");
24 }
25 if (mLooperThread.mHandler != null) {
26 Log.d(TAG, "Send a message to LooperThreadDemo.");
27 for(int i = 0; i < 10; i++){
28 Message msg = new Message();
29 msg.what = i;
30 mLooperThread.mHandler.sendMessage(msg);
31 }
32 } else {
33 Log.d(TAG, "LooperThreadDemo's Handler is not ready.");
34 }
35 }
36 }
MainActivity 类中创建并启动了一个 LooperThreadDemo 线程,并通过LooperThreadDemo的 mHandler 向它发送了10个 Message。
LooperThreadDemo.java 的代码:
1 package name.gaoce.looperdemo;
2
3 import android.os.Handler;
4 import android.os.Looper;
5 import android.os.Message;
6 import android.util.Log;
7
8 public class LooperThreadDemo extends Thread {
9 private static final String TAG = "LooperThreadDemo";
10 public Handler mHandler = null;
11
12 @Override
13 public void run() {
14 Log.d(TAG, "Looper thread is running.");
15 Looper.prepare();
16 mHandler = new Handler(Looper.myLooper()) {
17 @Override
18 public void handleMessage(Message msg) {
19 Log.d(TAG, "I got a message: " + msg.what);
20 super.handleMessage(msg);
21 }
22 };
23 Looper.loop();
24 }
25 }
LooperThreadDemo 与 NormalThreadDemo 的不同之处在于其 run() 函数,LooperThreadDemo 的 run() 函数中没有显式的无穷循环,而是使用了 Looper,从名字上看,Looper.loop() 应该就是实现了无穷循环的功能,实际上也正是这样。
除此之外,LooperThreadDemo 还有一个 Handler 类型的成员。接下来我们逐个进行分析。
3.3 Looper 代码分析
Looper 类的实现在 android.os 包的 ”Looper.java“ 文件中。Looper 包含了以下两个关键的成员变量:
- final MessageQueue mQueue;
- final Thread mThread;
- static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Looper 关联了一个 final 类型的 MessageQueue 和一个 final 类型的 Thread,即 Looper 的生命周期内,其关联的 MessageQueue 和 Thread 不能修改。 ThreadLocal 是 Java 语言中定义的一个模板,它的定义如下所示:
public class ThreadLocal<T>
ThreadLocal 代表线程局部变量,它为每一个使用该变量的线程提供一个变量值的副本,那么这里的 sLocalThread 就代表了和线程绑定的 Looper 类型的变量,这个变量同样也是 **final** 的。通过 sLocalThread.get() 函数可以返回该 Looper 类型的变量。
下面我们来看 Looper.prepare() 函数做了什么。
3.3.1 Looper.prepare()
1 /** Initialize the current thread as a looper.
2 * This gives you a chance to create handlers that then reference
3 * this looper, before actually starting the loop. Be sure to call
4 * {@link #loop()} after calling this method, and end it by calling
5 * {@link #quit()}.
6 */
7 public static void prepare() {
8 prepare(true);
9 }
10
11 private static void prepare(boolean quitAllowed) {
12 if (sThreadLocal.get() != null) {
13 throw new RuntimeException("Only one Looper may be created per thread");
14 }
15 sThreadLocal.set(new Looper(quitAllowed));
16 }
上述代码简单明了,就是 new 了一个 Looper 实体并将其设置为 sThreadLocal 所持有的线程局部变量,从代码中可以看到,在一个线程中 Looper.prepare() 函数只能调用一次。
线程执行了 Looper.prepare() 函数后就持有了一个 Looper 类型的线程局部变量,可以用下面的 Looper.myLooper() 获得该Looper。
3.3.2 Looper.myLooper()
1 /**
2 * Return the Looper object associated with the current thread. Returns
3 * null if the calling thread is not associated with a Looper.
4 */
5 public static @Nullable Looper myLooper() {
6 return sThreadLocal.get();
7 }
这个函数就更简单了,就是通过 sThreadLocal.get() 返回线程所持有的 Looper 对象。
3.3.3 Looper.loop()
Looper.loop() 函数稍微复杂一点,我们删除掉一些非核心代码来简化其流程,突出重点。精简后的代码如下所示:
1 /**
2 * Run the message queue in this thread. Be sure to call
3 * {@link #quit()} to end the loop.
4 */
5 public static void loop() {
6 /* me: 线程私有 Looper 对象的引用。*/
7 final Looper me = myLooper();
8 /* queue: 线程私有 Looper 对象关联的 MessageQueue。 */
9 final MessageQueue queue = me.mQueue;
10
11 // Make sure the identity of this thread is that of the local process,
12 // and keep track of what that identity token actually is.
13 Binder.clearCallingIdentity();
14 final long ident = Binder.clearCallingIdentity();
15
16 for (;;) {
17 Message msg = queue.next(); // might block
18 if (msg == null) {
19 // No message indicates that the message queue is quitting.
20 return;
21 }
22
23 try {
24 msg.target.dispatchMessage(msg);
25 } finally {
26 }
27
28 // Make sure that during the course of dispatching the
29 // identity of the thread wasn't corrupted.
30 final long newIdent = Binder.clearCallingIdentity();
31 if (ident != newIdent) {
32 }
33
34 msg.recycleUnchecked();
35 }
36 }
上述代码段删减掉了一部分内容,以凸显主要流程,可参考原始代码进行学习。
上述代码的逻辑也很清晰,就是一个无穷循环不断地从线程私有 Looper 对象关联的 MessageQueue 中读取 Message 并将其分发到对应的 target 处理。
由此可见, Looper.loop() 的本质就是一个无穷循环函数,不断地从 MessageQueue 中读取 Message 并分发处理。所以一个线程只能有一个 Looper,否则,如果一个线程包含了多个 Looper,那么只能有一个 Looper 的 loop() 函数能得到执行。
这里我们留下一个疑问,待后续分析:
- Message 都分发到什么地方去处理了?
如上所述,Looper.loop() 函数就是开始了一个无穷循环来处理 MessageQueue 中的 Message。 到此,LooperThreadDemo 的 run() 函数中还有一个 Handler 没有分析。 Handler 是线程之间交互的重要工具,下面我们对其进行深入分析。
3.3.4 Handler
Handler 定义在 android.os 包的 Handler.java 文件中,是 Android 引入的机制。
Handler 包含如下重要的成员变量:
- final Looper mLooper;
- final MessageQueue mQueue;
- final Callback mCallback;
- final boolean mAsynchronous;
- IMessenger mMessenger;
Handler 有多个构造器,我们只从该例中使用的构造器入手分析,其他的大同小异。 Handler 的构造器函数:
1 public Handler(Looper looper) {
2 this(looper, null, false);
3 }
4
5 public Handler(Looper looper, Callback callback, boolean async) {
6 mLooper = looper;
7 mQueue = looper.mQueue;
8 mCallback = callback;
9 mAsynchronous = async;
10 }
LooperThreadDemo 的 run() 函数中使用 new Handler(Looper.myLooper()) 创建了一个 Handler 对象。
从上述 Handler 的构造函数可以看到,创建这个 Handler 对象的时候主要做了几个事情:
- 新创建的 Handler 对象的 mLooper 成员变量引用了线程私有的 Looper 对象。
- 新创建的 Handler 对象的 mQueue 成员变量引用了线程私有的 Looper 对象的 WorkQueue 成员变量。
- 新创建的 Handler 对象的 mCallback 成员变量暂时为 null
- 新创建的 Handler 对象的 mAsynchronous 为 false
重要的是前两条,也就是说 Handler 创建后,绑定了创建它的线程的私有的 Looper 对象,并且与该 Looper 对象所持有的 WorkQueue 建立了关联。
LooperThreadDemo 的 mHandler 作为一个 public 的成员变量,可以在其它线程中被访问,这也正是主线程向 LooperThreadDemo 发送 Message 的方式。
在主线程中,通过 mLooperThread.mHandler.sendMessage(msg); 向 LooperThreadDemo 发送了一个 Message。我们来看一下这个 sendMessage() 函数做了什么。
3.3.5 Handler.sendMessage()
Handler.sendMessage() 的实现代码如下:
1 public final boolean sendMessage(Message msg)
2 {
3 return sendMessageDelayed(msg, 0);
4 }
5
6 public final boolean sendMessageDelayed(Message msg, long delayMillis)
7 {
8 if (delayMillis < 0) {
9 delayMillis = 0;
10 }
11 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
12 }
13
14 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
15 MessageQueue queue = mQueue;
16 if (queue == null) {
17 RuntimeException e = new RuntimeException(
18 this + " sendMessageAtTime() called with no mQueue");
19 Log.w("Looper", e.getMessage(), e);
20 return false;
21 }
22 return enqueueMessage(queue, msg, uptimeMillis);
23 }
sendMessage() 通过几层调用,最终调用了 sendMessageAtTime() 函数,其实际操作就是将 sendMessage(Message msg) 传入的 msg 参数加入到了 Handler 关联的 Looper 的 WorkQueue 中。
至此,我们已经把 Handler 的基本流程搞清楚了,简单总结如下:
- Handler 的使用涉及到两个线程,在我们的示例中即主线程和 LooperThreadDemo 线程,其中 LooperThreadDemo 线程是 Handler 的持有者,主线程是 Handler 的使用者。
- LooperThreadDemo 线程创建并持有一个 Looper,该 Looper 持有一个 MessageQueue,在 Looper.loop() 运行起来后,不断地从 MessageQueue中 读取 Message 并处理。
- LooperThreadDemo 线程创建一个 public 的可以从外部访问的 Handler,该 Handler 关联了 LooperThreadDemo 线程私有的 Looper 及其 MessageQueue。
- 主线程通过 LooperThreadDemo 的 Handler 向 LooperThreadDemo 的线程私有的 Looper 的 MessageQueue 中添加 Message。
以上就是 Handler 机制的基本流程。 但是 Handler 机制的功能还远不止如此,在 4.3.3 中我们还遗留了一个问题:
- Message 都分发到什么地方去处理了?
要解决这几个问题,就要深入分析一下 Message 的实现机制,看看用 Message 还能让 Handler 做什么事情。
四、 Message 分析
4.1 Message 代码分析
Message 定义在 android.os 包的 Message.java 文件中。其类声明为:
1 public final class Message implements Parcelable
其包含如下几个重要的数据成员:
- public int what;
- Handler target;
what 成员记录了该 Message 携带的消息内容,而 target 指明了处理该 Message 的 Handler。
我们再看一下示例中主线程向 LooperThreadDemo 发送 Message 的代码片段。
1 for(int i = 0; i < 10; i++){
2 Message msg = new Message();
3 msg.what = i;
4 mLooperThread.mHandler.sendMessage(msg);
5 }
在这段代码中创建了10个 Message 并通过 mLooperThread.mHandler.sendMessage(msg) 发送出去。
其中创建 Message 和对 msg.what 赋值的过程没有什么特殊的,都是些简单的 new 和赋值操作。
关键看 Handler.sendMessage(Message msg) 函数,在 4.3.5 中我们已经看到 sendMessage 最终是调用了 enqueueMessage(queue, msg, uptimeMillis) 函数将 Message 加入到一个 MessageQueue 中。
这里我们来看一下 enqueueMessage() 函数:
1 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2 msg.target = this;
3 if (mAsynchronous) {
4 msg.setAsynchronous(true);
5 }
6 return queue.enqueueMessage(msg, uptimeMillis);
7 }
该函数的第一行代码把 this 赋值给了待入列的 Message 的 target 成员,this 也就是调用 sendMessage 的 Handler 引用。
再来回顾一下 Looper.loop() 对 Message 的处理代码:
1 msg.target.dispatchMessage(msg);
在 Looper.loop() 的循环中从 MessageQueue 中读取 Message 并通过上述代码处理,也就是调用了 LooperThreadDemo 线程的 mHandler 的 dispatchMessage() 函数。
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 被分发(dispatch)到三个可能的路径进行处理,按照优先级排列为:
- handleCallback(msg):最终调用到 Message 自带的 callback 函数。
- mCallback.handleMessage(msg):mCallback 是 Handler 自带的 Callback 接口,本示例中为 null
- handleMessage(msg):Handler 的成员方法,在 Handler 创建时重新覆盖定义。
在本文的示例中,实际上是执行到了第3条路径。handleMessage 方法在 Handler 创建时被重新覆盖定义。
4.2 一些新的示例
了解了上述 Message 的分发机制,我们就可以尝试一些 Handler 及 Message 的不同的用法。
4.2 中的示例执行了 Message 的第三条处理路径,下面我们实现两个例子来分别执行 Message 的第一和第二条处理路径。 我们还是在之前示例的基础上进行改进。
4.2.1 第一条路径的示例代码
Message 自带 callback 处理函数:
1 package name.gaoce.looperdemo;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.util.Log;
6
7 public class MainActivity extends Activity {
8 private static final String TAG = "MainActivity";
9 private LooperThreadDemo mLooperThread;
10
11 @Override
12 protected void onCreate(Bundle savedInstanceState) {
13 super.onCreate(savedInstanceState);
14 setContentView(R.layout.activity_main);
15 mLooperThread = new LooperThreadDemo();
16 mLooperThread.start();
17 /* 等待LooperThreadDemo启动完成 */
18 try{
19 Thread.sleep(1000);
20 }catch(InterruptedException e){
21 Log.e(TAG, "onCreate sleep is interrupted.");
22 }
23 if (mLooperThread.mHandler != null) {
24 Runnable mRunnable = new Runnable() {
25 @Override
26 public void run() {
27 Log.d(TAG, "I am a runnable sent with a Message.");
28 }
29 };
30 mLooperThread.mHandler.post(mRunnable);
31 } else {
32 Log.d(TAG, "LooperThreadDemo's Handler is not ready.");
33 }
34 }
35 }
MainActivity 中通过 Handler 的 post 函数发送一个 Runnable 给 LooperThreadDemo 线程,LooperThreadDemo 的代码不需要更改。
运行后根据打印出来的 Log 可以看到,LooperThreadDemo 线程中 Handler 的 handleMessage() 并没有运行,取而代之打印 Log 的是 MainActivity 中的 Runnable 的 run() 函数。
我们可以看一下 Handler.post() 的代码:
1 public final boolean post(Runnable r)
2 {
3 return sendMessageDelayed(getPostMessage(r), 0);
4 }
代码非常简单,调用 sendMessageDelayed() 函数发送了一个 Message,和之前示例中的原理是一样的。 再看一下 getPostMessage() 函数会更加清晰。
1 private static Message getPostMessage(Runnable r) {
2 Message m = Message.obtain();
3 m.callback = r;
4 return m;
5 }
从消息池中获取一个消息并将 Runnable 对象赋值给消息的 callback 成员变量,从而构建了一个 Message。 到这里就可以和 Message 分发的第一种执行路径实现了对应。
4.2.2 第二条路径的示例代码
第二种路径要从 Handler 的角度去实现 Handler 的Callback 接口,该接口的实现必须在 Handler 创建时通过参数传递给构造器函数。
1 package name.gaoce.looperdemo;
2
3 import android.os.Handler;
4 import android.os.Looper;
5 import android.os.Message;
6 import android.util.Log;
7
8 public class LooperThreadDemo extends Thread {
9 private static final String TAG = "LooperThreadDemo";
10 public Handler mHandler = null;
11
12 private class HandlerCallback implements Handler.Callback{
13 @Override
14 public boolean handleMessage(Message msg) {
15 Log.d(TAG, "handle Message in Handler.Callback." + msg.what);
16 return false;
17 }
18 }
19 @Override
20 public void run() {
21 Log.d(TAG, "Looper thread is running.");
22 Looper.prepare();
23 mHandler = new Handler(Looper.myLooper(), new HandlerCallback()) {
24 @Override
25 public void handleMessage(Message msg) {
26 Log.d(TAG, "I got a message: " + msg.what);
27 super.handleMessage(msg);
28 }
29 };
30 Looper.loop();
31 }
32 }
MainActivity 中的代码保持不变,我们创建了一个 HandlerCallback 类实现了 Handler.Callback 接口,并在创建 Handler 的时候传入了一个 HandlerCallback 对象。
注意:在上述示例代码中 HandlerCallback.handleMessage 和 Handler.handleMessage 都会被执行,这是因为 HandlerCallback.handleMessage 的返回值我们设定成了 false。再回头看一下 Handler 的 dispatchMessage 函数:
1 public void dispatchMessage(Message msg) {
2 if (msg.callback != null) {
3 handleCallback(msg);
4 } else {
5 if (mCallback != null) {
6 /* mCallback.handleMessage 返回 false 时,dispatchMessage 不会return。 */
7 if (mCallback.handleMessage(msg)) {
8 return;
9 }
10 }
11 handleMessage(msg);
12 }
13 }
我们就可以理解为什么两个 handleMessage 函数都会被执行,要想 Handler 正确地处理 Message 逻辑,要正确地处理 HandlerCallback.handleMessage 的返回值。
五、总结
综上,我们对 Handler 机制进行了详细的分析,并结合示例对各种使用方式有了一个感性的认识。
用一段话对 Handler 机制的基本逻辑做一个简单的总结:
Handler 是 Android 为解决两个线程之间的交互问题而创建的一种机制,线程 A 创建一个可对外公开的 Handler 作为线程 B 等其他线程向线程 A 发送 Message 的抓手(Handler)。
线程 B 等其他线程可以通过调用这个 Handler 接口向线程 A 的 MessageQueue 中发送 Message,线程 A 中的主循环从 MessageQueue 中读取并处理相关 Message 携带的信息及操作。
Android Handler 分析学习的更多相关文章
- Android Handler leak 分析及解决办法
In Android, Handler classes should be static or leaks might occur, Messages enqueued on the applicat ...
- Android Handler练习
package com.example.myact12; import java.util.Random; import android.support.v7.app.ActionBarActivit ...
- Android Handler简单使用
package com.example.myhandlertest3; import android.os.Bundle; import android.os.Handler; import andr ...
- Android Handler的使用
大家好我们这一节讲的是Android Handler的使用,在讲Handler之前,我们先提个小问题,就是如何让程序5秒钟更新一下Title. 首先我们看一下习惯了Java编程的人,在不知道Handl ...
- [Android]Handler的消息机制
最经面试中,技术面试中有一个是Handler的消息机制,细细想想,我经常用到的Handler无非是在主线程(或者说Activity)新建一个Handler对象,另外一个Thread是异步加载数据,同时 ...
- 详解Android Handler的使用-别说你不懂handler
我们进行Android开发时,Handler可以说是使用非常频繁的一个概念,它的用处不言而喻.本文就详细介绍Handler的基本概念和用法. Handler的基本概念 Handler主 ...
- Android handler Thread 修改UI Demo
/********************************************************************** * Android handler Thread 修改U ...
- Android Handler的简单使用
大家好我们这一节讲的是Android Handler的使用,在讲Handler之前,我们先提个小问题,就是如何让程序5秒钟更新一下Title. 首先我们看一下习惯了Java编程的人,在不知道Handl ...
- 详解Android Handler的使用
我们进行Android开发时,Handler可以说是使用非常频繁的一个概念,它的用处不言而喻.本文就详细介绍Handler的基本概念和用法. Handler的基本概念 Handler主 ...
随机推荐
- 怎么给Ubuntu Server安装GUI桌面
sudo apt update sudo apt upgrade sudo add-apt-repository universe sudo add-apt-repository multiverse ...
- codeforce Round #665(div 2)A B C
A. Distance and Axis 题意:在一个0x轴上,给了a在0x轴上的坐标,要你放一个b点使得abs(0B - AB)的值等于 k,但是有的时候如果不移动A点就不能实现这个条件,所以要你求 ...
- 数据操纵DML
数据操纵DML 1. 在dept表中插入两行数据 (1)50,'IT','SHENYANG';(2)60,'HR','DALIAN'; 2. 设置保存点beforeup 3. 更新dept表,将60号 ...
- SpringBoot(19)---SpringBoot整合Apollo
SpringBoot(19)---SpringBoot整合Apollo 有关Apollo之前已经写了两篇文章: 1.[Apollo](1)--- Apollo入门介绍篇 2.[Apollo](2)-- ...
- CSS 选择器及优先级
CSS 选择器及优先级 1.根据权值计算 div .class1 #people的权值等于1+10+100=111 .class2 li #age的权值等于10+1+100=111 2.权值相同,那么 ...
- 开源搜索引擎排名第一,Elasticsearch是如何做到的?
一.引言 随着移动互联网.物联网.云计算等信息技术蓬勃发展,数据量呈爆炸式增长.如今我们可以轻易得从海量数据里找到想要的信息,离不开搜索引擎技术的帮助. 作为开源搜索引擎领域排名第一的 Elast ...
- Ubuntu 16.04 安装CP210x,CH340驱动
CH340 https://github.com/juliagoda/CH341SER CP210x 因为源码版本不是linux-source-4.15.0-91-generic,导致error,一个 ...
- 华为荣耀5X(畅玩版 全网通)USB调试模式如何开启教程(开发者模式 开发者选项打开)
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, 前一段时间,公司买了一款华为荣耀畅玩版5X全网通,进行测试.发现 拿usb数据线连接PC电脑,无法进 ...
- Unity 深度冲突的解决方法
Dillon|2014-02-12 10:00|5899次浏览|Unity(280)0 3d游戏中当2个片元距离近裁减平面 w 落在同一个区间的时候,他们的深度是相等的. 最终你所看到的结果,就是下 ...
- C++STL complex吃书使用指南
说在前面: complex即为复数 使用c++自带的complex类型,首先要有<complex>头文件,还要使用std命名空间 声明方式: complex <T> a: 声 ...