Android在主线程以外访问UI,会得到一个异常。
它是从ViewRootImpl类抛出的:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
安卓之所以不允许在主线程外访问UI,考虑的是并发访问会对UI显示造成影响。
如果加上锁机制,会产生两个问题:
1. UI访问逻辑会变复杂
2. 效率下降
所以安卓选择在主线程更新UI,但是安卓建议不要在主线程进行耗时任务,可能会导致ANR,所以我们需要这样一个机制:
1.在子线程执行耗时任务后,通知主线程更新UI
如果子线程需要主线程更新UI时,主线程正在更新UI,或者有其他的子线程也想更新UI,就需要解决两个问题:
1.子线程通知UI更新后,不想阻塞,要继续做自己的事
2.哪个更新UI的通知先处理,哪个后处理?
Android用一个MessageQueue来解决,我们看一下MessageQueue的入队代码:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
//msg.target是处理这个msg的Handler
throw new IllegalArgumentException("Message must have a target.");
}
//...
//when是UI更新的延迟时间
msg.when = when;
//链表头
Message p = mMessages;
boolean needWake;
//下面是入队方式,用链表插入删除更快
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.新的头结点
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//...
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
//...
}
return true;
}
1.MessageQueue用的是单链表
2.每个Message必须有个target属性,它是处理这个Message的Handler
下面是出队方法MessageQueue.next() 的一部分:
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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;
}
}
Message有个when属性,用来解决定时问题,入队时不管when都入队,出队判断now < msg.when时,取下一个消息。
MessageQueue提供了出队入队方法,谁来调用这些方法呢?
Looper:
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(); // 消息队列没有消息会在这里阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...
try {
msg.target.dispatchMessage(msg);
//...
msg.recycleUnchecked();
}
}
Looper.loop()是个死循环,不断从消息队列中取消息,然后调用 msg.target.dispatchMessage(msg); msg目标Handler的dispatchMessage(),也就是说调用了发送这条消息的Handler的dispatchMessage()方法:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg); //用法1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {//用法2
return;
}
}
handleMessage(msg);//用法3
}
}
整个流程如图所示:
从上面的代码可以得出使用Handler的几种方式
用法1. 用Handler.post(Runnable r)方法发送消息:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
创建消息:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
dispatchMessage中满足msg.callback != null,将会调用
private static void handleCallback(Message message) {
message.callback.run();
}
用法2.在Handler构造方法中传入Callback
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
public Handler(@Nullable Callback callback, boolean async) {
//...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
用法3.重写Handler的handleMessage()方法
private Handler mHadler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1: {
//TODO:自己写处理消息的方法
break;
}
}
}
};
我们看到:Handler中并没有切换线程的操作,Handler只负责发送消息和处理消息,那如何在子线程发送消息到UI线程呢?看看应用启动时调用的ActivityThread的main()方法:
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
//...
Looper.loop();
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
prepareMainLooper() 做了2件事:
1. prepare(false);
2.sMainLooper = myLooper();
看第一件事:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//一个线程只能创建一个Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
sThreadLocal.set(new Looper(quitAllowed));新建了一个quitAllowed为false的Looper,并把它放在了 sThreadLocal中。在Looper中:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal被设计为一种作用域为线程的变量,它的set方法会把变量放在只有与该方法调用线程相同的线程才能访问(用get()获得)。看下面的示例:
public class ThreadLocalTest {
ThreadLocal<Integer> mIntThreadLocal = new ThreadLocal<>();
public void firstThread(int val) {
new Thread("firstThread") {
@Override
public void run() {
super.run();
mIntThreadLocal.set(val);
printLog(mIntThreadLocal.get());
}
}.start();
}
public void secondThread(int val) {
new Thread("secondThread") {
@Override
public void run() {
super.run();
mIntThreadLocal.set(val);
printLog(mIntThreadLocal.get());
}
}.start();
}
public void printLog(Integer integer) {
System.out.println("thread name: "+ Thread.currentThread().getName()+ "\nval= " + integer );
}
}
public static void main(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
test.mIntThreadLocal.set(0);
test.printLog(test.mIntThreadLocal.get());
test.firstThread(1);
test.secondThread(2);
}
结果:
所以,在prepareMainLooper()中调用的Looper.prepare 就是为创建了一个当前线程独占的Looper,并且让sMainLooper指向这个Looper,然后我们就能在子线程调用 getMainLooper()中获取主线程的Looper:
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
在Looper.prepare()创建了Looper,在Looper的构造函数中,创建了MessageQueue(这里也说明我们可以在调用Looper.prepare()后就发送消息,MessageQueue会保留这些Message,而不必等Looper.loop()):
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
所以,应用在启动时,就新建了UI线程的Looper和MessageQueue。我们要在主线程更新UI,可以使用getMainLooper():
Handler UIHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//TODO: change UI
}
}
};
UIHandler.obtainMessage(1).sendToTarget();
下面测试一下在子线程使用Handler:
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
Looper myLooper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), r -> {
Thread thread = new Thread(r);
thread.setName("myLooperThread");
return thread;
});
executor.execute(() -> {
Looper.prepare();
myLooper = Looper.myLooper();
Looper.loop();
});
//----------主线程运行代码---------
if (myLooper != null) {
mHandler = new Handler(myLooper) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//在子线程处理消息
Toast.makeText(MainActivity.this, "收到消息1", Toast.LENGTH_SHORT).show();
break;
}
}
};
mHandler.obtainMessage(1).sendToTarget();
} else {
Toast.makeText(MainActivity.this, "myLooper == null!" , Toast.LENGTH_SHORT).show();
}
}
运行结果:
这是因为当执行到mHandler = new Handler(myLooper)
的时候,myLooperThread
的Looper可能还没有创建好。所以在子线程使用Handler,要注意Looper的创建时机,或者加访问锁,在子线程的获取Looper的方法中加上wait(),然后在创建完Looper之后notifyAll()。看看Android自带的HandlerThread的代码,果然加上了锁:
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//阻塞获取Looper的其他线程
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();//HandlerThread的run方法中包含prepare()
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//通知所有观察此对象的线程切换为运行态
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();//HandlerThread的run方法中包含loop()
mTid = -1;
}
使用HandlerThread,并且测试它的quit()方法:
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HandlerThread handlerThread = new HandlerThread("myLooperThread") {
@Override
public void run() {
super.run();//调用父类的run()方法来prepare loop
//如果不调用quit()或quitSafely()方法,下面的代码将不会调用,因为loop()方法是个死循环
Toast.makeText(MainActivity.this, "Looper.loop()停止了", Toast.LENGTH_SHORT).show();
}
};
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper()) { //当获取不到handlerThread的Looper
//时,主线程阻塞在这里
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//在子线程处理消息
Toast.makeText(MainActivity.this, "收到消息1", Toast.LENGTH_SHORT).show();
mHandler.obtainMessage(2).sendToTarget();
handlerThread.quit();
// handlerThread.quitSafely();
break;
case 2:
Toast.makeText(MainActivity.this, "收到消息2", Toast.LENGTH_SHORT).show();
break;
}
}
};
mHandler.obtainMessage(1).sendToTarget();
}
}
运行结果:
没有收到消息2,因为HandlerThread.quit()会让loop()结束:
for (;;) {
Message msg = queue.next(); // 没有消息会阻塞在这里
if (msg == null) {
// msg为null,说明MessageQueue正在退出,将跳出loop()循环
return;
}
//...
}
使用quit()方法将来不及收到消息2,而使用quitSafely()方法,将会收到消息2. quitSafely()让Looper能在处理完MessageQueue中现存的所有消息后退出。
Android framework开发揭秘 2022最新Android中高级面试题汇总最后,给大家分享一些大佬整理的学习资料,里面包括Java基础、framework解析、架构设计、高级UI开源框架、NDK、音视频开发、kotlin、源码解析、性能优化等资料,还有2022年一线大厂最新面试题集锦,都分享给大家,助大家学习路上披荆斩棘~ 能力得到提升,思维得到开阔~ 有需要的可以在我的【公众号】免费获取!!!
网友评论