Android中级篇之Handler详解(上)

在详解之前,贴出我们的基本代码。

  • 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技术博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

y1笑而过song

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值