Android知识点总结(十):FrameWork内核解析

196 阅读27分钟

1、Android中多进程通信方式有哪些

  • Socket、Binder、共享内存。常用的时binder。当然还有管道,信号,信号量,消息队列。

进程隔离

  • 操作系统有虚拟内存和物理内存。前者真实存在,后者是内存管理技术,通过虚拟映射手段让每个应用进程认为它有连续可用内存。在使用了虚拟存储器的情况下,通过MMU完成虚拟到物理的转换
  • 虚拟内存有两块,用户控件和内存控件,两块隔离的。用户程序崩了,内核不影响。同样A app崩了 B app会没事。也避免相互操作。总的来说就是用户空间隔了,但内核空间没隔。

IPC通信

进程间用户空间要隔离 ,但内核空间被所有进程共享,所以绝大部分IPC机制就利用这个特点来来实现跨进程通信。

管道

UNIX古老的进程通信方式,在内核空间管理一个固定大小的缓冲区,一端用户空间进,一端用户空间出。 - 匿名,4k,半双工,只支持父子和兄弟进程间通信 还有个FIFO实名管道,支持双向同行,建立时给它个名字,任何进程都可以通过名字打开管道另外一端,同时多个的话就有点力不从心

信号

  • 主要用于通知接收进程某个事件的发生,原理时中断模拟,进程收到信号和收到中断请求一样
  • 信号时一种异步通信机制,不需要阻塞,进程可以通过sigaction注册接收信号,就执行响应函数
  • 在Android中,如果程序出现ANR问题会发出:SINGALQUIT信号,aoo可以注册此信号监听ANR,爱奇艺xcrash、umeng、matrix都实现了该方式。

信号量

  • 可以看成计数器,是个进程间或者同进程不同线程间的同步手段。
  • 信号量有初始值,每当进程使用信号量,就-1。当计数器减少到0就没资源了,其它进程访问就必须等待,当该进程执行完工作,就会执行V操作来对信号量+1;

共享内存

就是多个进程共享一块物理内存区域。Android中提供了匿名共享内存,基于Linux的共享内存,都是在临时文件系统上创建虚拟文件,再映射到不同进程。能让多进程操作同一块内存区域,除了物理内存限制,没有其他大小限制。相比于Linux的共享内存,它加了互斥锁。

消息队列

  • 消息队列是一个链表,放在内核中。有异步功能,信号量信息少,它大。管道只能承载无格式字节流,消息队列内存有格式的。
  • 但是更耗费内存,消息队列传送数据上限一般是16kb
  • Android中大量的场景都市消息队列来设计的,比如handler,就是消息队列

socket

socket原本是为网络通信设计的,后来UNIX给用了只用了进程拷贝到另外一个进程。Zygote就是通过 LocalSocket接收启动应用进程通知的

Binder

在Android中Binder更多用在system_server进程和上层App之间的IPC就用的binder。Intent、contentProvider、messager、Broadcast、AIDL都是Binder机制完成的跨进程通信。

总结

  • 管道:创建一个page 4KB大小的内存,缓存区域大小比较有限
  • 信号:不适合信息交换,更适合进程中断控制
  • 信号量:常作为锁机制,防止某进程正在访问资源时,其他资源也访问。主要作为同步手段
  • 共享内存:无需赋值,无缓冲区直接附加到进程虚拟地址空间,速度快,但是进程间的同步问题,操作系统没实现,必须利用同步工具解决。
  • 消息队列,消息赋值两次,额外CPU消耗,不适合频繁或者信息量大的通信
  • socket:作为更通用的接口,消息消息低;
  • Binder
    • 性能:只要拷贝1次。共享内存0次,socket管道,消息队列都要2次。
    • 安全:Binder有可靠的身份表示,只有由IPC机制本身在内核中添加,而不是用户添加。
    • 操作性:基于CS架构,操作简单,socket也是。共享内存操作复杂,易用性差。 所以吧,Binder最适合system_server进程与App上层IPC通信

2、描述一下Binder进制原理

  • Binder时Android提供的IPC框架。基于CS架构,效率高、本质也是调用系统底层的共享内存。

进程隔离

不同进程无法是接通信,内核空间是被不同进程共享。那么用户到内核到用户不就可以了?所以现在进程到进程的问题就是,用户和内核之间通信的问题。

进程划分

32位虚拟内存空间4g。高1GB给内核,低3GB给到各个进程也就是用户空间。至于通信主要两个函数.copy_from_user和copy_to_user

Linux下的传统给IPC原理

发送方把要发送的内容放到内存缓存区中,通过系统调用进入内核态,然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用copyfromuser函数将数据从用户空间的内核缓冲区。接收方进程在接收时在自己用户空间开辟一块内存缓冲区,然后调用copytouser将数据从内核缓冲区拷贝到接收进程的内存缓冲区。

image.png

  • 拷贝2次,性能低下
  • 由于接收进程不知道需要多大的空间存放传递过来的数据,所以尽可能大或者先调API来接收消息头获取消息体大小,不是浪费空间就是浪费事件。

Binder跨进程通信原理

  • 动态内核可加载模块&& 内存映射
  • 跨进程通信需要内核空间支持。但是binder不是linux内核一部分。有个linux的动态内核可加载模块。运行时被链接到内核作为内核的一部分运行。这样,Android通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块来实现通信。
  • 这个运行在内核空间负责各个用户进程通过binder实现通信的内核模块就叫Binder 驱动
内存映射
  • Binder通过mmap来实现内存映射。将用户空间的一块内存区域映射到内核空间,关系建立之后,修改,内核和用户空间都一起变。减少了拷贝次数
Binder IPC实现原理
  • Binder IPC基于内存映射mmap来实现的,但是mmap通常时在由物理介质的文件系统上的
  • 用户空间不能直接和物理设备通信,磁盘读取到用户空间需要2次拷贝(磁盘-》内核空间-》用户空间),而mmap则只要一次。将内核空间和用户空间建立映射,磁盘拷贝到内核就行
一次Binder
  • Binder驱动在内核空间创建一个数据接收缓冲区
  • 建立内核缓冲区,内核缓冲区和数据接收缓冲区映射建立,数据缓冲区 和接收进程用户空间地址映射建立。
  • 发送方通过copyFromuser将数据拷贝到内核缓冲区,因为映射,所以接收进程的用户空间。

image.png

image.png

3 为什么Anroid 要采用binder作为IPC机制?

  • 性能
    • 只要1次拷贝。共享内存0次,socket2次。
    • 速度上仅次于共享内存,优于Socket 消息队列,管道,信号,信号量
  • 可用性
    • BInder基于C/S架构,可用性高
    • 共享内存,需要同步机制,搞不好容易死锁
    • socket,效率低,开销大
  • 安全性
    • Binder安全高,为每个App分配不同UID用来鉴别进程身份。
      • 支持实名Binder,有支持匿名binder
    • 传统IPC不安全
      • 依赖上层协议,写UID/pid
      • 访问接入点开放,任何程序都可以与其建立连接

4、 Binder线程池的工作过程(未完成)

  • 每个进程都只有一个processState俩描述当前进程在binder通信时binder的状态。
  • 有一个默认的binder主线程 Pool Thread(在processState.cpp中)和底层Binder Driver进行通信,这个线程主体就是一个IPCThreadState对象。

image.png

  • Binder线程创建
    • java层 Process.start() - 》
      • 向Zygote进程发送出创建进程的socket消息-》
      • Zygote收到消息后调用Zygote.forkAndSpecialize来fork新进程-》
      • 新进程调用RuntimeInit.nativeZygoteInit方法,该方法经过jni映射,-》
      • 最终会调用到app_main.cpp中的onZygoteInit
    • onZygoteInit
      • ProcessState::self()
        • open打开/dev/binder驱动设备
        • mmap映射内核地址空间,将binder驱动的fd赋值给ProcessState对象中的变量mDriverFD
      • startThreadPool()是创建一个新的binder线程,不断进行talkWithDriver()。
    • PS.startThreadPool
      • 通过变量mThreadPoolStarted来保证每个应用进程只允许启动一个binder线程池
      • 并且本次创建的是binder主线程,其它都是Binder驱动创建的子线程
    • PS.spawnPooledThread
      • startThreadPool 内部执行了spawnPooledThread
      • 获取BInder线程名,makeBinderThreadName
      • PoolThread.run
    • makeBinderThreadName,获取线程名
    • PoolThread.run-》IPC.joinThreadPool
    • IPC.joinThreadPool
      • isMain=true。代表是主线程。 - BC_ENTER_LOOPER发送给binderDriver之后,代表的是Binder主线程,不会退出的线程
      • isMain=false。代表是非主线程。 - BC_REGISTER_LOOPER发送给binderDriver后,表示是由binder驱动创建的线程。
      • processPendingDerefs(//清除队列的引用)
      • getAndExecuteCommand(处理下一条指令)
        • executeCommand
      • talkWithDriver(//false代表bwr数据的read_buffer为空)
    • IPC.joinThreadPool –> IPC.getAndExecuteCommand() ->IPC.talkWithDriver() ,但talkWithDriver收到事务之后, 便进入IPC.executeCommand(), 接下来,从executeCommand说起
    • executeCommand
      • //创建新的binder线程
      • mProcess->spawnPooledThread(false);

5、AIDL的全称是什么,如何工作,能处理哪些类型的数据?

  • AIDL,Android interface definition lanuage.Android接口描述语言。编译可以封装aidl文件生成一段代码,生成的代码封装了binder,可以 堪称是binder的延伸。
  • AIDL的使用是指就是对Binder机制的封装,主要 将Binder封装成一个代理对象proxy,从客户角度看,就像是客户端直接调用了服务端代码

怎么使用

- 服务端:创建aidl接口-》创建一个类继承aidl接口名下的Stub,并实现方法,假设名为MyBInder->创建一个Service,在服务的onBInder中返回MyBinder对象。
- 客户端:创建aidl接口,和服务端一样,包名也要一样。绑定服务-》通过service的action来服务端的服务-》并实现ServiceConnection绑定监听-》将ServiceConnection返回的IBinder。通过AIDL文件同名.Stub.asInterface(IBinder),返回得到代理对象。代理对象调用方法。就会得到服务端实现的内容了。

客户端和服务段绑定成功之后,就可以通过AIDL的接口代理对象,像调本地方法一言给,调用服务端的方法了。要注意的是AIDL间传递的对象要实现Parcelable接口

  • AIDL绑定过程
    • 客户端调用bindService,发起绑定服务的请求,通过ServiceManager,拿到ActivityManagerService(AMS),通过AMS像服务端发起bindService。
    • 然后服务端接收到绑定请求,以Handler消息机制的方式,发送一个绑定服务的Message,然后在ActivityThread中处理这个绑定请求,调用onBind函数,并返回了对应的IBinder对象。这个返回IBinder对象的操作,基本就和绑定过程的通过ServiceManager\AMS类似了。

AIDL支持的数据类型

  • Java基本数据类型
  • 引用类型比如String和CharSequence
  • 实现Parcelabale接口的数据类型
  • List Map。内包含的只能是上述几种。

6、Android 中pid&uid的区别和联系

  • 一个pid代表一个进程,一个uid代表一个应用程序。所以存在一个UID存在多个PID。但一个PID只能有一个uid
  • 完全暴露:android:exported= true。 activity service contentProvider申明true时。标识类允许外界的数据访问。如果加了intentFilter属性,则默认true。当然也能强制false。
  • 权限提示暴露。 访问 activity service contentProvider时要有某个权限

image.png

  • 私有暴露
    • 需要sharedUserId+同一套签名,才能在统一沙箱中相互访问。

image.png

image.png

  • 不同的pid之间用私有暴露和权限暴露。不同的uid之间用完全暴露的方法。
  • 如果一个应用是系统应用。则不需要其他应用暴露,便可直接访问应用数据,

7、Handler怎么进行线程通信?原理是什么?

怎么做:

  • handler对象和looper绑定。调用handler.post(runnable)或者handler.dispatchMessage(msg)
  • 当然前后得做looper.prepare和looper.loop。不过主线程是默认给你做了,就不用

原理:

  • 其实就是在发送端线程创建一个handler对象与接收端线程的looper绑定
  • 因为绑定了,所以handler发送的msg回到接收端的messagequeue
  • 接收端looper转动去从messagequeue中取出消息,
  • 取出的消息传递给handler的dispatcherMessage方法 -- dispatchMessage()方法进一步处理消息,可能是通过Runnable对象或handleMessage。

8、ThreadLocal的原理/以及在Looper是如何应用的?

  • ThreadLocal,提供了线程局部变量的实现。每个线程都有自己独立的数据副本,而不需要共享数据。

ThreadLocal的原理

  • 举例说明吧,Thread就是每个人,threadlocalmap就是每个人衣服上的口袋。threadlocal是口袋的标签,比如左上口袋,右下口袋。那么每个人只要通过东西在坐上口袋里,然后去翻对应口袋就能找到。

在Looper是如何应用的

LOOPEr和线程是一对一,那么在looper.prepare的时候,回去通过threadllocal< looper >去找该线程的ThreadLocalmap中是否有该looper。没有就新建,如果有了,说明重复创建,我就报错。

9\Handler如果没有消息处理是阻塞的还是非阻塞的?

没有消息MessageQueue会执行nativepollonce阻塞,直到有消息来就通知。这个阻塞是异步阻塞,主要用到了epoll(IO复用模型)

10、handler.post(new Runnable)是如何执行的?

runnable被封装成为一个message,然后添加到messagequeue里

  • runnable首先被放到sendMessageDelayed(getPosterMessage(r),0)
  • getPosterMessage中Message.obtain. m.callback = r
  • sendMessageDelayed(msg,0)
  • 消息被执行的时候,先判断有没有callback.如果有就执行msg.callback.run

11、handler的callbck存在,但是返回true,消息会不会执行?

如果msg有callback会执行,如果不是则不会执行

12、Handler的sendMessage和postDelay的区别?

后者使用到了sendMessageDelay函数,有多了个延时操作而已,没啥多大区别

13、Looper.looper会不会阻塞主线程?

  • 主线程Looper.looper当然有可能阻塞主线程啦,但是不用担心
  • ActivityThread是App的入口,ActivityThread.attach就是运行在主线程looper.prepare和looper.looper当中。
  • 那么既然是looper,那么没有消息,那么messagequeue.next调用nativepollonce当然会阻塞。不过不用担心,我们写得代码就是handler驱动起来的,每16ms都有vsync。不停会有消息过来。这些UI绘制信号都封装成为一个个message。我们通过looper来调用handleMessage来回调我们的各Activity、fragment。我们的代码就在循环里执行。主线程一切皆是Message
  • 如果主线程中操作时间过长,可能引起UI线程刷新卡顿。

死循环问题

死循环问题不会导致CPU空转,因为Messagqueue里有一个nativepollonce方法,没有消息就阻塞,它利用了epoll机制,epoll_wait。没消息就休眠。不会空转。

14、Looper无限循环的阻塞为什么没有ANR

什么是ANR

  • 应用无响应,Android对于一些事件要在一定事件内完成,不然就会ANR。
  • 服务20s。前台广播,contentProvider 10s。输入触摸事件分发5s。
  • 类似于拆炸弹,做这些事情在system_server进程中埋下倒计时,如果没有定时拆则会引发爆炸。对于输入事件是超过5s,必须要新事件来,才爆炸。

Looper无限循环如何导致阻塞的

  • Looper的无限循环是Looper不停去取MessageQueue中的messge,生命周期切换,长按点击啥的,都依赖这
  • 当messagequeue为空,messagequeue.next中会触发nativePollOnce阻塞,原理是epoll。主线程休眠释放CPU,有消息来时,通过向管道写数据来唤醒主线程。

Looper无限循环为什么没有ANR?

  • ANR是一个事件操作超过规定事件,而Looper无限循环就是为了去取消息操作事件,其阻塞时因为无消息让CPU停下来做其它事,避免空转。这两者不是一个维度。。

15、Looper如何在子线程中创建?

  • 先Looper.prepare
  • new handler(Looper.myLooper())
  • 最后Looper.loop

16、Looper、handler、线程间的关系。例如一个线程可以有几个Looper可以对应几个Handler?

  • 一个线程对应一个Looper。在prepare的时候有在threadlocalmap里检查的。
  • 但是可以对应N个handler

17、子线程发送消息到主线程更新UI,除了handler和Asynctask,还有什么?

  • 子线程中可以用runOnUiThread,本质上还是Handler的post方法
  • view.post()方法更新,本质上是getRunQueue().post。内部是先sendMessageDealyed方法后sendMessageAtTime方法,最后是queue.enqueueMessage,最终还是用到了handler

18、idleHandler是什么?怎么使用?能解决什么问题?

  • idleHandler是Messaagequeue内自定义的一个接口,一般用于性能优化。就是当消息队列中没有需要立即执行的message时,会主动触发idlehandler的queueIdle方法。放回值为 false,只会执行一次。返回值为true,每次消息队列内没有需要立即执行的消息时,都会触发该方法。

怎么使用

class MyIdleHandler implements IdleHandler {  
    @Override  
    public boolean queueIdle() {  
        // 在这里执行空闲状态时的操作  
        // 例如关闭数据库连接、清理资源等  
        return false; // 返回false表示处理完成,true表示还有更多任务要处理  
    }  
}  

Looper.myQueue().addIdleHandler(new MyIdleHandler());

能解决什么问题?

Idlehandler是在主线程空闲的时候被执行,那我们可以将相对耗时的或者一些 影响线程运行的事务放到IdleHandler里面处理。比如我们需要调用GC,一般会带来STW问题,于是可以将这个动作放到IdleHandler里面执行,而android源码确实也是这样进行的。

19、Android 系统启动流程(重要)

  • 按下电源键,引导芯片代码从预定的地方(固化在rom)开始执行,加载bootloaader加载到RAM,然后执行。Bootloader是一小段程序,主要功能是将内核映像从闪存加载到内存。
  • 生成第一个进程idle(pid=0),会初始化进程管理,内存管理和一些驱动
  • idle会生成两个进程。
    • pid=1的init进程,用户空间的鼻祖

      • 创建和挂在 文件系统
      • 初始化和启动系统服务
      • 给进程创建信号处理函数防止僵尸进程出现
      • 通过(init.rc配置文件) fork java进程的鼻祖 zygote
        • zygote
          • 启动Java虚拟机
          • 注册jni方法和预加载类和资源。
          • 使用jni方法调用zygoteinit的main方法
            • zygote通过 zygoteinit fork 一个systemserver,两者用socket通信,传递一些初始化啊和配置信息,systemserver创建AMS,两用binder通信,AMS通过socket和zygote 通信。
            • systemserver通过binder告诉AMS,然后AMS通过socket告诉zygote去创建app进程
    • pid=2的kthreadd的内核空间鼻祖

      • 它会创建内核工作线程、软中断等内核守护进程。

image.png

20、Zygote进程的启动流程(重要)

  • 收集开机后会执行init文件,启动init.rc脚本,在脚本里启动zygote
  • 来到app_main.cpp
    • 初始化AndroidRuntime
    • 设置zygote为启动模式,strcmp(arg,"--zygote")
    • 给start函数的参数args赋值
    • runtime.start启动Zygoteinit
    • zygoteinit
      • 创建虚拟机
      • 注册jni
      • 启动zygote的main函数
        • 预加载
        • 创建socket
        • 创建systemServer
        • 启动systemserver的main方法
        • 无限循环

21、Android中进程的优先级

从低到高 空进程-后台进程-服务进程-可见进程-前台进程。空侯福贱钱

22、SystemServer进程的启动流程(重要)

SystemServer进程是由zygote启动的,管理app运行的所需的AMS WMS PMS等等。

  • SystemServer被创建后
    • 启动Binder线程池,这样就可以与其它进程进行Binder跨进程通信。
    • SystemServer在启动过程总给,先初始化一些系统变量,加载类库,创建Context对象。
    • 创建SystemServiceManager,对系统服务进行创建、启动和生命周期管理。
    • 启动各种系统服务,AMS PMS WMS等
    • SystemServer启动前,尝试与Zygote建立socket通信,成功才启动服务
    • 启动的服务单独运行在systemserver的各线程中,同属于Systemserver进程。

23、AMS启动流程

AMS是ActivityManagerService。Android 10之前,主要对四大组件管理和调度,也对进程、电池、内存、权限进行管理。Android 10及之后,就把Activity的管理下方给了ATMS,activity task manager service。他们不是独立进程,都在systemServer进程中运行。

AMS&ATMS的启动

  • 在systemServer的main函数会有个run方法,startBootstrapServices,启动AMS和ATMS。他们只是一个服务,并非一个进程。
  • atms&ams启动后将自己的本地服务公布到Localservices列表,进程内别的服务要使用他们就可以去localservice中找。
  • atms&ams将自己的binder公布给ServiceManager进程,由这个进程存储,以后方便其它进程获取AMS和ATMS的binder代理。

24、SystemServer进程为什么要在Zygote中fork启动,而不是在init进程中直接启动

  • fork函数能创建两个几乎一样的进程,pid不一样,子线程没了。主进程会返回一个子进程pid(打印子和自己pid),子线程返回0(打印自己pid)
  • Zygote作为一个孵化器,可以提前加载一些资源,这个fork时基于Copy-On-Write机制创建的其它进程就能直接使用这些资源,不用重新加载,比如system_server就可以直接使用Zygote中的JNI函数、共享库、常用的类以及主题资源。然而init进程中没有这些资源提供,所以就没设计init进程船舰SystemServer

25、为什么要用Zygote进程去孵化app进程,而不是让SystermServer去孵化?

zygote进程:

  • 虚拟机初始化与启动
  • JNI函数的注册
  • 加载公用的各种资源
  • 创建socket服务器并在runSelectionLoop中死循环等待socket消息,fork了systemServer进程等操作

SystemServer进程作为Zygote进程的大儿子

  • 主要是启动和管理 引导服务 核心服务啥的 AMS WMS PMS 等90多种服务专门给App进程使用的。
  • App的运行需要什么?
    • 虚拟机
    • 能够调用framework中的资源和函数。Zygote加载了
    • AMS WMS等服务给app运行和管理提供支持。systemServer做了
  • 所以对应用程序来说 zygote内做的事情用的到 ,systemserver中的AMS WMS 等服务用不到。浪费了
  • 另外fork对多线程不友好,可能死锁,而system_server有很多子线程。
    • fork,复制整个用户空间数据(copy_on_write),然后仅仅复制当前线程到子进程中。那么其它线程就蒸发了。假设蒸发的这个子线程拿到了锁,锁被复制,但是哪个持锁的线程没了。那不就死锁了。

26、Zygote为什么不采用Binder机制进行IPC通信呢

  • zygote采用binder会导致fork出来的进程产生死锁。当然Zygote在fork子进程时也会进行子线程的锁释放,以确保子进程的独立运行和避免死锁的产生。
  • UNIX上有个准则:多线程程序里不准使用fork。因为容易死锁。
  • Binder支持多线程,Binder线程池有多个线程运行。binder中自然会出现子线程锁等待状态,如果fork。锁还在,子线程没了,嚯,死锁。

27、Android app的进程怎么启动的

  • 冷启动:后台没有改应用的进程。这时系统会重新创建一个新的进程分配给应用。必须先实例化Application
  • 热启动:应用启动时,后台已有该应用的进程,比如back或者home。就直接启动,不用创建Application。

启动App进程

  • 点击launcher桌面程序的App图标
  • laucner程序调用startActivity函数,通过Binder发给System_server进程,通过binder发给其中的AMS,AMS通过socket告诉Zygote进程fork出一个子进程(App进程)

详细步骤

  • 点击了launcher通过ServiceManager.getService("system_server")拿到system_server的实例systemManager代理对象,再通过systemManager.getService("activity")获取AMS的代理对象,Android10及之后是通过systemManager.getService("activity_task_manager")拿到ActivityTaskManagerProxy。
  • ActivityTaskManagerProxy和ActivityTaskManager通信,ActivityTaskManager和ATMS通信,ATMS检测判断,看App进程是否村咋爱,如果存在就热启动,如果不存在就是冷启动,然后ATMS发给送 socket请求给到Zygote进程,通知zygote去创建App进程
  • Zygote进程收到socekt请求就会去fork函数,fork会产生一个子进程,这个 就是需要启动的app进程,它具备Zygote进程几乎一切,没有子线程凉了没有。
  • 开启App主线程
    • App进程启动后,
    • native层面反射执行其main函数
    • 例化一个ActivityThread
    • 触发Activitythread.attch函数,前后Looper.prepareMainLooper()和Looper.loop()
    • ApplicationThread(处理App和安卓系统服务之间通信,非主线程)、Looper、Handler对象。开启主线程消息循环Looper.loop()

28、Android Application为什么是单例

  • application的创建来自于handleBindApplication
  • AppBindData.info.makeApplication(...)
  • 如果mApplication不为空就返回
  • 如果为空就通过mActivityThread.mInstrumentation.newApplication(...)

29、Intent的原理,作用,可以传递哪些类型的参数?

原理

系统启动时,PMS扫描所有已安装apkmu解析其中AndroidManifest清单文件得到App信息,都存起来构建完成apk信息树。Intent去各个组件通信的时候,会调用PMS去查查找对应的组件列表,找到相关组件进行类似用于启动的操作

作用

  • Activity之间传递信息
  • App之间传递信息
  • 发送广播

能传递哪些类型参数?

通过binder实现的

  • 基本数据类型和String
  • 其它类必须实现Parcelable或者Serializable
  • 如何是集合或者数组,其中的元素要么是基本数据类型的包装类、String。或者实现Parcelable或者Serializable

30、Activity启动流程分析

大概流程

  • 桌面系统其实时一个Launcher,点击后
  • 它调用ATMS(Android 10之前是AMS后面就不说了),
  • ATMS通过socket发给zygote,
  • 请求fork一个app进程,
  • app进程启动后,反射执行ActivityThread的main函数,
  • 然后Looper.prepare .新建一个ActivityThread对象,然后执行这个对象的attach函数。然后Looper.loop.
  • 然后执行这个对象的attach函数
    • ATMS的attachApplication方法,
    • ATMS通过AMS与ApplicationThread通信
    • ApplicationThread的bindeApplication函数执行,开始应用初始化,加载资源和设置
    • application的生命中后期,完成初始化和涉资
    • 启动Activity并执行相关流程

详细版本

  • 主线程中执行execStartActivity中有个instrumentation做检测。
  • ActivityManagerProxy startActivity和AMS通信,
  • AMS-》ATMS.execute-》ActivityStarter 解读activity启动模式在内的各种参数,然后 启动个黑白屏
  • 交给ActivityStack处理。因为其中存储了应用中所有的Activity
  • 为了更好管理Activity的各个生命周期,调用startSpecificActivity到ActivityStackSupervisor,把生命周期事件封装成clientTransaction
  • 交给ApplicationThreadProxy,通过schedulerLauchActivity跨进程binder访问App今晨给,然后给App进程的transactionExecutor统一执行。直到完成Activity的onResume

image.png

32、如果需要在Activtiy间传递大量数据怎么办?

Intent传递数据最大时1M-8K.一般更少因为还有包装,因为Binder锁映射的内存大小就是这么大。actvity间出纳第数据一般时用到binder。

  • 文件或者数据库
  • 将要传递的信息封装在静态类中,然后只传名字
  • 匿名共享内存
  • ViewModel和LiveData也不是不行

33、打开页面,如何实现一键退出

  • 获取当前系统的任务栈,逐步退出Activity
  • 同上,Applicaton中子类建立一个Activity链表,保存正在运行的Activity实例,需要时遍历退出。
  • 行System.exit(0) 强制退出/killProcess
  • 广播/viewModel/eventbus 每个Activity中都接收并退出。

34、startActivity(MainActivity.this,LoginActivity.class);LoginActivity配置的launchMode是何时解析的?

  • 在AMS中检查是否存在实例,通过检查activity包名、类名和标志等信息来判断
  • 确定启动模式
    • standard:标准
    • singleTop:如果在栈顶有就不会创建新的,不在栈顶就复用,没有就创建新的
    • singleTask:整个个系统中只有这一个activity,如果栈内有了,会清除它栈顶部的activity
    • singleInstance:在singletask的基础上,所在的task只有一个activity,就是它
  • 新建栈或者复用已存在栈的充分必要条件是什么?
    • singleTask/singleInstance会为mLaunchFlags自动添加FLAG_ACTIVITY_NEW_TASK,也就是说他们都有存在不使用当前栈的可能。
    • FLAG_ACTIVITY_NEW_TASK + taskAffinity(不同标识不同包名)一定会出新栈,优先级最高,及时是single'task这个模式也会被覆盖

35、清单文件中配置receiver,系统是合适会注册此广播接收者的?

  • 一般是由pkms来完成的。触发执行分为两个部分
    • 系统启动时,会启动system_server进程,而SystemServer进程会启动各种服务,这些服务包括PKMS,启动PKMS就会扫描apk安装目录解析Androidmanifest,做持久化存储。
    • app安装的时候,也会触发PKMS对apk进行检查,调用类似流程解析AndroidManifest
  • Receiver的计息,都只是整个AndroidManifest解析过程中的一环

36、如何通过WindowManager添加Window?

  • 权限设置
  • 获取windowmanagerservice的代理对象,一个WindowManagerImpl实例
  • 获取需要添加的view,设置一下参数-type/FLAG/宽高/坐标系
  • WindowManagerImpl.addView(view.params)

37、为什么Dialog不能用Application的Context

  • Dialog添加到Window中需要一个 合法的Token,这个在Activity运行后是可以获得的
  • Window、WM、WMS、Token的概念
    • Window是窗口的意思,对应屏幕上一块显示区域。实现类是PhoneWindow。Window有一个属性值Type(应用窗口、子窗口、系统窗口)
    • WM,WindowManager,WIndowManager是应用于窗口之间的桥梁。实现类是WindowManagerImpl
    • WMS,WindowManagerService单独的进程。WindowManagerImpl通过IWindowSession与其通过Binder IPC通信。它是窗口的管理这,负责窗口的启动、添加和删除。窗口的大小层级也是WMS进行管理的
    • Token:窗口令牌,是一种特殊的Binder令牌,WMS用它标识唯一识别系统中的一个窗口
  • Application或者Service的context去获取WindowManager服务的话,会得到一个WindowManagerImpl实例,这个实例的token是空的。
  • 因为这个令牌只会在Activity,Activity作为context去拿mWIndowManager,这个mWindowmanager在Activity的attach方法中被创建,Token指向此Activity的Token

38、WIndowManagerSErveice中的token到底是什么?token存在意义是什么?

  • Activity有自己的PhoneWindow,WindowManager同时PhoneWindow含有token

  • 而Application并没有自己的PhoneWindow,它返回的WindowManager是应用服务windowManager,并没有赋值token的过程。

  • 当我们使用Activity来弹出dialog时,此时Activity的DecorView已经是显示到屏幕上了。有界面了。这个dialog作为子窗口类型被添加到PhoneWindow上,它的token就是DecorView的token。此时DecorView已经被显示到屏幕上了,它本身是拥有token的;

    • 当一个view被添加到 屏幕后,它对应的 viewRootIMpl就有一个token对象。这个token来自WindowManagerGlobal,是 一个IWindowSEssion对象 PhoneWindow的DecorView添加到屏幕后,后续添加的子window的token,就都是这个IWindowSession 对象了

WMS是如何验证token的?

  • Activity的PhoneWindow是有token,而Application使用的是应用级服务windowManager,并不存在token。
  • mTokenMap 是一个 HashMap<IBinder, WindowToken> 对象,这里就可以保存一开始初始化的token以及后来创 建的windowToken两者的关系。WMS就可以根据IBinder对象拿到windowToken进行信息 对比。

image.png