参考文章:
Android Handler用法解析Android:为什么子线程不能更新UI

因为 Android 的UI更新是在主线程中做的,如果在主线程做一些耗时操作,比如进行网络请求,弱网环境下就极为容易阻塞住UI更新,画面看起来就像是被卡住了。
从Android4.0开始,Android不允许再主线程中进行网络请求,不允许在子线程中进行UI更新。
既然不允许在主线程进行网络请求,为什么还不允许在子线程更新UI呢?
假如允许多线程更新UI,但是访问UI是没有加锁的,一旦多线程抢占了资源,那么界面将会乱套更新了,体验效果就不言而喻了。而且,加了锁会让性能降低,所以在Android中规定必须在主线程更新UI。
那是不是在子线程更新UI就一定不行呢?
答案是:在onCreate方法中,如果你调用子线程不做太多耗时操作的话,是可以在onCreate方法中调用子线程去更新UI的。
下面的代码,在子线程更新UI就不会报错

public class MainActivity extends AppCompatActivity {
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.text);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("测试是否报出异常");
            }
        }).start();
    }
}

原因是ViewRootIml的checkThread方法会检查当前线程是否是主线程,不是的话会报错,而ViewRootImpl对象是在onResume方法回调之后才创建,所以上面的代码并不会报错。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

当我们在子线程做了一些耗时操作后,要更新UI时,我们需要通知给主线程,这个通知使用的工具就是Handler。
我们需要在主线程中定义Handler
如果写成下面这种写法,就会容易产生内存泄漏

public class MainActivity extends AppCompatActivity {
    private ImagerView iv;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case value:
                iv.setImageResoure(...)
                break;
            }
        }
    };

原因是,当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致 Activity泄露。
因此我们需要写成下面这种形式

private static class MyHandler extends Handler {
     private final WeakReference<MainActivity> mTarget;

     public MyHandler(MainActivity activity) {
         mTarget = new WeakReference<MainActivity>(activity);
     }

     @Override
     public void handleMessage(@NonNull Message msg) {
         super.handleMessage(msg);
         if (msg.what == 0) {
             Log.e("myhandler", "change textview");
             // 当没有任何强引用到widget对象时使用get时突然返回null
             MainActivity ma = mTarget.get();
             ma.textView.setText("hahah");
         }
     }
 }

我们需要将 MyHandler 定义成 static ,因为静态内部类不会引用外部类对象。而且,当我们要使用外部类对象的时候需要将其定义成 WeakReference ,这样一旦外部类销毁的时候使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现当没有任何强引用到MainActivity对象时使用get时返回null。
然后在主线程创建一个类型为MyHandler的私有属性:

private Handler myHandler= new MyHandler(this);

创建一个线程,在子线程中向主线程发送消息

new Thread(new Runnable() {
    @Override
    public void run() {
        myHandler.sendEmptyMessage(0);
    }
}).start();

还有一种情况是:如果主线程想通知子线程一些消息,那么就需要使用下面的代码:
先在主线程声明Handler

private Handler threadHandler;

然后创建一个线程

HandlerThread handlerThread = new HandlerThread("test Handler");
handlerThread.start();

然后,真正创建在主线程声明的 Handler 对象

threadHandler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what==2) {
            Toast.makeText(MainActivity.this, "主线程:", Toast.LENGTH_SHORT).show();
        }
    }
};

最后,我们需要在主线程调用

threadHandler.sendEmptyMessage(2);

这样 Handler 中的 handleMessage 方法处理消息。