您的位置:首页 > 移动开发 > Android开发

Android Handler机制简单分析

2018-03-07 00:34 351 查看

Android Handler机制简单分析

本文一切从简,将围绕以下流程展开叙述:



what?

接触Android的朋友都知道Handler机制用于多线程方面的通信,这好像是一句废话。

why?

我们知道java几个具有代表性的多线程通信方法,例如:

“wait”和”notify”通知机制

Java中每个类都是Oject的子类(万物皆对象,滑稽~~),也就具备Oject的”wait()”和”notify()”方法特性。简单举例说明:两个线程中,对于某类的对象a,在线程1中执行a.wait(),线程1则一直处于阻塞中,直到在线程2中执行a.notify(),线程1才被唤醒继续执行。

“synchronized”线程锁机制

多个线程共享一个变量,通过上锁( synchronized关键字 )限制线程们对该变量的访问,谁拿到锁,谁便可以对变量进行修改,待其他线程拿到锁访问该变量时,根据变量的变化作出相应的处理,以达到通信的目的。

此处省略n个字…

嗯,上述方法都是利用线程
阻塞
的方式进行通信。这若在Android中使用?你得先搞清楚3个问题:

Android中多线程通信是为UI线程(主线程)+Worker线程(子线程)的交互服务的。

基于问题1,Android的UI线程不允许阻塞,否则会造成”ANR”( 想了解ANR? 传送门)

基于问题2,为避免”ANR”,Android中所有的耗时操作(如网络请求,文件读写)须在子线程中完成,并通知进度或结果给主线程用于UI更新。

综上: 既然java原生方法无法满足Android程序设计方面的要求,那只能另辟新径了。还好google比较良心,自己挖“坑”自己补,于是设计了一系列UI线程与Worker线程通信的方法,如:

activity.runOnUiThread(Runable action)(Activity类下的切换回UI线程的方法)

view.post(Runable action),view.postDelayed(Runnable action, long delayMillis)(View类下的切换回UI线程的方法)

还有本文的主角Handler机制(异步消息处理机制)等等。

how?

先来一段Demo:

......
public class MainActivity extends AppCompatActivity {

private static final int MSG_DOWNLOAD_TASK = 1;
private TextView tv_progress;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_progress = findViewById(R.id.tv_progress);
}

private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_DOWNLOAD_TASK:
int progress = (int) msg.obj;
tv_progress.setText(progress + "");
break;
}
}
};

/**
* UI上的Button按钮点击事件
* 模拟执行下载任务
*
* @param view
*/
private void download(View view) {
new Thread(new Runnable() {
@Override
public void run() {
int progress = 0;
try {
while (progress >= 100) {
Message msg = Message.obtain();
msg.what = MSG_DOWNLOAD_TASK;
msg.obj = progress;
mHandler.sendMessage(msg);

/**
* 模拟下载进度回调中...
*/
Thread.sleep(1000);
progress++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}


上述demo便是Handler的简单用法,希望大家能看懂。为了简练代码,请忽略内存泄漏~~~

analyze

好了,知道怎么用了,接下来就得知道为什么这样写可以切换到主线程,这就麻烦了,得看源码,蛋疼!!!

怎么看?直接通过demo看:

1.
mHandler = new Handler() {... }
初始化Handler

来,我们来看看Handler构造方法在干嘛:

>>> 下文所有源码均源于Android8.0,为了简练,只保留核心代码 <<<

public Handler() {
this(null, false);//走的是下面的双参构造方法
}

public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();//把当前线程中Looper对象引用交给Handler
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
//不能在此线程中创建handler,因为还没有调用过Looper.prepare()
}
mQueue = mLooper.mQueue;//从Looper对象取出MessageQueue对象给Handler
mCallback = callback;//null值
mAsynchronous = async;//false
}


上述代码,我特意把抛异常的说明翻译了一下,Excuse me?我并没有执行啊,怎么没报异常?怎么Looper.myLooper()有值的啊?

其实这并不矛盾,在同一个线程中可以创建一个或多个Handler对象,但前提必须是当前线程已创建(通过Looper.prepare()创建)并保存或已存在唯一的Looper对象(不理解没关系,不了解Looper也没有关系,下文会继续说),Android所有线程之间的通信皆如此,主线程亦然。

Android中,app运行入口是在ActivityThread类里的main函数开始的,没错,你没看错,就是java程序的入口main函数,android app也是java写的,当然也是main入口的,那么我们直接看核心源码来解释上面的疑问:

......
public final class ActivityThread {
......
public static void main(String[] args) {//app程序入口
......
//1.其实本质还是走Looper.prepare(),见下面Looper类相关代码便知
Looper.prepareMainLooper();
......
if (sMainThreadHandler == null) {
//2.获取的是Handle子类H对象引用,在H中添加了处理各种消息的业务(不理解没关系,反正就是创建个Handler子类的对象)
sMainThreadHandler = thread.getHandler();
}
......
//3.轮询消息
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}
}


Looper类下相关代码:

......
public final class Looper {
......
public static void prepareMainLooper() {
//带参的Looper.prepare(quitAllowed)方法
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {

throw new IllegalStateException("The main Looper has already been prepared.");
//已存在Looper对象了,不要再创建了
}
sMainLooper = myLooper();
}
}

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");
//每个线程只能创建一个Looper对象,其实还是在说已存在Looper对象了,不要再创建了
}
//这里创建了久违的Looper对象
sThreadLocal.set(new Looper(quitAllowed));
}
--------------顺便看看Looper.prepare()在干什么--------------------

public static void prepare() {
/**
*本质是走上面的带参的prepare(quitAllowed)方法
*不要太在意quitAllowed参数,反正是传给Looper对象用的
*/
prepare(true);
}
}


上面代码可以总结两点:

1.由于是app程序入口,main函数一定执行在主线程(UI线程)上,并且程序一开始就为主线程创建并保存好了Looper对象,以便为Handler子类H提供服务,既然已存在,当然不需要自行“Looper.prepare()”了。

2.Android官方已经为我们提供了Handler机制代码模版↓↓↓



逻辑代码写法流程图:



所以,可以这样归纳:主线程与子线程间通信不需要写Looper.prepare(…)和Looper.loop(),子线程与主线程以及子线程与子线程间的通信则需要

2.
Message msg = Message.obtain()....mHandler.sendMessage(msg);
子线程中通过mHandler对象发送消息

先分析
Message.obtain()
源码:

/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*个人翻译:从全局池返回一个新Message实例(可能是新创建的,也可能是从全局池中c重新复用的)。
*允许我们在多数情况下避免分配(创建)过多的新对象
*/
public static Message obtain() {
synchronized (sPoolSync) {//同步锁访问机制
if (sPool != null) {//池不为null,复用已存在的对象
Message m = sPool;//从池中取出Message对象(很明显这个池也是Message类的对象)
sPool = m.next;
/**
*结合上面可以知道这个池其实是由多个Message对象组成的链表结构(不知道链表?找度娘...)
*每次复用的都是表头的Message对象
*表头被取走(复用)后,紧连着表头的另一个Message对象成为新的表头,以此类推
*先不要想这个链表是怎么添加Message对象的,也不要着急看Message类全部源码,因为不是本文重点
*/
m.next = null;//对即将复用的表头(Message对象)进行脱链,从此自由啦!
m.flags = 0; // clear in-use flag (清除“在使用中的”的标记,恢复初始状态以便复用)
sPoolSize--;//复用后,链表长度减1
return m;//返回表头(复用表头)
}
}
return new Message();//池为null时直接创建新Message对象
}


再看
new Message()
构造方法源码:

/**
* Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
* 个人翻译:构造器(但是推荐的方式是调用"Message.obtain()")
*/
public Message() {
}


嗯哼,如此简单明了的告诉你:其实我的构造方法没啥骚操作,但希望你优先使用Message.obtain()方式获取Message实例,避免铺张浪费。

不好意思,这段时间换了新工作,一直在折腾接入项目的事,然后就是CSDN编辑器不支持Mermaid语法让人头疼,一直在想办法解决这个问题。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: