剖析Framework面试-笔记(二)

其他应用组件相关

Service的启动原理

在这里插入图片描述
获取AMS的binder对象并发起startService的调用。
在这里插入图片描述
其中ServiceRecord跟启动的Service是在AMS里面是对应关系的。
pendingStarts是之后调用startServiceCommon的方法。
在这里插入图片描述
r.app表示Service对应的进程,r.app.thread表示进程已经就绪。如果r.app不为null,代表当前service在当前进程已经启动就绪了,因为在service启动就绪后才会给r.app赋值。
根据上图区分几种情况:

  • service启动就绪并且进程启动就绪,调用sendServiceArgsLocked
  • 进程启动了,就绪,但是service并未启动, 调用realStartServiceLocked,这个就是真正启动service的
  • 进程未启动,调用 startProcessLocked启动进程,然后在启动service

回顾下应用的启动流程:
在这里插入图片描述
跟进AMS里面attachApplication的方法:
在这里插入图片描述
会处理所以的pending意图,并调用realStartServiceLocked方法:
在这里插入图片描述
scheduleCreateService是一个IPC调用,然后应用端会创建Service。
再跟进应用端,在ActivityThread.handleCreateService里面:
在这里插入图片描述
就是反射调用创建Service,并调用了onCreate()方法。
再跟进前面的sendServiceArgsLocked方法,看如何触发onStartCommand
在这里插入图片描述
最后又调用到主线程ActivityThread里面了,调用handleServiceArgs
在这里插入图片描述
总结:
在这里插入图片描述
在这里插入图片描述

Service的绑定原理

使用

在这里插入图片描述

原理

先看下整体流程:

在这里插入图片描述

看下bindService具体实现:
在这里插入图片描述
因为ServiceConnection不是一个binder对象,所以需要一个IServceConnection的binder对象来跨进程通信,同时IServceConnection也是持有ServiceConnection的引用的。最后发起AMS端的bindService调用:

先看下getServiceDispatcher的调用:
在这里插入图片描述
就是一种缓存机制,真正创建的是在ServiceDispatcjer的构造方法里面:
在这里插入图片描述
跟进RunConnection干了什么:
RunConnectionLoadedApk的内部类,实现了Runnable接口,run方法里面执行了doConnected方法:
在这里插入图片描述
其中mActiveConnections其实就是个binder缓存的map,然后service!=null表示已经绑定了,然后加载到缓存中。如果old!=null表示之前已经绑定过了。最后一种情况,old==null&&service!=null 表示第一次绑定上了,回调到onServiceConnected方法。

总结下getServiceDispatcher这部分:
在这里插入图片描述
需要注意的是IServiceConnection是跟Context、ServiceConnection有关的。

再看绑定流程bindServiceLocked方法:
在这里插入图片描述
其中bringUpServiceLocked是用来启动Service的,最后由ActivityThread来调用Service的onCreate方法启动Servcie了。
而后面的判断:s是ServiceRecord对象,如果s.app!=null 表示Service已经运行。b.intent.received表示当前应用程序进程已经接受到绑定Service时返回的Binder,这样应用程序进程就可以通过Binder来获取要绑定的Service的访问接口。如果b.intent.received返回的是false,表示应用程序进程的客户端没有发送过绑定Service请求,则会调用requestServiceBindingLocked方法。

接着往下看之前,了解一下相关对象:
在这里插入图片描述
上面有2个关键的函数:bringUpServiceLocked()requestServiceBindingLocked()
先看bringUpServiceLocked函数:
在这里插入图片描述
这里同Service原理部分一样。

再看requestServiceBindingLocked函数:
在这里插入图片描述
首先i表示IntentBindRecordi.requested表示是否发送给绑定Service的请求,i.app.size()>0表示所以用当前Intent绑定Service的应用进程个数大于0。所以这个if条件是成立的,执行scheduleBindService方法:
在这里插入图片描述
这里调用s.onBind(data,intent)方法后,Service处于绑定状态了。如果rebing为true则会调用onRebind方法了。这里接着走未绑定的分支,在调用了onBind方法后,再调用AMS的publishService方法:
在这里插入图片描述
其中c.conn就是IServiceConnection,它是ServiceConnection的本地代理。然后又走到了doConnected方法了。
这时候再看看看 onRebind什么时候调用:
onRebind调用:
在这里插入图片描述
这里的条件有:进程是启动的、已经接受了binger对象、intent的第一个绑定的进程仅为一个、intent.doRebind是开启的。才会执行onRebind调用。其中doRebind调用在:
在这里插入图片描述
在这里插入图片描述
其实意思就是要先执行onUnbind才会开启。

bindService和startService区别:
bindService不会触发onStartServiceCommand(),因为binderService没有把ServiceRecord加到pendingStarts队列里 (pendingStarts是需要启动的Service列表) --> startServiceLocked函数里才会加到pendingStarts队列里

动态广播的注册与收发原理

动态广播的注册原理

先看registerReceiver方法:
在这里插入图片描述
最终会调向registerReceiverInternal方法:
其中IIntentReceiver是个Binder对象,这里有2个关键的方法:getReceiverDispatcherAMS.registerReceiver
先看getReceiverDispatcher方法:
在这里插入图片描述
这里可以看出,这里是跟Service的设计很像的。由ContextBroadcastReceiver两变量对应不同的IIntentReciver
接着看ReciverDispatcher的构造函数:
在这里插入图片描述
最后会形成以下引用链:
在这里插入图片描述
就可以达到IItentReceiver的IPC的通信了。

再回到AMS.registerReceiver方法:
在这里插入图片描述
其中mRegisteredReceivers是一个map对象:Map<IBinder,ReceiverLIst>,一个IIntentReceiver可能对应多个IntentReceive(一个Activity中,用多个IntentFilter注册同一个BroadcastReceiver)。addFilter(BroadcastFilter)是将BroadcastFilter添加到IntentResolver类型的mReceiverResolver中,这样AMS接到广播时就能从mReceiverResolver中找到对应的广播接受者了,从而达到了注册广播的目的。

广播的发送原理

在这里插入图片描述
对于普通的动态广播:系统端并行分发,到了应用端变成串行分发:
在这里插入图片描述
然后还做了很多其他事情:广播的校验、静态广播与动态广播列表的合并、有序无序广播区分。最终走到广播的接受部分。

广播的接受原理

在这里插入图片描述
其中Args是一个Runnable对象,ReceiveDispatcher的内部类。发送到主线程处理了。
在这里插入图片描述
其中执行了onReceive的回调。

总体回顾:
在这里插入图片描述

静态广播的注册与收发原理

广播的注册

静态广播的注册是要在清单文件中声明的。
所以静态广播会在系统启动后,ServerService会拉起PackageManagerService去扫描已安装app的清单文件。会调用PackageManagerService.scanPackageLI方法,然后最后调用到commitPackageSettings方法,部分代码如下:

 for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.receivers.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName);
                mReceivers.addActivity(a, "receiver");
                if (chatty) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(' ');
                    }
                    r.append(a.info.name);
                }
            }

广播的发送

在这里插入图片描述
跟进boradcastIntentLocked方法:
在这里插入图片描述
其中collectReceiverComponents用来寻找静态广播,mReceiverResolver.queryIntent用来寻找动态广播。
在这里插入图片描述
其中queue.scheduleBroadcastsLocked()来处理串行分发,然后会使用BroadcastHandle发送BROADCAST_INTENT_MSG消息,并在handleMessage里处理,执行了processNextBroadcast方法,嗯,代码非常长:
在这里插入图片描述
先看超时部分代码:
在这里插入图片描述
然后看broadcastTimeoutLocked方法:
在这里插入图片描述
接着往下执行,如果并未超时,此时就idle状态,就开始执行下一个:
在这里插入图片描述

继续:
在这里插入图片描述
这里需要理解一点,startProcessLocked这个现在可能没有那么好使了,流氓应用喜欢利用静态广播自启。

广播的接受

在这里插入图片描述
这里deliverToRegisteredReceiverLocked方法是处理动态广播的分发, 记住这里的BroadcastRecord状态切换成CALL_DONE_RECEIVE。然后调用到主线程的scheduleRegisteredReceiver方法。会执行performReceive方法:
在这里插入图片描述
主要干了两件事,调用广播的回调方法onReceive(),然后通过IPC调用AMS的finishReceiver()方法,通知AMS当前广播已经处理完了。因为在应用端,广播的分发是串行的。
在这里插入图片描述
其中finishReceiverLocked是来决定是否继续串行分发广播。还记得前面动态BroadcastRecord状态切换成CALL_DONE_RECEIVE了,这里会返回true了,然后执行下一个。
再看静态广播分发:
在这里插入图片描述
这里调用的是ActivitThread.scheduleReceiver方法,创建了一个消息,并发送到主线程处理,会执行到handleReceiver方法:
在这里插入图片描述
这里的getReceiverRestrictedContext返回的ReceiverRestrictedContext是一个以Application为mBase的ContextWrapper。其中静态广播的BroadcastRecord状态为APP_RECEIVE,所以调用finish()方法后可以继续分发。
这里需要注意的是,静态广播存在优先级,优先级高的广播有权提前结束广播,调用abortBroadcast方法来终止。

Provider的启动原理

这里没啥好讲的,一般用的少。最近的使用就是 app-start 框架 有用到,通过xml注册Provider来自动初始化第三方库。

UI体系相关

android的UI刷新机制

双缓存机制

在屏幕刷新中,Android系统引入了双缓冲机制。屏幕会以固定的频率从Frame Buffer读取数据,CPU每次提交数据给GPU后,GPU会向Back Buffer中缓存数据,Frame Buffer则会以每隔16.6ms与Back Buffer交换数据,然后交给屏幕绘制。
在这里插入图片描述

绘制流程

从常见入口 requestLayout() 开始
requestLayout() --> scheduleTraverals() --> Choreographer(创建是利用了ThreadLocal,线程独有)–>doTraversal()
注意点:一次vsync周期,只会调度一次Choreographer的绘制。

应用端是什么时候开始绘制的? 应用端绘制是独立的,如果vsync来临,当前绘制帧卡在来临之后,就会跳过当前的绘制帧,当前帧是不变化的,即使当前绘制操作优化的很好,频率过高也会给用户感觉丢帧。
优化思路:在vsync信号来的时候才进行绘制,并且绘制过程控制在16ms内,那么应用就会非常流畅
实现:Choreographer(舞蹈指导),把绘制操作封装成一个runnable丢给Choreographer,下一个vsync信号来的时候,就开始处理消息,真正的开始重绘(相当于绘制节奏,交由Choreographer处理了)

Choreographer工作原理:

  1. 应用层的View调用requestLayout方法,想要重绘。
  2. Choreographer.postCallback,其实是new了一个runnable,丢到Choreographer的消息队列里
  3. Choreographer没有马上处理消息,调用requestNextVsync函数,向SurfaceFlinger请求下一个vsync信号
  4. SurfaceFlinger在下一个vsync信号来的时候,通过postSyncEvent函数通知Choreographer
  5. Choreographer收到通知以后,就会处理消息队列里的消息,之前的requestLayout对应的就算执行里面的performTraversal函数,真正执行绘制。
  6. 利用 SyncBarrier:屏障,把这个屏障插到消息队列里,后面的普通消息是不能处理的。但对异步消息是没有影响的,为的就是让一些紧急处理的消息先执行。
  7. 处理绘制消息的对象:FrameDisplayEventReceiver(利用handler不是为了切线程,而是按时间戳发消息) --> doFrame()。
  8. doFrame()会计算当前时间与时间戳的间隔,间隔越大表示这一帧处理的时间越久,如果间隔超过一个周期,就会去计算跳过了多少帧,并打印出一个日志,这个日志我想很多人可能都见过:
  Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");

交互是通过管道机制,BitTube,一对Socket描述符(mSendFd和mReceiverFd),读信号和写信号能相互唤醒

  1. doFrame()最终mCallbackQueue中取出消息并按照时间顺序调用mTraversalRunnable的run()方法。
  2. 执行run方法:
  final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

在这里插入图片描述

补充问题

  • 丢帧一般是什么原因引起的?
    主线程有耗时操作,耽误了view的绘制
  • Android刷新频率60帧/秒,是每隔16ms调用onDraw绘制一次?
    这里的刷新频率是指vsync的频率,并不是每调一次绘制一次。需要应用端主动发起重绘,才会向SurfaceFlinger请求接收vsync信号,在下次来vsync信号的时候才会真正的去绘制。
  • onDraw完之后屏幕会马上刷新么?
    onDraw完之后不回立即刷新的,而是等vsync信号来了才会刷新,才会交换缓存。
  • 如果界面没有重绘,还会每隔16ms刷新屏幕么?
    没有重绘,屏幕还是会刷新,只是画面数据一直用的是旧的。
  • 如果在屏幕快要刷新的时候才去绘制会丢帧么?
    如果在屏幕快要刷新的时候才去onDraw绘制会丢帧吗?
    不会,View的重绘不会马上执行,需要等待下一次vsync信号。

surface跨进程原理

surface实现了Parcelable接口,就能表示它能跨进程通信。注意实现为writeToParcelreadFormParcel
Activity的Surface是怎么跨进程传递的?
在这里插入图片描述
跟进查看AMS端处理逻辑:
在这里插入图片描述
这里outSurface会从SurfaceControl里面拷贝一些内容:
在这里插入图片描述
SurfaceControl的native对象与Surface的Native对象进行绑定。
在这里插入图片描述
这里就是把SurfaceControl的GraphicBufferProducer(图像缓存生产者)传入创建的native层Surface对象,然后native层对象的Surface的指针会保存在传入进来outSurface里面。
总结:
Activity在第一次绘制就会去申请Surface,在调用relayoutWindow的时候申请,mWindowSession提供IPC通信,向WMS传了一个空壳的Surface,WMS从native层获取SurfaceControl,里面有个GraphicBufferProducer,可从里面拷贝buffer到Surface,同时传进来的空壳的Surface会保存native的Surface指针。

注意点:

  • surface本质是GraphicBufferProducer,而不是buffer
  • surface跨进程传递,本质就是GraphicBufferProducer的传递,本身也是binder对象,传输非常效率

问题点:

  • 怎么理解surface,它是一块buffer吗?
    不是buffer,是个壳子,里面包含一个能生产buffer对象的GraphicBufferProducer(GBP)
  • 如果不是,suface是怎么跨进程传递的?
    传递GraphicBufferProducer对象
  • Activity的surface在系统中创建后,是怎么跨进程传回应用的?
    系统创建的是SurfaceControl对象,里面有GBP,可以创建Surface,跨进程返回应用

surface的绘制原理

从performTraversals绘制开始:
在这里插入图片描述
默认采用软件绘制,执行drawSoftware方法:
在这里插入图片描述
lockCanvas方法,调用了native方法,mNativeObjec就是Surface在native层对象对应的指针,mCanvas是Surface里面的对象,里面有一个sk类型bitmap作为mCanvas的缓冲区,再调用完nativeLockCanvas方法后就会被赋予值,并返回mCanvas。具体如下:
在这里插入图片描述
这里的关键就是通过native层的surface对象调用lock方法获取一块buffer,之后SKBitmap调用setPixels设置其内存地址为bufeer的,然后canvas设置bitmap。
重点查看surface怎么获取一块buffer,跟进lock函数:

在这里插入图片描述
先调用dequeueBuffer去申请了一块buffer,然后转化成backBuffer,lockAsync这个函数就是把buffer的handle中的地址传到vaddr中。mLockedBuffer是Surface中表示backBuffer的变量。
查看dequeueBuffer如何获取buffer:
在这里插入图片描述
大意就是从远端的BufferQueue获取,返回一个result,并将下标赋值给buf。然后开始检测双端是否要同步,如果result带有BUFFER_NEEDS_REALLOCATION标签、或者本地gbuf为0,就需要调用requestBuffer刷新一下。
然后查看unlockCanvasAndPost来提交:
在这里插入图片描述
调用native层的方法,然后释放资源。
在这里插入图片描述
给nativeCanvas设置了一个空的SKBitmap,然后调用了surface的unlockAndPost方法:
在这里插入图片描述
首先mLockedBuffer解除锁定,然后调用queueBuffer方法,重新提交BufferQueue,给定buffer一个index下标,通过GBP提交给远端。然后mLockedBuffer就赋值给前台buffer mPostedBuffer,再自身重制。
总结:
在这里插入图片描述

  • 应用绘制首先要申请Surface。
  • Surface需要一块Buffer。
  • 调用dequeueBuffer方法得到一块Buffer。
  • 要获取buffer,得在SurfaceFlinger里创建一个BufferQueue --> 一个Surface对应一个BufferQueue。
  • 这个BufferQueue有两端(producer和consumer),producer端需要跨进程传给应用,交由Surface保管。
  • Surface需要绘制的时候,通过producer端向BufferQueue发起binder调用,申请一块buffer。
  • 这个buffer作为Canvas的Bitmap缓冲区(和我们用的Bitmap不同,是底层Skia绘制用的),绘制操作完成把这个buffer返回给BufferQueue。
  • BufferQueue通知consumer端,回调它的onFrameAvaliable,表示又有一帧数据绘制完毕了。 consumer可以在SurfaceFlinger进程里,也可以传给其他应用进程,就是用来消耗这一帧数据的。

相关问题:

  • surface绘制的buffer是怎么来的?
    通过GBP向BufferQueue申请
  • buffer绘制完了是怎么提交的
    通过GBP向BufferQueue提交

VSync信号机制

VSync信号的生成
硬件生成(HWComposer)/软件生成(VSyncThread),但是都是通过统一的接口回调到上层DispSyncThread。
分发机制:

硬件生成(HWComposer)/软件生成(VSyncThread)的信号通过接口回调给工作线程DispSyncThread,把它分发一分为二,分成两路进行分发(app-EventThread/sf—EventThread),分别通知app去绘制UI和通知sf去对应用生成对图像进行合成渲染。同时两者会有一定的错峰偏移,防止抢占CPU。
分发流程:
在这里插入图片描述

  1. VSync信号会唤醒EventThread线程
  2. 唤醒之后,把vsync信号通过注册的Connection分发出去
  3. 每个Connection会有两个描述符(mSenderFd,mReceiverFd),发送是往mSenderFd里写数据,对应的SF/App会监听EventThread的写事件,然后从mReceiverFd获取数据。

线程通信相关

线程的消息队列创建原理

先回忆下Handle、Looper、MessageQueue和线程的关系:
在这里插入图片描述

流程

在这里插入图片描述

消息队列创建其实就是MessageQueue的创建,Handle机制就不需要在细说了。

  1. 在ActivityThread的main方法,调用prepareMainLooper方法
  2. 再调用prepare(false)方法,其参数quitAllowed表示当前Looper是否能退出,主线程是false,不能退出的
  3. 创建Looper,ThreadLocal与Looper进行绑定操作
  4. Looper构造里面创建java层的MessageQueue
  5. MessageQueue构造里调用nativeInit()方法
 MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
  1. 查看native层方法nativeInit:
///frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    //转换为jni类型,将内存地址返回
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

创建native层的NativeMessageQueue

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

调用Looper::getForThread()从获取当前环境变量的looper,没有则创建一个native层的Looper

sp<Looper> Looper::getForThread() {
    int result = pthread_once(& gTLSOnce, initTLSKey);
    LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");

    return (Looper*)pthread_getspecific(gTLSKey);
}

getForThread获取当前线程额TLS,继续看Looper的构造:

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
        
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    AutoMutex _l(mLock);
    //重建epoll事件
    rebuildEpollLocked();
}

先理解 eventfd,eventfd是Linux 2.6提供的一种系统调用,它可以用来实现事件通知。eventfd包含一个由内核维护的64位无符号整型计数器,创建eventfd时会返回一个文件描述符,进程可以通过对这个文件描述符进行read/write来读取/改变计数器的值,从而实现进程间通信。
跟进rebuildEpollLocked方法:

void Looper::rebuildEpollLocked() {
//分配新的epoll实例并注册wake管道。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    //监听可读事件(EPOLLIN)
    eventItem.events = EPOLLIN;
    //监听mWakeEventFd的读写事件
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    ......
}

创建一个epoll实例,并添加一个mWakeEventFd的可读监听事件。

native层

怎么监听的了?在MessageQueue里插入一条新消息后,会调用enqueueMessage方法,从而调用到native的wake方法:
在这里插入图片描述
在wake()方法中会向mWakeEventFd写入数据。
没有消息时,会调用nativePollOnce方法进入阻塞,再查看nativepollOnce方法,实现在native层Looper的pollOnce方法:
,而pollOnce会监听这个mWakeEventFd是否可读。查看pollnner方法:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
		
        if (result != 0) {
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}

pollOnce也是个死循环,核心方法pollInner,会返回一个result值,当result不等于0时,就会跳出循环,返回到java层。查看pollnner方法:
第一部分:
在这里插入图片描述
epoll_wait是重点方法,这里会得到一段时间内(结合消息计算得来的)收到的事件个数,这里对于queue来说就是空闲(阻塞)状态。也不会下面的第二部分代码的执行了,会这里休眠。
遍历fd描述符,如果等于mWakeEventFd并且当前event是写事件,则会调用awoken()方法。其作用不断的读取管道数据,直到清空管道。
接着第二部分:处理native层消息,并返回result给上层,判断上层是否继续堵塞。

//system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) {    
    //...
    //事件集合(eventItems),EPOLL_MAX_EVENTS为最大事件数量,它的值为16
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //1、等待事件发生或者超时(timeoutMillis),如果有事件发生,就从管道中读取事件放入事件集合(eventItems)返回,如果没有事件发生,进入休眠等待,如果timeoutMillis时间后还没有被唤醒,就会返回
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
    
    //获取锁
    mLock.lock();
    
    //...省略的逻辑是:如果eventCount <= 0 都会直接跳转到Done:;标记的代码段

    //遍历事件集合(eventItems),检测哪一个文件描述符发生了IO事件
    for (int i = 0; i < eventCount; i++) {
        //取出文件描述符
        int fd = eventItems[i].data.fd;
        //取出事件类型
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {//如果文件描述符为mWakeEventFd
            if (epollEvents & EPOLLIN) {//并且事件类型为EPOLLIN(可读事件)
                //这说明当前线程关联的管道的另外一端写入了新数据
                //调用awoken方法不断的读取管道数据,直到清空管道
                //第一部分
                awoken();
            } else{...}
          
        } else {//如果是其他文件描述符,就进行它们自己的处理逻辑
           //...
        }
    }
    
    //第二部分:下面是处理Native的Message
    Done:;
    mNextMessageUptime = LLONG_MAX;
    //mMessageEnvelopes是一个Vector集合,它代表着native中的消息队列
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //取出MessageEnvelope,MessageEnvelop有收件人Hanlder和消息内容Message
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        //判断消息的执行时间
        if (messageEnvelope.uptime <= now) {//消息到达执行时间
            {
                //获取native层的Handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                //获取native层的消息
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                //释放锁
                mLock.unlock();
                //通过MessageHandler的handleMessage方法处理native层的消息
                handler->handleMessage(message);
            }
            mLock.lock();
            mSendingMessage = false;
            //result等于POLL_CALLBACK,表示某个监听事件被触发
            result = POLL_CALLBACK;
        } else {//消息还没到执行时间
            mNextMessageUptime = messageEnvelope.uptime;
            //跳出循环,进入下一次轮询
            break;
        }
    }
    //释放锁
    mLock.unlock();
    //...
}

如果当前没有消息处理,则还是会处于堵塞状态,让出CPU占用进入休眠等待。
总结:
在这里插入图片描述

补充问题

  • 可以在子线程创建handler么?
    可以,先调用Looper.prepare()即可,要注意使用 Looper.loop()和quit()方法。
  • 主线程的Looper和子线程的Looper有什么区别?
    子线程的Looper可以退出。
  • Looper和MessageQueue有什么关系?
    一个线程有一个Looper,对应一个MessageQueue,一个looper可以对应多个Handler,每个Handler都可以往MessageQueue插入消息,MessageQueue可以根据target分到不同的Handler。
  • MessageQueue是怎么创建的?
    Java层的MessageQueue在创建的时候就会调用一个native函数去创建Native层的MessageQueue。

android线程间消息传递机制

分发消息

先看handle如何分发消息:
在这里插入图片描述
这个callback就是调用post(Runnable)方法时设置的:

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

如果没有全局Callback就会执行handleMessage(msg)方法。

发送消息


在这里插入图片描述
其中nativeWake唤醒处于堵塞状态的MessagQueue的next()方法。

线程间传递

在这里插入图片描述

handler的消息延时实现

要分析两个位置:消息入队时候跟进延迟插入链表、取消息时是否有可用的消息(可能当前时间节点,延迟消息还没到)。

消息入队

通常我们通过sendMessageDelayed发送,或者postDelayed发送消息的。

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

会调用sendMessageAtTime方法,第二个参数为 当前系统开机后时间+延迟时间 的值。

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

然后调用MessageQueue的enqueueMessage方法:

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

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

首先要了解Message数据结构是一个时间纬度的单链表。
分析:

  1. p == null || when == 0 || when < p.when
    如果p == null 表示当前链表为空,直接赋值设置头节点;如果when == 0表示这个消息是要插入头部的,这里是调用Handler.sendMessageAtFrontOfQueue方法的传的0,然后将传入msg设置为头节点。 when < p.when表示传入msg的执行时间比当前msg更提前,所以设置为头节点。
 public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        MessageQueue queue = mQueue;
        return enqueueMessage(queue, msg, 0);
    }
  1. 处理else语句,启动一个死循环,插入节点。
    传入的节点when 小于 next节点的when,则说明找到插入位置,或者next节点为空,跳出循环执行msg.next = p; prev.next = msg;插入操作。否则next继续指向下一个节点msg。
  2. 判断是否有消息入列,有则执行nativeWake函数。

取消息

MessageQueue.next()方法:

 Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
        //第一次进来 是未堵塞的,如果当前消息队列没消息就会堵塞
            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;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            }
    }

分析:

  1. nextPollTimeoutMillis第一次for循环先是未堵塞的,如果当前无消息,则会设置为非0,下次for循环就会堵塞。
  2. 有消息,如果当前时间 小于 当前消息的when,即表示当前消息队列所有消息还没到达执行时间(头节点执行是最先的),则计算超时时间。
  3. 有消息,并且头部消息延迟时间已经到了,返回当前msg,并移除,将mMessages赋予下个消息。
  4. 然后进入下次for循环。

上面还有个重点在native层,就是超时时间nextPollTimeoutMillis,记得前面提到过的 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); 中,timeoutMillis就是nextPollTimeoutMillis,这个epoll_wait方法,如果再没有新的消息唤醒情况下,会在超时时间之后自动唤醒。

idleHandler的原理

介绍与使用

介绍
/**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

回调接口,用于发现线程何时将阻塞等待更多消息。而queueIdle()回调表示:当消息队列中的消息用完时调用,现在将等待更多。返回true可使您的空闲处理程序保持活动状态,返回false 使其被删除。如果队列中仍有消息待定,但是所有消息都计划在当前时间之后发送,则可以继续调用此方法。
官方解释已经很清楚了,通俗点讲就是 消息队列空闲时候回调。
空闲分成无消息和消息还没到触发条件两种情况。

使用
  Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
               	...
               	//处理事情
               	...
                return false;
            }
        });

其中返回值,true表示下次空闲还会回调这里;false表示仅执行一次回调。

原理

 public void addIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

这里就是IdleHandler列表添加一个IdleHandler。在那用的了?
还是看Message.next()方法:

   Message next() {
     
        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;
                }

				//从这里开始:就是处理Idle部分逻辑了
                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;
        }
    }

  1. if (pendingIdleHandlerCount < 0... )开始看起,上面是处理消息分发的,看这个判断逻辑就是暂且无消息或者消息触发条件未到,处于空闲状态了。
  2. 在执行pendingIdleHandlerCount = mIdleHandlers.size();赋值,这个mIdleHandlers就是我们添加addIdleHandler的list列表。
  3. 如果pendingIdleHandlerCount还是默认值或者size为0,就跳出。
  4. 大于0,继续处理IdleHandler逻辑。初始化临时IdleHandler[]的数组,并赋值为mIdleHandlers的值。
  5. 开启IdleHandler[]数组的遍历,取出IdleHandler实例,调用queueIdle()回调,如果返回true就表示下次还会回调,返回false就从mIdleHandlers的列表中移除,下次空闲不再回调。

应用

启动优化

启动优化可以使用IdleHandler来利用特性,避免占用页面绘制。这样会在页面绘制完后,空闲下来再处理一些事情,当然这里queueIdle()要返回false,只执行一次。

批量任务

存在一些任务密集、只关注最终结果的场景。

主线程进入loop循环为啥没ANR?

从几个方面回答:

  • ANR是应用没有在规定的时间内完成AMS指定的任务,跟Looper的for循环没有必然的联系。
  • AMS请求调到应用端binder线程,再丢消息去唤醒主线程处理,理解mWakeEventFd,nativePollOnce了(epoll_waite)。
  • ANR不是因为主线程loop循环,而是因为主线程中有耗时任务。

消息屏障

特点

消息是可以分成三类:普通消息、消息屏障、异步消息
一般我们使用的都是普通消息,而异步消息跟普通消息相比多了异步标志位setAsynchoronous。

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

消息屏障特点:

  • 没有target(Handler) --> 通过有没有handler判断是否是消息屏障,普通消息需要handler发送的。
  • 带有时间戳,只会影响它后面的消息
  • 可以插多个消息屏障
  • 插入消息屏障没有唤醒线程
  • 插入消息屏障后会返回token(屏障序列号,累加),移除消息屏障的时候用,移除的时候可能要唤醒线程(唤醒条件:线程是因为消息屏障block住的,且该屏障后面没有跟着一个屏障。
  • 私有的,非必要不要使用。

各种情况

Message.next()里,消息屏障只会block住普通消息,不会block住异步消息。

  • 如果第一条消息就是屏障,就往后遍历,看看是否有异步消息。
  • 如果没有异步消息,就无限休眠,等待被别人唤醒。
  • 如果有异步消息,就看离这个消息触发还有多久,设置一个超时,继续休眠。

有消息屏障的时候插入消息:

  • 插入普通消息:如果插入到队列头,当前线程数休眠的,则会唤醒线程。
  • 插入异步消息,如果当前消息是最早的一条异步消息,且队列头是屏障,且线程是休眠的,唤醒线程(根据时间是否到了设置)

相关

这个跟UI的绘制有关,看下图:

在这里插入图片描述
Choreographer里的scheduleTraversals(),把屏障后面的普通消息block住,让界面绘制的异步消息优先执行,再执行完后移除消息屏障。
消息屏障会block住普通消息,给异步消息开通个绿色通道,让异步消息优先执行(比如界面绘制、输入输出事件分发等等比较紧急的消息)

进程通信相关

framework的跨进程方式

管道

  • 半双工的、单向的
    半双工是指数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
    管道数据流只能一个方向流、要么读要么写。需要双方通信时,需要建立起两个管道;
    Linux提供了pipe(fds)可以生成一堆描述符:读、写。

  • 一般在父子进程之间或者兄弟进程之间使用

父进程向子进程写数据情景:
在这里插入图片描述

socket通信

  • 全双工的,既可读又可写
    通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指A→B的同时B→A,是瞬时同步的。
  • 两个进程之间无需存在亲缘关系

应用:
在Zygote进程的启动类ZygoteInit.main方法中:通过socket接受AMS进程的请求,来启动应用进程。

public static void main(String argv[]) {
    try {
    	...
    	registerZygoteSocket(socketName);
    	... 
        runSelectLoop(abiList);
        ....
    } catch (MethodAndArgsCaller caller) {
        caller.run();
    } catch (RuntimeException ex) {
        closeServerSocket();
        throw ex;
    }
}

通过registerZygoteSocket(socketName)创建了本地socket,进入runSelectLoop循环来检测socket是否有新过来的链接或者数据。

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

    // 将 zygote socket 的 fd加入数组
    fds.add(sServerSocket.getFileDescriptor());
    peers.add(null);

    // 这里使用到了linux的poll机制
    while (true) {
        StructPollfd[] pollFds = new StructPollfd[fds.size()];
        for (int i = 0; i < pollFds.length; ++i) {
            pollFds[i] = new StructPollfd();
            pollFds[i].fd = fds.get(i);
            pollFds[i].events = (short) POLLIN;
        }
        try {
        	//poll是来检测是否有事件发生的
            Os.poll(pollFds, -1);
        } catch (ErrnoException ex) {
            throw new RuntimeException("poll failed", ex);
        }
        for (int i = pollFds.length - 1; i >= 0; --i) {
          //采用I/O多路复用机制,当客户端发出连接请求或者数据处理请求时,跳过continue,执行后面的代码
            if ((pollFds[i].revents & POLLIN) == 0) {
                continue;
            }
            if (i == 0) {
                //表示zygote socket中有连接到来,那么就要创建客户端连接
                // 实际上就是执行socket accept操作
                // 然后把创建的这个连接也加入到监听数组中,索引肯定大于0了
                // 那么一旦这个连接中有数据来了,i肯定大于0
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                //调用ZygoteConnection.runOnce()处理客户端数据事务
                boolean done = peers.get(i).runOnce();
                // 处理完之后,断开连接,并且不在监听这个连接
                if (done) {
                    peers.remove(i);
                    fds.remove(i);
                }
            }
        }
    }
}

共享内存

  • 很快,不需要多次拷贝
  • 进程之间无需存在亲缘关系

应用:
对于Android主要使用的方式是匿名共享内存Ashmem(Anonymous Shared Memory),跟原生的不太一样,比如它在自己的驱动中添加了互斥锁,另外通过fd的传递来实现共享内存的传递。MemoryFile是Android为匿名共享内存而封装的一个对象,也是进程间大数据传递的一个手段。
在这里插入图片描述

信号

  • 单向的,发出去之后不管别人怎么处理
  • 只能带个信号,不能带别的参数
  • 知道进程pid就能发信号了,也可以一次给一群进程发信号

特点很像广播机制。
应用:
在这里插入图片描述

Binder的理解

Binder作用

binder 是 Android 中主要的跨进程通信方式,binder 驱动和 service manager 分别相当于网络协议中的路由器和 DNS,并基于 mmap 实现了 IPC 传输数据时只需一次拷贝。

特点

  • 性能
    基于 mmap 实现了 IPC 传输数据时只需一次拷贝。
  • 方便
    逻辑直接简单。
  • 安全
    binder声明信息只能在内核态添加的,而Socket方式的IP地址是开放的,管道方式拿到匿名管道的管道名称就可以往里面写数据。

binder通信架构

在这里插入图片描述
注意只有系统服务才能注册到ServiceManager,需要权限验证。

回顾启用binder机制:
打开binder驱动、内存映射、分配缓冲区、启动binder线程

完整的IPC通信流程

分层架构:
在这里插入图片描述
通信过程
在这里插入图片描述

binder对象跨进程原理

publishBinder里面,是通过writeStrongBinder方法,将callbac写入
在这里插入图片描述

onTransact方法里面,可以看出是通过Parcel来读写binder的
在这里插入图片描述

  • java层
    通过android.os.Parcel进行跨进程传输,通过writeStrongBinder写到Parcel里,目标进程通过readStrongBinder从Parcel中读出来

  • Native层
    binder以flat_binder_object形式存储在Parcel缓冲区里,Parcel有个数组专门保存其偏移,所以到目标进程,就可以根据这个偏移还原出flat_binder_object(所谓的一次拷贝)

  • 驱动层
    Parcel传到binder驱动里之后,这个驱动根据flat_binder_object里的binder对象创建一些数据结构,包括binder_node(实体对象)和传给其他进程的binder_ref(引用对象),一个binder实体对象可能会对应多个binder引用对象
    往上目标进程根据binder_ref的handle创建BpBinder(同一个进程直接返回)
    由BpBinder再往上到BinderProxy到业务层的Proxy。

binder的oneway机制

带oneway和不带oneway的接口(普通aidl接口)区别:是否需要等待回复
注意点:oneway的接口,函数默认是没有返回值的,带上编译会出错。oneway重点放在了Client端。
特点:

  • oneway是异步的,就算应用端处理非常耗时也不会阻塞系统服务
  • oneway是串形化的,系统服务是一个一个的把binder调用分发给应用端
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值