• "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

  • 相关阅读:
    EasyUI DataGrid及Pagination
    Easyui Datagrid的Rownumber行号显示问题
    Easyui控制combotree只能选择叶子节点
    Easyui datebox单击文本框显示日期选择
    EasyUI DataGrid可编辑单元格
    EasyUI获取DataGrid中某一列的所有值
    EasyUI DataGrid 多级表头设置
    EasyUI DataGrid 编辑单元格
    EasyUI combobox 加载JSON数据《一》
    EasyUI combobox 加载JSON数据
  • 原文地址:https://www.cnblogs.com/lynchyo/p/3499799.html
Copyright © 2020-2023  润新知