您的位置:首页 > 移动开发 > Android开发

Android中级篇之Handler详解(上)

2015-05-30 00:51 411 查看
在详解之前,贴出我们的基本代码。

layout_main.xml
布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/app_name" />

</LinearLayout>


MainActivity


public class MainActivity extends Activity {
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.layout_main);
        initView();
        initEvent();
    }

    private void initView() {
        textView = (TextView) findViewById(R.id.textview);
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    textView.setText("更新");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    private void initEvent() {
    }
}


在子线程中更新UI会抛出什么样的异常?

首先,当我们在子线程中直接更新UI会抛出什么异常呢。运行我们的程序,贴出我们的报错代码。

05-29 22:43:10.524: E/AndroidRuntime(20222): FATAL EXCEPTION: Thread-13219
05-29 22:43:10.524: E/AndroidRuntime(20222): Process: com.scp.handler, PID: 20222
05-29 22:43:10.524: E/AndroidRuntime(20222): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6386)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.widget.TextView.checkForRelayout(TextView.java:6714)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.widget.TextView.setText(TextView.java:3893)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.widget.TextView.setText(TextView.java:3744)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at android.widget.TextView.setText(TextView.java:3719)
05-29 22:43:10.524: E/AndroidRuntime(20222):    at com.scp.handler.MainActivity$1.run(MainActivity.java:25)


产生这个异常的根本原因是Android中不允许在子线程中直接更新UI,那么我们要更新UI可以怎么做呢?接下来,我们通过handler这种机制来更新UI。

handler.post
用法

public class MainActivity extends Activity {
    private TextView textView;
    private Handler handler = new Handler();//Handler 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.layout_main);
        initView();
        initEvent();
    }

    private void initView() {
        textView = (TextView) findViewById(R.id.textview);
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    handler.post(new Runnable() {

                        @Override
                        public void run() {
                            textView.setText("更新");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    private void initEvent() {
    }
}


我们这里呢使用了handler一个比较简单点的更新UI的方法。运行我们的程序,没有报错。那么为什么它能执行成功呢,这是因为我们这个线程是执行在UI线程里的,所以它是可以直接更新UI的。

handler.postDelayed
用法

修改我们的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/app_name" />

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />

</LinearLayout>


修改我们的
MainActivity
,各位自己准备三张图片,我们做一个图片轮播的程序。

public class MainActivity extends Activity {
    private TextView textView;
    private ImageView imageView;
    private int[] imgIds = new int[] { R.drawable.img1, R.drawable.img2,
            R.drawable.img3 };
    private int index;// 图片索引
    private MyRunnable myRunnable = new MyRunnable();
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.layout_main);
        initView();
        initEvent();
    }

    private void initView() {
        textView = (TextView) findViewById(R.id.textview);
        imageView = (ImageView) findViewById(R.id.imageview);
        handler.postDelayed(myRunnable, 1000);
    }

    class MyRunnable implements Runnable {

        @Override
        public void run() {
            index++;
            index = index % 3;
            imageView.setImageResource(imgIds[index]);
            /**
             * r:Runnable对象
             * 
             * delayMillis:每隔一段时间执行一次,单位是毫秒
             */
            handler.postDelayed(myRunnable, 1000);
        }

    }

    private void initEvent() {
    }
}


上面程序运行正常。

注意我们不能直接
setText


//省略上面代码
public void run() {
            index++;
            index = index % 3;
            imageView.setImageResource(imgIds[index]);
            textView.setText(index);//这是我们新加的
            /**
             * r:Runnable对象
             * 
             * delayMillis:每隔一段时间执行一次,单位是毫秒
             */
            handler.postDelayed(myRunnable, 1000);
        }


运行我们的程序,发现会出错。这是因为我们不能直接这样频繁
setText


05-29 23:26:14.774: E/AndroidRuntime(24525): FATAL EXCEPTION: main
05-29 23:26:14.774: E/AndroidRuntime(24525): Process: com.scp.handler, PID: 24525
05-29 23:26:14.774: E/AndroidRuntime(24525): android.content.res.Resources$NotFoundException: String resource ID #0x1
05-29 23:26:14.774: E/AndroidRuntime(24525):    at android.content.res.Resources.getText(Resources.java:289)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at android.widget.TextView.setText(TextView.java:3968)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at com.scp.handler.MainActivity$MyRunnable.run(MainActivity.java:41)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at android.os.Handler.handleCallback(Handler.java:733)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at android.os.Handler.dispatchMessage(Handler.java:95)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at android.os.Looper.loop(Looper.java:136)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at android.app.ActivityThread.main(ActivityThread.java:5336)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at java.lang.reflect.Method.invokeNative(Native Method)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at java.lang.reflect.Method.invoke(Method.java:515)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
05-29 23:26:14.774: E/AndroidRuntime(24525):    at dalvik.system.NativeStart.main(Native Method)


handler.sendMessage
用法

//修改部分代码
    private void initView() {
        textView = (TextView) findViewById(R.id.textview);
        imageView = (ImageView) findViewById(R.id.imageview);
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(2000);
                    Message message = new Message();
                    message.what = 01;
                    message.obj = "更新UI";
                    handler.sendMessage(message);// 发送消息
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    private Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            // 在子线程中更新UI文本信息
            switch (msg.what) {
            case 01:
                String update = (String) msg.obj;
                textView.setText(update);
                break;

            default:
                break;
            }
        }

    };


上面这段代码中,我们可以看到

Message message = new Message();
    message.what = 01;
    message.obj = "更新UI";
    handler.sendMessage(message);// 发送消息


其中

message.what = 01;
handleMessage
区分我们发送的是哪一个消息。作为一个执行ID来用。

message.obj = "更新UI";
则是存储数据用的,它不仅仅可以发送一段文本,还可以发送一个对象,大家可以自行试试发送一个对象,这里我们就不演示了,使用方法都相同。

handler.sendMessage(message);
发送一个消息。

public void handleMessage(Message msg) {
            // 在子线程中更新UI文本信息
            switch (msg.what) {
            case 01:
                String update = (String) msg.obj;
                textView.setText(update);
                break;

            default:
                break;
            }
        }


获取
message
对象

这里,我们不但可以使用
Message message = new Message();
来得到一个
message
对象;

还可以使用
Message message = handler.obtainMessage();
来得到一个
message
对象。他们大体上是一样的。

发送
message
消息

我们通常使用
handler.sendMessage(message);
来发送一个消息;

但是还有一种方法也可以发送一个消息。那就是使用
message.sendToTarget();
我们需要注意的是,当我们使用这种方式来发送一个消息时,我们
message
对象必须是通过
Message message = handler.obtainMessage();
来得到的,否则会出现异常。

05-30 00:14:26.974: E/AndroidRuntime(28528): FATAL EXCEPTION: Thread-13840
05-30 00:14:26.974: E/AndroidRuntime(28528): Process: com.scp.handler, PID: 28528
05-30 00:14:26.974: E/AndroidRuntime(28528): java.lang.NullPointerException
05-30 00:14:26.974: E/AndroidRuntime(28528):    at android.os.Message.sendToTarget(Message.java:360)
05-30 00:14:26.974: E/AndroidRuntime(28528):    at com.scp.handler.MainActivity$2.run(MainActivity.java:34)


message.setTarget(handler)
用法

解释原因

通过异常我们可以看到,这个是没有
handler
来处理我们发送的消息。我们需要绑定一个
handler
来处理这个消息。

为什么
Message message = handler.obtainMessage();
就可以?

那么我们刚刚也说了,我们必须通过
Message message = handler.obtainMessage();
来得到
message
对象,之后才能使用
message.sendToTarget();
来发送消息。大家想一想,我们这个时候是通过
handler
来得到的一个消息,换言之这个
message
绑定了
handler
,所以它可以。

解决办法

我们来看看
message
还有一个方法
message.setTarget(handler);
这个方法是设置一个handler来处理我们发送的消息。当我们在使用
Message message = new Message();
来得到一个
message
对象;时,我们就可以使用这样一个方法来解决消息无人处理的问题。

源码

public void sendToTarget(){
        target.sendMessage(this);//target也就是handler
    }


实质上还是通过
handler
在发送消息,只不过方式不同而已。

更多Handler用法将在下篇进行讲解。

y1笑而过的CSDN博客

y1笑而过的博客园

y1笑而过的新浪博客

y1笑而过的安卓巴士博客

y1笑而过的51CTO技术博客
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: