结合源码拆解Handler机制

news2024/11/18 13:31:17

作者:Pingred

前言

当初在讲App启动流程的时候,它的整个流程涉及到的类可以汇总成下面这张图:

那时着重讲了AMS、PMS、Binder这些知识点,有一个是没有对它进行详细讲解的,那就是常见的Handler,它不仅在这个流程里作用在ApplicationThread和ActivityThread进行通信,它在整个安卓体系中也扮演着重要的角色,所以也是面试过程中经常被问到的知识点,所以接下来将对它的源码进行分析。

Handler发送消息过程分析

先来从它的发送消息方法跟踪:

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

继续看sendMessageDelayed()方法:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

这里可以看到一个sendMessageAtTime()方法,把消息传进去之外,还传了消息时间(系统启动时间+用户定义的时长),它其实就是核心方法,而同样handler的sendMessage()方法跟踪下去,它最后也是调用这个sendMessageAtTime()方法,所以来看该方法的详情:

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

可以看到,在这里创建了MessageQueue对象,也就是我们常说的Handler机制里的四大元素之一队列,然后把发送的消息和队列以及时间参数传到了enqueueMessage()方法,也就是入队列操作:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

继续来看queue的enqueueMessage()方法:

boolean enqueueMessage(Message msg, long when) {
        ...

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

在分析这个方法前,先来想一想队列这个概念,可能有部分人对它有点陌生,这里解释一下,MessageQueue是一个队列,它并不是数组或者集合那样,是定义一个有容量的“容器”,而是通过单链表Message这个结构:

public final class MessageQueue {
    private static final String TAG = "MessageQueue";
    private static final boolean DEBUG = false;

    // True if the message queue can be quit.
    private final boolean mQuitAllowed;

    @SuppressWarnings("unused")
    private long mPtr; // used by native code

    Message mMessages;//单链表Message
    ...

作为MessageQueue持有的变量,Message里有一个指向下一个Message对象的引用,因此Message消息其实是一个单链表形式串联在一起的。它不同于线性表那样,比如一个数组是一块连续的内存空间,里面每一个对象按照顺序排在一起,因此查询的时候可以通过基址加偏移量(也就是直接a[index]的方式)来访问每一个元素对象,但它也有它的缺点,容易造成内存碎片,进而内存溢出:

每次构造都需要一大片连续内存空间才能构造出来,所以即使中间有些不是连续空出来的空间也只能浪费掉。

而单链表如果要访问第三个对象(比如此时有三个对象),则必须先要知道第二个对象的地址,而第二个对象又必须要靠第一个对象来获取,因此这就需要从头开始遍历才能最终访问到第三个对象。但单链表的好处就是,如果要在某个位置上插入或者删除某个节点对象,直接操作就可以(把它的指向下一个对象的引用的指向进行更改就可以)。

可以看到,创建链表不需要一大块连续内存空间,通过next引用的指向就能达到一个连一个,像一条链一样。了解单链表之后,回到enqueueMessage()方法来继续分析:

boolean enqueueMessage(Message msg, long when) {
    ...
            msg.markInUse();
            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 {
                ...
            }
...

这段代码其实很好理解,第一条消息进来时,P对象为空,mMessages对象也是为空,然后此时p==null成立,因此走if里面的代码块,然后让当前这条要发送的消息的下一个消息对象next引用指向p(也就是null),然后让mMessages等于当前消息对象。

那么当第二条消息进来时,又重新new了一个p对象,让它等于mMessage(也就是第一条消息对象),这次进来就需要判断第二条消息的时间是不是小于第一条消息的时间,也就是when < p.when是否成立,如果是小于的情况,那么就让当前消息对象msg(也就是第二条消息)的下一个指向next引用指向p(也就是第一条消息对象),而此时mMessages对象等于第二条消息对象,那现在这种情况也就是说如果我发送的第二条消息对象的时间是比第一条消息对象的时间还要小的话,那么就会让第二条消息排在第一条消息对象的前面,也就是先让第二条消息先发送:

第三条消息的时间也是比第二条消息要小,所以依然第三条消息的next引用指向第二条消息对象,那么当第四条消息对象的时间是比第三条消息对象的时间要大的话,则就走else块的代码,来看看会发生什么:

boolean enqueueMessage(Message msg, long when) {
    ...
            msg.markInUse();
            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 {
                // 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;
            }
...

可以看到,此时就是先创建一个prev对象,用来记录当前消息的上一个消息对象的,让它等于p(也就是第三条消息对象),然后p等于p(第三条消息对象)的下一个消息对象(此时是为第二条消息),所以现在p就为第二条消息对象,之后就判断当前对象(第四条消息对象)的时间when是否小于第二条消息对象的when,如果成立,则不用再循环,走msg.next = p;和prev.next = msg这两行代码,这两行代码的意思就是:当前消息对象msg(第四个消息对象)的下一个指向对象为p(此时为第二个消息),然后prev(此时为第三个消息对象)的下一个指向为msg当前消息对象,所以此时单链表的顺序是第三条消息对象指向第四条消息对象,第四条消息对象则指向第二条消息对象,因为第四条消息对象的时间是小于第二条的时间,大于第三条的时间:

那如果刚刚在for循环那里,此时第四条消息它的时间比第二条消息还要大,那循环就继续,prev等于第二条消息对象,而p等于 第二条消息对象的下一个对象即第一条消息对象,然后此时判断条件第四条消息对象时间比第一条消息对象时间要小,成立,则跳出循环,执行msg.next = p;和prev.next = msg这两行代码,所以此时第四条消息对象的下一个对象则指向第一条消息对象,而第二条消息对象则执行第四条消息对象,所以此时链的顺序是:第三条消息对象指向第二条消息对象,第二条消息对象则指向第四条消息对象,而第四条消息对象则指向第一条消息对象:

所以,handler的消息排序机制也就是靠这个时间when来决定谁先谁后,小的那个排在大的后面,借由单链表的插入特性来进行插入。因此,handler发送的消息其实是发送到MessageQueue里的链表Message里存储。

Handler请求在哪里被处理

那就要在ActivityThread里找到main()方法,也就是当应用启动时的方法,在里面可以看到:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        ...

        Looper.prepareMainLooper();

        ...

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

Looper.prepareMainLooper()方法就是创建Looper对象,而Looper.loop()方法则开启轮询消息,不断去取消息,然后进行分发,所以看看它是怎么处理消息的:

public static void loop() {
        final Looper me = myLooper();
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
}

可以看到是遍历MessageQueue队列,然后去取它里面的消息对象Message,调用queue的next()方法:

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

取之前,有个nativePollOnce()方法,是用来判断是否唤醒请求方法,使用了epoll机制,防止loop()方法一直轮询会卡死程序。然后接下来取消息的代码块里又加了synchronized同步锁,也就意味着多个消息不会出现并发的问题(即使多个handler同时发送消息):

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

取出消息对象后,再次回到loop()方法中,最后调用这个方法来处理消息:

public static void loop() {

            ...
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...

}

调用了msg消息对象的target的dispatchMessage()方法去分发消息,这里的target其实就是handler对象:

public final class Message implements Parcelable {
    public int what;
    ...
    /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;

    ...
}

Handler、Looper、MessageQueue和Message四者关系

首先Handler可以在任意Activity里创建的,而Looper则是在ActivityThread的main()方法里创建的,接下里回到main()方法里往下看prepareMainLooper()方法:

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

可以看到这里new了Looper对象,然后把它传到sThreadLocal的set()方法里,那么先来看看looper的构造方法:

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

好,那到现在为止,我们可以知道Looper是在ActivityThread的main()方法里调用Looper.prepareMainLooper()方法创建的,而Looper调用构造方法的同时,它也把MessageQueue给创建出来,创建的方法有直接new的方法,也有obtain()方法:

  Message message = new Message();
  Message message2 = Message.obtain();

官方建议使用obtain()方法来创建Message对象,因为obtain()方法采用了缓存池方式,定义了一个缓存池来构造了消息对象,然后要创建消息对象,就直接从池里取就行(没有缓存对象才去重新new一个新的消息对象),这样能避免了每次都要构造与销毁对象造成的性能消耗以及可能引发的内存抖动现象。

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

给它上锁,防止并发问题,然后定义m对象,让它等于sPool,也就是此时最后那个消息对象,然后sPool等于最后一个消息对象的下一个消息对象,那此时假设它为第一个消息对象,接着继续,此时m的下一个对象要指向null了,因为要把它取出来,然后容量要自减1,这段逻辑就是一个链表的常规取元素的逻辑,很容易理解。可以看到该池默认的容量是50:

不过这里有一点要补充的,就是这里的容量是50可能会有些歧义,让人觉得MessageQueue就真的只能缓存50个消息对象,但上面已经分析过Message结构是链表,理论上来说数量是没有限制的,而这里规定了50是处于性能的考虑以及方便我们去管理MessageQueue,如果实在是要突破这个容量去构造Message对象也是可以的,因为源码里也没有超出50之后抛异常的逻辑,所以当超出50之后,就重新new消息对象,直到本身内存不够报错。

为什么要用缓存池的方式来处理消息对象,因为除了我们用户之外,安卓底层其实很多地方都使用到了handler来发送和处理消息(比如ActivityThread和ApplicationThread通信),因此消息对象会频繁地被使用到,为了避免刚才说的频繁构造与销毁对象造成的性能消耗以及可能引发的内存抖动现象,就定义池来管理消息对象,一旦你要构造消息对象,就使用池里事先缓存好的消息对象,直到缓存池里的消息对象用完了,这时才去new新的消息对象。

ThreadLocal跟Handler的关联

在主线程中可以直接new我们的Handler对象,而在子线程中则要创建looper对象和开启looper轮询消息方法:

public class PingredActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                    }
                };
                handler.sendEmptyMessage(1);
                Looper.loop();
            }
        }).start();
    }


}

刚刚在看源码的时候可以看到主线程在activityThread中这两步已经帮我们写好了,因此直接创建Handler对象就可以。

还有一点要注意的是,每个线程的Looper都是不一样的,主线程中的Looper是属于主线程的,而子线程中Looper则属于子线程的,在创建Looper对象时,别忘了,它最后是作为参数传给了threadLocal对象里:

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是一个用来存储每个线程自己独有的一些副本数据的对象,用线程自己的id作为key值可以从ThreadLocal对象里取它对应的线程里存的数据,比如Looper对象,所以为什么说一个线程里可以有多个handler对象,而looper对象以及messageQueue只能是一个,就是因为它们都被set()方法存进ThreadLocal里。

或许用一个示例(伪)代码你就更能明白了:

 public void doThreadLocal(){
        final ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
            @Nullable
            @Override
            protected String initialValue() {
                //默认返回null,现重写方法返回HelloWorld
                return "HelloWorld";
            }
        };
        //从ThreadLocalMap中获取值,key是主线程
        Log.v("main","主线程的threadLocal:" + threadLocal.get());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //还没set值之前
                String before_value = threadLocal.get();
                Log.v("child","thread1:" + before_value);
                //set值之后
                threadLocal.set("pingred");
                String after_value = threadLocal.get();
                Log.v("child","after_thread1:" + after_value);

            }
        });
        thread.start();
    }

现在在主线程里定义了一个ThreadLocal对象,它有一个重写方法initialValu()方法,该方法返回了我们定义的值,该方法是当没有给ThreadLocal设置值的时候,就返回这个初始化的值HelloWorld。而无论是用哪个线程去获取ThreadLocal里的值时,如果没有线程在当初自己线程里往ThreadLocal存值时,那么无论是哪个线程去获取ThreadLocal里的值时,就会返回这个初始值HelloWorld。

而现在子线程里给initialValu()方法里调用set()方法设置了一个值,因此当在子线程里通过get()方法去取ThreadLocal里的值时,就会获取到“pingred”,也就是当前子线程当初存的值,而此时如果主线程去获取,则获取到的值则是“HelloWorld”,也就是主线程自己设置的那个值,这也印证了我们上面说过的,每个线程通过ThreadLocal去获取值时,只会获取到它们自己存的值,这些值不是共享的,是独有的。

当然我们还是要靠源代码来证实,因此看看get()方法的源码:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可以看到,通过获取到当前的线程去构造了线程对象t,然后通过它去获取到一个ThreadLocalmap集合,这样一来其实就是表示每个线程都有它自己的独有的这个map集合,用来存储它们自己独有的数据,最后返回获取到数据。如果map里没有数据,则就返回初始方法里的初始值。比如现在是在主线程里调用get()方法,那它现在得到的map肯定就不是子线程的map,那它自然就不可能得到子线程里存储的数据,因此返回的是setInitiaValue()方法里的值。

而同样如果此时是子线程里调用get()方法,那它也只能是根据自己的map去得到自己存的值,而不会得到主线程存的值,除非子线程没有存值,因此就会走setInitiaValue()方法,返回的是该方法里设置的值。set()方法也是遵循这个原则:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

使用当前线程去操作,不同的线程设置自己的数据。所以ThreadLocal也有防止多线程并发问题的作用,这样一来Looper对象以及消息队列及其里面的消息就不用被其他线程访问到。

说到这个Thread.currentThread()方法,就不得不提到一个常见的问题,就是为什么子线程里不能更新UI,我们来看看requestLayout()方法的源码:

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

它里面有这么一个方法checkThread():

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

可以看到,当View进行测量、布局和绘制之前会先调用checkThread()方法去当前线程是不是同一个线程,如果不是就会抛出异常。其实这样也是避免了多线程都可以去控制UI的更新进而引发并发问题,而且每一次UI更新都是一次性能的损耗,需要测量和绘制等,而多线程操纵UI会导致多次调用测量和绘制。

epoll机制

为什么Looper一直循环都不会导致应用卡死,因为使用了epoll机制,epoll机制又是什么,它是linux的通知机制,最早的时候,linux的(请求)通知机制是请求方不停地去轮询发送请求,也就是每隔几秒就会发送请求,这种方式太损耗发送方的性能和资源了。

于是就改为第二种方式,也就是当请求方发送请求之后,就会阻塞:一直等待接收方,直到接收方把相应数据消息返回回来,发送方才关闭请求。但这样的话,有不好的地方,如果接收方一直没回应,发送方就一直阻塞在这里,还有,假如有100个请求发送给接收方,只有50条请求是有数据返回过去,剩下50条不用返回数据,这时服务器就会不断循环这100条然后进行判断哪条需要发送数据,哪条不用发送数据,这样也很消耗服务器资源和性能,因为的app这么多人使用,同一个时间内能发送几百万条请求也不是不可能的。

因此基于上面种种,就有了epoll机制,监听每个请求,并且用表记录每个请求的发送情况,当有数据发过来,就记录该请求是有事件发生的,然后epoll_wait就加1,表示这条请求连接它是有事件发生的,是需要返回数据的,那这样服务器就之后就不用遍历每条请求连接,因为只需去看这个表的记录情况,当检查到某条连接记录epoll_wait是发生变化的,则就表示需要返回数据,而没变化,则不用返回数据,继续让该请求连接保持休眠状态,然后下次再监听到该请求的表中的epoll_wait有变化,则就唤醒该连接,然后把数据返回给发送方,这样请求方也不用一直阻塞了。

可以理解epoll_create表示创建一个epoll对象,epoll_ctl记录epoll对象中添加请求socket,epoll_wait记录发生事件的请求连接。

nativeWake()方法就是如果当前请求需要休眠,则调用linux的epoll机制的唤醒/休眠方法去处理该Handler请求,所以Looper一直轮训发送/接收请求都不会卡死应用。

最后,Handler使用不当容易引起内存泄露问题,因为Handler对于Activity来说它是内部类,持有Activity的引用,因此当Activity销毁后,Handler消息此时刚好传回来的时候,它由于持有Activity的引用,导致Activity还有GCroot,而不会被回收,从而导致内存泄漏,要解决这个问题,可以把内部类修饰为static静态,这样就等于断开Activity与Handler的联系,Handler不再持有Activity。又或者可以在Activity的onDestroy方法里调用removeCallbacksAndMessages方法:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler.sendEmptyMessageDelayed(123, 1000);
//        Message message = new Message();
//        Message message2 = Message.obtain();
    }

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            handler.sendEmptyMessageDelayed(123, 1000);
        }
    };

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        handler.removeMessages(123);
        super.onDestroy();
    }
}

如果你还没有掌握 Handler,现在想要在最短的时间里吃透它,可以参考一下《Android Framework核心知识点》,里面内容包含了:Init、Zygote、SystemServer、Binder、Handler、AMS、PMS、Launcher……等知识点记录。

《Framework 核心知识点汇总手册》:https://qr18.cn/AQpN4J

Handler 机制实现原理部分:
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

Binder 原理:
1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

Zygote :

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

AMS源码分析 :

  1. Activity生命周期管理
  2. onActivityResult执行过程
  3. AMS中Activity栈管理详解

深入PMS源码:

1.PMS的启动过程和执行流程
2.APK的安装和卸载源码分析
3.PMS中intent-filter的匹配架构

WMS:
1.WMS的诞生
2.WMS的重要成员和Window的添加过程
3.Window的删除过程

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

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

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

相关文章

一篇掌握BFD技术(一):静态路由与BFD联动配置

1. 实验目的 熟悉静态路由与BFD联动的应用场景掌握静态路由与BFD联动的配置 2. 实验拓扑 想要华为数通配套实验拓扑和配置笔记的朋友们点赞关注&#xff0c;评论区留下邮箱发给你 3. 实验步骤 1&#xff09;配置IP地址 AR1的配置 <Huawei>system-view Enter system…

Linux——socket网络通信

一、什么是socket Socket套接字 由远景研究规划局&#xff08;Advanced Research Projects Agency, ARPA&#xff09;资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将 TCP/IP 协议相关软件移植到UNIX类系统中。设计者开发了一个接口&#xff0c;以便应用程序能简单地…

继续深挖,Jetpack Compose的State快照系统

Jetpack Compose 有一种特殊的方式来表示状态和传播状态变化&#xff0c;从而驱动最终的响应式体验&#xff1a;状态快照系统&#xff08;State snapshot system&#xff09;。这种响应式模型使我们的代码更加强大和简洁&#xff0c;因为它允许组件根据它们的输入自动重组&…

Docker安装及Docker构建简易版Hadoop生态

一、首先在VM创建一个新的虚拟机将Docker安装好 更新系统&#xff1a;首先打开终端&#xff0c;更新系统包列表。 sudo apt-get update sudo apt-get upgrade下图是更新系统包截图 安装Docker&#xff1a;使用以下命令在Linux上安装Docker。 sudo apt-get install -y docker.i…

离谱事件解决方法2 无法定位程序输入点XXX于动态链接库XXX.dll

事情经过&#xff1a; 本人一只acmer&#xff0c;使用sublime编写代码&#xff0c;但是前两天在打开cpp类型的文件的时候显示报错如下&#xff1a; 这里的dll文件就是动态链接库&#xff0c;它并不是一个可执行文件&#xff0c;里面存放的是程序的函数实现过程&#xff08;公用…

postgresql基于postgis常用空间函数

1、ST_AsGeoJSON 图元转geojson格式 select ST_AsGeoJSON(l.geom) from g_zd l limit 10 2、 ST_Transform 坐标转换 select st_transform(l.shape, 3857) from sde_wf_cyyq l limit 10select st_astext(st_transform(l.shape, 3857)) from sde_wf_cyyq l limit 103、st_aste…

创建本地镜像

通过前面文章的阅读&#xff0c;读者已经了解到所谓的容器实际上是在父镜像的基础上创建了一个可读写的文件层级&#xff0c;所有的修改操作都在这个文件层级上进行&#xff0c;而父镜像并未受影响&#xff0c;如果读者需要根据这种修改创建一个新的本地镜像&#xff0c;有两种…

【位运算进阶之----左移(<<)】

今天我们来谈谈左移这件事。 ❤️简单来说&#xff0c;对一个数左移就是在其的二进制表达末尾添0。左移一位添一个0&#xff0c;结果就是乘以2&#xff1b;左移两位添两个0&#xff0c;结果就乘以2 ^ 2&#xff1b;左移n位添n个0&#xff0c;结果就是乘以2 ^ n&#xff0c;小心…

shopee店铺如何注册?卖家需要准备哪些材料?

shopee是这两年发展迅速的东南亚电商平台&#xff0c;国内也是有越来越多的卖家入驻开店。目前&#xff0c;Shopee入驻的门槛是比较低的&#xff0c;卖家账号注册也比较简单。如果你想入驻Shopee&#xff0c;但是又不知道要这么注册卖家账号&#xff0c;那么就要往下看了。 申请…

ch3_1汇编语言程序的源程序

mark 一下&#xff0c; 2023.Aug.15 从湖北返回学习&#xff0c;参加了一场学术会议&#xff0c; 看来做学术确实是需要交流的&#xff0c; 尤其该领域的多交流&#xff0c; 还是需要至少一年参加一次学术会议. &#xfeff; 不至于让自己太孤陋寡闻&#xff0c; 局限于自…

小程序如何手动变更会员卡等级

有时候需要商家手动变更会员卡等级&#xff0c;以让会员获取更多的福利和特权。下面就介绍一些小程序手动变更会员卡等级的常见方法和策略。 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要更改等级的会员卡。也支持对会员卡按卡号、手机号和等级进行…

宝塔计划任务读取文件失败

想挂计划任务 相关文章【已解决】计划任务读取文件失败 - Linux面板 - 宝塔面板论坛 对方反馈的是执行下面的命令 chattr -ai /var/spool/cron 后来发现直接没有这个文件夹&#xff0c;然后通过mkdir命令创建文件夹&#xff0c;成功在宝塔创建了计划任务 后面发现任务虽然添…

Markdown初级使用指南

前言 大家好&#xff0c;我是艾老虎尤&#xff0c;我在一篇官方的文章中&#xff0c;我了解到了markdown&#xff0c;原本我写博客一直是使用的富文本编译器&#xff0c;之前我也有同学叫我使用MD&#xff0c;但是我嫌它复杂&#xff0c;就比如说一个标题&#xff0c;我在富文…

SFM structure from motion

struction就是空间三维点的位置 motion 就是相机每帧的位移 https://www.youtube.com/watch?vUhkb8Zq-dnM&listPL2zRqk16wsdoYzrWStffqBAoUY8XdvatV&index9

单片机学习-蜂鸣器电子元件

蜂鸣器是有什么作用的&#xff1f; 蜂鸣器 是 一种 一体化结构 的电子训响器&#xff0c;可以发出声音的电子元器件 蜂鸣器分类&#xff1f; ①压电式蜂鸣器&#xff08;图左&#xff09; 称&#xff1a; 无源蜂鸣器 ②电磁式蜂鸣器&#xff08;图右&#xff09; 称&#xf…

ISIS路由协议

骨干区域与非骨干区域 凡是由级别2组建起来的邻居形成骨干区域&#xff1b;级别1就在非骨干区域&#xff0c;骨干区域有且只有一个&#xff0c;并且需要连续&#xff0c;ISIS在IP环境下目前不支持虚链路。 路由器级别 L1路由器只能建立L1的邻居&#xff1b;L2路由器只能建立L…

SpringCloud学习笔记(十)_SpringCloud监控

今天我们来学习一下actuator这个组件&#xff0c;它不是SpringCloud之后才有的&#xff0c;而是SpringBoot的一个starter&#xff0c;Spring Boot Actuator。我们使用SpringCloud的时候需要使用这个组件对应用程序进行监控与管理 在SpringBoot2.0版本中&#xff0c;actuator可以…

TensorFlow中slim包的具体用法

TensorFlow中slim包的具体用法 1、训练脚本文件&#xff08;该文件包含数据下载打包、模型训练&#xff0c;模型评估流程&#xff09;3、模型训练1、数据集相关模块&#xff1a;2、设置网络模型模块3、数据预处理模块4、定义损失loss5、定义优化器模块 本次使用的TensorFlow版本…

电商项目part07 订单系统的设计与海量数据处理

订单重复下单问题&#xff08;幂等&#xff09; 用户在点击“提交订单”的按钮时&#xff0c;不小心点了两下&#xff0c;那么浏览器就会向服务端连续发送两条创建订单的请求。这样肯定是不行的 解决办法是,让订单服务具备幂等性。什么是幂等性&#xff1f;幂等操作的特点是&a…

Vue2向Vue3过度Vue3组合式API

目录 1. Vue2 选项式 API vs Vue3 组合式API2. Vue3的优势3 使用create-vue搭建Vue3项目1. 认识create-vue2. 使用create-vue创建项目 4 熟悉项目和关键文件5 组合式API - setup选项1. setup选项的写法和执行时机2. setup中写代码的特点3. <script setup>语法糖 6 组合式…