Android11 framework Handler

news2024/11/25 14:32:44

Android11 framework Handler

  • 引言
  • Handler工作流程
  • MessageQueue主要函数
  • Looper主要函数
  • 思考
    • 1.一个线程有几个handler,有几个looper
    • 2.为什么handler会有内存泄漏
    • 3.如果想要在子线程new Handler怎么做?
    • 4.子线程中的loop如果消息队列中没有消息处理的时候怎么做?
      • 消息睡眠和唤醒机制
    • 5.多个可能存在于不同线程的Handler往MQ中添加数据,怎么确保线程安全?
    • 6.使用Message应该如何创建
    • 7.Looper死循环block为什么不会导致应用ANR
  • 同步屏障
  • HandlerThread
  • IntentService——HandlerThread的应用

引言

电梯,把人从楼下运送到楼上;即连接楼层
handler,把消息从主/子线程运送到子/主线程;即连接不同线程的消息管理机制

handler真正的作用,是所有的代码都是在handler上运行的;因为app启动时,会通过ActivityThread的main方法启动JVM,然后通过Looper.prepareMainLopper()Looper.loop()启动loop死循环等待消息。
在这里插入图片描述

Handler工作流程

一般我们使用handler是以send*方法开始,以执行重写的handler.handleMessage()中的逻辑结束

handler -> sendMessage() -> MessageQueue.enqueueMessage()
Lopper.loop() -> MessageQueue.next() -> Handler.dispatchMessage() -> handler.handleMessage()

而通过翻阅源码,可以发现不管是调用send*也好,调用post()也好,最终都是调用到sendMessageAtTime()
在这里插入图片描述
sendMessageAtTime()中调用Handler.enqueueMessage(),从而调用MessageQueue.enqueueMessage(),MessageQueue.enqueueMessage()中进行msg的入队操作,而MessageQueue.next()中会进行出队操作,被loop()中的死循环调用,顺序取出msg,然后进行dispatchMessage()操作进行逻辑处理

注意:假设在点击事件中sendmsg(此时在子线程),而handleMessage是在ui线程(一般为主线程)中执行,即完成了子线程和主线程的消息传递。

MessageQueue主要函数

消息队列是一个由单链表实现的优先级队列
Message中有一个Message类型的属性next
msg->next(msg)->next(msg)->...

MessageQueue.enqueueMessage()

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 {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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; // invariant: p == prev.next
                prev.next = msg;
            }

其中for循环表示以插入排序进行msg管理
在这里插入图片描述
MessageQueue.enqueueMessage()插入msg使用的是其when属性
MessageQueue.next()中取msg是从头部取的,满足队列的属性,为优先级队列

Looper主要函数

核心就在于其构造方法loop()ThreadLocal

	private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

...

	    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    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));
    }

其中 ThreadLocal是一个线程上下文的存储变量
这样一个线程只有一个looper
因为一个Thread->一个ThreadLocalMap<唯一的ThreadLocal, values>->唯一的Looper>MessageQueue

注:Looper中MessageQueue对象为final修饰,且在构造方法中初始化,并提供给Handler

思考

1.一个线程有几个handler,有几个looper

一个线程可以有很多个handler,因为可以在任何地方new;但是一个线程只能由一个looper,通过Threadlocal中注入looper泛型,而Looper.prepare()方法在构造handler之前会判断当前ThreadLocal是否已经set过looper,保证唯一性
在这里插入图片描述

2.为什么handler会有内存泄漏

handler中的callback作为匿名内部类如果持有外部类对象,就会引发内存泄漏,原因如下

Handler.java

    public boolean sendMessageAtTime(@NonNull 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);
    }

	private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

其中msg.target = this;说明msg引用了handler

而msg可能会带有长延时,那么在这个延时中,msg持有handler,handler持有activity/fragment,那么ac/frag会一直不被回收

3.如果想要在子线程new Handler怎么做?

需要手动调用prepare()loop(),用于初始化Looper、MQ(MessageQueue)和开启loop循环

4.子线程中的loop如果消息队列中没有消息处理的时候怎么做?

loop()中的死循环需要退出,msg就需要为null,即queue.next()返回null

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
        }

MessageQueue.java

    Message next() {
    	...
        if (mQuitting) {
            dispose();
            return null;
        }
        ...
    }

mQuitting需要为true

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

通过removeAll...()清除所有消息,然后通过nativeWake(mPtr)唤醒,唤醒是为了避免next()方法处于阻塞状态,下面会说明

Looper.java

    public void quit() {
        mQueue.quit(false);
    }

因此,需要调用 looper中的quit()方法, 用于终止loop死循环

消息睡眠和唤醒机制

消息机制属于生产者-消费者设计模式
在这里插入图片描述
既然MQ作为一个容器,肯定不能无限制的往里面放msg,假设无限制地放,那么内存会爆,这个应该比较好理解。

入队:根据时间排序,当队列满的时候,阻塞,直到消费者通过next取出消息时,通知MQ唤醒,进行消息的入队

出队:通过loop()死循环进行next循环调用,当MQ为空的时候,阻塞,直到调用MQ.enqueueMessage()的时候,通知队列可以调用next,唤醒

但是Handler机制中MQ并没有对容量做限制,即入队阻塞不存在。因为系统也会有消息需要往里面塞,如果设置上限,会导致消息进不去,因此可以无限往里面放,但是放多了内存会炸。

而针对出队这一块,有两个方面的阻塞:

第一,队首消息延时还没到;

第二,消息队列为空;

/*
nextPollTimeoutMillis用于记录当前时间和msg.when延时的差值 now-msg.when
并通过nativePollOnce(ptr, nextPollTimeoutMillis)方法进行阻塞
其中nextPollTimeoutMillis = 0不会阻塞,直接返回
nextPollTimeoutMillis = -1,一直阻塞
nextPollTimeoutMillis > 0,最大阻塞nextPollTimeoutMillis时长,一旦有消息发送给MQ即唤醒

关键逻辑:
获取当前msg
1.如果为null,则nextPollTimeoutMillis = -1,并于之后continue,到下一次for循环阻塞
2.如果不为null,但延时未到,则计算延时time,并且则nextPollTimeoutMillis = time,并continue后阻塞
3.如果不为null,且延时已到,则正常返回msg
*/
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                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;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            ...
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr); //有消息入队时唤醒MQ
            }
            ...
        }
    }

对于nativePollOnce()nativeWake()两个JNI方法,调用栈如下
在这里插入图片描述
在这里插入图片描述

5.多个可能存在于不同线程的Handler往MQ中添加数据,怎么确保线程安全?

锁机制

synchronized:内置锁,由JVM自动完成

synchronized (this) {
}
//this,指messagequeue,对象里的所有方法、代码块,都会受到限制

由于一个线程只有一个looper,只有一个mq对象,又因为synchronized (this),所以同一时间只有一个消息能够被操作,可能是入队、也可能是出队。包括quit也需要加锁

6.使用Message应该如何创建

Message使用了一种设计模式——享元设计模式,即内存复用。使用obtain()而不是构造方法初始化,使用recycleUnchecked()回收,而Message类中维持next和sPool用于保存回收的msg

    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

这是为了防止内存抖动,避免不断new和注销对象,避免内存空洞

7.Looper死循环block为什么不会导致应用ANR

ANR的成因都是因为Message没有及时处理,比如点击事件5s,广播响应10s,services响应20s,都是指最终转换成的msg没有及时地处理,就会通过Handler发送一个ANR提醒,也就是说,就连ANR的提醒也是Handler机制的工作内容,当looper block时只是说明当前没有msg需要执行,即线程没有事情可做,理所应当交出cpu的占用权。

同步屏障

消息是入队是按照执行时间先后排序的,而出队是从队首取出来,那么,如果遇到紧急的消息应该怎么办?

插队!
在这里插入图片描述

消息分为同步消息和异步消息,同步消息正常排队,而当有紧急任务需要处理的时候,就会被声明为异步消息,表示需要优先处理,此时会调用postSyncBarrier()在队列中(不一定是队首)插入一个target为空的msg,叫做同步屏障;那么同步消息什么时候被处理呢,就需要移除同步屏障,即调用removeSyncBarrier()

MessageQueue.java

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            //通过p的时间和屏障的时间,确定屏障消息插入的位置
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                //说明屏障消息不是插入消息队列的头部
                msg.next = p;
                prev.next = msg;
            } else {
                //屏障消息在消息队列的头部
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

    /**
     * Removes a synchronization barrier.
     *
     * @param token The synchronization barrier token that was returned by
     * {@link #postSyncBarrier}.
     *
     * @throws IllegalStateException if the barrier was not found.
     *
     * @hide
     */
    @UnsupportedAppUsage
    @TestApi
    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

	...
    
    Message next() {
        ...
        // Try to retrieve the next message.  Return if found.
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            // Stalled by a barrier.  Find the next asynchronous message in the queue.
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());
        }
        ...
    }

Message.java

    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

    
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

通过MessageQueue.next()源码中可以看到,当检测到同步屏障时:msg.target == null

会从屏障开始往后遍历当前消息队列,直到找到异步消息为止,并将异步消息赋值给msg往下执行,完成插队。

在Android中UI相关的一些操作、ANR等都需要使用异步消息和同步屏障进行插队

比如:ViewRootImpl.java

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //开启同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送异步消息,调用栈如下
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

mChoreographer.postCallback -> postCallbackDelayed -> postCallbackDelayedInternal

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);//异步消息
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

总结:同步屏障的设置可以方便地处理那些优先级较高的异步消息,当我们调用Handler.getLooper().getQueue(). postSyncBarrier()并设置消息的setAsynchronous(true)时,target即为null,也就开启了同步屏障。当在消息轮询器Looper在loop()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

HandlerThread

继承于Thread,在子线程中使用looper时使用。

好处:

1.封装了一个looper,方便了初始化

2.保证了线程的安全

通过wait()notify()对,用来保证多线程的安全性

怎么理解呢?先看看下面的代码

	private void doThread() {
        Thread thread = new Thread(new Runnable() {
            Looper looper;
            @Override
            public void run() {
                Looper.prepare();
                looper = Looper.myLooper();
                Looper.loop();
            }

            public Looper getLooper() {
                return looper;
            }
        });

        thread.start();
        Handler handler = new Handler(thread.getLooper());
    }

这一段代码运行会不会有问题?

会,但是不绝对。因为thread.start()时会异步执行run()方法的逻辑,此时thread.getLooper()run()中的逻辑谁先被执行就不好说了,需要看当时的调度情况,一旦threaed.getLooper()先执行,那返回的looper就为空了。

而转过头再来看看HandlerThread.java的代码

public class HandlerThread extends Thread {
    ...
    ...
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    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();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    ...
    ...
}

可以看到getLooper()方法中加入了mLooper的非空判断,一旦为空,则wait(),释放当前代码块的锁,并使当前线程等待;而执行完run()mLooper初始化的逻辑后,通过notifyAll()唤醒等待的所有线程使其等待此处逻辑结束后执行,此时返回的mLooper一定不为空,所以说HandlerThread能够保证多线程的安全问题

IntentService——HandlerThread的应用

用于处理后台任务,这个类看起来已经弃用了,不过尚可作为学习内容

@Deprecated
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    ...
    ...
        
    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * This method is invoked on the worker thread with a request to process.
     * Only one Intent is processed at a time, but the processing happens on a
     * worker thread that runs independently from other application logic.
     * So, if this code takes a long time, it will hold up other requests to
     * the same IntentService, but it will not hold up anything else.
     * When all requests have been handled, the IntentService stops itself,
     * so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link
     *               android.content.Context#startService(Intent)}.
     *               This may be null if the service is being restarted after
     *               its process has gone away; see
     *               {@link android.app.Service#onStartCommand}
     *               for details.
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
    ...
    ...
}

在此类onCreate()中初始化了一个子线程HandlerThread,在onStart()中发送消息进行入队操作;在handleMessage()中调用抽象方法onHandleIntent执行使用者编写的后台任务逻辑,执行完毕后stopSelf停止service,进行内存释放,避免内存泄露等问题。

同时,因为IntentService根据mName来初始化HandlerThread,即只要mName相同,使用的就是同一个HandlerThread,即同一个子线程looper,所以相同nameIntentService多任务的执行顺序一定是可控制的,先调用的先执行,因为在同一个线程中,由同一个looper轮询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/27869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入底层学git:目录中包含的秘密

1.Git简介 Git具有最优的存储能力&#xff0c;在没有远端git服务器的情况下&#xff0c;git本地就可以独立作为版本管控系统&#xff0c;这其中.git裸仓库中起了关键作用&#xff0c;那么我们一起来看看.git下都放了哪些文件。 打开Git Bash&#xff0c;切换到项目目录&#x…

王道考研——操作系统(第二章 进程管理)(进程;线程)

一、进程的概念、组成、特征 进程的概念 进程的组成——PCB 进程的组成——程序段、数据段 知识滚雪球&#xff1a;程序是如何运行的&#xff1f; 进程的组成 进程的特征 知识回顾与重要考点 二、进程的状态与转换 进程的状态——创建态、就绪态 进程的状态——运行态 进程的…

刷题日记【第十二篇】-笔试必刷题【洗牌+MP3光标位置+年终奖+迷宫问题】

洗牌【编程题】 import java.util.*;public class Main {// 左: i --> 2*i;// 右: in --> 2*i 1;private static void playCard(int[] cards, int n, int k ) {for (int i 0; i < k; i) {//一次洗牌的顺序int[] newCards new int[cards.length];//遍历编号为0-n-1…

【Servlet】2:认识一下Web服务器——Tomcat

目录 第三章 | Tomcat 认识与配置 | 章节概述 | HTTP服务器概述 | Tomcat 安装与配置 | Tomcat 的目录结构、端口号 第四章 | Tomcat 基本使用 | 章节概述 | 本地Tomcat 静态资源网站访问 | IDEATomcat 静态资源网站访问 | IDEA中最基础web项目的目录结构 本文章属于后…

从零开始操作系统-07:APIC

这一节主要主要是APIC。 所需要的文件在Github&#xff1a;https://github.com/yongkangluo/Ubuntu20.04OS/tree/main/Files/Lec7-ExternalInterrupt 历史方法&#xff1a;PIC&#xff08;Programmable Interrupt Controller&#xff09; Intel 8259&#xff1a; APIC&#…

小侃设计模式(十三)-策略模式

1.概述 策略模式&#xff08;Strategy Pattern&#xff09;是一种比较简单的模式&#xff0c;它定义了算法家族&#xff0c;分别封装起来&#xff0c;让它们之间可以互相替换&#xff0c;此模式让算法的变化&#xff0c;不会影响到使用算法的客户。策略模式具有较强的实用性&a…

ARM学习扫盲篇(一):CPSRSPSR、LcacheDcache、w/parityw/ECC

1、CPSR&SPSR CPSR—程序状态寄存器(current program status register) SPSR—程序状态保存寄存器&#xff08;saved program status register&#xff09; Icache&Dcache icache用来缓存指令&#xff1b; dcache用来缓存数据&#xff0c;dcache用的前提是mmu要启动…

(续)SSM整合之SSM整合笔记(ContextLoaderListener)(P177-178)

目录 ContextLoaderListener 一 ContextLoaderListener 二 测试ContextLoaderListener 1 新建模块spring_listener com.atguigu 2. 导入依赖 3 .转web 4 .web.xml 5 springmvc.xml 6 .spring.xml 7 首页index.html 8 控制层 HelloController 9 service接口…

【24计算机考研】备考前必须了解的避坑小知识,建议收藏

前言 我们可能已经了解到最近两三年的考研趋势&#xff0c;疫情的原因&#xff0c;不断增加的二战三战考生&#xff0c;导致每年考研人数持续增长&#xff0c;那么&#xff0c;如何在相同的时间里&#xff0c;赶超你的竞争对手&#xff0c;避坑 绝对是很重要的。 考研将是一场…

【Spring】——9、如何指定初始化和销毁的方法?

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

(STM32)从零开始的RT-Thread之旅--SPI驱动ST7735(3)使用DMA

上一篇&#xff1a; (STM32)从零开始的RT-Thread之旅--SPI驱动ST7735(2) 上一篇完成了ST7735驱动的移植&#xff0c;并已经可以通过SPI在屏幕上显示字符了&#xff0c;这一章会把SPI修改为DMA的传输方式。由于RTT对于STM32H7的SPI的DMA传输方式目前支持的并不好&#xff0c;这…

Vuex3使用教程(待续)

Vuex定义 以下是Vue官网对于Vuex的定义&#xff1a; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 从官方定义上看&#xff1a; Vuex提供了一个全部组件…

Java注释:单行、多行和文档注释

注释是对程序语言的说明&#xff0c;有助于开发者和用户之间的交流&#xff0c;方便理解程序。注释不是编程语句&#xff0c;因此被编译器忽略。 Java入门基础视频教程&#xff0c;java零基础自学就选黑马程序员Java入门教程&#xff08;含Java项目和Java真题&#xff09; Ja…

【Django】Django4.1.2使用xadmin避坑指南(二)

上一篇【Django】Django4.1.2使用xadmin避坑指南调完后&#xff0c;还是继续有问题&#xff0c;没事&#xff0c;咱们继续&#xff0c;必须啃下硬骨头~ 文章目录环境问题一&#xff1a;if not ContentType._meta.installed:这一句报错&#xff1a;AttributeError: Options obje…

《深度学习进阶 自然语言处理》第八章:Attention介绍

文章目录8.1 Attention结构8.1.1 seq2seq存在的问题8.1.2 编码器的改进8.1.3 解码器的改进8.2 Attention的应用8.3 总结之前文章链接&#xff1a; 开篇介绍&#xff1a;《深度学习进阶 自然语言处理》书籍介绍 第一章&#xff1a;《深度学习进阶 自然语言处理》第一章&#xf…

SSH连接WSL2踩坑记录与增加端口转换规则,实现外网与WSL2的连接

SSH连接WSL2踩坑记录 文章目录SSH连接WSL2踩坑记录1. 在WSL里的操作2. ssh连接3. 可能出现的错误4. 再配置端口转发到WSL1. 在WSL里的操作 1.1 重装openssh-server sudo remove openssh-server # 如果已经安装了&#xff0c;建设先卸载 sudo apt install openssh-server…

Ansys Lumerical | 行波 Mach-Zehnder 调制器仿真分析

前言 本示例描述了行波 Mach-Zehnder 调制器的完整多物理场&#xff08;电气、光学、射频&#xff09;仿真&#xff0c;最后在INTERCONNECT中进行了紧凑模型电路仿真。计算了相对相移、光学传输、传输线带宽和眼图等关键结果。 综述 此示例中5毫米长的Si波导由5毫米长的Al共面…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.24 SpringBoot 整合 RabbitMQ(topic 模式)

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.24 SpringBoot 整合 RabbitMQ(topic 模式)5.24.1 …

FL Studio2023水果编曲软件最新版安装教程

FL Studio中文版是知名的音乐制作软件&#xff0c;让你的计算机就像是全功能的录音室&#xff0c;软件包含13种虚拟音源&#xff0c;可同时录制64轨音频轨&#xff0c;FL Studio中文版拥有的漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制&a…

智能化油田建设规划

一、数字化油田-技术现状 数字化油田实现了设备的远程生产过程监控&#xff0c;使井场实现无人值守。所以目前的设备运行维护管理系统只能实现数据统计管理&#xff0c;并不能实现设备状态监控及远程维护及故障诊断。 1、数字化油田— 存在的问题 缺少设备状态在线监测系统&a…