zoukankan      html  css  js  c++  java
  • "Only the original thread that created a view hierarchy can touch its views"引发的思考_Handler的使用

      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

  • 相关阅读:
    LeetCode(287)Find the Duplicate Number
    LeetCode(290) Word Pattern
    LeetCode(205)Isomorphic Strings
    LeetCode(201) Bitwise AND of Numbers Range
    LeetCode(200) Number of Islands
    LeetCode(220) Contains Duplicate III
    LeetCode(219) Contains Duplicate II
    命令行执行Qt程序
    LeetCode(228) Summary Ranges
    redis 的安装和使用记录
  • 原文地址:https://www.cnblogs.com/lynchyo/p/3499799.html
Copyright © 2011-2022 走看看