Android应用程序开发过程中,涉及主线程(UI线程)与子线程要注意的问题可能有很多,但我觉得最重要的莫过于UI在子线程中的更新问题(这样说其实有问题,因为子线程中根本就不能更新UI控件)。
当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程),主线程为管理界面中的UI控件,进行事件分发,比如说,你要是点击一个 Button,Android会分发事件到Button上来响应你的操作。如果此时需要一个耗时的操作,例如联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象,如果5秒钟还没有完成的话,会收到Android系统的一个错误提示“强制关闭”。这个时候我们需要把这些耗时的操作放在一个子线程中。由于子线程涉及到UI更新,而Android主线程是线程不安全的,也就是说,UI更新只能在主线程中进行,在子线程中操作是危险的。如果在子线程中操作UI的更新,那么便会在logcat中出现"Only the original thread that created a view hierarchy can touch its views"的错误。由于很多情况下我们都要在子线程涉及UI更新,因此遇到这种情况,我们要怎么处理呢?
解决办法是:利用Handler。Handler是Android中的消息发送器,其在哪个Activity中创建就属于且仅仅属于该Activity。还可以说其在哪个线程中new的,就是那个线程的Handler。
Handler的定义:主要接受子线程发送的数据,并用此数据配合主线程更新UI。由于Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象,(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。
Handler的特点:handler可以分发Message对象和Runnable对象到主线程中,每个Handler实例,都会绑定到创建他的线程中。它有两个作用: (1)安排消息或Runnable在某个主线程中某个地方执行 (2)安排一个动作在不同的线程中执行。
Handler中分发消息的一些方法
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
以上post类方法允许你排列一个Runnable对象到主线程队列中,sendMessage类方法,允许你安排一个带数据的Message对象到队列中,等待更新。另外还有一个值得关注的特点:一个线程里面可以有过个handler,向哪个handler 发送消息,就必须在哪个handler 里面接收处理。
以下是我在一个Activity中采用匿名内部类的方式定义了两个Handler用来分开处理不同子线程发过来的消息。
public static Handler myhandler=new Handler(){ @Override public void handleMessage(Message msg) { Log.e("lyn_handler", "on_call_media_state be called"); switch (msg.what) { case 10: LinearLayout incontainer1=null; LinearLayout prvcontainer1=null; String s=new String(); int callId = -1; if(HspSipStackReceiver.initialSession!=null) callId=HspSipStackReceiver.initialSession.getCallId(); if (HspDeviceList.callIdMap.containsValue(callId)){ s = (String) HspSipStackReceiver.getKeyFrmValue(HspDeviceList.callIdMap,callId); if(HspSipStackReceiver.setItemView(s)==true){ incontainer1 = (LinearLayout) HspSipStackReceiver.itemView.findViewById(R.id.incomingvid_dialog); prvcontainer1= (LinearLayout) HspSipStackReceiver.itemView.findViewById(R.id.previd_dialog); prvcontainer1.removeView(prvcontainer1.getChildAt(0)); prvcontainer1.addView(HspDeviceList.cameraPreview); pjsua.vid_set_android_capturer(HspDeviceList.cameraPreview); if(incontainer1.getChildAt(0)!=null){ incontainer1.removeView(incontainer1.getChildAt(0)); incontainer1.addView(HspDeviceList.vidRenderView); pjsua.vid_set_android_renderer(callId,HspDeviceList.vidRenderView); }else{ mLayout.removeAllViews(); mLayout.addView(HspDeviceList.vidRenderView1); pjsua.vid_set_android_renderer(callId,HspDeviceList.vidRenderView1); } } } break; default: break; } } };
Handler myMessageHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 5:// connect success if (makecallDialog != null) { makecallDialog.dismiss(); } // showVidWin(); break; case 6: // connect fail if (makecallDialog != null) { makecallDialog.dismiss(); } if ((HspSipStackReceiver.initialSession.getLastStatusCode() != 487) && (HspSipStackReceiver.initialSession .getLastStatusCode() != 200)) {// 自己挂断不显示对话框 Dialog tipDialog = new AlertDialog.Builder( HspDeviceList.this) .setMessage( "连接失败:" + HspSipStackReceiver.initialSession .getLastStatusCode() + " / " + HspSipStackReceiver.initialSession .getLastStatusComment()) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialog, int which) { dialog.dismiss(); } }).create(); tipDialog.show(); } // HspSipStackReceiver.initialSession = null; break; default: break; } super.handleMessage(msg); } };
其中一个子线程中的代码:
@Override public void on_call_media_state(final int callId) { pjsua.css_on_call_media_state(callId); Log.e("lyn", "on_ice_complete"); lockCpu(); if(pjService.mediaManager != null) { // Do not unfocus here since we are probably in call. // Unfocus will be done anyway on call disconnect pjService.mediaManager.stopRing(); } try { final SipCallSession callInfo = updateCallInfoFromStack(callId, null); /* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD, * otherwise we should NOT connect the ports. */ if (callInfo.getMediaStatus() == SipCallSession.MediaState.ACTIVE || callInfo.getMediaStatus() == SipCallSession.MediaState.REMOTE_HOLD) { int callConfSlot = callInfo.getConfPort(); Log.e("lyn-port", String.valueOf(callConfSlot)); pjsua.conf_connect(callConfSlot, 0);//lyn audio pjsua.conf_connect(0, callConfSlot);//lyn audio // Adjust software volume if (pjService.mediaManager != null) { pjService.mediaManager.setSoftwareVolume(); } // Auto record if (pjService.canRecord(callId) && pjService.prefsWrapper .getPreferenceBooleanValue(SipConfigManager.AUTO_RECORD_CALLS)) { pjService.startRecording(callId, SipManager.BITMASK_IN | SipManager.BITMASK_OUT); } } msgHandler.sendMessage(msgHandler.obtainMessage(ON_MEDIA_STATE, callInfo)); } catch (SameThreadException e) { // Nothing to do we are in a pj thread here } unlockCpu(); }
在上面on_call_media_state回调函数中实现了向两个Handler发送消息,第二个Handler的定义这里不列出。
再举几个网上参考的例子。
第一个:子类需要继承Handler类,并重写handleMessage(Message msg) 方法, 用于接受线程数据以下为一个实例,它实现的功能为:通过线程修改界面Button的内容:
public class MyHandlerActivity extends Activity { Button button; MyHandler myHandler; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.handlertest); button = (Button) findViewById(R.id.button); myHandler = new MyHandler(); // 当创建一个新的Handler实例时, 它会绑定到当前线程和消息的队列中,开始分发数据 // Handler有两个作用, (1) : 定时执行Message和Runnalbe 对象 // (2): 让一个动作,在不同的线程中执行. // 它安排消息,用以下方法 // post(Runnable) // postAtTime(Runnable,long) // postDelayed(Runnable,long) // sendEmptyMessage(int) // sendMessage(Message); // sendMessageAtTime(Message,long) // sendMessageDelayed(Message,long) // 以上方法以 post开头的允许你处理Runnable对象 //sendMessage()允许你处理Message对象(Message里可以包含数据,) MyThread m = new MyThread(); new Thread(m).start(); } /** * 接受消息,处理消息 ,此Handler会与当前主线程一块运行 * */ class MyHandler extends Handler { public MyHandler() { } public MyHandler(Looper L) { super(L); } // 子类必须重写此方法,接受数据 @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub Log.d("MyHandler", "handleMessage......"); super.handleMessage(msg); // 此处可以更新UI Bundle b = msg.getData(); String color = b.getString("color"); MyHandlerActivity.this.button.append(color); } } class MyThread implements Runnable { public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.d("thread.......", "mThread........"); Message msg = new Message(); Bundle b = new Bundle();// 存放数据 b.putString("color", "我的"); msg.setData(b); MyHandlerActivity.this.myHandler.sendMessage(msg); // 向Handler发送消息,更新UI } }
以上代码中的MyThread类继承了Runnable接口,重写了run()方法,可以看成是一个任务,在该任务里面实现消息(这个消息发到Handler里面的handlemessage函数中处理)的发送。new Thread(m).start()利用Thread的构造函数传入一个Runnable对象new了一个线程,并启动该线程,在该线程中执行上述的任务。
第一个例子参考: http://www.cnblogs.com/dawei/archive/2011/04/09/2010259.html
第二个:
package djx.android; import djx.downLoad.DownFiles; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class downLoadPractice extends Activity { private Button button_submit=null; private TextView textView=null; private String content=null; private Handler handler=null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //创建属于主线程的handler handler=new Handler(); button_submit=(Button)findViewById(R.id.button_submit); textView=(TextView)findViewById(R.id.textView); button_submit.setOnClickListener(new submitOnClieckListener()); } //为按钮添加监听器 class submitOnClieckListener implements OnClickListener{ @Override public void onClick(View v) { //本地机器部署为服务器,从本地下载a.txt文件内容在textView上显 final DownFiles df=new DownFiles("http://192.168.75.1:8080/downLoadServer/a.txt"); textView.setText("正在加载......"); new Thread(){ public void run(){
content=df.downLoadFiles();
handler.post(runnableUi); } }.start(); } } // 构建Runnable对象,在runnable中更新界面 Runnable runnableUi = new Runnable(){ @Override public void run() { //更新界面 textView.setText("the Content is:"+content); } }; }
以上代码顺序:创建Handler对象(此处创建于主线程中便于更新UI);构建Runnable对象,在Runnable中更新界面;在子线程的run方法中向UI线程post,runnable对象来更新UI。
利用Handler在子线程中处理更新UI操作的方法可以归结为以下几种:1、TimerTask+Handler;2、Thread+Handler;3、Runnable+Handler.postDelayed(runnable,time);4、Message+Handler。
可以参考:http://www.cnblogs.com/devinzhang/archive/2011/12/30/2306980.html