I. Android系统Binder机制之一(Service Manager篇)

本文转载整理自: http://my.unix-center.net/~Simon_fu/?p=875 


 

一、前言

Android虽然构建在Linux上面,但是在IPC(进程间)机制方面,没有利用Linux提供IPC机制,而是自己实现了一套轻量级的IPC机制——binder机制。并且Android Binder机制之上,Android框架提供了一套封装,可以实现对象代理(在本地进程中代理远程进程的对象)。本文简单分析一下Android Binder机制。

二、Binder情景分析

    一个IPC通讯我们可以理解成客户端-服务器模式,因此我们先在这里分析一下典型的Binder应用模式:

 

1、客户端通过某种方式(后文会详细介绍)得到服务器端的代理对象。从客户端角度看来代理对象和他的本地对象没有什么差别。它可以像其他本地对象一样调用其方法,访问其变量。 

2、客户端通过调用服务器代理对象的方法向服务器端发送请求。 

3、代理对象把用户请求通过Android内核(Linux内核)的Binder驱动发送到服务器进程。 

4、服务器进程处理用户请求,并通过Android内核(Linux内核)的Binder驱动返回处理结果给客户端的服务器代理对象。 

5、客户端收到服务器端的返回结果。

 

    如果你对COM或者Corba熟悉的话,以上的情景是否会让你联想到什么呢?没错!都是对象代理。以上的情景,在Android中经常会被用到。如果你还没有注意到这点儿,那么本文非常适合你。

三、Binder机制的组成

1、Binder驱动

    binder是内核中的一个字符驱动设备位于:/dev/binder。这个设备是Android系统IPC的核心部分,客户端的服务代理用来通过它向服务器(server)发送请求,服务器也是通过它把处理结果返回给客户端的服务代理对象。我们只需要知道它的功能就可以了,本文我们的重点不在这里,所以后面不会专门介绍这部分。如果想深入了解的话,请研究内核源码中的binder.c。

2、Service Manager

    负责管理服务。对应于第一步中,客户端需要向Service Manager来查询和获得所需要服务。服务器也需要向Service Manager注册自己提供的服务。可以看出Service Manager是服务的大管家。

3、服务(Server)

    需要强调的是这里服务是指的是System Server,而不是SDK server,请参考《(转)高焕堂——Android框架底层结构知多少?》关于两种Server的介绍(其实应该是三种,丢掉了init调用的server,在init.rc中配置)。

4、客户端

    一般是指Android系统上面的应用程序。它可以请求Server中的服务。

5、对象代理

    是指在客户端应用程序中生成的Server代理(proxy)。从应用程序角度看代理对象和本地对象没有差别,都可以调用其方法,方法都是同步的,并且返回相应的结果。

 

四、大内总管——Service Manager

    Android系统Binder机制的总管是Service Manager,所有的Server(System Server)都需要向他注册,应用程序需要向其查询相应的服务。可见其作用是多么的重要,所以本文首先介绍Service Manager。

    通过上面介绍我们知道Service Manager非常重要,责任重大。那么怎样才能成为Service Manager呢?是不是谁都可以成为Service Manager呢?怎样处理server的注册和应用程序的查询和获取服务呢?为了回答这些问题先查看,Android中Service Manager的源码,其源码位于:

frameworks\base\cmds\servicemanager\service_manager.c

我们发现了main函数,说明他自己就是一个进程,在init.rc中我们发现:

 

[java] view plaincopy
  1. <span style="color:#000000;">service servicemanager /system/bin/servicemanager  
  2.   
  3. user system  
  4.   
  5. critical  
  6.   
  7. onrestart restart zygote  
  8.   
  9. onrestart restart media  
  10.   
  11. </span>  


 

说明其是Android核心程序,开机就会自动运行。

    下面我们在研究一下它的代码,main函数很简单:

 

[java] view plaincopy
  1. <span style="color:#000000;">int main(int argc, char **argv)  
  2.   
  3. {  
  4.   
  5.     struct binder_state *bs;  
  6.   
  7.     void *svcmgr = BINDER_SERVICE_MANAGER;  
  8.   
  9.     bs = binder_open(128*1024);  
  10.   
  11.     if (binder_become_context_manager(bs)) {  
  12.   
  13.         LOGE("cannot become context manager (%s)\n", strerror(errno));  
  14.   
  15.         return -1;  
  16.   
  17.     }  
  18.   
  19.     svcmgr_handle = svcmgr;  
  20.   
  21.     binder_loop(bs, svcmgr_handler);  
  22.   
  23.     return 0;  
  24.   
  25. }  
  26.   
  27. </span>  


 

我们看到它先调用binder_open打开binder设备(/dev/binder),其次它调用了binder_become_context_manager函数,这个函数使他自己变为了“Server大总管”,其代码如下:

 

[java] view plaincopy
  1. <span style="color:#000000;">int binder_become_context_manager(struct binder_state *bs)  
  2.   
  3. {  
  4.   
  5.     return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);  
  6.   
  7. }  
  8.   
  9. </span>  


 

也就是通过ioctl向binder设备声明“我就是server大总管”。

    Service Manager作为一个Server大总管,本身也是一个server。既然是一个server就要时刻准备为客户端提供服务。最后Service Manager调用binder_loop进入到循环状态,并提供了一个回调函数,等待用户的请求。注意他的Service Manager的客户端既包括应用程序(查询和获取服务),也包括Server(注册服务)。

    Service Manager的客户怎样才能请求其服务呢?答案是上文我们提到的情景一样。客户需要在自己进程中创建一个服务器代理。现在没有地方去查询服务,那么它的客户怎样生成他的服务代理对象呢?答案是binder设备(/dev/binder)为每一个服务维护一个句柄,调用binder_become_context_manager函数变为“Server大总管”的服务,他的句柄永远是0,是一个“众所周知”的句柄,这样每个程序都可以通过binder机制在自己的进程空间中创建一个Service Manager代理对象了。其他的服务在binder设备在设备中的句柄是不定的,需要向“Server大总管”查询才能知道。

    现在我们需要研究Server怎样注册服务了,还是在其源码中,我们可以看到在其服务处理函数中(上文提到binder_loop函数注册给binder设备的回调函数svcmgr_handler)有如下代码:

 

    

[java] view plaincopy
  1. <span style="color:#000000;">case SVC_MGR_ADD_SERVICE:  
  2.   
  3.         s = bio_get_string16(msg, &len);  
  4.   
  5.         ptr = bio_get_ref(msg);  
  6.   
  7.         if (do_add_service(bs, s, len, ptr, txn->sender_euid))  
  8.   
  9.             return -1;  
  10.   
  11.         break;  
  12.   
  13. </span>  


有server向binder设备写入请求注册Service时,Service Manager的服务处理回调函数将会被调用。我们在仔细看看do_add_service函数的实现:

 

[java] view plaincopy
  1. <span style="color:#000000;">int do_add_service(struct binder_state *bs,  
  2.   
  3.                    uint16_t *s, unsigned len,  
  4.   
  5.                    void *ptr, unsigned uid)  
  6.   
  7. {  
  8.   
  9.     struct svcinfo *si;  
  10.   
  11. //    LOGI("add_service('%s',%p) uid=%d\n", str8(s), ptr, uid);  
  12.   
  13.   
  14.   
  15.   
  16.     if (!ptr || (len == 0) || (len > 127))  
  17.   
  18.         return -1;  
  19.   
  20.     if (!svc_can_register(uid, s)) {  
  21.   
  22.         LOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n",  
  23.   
  24.              str8(s), ptr, uid);  
  25.   
  26.         return -1;  
  27.   
  28.     }  
  29.   
  30.     si = find_svc(s, len);  
  31.   
  32.     if (si) {  
  33.   
  34.         if (si->ptr) {  
  35.   
  36.             LOGE("add_service('%s',%p) uid=%d - ALREADY REGISTERED\n",  
  37.   
  38.                  str8(s), ptr, uid);  
  39.   
  40.             return -1;  
  41.   
  42.         }  
  43.   
  44.         si->ptr = ptr;  
  45.   
  46.     } else {  
  47.   
  48.         si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));  
  49.   
  50.         if (!si) {  
  51.   
  52.             LOGE("add_service('%s',%p) uid=%d - OUT OF MEMORY\n",  
  53.   
  54.                  str8(s), ptr, uid);  
  55.   
  56.             return -1;  
  57.   
  58.         }  
  59.   
  60.         si->ptr = ptr;  
  61.   
  62.         si->len = len;  
  63.   
  64.         memcpy(si->name, s, (len + 1) * sizeof(uint16_t));  
  65.   
  66.         si->name[len] = '\0';  
  67.   
  68.         si->death.func = svcinfo_death;  
  69.   
  70.         si->death.ptr = si;  
  71.   
  72.         si->next = svclist;  
  73.   
  74.         svclist = si;  
  75.   
  76.     }  
  77.   
  78.     binder_acquire(bs, ptr);  
  79.   
  80.     binder_link_to_death(bs, ptr, &si->death);  
  81.   
  82.     return 0;  
  83.   
  84. }  
  85.   
  86. </span>  


 

我们看到首先检查是否有权限注册service,没权限就对不起了,出错返回;然后检查是否已经注册过,注册过的service将不能再次注册。然后构造一个svcinfo对象,并加入一个全局链表中svclist中。最后通知binder设备:有一个service注册进来。  si->ptr = ptr;这里的ptr应该就是注册的service的句柄。

    我们再来看看客户端怎样通过Service Manager获得Service,还是在服务处理函数中(上文提到binder_loop函数注册给binder设备的回调函数)有如下代码:

 

   

[java] view plaincopy
  1. <span style="color:#000000;"case SVC_MGR_GET_SERVICE:  
  2.   
  3.     case SVC_MGR_CHECK_SERVICE:  
  4.   
  5.         s = bio_get_string16(msg, &len);  
  6.   
  7.         ptr = do_find_service(bs, s, len);  
  8.   
  9.         if (!ptr)  
  10.   
  11.             break;  
  12.   
  13.         bio_put_ref(reply, ptr);  
  14.   
  15.         return 0;  
  16.   
  17. </span>  


 

    我们可以看到通过do_find_service查找Service如果查找到的话,写入reply中返回给客户端。

do_find_service函数也在service_manager.c文件中,其源码如下:

 

[java] view plaincopy
  1. <span style="font-size:12px;color:#000000;">void *do_find_service(struct binder_state *bs, uint16_t *s, unsigned len)  
  2.   
  3. {  
  4.   
  5.     struct svcinfo *si;  
  6.   
  7.     si = find_svc(s, len);  
  8.   
  9. //    LOGI("check_service('%s') ptr = %p\n", str8(s), si ? si->ptr : 0);  
  10.   
  11.     if (si && si->ptr) {  
  12.   
  13.         return si->ptr;  
  14.   
  15.     } else {  
  16.   
  17.         return 0;  
  18.   
  19.     }  
  20.   
  21. }  
  22.   
  23. </span>  


do_find_service又调用了find_svc函数,其源码如下:

[java] view plaincopy
  1. <span style="color:#000000;">struct svcinfo *svclist = 0;  
  2.   
  3. struct svcinfo *find_svc(uint16_t *s16, unsigned len)  
  4.   
  5. {  
  6.   
  7.     struct svcinfo *si;  
  8.   
  9.     for (si = svclist; si; si = si->next) {  
  10.   
  11.         if ((len == si->len) &&  
  12.   
  13.             !memcmp(s16, si->name, len * sizeof(uint16_t))) {  
  14.   
  15.             return si;  
  16.   
  17.         }  
  18.   
  19.     }  
  20.   
  21.     return 0;  
  22.   
  23. }  
  24. </span>  

 

II. Android系统Binder机制之二(服务代理对象 上篇)

    上文《Android系统Binder机制之一(Service Manager篇)》我们学习了Service Manager在Android Binder中的作用——服务(Service)注册,服务(Service)查询的功能。本文我们一起学习服务(Service)在客户端中的代理机制。重点介绍其核心对象BpBinder。 

一、服务代理的基本原理

    如下是客户端请求service服务的场景:

 

1、首先客户端向Service manager查找相应的Service。关于此,上文《Android系统Binder机制之一(Service Manager篇)》已有比较详细的介绍。注意客户端和Service可能在两个不同的进程中。 

2、Android系统将会为客户端进程中创建一个Service代理。关于此,下文将详细介绍。 

3、客户端视角只有Service代理,他所有对Service的请求都发往Service代理,然后有Service代理把用户请求转发给Service本身。Service处理完成之后,把结果返回给Service代理,Service代理负责把处理结果返回给客户端。注意客户端对Service代理的调用都是同步调用(调用挂住,直到调用返回为止),这样客户端视角来看调用远端Service的服务和调用本地的函数没有任何区别。这也是Binder机制的一个特点。

二、Android进程环境——ProcessState类型和对象

    在Android系统中任进程何,要想使用Binder机制,必须要创建一个ProcessState对象和IPCThreadState对象。当然如果Android进程不使用Binder机制,那么这两个对象是不用创建的。这种情况很少见,因为Binder机制是整个Android框架的基础,可以说影响到Android方方面面。所以说了解这两个对象的作用非常重要。

    台湾的高焕堂先生一片文章《认识ProcessState类型和对象》,可以在我的博文《(转)高焕堂——Android框架底层结构知多少?》中找到。可以先通过这篇文章对ProcessState进行一个大概了解。

    ProcessState是一个singleton类型,一个进程只能创建一个他的对象。他的作用是维护当前进程中所有Service代理(BpBinder对象)。一个客户端进程可能需要多个Service的服务,这样可能会创建多个Service代理(BpBinder对象),客户端进程中的ProcessState对象将会负责维护这些Service代理。

我们研究一下创建一个Service代理的代码:

[plain] view plaincopy
  1. frameworks\base\libs\binder\ProcessState.cpp中  
  2.   
  3.   
  4.   
  5. ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle)  
  6.   
  7. {  
  8.   
  9.     const size_t N=mHandleToObject.size();  
  10.   
  11.     if (N <= (size_t)handle) {  
  12.   
  13.         handle_entry e;  
  14.   
  15.         e.binder = NULL;  
  16.   
  17.         e.refs = NULL;  
  18.   
  19.         status_t err = mHandleToObject.insertAt(e, N, handle+1-N);  
  20.   
  21.         if (err < NO_ERROR) return NULL;  
  22.   
  23.     }  
  24.   
  25.     return &mHandleToObject.editItemAt(handle);  
  26.   
  27. }  
  28.   
  29.   
  30.   
  31.   
  32.   
  33. sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)  
  34.   
  35. {  
  36.   
  37.     sp<IBinder> result;  
  38.   
  39.     AutoMutex _l(mLock);  
  40.   
  41.     handle_entry* e = lookupHandleLocked(handle);  
  42.   
  43.     if (e != NULL) {  
  44.   
  45.         // We need to create a new BpBinder if there isn't currently one, OR we  
  46.   
  47.         // are unable to acquire a weak reference on this current one.  See comment  
  48.   
  49.         // in getWeakProxyForHandle() for more info about this.  
  50.   
  51.         IBinder* b = e->binder;  
  52.   
  53.         if (b == NULL || !e->refs->attemptIncWeak(this)) {  
  54.   
  55.             b = new BpBinder(handle);   
  56.   
  57.             e->binder = b;  
  58.   
  59.             if (b) e->refs = b->getWeakRefs();  
  60.   
  61.             result = b;  
  62.   
  63.         } else {  
  64.   
  65.             // This little bit of nastyness is to allow us to add a primary  
  66.   
  67.             // reference to the remote proxy when this team doesn't have one  
  68.   
  69.             // but another team is sending the handle to us.  
  70.   
  71.             result.force_set(b);  
  72.   
  73.             e->refs->decWeak(this);  
  74.   
  75.         }  
  76.   
  77.     }  
  78.   
  79.     return result;  
  80.   
  81. }  


 

 getWeakProxyForHandle函数的作用是根据一个binder句柄(上文《Android系统的Binder机制之一——Service Manager》提到Binder驱动为每个Service维护一个Binder句柄,客户端可以通过句柄来和Service通讯)创建对应的Service代理对象。

    当前进程首先调用lookupHandleLocked函数去查看当前进程维护的Service代理对象的列表,该待创建Service代理对象是否已经在当前进程中创建,如果已经创建过了,则直接返回其引用就可以了。否则将会在Service代理对象的列表增加相应的位置(注意系统为了减少分配开销,可能会多分配一些空间,策略是“以空间换时间”),保存将要创建的代理对象。具体代码请参考lookupHandleLocked的源码。

    后面代码就好理解了,如果Service代理对象已经创建过了且当前弱引用数大于0(该值是通过attemptIncWeak返回),直接引用就行了。否则,则需要创建一个新的Service代理对象。

 

三、Android进程环境——IPCThreadState类型和对象

    Android进程中可以创建一个ProcessState对象,该对象创建过程中会打开/dev/binder设备,并保存其句柄。并初始化该设备。代码如下:

 

[plain] view plaincopy
  1. static int open_driver()  
  2.   
  3. {  
  4.   
  5.     int fd = open("/dev/binder", O_RDWR);  
  6.   
  7.     if (fd >= 0) {  
  8.   
  9.         fcntl(fd, F_SETFD, FD_CLOEXEC);  
  10.   
  11.         int vers;  
  12.   
  13.         status_t result = ioctl(fd, BINDER_VERSION, &vers);  
  14.   
  15.         if (result == -1) {  
  16.   
  17.             LOGE("Binder ioctl to obtain version failed: %s", strerror(errno));  
  18.   
  19.             close(fd);  
  20.   
  21.             fd = -1;  
  22.   
  23.         }  
  24.   
  25.         if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {  
  26.   
  27.             LOGE("Binder driver protocol does not match user space protocol!");  
  28.   
  29.             close(fd);  
  30.   
  31.             fd = -1;  
  32.   
  33.         }  
  34.   
  35.         size_t maxThreads = 15;  
  36.   
  37.         result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);  
  38.   
  39.         if (result == -1) {  
  40.   
  41.             LOGE("Binder ioctl to set max threads failed: %s", strerror(errno));  
  42.   
  43.         }  
  44.   
  45.     } else {  
  46.   
  47.         LOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));  
  48.   
  49.     }  
  50.   
  51.     return fd;  
  52.   
  53. }  
  54.   
  55. ProcessState::ProcessState()  
  56.   
  57.     : mDriverFD(open_driver())  
  58.   
  59.     , mVMStart(MAP_FAILED)  
  60.   
  61.     , mManagesContexts(false)  
  62.   
  63.     , mBinderContextCheckFunc(NULL)  
  64.   
  65.     , mBinderContextUserData(NULL)  
  66.   
  67.     , mThreadPoolStarted(false)  
  68.   
  69.     , mThreadPoolSeq(1)  
  70.   
  71. {  
  72.   
  73.     if (mDriverFD >= 0) {  
  74.   
  75.         // XXX Ideally, there should be a specific define for whether we  
  76.   
  77.         // have mmap (or whether we could possibly have the kernel module  
  78.   
  79.         // availabla).  
  80.   
  81. #if !defined(HAVE_WIN32_IPC)  
  82.   
  83.         // mmap the binder, providing a chunk of virtual address space to receive transactions.  
  84.   
  85.         mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);  
  86.   
  87.         if (mVMStart == MAP_FAILED) {  
  88.   
  89.             // *sigh*  
  90.   
  91.             LOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");  
  92.   
  93.             close(mDriverFD);  
  94.   
  95.             mDriverFD = -1;  
  96.   
  97.         }  
  98.   
  99. #else  
  100.   
  101.         mDriverFD = -1;  
  102.   
  103. #endif  
  104.   
  105.     }  
  106.   
  107.     LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");  
  108.   
  109. }  


 

mDriverFD保存了/dev/binder设备的句柄,如果仔细查看ProcessState的源码会发现这个句柄不会被ProcessState对象使用。那么保存这个句柄做什么用呢?被谁使用呢?非常奇怪。经过查看ProcessState的头文件,发现如下代码:

[plain] view plaincopy
  1. friend class IPCThreadState;  


    发现IPCThreadState是ProcessState的友元类,那么就可以怀疑这个句柄是被IPCThreadState的对象使用的,然后查看代码发现确实如此。

    IPCThreadState也是一个singleton的类型,一个进程中也只能有一个这样的对象。它的源码也位于frameworks\base\libs\binder目录下。我们查看一下它的talkWithDriver函数:        

[plain] view plaincopy
  1. if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)  
  2.   
  3.             err = NO_ERROR;  
  4.   
  5.         else  
  6.   
  7.             err = -errno;  


 IPCThreadState通过ioctl系统调用对ProcessState打开的句柄进行读写。这样我们也可以看出IPCThreadState对象的作用:

 

1、维护当前进程中所有对/dev/binder的读写。换句话说当前进程通过binder机制进行跨进程调用都是通过IPCThreadState对象来完成的。

2、IPCThreadState也可以理解成/dev/binder设备的封装,用户可以不直接通过ioctl来操作binder设备,都通过IPCThreadState对象来代理即可。

不管是客户端进程和Service进程都是需要用IPCThreadState来和binder设备通讯的。

如果是客户端进程则通过服务代理BpBinder(IBinder的一个子类)对象的transact函数调用IPCThreadState的transact函数,该函数作用就是把客户端的请求写入binder设备另一端的Service进程,具体请参阅IPCThreadState类的transact方法。

如果是Service进程,当他完成初始化工作之后,他需要他们需要进入循环状态等待客户端的请求,Service进程调用它的IPCThreadState对象的joinThreadPool方法,开始轮询binder设备,等待客户端请求的到来,后面我们讨论Service时候会进一步讨论joinThreadPool方法。有兴趣的朋友可以先通过查看代码来了解joinThreadPool方法。

 

四、Service代理对象BpBinder

    上文关于ProcessState的介绍提到了,客户端进程创建的Service代理对象其实就是BpBinder对象。BpBinde其实是IBinder的一个子类。我们首先了解怎样创建BpBinder对象。

[plain] view plaincopy
  1. BpBinder::BpBinder(int32_t handle)  
  2.   
  3.     : mHandle(handle)  
  4.   
  5.     , mAlive(1)  
  6.   
  7.     , mObitsSent(0)  
  8.   
  9.     , mObituaries(NULL)  
  10.   
  11. {  
  12.   
  13.     LOGV("Creating BpBinder %p handle %d\n", this, mHandle);  
  14.   
  15.   
  16.   
  17.   
  18.     extendObjectLifetime(OBJECT_LIFETIME_WEAK);  
  19.   
  20.     IPCThreadState::self()->incWeakHandle(handle);  
  21.   
  22. }  


我们可以看出首先是通过IPCThreadState读写binder设备增加中相应binder句柄上的Service的引用计数。然后本地保存代理Service的binder句柄mHandle。

    客户进程对Service的请求都通过调用BpBinder的transact方法来完成:

IBinder的transact函数源码如下

 

[plain] view plaincopy
  1. status_t BpBinder::transact(  
  2.   
  3.     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)  
  4.   
  5. {  
  6.   
  7.     // Once a binder has died, it will never come back to life.  
  8.   
  9.     if (mAlive) {  
  10.   
  11.         status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);  
  12.   
  13.         if (status == DEAD_OBJECT) mAlive = 0;  
  14.   
  15.         return status;  
  16.   
  17.     }  
  18.   
  19.     return DEAD_OBJECT;  
  20.   
  21. }  


 

五、Android系统对Binder机制的抽象——IBinder

    上面我们讲解了Binder机制比较底层的机制,这些机制直接用还是比较麻烦的,比如使用binder设备的ioctl,需要记住很多ioctl的代码。

    Android为了是Binder机制容易使用,对Binder机制进行了抽象,定义了IBinder接口,该接口在C/C++和Java层都有定义。IBinder定义了一套使用Binder机制使用和实现客户程序和服务器的通讯协议。可以理解如下定义:

 

1、向Android注册的Service也必须是IBinder(继承扩展IBinder接口)对象。后续文章中我们讨论Service的时候我们会介绍到这方面的内容。

2、客户端得到Service代理对象也必须定义成IBinder(继承扩展IBinder接口)对象。这也是为什么BpBinder就是继承自IBinder。

3、客户端发送请求给服务器端,将Service代理对象IBinder接口的transact方法。

4、Android系统Binder机制将负责把用户的请求,调用Service对象IBinder接口的onTransact方法。具体实现我们将在以后介绍Service的时候讨论。

 

六、Service Manager代理对象

    我们知道Service Manager是Android Binder机制的大管家。所有需要通过Binder通讯的进程都需要先获得Service Manager的代理对象才能进行Binder通讯。Service Manager即在C/C++层面提供服务代理,又在Java层面提供服务代理,本文先介绍一下C/C++层面的服务代理,Java层面的服务代理将在后续文章中介绍。

    进程在C/C++层面上面,Android在Android命名空间中定义了一个全局的函数defaultServiceManager(定义在framework/base/libs/binder/IServiceManager.cpp),通过这个函数可以使进程在C/C++层面获得Service Manager的代理。我们先看一下该函数的定义:

 

[plain] view plaincopy
  1. sp<IServiceManager> defaultServiceManager()  
  2.   
  3. {  
  4.   
  5.     if (gDefaultServiceManager != NULL) return gDefaultServiceManager;  
  6.   
  7.     {  
  8.   
  9.         AutoMutex _l(gDefaultServiceManagerLock);  
  10.   
  11.         if (gDefaultServiceManager == NULL) {  
  12.   
  13.             gDefaultServiceManager = interface_cast<IServiceManager>(  
  14.   
  15.                 ProcessState::self()->getContextObject(NULL));  
  16.   
  17.         }  
  18.   
  19.     }  
  20.   
  21.     return gDefaultServiceManager;  
  22.   
  23. }  


我们可以看到defaultServiceManager是调用ProcessState对象的getContextObject方法获得Service Manager的getContextObject方法获得Service Manager代理对象。我们再看一下getContextObject函数的定义:

[plain] view plaincopy
  1. sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)  
  2.   
  3. {  
  4.   
  5.     return getStrongProxyForHandle(0);  
  6.   
  7. }  


    我们可以看出其实是调用我们上面描述过的getStrongProxyForHandle方法,并以句柄0为参数来获得Service Manager的代理对象。

    ProcessState::self()->getContextObject(NULL)返回一个IBinder对象,怎样把它转化成一个IServiceManager的对象呢?这就是模板函数interface_cast<IServiceManager>的作用了。调用的是IServiceManager.asInterface方法。IServiceManager的asInterface方法通过DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏来定义,详细情况请查看IServiceManager类的定义。IMPLEMENT_META_INTERFACE宏关于asInterface的定义如下:

[plain] view plaincopy
  1. #define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \  
  2.   
  3.     const android::String16 I##INTERFACE::descriptor(NAME);             \  
  4.   
  5.     const android::String16&                                            \  
  6.   
  7.             I##INTERFACE::getInterfaceDescriptor() const {              \  
  8.   
  9.         return I##INTERFACE::descriptor;                                \  
  10.   
  11.     }                                                                   \  
  12.   
  13.     android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \  
  14.   
  15.             const android::sp<android::IBinder>& obj)                   \  
  16.   
  17.     {                                                                   \  
  18.   
  19.         android::sp<I##INTERFACE> intr;                                 \  
  20.   
  21.         if (obj != NULL) {                                              \  
  22.   
  23.             intr = static_cast<I##INTERFACE*>(                          \  
  24.   
  25.                 obj->queryLocalInterface(                               \  
  26.   
  27.                         I##INTERFACE::descriptor).get());               \  
  28.   
  29.             if (intr == NULL) {                                         \  
  30.   
  31.                 intr = new Bp##INTERFACE(obj);                          \  
  32.   
  33.             }                                                           \  
  34.   
  35.         }                                                               \  
  36.   
  37.         return intr;                                                    \  
  38.   
  39.     }                                                                   \  
  40.   
  41.     I##INTERFACE::I##INTERFACE() { }                                    \  
  42.   
  43.     I##INTERFACE::~I##INTERFACE() { }                                   \    


    最终asInterface将会用一个IBinder对象创建一个BpServiceManager对象,并且BpServiceManager继承自IServiceManager,这样我们就把IBinder对象转换成了IServiceManager对象。如果你仔细查看BpServiceManager的定义,你会发现查询Service,增加Service等方法其实都是调用底层的IBinder对象来完成的。

    当我们在C/C++层面编写程序使用Binder机制的时候将会调用defaultServiceManager函数来获得Service Manager,比如:很多Android系统Service都是在C/C++层面实现的,他们就需要向Service Manager注册其服务,那么这些服务将调用defaultServiceManager获得Service Manager代理对象。我们在后续介绍Android系统Service的时候将会详细介绍。

另注:IPCThreadState虽然也是Singleton模式(构造函数私有,通过IPCThreadState::self获取对象实例),但是IPCThreadState可不是一个进程只有一个哦!

    IPCThreadState对象是放到线程私有存储(Thread Local Storage)中的,一个进程可能有多个线程,因此IPCThreadState是每个线程都可能拥有一个。TLS通过全局的key获取。TLS相当于线程中的“线程全局”变量,线程中的函数是可以直接获取的(你不能在线程函数栈中new一个IPCThreadState对象,但你能获得该线程唯一的IPCThreadState对象)。

    参考多线程编程模型,再看看IPCThreadState::self为什么那样实现,就知道我所言非虚。

 

III. Android系统Binder机制之三(服务代理对象 下篇)

   上文《Android系统的Binder机制之二——服务代理对象(1)》我们学习了进程的C/C++层面的服务代理对象BpBinder,和Binder底层处理方式。本文我们将深入分析一下在进程的Java层面服务代理对象的创建和使用。

一、Android进程的C/C++层面和Java层

    Android中程序大部分都是java开发,底层通过JNI调用C/C++的代码。这样一个程序就分为了两个层面C/C++层面和Java层面。运行状态下,我们说它们都在一个进程之中,拥有相同的进程属性(UID,GID等等)。

    Binder客户程序的C/C++层面的对象和原理我们在上文《Android系统的Binder机制之二——服务代理对象(1)》已经学习。下面我们将介绍客户程序怎样在Java层面通过JNI调用底层C/C++代码的创建服务代理。

ServiceManager类型和对象

    我在《Android系统的Binder机制之一——Service Manager》中介绍过,客户端要想获得服务代理,首先要向ServiceManager查询Service。在Java层面也是这样,所以我们首先分析Java层面ServiceManager类。

    我们通过查看android.os.ServiceManager的源码我们发现,ServiceManager类型也是一个Singleton类型。所有的方法都是静态方法,所有静态方法都是访问它的IServiceManager类型的静态变量sServiceManager,定义如下:

[plain] view plaincopy
  1. <span style="font-size:12px;">private static IServiceManager sServiceManager;</span>  

所以可以理解ServiceManager就是IServiceManager对象的一个代理。为创建和访问这个变量都是通过ServiceManager的getIServiceManager方法,定义如下:

 

[plain] view plaincopy
  1. private static IServiceManager getIServiceManager() {  
  2.     if (sServiceManager != null) {  
  3.   
  4.         return sServiceManager;  
  5.   
  6.     }  
  7.   
  8.     // Find the service manager  
  9.   
  10.     sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());  
  11.   
  12.     return sServiceManager;  
  13.   
  14. }  


   通过上面的代码,非常清晰的告诉我们ServiceManager类型是一个Singleton类型。现在我们主要研究sServiceManager对象怎样创建的。如下代码创建IServiceManager对象:

[plain] view plaincopy
  1. <span style="font-size:12px;">sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());</span>  

 

我们首先查看BinderInternal类的getContextObject方法的代码,发现是Native代码(哈哈!有终于到达C/C++层面了,我们已经熟悉了),对应的代码为android_util_binder.cpp中的android_os_BinderInternal_getContextObject函数,代码如下:

[plain] view plaincopy
  1. static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)  
  2. {  
  3.   
  4.     sp<IBinder> b = ProcessState::self()->getContextObject(NULL);  
  5.   
  6.     return javaObjectForIBinder(env, b);  
  7.   
  8. }  


 

    终于看到我们上文《Android系统的Binder机制之二——服务代理对象(1)》介绍过的ProcessState对象了,我们再去查看ProcessState对象的getContextObject方法,代码如下:

 

C语言的sprintf函数跟printf在用法上几乎一样,只是两者打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出

sprintf是个变参函数,定义如下:

[plain] view plaincopy
  1. sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)  
  2. {  
  3.   
  4.     return getStrongProxyForHandle(0);  
  5.   
  6. }  


 

    我们看到在当前进程的ProcessState对象其实是调用getStrongProxyForHandle方法来创建binder句柄为0的服务代理对象——BpBinder对象,我们在《Android系统的Binder机制之一——Service Manager》提到过ServiceManager的binder句柄是一个闻名句柄0。上文《Android系统的Binder机制之二——服务代理对象(1)》已经介绍过ProcessState对象的getStrongProxyForHandle方法,这里就不多说了。

    我们可以看出Java调用C/C++,创建一个服务代理对象BpBinder,在查看BpBinder的定义我们发现继承自IBinder接口,然后在android_util_binder.cpp中的方法android_os_BinderInternal_getContextObject中,把C/C++层面的IBinder对象封装成Java层面的IBinder对象。具体实现可以查看上文的android_os_BinderInternal_getContextObject方法。

    至此我们已经清楚BinderInternal.getContextObject(),返回的是ServiceManager的服务代理对象——BpBinder对象。那么ServiceManagerNative类的静态方法asInterface做什么用呢?我们还是通过代码来分析,在ServiceManagerNative.java中,asInterface的代码如下:

     
[plain] view plaincopy
  1. static public IServiceManager asInterface(IBinder obj)  
  2.     {  
  3.   
  4.         if (obj == null) {  
  5.   
  6.             return null;  
  7.   
  8.         }  
  9.   
  10.         IServiceManager in =  
  11.   
  12.             (IServiceManager)obj.queryLocalInterface(descriptor);  
  13.   
  14.         if (in != null) {  
  15.   
  16.             return in;  
  17.   
  18.         }  
  19.   
  20.           
  21.   
  22.         return new ServiceManagerProxy(obj);  
  23.   
  24.     }  

    将会用IBinder对象创建一个ServiceManagerProxy对象,ServiceManagerProxy类型实现了IServiceManager接口,所以asInterface方法最终目的是用一个IBinder对象创建一个IServiceManager对象。

    为什么要用IBinder对象创建一个IServiceManager对象呢?通过ServiceManager的代理对象——IBinder对象(BpBinder对象)应该可以直接请求ServiceManager中的服务了啊?我们在前文《Android系统的Binder机制之二(服务代理对象 上篇)》简单介绍了一下IBinder类型,客户端通过transact方法向Service发送请求,且请求通过请求代码来区分。具体请参考Android手册,也可以参照后面将介绍的ServiceManagerProxy的getService()方法。如果客户端直接用调用ServiceManager的代理对象的IBinder接口,那么客户端必须要记住所有请求的请求代码,对客户端来说不太友好。所以在ServiceManagerNative类中就把ServiceManager的代理对象——IBinder对象(BpBinder对象)封装成ServiceManagerProxy对象,暴露给客户程序一个IServiceManager接口,这样IServiceManager对象(其实是ServiceManagerProxy对象)将会代理客户程通过IBinder把请求发往服务器。客服程序只是简单的调用IServiceManager接口的方法来给ServiceManager发送请求,这样对客户程序来讲和本地的函数调用是一致的,接口非常友好。比如我们客户程序需要调用IServiceManager的getService方法来查询一个Service,ServiceManagerProxy实现代码如下:

[plain] view plaincopy
  1. class ServiceManagerProxy implements IServiceManager {  
  2.     public ServiceManagerProxy(IBinder remote) {  
  3.   
  4.         mRemote = remote;  
  5.   
  6.     }  
  7.   
  8.     public IBinder asBinder() {  
  9.   
  10.         return mRemote;  
  11.   
  12.     }  
  13.   
  14.     public IBinder getService(String name) throws RemoteException {  
  15.   
  16.         Parcel data = Parcel.obtain();  
  17.   
  18.         Parcel reply = Parcel.obtain();  
  19.   
  20.         data.writeInterfaceToken(IServiceManager.descriptor);  
  21.   
  22.         data.writeString(name);  
  23.   
  24.         mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);  
  25.   
  26.         IBinder binder = reply.readStrongBinder();  
  27.   
  28.         reply.recycle();  
  29.   
  30.         data.recycle();  
  31.   
  32.         return binder;  
  33.   
  34.     }  
  35.   
  36. }  


 

    我们可以非常清晰的看到ServiceManagerProxy对象将客户程序的请求转换成对ServiceManager代理对象——IBinder对象(BpBinder对象)的调用。后文我们将会详细介绍怎样通过IServiceManager查询和获得一个系统Service代理对象。

    到这里我们已经分析完了,ServiceManager的Singleton对象——sServiceManager的创建。如果有不清楚的地方请查看代码。

二、查询和获得Service代理对象

    客户程序通过调用ServiceManagerNative的静态方法asInterface获得了IServiceManager对象,但是最终目的一般都是要查询和获得其他的Service,一般都是要调用IServiceManager的getService方法,向ServiceManager获得其他的Service。比如:

 IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);

    上面的代码中就是调用ServiceManager的静态方法获得网络管理服务代理对象。我们顺藤摸瓜,看看系统是怎样生成这个Service代理对象的。这里只是简单说明一下调用顺序,详细过程请查看源码。

1、调用ServiceManager.java中的ServiceManager静态方法getService。

2、调用ServiceManagerNative.java中的ServiceManagerProxy方法getService。代码如下:

    
[plain] view plaincopy
  1. public IBinder getService(String name) throws RemoteException {  
  2.         Parcel data = Parcel.obtain();  
  3.   
  4.         Parcel reply = Parcel.obtain();  
  5.   
  6.         data.writeInterfaceToken(IServiceManager.descriptor);  
  7.   
  8.         data.writeString(name);  
  9.   
  10.         mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);  
  11.   
  12.         IBinder binder = reply.readStrongBinder();  
  13.   
  14.         reply.recycle();  
  15.   
  16.         data.recycle();  
  17.   
  18.         return binder;  
  19.   
  20.     }  

    请注意IBinder的transact方法是同步方法(本例中ServiceManager处理完成请求之后才会返回),我们可以看出调用transact之后,调用reply.readStrongBinder()来读取IBinder对象。

3、查看Parcel.java中的readStrongBinder方法,发现是Native方法,将会调用到C/C++的代码。

4、查看android_util_binder.cpp中的android_os_Parcel_readStrongBinder函数。代码如下:

[plain] view plaincopy
  1. static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jobject clazz)  
  2. {  
  3.   
  4.     Parcel* parcel = parcelForJavaObject(env, clazz);  
  5.   
  6.     if (parcel != NULL) {  
  7.   
  8.         return javaObjectForIBinder(env, parcel->readStrongBinder());  
  9.   
  10.     }  
  11.   
  12.     return NULL;  
  13.   
  14. }  


 

parcel->readStrongBinder()将会产生一个IBinder对象。

5、查看Parcel.cpp中,Parcel的方法readStrongBinder。代码如下:

[plain] view plaincopy
  1. sp<IBinder> Parcel::readStrongBinder() const  
  2. {  
  3.   
  4.     sp<IBinder> val;  
  5.   
  6.     unflatten_binder(ProcessState::self(), *this, &val);  
  7.   
  8.     return val;  
  9.   
  10. }  


 

6、查看Parcel.cpp中,Parcel的方法unflatten_binder。代码如下:

[plain] view plaincopy
  1. status_t unflatten_binder(const sp<ProcessState>& proc,  
  2.     const Parcel& in, sp<IBinder>* out)  
  3.   
  4. {  
  5.   
  6.     const flat_binder_object* flat = in.readObject(false);  
  7.   
  8.     if (flat) {  
  9.   
  10.         switch (flat->type) {  
  11.   
  12.             case BINDER_TYPE_BINDER:  
  13.   
  14.                 *out = static_cast<IBinder*>(flat->cookie);  
  15.   
  16.                 return finish_unflatten_binder(NULL, *flat, in);  
  17.   
  18.             case BINDER_TYPE_HANDLE:  
  19.   
  20.                 *out = proc->getStrongProxyForHandle(flat->handle);  
  21.   
  22.                 return finish_unflatten_binder(static_cast<BpBinder*>(out->get()), *flat, in);  
  23.   
  24.         }          
  25.   
  26.     }  
  27.   
  28.     return BAD_TYPE;  
  29.   
  30. }  



 

终于看到调用我们的老朋友ProcessState对象的getStrongProxyForHandle方法了,这样将会创建一个BpBinder对象,然后该BpBinder对象将会被转换成IBinder对象返回给Java层。

7、Java层为了用使用Service方便,可以把Service代理对象——BpBinder对象(IBinder对象)封装成一个对客户程序友好的代理对象,就如前面ServiceManagerProxy所示那样。

8、用户程序就可以通过该代理对象访问相应Service了。

    通过所述,我们了解了Service代理对象在Java层的创建和使用。Android系统的Binder机制博大精深,我在本文中很多方面都是蜻蜓点水,如果想深入学习请参阅Android的源码。

参考资料:

IPC框架分析 Binder,Service,Service manager

IV. Android系统的Binder机制之四(系统Service篇)

前面我们已经介绍了Android Binder机制的Service ManagerService对象代理(上)Service对象代理(下)。本文将介绍一下Android机制的另外一个重要部分——系统Service。

一、系统Service实例——Media server

    首先我们先看一下Android一个实例Media Service,代码位于framework/base/media/mediaserver/main_mediaserver.cpp文件:

[cpp] view plaincopy
  1. #include <sys/types.h>  
  2. #include <unistd.h>  
  3.   
  4. #include <grp.h>  
  5.   
  6.   
  7.   
  8.   
  9. #include <binder/IPCThreadState.h>  
  10.   
  11. #include <binder/ProcessState.h>  
  12.   
  13. #include <binder/IServiceManager.h>  
  14.   
  15. #include <utils/Log.h>  
  16.   
  17.   
  18.   
  19.   
  20. #include <AudioFlinger.h>  
  21.   
  22. #include <CameraService.h>  
  23.   
  24. #include <MediaPlayerService.h>  
  25.   
  26. #include <AudioPolicyService.h>  
  27.   
  28. #include <private/android_filesystem_config.h>  
  29.   
  30.   
  31.   
  32.   
  33. using namespace android;  
  34.   
  35.   
  36.   
  37.   
  38. int main(int argc, char** argv)  
  39.   
  40. {  
  41.   
  42.     sp<ProcessState> proc(ProcessState::self());  
  43.   
  44.     sp<IServiceManager> sm = defaultServiceManager();  
  45.   
  46.     LOGI("ServiceManager: %p", sm.get());  
  47.   
  48.     AudioFlinger::instantiate();  
  49.   
  50.     MediaPlayerService::instantiate();  
  51.   
  52.     CameraService::instantiate();  
  53.   
  54.     AudioPolicyService::instantiate();  
  55.   
  56.     ProcessState::self()->startThreadPool();  
  57.   
  58.     IPCThreadState::self()->joinThreadPool();  
  59.   
  60. }  


 

 我们发现Media Server是一个进程,并且该程序的实现表面上也挺简单,其实并不简单,让我们慢慢分析一下Media Server。

1、第一句创建创建一个ProcessState的引用,但是这个对象后面并没有被调用到,那么为什么创建呢?回想一下《

Android系统的Binder机制之二(服务代理对象 上篇)》中介绍ProcessState对象时提到:如果一个进程要使用Binder机制,

那么他的进程中必须要创建一个ProcessState对象来负责管理Service的代理对象。

2、第二句调用defaultServiceManager获得一个Service Manager代理对象,

我在《Android系统的Binder机制之二(服务代理对象 上篇)》已经对此有了详细的介绍这里就不赘述了。


 

3、后面几行都是创建具体的Service,我们展开之后发现都是一些调用Service Manager的addService进行注册的函数,以AudioFlinger为例,instantiate代码如下:

[plain] view plaincopy
  1. void AudioFlinger::instantiate() {  
  2.   
  3.     defaultServiceManager()->addService(  
  4.   
  5.             String16("media.audio_flinger"), new AudioFlinger());  
  6.   
  7. }  

 

4、最后调用ProcessState的startThreadPool方法和IPCThreadState的joinThreadPool使Media Server进入等待请求的循环当中。

    我们可以看出一个进程中可以有多个Service,Media Server这个进程中就存在AudioFlinger,MediaPlayerService,CameraService,AudioPolicyService四个Service。

二、系统Service的基础——BBinder

    我们仔细查看一下Media Server中定义的四个Service我们将会发现他们都是继承自BBinder,而BBinder又继承自IBinder接口,详细情况请自行查看他们的代码。每个Service都改写了BBinder的onTransact虚函数,当用户发送请求到达Service时,框架将会调用Service的onTransact函数,后面我们将会详细的介绍这个机制。

三、Service注册

    每个Service都需要向“大管家”Service Manager进行注册,调用Service Manager的addService方法注册。这样Service Manager将会运行客户端查询和获取该Service(代理对象),然后客户端就可以通过该Service的代理对象请求该Service的服务。

四、Service进入等待请求的循环

    每个Service必须要进入死循环,等待客户端请求的到达,本例中最后两句就是使Service进行等待请求的循环之中。

ProcessState的startThreadPool方法的代码如下

[plain] view plaincopy
  1. void ProcessState::startThreadPool()  
  2. {  
  3.   
  4.     AutoMutex _l(mLock);  
  5.   
  6.     if (!mThreadPoolStarted) {  
  7.   
  8.         mThreadPoolStarted = true;  
  9.   
  10.         spawnPooledThread(true);  
  11.   
  12.     }  
  13.   
  14. }  
  15.   
  16. void ProcessState::spawnPooledThread(bool isMain)  
  17. {  
  18.   
  19.     if (mThreadPoolStarted) {  
  20.   
  21.         int32_t s = android_atomic_add(1, &mThreadPoolSeq);  
  22.   
  23.         char buf[32];  
  24.   
  25.         sprintf(buf, "Binder Thread #%d", s);  
  26.   
  27.         LOGV("Spawning new pooled thread, name=%s\n", buf);  
  28.   
  29.         sp<Thread> t = new PoolThread(isMain);  
  30.   
  31.         t->run(buf);  
  32.   
  33.     }  
  34.   
  35. }  
  36.   
  37. IPCThreadState的joinThreadPool方法的代码如下:  
  38.   
  39.   
  40.   
  41.   
  42. void IPCThreadState::joinThreadPool(bool isMain)  
  43. {  
  44.   
  45.     LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());  
  46.   
  47.     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);  
  48.   
  49.     // This thread may have been spawned by a thread that was in the background  
  50.   
  51.     // scheduling group, so first we will make sure it is in the default/foreground  
  52.   
  53.     // one to avoid performing an initial transaction in the background.  
  54.   
  55.     androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);  
  56.   
  57.     status_t result;  
  58.   
  59.     do {  
  60.   
  61.         int32_t cmd;  
  62.   
  63.         // When we've cleared the incoming command queue, process any pending derefs  
  64.   
  65.         if (mIn.dataPosition() >= mIn.dataSize()) {  
  66.   
  67.             size_t numPending = mPendingWeakDerefs.size();  
  68.   
  69.             if (numPending > 0) {  
  70.   
  71.                 for (size_t i = 0; i < numPending; i++) {  
  72.   
  73.                     RefBase::weakref_type* refs = mPendingWeakDerefs[i];  
  74.   
  75.                     refs->decWeak(mProcess.get());  
  76.   
  77.                 }  
  78.   
  79.                 mPendingWeakDerefs.clear();  
  80.   
  81.             }  
  82.   
  83.   
  84.   
  85.   
  86.             numPending = mPendingStrongDerefs.size();  
  87.   
  88.             if (numPending > 0) {  
  89.   
  90.                 for (size_t i = 0; i < numPending; i++) {  
  91.   
  92.                     BBinder* obj = mPendingStrongDerefs[i];  
  93.   
  94.                     obj->decStrong(mProcess.get());  
  95.   
  96.                 }  
  97.   
  98.                 mPendingStrongDerefs.clear();  
  99.   
  100.             }  
  101.   
  102.         }  
  103.   
  104.         // now get the next command to be processed, waiting if necessary  
  105.   
  106.         result = talkWithDriver();  
  107.   
  108.         if (result >= NO_ERROR) {  
  109.   
  110.             size_t IN = mIn.dataAvail();  
  111.   
  112.             if (IN < sizeof(int32_t)) continue;  
  113.   
  114.             cmd = mIn.readInt32();  
  115.   
  116.             IF_LOG_COMMANDS() {  
  117.   
  118.                 alog << "Processing top-level Command: "  
  119.   
  120.                     << getReturnString(cmd) << endl;  
  121.   
  122.             }  
  123.   
  124.             result = executeCommand(cmd);  
  125.   
  126.         }  
  127.   
  128.         // After executing the command, ensure that the thread is returned to the  
  129.   
  130.         // default cgroup before rejoining the pool.  The driver takes care of  
  131.   
  132.         // restoring the priority, but doesn't do anything with cgroups so we  
  133.   
  134.         // need to take care of that here in userspace.  Note that we do make  
  135.   
  136.         // sure to go in the foreground after executing a transaction, but  
  137.   
  138.         // there are other callbacks into user code that could have changed  
  139.   
  140.         // our group so we want to make absolutely sure it is put back.  
  141.   
  142.         androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);  
  143.   
  144.         // Let this thread exit the thread pool if it is no longer  
  145.   
  146.         // needed and it is not the main process thread.  
  147.   
  148.         if(result == TIMED_OUT && !isMain) {  
  149.   
  150.             break;  
  151.   
  152.         }  
  153.   
  154.     } while (result != -ECONNREFUSED && result != -EBADF);  
  155.   
  156.   
  157.   
  158.   
  159.     LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",  
  160.   
  161.         (void*)pthread_self(), getpid(), (void*)result);  
  162.   
  163.       
  164.   
  165.     mOut.writeInt32(BC_EXIT_LOOPER);  
  166.   
  167.     talkWithDriver(false);  
  168.   
  169. }  

 

    Service在IPCThreadState的joinThreadPool方法中,调用talkWithDriver方法和Binder驱动进行交互,读取客户端的请求。当客户端请求到达之后调用executeCommand方法进行处理。

    我们再看一下Service怎样处理客户端的请求?我们们查看一下executeCommand方法的源码:


 

   

[plain] view plaincopy
  1. status_t IPCThreadState::executeCommand(int32_t cmd)  
  2.   
  3.    {  
  4.   
  5.        BBinder* obj;  
  6.   
  7.        RefBase::weakref_type* refs;  
  8.   
  9.        status_t result = NO_ERROR;  
  10.   
  11.          
  12.   
  13.        switch (cmd) {  
  14.   
  15.            ......   
  16.   
  17.             case BR_TRANSACTION:  
  18.   
  19.            {  
  20.   
  21.                ......  
  22.   
  23.                if (tr.target.ptr) {  
  24.   
  25.                    sp<BBinder> b((BBinder*)tr.cookie);  
  26.   
  27.                    const status_t error = b->transact(tr.code, buffer, &reply, 0);  
  28.   
  29.                    if (error < NO_ERROR) reply.setError(error);  
  30.   
  31.                      
  32.   
  33.                }   
  34.   
  35.                ......              
  36.   
  37.            }  
  38.   
  39.            break;  
  40.   
  41.         
  42.   
  43.        ......  
  44.   
  45.        }  
  46.   
  47.       
  48.   
  49.        if (result != NO_ERROR) {  
  50.   
  51.            mLastError = result;  
  52.   
  53.        }  
  54.   
  55.          
  56.   
  57.        return result;  
  58.   
  59.    }  


可以看到IPCThreadState将会直接调用BBinder的transact方法来处理客户端请求,我们再看一下BBinder的transact方法:

[plain] view plaincopy
  1. status_t BBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)  
  2. {  
  3.   
  4.     data.setDataPosition(0);  
  5.   
  6.     status_t err = NO_ERROR;  
  7.   
  8.     switch (code) {  
  9.   
  10.         case PING_TRANSACTION:  
  11.   
  12.             reply->writeInt32(pingBinder());  
  13.   
  14.             break;  
  15.   
  16.         default:  
  17.   
  18.             err = onTransact(code, data, reply, flags);  
  19.   
  20.             break;  
  21.   
  22.     }  
  23.   
  24.     if (reply != NULL) {  
  25.   
  26.         reply->setDataPosition(0);  
  27.   
  28.     }  
  29.   
  30.     return err;  
  31.   
  32. }  


我们发现他将会叫用自己的虚函数onTransact。我们前面提到所有的Service都集成自BBinder,并且都改写了onTransact虚函数。那么IPCThreadState将会调用Service定义onTransact方法来响应客户请求。

五、结论

    本文简单介绍了一下系统Service的原理,台湾的高焕堂先生有一篇文章,手把手教怎样实现一个系统Service,你可以在我的博文《(转)高焕堂——Android框架底层结构知多少?》中找到。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 调用自己编写的matlab函数

    matlab是一款功能强大的,可以用于算法开发、数据分析、数据可视化、数据计算等的高级技术计算语言。 因此matlab本身就已经包含了很多供用户使用的函数。但是,有时候我们也需要定义自己需要的函数以便于 更好的完成想要实现的功能。那么,自己定义的函数应该怎样调用呢? 1、…...

    2024/5/3 5:18:41
  2. 微信公众号开发框架 For Java —— wechatapi

    微信公众号开发框架 For Java 微信公共平台API。 功能列表公共API 发送客服消息(文本、图片、语音、视频、音乐、图文) 菜单操作(查询、创建、删除、个性化菜单) 二维码(创建临时、永久二维码,查看二维码URL) 分组操作(查询、创建、修改、移动用户到分组) 用户信息(查…...

    2024/4/17 3:54:55
  3. 【Python3网络爬虫开发实战】 2.3-爬虫的基本原理

    【摘要】 我们可以把互联网比作一张大网,而爬虫(即网络爬虫)便是在网上爬行的蜘蛛。把网的节点比作一个个网页,爬虫爬到这就相当于访问了该页面,获取了其信息。可以把节点间的连线比作网页与网页之间的链接关系,这样蜘蛛通过一个节点后,可以顺着节点连线继续爬行到达下一…...

    2024/5/3 2:41:22
  4. Android IPC基础之Binder 机制

    IPC概念 :进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程 起由 :两个对象能直接相互访问的前提是这两个对象都存在于相同的内存地址空间中,如果两个对象分别存在于两个不同的进程中,那么这两个对象是不能直接相互调用的,需要跨进程通信技术Binder 机制Binde…...

    2024/4/17 3:52:01
  5. 微信公众号开发----测试号的使用

    由于用户体验和安全性方面的考虑,微信公众号的注册有一定门槛,某些高级接口的权限需要微信认证后才可以获取。 所以,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,微信推出了微信公众帐号测试号,通过手机微信扫描二维码即可获得测试号。测试号进入步骤…...

    2024/4/18 0:35:04
  6. MATLAB是否支持多线程

    最近一直在利用matlab的并行运算处理数据,时间真的是缩短很多,但是我发现,开启并行后,它的运行过程和多线程很相似。parfor只用于开启matlab并行循环。parfor对于我使用的蒙特卡洛模拟很有用,parfor可以将循环迭代分组执行,那么每个worker执行迭代的一部分,大大缩短运行…...

    2024/5/3 5:09:03
  7. 使用JSOUP实现网络爬虫

    最近想做一个app,没什么思路,网上看了,想做一个图片浏览的,可是没有数据呀,想到了网上比较火的网络爬虫,实现方式有很多种,这里选择了Jsoup。想到爬虫,还想到了网上有收费的什么电子书呀等等这些,那我是不是也可以通过爬虫来获取数据呢?当然,这里说的是一些比较low的…...

    2024/3/30 12:17:45
  8. 《高效能人士的七个习惯》--读书笔记

    原谅我的无知吧~~这是一本风靡全世界的书籍,可是我一直没有看,因为我对所谓的鸡汤之类的书籍都是敬而远之的。看着名字一眼就觉得应该是鸡汤,所以一直都没有想看的愿望。那天无意在网上下载电子书,手贱下载下来了~~ 闲着没事看看 也许真的会有点事收获? “谁也无法说服他人…...

    2024/4/17 3:52:13
  9. 天泰 OpenWAF 开源防爬虫模块

    最近在网上看到一个有趣的问题:整个互联网的流量中,真人的占比有多少?根据 Aberdeen Group在近期发布的以北美几百家公司数据为样本的爬虫调查报告显示,最近三年网站流量中的真人访问平均仅为总流量的50%,剩余的流量由28.11%的善意爬虫和21.89%的恶意爬虫构成,可见爬虫数…...

    2024/4/18 4:12:15
  10. 《高效能人士的七个好习惯》读书笔记

    一、何谓“效能”效能是产出和产能的结合,追求高效能既要注重产出,也要注重产能的提高。只注重产能而不注重于产出,无法获得可观的汇报和机会;只追求产出而忽视产能,则是杀鸡取卵。习惯一:主动积极人的的行为模式:人的行为模式既非完全由遗传因素决定的先天决定论,也不…...

    2024/3/31 12:53:42
  11. 微信公众号开发之消息模板

    微信公众号开发之消息模板这个消息模板是以订阅号进行模板配置的,如果是认证号就更加简单了,不用自己配置!!!只需要知道模板ID,和模板的样式就行。微信公众号发送消息模板,当前首先要关注这个公众号,然后这个公众号,可以将消息发送给关注它的用户。先上如下效果图:1、…...

    2024/4/17 3:54:07
  12. MATLAB报错:未定义函数或变量

    出现这种错误一般是因为定义的函数和当前的工作环境不在一块导致。 一般情况MATLAB工作环境默认为C盘,但我们有时候会将自己编写的函数放在其他盘。当我们调用这个函数是就会出现 未定义函数或变量:‘’xxx‘’。我编写的这个函数只能完成一个加法,目的就是为了测试使用。 我…...

    2024/5/3 2:36:02
  13. ActivityGroup 实现分页和自定义标签(内有GridView的点击背景样式的改变方法)

    我这里实现的是方法和这个帖子的主要差别的就是界面都是自定义的。这样可以实现很多美观的分页,新浪微博等的效果一样可以达到。上效果图如下: 可以看到下方的就是标签界面了,这是一个GridVIew。很多人都说设置GridVIew的list setselector属性,但是这个属性可是改变点击后一…...

    2024/4/18 16:27:01
  14. android的binder机制研究二

    转载:android的binder机制研究(C++部分) 2010年10月16日 星期六 14:31http://kahweh.blog.sohu.com/160696746.html (一) 概述 android的binder机制提供一种进程间通信的方法,使一个进程可以以类似远程过程调用的形式调用另一个进程所提供的功能。binder机制在 Java环…...

    2024/4/27 10:56:07
  15. Python使用Scrapy爬虫框架爬取天涯社区小说“大宗师”全文

    大宗师是著名网络小说作家蛇从革的系列作品“宜昌鬼事”之一,在天涯论坛具有超级高的访问量。这个长篇小说于2015年3月17日开篇,并于2016年12月29日大结局,期间每天有7万多读者阅读。如果在天涯社区直接阅读的话,会被很多读者留言干扰,如图于是,我写了下面的代码,从天涯…...

    2024/3/31 20:59:00
  16. 《高效能人士的七个习惯》读书笔记一

    第一章 重新探索自我个人的思维定式常常决定了他所看到的世界,并会改变个个人的行为和态度。思维和个人的价值取向息息相关,应建立正确的价值取向。有些正确价值类似于自然定理,在人类社会具有普遍意义,如:公平,尽管不同的社会对公平及如何维护公平有不同看法;诚实,正直…...

    2024/5/3 1:42:39
  17. 微信开放平台开发第三方授权登陆(四):微信公众号

    微信开放平台开发系列文章:微信开放平台开发第三方授权登陆(一):开发前期准备微信开放平台开发第三方授权登陆(二):PC网页端微信开放平台开发第三方授权登陆(三):Android客户端微信开放平台开发第三方授权登陆(四):微信公众号微信开放平台开发第三方授权登陆(五)…...

    2024/5/1 12:30:36
  18. Matlab中的inf

    在Matlab中,inf为无穷大量+∞,-inf为无穷小量-∞,在Matlab程序执行时,即使遇到了以0为除数的运算,也不会终止程序的运行,而只给出一个“除0”警告,并将结果赋成inf,继续执行...

    2024/5/1 19:41:00
  19. 用ActivityGroup实现动态加载-Android网易顶部导航栏(转)

    至于顶部导航的具体要用到的图片和布局大家自己调整。由于前面已经介绍了底部菜单栏了,所以一些重复性的代码就不贴上来了,最后我也会把下载地址贴上大家有兴趣自行下载。首先看一些顶部导航栏的布局文件:[html] view plaincopy<?xml version="1.0" encoding=…...

    2024/5/1 8:54:22
  20. Python爬虫之如何跟妈妈解释什么是爬虫

    作者 | 猪哥 来源 | 裸睡的猪(ID:IT–Pig) 前段时间我妈突然问我:儿子,爬虫是什么?我当时既惊讶又尴尬,惊讶的是为什么我妈会对爬虫好奇?尴尬的是我该怎么给她解释呢? 一、爬虫介绍 1.爬虫是什么 网络爬虫(web crawler 简称爬虫)就是按照一定规则从互联网上抓取信息的程…...

    2024/5/1 5:35:31

最新文章

  1. 旅游新策略,共享与补贴助力地方经济繁荣

    在当前的经济环境中&#xff0c;旅游业对于地方经济增长的重要性日益凸显。各个城市都在积极探索增加旅游流量的方法&#xff0c;以刺激本地经济的增长。 例如&#xff0c;淄博政府通过政策推动和合作模式&#xff0c;成功吸引了大量游客&#xff0c;这成为了一个成功的案例。…...

    2024/5/3 7:20:56
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. Gin环境搭建详解

    Gin环境搭建详解&#xff1a; 要安装Gin软件包&#xff0c;需要先安装Go并设置Go工作区。Gin环境搭建步骤如下&#xff1a; 【Gin框架】Gin环境搭建 Gin程序的热加载 Gin路由 GET POST PUT DELETE 1. 下载并安装 gin &#xff1a; $ go get -u github.com/gin-gonic/gin 2. …...

    2024/5/1 20:13:42
  4. STL--vector有哪些应用场景

    vector 在 C 中是一种非常灵活和强大的容器&#xff0c;适用于多种不同的应用场景。以下是一些常见的应用场景&#xff1a; 1 动态数据集合&#xff1a;当你不确定数据集的大小&#xff0c;或者数据集的大小会随时间变化时&#xff0c;vector 是理想的选择。例如&#xff0c;在…...

    2024/5/3 5:48:45
  5. 电脑上音频太多,播放速度又不一致,如何批量调节音频播放速度?

    批量调节音频速度是现代音频处理中的一个重要环节&#xff0c;尤其在音乐制作、电影剪辑、有声书制作等领域&#xff0c;它能够帮助制作者快速高效地调整音频的播放速度&#xff0c;从而满足特定的制作需求。本文将详细介绍批量调节音频速度的方法、技巧和注意事项&#xff0c;…...

    2024/5/2 23:06:54
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57