先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新HarmonyOS鸿蒙全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注鸿蒙)
正文
// 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;
}
}
// 把传进来的message插入了队列中
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
建议你仔细看几遍以上代码,然后,看下面的流程图
一图以蔽之
此方法执行完后,如果一切正常的话,就可以将Message入队了。
next方法的执行过程
下面,我们来看,从MessageQueue中取出Message的部分,也就是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.
// 如果应用程序试图在退出后重新启动looper(这是不支持的),就会发生这种情况。
final long ptr = mPtr; // mPtr是native代码使用的
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0; // 阻塞时间
for (;😉 { // 注意,这里是一个死循环
if (nextPollTimeoutMillis != 0) {
// 将当前线程中挂起的Binder命令刷新到内核驱动程序。
// 在执行可能会阻塞很长时间的操作之前调用此方法非常有用,
// 以确保所有挂起的对象引用已被释放,以防止进程持有对象的时间超过所需时间。
// 总之,这个方法在执行阻塞任务之前调用会好,又因为下面有可能会阻塞,所以在这里调用了这个方法。
Binder.flushPendingCommands();
}
// 阻塞,阻塞时长为nextPollTimeoutMillis
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
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());
}
// 此时,msg要么为null,要么为队首消息,要么为第一个异步消息
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 {
// -1表示一直阻塞
nextPollTimeoutMillis = -1;
}
// 当处理完所有该处理的message时,才去处理要不要quit
if (mQuitting) {
dispose();
return null;
}
// idle handles 只能在MessageQueue为空,或者队首的Message的when不符合要求时,才会执行
// 下面的英文其实说的更详细
// 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();
}
// 如果第一次循环,处理了mIdleHandlers里面的任务,那么第二次循环时
// pendingIdleHandlerCount为0,所以直接continue,而不会重新处理mIdleHandlers
// 注意:这里的循环指的是某一次next方法执行时,里面for死循环中的一次循环。
// 而又因为next方法会执行多次,所以第二次next方法执行时,又会重新启动一次for死循环,
// 那么这时,还是会判断是否要处理mIdleHandlers里面的任务。
if (pendingIdleHandlerCount <= 0) {
// No idle Handlers to run. Loop and wait some more.
mBlocked = true;
continue; // 注意一下这个continue。
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行IdleHandler里面的代码,只有第一次迭代的时候会执行到这里
// 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);
}
}
}
// 注意这里,不管上面的mIdleHandlers是否还有元素,都重置。因为queueIdle都已经执行过一次了
// Reset the idle Handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 设置阻塞时长为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;
}
}
一图以蔽之
出现了!!!你绝没见过的流程图!!! 这个就稍显复杂了,不过多看几遍代码,和我画的流程图做对照,相信你很快就能理解。
需要注意的点,是IdleHandler。在这里,可以看到,它执行的时机,是取不出合适的Message,在阻塞线程之前,执行。同时,执行完之后,会刷新阻塞时长。
所以在这里,它也有坑,
一是有可能永远都不会执行,因为有可能一直都能取出合适的Message。
二是有可能delay正常的Message。比如队首的Message,需要等待1ms就可以执行,然后在阻塞1ms之前,发现有idleHandler可以执行,那么就去执行idleHandler,如果执行idleHandler的耗时为10ms,那么就delay了队首的Message的执行时机,delay了9ms。
尽管它有坑,但还是会在性能优化的场景中用到,用于在线程空闲时执行一些优化任务。
好,以上,关于MessageQueue的拿到Message,和交付Message,就全部介绍完了。
Looper怎么调度的消息
下面介绍Looper的拿到Message,和交付Message的过程,也就是这一部分
如何拿到消息
拿到Message,是在开启消息循环,即Looper.loop
中实现的
可以看到这个方法的注释,谷歌官方特意提示,一定要调用!所以我们在开启子线程的消息循环的时候,一定要调用Looper.loop
方法。里面有一个核心逻辑,是死循环,每一次循环,会调用loopOnce
方法,让我们进入到这个loopOnce
方法中,看一看
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // 这里有可能阻塞
if (msg == null) {
// msg为null,只有两种情况,即MessageQueue正在退出或者已经退出
return false;
}
…
msg.target.dispatchMessage(msg); // 把message交付给Handler
…
msg.recycleUnchecked(); // 回收message,缓存起来,以便下次复用
…
return true;
}
在每次loopOnce
中,一切正常的话,就可以取出一个消息。
如何交付给Handler的
把message
交付给Handler是怎么交付的呢?Message的target
属性就是Handler,所以在这里就是调用了Handler的dispatchMessage
方法,进去看下
这里就非常明朗了,在这里,进行最终关于message的处理,然后形成了闭环。
一图以蔽之(Looper的loop)
细看Handler对Message的处理
其实,这里值得再强调下,我们看到,这里有三种方式来处理message,分别是
我们一个一个地看。
处理方式一:handleCallback方法
第一个,handleCallback
,是Message的callback
属性不为null时,调用。在前面的内容中有过介绍,Message的callback
属性,就是调用Handler.post()
时,传入的Runnable。可以看下图
所以,如果此Message,是通过Handler.post
方式传进来的,那么就直接执行Runnable里面的run
方法。
这是第一种处理message的方式。
处理方式二:mCallback的handleMessage方法
第二种,是看Handler的mCallback
属性是否为null,如果不为null,则调用mCallback
的handleMessage
方法。这里的mCallback
和前面说的Message的callback
属性不同。这里的mCallback
是创建Handler的时候,通过构造方法得到的,
这个Callback和Runnable没有关系,而是一个自定义的接口,里面有一个方法,是handleMessage
所以第二种处理方式,是调用mCallback
的handleMessage
方法。如果返回了true,则处理结束,如果返回了false,那么就进入到第三种处理方式,即handleMessage
处理方式三:子类实现handleMessage方法
这里默认实现为空,所以此方式,一般是调用的子Handler的handleMessage
方法,来处理的Message。
一图以蔽之
为什么分了三种处理方式?
我个人认为,有两个原因。
(1)保证消息最终能够进行处理
Handler可以通过多种方式进行创建:比如可以直接new,或者new一个子类,同时呢,Handler的构造函数还有好几种。每一种创建方式,对应的消息处理方式,不一定是相同的。所以,分了三种,原因之一,是要把所有的消息处理方式全部覆盖到,尽可能防止有消息处理不到的情况。
(2)有些场景,可能需要对message多次处理,有第二种和第三种处理方式,可以满足此需求。
在代码上,关于第二种和第三种处理方式,分别是这样实现的
// 第二种处理方式,赋值给Handler的mCallback属性
val Handler1 = Handler(object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
return true/false
}
})
// 第三种处理方式,派生子类,重写handleMessage方法
val Handler2 = object : Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
这两种处理方式有一种关系,就是如果第二种处理方式返回了false,那么第三种处理方式还要执行。即有可能两种处理都会执行,这在某些降级处理场景,可能会被用到。
但其实呢,谷歌提示我们尽量避免创建子类,进而避免调用子类的消息处理方法,而优先用前两种处理Message的方式。原因来自这个注释:
在Callback的类注释中,有“尽量避免不得不实现Handler的子类”这样的字眼,所以得出了上面那个结论。
所以,为什么分了三种处理方式,总结下,就是两个原因
- 保证消息最终能够进行处理
- 有些场景,可能需要对Message多次处理,有第二种和第三种处理方式,可以满足此需求。
以上,就把Handler机制的全貌,都捋了一遍了。
三. 加餐:Handler那些事儿
1. 为什么子线程不能访问UI,非要整一个Handler?
因为在Android中,关于UI操作,是没有加锁的,所以不是线程安全的,所以只能是单线程访问才可以。因此Android只能单线程修改UI,而且这个单线程只能是主线程。
那么,为什么不加锁呢?
我能想到的一个很重要的原因,就是流畅性,UI修改一定要是瞬时就要变化的,如果有多线程互相抢着锁,那UI的访问及修改效率都会低很多,手机就会表现的很卡。
而且像UI这种,非常基础的能力,没必要做的这么复杂,如果访问一个UI控件,修改一个UI控件都要加锁的话,那就太复杂了,完全没必要。
总结
Android只能允许单线程修改UI,然后设计了Handler机制来实现子线程修改UI的需求。
2. MessageQueue的阻塞唤醒机制是如何实现的?
其实就是:epoll、pipe机制
什么是pipe?
在类Unix-like操作系统中,一切皆文件,包括管道pipe。管道可以像文件一样在文件系统中存在,并且可以使用文件描述符来引用它们。
管道的作用之一,就是可以实现,线程间的通信。一个线程与另外一个线程之间发生的读、写操作,都可以通过管道,来实现。
什么是epoll?
epoll 是 Linux 中的高效的 I/O 多路复用机制,它会监听一个或多个文件描述符的多个事件类型,其中之一是文件描述符的写入事件。
epoll如何实现阻塞、唤醒?
epoll能够实现阻塞、唤醒的原理,就是能够监听管道这个文件描述符的读操作和写操作,实现阻塞和唤醒。
比如,一个MessageQueue中,没有了消息,那么就应该被阻塞了,那么怎么保持一直阻塞的?什么时候会被唤醒?答案就是:epoll可以监听,pipe的文件描述符的写操作。
当有新的消息写入时,epoll就可以监听到,从而通知线程,实现唤醒。没有写入时,就能保持一直阻塞。
因此,可以这么理解:在没有数据可读时,MessageQueue主要使用了 native 层的 epoll 机制来监听文件描述符(通常是pipe)的写入事件,以实现持续的阻塞和唤醒。
一个更好理解的例子
举个生活中的例子,当有人给我打过来电话时,我的手机会通过亮屏,响手机铃声或者震动等方式来通知我。
OK,这里面,包含了两个方面。一是可以打电话,有电话线这条“管道”。二是可以通知我,让我知道,有人打电话过来了。
在这里,电话线就像是pipe,通知就像是epoll。在没有电话打来时,且我当前没有正在接听电话时,属于“读阻塞”的状态,此时如果有写入操作,则会进行唤醒。这里的写入,就是有新的人给我打来了电话,然后epoll通过通知我,唤醒了我,然后我就可以接听电话,即对写入的这条消息进行处理。同时呢,手机不光是在收到电话的时候可以通知,在比如收到微信视频通话的通知时,来了一条短信时,都可以发起通知,这对应epoll的可以监听多个文件描述符的事件的特点。
在MessageQueue中,哪些地方进行了阻塞
在next
方法中,总共有两个地方,对mBlocked
进行了赋值。
- 一是消息正常取出时,将
mBlocked
赋值为false
,表示不阻塞。 - 二是当没有可取出的消息,且也没有可以处理的IdleHandler时,赋值为
true
,表示阻塞。
3. 同步屏障消息和异步消息?
同步屏障消息即Message的target
属性为null
异步消息即Message的isAsynchronous
返回true
两者之间是如何运转的?
在没有遇到同步屏障消息时,同步消息和异步消息都是正常在MessageQueue上面根据when
属性排列,依次取出,执行。然而,当遇到同步屏障消息时,就会向后遍历,找到第一个异步消息,然后根据when
是否满足要求,决定如何处理。
这里呢,这种机制,一般会用在优先级比较高的,需要尽快执行的任务,比如绘制UI(draw、invalidate、requestLayout
等方法)
4. 消息循环如何退出
其实这块的内容非常多,完全可以单拎出一篇文章来。我们先从入口说起。
退出,是通过调用Looper的quit
或者quitSafely
来实现的。
两者都是调用了MessageQueue的quit
方法,只是传递的参数不同。让我们进去MessageQueue的quit
来瞧一瞧
safe
参数,顾名思义,就是描述是否安全退出。在这个方法中,首先会判断,是否允许退出,如果不允许,则抛出异常。如果允许,则看当前是否已经在退出中,如果是的话则return,不是,则继续。这里就用到传过来的参数safe
了。如果是true,则代表安全退出,否则是非安全退出。让我们看下这两个方法有什么区别?
非安全退出
先看非安全退出,也就是removeAllMessagesLocked
方法,它是找到Message队列的头节点,然后依次向后遍历,把所有的message
全部回收。非常简单粗暴
安全退出
如果是安全退出
在这里,它会判断队首的message
的when
属性,和now
的大小。如果now
小于when
,则说明队首的消息不满足被取出的条件,则调用removeAllMessagesLocked
方法,将所有的消息全部回收。否则就向后遍历到第一个不符合取出要求的message
,将它以及它后面的message
全部回收。 也就是说,when
满足要求的Message,还是会被Looper正常取出然后交给Handler进行消费。
回收Message后,做了什么?
然后,再回到MessageQueue的quit
方法来,我们看到,在回收完之后,还调用了一个nativeWake
方法
这里的目的其实是唤醒所有阻塞的线程,以免引起不必要的等待,因为消息循环,都已经退出了,所有该回收的消息都回收了,这时候就应该考虑释放线程的一些资源了,此时有可能有一些阻塞的线程,所以要把阻塞的线程全部唤醒,让他们知道,“消息循环已经退出了,你别再等了”。防止这些线程,无限期地阻塞下去。
一图以蔽之
退出之后,如果再继续放入消息,会发生什么呢?
我们知道,Handler的放入消息操作,最终会调用到MessageQueue的enqueueMessage
方法
在方法里面,会判断,mQuitting
是否为true,如果为true,则直接回收这个消息,然后返回false
所以这时候再放入消息,是不会被取出的,而是放一个回收一个。
退出之后,Looper的loop方法会发生什么呢?
我们都知道Looper的loop
是进行消息循环的方法。当退出的时候,MessageQueue的next
方法会返回null
此时,loopOnce
方法会返回false
然后,在loop
里面,当发现loopOnce
返回false时,直接return
从而退出消息循环
5. HandlerThread是什么
看类的定义
我们可以发现,HandlerThread是一个线程,并且此线程具有消息循环的能力。也就是说,它是一个封装了Handler机制的线程。
在run
方法里,有一个注意点,就是,给mLooper
赋值后,调用了notifyAll
方法进行唤醒。那么,是在哪里进行的阻塞呢?
啊哈!就是这里,当调用getLooper
方法,获取Looper的时候,如果发现mLooper
为null,则阻塞,直到它被成功赋值,才会唤醒阻塞在这的线程。
6. IntentService是什么
直接从类注释中找答案,最简单也最准确!
下面看下这个类的具体内容
@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);
}
}
public IntentService(String name) {
super();
mName = name;
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
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);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
我们通过类注释,和类内容,可以总结出
它是个Service,并且内部维护了一个HandlerThread。它能够在子线程处理任务,并且只有一个子线程,同时只能处理一个子任务,并且当所有任务处理完时,自动关闭。
所以,它是一个可以在子线程执行耗时任务,同时只能执行一个任务,并且在所有任务执行完后,可以自动停止的Service。
7. Handler内存泄漏
什么是内存泄漏?
内存泄漏的根本原因,是这样的:一个长生命周期的对象,持有着一个短生命周期的对象的引用,同时此长生命周期的对象又被其他对象引用着,向上能够追溯到GC Root,从而导致这个短生命周期的对象无法在合适时机回收。
Handler发生内存泄漏的原因?
那么在Handler这里,很常见的一种内存泄漏的场景,是这样的:
Handler(长生命周期对象)持有了Activity(短生命周期的对象)的引用,同时Handler向上能够被主线程(GC Root)引用着,导致Activity无法在合适时机进行回收。
那么这个引用链,是这样的:
主线程-ThreadLocal-Looper-MessageQueue-Message-Handler-Activity
所以导致有可能发生内存泄漏。
如何解决Handler的内存泄漏问题?
解决方案有三个:
(1)不要用非静态内部类来使用Handler,不然Handler就会持有Activity的引用。
可以用静态内部类或者独立的外部类的方式,创建Handler,这样创建的Handler就不会自带Activity的引用了
(2)如果非要持有Activity引用,那么可以用弱引用包裹Activity
因为弱引用在垃圾回收时是一定会被回收的,所以可以解决内存泄漏的问题
(3)在短生命周期对象(比如Activity)结束时,释放Handler。
这种方式虽然可以解决,但是不太推荐,因为难以维护,容易忘记。如下:
@Override
protected void onDestroy() {
if(mHanlder != null){
mHandler.removeCallbacksAndMessages(null)
}
super.onDestroy();
}
8. 为什么死循环不会导致ANR?
来了来了,非常经典的问题!!!
为什么消息循环是死循环?
死循环是为了能够保证线程能持续运行下去,而不是执行完就退出了。
为什么这个死循环没有导致ANR?
这里的死循环并不满足ANR的条件。Android的ANR机制,它会检测主线程是否在一定时间内无法响应用户输入。而在消息循环这里,用户在屏幕上操作后,都会通过立即发送一个消息或者其他方式,响应用户的输入。所以这里并不满足Android认定的符合ANR的标准。所以不会ANR。
所以,这是两个维度,死循环只是为了能够让程序一直运行下去。和ANR没有关系。
所以真正触发ANR的并非Looper的死循环,而是某一次消息执行时阻塞了很久,即run
方法或者handleMessage
方法停滞了很久,比如在run
方法或者handleMessage
方法里面有一个死循环,导致主线程无法在一定时间内响应用户输入。
死循环,不会很耗费资源吗?
在这里也不会很消耗资源,因为在空闲时间会进行阻塞,在需要的时候会进行唤醒。就算有阻塞,用户做了什么操作,也是可以立刻向线程的MessageQueue发送消息,并唤醒线程的。
总结
以上,我就把关于Handler机制的核心知识全部介绍完了,此文花费了我大量精力,如果能对你有所帮助,那是我的荣幸!同时,如果有什么问题,也欢迎在评论区讨论。
如果你还没有掌握Framework,现在想要在最短的时间里吃透它,可以参考一下《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中系统服务的注册过程
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注鸿蒙)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
要在最短的时间里吃透它,可以参考一下《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中系统服务的注册过程
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注鸿蒙)
[外链图片转存中…(img-XyZynV1F-1713620784691)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!