关于Handle的几个问题

提出问题

现在这里列出几个问题:

  1. Handler的作用
  2. 为什么Android中要设计为只能在UI线程中去更新UI呢?
  3. Handler、Looper MessageQueue之间的关系
  4. 主线程如何让往子线程发消息
  5. 子线程中可以使用Handler吗?
  6. 可以在非主线程中更新UI吗?
  7. 使用Handle要注意什么(会引发什么问题)?

问题分析

以上几个问题如果都能回答的上来,说明关于Handle你已经基本掌握了,如果还有点模糊的话可以先看下以下两篇文章:
Android消息处理机制
View的绘制流程
ok,现在先看第一个问题:

Handler的作用

  • 接收消息:在非UI线程中完成耗时操作,在UI线程中去更新UI。
  • 发送消息:可以在主线程中发送延时消息。

    为什么Android中要设计为只能在UI线程中去更新UI呢?

  • 从源码的角度来回答:
    我们知道既然是要更新UI,那么一定会涉及到View的绘制,既然要绘制View,那么一定会调用ViewRootImpl. ()方法,requestLayout()方法,里面都会调用checkThread();里面就对当前是否是主线程进行判断。如果不是主线程,就会抛异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Override
    public void requestLayout() {//我们在自定义ViewGroup的时候,如果要更新布局就会调用这个方法刷新界面,其实就是调用了View的重新绘制流程。
    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.");
    }
    }
  • 从设计思想的角度来回答:

  1. 解决多线程并发问题(根本原因)在子线程中加锁也可以解决并发的问题,可是会造成UI卡顿的新问题,性能低下,所以不能加锁。
  2. 提高界面更新的性能问题
  3. 架构设计的简单

    说说Handler、Looper MessageQueue之间的关系


    这里的话我举个例子来回答:在一个流水线生产车间中,整个大的机器就是Looper(一直在工作状态),而传送带就是MessageQueue,有货物(Massage)时,就会一直传送(Loop循环),机器处理这些货物(Massage)。没有货物(Massage)时就机器就关闭(主线程进入休眠状态),因为节省成本要省电(释放CPU资源),而工人(Handle)负责把货物(Massage)放到传送带上(Handler.sendMessage(Message))。
    这个例子不知道贴不贴切。。。

    主线程往子线程发消息

    我们用的最多的就是子线程向主线程发送消息,那如何让主线程给子线程发送消息呢?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public class MainActivity extends Activity implements OnClickListener {
    public static final int UPDATE_TEXT = 1;
    private TextView tv;
    private Button btn;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = (TextView) findViewById(R.id.tv);
    btn = (Button) findViewById(R.id.btn);
    btn.setOnClickListener(this);
    //固定写法
    new Thread(new Runnable() {
    @Override
    public void run() {
    //1、准备Looper对象
    Looper.prepare();
    //2、在子线程中创建Handler
    handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    Log.i("当前线程2", Thread.currentThread().getName());
    }
    };
    //3、调用Looper的loop()方法,取出消息对象
    Looper.loop();
    }
    }).start();

    }
    @Override
    public void onClick(View v) {
    Log.i("当前线程1", Thread.currentThread().getName());
    switch (v.getId()) {
    case R.id.btn:
    Message msg = handler.obtainMessage();
    handler.sendMessage(msg);
    break;

    default:
    break;
    }
    }
    }

由此可见可以在子线程中创建使用Handle的,但是以下写法是不对的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);

//尝试在子线程中去创建Handler
new Thread(new Runnable() {
@Override
public void run() {
new Handler();
}
}).start();
}

Handle.class:

1
2
3
4
5
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}

以上会有 “Can’t create handler inside thread that has not called Looper.prepare()”的错误。
所以要在子线程中创建Handle的话必须要执行Looper的prepare()方法,生成Looper对象,把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用Looper的myLooper()方法,mLooper就不会为null了,得到与Handler所对应的Looper对象。handler、looper 、消息队列就形成了一一对应的关系
可以在非主线程中更新UI吗?
可以吗?答案是可以的。要从源码的角度来分析就很好理解。
还是说回View的绘制吧,刚刚也说了View的绘制肯定会调用CheckThread()方法,不是主线程的话肯定会执行不下去的。那好,我们想想,当我们开启一个Activiy的时候,在哪个生命周期方法中开始View的绘制的,是在onCreate()吗?我们看了源码就会知道是在onRsume()中才开始绘制View的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// H();
case RESUME_ACTIVITY://Acticity生命周期Resume
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 调用onResume方法
r = performResumeActivity(token, clearHide, reason);
// 将decorView添加到屏幕中
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//wm其实就是WindowManagerImpl,而在WindowManagerImpl中又调用了WindowManagerGlobal的addView方法。
//将DecorView添加到Window中
wm.addView(decor, l);//调用AddView,开始View的绘制
}

所以,我们只要在onResume方法之前,比如说在onCreate方法中:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) this.findViewById(R.id.startBtn);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("子线程更细UI");
}
}).start();
}

以上代码可直接运行。