您的位置:首页 > 其它

"Only the original thread that created a view hierarchy can touch its views"引发的思考_Handler的使用

2014-07-13 23:04 459 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐