我们知道Android为了线程安全,并不允许我们在UI线程外直接更新UI(多线程并发操作更新很可能导致安全问题),不能在UI线程中执行耗时操作(UI线程超过5s没有响应用于请求会导致ANR),也不能在UI线程中执行网络操作(很可能耗时)。因此我们想要实现UI界面更新可以通过Handler来通知UI组件更新,也可以直接使用runOnUiThread()来更新,同时,Android官方也提供了AsyncTask这个封装好的轻量级异步任务类。因此,分别来学习下这几种方式。当然,平常使用中更多的肯定会用一些成熟的第三方框架,以后有机会再学习。
学习操作系统的时候,我们知道进程和线程的主要区别是:
进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
系统在运行的时候会为每个进程分配不同的内存空间,因此进程都有独立的代码和数据空间(程序上下文),进程之间的切换开销较大,而系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
一个进程可包含多个线程。
Android OS也是如此:参考博文
应用程序(Application):为了完成特定任务,用某种语言编写的一组静态代码。
进程(Process) :一个运行中的程序可以看作是一个进程,是Android OS调度与资源分配的基本单位,操作系统会为每个进程分配一
段内存空间,程序依次执行代码加载 -> 执行 -> 执行完毕的流程。
线程(Thread):比进程更小的执行单元, 一个进程可包含多个线程,线程需要放在一个进程中才能执行。线程是由程序负责管理
的,而进程则是由系统进行调度的。
多线程(Multithreading):并行地执行多条指令,将CPU的时间片按照调度算法,轮流分配给各个线程,由于时间很短,用户并不会
感觉到OS在执行多个任务,但是实际上是在执行多任务,只不过分时执行。
我们这里主要关心的是Handler在UI线程中的情况,在非UI线程的子线程情况之后再讨论,执行流程图大致如下:
**UI线程:**主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue(消息队列)。
**Handler:**作用就是发送与处理消息(包括Message或者Runnable对象)。如果希望Handler正常工作,在当前线程中要有一个Looper对象,UI线程由于默认初始化一个Looper对象,因此可以直接使用,并且会关联到主线程的MessageQueue中;但是如果Handler是在非UI线程的子线程中,就得自己创建一个Looper对象,并且会自动创建一个与其关联的MessageQueue。
通俗点说,通过它可以很轻松的将一个子线程任务切换到Handler所在的线程中去执行(也就是在子线程中发送消息,在UI线程中获取并处理消息)。按照这种说法,如果Handler在UI线程中,那么就可以通过Handler切换到UI线程(当然,切换到UI线程之后,UI线程不能做的事例如网络请求、耗时操作等,Handler也不能处理),这样就可以更新UI了。
上面说到Handler可以发送的消息包括Message或者Runnable对象,那么Handler的发送方法(发送的结果就是将消息插入消息队列中)就有两种,有Delay的一般都带有延迟操作,后面会具体分析怎么延迟:
1)Post:通过Post把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long),post本质实际上调用的其实还是底下2)中的sendxxx方法。
2)sendMessage:通过sendMessage把一个包含消息数据的Message对象插入到消息队列中。它的方法有:sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message,long)、sendMessageDelayed(Message,long)。
Message:Handler接收与处理的消息,可以是简单的数据,也可以是复杂的对象等。 当发送Message对象时,可以有两种方法:
1)借助Message的setData(Bundle budle)与getData(Bundle budle)方法,我们之前在学习活动之间的数据通信以及序列化知识时学习过,Bundle不仅提供了getXXX(key)与putXXX(key,value)方法传递简单的数据,还提供了putParcelable(key,value)、putSerializable(key,value)的方法传递对象。那么就可以通过这种方法发送消息:Message msg=Message.obtain();msg.setData(bundle);
sendMessage(msg)。
2)借助Message的几个属性完成。Message自带了几个属性:
int arg1:参数一,用于传递不复杂的数据,复杂数据使用方法1)中setData()传递。
int arg2:参数二,用于传递不复杂的数据,复杂数据使用方法1)中setData()传递。
Object obj:传递一个任意的对象。
int what:定义的消息码,一般用于设定消息的标志。
因此这里就可以直接Message.obj =XXX或者Message.what=XXX。我们看sendMessage的两个方法sendEmptyMessage(int)和sendMessage(Message)。假如我们要发送消息标志为0x123的消息,分别使用这两种方法就可以写成:sendEmptyMessage(0x123)以及
Message msg=Message.obtain(); msg.what = 0x123; sendMessage(msg)。
Runnable :Runnable对象的run()方法的代码,就执行在UI线程上。
**MessageQueue:**消息队列,用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表。Looper先进先出管理
Message,在初始化Looper对象时会创建一个与之关联的MessageQueue。
**Looper:**每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理。
Demo参考plokmju博文
无网络请求的情况:
首先定义布局文件,1个textview和两个按钮:
修改MainAvtivity代码,在子线程中使用post和postDelayed两种方式发送Runnable对象,Runnable对象的run()方法的代码,就执行在UI线程上:
效果如下,点击post按钮:
点击postDelayed按钮,延迟3s后:
这里看到postDelayed确实延迟了3s后更新了UI,我们知道,发送消息实质上是插入了消息队列中,然后looper不断地从MessageQueue中取出消息分发给对应的Handler处理。这里再次强调消息队列的本质是单链表而非栈,那么postDelayed延迟时间更新是怎么实现的呢?为什么Runnable对象的run()方法的代码或者说这个例子中没有使用的sendxxx的handleMessage方法,就执行在UI线程上?参考AndyJennifer 博文
postDelayed本质上调用的是sendMessageDelayed方法:
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
而sendMessageDelayed实际上又调用了sendMessageAtTime方法,这里注意第二个参数是SystemClock.uptimeMillis()相对时间而不是System.currentTimeMillis()绝对时间,因为在handler受到阻塞,挂起状态,睡眠等状况下,这些时候是不应该执行handler 的;但是如果使用绝对时间的话,就会抢占资源来执行当前handler的内容,这就存在问题了。sendMessageDelayed方法如下:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageAtTime方法如下,又调用了enqueueMessage方法,enqueueMessage方法是MessageQueue用来进行插入操作的,对应的读取并删除操作的是next方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
在sendMessageAtTime方法中就发现,postDelayed最终通过enqueueMessage方法直接将消息插入到消息队列中了,并且将延迟时间作为参数也传了进去。而不是延迟了 延迟时间后再进行插入的。这里虽然直接插入了,但是插入的位置肯定和延迟时间有关。继续查看enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//设置message.target为当前Handler对象
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//获取当前MessageQueue.将消息加入队列中
}
msg.target被复制为当前的Hanlder对象,继续查看MessageQueue的enqueueMessage
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//如果当前消息循环已经结束,直接退出
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;//传入的延迟时间
Message p = mMessages;//mMessages是头部消息
boolean needWake;//是否需要唤醒
//如果队列中没有消息,或者当前进入的消息比消息队列中的消息等待时间短,那么就放在消息队列的头部
if (p == null || when == 0 || when < p.when) {
//如果阻塞则唤醒消息队列
msg.next = p;//单链表操作
mMessages = msg;//将传入的msg作为头部消息
needWake = mBlocked;//如果当前的队列是出于阻塞的状态,那么mBlocked就会为true
} else {
//判断唤醒条件
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//循环遍历消息队列,把当前进入的消息放入合适的位置(比较等待时间)
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//将消息插入合适的位置
msg.next = p;
prev.next = msg;
}
//如果需要唤醒,调用nativeWake,结束等待
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
由上述方法可以看到,postDelayed最终是在插入消息队列中时使用到了延迟时间:在将消息插入到消息队列中时,如果当前消息的延迟时间比链表头部短或者消息队列中没有消息,就将这个消息插入到头部,反之循环遍历消息队列依次比较等待时间,把当前进入的消息放入合适的位置。头部对应的消息就是looper即将取出的消息。
唤醒的方式分为两种情况:
1)如果消息队列本来就为空,那么插入的这个消息就是头部消息,就需要唤醒线程来处理这个消息。
2)消息队列中有消息,但是链表头部消息执行时间大于当前时间,也就是时间还没到,这个时候线程就会阻塞以等待时间到了再执行,进入阻塞前,会把mBlocked标志位置为true。这时候假如我们插入的消息的延迟时间比头部小或者没有延迟时间,就插入头部,到时间或者立即唤醒当前线程处理消息。
那么接下来就看下looper的工作原理,UI线程中由于已经默认初始化一个Looper对象,因此这里就不用再创建Looper了,Looper中最重要的方法是loop,只有调用了loop后,消息系统才会真正起作用(也就是开启消息循环),loop方法如下:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
......
......
for (;;) {//死循环,获取消息队列中的消息
Message msg = queue.next(); //调用next方法进行消息读取与删除,没有消息时next方法阻塞,导致loop方法也阻塞
if (msg == null) {
//唯一退出循环的方式就是next方法返回值为空
return;
}
......
......
try {
//获取消息后,执行发送消息的handler的dispatchMessage方法。
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
......
}
msg.recycleUnchecked();
}
}
上面可以看到loop方法是个死循环,里面最重要的方法就是next()方法,而且唯一退出循环的方式就是next()方法返回值为空。当消息队列中没有消息时next方法会阻塞,导致loop方法也阻塞,如果有新消息了,就调用msg.target.dispatchMessage方法进行消息处理,这时已经是在执行Handler对象的方法了,可以说,在这里完成了线程的切换进行消息处理,也就可以回答为什么Runnable对象的run()方法的代码,就执行在UI线程上。
我们继续详细解析,next()方法如下:
Message next() {
......
......
for (;;) {
synchronized (this) {
......
......
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//遍历消息列表,取出消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
}
......
......
}
}
}
next()方法会一直从MessageQueue中去获取消息,没有消息的话,next()方法一直会阻塞在这里,直到获取消息后返回这条消息并将其从单链表中删除。返回的消息会调用Handler对象的dispatchMessage方法,具体方法如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//首先判断msg.callback是否为空
handleCallback(msg);//不为空,走handleCallback
} else {
if (mCallback != null) {//第二步,判断Handler的callBack
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//第三步,执行Handler的handleMessage方法
}
}
重点就在第一步,Message对象的callback是一个Runnable 对象,实际上就是Handler post方法所传递的Runnable参数。原因如下:
Handler使用post方法发送Runnable对象时,都会调用getPostMessage(Runnable r) 方法,且该方法都会将Runnable封装在Message对象的callback属性上。因此上面才说Message对象的callback是一个Runnable 对象。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;//Message对象的callback是一个Runnable 对象
return m;
}
如果Runnable对象参数不为空,就走handleCallback方法,handleCallback方法如下:
private static void handleCallback(Message message) {
message.callback.run();//看到了熟悉的run方法,也就是Runnable的run方法,这是消息的最终处理方法,且运行在Handler所在线程
}
到这里是不是就看到了,最终消息的处理在先在loop方法中调用了Handler对象的dispatchMessage方法切换到Handler所在线程,这里是UI线程进行处理,由于Runnable对象不为空,就调用了handleCallback方法进行消息处理,最终handleCallback调用了Runnable的run方法中处理,这个run就是我们post方法的Runnable对象参数。
那么如果msg.callback为空,就走的是判断mCallback是否为空。通过Callback 可以采用如下方式来创建Handler 对象: Handler handler = new Handler(callback)。可以用来创建一个Handler的实例但并不需要派生Handler的子类。在日常开发中,创建Handler最常见的方式就是派生一个Handler 的子类并重写其handleMessage方法来处理具体的消息,而Callback给我们提供了另外一种使用Handler 的方式,当我们不想派生子类时,就可以通过Callback来实现。
public interface Callback {
public boolean handleMessage(Message msg);
}
上述两个都不满足时,最后调用Handler对象的handleMessage方法处理消息。这里就看到了大致原理就是从调用Handler对象的dispatchMessage方法切换线程到最终的Handler对象的handleMessage方法处理消息。那么接下来就看下sendxxx方法。
有网络请求的情况:
学习下上节课的Bitmap加载,为了解决ARN问题,采用了Handler机制来异步下载,这里没有考虑OOM与ML的情况。
public class MainActivity extends AppCompatActivity {
private Button button;
private ImageView imageView;
private ProgressDialog dialog;
private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
Bitmap bitmap = (Bitmap) msg.obj;
imageView.setImageBitmap(bitmap);
dialog.dismiss();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindviews();
dialog = new ProgressDialog(this);
dialog.setTitle("提示消息");
dialog.setMessage("正在下载");
dialog.setCancelable(false);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new dlThread()).start();
dialog.show();
}
});
}
public class dlThread implements Runnable{
@Override
public void run() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(image_path).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Message msg = Message.obtain();//推荐用obtain()获取Message
msg.obj = bitmap;
handler.sendMessage(msg);
}
});
}
}
private void bindviews() {
button = findViewById(R.id.button);
imageView = findViewById(R.id.imageview);
}
}
可以看到,最后是在Handler的handleMessage方法中处理消息的。最后别忘了加网络权限,安卓6之后要改为运行时权限,这里4.4就直接加了:
最终效果如下:
UI线程中默认提供了Looper,如果我们在非UI线程中创建Handler时,就得自己创建Looper了,方法如下:
1 )直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue;
2 )创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了!
3 )调用Looper.loop()方法启动Looper
看一个Handler在非UI线程中简单的例子:
public class MainActivity extends AppCompatActivity {
private Button button;
private EditText editText;
dlThread dt;
public class dlThread extends Thread{
public Handler handler;//Handler定义在子线程中
@Override
public void run() {
Looper.prepare();//创建Looper
handler = new Handler(){//消息处理
@Override
public void handleMessage(@NonNull Message msg) {
if(msg.what == 0x123){
int number = msg.getData().getInt("number");//getData得到的是Bundle对象
number = number * number;
Toast.makeText(MainActivity.this,"计算平方结果为:" + number,Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();//上面说的loop方法
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindviews();
dt = new dlThread();
dt.start();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putInt("number",Integer.parseInt(editText.getText().toString()));
msg.what = 0x123;
msg.setData(bundle);//前面说的利用Bundle传递信息
dt.handler.sendMessage(msg);
}
});
}
private void bindviews() {
button = findViewById(R.id.button);
editText = findViewById(R.id.edit);
}
}
效果如下:
参考AndyJennifer:
和之前学习Bitmap的时候一样,Handler异步机制也会导致ML问题。
public class HandlerLeakageActivity extends BaseActivity {
public static final int UPDATE_UI = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_UI) {
updateUI();
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_leakage);
Message message = Message.obtain();
message.what = UPDATE_UI;
mHandler.sendMessageDelayed(message, 1000 * 3600 * 24);//发送延时24小时消息
}
//更新ui
private void updateUI() {...}
}
在HandlerLeakageActivity 中创建了内部类Handler,同时发送了一个延时为24小时的消息。当HandlerLeakageActivity 收到这个延迟消息后在handleMessage中更新UI。造成ML原因在于:由于Handler是非静态内部类,因此会持有有外部类HandlerLeakageActivity的引用;而Message最终是通过Looper的loop()方法取出后,分发给相应的Handler来处理消息,因此Message会持有Handler的引用,而且还是强引用,这时就算我们退出HandlerLeakageActivity,由于消息队列中还有消息,并且延迟消息的迟迟不能被取出执行,导致HandlerLeakageActivity不会被GC。
简单解释的话:
HandlerLeakageActivity finish之后,handler发送的消息最终仍然是通过handler去处理的,而此时Handler内部非静态类还持有HandlerLeakageActivity的实例引用,GC在回收HandlerLeakageActivity时发现HandlerLeakageActivity还存在有强引用关系,就不回去回收HandlerLeakageActivity,导致ML。
解决方法和前面提到的一样,静态内部类+弱引用。
public class HandlerLeakageActivity extends BaseActivity {
public static final int UPDATE_UI = 1;
private MyHandler mHandler = new MyHandler(this);
//使用静态内部类
private static class MyHandler extends Handler {
private final WeakReference<HandlerLeakageActivity> mWeakReference;
MyHandler(HandlerLeakageActivity activity) {
mWeakReference = new WeakReference<HandlerLeakageActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerLeakageActivity activity = mWeakReference.get();
if (activity != null) {
activity.updateUI();
}
}
}
//更新ui
private void updateUI() {...}
}
这样静态内部类就不会不会持有外部类引用,GC在回收时就可以直接回收了。而采用弱引用WeakReference 包裹外部类的对象是因为在更新UI时可能会使用到外部类的成员变量或方法,如果不采用弱引用,直接使用强引用,这显然又会导致Activity内存泄露。
上述就是Handler的基础知识,后面有新的认识会添加进去的~