初探Android线程

今天接触到了Handler,说到Handler,就不得不说到线程编程。Android为什么要引入Handler机制呢?很多大神都在自己的博客中提到了,一搜一大把,在这里讲一下自己的感受。
每个用户可见的界面(最顶端)的Activity使用的都是同一个线程(main线程,测试过程:从一个界面跳转到另一个界面,打印出各自的threadName,threadId)。如下图:
线程名和线程Id
如果在这一个线程中进行比较耗时的操作,如扫描一个大文件、与数据库进行数据交流等,主界面会卡住,就是所谓的假死,超过5秒手机黑屏甚至引起系统的警报。所以诸如上述行为不能全都在主线程中执行,这就用到了多线程编程。

一、假死的模拟和貌似多线程

1.思路

①Activity1—>跳转到Activity2;
②在Activity2中创建两个Runnable对象,delay1和delay2,delay1单纯sleep5秒,delay2sleep比delay1长(10秒),最后修改TextView的内容。
③在Activity2的onCreate方法中把这两个Runnable对象post()到handler中

伪代码如下:

handler = new Handler();    
handler.post(runDelay1);

setContentView(R.layout.activity_thread_test);

handler.post(runDelay2);
tv.setText("Delayed?");

2.预计结果:

①点击Activity1中的按钮,跳转到Activity2,屏幕空白5s!
②显示出原始字符串【Hello World!】屏幕卡主5s;
③字符串更改成【Delayed?】。

3.BUT! 现实结果:

①点击跳转按钮,在Activity1卡住;
②5s之后,屏幕黑了。。
③5s之后,屏幕亮了,显示的是字符串【Delayed?】

原因下面说明,先附上主要代码:
Activity1:

button.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName() + "," 
                           + Thread.currentThread().getId() + "," 
                           + "MainActivity");

        Intent intent = new Intent();
        intent.setClass(MainActivity.this, ThreadTestActivity.class);
        MainActivity.this.startActivity(intent);
    }
});
Activity2:
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    handler.post(runDelay1);


    setContentView(R.layout.activity_thread_test);

    tv = (TextView)findViewById(R.id.hh);
    handler.post(runDelay2);
    }

    Runnable runDelay1 = new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {

                    while (i > 0) {
                        System.out.println(Thread.currentThread().getName() + "," + Thread.currentThread().getId() + "," + i );
                        Thread.sleep(1000);
                        i--;
                    }

            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    };
    Runnable runDelay2 = new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {

                    while (j > 0) {
                                                            System.out.println(Thread.currentThread().getName() + "," 
                   + Thread.currentThread().getId() + ","
                   + j );
                        Thread.sleep(1000);
                        j--;
                    }

                handler.sendEmptyMessage(1);

            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    };

洋洋洒洒的Activity2白写了,蛤蛤蛤蛤

为什么会这样呢。上面这段代码我试了又试,放在setContentView()前面,后面等等,调试了整整一上午,就是想知道:

  1. 为什么代码不按照顺序执行呢?

  2. handler把线程post到哪里去了啊?不是说好的有个队列吗?

随着调试的深入,渐渐有了表面上的认识:

  1. 在编译过程中,是把setContentView()编译在最后的,执行的时候是在OnCreate方法中最后执行的。

  2. handler有个队列(Looper),可以用来调度这个队列内的Runnable对象,但只是相当于把代码线性插入到线程中去。

  3. 被post进去的run()方法在宏观上就像是嵌入代码一样,并没有开启一个线程去执行。

  4. handler与activity是异步的,不要被这句话骗了,以为是线程间的异步,其实呢,用上面的理论解释,当然是异步的,因为线性执行的一段代码怎么同步?

综上,实际上就是,runDelay1执行了,sleep了5s,然后runDelay2执行,睡5s,直到界面显示Activity2的内容。

二、真多线程

手动创建线程,来实现5s后字符串变成【Delayed?】

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Thread th1 = new Thread(runDelay1);
    th1.start();

    setContentView(R.layout.activity_thread_test);

    Thread th2 = new Thread(runDelay2);
    th2.start();
    tv = (TextView)findViewById(R.id.hh);
    }

    Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            tv.setText("Delayed?");
        }

    };

Runnable的代码差不多,只是多了在runDelay2中给handler发了个空消息。
如此,完美实现了跳转、变字符串。也跟之前预计的一样了。

Note:
在调试过程中遇到了一个问题,就是我直接在runDelay2中修改了TextView的值,运行时报错说在其他线程中修改了UI,查了一下才知道,修改UI只能在main线程中修改。
所以在这个例子中,handler相当于线程间触发器,同时负责线程间通信。

三、符合基本法的写法

  1. 什么是HandlerThread?
    HandlerThread本质上是一个Thread(Extends Thread),而又不仅仅是一个Thread,这个Thread拥有Handler的很多特性,使用HandlerThread创建线程能创建Looper对象,我们用这个Looper对象来初始化Handler,就能达到既使用Handler来管理一些操作,又能使之在新的线程中运行。
    要想使用HandlerThread需要三个步骤:

    • 创建HandlerThread线程

    • 启动线程

    • 获取HandlerThread的Looper,并用它初始化Handler

  2. 同步通讯消息

Message msg = myHandler.obtainMessage();
msg.sendToTarget();//target就是创建mseeage的那个对象

使用Handler创建Message对象,Handler就是Message的target,当消息对象调用sendToTarget()方法时,就会把对象发送到Handler对象。

public class HandlerThreadTestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread_test);

        System.out.println("Activity: " + Thread.currentThread().getName());

        //创建HandlerThread
        HandlerThread handlerThread = new HandlerThread("hth");
        //开启线程
        handlerThread.start();

        //创建Handler
        MyHandler myHandler = new MyHandler(handlerThread.getLooper());
        //用handler创建message
        Message msg = myHandler.obtainMessage();
        msg.sendToTarget();//target就是创建mseeage的那个对象


    }

    class MyHandler extends Handler{
        //构造函数  
        public MyHandler(){

        }

        public MyHandler(Looper looper){
            super(looper);
        }

        @Override
        public void handleMessage(Message msg){
            System.out.println("handlerThread: " + Thread.currentThread().getName());
        }
    }

}

运行结果:
呵呵

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值