Android Framework-Android启动过程

news2024/9/23 17:20:48

第一个系统进程(init)

Android设备的启动必须经历3个阶段,即Boot Loader、Linux Kernel和Android系统服务,默认情况下它们都有各自的启动界面。严格来说,Android系统实际上是运行于Linux内核之上的一系列“服务进程”,并不算一个完整意义上的“操作系统”。这些进程是维持设备正常工作的关键,而它们的“老祖宗”就是init。
init的PID值为0,它通过解析init.rc脚本来构建出系统的初始运行形态。
 init.rc实例分析

 export PATH /sbin:/system/sbin:/system/bin #响应boot事件,设置系
统环境变量
 export LD_LIBRARY_PATH /system/lib #响应boot事件,设置库路径
 mkdir /dev #创建/dev目录
 mkdir /proc #创建/proc目录
 mkdir /sys #创建/sys目录。这时还没有超出on boot的作用范围,下同
 mount tmpfs tmpfs /dev
 mkdir /dev/pts
 mkdir /dev/socket
 mount devpts devpts /dev/pts
 mount proc proc /proc
 mount sysfs sysfs /sys #以上几行用于挂载文件系统以及创建新的目录
 write /proc/cpu/alignment 4 #打开文件,并写入数值
 ifup lo #建立lo网络连接
 hostname localhost #设置主机名
 domainname localhost #设置域名
 mount yaffs2 mtd@system /system
 mount yaffs2 mtd@userdata /data
 import /system/etc/init.conf #导入另一个配置文件
 class_start default #启动所有标志为default的服务
service adbd /sbin/adbd #启动adbd服务进程
 user adb
 group adb # Adbd是android debug bridge daemon的缩写,它为开发者与设
备之间建立了一条通道。 
service usbd /system/bin/usbd -r
 user usbd
 group usbd
 socket usbd 666 #启动usbd服务。
service zygote /system/bin/app_process -Xzygote /system/bin --
zygote
 socket zygote 666 #启动zygote服务。Zygote是系统的“孵化器”,负责生产
“进程”
on device-added-/dev/compass
 start akmd #当增加了/dev/compass节点后,启动akmd服务
on device-removed-/dev/compass
 stop akmd #当移除了/dev/compass节点后,停止akmd服务
service akmd /sbin/akmd
 disabled
 user akmd
 group akmd #因为这里对akmd服务使用了disabled选项,所以系统不会主动去启
动它。而是要等到上面
 #描述的/dev/compass节点出现时,才显式地调用此服务

系统关键服务的启动简析

作为Android系统的第一个进程,init将通过解析init.rc来陆续启动其他关键的系统服务进程——其中最重要的就是ServiceManager、Zygote和SystemServer。
Android的“DNS服务器”——ServiceManager
ServiceManager是Binder机制中的“DNS服务器”,负责域名(某Binder服务在ServiceManager注册时提供的名称)到IP地址(由底层Binder驱动分配的值)的解析。
ServiceManager是在Init.rc里描述并由init进程启动的。如下所示:

/*sytem/core/rootdir/Init.rc*/
service servicemanager /system/bin/servicemanager
 class core
 user system
 group system
 critical
 onrestart restart healthd
 onrestart restart zygote
 onrestart restart media
 onrestart restart surfaceflinger
 onrestart restart drm

可以看到,servicemanger是一个Linux程序。它在设备中的存储路径是/system/bin/service- manager,源码路径则是/frameworks/native/cmds/servicemanager。
ServiceManager所属class是core,其他同类的系统进程包括ueventd、console(/system/bin/sh)、adbd等。根据core组的特性,这些进程会同时被启动或停止。另外,critical选项说明它是系统的关键进程——意味着如果进程不幸地在4分钟内异常退出超过4次,则设备将重启并进入还原模式。当ServiceManager每次重启时,其他关键进程如Zygote、media、surfaceflinger等也会被restart。
“孕育”新的线程和进程——Zygote
Zygote这个词的字面意思是“受精卵”,因而可以“孕育”出一个“新生命”。正如其名所示,Android中大多数应用进程和系统进程都是通过Zygote来生成的。
以init.zygote64.rc为例,相关代码如下:

service zygote /system/bin/app_process64 -Xzygote /system/bin --
zygote --start-system-server
 class main
 socket zygote stream 660 root system
 onrestart write /sys/android_power/request_state wake
 onrestart write /sys/power/state on
 onrestart restart media
 onrestart restart netd

从上面这段脚本描述可以看出:

ServiceName: zygote
Path: /system/bin/app_process64
Arguments: -Xzygote /system/bin --zygote --start-system-server

Zygote所属class为main,而不是core。和其同class的系统进程有netd、debuggerd、rild等。
从zygote的path路径可以看出,它所在的程序名叫“app_process64”,而不像ServiceManager一样在一个独立的程序中。通过指定–zygote参数,app_process可以识别出用户是否需要启动zygote。app_process又是何方神圣呢?先来看看它的Android.mk:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
 app_main.cpp
LOCAL_SHARED_LIBRARIES := \
 libcutils \
 libutils \
 liblog \
 libbinder \
 libandroid_runtime
LOCAL_MODULE:= app_process
LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := app_process32
LOCAL_MODULE_STEM_64 := app_process64
include $(BUILD_EXECUTABLE)

上述是构建Multilib(64位和32位系统)的一个编译脚本范例。其中LOCAL_MULTILIB用于指示你希望针对的硬件平台架构。可选值如下:“32” :表示只编译32位版本。“64”: 表示只编译64位版本。 “both” :表示同时编译32位和64位的版本。“” :表示由系统根据其他变量来决定要编译的目标。
从上面的描述可以很明显地看到,app_process其实扮演了一个类似于“壳”的角色,那么它容纳了哪些“内容”呢?

/*frameworks/base/cmds/app_process/App_main.cpp*/
int main(int argc, char* const argv[])
{…
 AppRuntime runtime(argv[0], computeArgBlockSize(argc,
argv));//Android运行时环境
 …
 bool zygote = false;
 bool startSystemServer = false;
 bool application = false;
 String8 niceName;
 String8 className;
 ++i; // Skip unused "parent dir" argument.
 while (i < argc) {
 const char* arg = argv[i++];
 if (strcmp(arg, "--zygote") == 0) { //当前进程是否用于承载
zygote
 zygote = true;
 niceName = ZYGOTE_NICE_NAME;
 } else if (strcmp(arg, "--start-system-server") == 0) {//
是否需要启动system server
 startSystemServer = true;
 } else if (strcmp(arg, "--application") == 0) {
 application = true;
 } else if (strncmp(arg, "--nice-name=", 12) == 0) {
 niceName.setTo(arg + 12);
 } else if (strncmp(arg, "--", 2) != 0) {
 className.setTo(arg);
 break;
 } else {
 --i;
 break;
 }
 }if (zygote) {
 runtime.start("com.android.internal.os.ZygoteInit",
args);
 } else if (className) {
 runtime.start("com.android.internal.os.RuntimeInit",
args);
 } else {}
}

这个函数用于解析启动app_process时传入的参数,具体如下。
–zygote: 表示当前进程用于承载zygote。
–start-system-server:是否需要启动system server。
–application:启动进入独立的程序模式。
–nice-name:此进程的“别名”。
对于非zygote的情况下,在上述参数的末尾会跟上main class的名称,而后的其他参数则属于这个class的主函数入参;对于zygote的情况,所有参数则会作为它的主函数入参使用。
在我们这个场景中,init.rc指定了–zygote选项,因而app_process接下来将启动“ZygoteInit”并传入“start-systemserver”。之后ZygoteInit会运行于Java虚拟机上,为什么?
原因就是runtime这个变量——它实际上是一个AndroidRuntime对象,其start函数源码如下:

/*frameworks/base/core/jni/AndroidRuntime.cpp*/
void AndroidRuntime::start(const char* className, const char*
options)
{
 …
 JNIEnv* env;
 if (startVm(&mJavaVM, &env) != 0) {//启动虚拟机
 return;
 }
 onVmCreated(env);//虚拟机启动后的回调}

我们这里假设VM可以成功启动,并进入ZygoteInit的执行中:

/*frameworks/base/core/java/com/android/internal/os/ZygoteInit.ja
va*/ 
public static void main(String argv[]) {
 try {…
 boolean startSystemServer = false;
 String socketName = "zygote";
 String abiList = null;
 for (int i = 1; i < argv.length; i++) {
 if ("start-system-server".equals(argv[i])) {
 startSystemServer = true;//需要启动System
Server
 } else if (argv[i].startsWith(ABI_LIST_ARG)) {
 abiList =
argv[i].substring(ABI_LIST_ARG.length());
 } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
 socketName =
argv[i].substring(SOCKET_NAME_ARG.length());
 } else {
 throw new RuntimeException("Unknown command
line argument: " + argv[i]);
 }
 }
 if (abiList == null) {
 throw new RuntimeException("No ABI list
supplied.");
 }
 registerZygoteSocket(socketName);//注册一个Socket
 preload();//预加载各类资源if (startSystemServer) {
 startSystemServer(abiList, socketName);//后面对此
函数进行详细分析
 }
 Log.i(TAG, "Accepting command socket connections");
 runSelectLoop(abiList);
 closeServerSocket();
 } catch (MethodAndArgsCaller caller) {
 caller.run();
 } catch (RuntimeException ex) {
 Log.e(TAG, "Zygote died with exception", ex);
 closeServerSocket();
 throw ex;
 }
 }

ZygoteInit的主函数并不复杂,它主要完成两项工作:

  • 注册一个Socket
    Zygote是“孵化器”,一旦有新程序需要运行时,系统会通过这个Socket(完整的名称为ANDROID_SOCKET_zygote)在第一时间通知“总管家”,并由它负责实际的进程孵化过程。
  • 预加载各类资源
    函数preload用于加载虚拟机运行时所需的各类资源,包括:
    preloadClasses();
    preloadResources();
    preloadOpenGL();
    preloadSharedLibraries();
  • 启动System Server
    如果app_process的调用参数中带有“–start-system-server”,那么此时就会通过startSystemServer来启动System Server。Zygote在前期主要担任启动系统服务的工作,后期则又担当“程序孵化”的重任。但是Zygote只在init.rc中被启动一次,它如何协调好这两项工作的关系呢?我们可以推断一下,上述的startSystemServer应该会新建一个专门的进程来承载系统服务的运行,而后app_process所在的进程则转变为Zygote的“孵化器”守护进程。那么是不是这样子的呢?
/*
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
*/
private static boolean startSystemServer(String abiList, String
socketName)
 throws MethodAndArgsCaller, RuntimeException {/* Hardcoded command line to start the system server */
 String args[] = {
 "--setuid=1000",
 "--setgid=1000",
 "--
setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,
 1032,3001,3002,3003,3006,3007",
 "--capabilities=" + capabilities + "," +
capabilities,
 "--runtime-init",
 "--nice-name=system_server",
 "com.android.server.SystemServer",
 };
 ZygoteConnection.Arguments parsedArgs = null;
 int pid;
 try {
 parsedArgs = new ZygoteConnection.Arguments(args);
 
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
 
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
 /* Request to fork the system server process */
 pid = Zygote.forkSystemServer(
 parsedArgs.uid, parsedArgs.gid,
 parsedArgs.gids,
 parsedArgs.debugFlags,
 null,
 parsedArgs.permittedCapabilities,
 parsedArgs.effectiveCapabilities); //果然需要
fork一个新进程
 } catch (IllegalArgumentException ex) {
 throw new RuntimeException(ex);
 }
 if (pid == 0) {//子进程,即System Server所承载进程
 if (hasSecondZygote(abiList)) {
 waitForSecondaryZygote(socketName);
 }
 handleSystemServerProcess(parsedArgs);//启动各System
Server
 }
 return true;
 }

forkSystemServer在内部利用UNIX的fork机制创建了一个新进程;而这个“新生儿”(即pid == 0分支)会在随后的执行过程中通过handleSystemServerProcess来启动各种支撑系统运行的System Server。
System Server的启动是在startSystemServer中完成的。Zygote首先会利用Zygote.forkSystemServer来孵化出一个子进程,然后在pid==0的分支中调用handleSystem ServerProcess,后者在函数的末尾又会进一步调用RuntimeInit.zygoteInit:

/*frameworks/base/core/java/com/android/internal/os/RuntimeInit.j
ava */ 
public static final void zygoteInit(int targetSdkVersion,
String[] argv, 
ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller {commonInit();
 nativeZygoteInit();
 applicationInit(targetSdkVersion, argv, classLoader);
 }

函数zygoteInit通过3个方面来完成初始化,分别是:

  • commonInit
    通用部分的初始化,包括设置默认的uncaught exception handler(具体对应的是RuntimeInit中的UncaughtHandler类);为HttpURLConnection准备好默认的HTTP User-Agent (User Agent包含了
    与系统浏览器相关的一系列信息,如“Dalvik/1.1.0 (Linux; U;Android Eclair Build/MASTER)”.);开启trace模式(只在emulator下才有必要)等。
  • nativeZygoteInit
    这是一个本地初始化函数
  • applicationInit
    这个函数的声明为:private static void applicationInit(int targetSdkVersion, String[] argv, Class Loader classLoader);从中可以看出它是程序运行的“起点”。
    第二个参数argv,这个String[]实际上包含了两个重要的成员变量,即startClass和startArgs。而这两个变量的赋值可以追溯到startSystemServer中,具体代码如下:
String args[] = {
 "--setuid=1000",
 "--setgid=1000", 
 "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,
 1009,1010,1018,1032,3001,3002,3003,3006,3007",
 "--capabilities=" + capabilities + "," +
capabilities,
 "--runtime-init",
 "--nice-name=system_server",
 "com.android.server.SystemServer",
 };

换句话说,startClass对应的就是com.android.server.SystemServer。因而applicationInit最终将调用
main@SystemServer:

public static void main(String[] args) {
 new SystemServer().run();
 }

经过上面的初始化后,程序现在会有两个分支,其一是nativeZygoteInit主导的本地系统服务的启动;另一个则是applicationInit负责的Java层系统服务的启动。
在这里插入图片描述
Zygote和System Server的启动流程

Android的“系统服务”——SystemServer
 SystemServer是Android进入Launcher前的最后准备。 一旦我们在init.rc中为Zygote指定了启动参数–start-system-server,那么ZygoteInit就会调用startSystemServer来进入SystemServer。而且系统服务又分别分为Java层和本地层两类。其中Native层服务的实现体在android_servers中,需要在run@SystemServer中首先通过System.loadLibrary(“android_servers”)加载到内存中才能使用。而nativeInit则负责为启动本地层服务而努力。
再回到Java层来看一下这类系统服务是如何管理的。从代码中可以看到,这部分Server又可细分为3类,如下所示:

  • Bootstrap Services
    BootStrap的原意是“引导程序”,用在这里则代表系统服务中最核心的那一部分。另外,这些Services间相互的依赖关系比较强,因而需要在一起统一管理启动,具体对应的是startBootstrapServices这个
    函数。按照Android的建议,如果你自己添加的系统服务和它们也有较强的依赖,那么可以与这类系统服务统一放置,否则就应该考虑下面所述的另两类服务:
/*frameworks/base/services/java/com/android/server/SystemServer.j
ava*/ 
 private void startBootstrapServices() {
 mInstaller =
mSystemServiceManager.startService(Installer.class);
 mActivityManagerService =
mSystemServiceManager.startService(
 
ActivityManagerService.Lifecycle.class).getService();
 
mActivityManagerService.setSystemServiceManager(mSystemServiceMan
ager);
 mPowerManagerService =
mSystemServiceManager.startService(PowerManagerService.class);
 mActivityManagerService.initPowerManagement();
 mDisplayManagerService =
mSystemServiceManager.startService(DisplayManagerService.class);
 
mSystemServiceManager.startBootPhase(SystemService.PHASE_WAIT_FOR
_DEFAULT_DISPLAY);
 // Only run "core" apps if we're encrypting the device.
 String cryptState = SystemProperties.get("vold.decrypt");
 if (ENCRYPTING_STATE.equals(cryptState)) {
 Slog.w(TAG, "Detected encryption in progress - only
parsing core apps");
 mOnlyCore = true;
 } else if (ENCRYPTED_STATE.equals(cryptState)) {
 Slog.w(TAG, "Device encrypted - only parsing core
apps");
 mOnlyCore = true;
 }
 // Start the package manager.
 Slog.i(TAG, "Package Manager");
 mPackageManagerService =
PackageManagerService.main(mSystemContext, mInstaller,
 mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
mOnlyCore);
 mFirstBoot = mPackageManagerService.isFirstBoot();
 mPackageManager = mSystemContext.getPackageManager();
 Slog.i(TAG, "User Service");
 ServiceManager.addService(Context.USER_SERVICE,
UserManagerService.getInstance());
 // Initialize attribute cache used to cache resources
from packages.
 AttributeCache.init(mSystemContext);
 // Set up the Application instance for the system process
and get started.
 mActivityManagerService.setSystemProcess();
 }

上面的函数中被调用最多的语句是mSystemServiceManager.startService。目前所有System
Service都统一由SystemServiceManager来管理。System Service Manager首先会启动Installer,这是为了让Installer可以优先完成初始化,并完成关键目录(如/data/user)的创建。这些都是其他服务可以顺利启动的先决条件。接下来启动的系统服务是ActivityManagerService,在AMS之后相继启动的服务包括电源管理Power Manager、Display Manager、PackageManager等,最后调用setSystemProcess来添加进程相关的服务,如meminfo、gfxinfo、dbinfo、cpuinfo等,从而完成最核心部分系统服务的启动。

  • Core Services
    Core Service相对于BootStrap的优先级略低,主要包括LED和背光管理器、电池电量管理器、应用程序使用情况(Usage Status)管理器等
  • Other Services
    这部分服务在3类Service中优先级最低,但数量却最多。比如AccountManagerService、VibratorService、MountService、NetworkManagementService、NetworkStatsService、ConnectivityService、WindowManagerService、UsbService、SerialService、AudioService等。这些服务全面构筑起Android系统这座“参天大厦”,为其他进程、应用程序的正常运行奠定了基础。
    最后,SystemServer通过Looper.loop()进入长循环中,并依托onZygoteInit中启动的Binder服务接受和处理外界的请求。
    Vold和External Storage存储设备
    Android系统中的内/外存储设备定义如下:
  • Internal Storage
    按照Android的设计理念,Internal Storage代表的是/data存储目录。
  • External Storage
    所有除Internal Storage之外的可存储区域

从物理设备的角度来看,External Storage由如下几种类型组成:

  • Emulated Storage
    Android设备中存在的一个典型做法,是从Internal Storage(如Flash)中划分一定的区域(如1GB)来作为外部存储设备,称为Emulated Storage
  • SDCARD/USB Devices
    通过扩展卡槽或者USB端口来扩展设备的存储能力,也是Android设备中的常见情况。
    Android系统中的外部存储设备由Vold和Mount Service来统一管理。其中Vold对应的源码路径是:AOSP/system/vold。它是通过init.rc启动的,如下所示
on post-fs-data
…
 start vold

Vold在启动以后,会通过NETLINK和内核取得联系,并根据后者提供的event来构建存储设备管理系统。和以往版本不同的是,Vold的配置文件不再是vold.fstab,而变成了/fstab.<ro.hardware>。例如
AOSP/device/fugu/fstab.fugu
Android 6.0及以后版本中,根据设备具体情况不同主要有如下几种典型配置:
(1)Emulated primary only
即只有Emulated Storage的情况,此时fstab.device的配置范例如
下:
/devices//xhci-hcd.0.auto/usb auto auto defaults
voldmanaged=usb:auto
(2)Physical primary only
即只有一个外置物理存储设备的情况,此时fstab.device的配置范
例如下:
/devices/platform/mtk-msdc.1/mmc_host* auto auto defaults
voldmanaged=sdcard0:auto,encryptable=userdata,noemulatedsd
(3)Emulated primary, physical secondary
有两个外置的物理存储设备,它们会被分别设定为primary和secondary,此时fstab.device的配置范例如下:
/devices/platform/mtk-msdc.1/mmc_host* auto auto defaults
voldmanaged=sdcard1:auto,encryptable=userdata

Vold在启动过程中会通过process_config函数来处理fstab配置文件,并把它们存储在VolumeManager的全局变量中。后续当收到内核的NetlinkEvent (add)时,VolumeManager再在handleBlockEvent中根据
规则判断本次事件是否和之前记录的fstab配置相匹配——如果答案是肯定的话,则新创建一个Disk对象来管理,并将它们统一添加到mDisks中,如图所示。
在这里插入图片描述

我们不难发现,Emulated Storage所需的存储空间来源于设备的data分区。换句话说,Emulated Storage的存储空间和data分区是共享存储区域的。Emulated Storage当然也是由Volume Manager来统一管理的,如下所示:

/*system/vold/VolumeManager.cpp*/
int VolumeManager::start() {CHECK(mInternalEmulated == nullptr);
 mInternalEmulated =
std::shared_ptr<android::vold::VolumeBase>(
 new android::vold::EmulatedVolume("/data/media"));
 mInternalEmulated->create();
 return 0;
}

VolumeManager::start会被vold的main函数调用,因而从Vold的角度来看所有Android设备都是带有Emulated Storage的,只不过最终是否需要执行mount操作则由MountService来决定。从EmulatedVolume构造函数的参数可以看到,它在data分区中对应的路径是/data/media。VolumeManager的全局变量mInternalEmulated用于记录系统的EmulatedStorage。

接下来mInternalEmulated->create()除了给Storage创建运行环境外,还会向MountService发送一个VolumeCreated的消息,并将自身的状态迁移到kUnmounted。MountService收到这一信息后,会根据系统的实际情况决定是否挂载这个Storage——如果答案是肯定的话,那么它会回应一个mount指令给vold,而后者对此的处理过程中会进一步调用到doMount函数——这个函数最关键的步骤之一是fork一个新进程,用于运行/system/bin/sdcard。

例如domount @EmulatedVolume.cpp中的如下代码段:

if (!(mFusePid = fork())) {
 if (execl(kFusePath, kFusePath,
 "-u", "1023", // AID_MEDIA_RW
 "-g", "1023", // AID_MEDIA_RW
 "-m",
 "-w",
 mRawPath.c_str(),
 label.c_str(),
 NULL)) {
 PLOG(ERROR) << "Failed to exec";
 }
 LOG(ERROR) << "FUSE exiting";
 _exit(1);
 }

变量kFusePath指向的是“/system/bin/sdcard”,我们可以通过execl系统调用将其启动起来。Sdcard daemon对应的源代码目录是AOSP/system/core/sdcard。
Sdcard daemon属于Fuse Service。Fuse的全称是“Filesystem in Userspace”,即在用户态实现的一种File System。它的典型框架如图所示
在这里插入图片描述
当使用者(左半部分)希望访问FUSE文件系统时,这一请求会经过Kernel的VFS首先传递给FUSE对应的驱动模块,然后再通知到用户层的fuse管控程序(例如这个场景中的sdcard)。后者处理请求完成后会将结果数据返回给最初的调用者,从而完成一次交互过程。可见与传统的文件系统相比,FUSE文件系统因为处理层次较多,所以在效率上注定会存在不足的地方。不过“瑕不掩瑜”,FUSE文件系统的灵活性依然为其获得了广泛的应用
了解了FUSE文件系统后,我们再来看sdcard daemon是怎么做的。简单来说它会执行以下几个核心操作。

  • 将/dev/fuse挂载到3个目标路径下
    这几个目标路径分别是:/mnt/runtime/default/%s、/mnt/runtime/read/%s和/mnt/runtime/write/%s,其中“%s”代表的是Volume的label,在Emulated Storage这个场景下对应的是“emulated”。
  • 创建3个线程
    在代码中对应的是thread_default、thread_read和thread_write。这3个线程启动后都会进入for死循环,然后不停地从自己对应的fuse->fd(即打开/dev/fuse产生的文件描述符)中读取fuse模块发过来的消息命令,并根据命令的具体类型(例如FUSE_READ、FUSE_WRITE、FUSE_OPEN等)执行处理函数。
    为什么我们需要将/dev/fuse挂载到3个路径下,并通过不同的线程来管理呢?这和Android 6.0中引入的Runtime Permission有关系。
    Runtime permission允许用户在程序运行到某些特别功能时再动态决定是否赋予程序相应的权限。这样带来的好处是用户可以更清楚地知道应用程序需要(或者已经授予了)哪些权限,以实现更为“透明”的管
    理。不过Runtime Permission只对那些系统认为危险的权限进行保护,大家可以利用如下命令获取详细的权限列表:
adb shell pm list permissions -g -d

Runtime Permission权限管理方式的一种很重要的特性就是要求应用程序的权限可以在运行过程中进行动态调整,而且不能导致应用程序的重启。这其中就涉及Package Manager Service、Activity
Manager Service、Zygote等多个系统服务,我们按照顺序逐一阐述。
首先需要关注的是应用程序启动时的初始化权限处理,此时AMS在startProcessLocked中会做如下处理:

/*frameworks/base/services/core/java/com/android/server/am/Activi
tyManagerService.java*/
private final void startProcessLocked(ProcessRecord app, String
hostingType,
 String hostingNameStr, String abiOverride, String
entryPoint, String[] entryPointArgs) {…
try {
 checkTime(startTime, "startProcess: getting gids from
package manager");
 final IPackageManager pm =
AppGlobals.getPackageManager();
 permGids = pm.getPackageGids(app.info.packageName,
app.userId);
 MountServiceInternal mountServiceInternal =
LocalServices.getService(
 MountServiceInternal.class);
 mountExternal =
mountServiceInternal.getExternalStorageMountMode(uid,
 app.info.packageName);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
 …
 Process.ProcessStartResult startResult =
Process.start(entryPoint,
 app.processName, uid, uid, gids, debugFlags,
mountExternal,
 app.info.targetSdkVersion, app.info.seinfo,
requiredAbi, 
 instructionSet, app.info.dataDir,
entryPointArgs);

mountExternal是由MountService提供的,其代码实现如下所示:

public int getExternalStorageMountMode(int uid, String
packageName) {
 // No locking - CopyOnWriteArrayList
 int mountMode = Integer.MAX_VALUE;
 for (ExternalStorageMountPolicy policy : mPolicies) {
 final int policyMode = policy.getMountMode(uid,
packageName);
 if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
 return Zygote.MOUNT_EXTERNAL_NONE;
 }
 mountMode = Math.min(mountMode, policyMode);
 }
 if (mountMode == Integer.MAX_VALUE) {
 return Zygote.MOUNT_EXTERNAL_NONE;
 }
 return mountMode;
 }

这个函数的处理逻辑是:遍历所有的Policy规则,并从中挑选出数值最小的MountMode——按照由小而大的顺序排列,它们依次是:MOUNT_EXTERNAL_NONE、MOUNT_EXTERNAL_DEFAULT、
MOUNT_EXTERNAL_READ和MOUNT_EXTERNAL_WRITE。应用程序的MountMode的具体取值主要由PMS的checkUidPermission来判断,而后者则会根据APP在AndroidManifest中申请WRITE_MEDIA_STORAGE、READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE等权限的情况来给出结论
当Zygote孵化出一个应用程序进程后,会在MountEmulatedStorage中对Mount Mode做进一步处理,核心实现如下:

if (unshare(CLONE_NEWNS) == -1) {
 ALOGW("Failed to unshare(): %s", strerror(errno));
 return false;
 }
 String storageSource;
 if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
 storageSource = "/mnt/runtime/default";
 } else if (mount_mode == MOUNT_EXTERNAL_READ) {
 storageSource = "/mnt/runtime/read";
 } else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
 storageSource = "/mnt/runtime/write";
 } else {
 // Sane default of no storage visible
 return true;
 }
 if (TEMP_FAILURE_RETRY(mount(storageSource.string(),
"/storage",
 NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {}

透过不同的“View”所能“看到”的Mount Tree是不一样的,从而实现了程序在外部存储上的分权限管理。更为重要的是,采用这种实现方式在应对Runtime Permission变化时是不需要重启应用程序就可以生效的。具体来说,当程序取得Storage新的Runtime Permission以后,PMS会通过MountService向Vold的CommandListener发送一条名为“remount_uid”的命令,后者则进一步将消息传递给Vold的VolumeManager::remountUid函数——在这个函数中就可以对相应的进程进行“视角”的重新调整,从而达到我们的预期效果。

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

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

相关文章

元宇宙XR应用,如何迎接大规模普及的时代?

未来&#xff0c;具有互动性、沉浸感的元宇宙/XR应用将逐渐成为主流&#xff0c;这个趋势已毋庸置疑。 然而&#xff0c;在大趋势下&#xff0c;大众终端用户普遍设备能力不足、网络传输时延、GPU算力分配限制等技术挑战&#xff0c;依然是元宇宙/XR应用在大众广泛渗透的瓶颈。…

【vulhub漏洞复现】Fastjson 1.2.24反序列化漏洞

一、漏洞详情Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 JSON 格式&#xff0c;也可以将 JSON 字符串转换为 Java 对象。漏洞成因&#xff1a;目标网站在解析 json 时&#xff0c;未对 json 内容进行验证&#xff0c;直接将 json 解析成 java 对象并执行&…

国产数字源表在压力传感器电阻测量上的应用

压力传感器分类压力传感器(Pressure Transducer)是能感受压力信号&#xff0c;并能按照一定的规律将压力信号转换成可用的输出的电信号的器件或装置,压力传感器通常由压力敏感元件和信号处理单元组成。常见的压力传感器有四种:应变式压力传感器、压阻式压力传感器、电容式压力传…

OpenMMLab 目标检测

OpenMMLab 目标检测1. 目标检测简介1.1 滑窗2. 基础知识2.1 边界框&#xff08;Bounding Box&#xff09;3. 两阶段目标检测算法3.1 多尺度检测技术4. 单阶段目标检测算法4.1 YOLO: You Only Look Once (2015)4.2 SSD: Single Shot MultiBox Detetor (2016)5. 无锚框目标检测算…

Nginx的搭建与核心配置

目录 一.Nginx是什么&#xff1f; 1.Nginx概述 2.Nginx模块与作用 3.Nginx三大作用&#xff1a;反向代理、负载均衡、动静分离 二.Nginx和Apache的差异 三.安装Nginx 1.编译安装 2.yum安装 四.Nginx的信号使用 五.Nginx的核心配置指令 1.访问状态统计配置 2.基于授…

非华为电脑安装华为电脑管家以及注意事项

非华为电脑安装华为电脑管家前言安装注意事项效果展示前言 非华为电脑是可以安装华为电脑管家的&#xff0c;不过部分功能可能不兼容。值得一提的是&#xff0c;超级终端、多屏协同、文件共享、远程控制等功能大部分电脑是可以使用的&#xff0c;本人在联想ThinkBook 15电脑上…

【CS144】Lab1总结

Lab1Lab汇总概述具体实现Lab汇总 概述 lab1要求实现一个字符串的装配器&#xff0c;用于将TCPTCPTCP接收方接收到的字节流拼接起来&#xff0c;并缓存一定量的乱序到达的字节&#xff0c;便于TCPTCPTCP接收方相关功能的实现。 具体实现 该装配器实现的重点是push_substring…

Java线程池使用与原理解析2(自定义线程池、合适的线程数量、线程池阻塞队列、线程拒绝策略)

在上篇我们学习了线程池各个参数的含义&#xff0c;线程池任务处理流程&#xff0c;使用线程池的好处等内容&#xff0c;本篇我们学习如何创建一个适合我们业务的线程池。为此&#xff0c;我们有必要先学习一下如何大概确定我们线程池核心线程数、怎么设置阻塞队列的类型与大小…

Malware Dev 04 - 隐匿之 ETW(Event Tracing for Windows)Bypass

写在最前 如果你是信息安全爱好者&#xff0c;如果你想考一些证书来提升自己的能力&#xff0c;那么欢迎大家来我的 Discord 频道 Northern Bay。邀请链接在这里&#xff1a; https://discord.gg/9XvvuFq9Wb我拥有 OSCP&#xff0c;OSEP&#xff0c;OSWE&#xff0c;OSED&…

使用leangoo领歌单团队敏捷开发项目管理

概述单团队敏捷开发主要是针对10人以下、只有一个Scrum团队的小型产品或项目的敏捷开发。对于小型团队来说&#xff0c;在Leangoo中创建一个单团队敏捷开发项目就可以很好地支持团队产品或项目的开发。适用场景适用于单个团队进行Scrum敏捷开发协作&#xff0c;Leangoo项目内也…

Linux - 磁盘存储管理 磁盘引入

# 我们要介绍下 磁盘管理&#xff0c; 那不妨先来看一张图来简单 引入 &#xff1a;这张图呢&#xff0c;是我们 Windows 上的磁盘管理的显示 。根据这幅图呢&#xff0c;提出一个问题 &#xff1a;>>> 这幅图磁盘管理所显示的内容&#xff0c;你能判断出 该电脑 有几…

【FMCW 04】测角-Angle FFT

在之前的文章中&#xff0c;我们已经详尽讨论过FMCW雷达测距和测速的原理&#xff0c;现在来讲最后一块内容&#xff0c;测角。测角对于硬件设备具有要求&#xff0c;即要求雷达具有多发多收结构&#xff0c;从而形成多个空间信道&#xff08;channel&#xff09;&#xff0c;我…

css选择器详解

简单选择器&#xff08;根据名称、id、类来选取元素&#xff09;组合器选择器&#xff08;根据它们之间的特定关系来选取元素&#xff09;伪类选择器&#xff08;根据特定状态选取元素&#xff09;伪元素选择器&#xff08;选取元素的一部分并设置其样式&#xff09;属性选择器…

第六讲:ambari-web 模块二次开发

上述图片为 Ambari 部署及操作 hdp 集群相关的部分界面截图。这些页面如果想调整的话,比如汉化,二次开发等,则可以修改 ambari-web 模块的源码来实现。 一、介绍 ambari-web 模块涉及到的界面有: HDP 集群部署向导已安装服务的仪表板、配置界面等主机列表及详细信息告警列…

【Opencv项目实战】图像的像素值反转

文章目录一、项目思路二、算法详解2.1、获取图像信息2.2、新建模板2.3、图像通道顺序三、项目实战&#xff1a;彩图的像素值反转&#xff08;方法一&#xff09;四、项目实战&#xff1a;彩图的像素值反转&#xff08;方法二&#xff09;五、项目实战&#xff1a;彩图转换为灰图…

Java中class文件的格式

常见的class文件格式如下图所示&#xff0c;下面我将对一下格式一一作出解释。 一、magic 该部分主要是对语言类型的规范&#xff0c;只有magic这个部分是CAFEBABE时才能被检测为Java语言&#xff0c;否则则不是。 二、minor version和major version minor version主要表示了…

【微信小程序-原生开发】实用教程16 - 查看详情(含页面跳转的传参方法--简单传参 vs 复杂传参)

需在实现列表的基础上开发 【微信小程序-原生开发】实用教程15 - 列表的排序、搜索&#xff08;含云数据库常用查询条件的使用方法&#xff0c;t-search 组件的使用&#xff09;_朝阳39的博客-CSDN博客 https://sunshinehu.blog.csdn.net/article/details/129356909 效果预览 …

【计算机网络】数据链路层可靠传输机制的三大协议:停止等待协议SW、后退N帧协议GBN、选择重传协议SR

一、可靠传输实现机制 1.停止等待协议SW case1、确认与否认 在发送端发送数据出现误码时&#xff0c;接收端回复一个NAK否认码&#xff0c;并要求发送端再发送一次。 case2、超时重传 接收端接收不到数据分组时&#xff0c;发送端就会一直处于等待接受端回复ACK或NAK的状态…

32 文件操作

目录 一、文件的概念 二、文件的分类&#xff08;分类依据&#xff1a;能否使用文本编辑器打开文件&#xff09; 1、文本文件 2、二进制文件 三、文件操作的步骤 1、打开文件&#xff1a;open()函数 2、打开文件的另一种写法&#xff08;推荐&#xff09;&#xff1a;with open…

21- PyTorch通过CNN实现手写数字识别 (PyTorch系列) (项目二十一)

项目要点 torch 版本: torch.__version__ # 1.13.1cpu 设置GPU: device torch.device(cuda:0 if torch.cuda.is_available() else cpu) train_ds datasets.MNIST(./, train True, transformtransformation, download True) # 数据导入 transformation transforms.…