我的理解是,子线程要和主线程通讯,就需要Handler+Message-消息机制

 


 

 

案例一:倒计时Demo(子线程+Handler+Message)

package liudeli.async;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView tvInfo;

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

        tvInfo = findViewById(R.id.tv_info);
    }

    /**
     * 定义Handler
     */
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if (msg.obj != null) {
                tvInfo.setText(msg.obj.toString());
                return;
            }

            tvInfo.setText(msg.what + "");
        }
    };

    /**
     * 耗时操作不能在主线程 定义主线程
     */
    private class MyTimeing implements Runnable {

        @Override
        public void run() {
            for (int i = 10; i >= 0; i--) {
                // mHandler.obtainMessage(i); 这种消息池方式获取Message消耗小
                Message message = mHandler.obtainMessage(i); // what 其实就是ID的意思,唯一标示
                if (i <= 0) {
                    // obj 是Object 什么类型都可以传递: 传递T类型,获取的时候就强转T类型
                    message.obj = "倒计时完成✅";
                }
                // 用Android提供的睡眠方法,其实是封装来Thread.sleep
                SystemClock.sleep(1000);

                // 从子线程 发送消息 到---> 主线程的handleMessage方法
                mHandler.sendMessage(message);
            }
        }
    }

    /**
     * 执行倒计时
     * @param view
     */
    public void timeing(View view) {
        // 主线程被阻塞 5秒 未响应,系统就会自动报错 ANR Application Not Responding
        /**
         * Android系统中,ActivityManagerService(简称AMS) 和 WindowManagerService(简称WMS)会检测App的响应时间
         * 如果App在特定时间无法响应屏幕触摸或键盘输入事件,或者特定事件没有处理完毕,就会出现ANR
         */
        // 启动自行车来做耗时操作
        new Thread(new MyTimeing()).start();
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">

        <TextView
            android:id="@+id/tv_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0000000"
            android:layout_gravity="center_vertical"
            android:textSize="20sp"
            android:layout_marginLeft="10dp"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="倒计时"
            android:onClick="timeing"
            android:layout_gravity="right"
            />

    </LinearLayout>

</RelativeLayout>

 


 

 

案例二:文字变化(Handler+Message)

package liudeli.async;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

public class MainActivity2 extends AppCompatActivity {

    private TextView tvInfo;

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

        tvInfo = findViewById(R.id.tv_info);
    }

    private int count;

    /**
     * 定义Handler
     */
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if (msg.obj == null) {
                return;
            }
            if (msg.what <= 7) {
                tvInfo.setText(msg.obj.toString() + "");
                Message message = mHandler.obtainMessage();
                message.what = count++;
                message.obj = "竟然渐渐清晰  想要说些什么  又不知从何说起" + count;
                mHandler.sendMessageDelayed(message, 900);
            }

        }
    };

    /**
     * 执行文字变化
     * @param view
     */
    public void textChange(View view) {
        // mHandler.obtainMessage(i); 这种消息池方式获取Message消耗小
        Message message = mHandler.obtainMessage();
        message.what = 1;
        message.obj = "从那遥远海边  慢慢消失的你  本来模糊的脸";
        // 从主线程 发送消息 到---> 主线程的handleMessage方法
        mHandler.sendMessageDelayed(message, 800);
        count = 0;
    }
}

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center">

        <TextView
            android:id="@+id/tv_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Test"
            android:layout_gravity="center_vertical"
            android:textSize="20sp"
            android:layout_marginLeft="10dp"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="文字变化"
            android:onClick="textChange"
            android:layout_gravity="right"
            android:layout_marginTop="20dp"
            />

    </LinearLayout>

</RelativeLayout>

 


 

 注意:⚠️在Activity的 onDestroy() 方法中,一定要记得回收

 

postDelayed 看似子线程,其实是属于主线程,postDelayed能延时run,但不能在子线程中执行,否则报错

这个开启的Runnable会在这个Handler所依附线程中运行,而这个Handler是在UI线程中创建的,所以自然地依附在主线程中了。

  /**
     * 参数一:可以在Runnable中执行UI操作
     * 参数二:延时时间 毫秒
     * new Handler().postDelayed(Runnable r, long delayMillis);
     * @param view
     */
    public void test(View view) {

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity2.this, "postDelayed", Toast.LENGTH_LONG).show();
            }
        }, 2000);
}

 

子线程 send 到 主线程 的常用方法

     // 参数一:Message消息,可传递数据
        // 参数二:delayMillis延时时间,延时多久才send,毫秒为单位
        // 特点是:send Message消息,并可设置延时时间
        // sendMessageDelayed(Message msg, long delayMillis)

        // 参数一:Message消息,可传递数据
        // 特点是:立即send,不延时
        // mHandler.sendMessage(Message msg)

        // 参数一:唯一标识what
        // 参数二:delayMillis延时时间,延时多久才send,毫秒为单位
        // 特点是:send 唯一标识what,并可设置延时时间
        // mHandler.sendEmptyMessageDelayed(int what, long delayMillis)

        // 参数一:唯一标识what
        // 特点是:send 唯一标识what,立即send
        // mHandler.sendEmptyMessage(int what)

 

消息池的概念:

   Android操作系统启动后默认会有很多的消息池,这样的方式直接到系统里面消息池拿消息mHandler.obtainMessage(), 而new Message(); 是实例化消息(消耗多些) 

     推荐用mHandler.obtainMessage()方式

 


 

 

面试题:postDelayed(Runnable r) Runnable 是属于子线程吗?

答:和子线程没有半毛钱关系,命名叫Runnable而已,子线程必须是 有run方法,有.start();

     new Thread(){
            @Override
            public void run() {
                super.run();
            }
        }.start();

 

按原理来说:子线程是不能执行UI操作,确实不能执行UI操作,但Android设计了API看起来可以在子线程(这是假象),实际是对Handler+Message进行了封装

/**
     * 在子线程中操作UI
     * @param view
     */
    public void uiRun(View view) {
        new Thread(){
            @Override
            public void run() {
                super.run();
                /**
                 * 第一种方式
                 */
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity2.this, "runOnUiThread", Toast.LENGTH_LONG).show();
                    }
                });

                /**
                 * 第二种方式
                 */
                Looper.prepare();
                Toast.makeText(MainActivity2.this, "Looper", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
    }