Android: Looper, Handler, HandlerThread. Part II
2015-09-08 17:50
323 查看
In the previous part I've covered basic interaction in a bundle
The significant part under the hood of this team was
tasks represented by
which is used to simplify user's life. But in reality
ofMessages, not the
Let's look on this class closer.
Official documentation says the following regarding the
description:
Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
We are very interested in these "extra" fields. Here they are according to documentation:
lower-cost alternatives to using
a few integer values.
lower-cost alternatives to using
a few integer values.
send to the recipient.
replies to this message can be sent.
so that the recipient can identify what this message is about.
Not very clear how to use them, right? But the most interesting fields are hidden inside the class with package level access, here they are:
If this is a message, then you should ask yourself the following questions: How can I get a message? How should I fill it? How can I send it? How it will be processed? Let's try to answer on these questions:
How can I get a message? Since every message represent the task we need to process, you may need many messages. Eventually, instead of creating a new
for each task, you can reuse messages from the pool, it's much cheaper. To do that, just call
How should I fill it? There are several overloaded variants of
you can provide data you want (or copy data from another message):
If we want our message to be associated with specific
will be written to the
(or you can call
by calling
the
They look almost identical to
current instance of
is used to identify a type of message,
object you want to attach to the message,
want to run when
have used in the previous part to post tasks to the
will get back to them later).
How can I send message? You have 2 choices here:
you can call
message will be placed at the end of
you can call one of the following methods on your
providing message as an argument:
How it will be processed? Messages taken by the
going to
specified in
message at the
is
be called, otherwise message will be passed to
By default, this method has an empty body at the
you should either extend
can provide an implementation of
call. This interface has only one method you should write -
Now it is clear, that when we used
part, we actually created messages with
Ok, we are done with theory, now it's time to make something useful. Like at the previous part we still have a layout with progress bar as an indicator of non-blocking UI execution, but now we will add two vertical
equal widths (both occupy half or the screen) to host
And here is a code of
test:
And finally
What does this code do? It loads 4 images from http://developer.android.com and puts its either to the left or right
I'll skip views initialization and go to the interesting part:
At the code above I created a new instance of
providing a
thread (it is implicitly tied to UI thread as I said in previous part) and a callback (which is implemented by our activity instead of creating stand-alone object for it). Callback is represented by the following simple interface and its purpose is to do the
necessary UI updates:
And that's it for activity, we delegated the task of loading images to another thread. Now it's turn of
Nothing interesting in constructor, we just save the necessary objects, lets take a look on the
We are adding
create a message with
calling its
the value of
of
pulled from
worker
Instead of sub-classing
of
2 seconds delay was added to emulate the delay in handling images. All we need to do is just to extract the necessary data from the message and pass it to our processing method -
It loads the necessary bitmap and once we are done we can remove this item from request map and call a callback which will be executed on the
That's it, nothing complex. Now we have a background sequential worker which is tied to the activity's lifecycle.
Handler+
Looper+
HandlerThread.
The significant part under the hood of this team was
MessageQueuewith
tasks represented by
Runnables. This is very straightforward approach,
which is used to simplify user's life. But in reality
MessageQueueconsists
ofMessages, not the
Runnables.
Let's look on this class closer.
Official documentation says the following regarding the
Messageclass
description:
Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
We are very interested in these "extra" fields. Here they are according to documentation:
public int arg1:
arg1and
arg2are
lower-cost alternatives to using
setData()if you only need to store
a few integer values.
public int arg2:
arg1and
arg2are
lower-cost alternatives to using
setData()if you only need to store
a few integer values.
public Object obj: An arbitrary object to
send to the recipient.
public Messenger replyTo: Optional
Messengerwhere
replies to this message can be sent.
public int what: User-defined message code
so that the recipient can identify what this message is about.
Not very clear how to use them, right? But the most interesting fields are hidden inside the class with package level access, here they are:
int flags
long when
Bundle data
Handler target
Runnable callback
If this is a message, then you should ask yourself the following questions: How can I get a message? How should I fill it? How can I send it? How it will be processed? Let's try to answer on these questions:
How can I get a message? Since every message represent the task we need to process, you may need many messages. Eventually, instead of creating a new
Messageobject
for each task, you can reuse messages from the pool, it's much cheaper. To do that, just call
Message.obtain.
How should I fill it? There are several overloaded variants of
Message.obtainwhere
you can provide data you want (or copy data from another message):
obtain(Handler h, int what, int arg1, int arg2)
obtain(Handler h, Runnable callback)
obtain(Handler h)
obtain(Handler h, int what)
obtain(Handler h, int what, Object obj)
obtain(Handler h, int what, int arg1, int arg2, Object obj)
obtain(Message orig)
If we want our message to be associated with specific
Handler(which
will be written to the
targetfield), we should provide it explicitly
(or you can call
setTargetlater). Also you can attach a
Bundlewith
Parcelabletypes
by calling
setData. However, if we are going to obtain messages from
the
Handler, it has a family of shorthand methods:
obtainMessage.
They look almost identical to
Message.obtainmethods, but without
Handlerargument,
current instance of
Handlerwill be provided automatically.
whatfield
is used to identify a type of message,
objis used to store some useful
object you want to attach to the message,
callbackis any
Runnableyou
want to run when
Messagewill be processed (it is the same
Runnablewe
have used in the previous part to post tasks to the
MessageQueue, we
will get back to them later).
How can I send message? You have 2 choices here:
you can call
sendToTargetmethod on your
Messageinstance,
message will be placed at the end of
MessageQueue.
you can call one of the following methods on your
Handlerinstance
providing message as an argument:
sendMessageAtFrontOfQueue
sendMessageAtTime
sendMessageDelayed
sendMessage
How it will be processed? Messages taken by the
Looperfrom
MessageQueueare
going to
dispatchMessagemethod of the
Handlerinstance
specified in
message.targetfield. Once
Handlergets
message at the
dispatchMessageit checks whether
message.callbackfield
is
nullor not. If it's not
null
message.callback.run()will
be called, otherwise message will be passed to
handleMessagemethod.
By default, this method has an empty body at the
Handlerclass, therefore
you should either extend
Handlerclass and override this method or you
can provide an implementation of
Handler.Callbackinterface at the
Handlerconstructor
call. This interface has only one method you should write -
handleMessage.
Now it is clear, that when we used
handler.post*methods at the previous
part, we actually created messages with
callbackfield set to our
Runnable.
Ok, we are done with theory, now it's time to make something useful. Like at the previous part we still have a layout with progress bar as an indicator of non-blocking UI execution, but now we will add two vertical
LinearLayoutswith
equal widths (both occupy half or the screen) to host
ImageViews:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center"> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/progressBar"/> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="0dp" android:layout_height="fill_parent" android:layout_weight="1" android:id="@+id/leftSideLayout"> </LinearLayout> <LinearLayout android:orientation="vertical" android:layout_width="0dp" android:layout_height="fill_parent" android:layout_weight="1" android:id="@+id/rightSideLayout"> </LinearLayout> </LinearLayout> </LinearLayout>
And here is a code of
MyActivity.javawe will be using for
test:
public class MyActivity extends Activity implements MyWorkerThread.Callback { private static boolean isVisible; public static final int LEFT_SIDE = 0; public static final int RIGHT_SIDE = 1; private LinearLayout mLeftSideLayout; private LinearLayout mRightSideLayout; private MyWorkerThread mWorkerThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); isVisible = true; mLeftSideLayout = (LinearLayout) findViewById(R.id.leftSideLayout); mRightSideLayout = (LinearLayout) findViewById(R.id.rightSideLayout); String[] urls = new String[]{"http://developer.android.com/design/media/principles_delight.png", "http://developer.android.com/design/media/principles_real_objects.png", "http://developer.android.com/design/media/principles_make_it_mine.png", "http://developer.android.com/design/media/principles_get_to_know_me.png"}; mWorkerThread = new MyWorkerThread(new Handler(), this); mWorkerThread.start(); mWorkerThread.prepareHandler(); Random random = new Random(); for (String url : urls){ mWorkerThread.queueTask(url, random.nextInt(2), new ImageView(this)); } } @Override protected void onPause() { isVisible = false; super.onPause(); } @Override protected void onDestroy() { mWorkerThread.quit(); super.onDestroy(); } @Override public void onImageDownloaded(ImageView imageView, Bitmap bitmap, int side) { imageView.setImageBitmap(bitmap); if (isVisible && side == LEFT_SIDE){ mLeftSideLayout.addView(imageView); } else if (isVisible && side == RIGHT_SIDE){ mRightSideLayout.addView(imageView); } } }
And finally
MyWorkerThread.java:
public class MyWorkerThread extends HandlerThread { private Handler mWorkerHandler; private Handler mResponseHandler; private static final String TAG = MyWorkerThread.class.getSimpleName(); private Map<ImageView, String> mRequestMap = new HashMap<ImageView, String>(); private Callback mCallback; public interface Callback { public void onImageDownloaded(ImageView imageView, Bitmap bitmap, int side); } public MyWorkerThread(Handler responseHandler, Callback callback) { super(TAG); mResponseHandler = responseHandler; mCallback = callback; } public void queueTask(String url, int side, ImageView imageView) { mRequestMap.put(imageView, url); Log.i(TAG, url + " added to the queue"); mWorkerHandler.obtainMessage(side, imageView) .sendToTarget(); } public void prepareHandler() { mWorkerHandler = new Handler(getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } ImageView imageView = (ImageView) msg.obj; String side = msg.what == MyActivity.LEFT_SIDE ? "left side" : "right side"; Log.i(TAG, String.format("Processing %s, %s", mRequestMap.get(imageView), side)); handleRequest(imageView, msg.what); msg.recycle(); return true; } }); } private void handleRequest(final ImageView imageView, final int side) { String url = mRequestMap.get(imageView); try { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); final Bitmap bitmap = BitmapFactory .decodeStream((InputStream) connection.getContent()); mRequestMap.remove(imageView); mResponseHandler.post(new Runnable() { @Override public void run() { mCallback.onImageDownloaded(imageView, bitmap, side); } }); } catch (IOException e) { e.printStackTrace(); } } }
What does this code do? It loads 4 images from http://developer.android.com and puts its either to the left or right
LinearLayoutrandomly.
I'll skip views initialization and go to the interesting part:
String[] urls = new String[]{"http://developer.android.com/design/media/principles_delight.png", "http://developer.android.com/design/media/principles_real_objects.png", "http://developer.android.com/design/media/principles_make_it_mine.png", "http://developer.android.com/design/media/principles_get_to_know_me.png"}; mWorkerThread = new MyWorkerThread("myWorkerThread", new Handler(), this); mWorkerThread.start(); mWorkerThread.prepareHandler(); Random random = new Random(); for (String url : urls){ mWorkerThread.queueTask(url, random.nextInt(2), new ImageView(this)); }
At the code above I created a new instance of
MyWorkerThreadby
providing a
Handlerwhich will be used for posting results to the UI
thread (it is implicitly tied to UI thread as I said in previous part) and a callback (which is implemented by our activity instead of creating stand-alone object for it). Callback is represented by the following simple interface and its purpose is to do the
necessary UI updates:
public static interface Callback { public void onImageDownloaded(ImageView imageView, Bitmap bitmap, int side); }
And that's it for activity, we delegated the task of loading images to another thread. Now it's turn of
HandlerThread.
Nothing interesting in constructor, we just save the necessary objects, lets take a look on the
queueTaskmethod:
public void queueTask(String url, int side, ImageView imageView) { mRequestMap.put(imageView, url); Log.i(TAG, url + " added to the queue"); mWorkerHandler.obtainMessage(side, imageView) .sendToTarget(); }
We are adding
ImageViewand URL to the request map here and
create a message with
message.targetfield set to
mWorkerHandlerby
calling its
obtainMessagemethod, also we set
message.objto
imageViewand
message.whatto
the value of
sideargument. After that the message is sent to the end
of
MessageQueue, now we can take a look on handling message once it is
pulled from
MessageQueue, the necessary processing was written at the
worker
Handlerinitialization at the
prepareHandlermethod:
public void prepareHandler() { mWorkerHandler = new Handler(getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } ImageView imageView = (ImageView) msg.obj; String side = msg.what == MyActivity.LEFT_SIDE ? "left side" : "right side"; Log.i(TAG, String.format("Processing %s, %s", mRequestMap.get(imageView), side)); handleRequest(imageView, msg.what); msg.recycle(); return true; } }); }
Instead of sub-classing
Handlerto make my own implementation
of
handleMessagemethod, I've used
Handler.Callbackinterface,
2 seconds delay was added to emulate the delay in handling images. All we need to do is just to extract the necessary data from the message and pass it to our processing method -
handleRequest:
private void handleRequest(final ImageView imageView, final int side) { String url = mRequestMap.get(imageView); try { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); final Bitmap bitmap = BitmapFactory .decodeStream((InputStream) connection.getContent()); mRequestMap.remove(imageView); mResponseHandler.post(new Runnable() { @Override public void run() { mCallback.onImageDownloaded(imageView, bitmap, side); } }); } catch (IOException e) { e.printStackTrace(); } }
It loads the necessary bitmap and once we are done we can remove this item from request map and call a callback which will be executed on the
UIthread.
That's it, nothing complex. Now we have a background sequential worker which is tied to the activity's lifecycle.
相关文章推荐
- 解决在android平台上apk的覆盖安装so文件不覆盖问题
- Android: Looper, Handler, HandlerThread. Part I
- android下拉刷新android-Ultra-Pull-To-Refresh使用
- 最近开发中的一些坑#1
- 【Android Studio简易教程】断点调试及相关技巧
- Android中Context的理解与使用技巧
- Android客户端与服务器端通过DES加密认证
- 在百度地图的基础上增加了实时定位和轨迹
- android序列化Serializable、Parcelable(一)
- Android 动态添加每条输入的内容
- Android下拉刷新SwipeRefreshLayout
- Android笔记:20150823
- Android笔记:20150824(this关键字的使用)
- Android笔记:20150820
- 【第四篇章-android平台MediaCodec】根据编码类型MIME_TYPE获取MediaCodecInfo
- Android笔记:20150408
- Android笔记:20150414
- Android客户端写Cookie和内嵌的网页实现登录状态的同步
- Android笔记:20150406
- 基于xmpp openfire smack开发之Android消息推送技术原理分析和实践[4]