技术开发 频道

Android多线程:Looper和HandlerThread

  【IT168技术】更新UI的时候主线程必须是目标线程,如何掌握这个主动性?是通过Looper和HandlerThread实现的。Android中每一个线程都跟着一个Looper,Looper可以帮助线程维护一个消息队列,Looper对象的执行需要初始化Looper.prepare()方法,使用Looper.loop()方法启动消息队列管理机制,退出时还要使用Looper.release()方法释放资源,下面代码为在Android中创建一个Thread类线程:

class LooperThread extends Thread {      
public Handler mHandler;    
    
public void run() {      
       Looper.prepare();          
       mHandler
= new Handler() {      
      
public void handleMessage(Message msg) {          
        
/* process incoming messages here */
       }    
     };              
     Looper.loop();
   }
}

  Android使用Looper机制才能接收消息和处理计划任务,上面的代码编写起来实在是麻烦,所以Android提供了一个线程类——HanderThread类,HanderThread类继承了Thread类,它封装了Looper对象,使我们不用关心Looper的开启和释放的细节问题。HandlerThread对象中可以通过getLooper方法获取一个Looper对象引用。

  不管是主线程(一般是我们的UI线程)还是子线程,只要有Looper的线程,别的线程就可以向这个线程的消息队列中发送消息和计划任务,然后做相应的处理。

  我们通过debug方式看看案例chapter8_5线程情况,在程序的39行和56行设置断点,39行是handleMessage()方法用于接收消息的目标线程,56行是发出消息的线程。程序以debug模式运行,在断点56行挂起了,如图8-14所示。

Looper和HandlerThread
▲图8-14 debug模式运行图一

  从图8-14可以看出,发送消息的程序隶属于Thread-8线程,它不是主线程。接着运行程序挂起在39行,如图8-15所示。

Looper和HandlerThread

  在图8-15中我们可以看到,接收消息的处理方法隶属于main线程,即主线程(UI线程),在8-15中还可以看到Looper对象。

  比较发送消息和提交计划任务的不同之后,再以同样的方式debug一下程序chapter8_6代码,在程序chapter8_6的42行和50行设定断点,42行执行计划任务的目标线程,并以debug模式运行,如图8-16所示。

Looper和HandlerThread
▲图8-16 debug模式运行图三

  如图8-16所示,程序挂起在50行,提交计划任务请求的程序隶属于main线程(UI线程),接着运行程序挂起在42行,如图8-17所示。

Looper和HandlerThread
▲图8-17 debug模式运行图四

  图8-17所示程序挂起在42行,run方法是接收计划任务的程序,它也隶属于main线程(UI线程)。事实上chapter8_6程序只有一个UI主线程,它们在UI主线程中提交计划任务请求,再由UI线程作为目标线程接收计划任务执行。

  但是在chapter8_5和chapter8_6的程序代码中并没有看到有关Looper和HandlerThread代码,这是因为默认情况下系统会创建一个无参构造方法的Handler对象,利用这种方法Handler可以自动与当前运行线程(UI线程)的Looper关联。

  如果使用HandlerThread类替代Thread类创建线程对象,就可以直接使用HandlerThread线程中的Looper了,再使用这个Looper对象构造Handler对象,这样接收消息的目标线程就不是UI线程了。但就本例而言这种处理会有问题,我们先看看代码清单8-8,完整代码请参考chapter8_6工程中 chapter8_6_2代码部分。

  【代码清单8-8】

public class chapter8_6_2 extends Activity {

    
private String TAG = "chapter8_6_2";
    
private Button btnEnd;
    
private TextView labelTimer;
    
private boolean isRunning = true;
    
private Handler handler;
    
private int timer = 0;

    @Override

    
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btnEnd
= (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(
new OnClickListener() {
            @Override
            
public void onClick(View v) {
                isRunning
= false;
            }
        });
        
        labelTimer
= (TextView) findViewById(R.id.labelTimer);
        
        HandlerThread thread
= new HandlerThread("MyHandlerThread");
        thread.start();
        
        handler
= new Handler(thread.getLooper());
        
        Runnable r
= new Runnable() {
            
public void run() {
                
if (isRunning) {
                    labelTimer.setText(
"逝去了 " +timer + "");
                    
timer++;
                    handler.postDelayed(this,
1000);
                }

            }
        };
        handler.postDelayed(r,
1000);

    }
}

  chapter8_6_2中用new HandlerThread("MyHandlerThread")创建HandlerThread线程,通过thread.start()方法启动该线程。HandlerThread的getLooper()方法可以获得与HandlerThread线程对象关联的Looper对象。再用Looper对象构建new Handler(looper)。

  我们再通过debug看看chapter8_6_2的线程情况,在程序chapter8_6_2的46行和53行设定断点,并以debug模式运行,如图8-18所示。

  如图8-18所示,程序挂起在53行,提交计划任务请求的程序隶属于main线程(UI线程),接着运行程序挂起在46行,如图8-19所示。

  如图8-19所示,程序挂起在46行,run方法是接收计划任务的程序,它隶属于MyHandlerThread线程(非UI线程)。这样接收计划任务的线程体run()就放到一个子线程中了,可见Looper是关键,Looper在哪个线程,哪个线程就可以接收消息和计划任务了。

Looper和HandlerThread
▲图8-18 debug模式运行图五

Looper和HandlerThread
▲图8-19 debug模式运行图六

  事实上chapter8_6_2代码执行是有问题的,会出现错误“Only the original thread that created a view hierarchy can touch its views.”。这个错误应该不陌生,它是由于在非主线程(UI线程)中更新了UI,这是因为chapter8_6_2中把两个线程的职责掉换了,就这个案例本身而言这样修改没有意义,只是想通过这个例子让大家熟悉Looper和HandlerThread的使用。

  小结

  通过计时器案例向大家介绍了Java线程和Android中的线程。关键是Android线程,它与一般的Java多线程处理方式是不同的,其中重点是消息发送和计划任务,接受消息发送和计划任务的处理是目标线程,它是通过Looper机制维护消息队列,如果应用中有包含更新UI处理,则要把更新UI的处理代码放置在目标线程中,这个时候还要保障更新UI的线程是主线程。

0
相关文章