文章较长,阅读时刻大概3~6分钟,如有不对,欢迎评论讨论
Handler是Android中的音讯处理机制,是一种线程间通信的解决方案,一起你也能够理解为它天然的为咱们在主线程创立一个行列,行列中的音讯次序便是咱们设置的推迟的时刻,假如你想在Android中完结一个行列的功用,无妨第一时刻考虑一下它。本文分为三部分:
Handler的源码和常见问题的回答
- 一个线程中最多有多少个Handler,Looper,MessageQueue?
- Looper死循环为什么不会导致运用卡死,会耗费很多资源吗?
- 子线程的怎么更新UI,比方Dialog,Toast等?体系为什么不建议子线程中更新UI?
- 主线程怎么拜访网络?
- 怎么处理Handler运用不当构成的内存走漏?
- Handler的音讯优先级,有什么运用场景?
- 主线程的Looper何时退出?能否手动退出?
- 怎么判别当前线程是安卓主线程?
- 正确创立Message实例的办法?
Handler深层次问题回答
- ThreadLocal
- epoll机制
- Handle同步屏障机制
- Handler的锁相关问题
- Handler中的同步办法
- Handler在体系以及第三方结构的一些运用
- HandlerThread
- IntentService
- 怎么打造一个不崩溃的APP
- Glide中的运用
Handler的源码和常见问题的回答
下面来看一下官方对其的定义:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.
大意便是Handler答应你发送Message/Runnable到线程的音讯行列(MessageQueue)中,每个Handler实例和一个线程以及那个线程的音讯行列相相关。当你创立一个Handler时应该和一个Looper进行绑定(主线程默许现已创立Looper了,子线程需求自己创立Looper),它向Looper的对应的音讯行列传送Message/Runnable一起在那个Looper地点线程处理对应的Message/Runnable。下面这张图便是Handler的作业流程。
Handler作业流程图
能够看到在Thread中,Looper的这个传送带其实就一个死循环,它不断的从音讯行列MessageQueue中不断的取音讯,终究交给Handler.dispatchMessage进行音讯的分发,而Handler.sendXXX,Handler.postXXX这些办法把音讯发送到音讯行列中MessageQueue,整个形式其实便是一个出产者-消费者形式,连绵不断的出产音讯,处理音讯,没有音讯时进行休眠。MessageQueue是一个由单链表构成的优先级行列(取的都是头部,所以说是行列)。
//ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
能够看到在ActivityThread中的main办法中,咱们先调用了Looper.prepareMainLooper()办法,然后获取当前线程的Handler,终究调用Looper.loop()。先来看一下Looper.prepareMainLooper()办法。
//Looper.java
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//prepare
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
能够看到在Looper.prepareMainLooper()办法中创立了当前线程的Looper,一起将Looper实例存放到线程局部变量sThreadLocal(ThreadLocal)中,也便是每个线程有自己的Looper。在创立Looper的时分也创立了该线程的音讯行列,能够看到prepareMainLooper会判别sMainLooper是否有值,假如调用多次,就会抛出反常,所以也便是说主线程的Looper和MessageQueue只会有一个。同理子线程中调用Looper.prepare()时,会调用prepare(true)办法,假如多次调用,也会抛出每个线程只能由一个Looper的反常,总结起来便是每个线程中只要一个Looper和MessageQueue。
//Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
再来看看主线程sMainThreadHandler = thread.getHandler(),getHandler获取到的实际上便是mH这个Handler。
//ActivityThread.java
final H mH = new H();
@UnsupportedAppUsage
final Handler getHandler() {
return mH;
}
mH这个Handler是ActivityThread的内部类,经过检查handMessage办法,能够看到这个Handler处理四大组件,Application等的一些音讯,比方创立Service,绑定Service的一些音讯。
//ActivityThread.java
class H extends Handler {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SERVICE_ARGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
handleServiceArgs((ServiceArgsData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
handleStopService((IBinder)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case APPLICATION_INFO_CHANGED:
mUpdatingSystemConfig = true;
try {
handleApplicationInfoChanged((ApplicationInfo) msg.obj);
} finally {
mUpdatingSystemConfig = false;
}
break;
case RUN_ISOLATED_ENTRY_POINT:
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
(String[]) ((SomeArgs) msg.obj).arg2);
break;
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
case PURGE_RESOURCES:
schedulePurgeIdler();
break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
}
终究咱们检查Looper.loop()办法:
//Looper.java
public static void loop() {
//获取ThreadLocal中的Looper
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) { //死循环
//获取音讯
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
//收回复用
msg.recycleUnchecked();
}
}
在loop办法中是一个死循环,在这儿从音讯行列中不断的获取音讯queue.next(),然后经过Handler(msg.target)进行音讯的分发,其实并没有什么详细的绑定,由于Handler在每个线程中对应只要一个Looper和音讯行列MessageQueue,自然要靠它来处理,也便是是调用Looper.loop()办法。在Looper.loop()的死循环中不断的取音讯,终究收回复用。
这儿要强调一下Message中的参数target(Handler),正是这个变量,每个Message才能找到对应的Handler进行音讯分发,让多个Handler一起作业。
再来看看子线程中是怎么处理的,首先在子线程中创立一个Handler并发送Runnable。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
}
});
}
}).start();
}
运转后能够看到错误日志,能够看到提示咱们需求在子线程中调用Looper.prepare()办法,实际上便是要创立一个Looper和你的Handler进行“相关”。
--------- beginning of crash
2020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.jackie.testdialog, PID: 21122
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:207)
at android.os.Handler.<init>(Handler.java:119)
at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)
at java.lang.Thread.run(Thread.java:919)
增加Looper.prepare()创立Looper,一起调用Looper.loop()办法开端处理音讯。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
//创立Looper,MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
}
});
//开端处理音讯
Looper.loop();
}
}).start();
}
这儿需求留意在一切作业处理完结后应该调用quit办法来停止音讯循环,不然这个子线程就会一向处于循环等候的状况,因而不需求的时分停止Looper,调用Looper.myLooper().quit()。
看完上面的代码或许你会有一个疑问,在子线程中更新UI(进行Toast)不会有问题吗,咱们Android不是不答应在子线程更新UI吗,实际上并不是这样的,在ViewRootImpl中的checkThread办法会校验mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的结构器中,也便是说一个创立ViewRootImpl线程必须和调用checkThread地点的线程一致,UI的更新并非只能在主线程才能进行。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这儿需求引入一些概念,Window是Android中的窗口,每个Activity和Dialog,Toast分别对应一个详细的Window,Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View经过ViewRootImpl来树立联络,因而,它是以View的形式存在的。咱们来看一下Toast中的ViewRootImpl的创立进程,调用toast的show办法终究会调用到其handleShow办法。
//Toast.java
public void handleShow(IBinder windowToken) {
if (mView != mNextView) {
// Since the notification manager service cancels the token right
// after it notifies us to cancel the toast there is an inherent
// race and we may attempt to add a window after the token has been
// invalidated. Let us hedge against that.
try {
mWM.addView(mView, mParams); //进行ViewRootImpl的创立
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
}
这个mWM(WindowManager)的终究完结者是WindowManagerGlobal,其的addView办法中会创立ViewRootImpl,然后进行root.setView(view, wparams, panelParentView),经过ViewRootImpl来更新界面并完结Window的增加进程。
//WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); //创立ViewRootImpl
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//ViewRootImpl
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
setView内部会经过requestLayout来完结异步刷新请求,一起也会调用checkThread办法来查验线程的合法性。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
因而,咱们的ViewRootImpl的创立是在子线程,所以mThread的值也是子线程,一起咱们的更新也是在子线程,所以不会产生反常,一起也能够参考这篇文章分析,写的非常详细。同理下面的代码也能够验证这个状况。
//子线程中调用
public void showDialog(){
new Thread(new Runnable() {
@Override
public void run() {
//创立Looper,MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run() {
builder = new AlertDialog.Builder(HandlerActivity.this);
builder.setTitle("jackie");
alertDialog = builder.create();
alertDialog.show();
alertDialog.hide();
}
});
//开端处理音讯
Looper.loop();
}
}).start();
}
在子线程中调用showDialog办法,先调用alertDialog.show()办法,再调用alertDialog.hide()办法,hide办法只是将Dialog隐藏,并没有做其他任何操作(没有移除Window),然后再在主线程调用alertDialog.show();便会抛出Only the original thread that created a view hierarchy can touch its views反常了。
2020-11-09 18:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jackie.testdialog, PID: 24819
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
at android.view.View.requestLayout(View.java:24454)
at android.view.View.setFlags(View.java:15187)
at android.view.View.setVisibility(View.java:10836)
at android.app.Dialog.show(Dialog.java:307)
at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)
所以在线程中更新UI的重点是创立它的ViewRootImpl和checkThread地点的线程是否一致。
怎么在主线程中拜访网络
在网络请求之前增加如下代码:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);
StrictMode(苛刻形式)Android2.3引入,用于检测两大问题:ThreadPolicy(线程战略)和VmPolicy(VM战略),这儿把苛刻形式的网络检测关了,就能够在主线程中履行网络操作了,一般是不建议这么做的。
体系为什么不建议在子线程中拜访UI?
这是由于 Android 的UI控件不是线程安全的,假如在多线程中并发拜访或许会导致UI控件处于不可预期的状况,那么为什么体系不对UI控件的拜访加上锁机制呢?缺点有两个:
首先加上锁机制会让UI拜访的逻辑变得复杂
锁机制会降低UI拜访的效率,由于锁机制会阻塞某些线程的履行。
所以最简略且高效的办法便是采用单线程模型来处理UI操作。(安卓开发艺术探究)
子线程怎么告诉主线程更新UI(都是经过Handle发送音讯到主线程操作UI的)
主线程中定义 Handler,子线程经过 mHandler 发送音讯,主线程 Handler 的 handleMessage 更新UI。
用 Activity 目标的 runOnUiThread 办法。
创立 Handler,传入 getMainLooper。
View.post(Runnable r) 。
Looper死循环为什么不会导致运用卡死,会耗费很多资源吗?
早年面的主线程、子线程的分析能够看出,Looper会在线程中不断的检索音讯,假如是子线程的Looper死循环,一旦任务完结,用户应该手动退出,而不是让其一向休眠等候。(引证自Gityuan)线程其实便是一段可履行的代码,当可履行的代码履行完结后,线程的生命周期便该停止了,线程退出。而关于主线程,咱们是绝不期望会被运转一段时刻,自己就退出,那么怎么保证能一向存活呢?简略做法便是可履行代码是能一向履行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的办法,经过循环办法不同与 Binder 驱动进行读写操作,当然并非简略地死循环,无音讯时会休眠。Android是基于音讯处理机制的,用户的行为都在这个Looper循环中,咱们在休眠时点击屏幕,便唤醒主线程继续进行作业。
主线程的死循环一向运转是不是特别耗费 CPU 资源呢?其实不然,这儿就涉及到 Linux pipe/epoll机制,简略说便是在主线程的 MessageQueue 没有音讯时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 办法里,此刻主线程会开释 CPU 资源进入休眠状况,直到下个音讯抵达或者有事务产生,经过往 pipe 管道写端写入数据来唤醒主线程作业。这儿采用的 epoll 机制,是一种IO多路复用机制,能够一起监控多个描述符,当某个描述符安排妥当(读或写安排妥当),则马上告诉相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时分都是处于休眠状况,并不会耗费很多CPU资源。
主线程的Looper何时退出
在App退出时,ActivityThread中的mH(Handler)收到音讯后,履行退出。
//ActivityThread.java
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
假如你尝试手动退出主线程Looper,便会抛出如下反常。
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:428)
at android.os.Looper.quit(Looper.java:354)
at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)
at android.app.Activity.performCreate(Activity.java:7802)
at android.app.Activity.performCreate(Activity.java:7791)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
为什么不答应退出呢,由于主线程不答应退出,一旦退出就意味着程序挂了,退出也不应该用这种办法退出。
Handler的音讯处理次序
在Looper履行音讯循环loop()时会履行下面这行代码,msg.targe便是这个Handler目标
msg.target.dispatchMessage(msg);
咱们来看看dispatchMessage的源码:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
//假如 callback 处理了该 msg 并且回来 true, 就不会再回调 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
1.假如Message这个目标有CallBack回调的话,这个CallBack实际上是个Runnable,就只履行这个回调,然后就完毕了,创立该Message的CallBack代码如下:
Message msgCallBack = Message.obtain(handler, new Runnable() {
@Override
public void run() {
}
});
而handleCallback办法中调用的是Runnable的run办法。
private static void handleCallback(Message message) {
message.callback.run();
}
2.假如Message目标没有CallBack回调,进入else分支判别Handler的CallBack是否为空,不为空履行CallBack的handleMessage办法,然后return,构建Handler的CallBack代码如下:
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
//retrun true,就不履行下面的逻辑了,能够用于做优先级的处理
return false;
}
};
3.终究才调用到Handler的handleMessage()函数,也便是咱们经常去重写的函数,在该办法中做音讯的处理。
运用场景
能够看到Handler.Callback 有优先处理音讯的权力 ,当一条音讯被 Callback 处理并阻拦(回来 true),那么 Handler 的 handleMessage(msg) 办法就不会被调用了;假如 Callback 处理了音讯,可是并没有阻拦,那么就意味着一个音讯能够一起被 Callback 以及 Handler 处理。咱们能够运用CallBack这个阻拦来阻拦Handler的音讯。
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,简直一切的插件化结构都运用了这个办法。
Handler.post(Runnable r)办法的履行逻辑
咱们需求分析平时常用的Handler.post(Runnable r)办法是怎么履行的,是否新创立了一个线程了呢,实际上并没有,这个Runnable目标只是被调用了它的run办法,底子并没有发动一个线程,源码如下:
//Handler.java
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;
}
终究该Runnable目标被包装成一个Message目标,也便是这个Runnable目标便是该Message的CallBack目标了,有优先履行的权力了。
Handler是怎么进行线程切换的
原理很简略,线程间是共享资源的,子线程经过handler.sendXXX,handler.postXXX等办法发送音讯,然后经过Looper.loop()在音讯行列中不断的循环检索音讯,终究交给handle.dispatchMessage办法进行音讯的分发处理。
怎么处理Handler运用不当构成的内存走漏?
有延时音讯,在界面关闭后及时移除Message/Runnable,调用handler.removeCallbacksAndMessages(null)
内部类导致的内存走漏改为静态内部类,并对上下文或者Activity/Fragment运用弱引证。
一起还有一个很要害的点,假如有个延时音讯,当界面关闭时,该Handler中的音讯还没有处理完毕,那么终究这个音讯是怎么处理的?经过测验,比方我翻开界面后推迟10s发送音讯,关闭界面,终究在Handler(匿名内部类创立的)的handMessage办法中仍是会收到音讯(打印日志)。由于会有一条MessageQueue -> Message -> Handler -> Activity的引证链,所以Handler不会被毁掉,Activity也不会被毁掉。
正确创立Message实例
经过 Message 的静态办法 Message.obtain() 获取;
经过 Handler 的公有办法 handler.obtainMessage()
一切的音讯会被收回,放入sPool中,运用享元设计形式。
今日共享到此完毕,对你有协助的话,点个赞再走呗,每日一个面试小技巧
关注公众号:Android老皮
解锁 《Android十大板块文档》 ,让学习更靠近未来实战。已构成PDF版
内容如下:
1.Android车载运用开发体系学习指南(附项目实战)
2.Android Framework学习指南,助力成为体系级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习道路+项目实战(附源码)
5.Android Jetpack从入门到通晓,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构根底
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 功能优化实战+360全方面功能调优
10.Android零根底入门到通晓,高手进阶之路