【我的AOSP第一课】Android bootanim 的启动

安卓在开机后,会加载一个开机动画,本文将介绍开机动画是如何被加载的

安卓版本 API版本
Android 8.1 API 27

Init 初始化

众所周知,一个软件总归是有一个入口的,基于AOSP的架构,Android系统的启动入口一定也是Linux系统的启动入口。在 Linux 内核加载完成后,会启动用户空间的第一个进程 ------ init 进程(PID 为 1),该进程的启动位置为 system/core/init/init.cpp中的main方法。

在上一文中,我们认识了rc文件的主要构成,图中可以看到,main函数创建了Service、Action以及Import的解析器,并加载了init.rcsystem/ect/init/*vendor/ect/init/*odm/etc/init/*下的 rc文件。

如果解析到Action会将其加入ActionManager,解析到Service会将其加入ServiceManager

其中,init.rc的位置:

其中,和开机动画有关的rc文件就位于system/ect/init/

rc 复制代码
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    writepid /dev/stune/top-app/tasks

根据上一文的内容,我们可以这样描述该文件:

指令 说明
service bootanim /system/bin/bootanimation 定义bootanim服务,对应程序为/system/bin/bootanimation
class core animation 该服务归类为 coreanimation 两个类。当系统启动这些类时,会尝试启动该服务
user graphics 指定运行该服务的用户为graphics,该用户拥有访问图形缓冲区的权限
group graphics audio 指定运行该服务的用户组。除了图形权限,还赋予了 audio 权限
disabled 非常关键 。表示该服务不会在类启动时自动运行。它必须由其他进程手动启动
oneshot 表示该服务只运行一次
writepid /dev/stune/top-app/tasks 将该进程的 PID 写入文件/dev/stune/top-app/tasks

所以说,此时开机动画相关的服务已经定义好了,但处于禁用状态,需要重新启用 ;根据前文的内容,我们已经了解到了两种启用方式enable (解除禁用,但不一定立即执行,除非该服务的class已经执行完毕)start (启动服务)。不过除了用rc文件的方式再启用,还有一种使用系统属性的方式ctl.start bootanim,可以在代码中的ServiceManager中看到。

cpp 复制代码
// The length of a service name should not exceed SERVICE_NAME_MAX. Starting
// a service is done by writing its name to the "ctl.start" system property
// and stopping a service is done by writing its name to "ctl.stop". If a
// service name is too long to fit in a property, you won't be able to start
// or stop it.

propertyService 初始化

在安卓中,system property 的修改也是靠对应的服务来完成的;比如,在解析rc文件前,可以看到 start_property_service();

cpp 复制代码
void start_property_service() {
    property_set("ro.property_service.version", "2");

    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr, sehandle);
    if (property_set_fd == -1) {
        PLOG(ERROR) << "start_property_service socket creation failed";
        exit(1);
    }

    listen(property_set_fd, 8);

    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

在这段代码中,我们创建了一个Unix Domain Socket,以供外部进程使用,其监听/dev/socket/property_service,其使用handle_property_set_fd处理请求

cpp 复制代码
static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */

    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
    if (s == -1) {
        return;
    }

    struct ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];

        if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
            !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }

        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;

        handle_property_set(socket, prop_value, prop_value, true);
        break;
      }

    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        if (!socket.RecvString(&name, &timeout_ms) ||
            !socket.RecvString(&value, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }

        handle_property_set(socket, name, value, false);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}

最终真正执行处理的是下面的函数

cpp 复制代码
static void handle_property_set(SocketConnection& socket,
                                const std::string& name,
                                const std::string& value,
                                bool legacy_protocol) {
  const char* cmd_name = legacy_protocol ? "PROP_MSG_SETPROP" : "PROP_MSG_SETPROP2";
  if (!is_legal_property_name(name)) {
    LOG(ERROR) << "sys_prop(" << cmd_name << "): illegal property name "" << name << """;
    socket.SendUint32(PROP_ERROR_INVALID_NAME);
    return;
  }

  struct ucred cr = socket.cred();
  char* source_ctx = nullptr;
  getpeercon(socket.socket(), &source_ctx);

  if (android::base::StartsWith(name, "ctl.")) {
    if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
      handle_control_message(name.c_str() + 4, value.c_str());
      if (!legacy_protocol) {
        socket.SendUint32(PROP_SUCCESS);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
                 << " service ctl [" << value << "]"
                 << " uid:" << cr.uid
                 << " gid:" << cr.gid
                 << " pid:" << cr.pid;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
      }
    }
  } else {
    if (check_mac_perms(name, source_ctx, &cr)) {
      uint32_t result = property_set(name, value);
      if (!legacy_protocol) {
        socket.SendUint32(result);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
      }
    }
  }

  freecon(source_ctx);
}

该方法中,主要的逻辑如红框下所示:

最终,会去根据name (name为service的名称,比如 bootanim),从ServiceManager 中查找对应的Service,根据 ctl.后的内容执行 startstoprestart

至此,/system/bin/bootanimation 正式开始执行。我们也可以使用setprop ctl.start bootanim,直接查看效果。

property_set

根据前面的介绍,我们得知,想要执行开机动画,一定有一个地方设置了系统属性 ctl.start bootanim。所以我们现在需要在 propertyService 里找到用于设置系统属性的方法,不过观察 init.cpp 中的 main 已经可以发现对应的方法了

搜索一下property_set("ctl.start", "bootanim"),发现在存在于frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp

所以说,很明显,surfaceflinger启动后,此时系统具备了图像渲染能力,随后由surfaceflinger修改系统属性,来触发bootanim的启动

番外

init进程中的property_set vs surfaceflinger进程中的property_set

相关推荐
shenshizhong2 小时前
Compose + Mvi 架构的玩android 项目,请尝鲜
android·架构·android jetpack
Chuck_Chan2 小时前
Launcher3模块化-组件化
android
xuyin12042 小时前
Android内存优化
android
jzlhll1232 小时前
android kotlinx.serialization用法和封装全解
android
龚子亦2 小时前
【Unity开发】安卓应用开发中,用户进行权限请求
android·unity·安卓权限
共享家95273 小时前
MySQL-基础查询(下)
android·mysql
查克陈Chuck3 小时前
Launcher3模块化-组件化
android·launcher开发
千里马学框架3 小时前
google官方文档:深入剖析ProtoLog原理及Winscope的查看方式
android·车载系统·framework·perfetto·系统开发·winscope
apihz3 小时前
获取当前北京时间的免费API接口教程
android