Android Dev Intro - Android Looper
2016-05-06 14:38
429 查看
http://prasanta-paul.blogspot.kr/2013/09/android-looper-and-toast-from.html
this error-
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
In this article we are going to explore reason behind the above exception and try to understand how Looper works
in Android. At the end, I am going to explain one approach to run Toast from a worker thread, but before that we need to understand how Looper works. If you already know in/out of Looper, you can skip below section and directly move to the solution part.
Looper is a very basic wrapper class which attach a MessageQueue to
a Thread and manage this queue. MessageQueue is a structure to sequentialize simultaneous processing requests of a Thread. In Android,
message/request processing classes like Handler uses Looper to manage their respective MessageQueue.
Looper = Thread + MessageQueue
Android Looper Life Cycle:
As you can see in the above figure, Looper Life cycle starts when we call prepare(), this static method creates instance of Looper
class and store this in a ThreadLocal variable. Below code snippet from Looper.java
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));
}
private Looper(boolean quitAllowed)
{
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
Once Looper instance is created we have to call loop() which starts an infinite Loop and process requests accumulated in the Message Queue. Below code from Looper.java
public static void loop()
{
// Looper instance and Thread verification
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Process Messages
}
}
Due to above Infinite Loop, Looper blocks the current Thread execution until we call Quit. To get a hook
to know when Looper is done with Message processing or when Message Queue is empty, we need to register MessageQueue.IdleHandler listener
to the MessageQueue associated to the respective Looper. This can be achieved as-
// Prepare looper
Looper.prepare();
// Register Queue listener hook
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
// TODO Auto-generated method stub
return false;
}
});
// Start looping Message Queue
Looper.loop();
Running Toast from Worker Thread
Now, lets explain how we can run Toast in a thread other than UI or Main Thread. If we look inside of Toast.java, when we call makeText()internally it initialize Handler on the Calling thread. So, if the calling thread
has not yet been attached to a MessageQueue Toast fails to create its instance. The only way to attach MessageQueue to a Thread is to use Looper, as a result Toast.java throws
below exception-
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
Toast.java -> Toast.TN.java -> Handler.java
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
}
public Handler() {
// .......
mLooper = Looper.myLooper();
if (mLooper == null)
{
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
So, I hope we now understand the real meaning of this famous error message. Lets now try an alternate approach to execute Toast in a separate Thread.
We need to initialize Looper and attach MessageQueue to our calling Thread prior to launch Toast. When we call show() method of Toast, it sends Handler message to process display logic of Toast, then it waits for defined
time (LONG/SHORT) and then calls hide() which again sends Handler message to remove Toast View. Thus Looper associated to Toast is requesting for MessageQueue twice, so we'll keep a counter to track number of MessageQueue requests and when it is 2, we'll quit
the Looper. It is really important to quit the Looper, as if we don't do so, it will block all further operations, since Looper.loop() is a blocking call.
Here is the code snippet to run Toast in a separate Thread-
public void aboutLooper() {
Thread th = new Thread() {
public void run() {
System.out.println("Start Looper...");
// Prepare looper
Looper.prepare();
// Register Queue listener hook
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
int mReqCount = 0;
@Override
public boolean queueIdle()
{
if (++mReqCount == 2) {
// Quit looper
Looper.myLooper().quit();
return false;
} else
return true;
}
});
// Show Toast- will be called when Looper.loop() starts
Toast.makeText(MainActivity.this, "Hey there!!",
Toast.LENGTH_LONG).show();
// Start looping Message Queue- Blocking call
Looper.loop();
System.out.println("It appears after Looper.myLooper().quit()");
}
};
th.start();
}
Android Looper and Toast from WorkerThread
Have you ever tried to launch Android Toast message from worker thread ? Probably you are wondering why the heck it is givingthis error-
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
In this article we are going to explore reason behind the above exception and try to understand how Looper works
in Android. At the end, I am going to explain one approach to run Toast from a worker thread, but before that we need to understand how Looper works. If you already know in/out of Looper, you can skip below section and directly move to the solution part.
Looper is a very basic wrapper class which attach a MessageQueue to
a Thread and manage this queue. MessageQueue is a structure to sequentialize simultaneous processing requests of a Thread. In Android,
message/request processing classes like Handler uses Looper to manage their respective MessageQueue.
Looper = Thread + MessageQueue
Android Looper Life Cycle:
As you can see in the above figure, Looper Life cycle starts when we call prepare(), this static method creates instance of Looper
class and store this in a ThreadLocal variable. Below code snippet from Looper.java
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));
}
private Looper(boolean quitAllowed)
{
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
Once Looper instance is created we have to call loop() which starts an infinite Loop and process requests accumulated in the Message Queue. Below code from Looper.java
public static void loop()
{
// Looper instance and Thread verification
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Process Messages
}
}
Due to above Infinite Loop, Looper blocks the current Thread execution until we call Quit. To get a hook
to know when Looper is done with Message processing or when Message Queue is empty, we need to register MessageQueue.IdleHandler listener
to the MessageQueue associated to the respective Looper. This can be achieved as-
// Prepare looper
Looper.prepare();
// Register Queue listener hook
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
// TODO Auto-generated method stub
return false;
}
});
// Start looping Message Queue
Looper.loop();
Running Toast from Worker Thread
Now, lets explain how we can run Toast in a thread other than UI or Main Thread. If we look inside of Toast.java, when we call makeText()internally it initialize Handler on the Calling thread. So, if the calling thread
has not yet been attached to a MessageQueue Toast fails to create its instance. The only way to attach MessageQueue to a Thread is to use Looper, as a result Toast.java throws
below exception-
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
Toast.java -> Toast.TN.java -> Handler.java
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
}
public Handler() {
// .......
mLooper = Looper.myLooper();
if (mLooper == null)
{
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
So, I hope we now understand the real meaning of this famous error message. Lets now try an alternate approach to execute Toast in a separate Thread.
We need to initialize Looper and attach MessageQueue to our calling Thread prior to launch Toast. When we call show() method of Toast, it sends Handler message to process display logic of Toast, then it waits for defined
time (LONG/SHORT) and then calls hide() which again sends Handler message to remove Toast View. Thus Looper associated to Toast is requesting for MessageQueue twice, so we'll keep a counter to track number of MessageQueue requests and when it is 2, we'll quit
the Looper. It is really important to quit the Looper, as if we don't do so, it will block all further operations, since Looper.loop() is a blocking call.
Here is the code snippet to run Toast in a separate Thread-
public void aboutLooper() {
Thread th = new Thread() {
public void run() {
System.out.println("Start Looper...");
// Prepare looper
Looper.prepare();
// Register Queue listener hook
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
int mReqCount = 0;
@Override
public boolean queueIdle()
{
if (++mReqCount == 2) {
// Quit looper
Looper.myLooper().quit();
return false;
} else
return true;
}
});
// Show Toast- will be called when Looper.loop() starts
Toast.makeText(MainActivity.this, "Hey there!!",
Toast.LENGTH_LONG).show();
// Start looping Message Queue- Blocking call
Looper.loop();
System.out.println("It appears after Looper.myLooper().quit()");
}
};
th.start();
}
相关文章推荐
- Android ORMLite 框架的入门用法
- Android 快速开发系列 ORMLite 框架最佳实践
- Android 二维码 生成和识别(附Demo源码)
- Android 序列化
- Android Dev Intro - Some Concepts
- 加速Android Studio/Gradle构建详解
- Android浏览图片,点击放大至全屏效果(包含点击前位置到全屏的动画)
- android事件分发的tips
- Android6.0找不到settings.db数据库问题
- NavigationView+DrawerLayout实现侧拉抽屉效果
- Android ViewGroup事件分发机制
- Android 自定义控件打造史上最简单的侧滑菜单
- Android实现桌面悬浮窗、蒙板效果实例代码
- Android开发初学者必看文档:Android开发规范
- Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭
- Android布局常见异常错误
- 为一个驱动添加Android中间层的步骤
- Android 手势检测实战 打造支持缩放平移的图片预览效果(上)
- android开发设置Dialog出现消失动画
- 调用android系统相机拍照并保存