Android handler

来源:互联网 发布:代码加密软件 编辑:程序博客网 时间:2024/05/17 18:00
一、问题的提出

       若把一些比较耗时的操作(如:下载)写在Activity(主线程)里,会导致Activity阻塞,长时间无响应,直至页面假死(如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭")。

       因此,我们需要把这些耗时的操作放在单独的子线程中操作,由Handler进行异步处理。


二、Handler简介

       Handler 为Android操作系统中的线程通信工具,它主要由两个作用:

(1) 安排消息或Runnable 在某个主线程中某个地方执行;

(2) 安排一个动作在另外的线程中执行。

       每个Handler对象维护两个队列(FIFO),消息队列Runnable队列, Handler可以通过这两个队列来分别完成:

(1) 发送、接受、处理消息——消息队列;

(2) 启动、结束、休眠线程——Runnable队列;

       Handler的使用方法大体分为3个步骤:

1.创建Handler对象。

2.创建Runnable和消息。

3.调用post以及sendMessage方法将Runnable和消息添加到队列。


三、Runnable队列

1.java中的线程

       在java中,线程的创建有两种方法:继承Thread类和实现Runnable接口。而这最重要的都是要复写run方法来实现线程的功能。当线程的时间片到了,开始运行时,就执行run()函数,执行完毕,就进入死亡状态。


2.关于Runnable队列
(1)原理
       Android的线程异步处理机制:Handler对象维护一个线程队列,有新的Runnable送来(post())的时候,把它放在队尾,而处理 Runnable的时候,从队头取出Runnable执行。当向队列发送一个Runnable后,立即就返回,并不理会Runnable是否被执行,执行 是否成功等。而具体的执行则是当排队排到该Runnable后系统拿来执行的。
(2)具体操作
向队列添加线程:
handler.post(Runnable );将Runnable直接添加入队列
handler.postDelayed(Runnable, long)延迟一定时间后,将Runnable添加入队列
handler.postAtTime(Runnable,long)定时将Runnable添加入队列
终止线程:
handler.removeCallbacks(thread);将Runnable从Runnable队列中取出

(3)例子

【实验1】

package com.example.testthread1;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.Menu;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity {private final String TAG = "ThreadTest";private TextView text_view = null;private Button start = null;private Button end = null;// 使用handler时首先要创建一个handlerHandler handler = new Handler();//runnable run()Runnable update_thread = new Runnable() {public void run() {text_view.append("\nUpdateThread...");// 延时10s后又将线程加入到线程队列中handler.postDelayed(update_thread, 10000);Log.d(TAG, "Current Thread id:----------+>" + Thread.currentThread().getId());}};@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "Main Thread id:----------+>" + Thread.currentThread().getId());text_view = (TextView) findViewById(R.id.text_view);start = (Button) findViewById(R.id.start);start.setOnClickListener(new StartClickListener());end = (Button) findViewById(R.id.end);end.setOnClickListener(new EndClickListener());}private class StartClickListener implements OnClickListener {public void onClick(View v) {// handler post runnalbehandler.post(update_thread);}}private class EndClickListener implements OnClickListener {public void onClick(View v) {// 将接口从线程队列中移除handler.removeCallbacks(update_thread);}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.main, menu);return true;}}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <TextView        android:id="@+id/text_view"        android:layout_width="fill_parent"        android:layout_height="200dip"        android:text="@string/hello_world"        tools:context=".MainActivity" />    <Button         android:id="@+id/start"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:text="@string/start"        />    <Button         android:id="@+id/end"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:text="@string/end"        /></LinearLayout>

实验结果:

05-28 06:23:12.682: D/ThreadTest(1334): Main Thread id:----------+>1
05-28 06:23:17.911: D/ThreadTest(1334): Current Thread id:----------+>1
05-28 06:23:27.923: D/ThreadTest(1334): Current Thread id:----------+>1

分析:

       这个程序看上去似乎实现了Handler的异步机制, handler.post(thread)似乎实现了新启线程的作用,不过通过执行我们发现,两个线程的ID相同!也就是说,实际上thread还是原来的主线程,由此可见,handler.post()方法并未真正新建线程,只是在原线程上执行而已。

【实验2】

package com.example.testthread1;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.Menu;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity {private final String TAG = "ThreadTest";private TextView text_view = null;private Button start = null;private Button end = null;// 使用handler时首先要创建一个handlerHandler handler = new Handler();//runnable run()Runnable update_thread = new Runnable() {public void run() {text_view.append("\nUpdateThread...");// 延时10s后又将线程加入到线程队列中handler.postDelayed(update_thread, 10000);Log.d(TAG, "Current Thread id:----------+>" + Thread.currentThread().getId());}};@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "Main Thread id:----------+>" + Thread.currentThread().getId());text_view = (TextView) findViewById(R.id.text_view);start = (Button) findViewById(R.id.start);start.setOnClickListener(new StartClickListener());end = (Button) findViewById(R.id.end);end.setOnClickListener(new EndClickListener());}private class StartClickListener implements OnClickListener {public void onClick(View v) {// handler post runnalbe//handler.post(update_thread);Thread t = new Thread(update_thread);t.start();}}private class EndClickListener implements OnClickListener {public void onClick(View v) {// 将接口从线程队列中移除//handler.removeCallbacks(update_thread);}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.main, menu);return true;}}

实验结果:

05-28 06:28:39.048: E/AndroidRuntime(1396): FATAL EXCEPTION: Thread-152
05-28 06:28:39.048: E/AndroidRuntime(1396): Process: com.example.testthread1, PID: 1396
05-28 06:28:39.048: E/AndroidRuntime(1396): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

分析:

CalledFromWrongThreadException错误。因为android的线程安全机制要求UI函数只能在UI线程中调用。

【实验3】

package com.example.testthread2;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.Menu;import android.view.MenuItem;public class MainActivity extends Activity {private final String TAG = "Thread Handler";private Handler mhandler = new Handler();Runnable mRunnable = new Runnable() {@Overridepublic void run() {Log.d(TAG,"Thread name is " + Thread.currentThread().getId() + "! id is " + Thread.currentThread().getName());try {Thread.sleep(10000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "mian thread name is " + Thread.currentThread().getId() + "! id is "+ Thread.currentThread().getName());// 将Runnable直接添加入runnable队列// mhandler.post(mRunnable);Thread t = new Thread(mRunnable);t.start();}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}}

实验结果:

05-28 02:12:10.809: D/Thread Handler(1518): mian thread name is 1! id is main
05-28 02:12:10.820: D/Thread Handler(1518): Thread name is 157! id is Thread-157
05-28 02:12:11.068: D/gralloc_goldfish(1518): Emulator without GPU emulation detected.

结果分析:

      这个程序中,我们就没有在其他线程中使用UI函数了。通过打印我们可以看到,两个ID是不同的,新的线程启动了!


【实验4】

package com.example.testthread4;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity {private final String TAG = "handler post";private TextView text_view = null;private Button start = null;private Button end = null;private Handler mhandler = new Handler();Runnable updateUI = new Runnable(){@Overridepublic void run() {text_view.append("\nUpdateThread...");Log.d(TAG, "updateUI Thread id:----------+>" + Thread.currentThread().getId());}};Runnable thread1 = new Runnable() {@Overridepublic void run() {//这里添加耗时操作Log.d(TAG, "Current Thread1 id:----------+>" + Thread.currentThread().getId());mhandler.post(new Runnable() {public void run() {mhandler.postDelayed(updateUI, 5000);}});}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "Main Thread id:----------+>" + Thread.currentThread().getId());text_view = (TextView) findViewById(R.id.text_view);start = (Button) findViewById(R.id.start);start.setOnClickListener(new StartClickListener());end = (Button) findViewById(R.id.end);end.setOnClickListener(new EndClickListener());}private class StartClickListener implements OnClickListener {public void onClick(View v) {// handler post runnalbe// mhandler.post(thread1);Thread t = new Thread(thread1);t.start();}}private class EndClickListener implements OnClickListener {public void onClick(View v) {// 将接口从线程队列中移除 mhandler.removeCallbacks(thread1);}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}}
实验结果:

05-28 09:19:14.932: D/handler post(1164): Main Thread id:----------+>1
05-28 09:19:15.198: D/gralloc_goldfish(1164): Emulator without GPU emulation detected.
05-28 09:19:20.874: D/handler post(1164): Current Thread1 id:----------+>137
05-28 09:19:26.010: D/handler post(1164): updateUI Thread id:----------+>1

分析:

这个程序里,定义了两个runnable,所以,应该有三个线程,但是可看到只有有两个线程,原因是:一个线程将UI操作的函数post到UI线程里去执行。


四.、消息队列

1.消息对象

(1)Message对象

         Message对象携带数据,通常它用arg1,arg2来传递消息,当然它还可以有obj参数,可以携带Bundle数据。它的特点是系统性能消耗非常少。

         初始化: Message msg=handler.obtainMessage();

(2)Bundle对象

         Bundle是Android提供的类,可以把它看做是特殊的Map,即键值对的包。而它特殊在键和值都必须要是基本数据类型或是基本数据类型的数组(Map的键值要求都是对象),特别的,键要求都是String类型。用Message来携带Bundle数据:

放入:msg.setData(Bundle bundle);

取出:msg.getData();

2.关于消息队列

(1)原理

        Android的消息异步处理机制:Handler对象维护一个消息队列,有新的消息送来(sendMessage())的时候,把它放在队尾,之后排队 到处理该消息的时候,由主线程的Handler对象处理(handleMessage())。整个过程也是异步的,和Runnable队列的原理相同。

(2)具体操作:

        向队列添加Runnable:handler.sendMessage(Message);

        将消息发送到消息队列msg.sendToTarget();

        延迟一定时间后,将消息发送到消息队列 handler.sendMessageDelayed(Message,long);

        定时将消息发送到消息队列 handler.sendMessageAtTime(Message,long)

处理消息:

        消息的具体处理过程,需要在new Handler对象时使用匿名内部类重写Handler的handleMessage(Message msg)方法。

3、例子——使用主线程的looper

package com.example.testthreadmessage;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ProgressBar;public class MainActivity extends Activity {private Button mbutton = null;private ProgressBar mProgressBar = null;private final String TAG = "handleMessage";// 创建一个handler,内部完成处理消息方法Handler handlerProgressBar = new Handler() {@Overridepublic void handleMessage(Message msg) {// 显示进度条mProgressBar.setProgress(msg.arg1);// 重新把进程加入到进程队列中handlerProgressBar.post(updateThread);}};Runnable updateThread = new Runnable() {int i = 0;public void run() {i += 10;// 首先获得一个消息结构Message msg = handlerProgressBar.obtainMessage();// 给消息结构的arg1参数赋值msg.arg1 = i;// 延时1s,java中的try+catch用来排错处理try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO: handle exceptione.printStackTrace();}// 把消息发送到消息队列中handlerProgressBar.sendMessage(msg);if (i == 100)// 把线程从线程队列中移除Log.d(TAG, "thread is " + Thread.currentThread().getName() + " " + Thread.currentThread().getId());handlerProgressBar.removeCallbacks(updateThread);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);mbutton = (Button) findViewById(R.id.start);mbutton.setOnClickListener(new StartOnClickListenr());}private class StartOnClickListenr implements OnClickListener {@Overridepublic void onClick(View v) {// 让进度条显示出来mProgressBar.setVisibility(View.VISIBLE);// 将线程加入到handler的线程队列中handlerProgressBar.post(updateThread);Log.d(TAG, "thread is " + Thread.currentThread().getName() + " " + Thread.currentThread().getId());}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}}


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.example.testthreadmessage.MainActivity" >        <Button         android:id="@+id/start"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:text="@string/start"        />    <ProgressBar         android:id="@+id/progress_bar"        android:layout_width="fill_parent"        android:layout_height="100dip"        android:layout_alignParentTop="true"        style="?android:attr/progressBarStyleHorizontal"        android:visibility="gone"        /></RelativeLayout>

实验结果:

05-28 05:48:38.041: D/handleMessage(1023): thread is main 1
05-28 05:48:50.620: D/handleMessage(1023): thread is main 1
05-28 05:49:24.961: I/Choreographer(1023): Skipped 113 frames!  The application may be doing too much work on its main thread.

五、Looper
        Looper类用来为线程开启一个消息循环,作用是可以循环的从消息队列读取消息,所以Looper实际上就是消息队列+消息循环的封装。每个线程只能对应一个Looper,除主线程外,Android中的线程默认是没有开启Looper的。
       通过Handler与Looper交互,Handler可以看做是Looper的接口,用来向指定的Looper发送消息以及定义处理方法。默认情况下Handler会与其所在线程的Looper绑定,即:
Handler handler=new Handler();等价于Handler handler=new Handler(Looper.myLooper());
       Looper有两个主要方法:
Looper.prepare();启用Looper
Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。
       注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。


六、HandlerThread

      通过HandlerThread 创建一个封装好Looper的线程。

实验:

package com.example.testthread3;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.HandlerThread;import android.os.Looper;import android.os.Message;import android.util.Log;import android.view.Menu;import android.view.MenuItem;public class MainActivity extends Activity {private final String TAG = "HandlerThread ";class myHandler extends Handler {public myHandler() {}public myHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubLog.d(TAG, "Thread:" + Thread.currentThread().getId());// 将消息中的bundle数据取出来Bundle b = msg.getData();String whether = b.getString("whether");int temperature = b.getInt("temperature");Log.d(TAG, "whether= " + whether + " ,temperature= " + temperature);}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "Thread:" + Thread.currentThread().getId());// 创建一个名叫handler_hread的HandlerThread 对象HandlerThread handlerThread = new HandlerThread("handler_hread");// 开启handlerThread,在使用handlerThread.getLooper()之前必须先调用start方法,否则取出的是空handlerThread.start();// 将handler绑定在handlerThread的Looper上,即这个handler是运行在handlerThread线程中的myHandler handler = new myHandler(handlerThread.getLooper());Message msg = handler.obtainMessage();Bundle b = new Bundle();b.putString("whether", "晴天");b.putInt("temperature", 34);msg.setData(b);// 将msg发送到自己的handler中,这里指的是my_handler,调用该handler的HandleMessage方法来处理该mugmsg.sendToTarget();}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}}
实验结果:

05-28 05:59:27.201: D/HandlerThread(1390): Thread:1
05-28 05:59:27.317: D/HandlerThread(1390): Thread:152
05-28 05:59:27.318: D/HandlerThread(1390): whether= 晴天 ,temperature= 34
05-28 05:59:27.688: D/gralloc_goldfish(1390): Emulator without GPU emulation detected.

1 0
原创粉丝点击