iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Android ServiceManager的启动和工作原理
  • 899
分享到

Android ServiceManager的启动和工作原理

2024-04-02 19:04:59 899人浏览 独家记忆
摘要

目录ServiceManager启动 binder_openbinder_become_context_managerbinder_loop 系统服务注册 ServiceManage

ServiceManager启动

所有的系统服务都是需要在ServiceManager中进行注册的,而ServiceManager作为一个起始的服务,是通过init.rc来启动的。


  //system\core\rootdir\init.rc	
	//启动的服务,这里是用的服务名称。服务名称是在对应的rc文件中注册并启动的
  start servicemanager

具体的服务信息是在servicemanger.rc命名并定义的


//\frameworks\native\cmds\servicemanager\servicemanager.rc
service servicemanager /system/bin/servicemanager
  class core animation
  user system //说明以用户system身份运行
  group system readproc
  //说明servicemanager是系统中的关键服务,
  //关键服务是不会退出的,如果退出了,系统就会重启,当系统重启时就会启动用onrestart关键字修饰的进程,
  //比如zyGote、media、surfaceflinger等等。
  critical
  onrestart restart healthd
  onrestart restart zygote
  onrestart restart audiOServer
  onrestart restart media
  onrestart restart surfaceflinger
  onrestart restart inputflinger
  onrestart restart drm
  onrestart restart cameraserver
  onrestart restart keystore
  onrestart restart gatekeeperd
  onrestart restart thermalservice
  ..

servicemanager的入口函数在service_manager.c中


//frameworks\native\libs\binder\ndk\service_manager.cpp
int main(int arGC, char** argv)
{
	//binder_state结构体,用来存储binder的三个信息
  struct binder_state *bs;
	//打开binder驱动,并申请125k字节的内存空间
  bs = binder_open(driver, 128*1024);
  ...
	//将自己注册为Binder机制的管理者
  if (binder_become_context_manager(bs)) {
    ALOGE("cannot become context manager (%s)\n", strerror(errno));
    return -1;
  }
  ...
	//启动循环,等待并处理client端发来的请求
  binder_loop(bs, svcmgr_handler);

  return 0;
}

在main函数中主要做了3件事情。

  • 打开驱动,并申请了128k字节大小的内存空间
  • 将自己注册为Binder机制的管理者
  • 启动循环,等待并处理Client端发来的请求

binder_open


//frameworks\native\cmds\servicemanager\binder.c
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
  struct binder_state *bs;
  struct binder_version vers;
  //申请对应的内存空间 
  bs = malloc(sizeof(*bs));
	//打开binder设备文件,这种属于设备驱动的操作方法
  bs->fd = open(driver, O_RDWR | O_CLOEXEC);
	//通过ioctl获取binder的版本号
  if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
    (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
    fprintf(stderr,
        "binder: kernel driver version (%d) differs from user space version (%d)\n",
        vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);
    goto fail_open;
  }
  bs->mapsize = mapsize;
	//mmap进行内存映射,将Binder设备文件映射到进程的对应地址空间,地址空间大小为128k
	//映射之后,会将地址空间的起始地址和大小保存到结构体中,
  bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
  return bs;
}

binder_opende的主要功能是打开了Binder的驱动文件,并将文件进行了mmap映射,并将对应的地址空间保存到了结构体中。

binder_become_context_manager


//frameworks\native\cmds\servicemanager\binder.c
int binder_become_context_manager(struct binder_state *bs)
{
  return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

ioctl会调用Binder驱动的binder_ioctl函数,去注册成为管理者。

binder_loop

将servicemanger注册为Binder的上下文管理者后,它就是Binder机制的“大总管”了,它会在系统运行期间处理Client端的请求,因为请求的时间不确定性,这里采用了无限循环来实现。也就是binder_loop


//frameworks\native\cmds\servicemanager\binder.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
  int res;
  struct binder_write_read bwr;
  uint32_t readbuf[32];

  bwr.write_size = 0;
  bwr.write_consumed = 0;
  bwr.write_buffer = 0;
	//当前线程注册为Binder的指令
  readbuf[0] = BC_ENTER_LOOPER;
	//将BC_ENTER_LOOPER指令写入到Binder驱动,
	//将当前的ServiceManager线程注册为了一个Binder线程(注意ServiceManager本身也是一个Binder线程)。
	//注册为Binder线程之后,就可以处理进程间的请求了
  binder_write(bs, readbuf, sizeof(uint32_t));
	//不断的循环遍历
  for (;;) {
    bwr.read_size = sizeof(readbuf);
    bwr.read_consumed = 0;
    bwr.read_buffer = (uintptr_t) readbuf;
		//使用BINDER_WRITE_READ指令查询Binder驱动中是否有请求。
		//如果有请求,就走到下面的binder_parse部分处理,如果没有,当前的ServiceManager线程就会在Binder驱动中水命,等待新的进程间请求
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
		//走到这里说明有请求信息。将请求的信息用binder_parse来处理,处理方法是func
    res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
  }
}

servicemanager会先将自己注册为一个Binder线程。因为只有注册成为Binder服务之后才能接收进程间的请求。而注册为Binder服务的指令是BC_ENTER_LOOPER。然后通过**binder_write()**方法写入到binder驱动。


//frameworks\native\cmds\servicemanager\binder.c
int binder_write(struct binder_state *bs, void *data, size_t len)
{
  struct binder_write_read bwr;
  int res;

  bwr.write_size = len;
  bwr.write_consumed = 0;
  bwr.write_buffer = (uintptr_t) data;
  bwr.read_size = 0;
  bwr.read_consumed = 0;
  bwr.read_buffer = 0;
	//BINDER_WRITE_READ既可以读也可以写。关键在于read_size和write_size。
	//如果write_size>0。则是写。如果read_size>0则是读。
	//如果都大于0,则先写,再读
  res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
  if (res < 0) {
    fprintf(stderr,"binder_write: ioctl failed (%s)\n",
        strerror(errno));
  }
  return res;
}

系统服务注册

Android中,每个进程获取系统提供的各种系统服务(AMS,PMS,WMS等)都是需要通过ServiceManager才可以。而这些系统服务进行Binder注册,也需要获取ServiceManager服务才可以。在刚才我们讲过,ServiceManager会将自己也注册成为一个Binder服务。

这里我们以SurfaceFling获取ServiceManager服务为例来看一下是如何获取的。


//frameworks\native\services\surfaceflinger\main_surfaceflinger.cpp
#include <binder/IServiceManager.h>
int main(int, char**) {
  ....
  //获取一个SM对象,相当于是new BpServiceManager(new BpBinder(0))
  sp<IServiceManager> sm(defaultServiceManager());
	//向ServiceManager注册SurfaceFling服务
  sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
          IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
	//在SurfaceFlinger调用init方法的时候,会初始化Display的相关信息
  startDisplayService(); // dependency on SF getting reGIStered above
  ...
  return 0;
}

系统服务的注册过程主要有2点

  • 获取ServiceManager所对应的Binder对象。
  • 通过addService注册为系统服务。

ServiceManager的Binder对象获取

**defaultServiceManager()**方法就是用来获取ServiceManager服务的Binder对象。

defaultServiceManager


//frameworks\native\libs\binder\IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{
  if (gDefaultServiceManager != nullptr) return gDefaultServiceManager;

  {
    AutoMutex _l(gDefaultServiceManagerLock);
		
		
		

    while (gDefaultServiceManager == nullptr) {//如果不为空,表示设置过了,直接返回
    	//尝试不断的获取ServiceManager对象,如果获取不到,就sleep(1),
    	//这里之所以会获取不到,是因为ServiceManager和一些通过init.rc启动的服务是同时启动的,不能保证ServiceManager能够优先启动完成。
    	//所以会存在获取ServiceManager的时候获取不到。
      gDefaultServiceManager = interface_cast<IServiceManager>(
        ProcessState::self()->getContextObject(nullptr));
      if (gDefaultServiceManager == nullptr)
        sleep(1);
    }
  }

  return gDefaultServiceManager;
}

这里会直接调用**ProcessState::self()->getContextObject(nullptr)**来获取对应的服务。

  • ProcessState::self()->getContextObject(NULL): 返回的是一个 BpHwBinder。ServiceManager 的 desc 默认为0。
  • interface_cast 就是将 BpBinder 封装为 IServiceManager

ProcessState::self()


//system\libhwbinder\ProcessState.cpp
//返回一个ProcessState
sp<ProcessState> ProcessState::self()
{
  Mutex::Autolock _l(gProceSSMutex);
  if (gProcess != nullptr) {
    return gProcess;
  }
  gProcess = new ProcessState(kDefaultDriver);
  return gProcess;
}

这里会返回一个ProcessState对象。

getContextObject


//system\libhwbinder\ProcessState.cpp
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& )
{
  //传入的参数是handle。0,
  return getStrongProxyForHandle(0);
}

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
  handle_entry* e = lookupHandleLocked(handle);
  if (e != nullptr) {
    IBinder* b = e->binder;
    if (b == nullptr || !e->refs->attemptIncWeak(this)) {
			//如果b为空,那么创建一个BpHwBinder
      b = new BpHwBinder(handle);
      e->binder = b;
      if (b) e->refs = b->getWeakRefs();
      result = b;
    } else {
      result.force_set(b);
      e->refs->decWeak(this);
    }
  }

  return result;
}

当不存在的时候,这里会创建一个BpHwBinder对象。所以可以理解为最后我们返回的是一个BpBinder对象

这里,有一个设计思想:

  1. defaultServiceManager 首先实例化 BpBinder。
  2. interface_cast 就是 实例化 BpXXX,并将 BpBinder交给其管理。

Proxy 端的用户无法直接看到 BpBinder, BpBinder 由 BpXXX 持有.用户本身不关心 BpBinder的能力,只关心 IXXX 定义的 接口。所以这里很好的进行了封装。

回到前文的defaultServiceManger方法中,将返回值带入,可以得到


//注意,方法中传入的handle为0,所以BpBinder参数为0
gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));

interface_cast


//frameworks\native\include\binder\IInterface.h
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
  return INTERFACE::asInterface(obj);
}
//INTERFACE带入为IServiceManager之后,得到的代码为
inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj)
{
  return IServiceManager::asInterface(obj);//静态方法所以直接调用
}

调用IServiceManager接口的成员函数asInterface,将一个句柄值为0的Binder代理对象封装为一个ServiceManger代理对象。将一个句柄值为0的Binder代理对象封装为一个ServiceManger代理对象。

这里 IServiceManager接口的成员函数asInterface是通过宏IMPLEMENT_META_INTERFACE实现,如下所示:


#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)            
  DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)  
#endif

#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)\
  ::android::sp<I##INTERFACE> I##INTERFACE::asInterface(       \
      const ::android::sp<::android::IBinder>& obj)        \
  {                                  \
    android::sp<I##INTERFACE> intr;                \
    if (obj != nullptr) {                      \
      intr = static_cast<I##INTERFACE*>(             \
        obj->queryLocalInterface(                \
            I##INTERFACE::descriptor).get());        \
      if (intr == nullptr) {                   \
        intr = new Bp##INTERFACE(obj);             \
      }                              \
    }                                \
    return intr;                          \
  }                                  \

带入IServiceManager之后的代码为:


android::sp<IServiceManager> IIServiceManager::asInterface(const android::sp<android::IBinder>& obj)                 
{                                           
	android::sp<IServiceManager> intr;                          
	if (obj != NULL) {                                   
		intr = static_cast<IIServiceManager*>(                         
          obj->queryLocalInterface(IServiceManager::descriptor).get());//返回NULL
		if (intr == NULL) {        
			intr = new BpServiceManager(obj); //创建了ServiceManager代理对象           
		}                     
	}
	return intr;                 
} 

到这里为止,我们创建了一个BpIServiceManager对象,并将他的接口IServiceManager返回给了调用者。

整体的逻辑可以理解为:new BpServiceManager(new BpBinder())。当然了,这只是简化之后的代码,其内部复杂的逻辑现在可以暂不考虑。整体流程如下:

添加Service

客户端请求

当获取到ServiceManager服务之后,就可以使用addService方法来进行服务的注册了。在获取服务的时候,最终返回的是BpServiceManager对象,所以这里我们可以直接找到对应的添加服务方法


  virtual status_t addService(const String16& name, const sp<IBinder>& service,
                bool allowIsolated, int dumpsysPriority) {
    Parcel data, reply;
    data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
    data.writeString16(name);
    data.writeStrongBinder(service);
    data.writeInt32(allowIsolated ? 1 : 0);
    data.writeInt32(dumpsysPriority);
    status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
    return err == NO_ERROR ? reply.readExceptionCode() : err;
  }

BpServiceManager的构造函数传入的了BpBinder对象,这里的remote()方法其实就是BpBinder对象。


status_t BpBinder::transact(
  uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
  //如果binder已经died,则不会返回数据
  if (mAlive) {
    ...
		//调用IPCThreadState的transact方法。
    status_t status = IPCThreadState::self()->transact(
      mHandle, code, data, reply, flags);
    if (status == DEAD_OBJECT) mAlive = 0;
    return status;
  }
  return DEAD_OBJECT;
}

这里调用了IPCThreadState的transact方法将对应的数据写入到了Binder驱动了。当Binder驱动接收到注册服务的信息的时候,就会将对应的服务注册到ServiceManager中。我们可以看下传递的参数:

  • mHandle:0,表示要处理该请求的进程号,ServiceManager在注册的时候,其对应的进程号是0。所以处理请求的也就是ServiceManager进程。
  • code:参数是ADD_SERVICE_TRANSACTION。
  • data:包含了要添加的进程相关信息:包括名称、是否单独运行等等相关信息

ServiceManager处理请求

当客户端发送请求之后,我们的ServiceManger就可以接收到消息,并且进行消息的处理了。在ServiceManager的启动中我们了解到,当ServiceManger启动之后,会调用binder_looper来不断的循环,检测是否接收到对应的数据信息。

这个功能是在binder_loop()方法的入参中的svcmgr_handler来实现的。


//frameworks\native\libs\binder\ndk\service_manager.cpp
int main(int argc, char** argv){
	...
	//启动循环,等待并处理client端发来的请求
  binder_loop(bs, svcmgr_handler);
  ...
}

svcmgr_handler就是我们具体的请求处理方法。


//frameworks\native\cmds\servicemanager\service_manager.c
int svcmgr_handler(struct binder_state *bs,
          struct binder_transaction_data_secctx *txn_secctx,
          struct binder_io *msg,
          struct binder_io *reply)
{
  ...
  struct binder_transaction_data *txn = &txn_secctx->transaction_data;
  ...	
	//根据传输的不同类型来进行处理。
  switch(txn->code) {
    case SVC_MGR_ADD_SERVICE://添加服务
      //进行服务的添加
      do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, dumpsys_priority,
                txn->sender_pid, (const char*) txn_secctx->secctx)
      ... 
}

  
int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle,
          uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid, const char* sid) {
  struct svcinfo *si;

  //ALOGI("add_service('%s',%x,%s) uid=%d\n", str8(s, len), handle,
  //    allow_isolated ? "allow_isolated" : "!allow_isolated", uid);
  //服务的名称长度不能超过127字节
  if (!handle || (len == 0) || (len > 127))
    return -1;
	//最终调用selinux_check_access方法,会进行权限的检测,检查服务是否有进行服务注册
  if (!svc_can_register(s, len, spid, sid, uid)) {
    ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
       str8(s, len), handle, uid);
    return -1;
  }
	//查询是否已经有包含了name的svcinfo
  si = find_svc(s, len);
  if (si) {
    if (si->handle) {
      ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
         str8(s, len), handle, uid);
			//已经注册了,释放相应的服务
      svcinfo_death(bs, si);
    }
		//更新服务的handle
    si->handle = handle;
  } else {
		//申请内存
    si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
    si->handle = handle;
    si->len = len;
    memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
    si->name[len] = '\0';
    si->death.func = (void*) svcinfo_death;
    si->death.ptr = si;
    si->allow_isolated = allow_isolated;
    si->dumpsys_priority = dumpsys_priority;
		//将其注册到服务列表svclist中,这里使用的链表来保存数据
    si->next = svclist;
    svclist = si;
  }
	//以handle为目标,发送BC_ACQUIRE指令。
  binder_acquire(bs, handle);
	//以handle为目标,发送BC_REQUEST_DEATH_NOTIFICATION指令。
  binder_link_to_death(bs, handle, &si->death);
  return 0;
}

当拿到请求信息之后,ServiceManager会生成对应的svcinfo对象,将其保存到服务列表svclist中。

整体流程如下:

我们也可以从另一个维度去看看Binder的具体

系统服务获取

对于Servie服务的获取,其实也是答题思路也是相同的。显示获取ServiceManager的Binder对象,然后服务端发送获取某项服务的请求,ServiceManager来进行处理。

这里我们只看一下ServiceManager接收到服务获取的处理机制。也是在**svcmgr_handler()**中。


//frameworks\native\cmds\servicemanager\service_manager.c
int svcmgr_handler(struct binder_state *bs,
          struct binder_transaction_data_secctx *txn_secctx,
          struct binder_io *msg,
          struct binder_io *reply)
{
  ...
  struct binder_transaction_data *txn = &txn_secctx->transaction_data;
  ...	
	//根据传输的不同类型来进行处理。
  switch(txn->code) {
    case SVC_MGR_GET_SERVICE://获取服务
    case SVC_MGR_CHECK_SERVICE:
      s = bio_get_string16(msg, &len);
      if (s == NULL) {
        return -1;
      }
      //根据pid,uid来获取服务对应的handle值
      handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid,
                   (const char*) txn_secctx->secctx);
      if (!handle)
        break;
      //返回服务对应的handle
      bio_put_ref(reply, handle);
      return 0;
}

这里主要做了2个操作:

  1. 从服务列表中获取到对应的服务的handle
  2. 将handle写入到要返回的reply数据中。

do_find_service


//frameworks\native\cmds\servicemanager\service_manager.c 
//获取对应的服务
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid, const char* sid)
{
	//获取对应的服务
  struct svcinfo *si = find_svc(s, len);

  if (!si || !si->handle) {
    return 0;
  }

  if (!si->allow_isolated) {
    // If this service doesn't allow access from isolated processes,
    // then check the uid to see if it is isolated.
    uid_t appid = uid % AID_USER;
		//检查服务是否是允许孤立于进程而单独存在的
    if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {
      return 0;
    }
  }
	//检测是否有selinx权限。
  if (!svc_can_find(s, len, spid, sid, uid)) {
    return 0;
  }
	//返回服务的handle
  return si->handle;
}

bio_put_ref

当获取到服务之后handle之后,会调用**bio_put_ref()**方法将服务对应的handle写入到返回的数据中。


//frameworks\native\cmds\servicemanager\service_manager.c
void bio_put_ref(struct binder_io *bio, uint32_t handle)
{
  struct flat_binder_object *obj;
	//申请对应的地址空间
  if (handle)
    obj = bio_alloc_obj(bio);
  else
    obj = bio_alloc(bio, sizeof(*obj));

  if (!obj)
    return;

  obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
	//类型是BINDER_TYPE_HANDLE
  obj->hdr.type = BINDER_TYPE_HANDLE;
	//记录handle
  obj->handle = handle;
  obj->cookie = 0;
}

对于服务的获取,肯定是需要将reply的数据写回到请求服务的进程的。这时候就需要回到我们在binder_loop()函数了。在该函数中,存在一个binder_parse(),在这个方法里面会处理请求信息,并将reply信息通过binder驱动发送给客户端。

binder_parse


//frameworks\native\cmds\servicemanager\binder.c
int binder_parse(struct binder_state *bs, struct binder_io *bio,
         uintptr_t ptr, size_t size, binder_handler func)
{
  ...
    case BR_TRANSACTION: {
      ...
				//调用func函数
        res = func(bs, txn, &msg, &reply);
        if (txn->flags & TF_ONE_WAY) {
          binder_free_buffer(bs, txn->data.ptr.buffer);
        } else {
        	//发送协议指令给Binder驱动,向Client端发送reply
          binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
        }
      ...
  return r;
}

当调用了func函数,有对应返回信息之后,会通过binder_send_reply()方法,将reply数据信息发送给client端。


void binder_send_reply(struct binder_state *bs,
            struct binder_io *reply,
            binder_uintptr_t buffer_to_free,
            int status)
{
  struct {
    uint32_t cmd_free;
    binder_uintptr_t buffer;
    uint32_t cmd_reply;
    struct binder_transaction_data txn;
  } __attribute__((packed)) data;

  data.cmd_free = BC_FREE_BUFFER;
  data.buffer = buffer_to_free;
	//返回指令
  data.cmd_reply = BC_REPLY;
  data.txn.target.ptr = 0;
  data.txn.cookie = 0;
  data.txn.code = 0;
  if (status) {
    data.txn.flags = TF_STATUS_CODE;
    data.txn.data_size = sizeof(int);
    data.txn.offsets_size = 0;
    data.txn.data.ptr.buffer = (uintptr_t)&status;
    data.txn.data.ptr.offsets = 0;
  } else {//svcmgr_handler执行成功,将reply数据组装到txn中
    data.txn.flags = 0;
    data.txn.data_size = reply->data - reply->data0;
    data.txn.offsets_size = ((char*) reply->offs) - ((char*) reply->offs0);
    data.txn.data.ptr.buffer = (uintptr_t)reply->data0;
    data.txn.data.ptr.offsets = (uintptr_t)reply->offs0;
  }
	//发送数据
  binder_write(bs, &data, sizeof(data));
}

总结

ServiceManager是一个守护进程,负责管理系统中的所有服务信息。通过一个链表来保存了所有注册过的信息。而且其本身也是一个服务,在通过Binder驱动将其注册为守护进程之后,会将自己也注册为一个服务,供其他服务调用。

以上就是Android ServiceManager的启动和工作原理的详细内容,更多关于Android ServiceManager的资料请关注编程网其它相关文章!

--结束END--

本文标题: Android ServiceManager的启动和工作原理

本文链接: https://www.lsjlt.com/news/122576.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android ServiceManager的启动和工作原理
    目录ServiceManager启动 binder_openbinder_become_context_managerbinder_loop 系统服务注册 ServiceManage...
    99+
    2024-04-02
  • Android中SurfaceFlinger工作原理
    概念 SurfaceFlinger是一个系统服务,如:audioflinger,audiopolicyservice等等,系统的主要服务通过这个文章进行了解,Android的系统服务...
    99+
    2024-04-02
  • 浅谈Android中AsyncTask的工作原理
    目录概述AsyncTask使用方法AsyncTask的4个核心方法AsyncTask的工作原理概述 实际上,AsyncTask内部是封装了Thread和Handler。虽然Async...
    99+
    2024-04-02
  • Android中AsyncTask的工作原理是什么
    这篇文章给大家分享的是有关Android中AsyncTask的工作原理是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述实际上,AsyncTask内部是封装了Thread和Handler。虽然AsyncTa...
    99+
    2023-06-15
  • Android中的SurfaceFlinger工作原理是什么
    这篇文章将为大家详细讲解有关Android中的SurfaceFlinger工作原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。概念SurfaceFlinger是一个系统服务,如:au...
    99+
    2023-06-22
  • 详解Android JetPack之LiveData的工作原理
    目录前言 介绍 原理分析 前言 本篇文章主要讲解LiveData工作的原理,如果还不知道LiveData如何用的话,请参考官方文档。 LiveData的讲解涉及到了Lifecycl...
    99+
    2024-04-02
  • android定时器的工作原理是什么
    Android定时器的工作原理是通过使用Java中的Timer或者Handler类来实现的。1. Timer类:Timer类是Jav...
    99+
    2023-08-29
    android
  • Springboot启动原理和自动配置原理解析
    目录启动原理SpringApplication1、初始化2、调用run方法自动配置原理放本地文件夹都快吃土了,准备清理文件夹,关于Springboot的! 启动原理 @SpringB...
    99+
    2023-05-17
    Springboot启动原理和自动配置 Springboot自动配置 Springboot启动
  • cookie和session的工作原理和区别
    本篇内容主要讲解“cookie和session的工作原理和区别”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“cookie和session的工作原理和区别”吧!为...
    99+
    2024-04-02
  • CSS Hack的工作原理和用法
    本篇内容介绍了“CSS Hack的工作原理和用法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!你对CSS ...
    99+
    2024-04-02
  • linux下du和df的工作原理
    本篇内容主要讲解“linux下du和df的工作原理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“linux下du和df的工作原理”吧!1.原理介绍1 .1du的工作原理du命令会对待统计文件逐个...
    99+
    2023-06-05
  • 动态代理ip的工作原理是什么
    这篇文章主要讲解了“动态代理ip的工作原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“动态代理ip的工作原理是什么”吧!所以它要求对HTTP进行请求和响应模拟。基于以下图像直观地展示...
    99+
    2023-06-25
  • jsonp的工作原理
    JavaScript是一种在Web开发中经常使用的前端动态脚本技术。在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略)。 这一策略对于JavaScript代码能够访问的页面内容做了...
    99+
    2023-09-01
    javascript ajax php
  • nginx作用和工作原理是什么
    nginx是一种高性能的开源Web服务器和反向代理服务器。它可以用于处理静态和动态的HTTP、HTTPS、SMTP、POP3和IMA...
    99+
    2023-10-07
    nginx
  • 浅谈Vue3中key的作用和工作原理
    这个key属性有什么作用呢?我们先来看一下官方的解释: kekey属性主要用在Vue的虚拟DOM diff算法中,在新旧nodes对比时辨识Vnodes; 如果不使...
    99+
    2024-04-02
  • Cookie的工作原理和应用详解
    目录1. Cookie 原理1.1 Cookie 背景信息1.2 Cookie 工作原理1.3 Cookie 创建、获取、修改1.4 Cookie 共享范围1.5 Cookie 生命...
    99+
    2024-04-02
  • CSS hack的工作原理
    本篇内容主要讲解“CSS hack的工作原理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“CSS hack的工作原理”吧!CSS hack简介CSS hack由...
    99+
    2024-04-02
  • MyBatis中动态SQL的工作原理是什么
    MyBatis中动态SQL的工作原理是利用XML配置文件中的各种标签和属性来动态构建SQL语句。通过在XML配置文件中使用if、ch...
    99+
    2024-04-23
    MyBatis
  • MySQL Mydumper的工作原理
    这篇文章主要讲解了“MySQL Mydumper的工作原理”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“MySQL Mydumper的工作原理”吧! ...
    99+
    2024-04-02
  • vue-router的工作原理
    本篇内容主要讲解“vue-router的工作原理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vue-router的工作原理”吧!单页面应用的工作原理我理解的单...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作