(一) 概述   

    android的binder机制提供一种进程间通信的方法,使一个进程可以以类似远程过程调用的形式调用另一个进程所提供的功能。binder机制在Java环境和C/C++环境都有提供。

    android的代码中,与C/C++的binder包括一些类型和接口的定义和实现,相关的代码在下面这几个文件中:

    frameworks\base\include\utils\IInterface.h
    frameworks\base\include\utils\Binder.h
    frameworks\base\include\utils\BpBinder.h
    frameworks\base\include\utils\IBinder
    frameworks\base\include\utils\Parcel.h
    frameworks\base\include\utils\IPCThreadState.h
    frameworks\base\include\utils\ProcessState.h
    frameworks\base\libs\utils\Binder.cpp
    frameworks\base\libs\utils\BpBinder.cpp
    frameworks\base\libs\utils\IInterface.cpp
    frameworks\base\libs\utils\IPCThreadState.cpp
    frameworks\base\libs\utils\Parcel.cpp
    frameworks\base\libs\utils\ProcessState.cpp
    为了了解这些类、接口之间的关系以及binder的实现机制,最好是结合一个例子来进行研究。我选择的例子是android自带的媒体播放器的实现。其媒体播放器的相关代码在下面这些目录中:

   frameworks\base\include\media
frameworks\base\media

   使用startUML的反向工程功能分析上面这些代码,并进行了一定的整理之后,得到下面这幅类图(点击可查看原尺寸图片)。

 

   android的媒体播放功能分成两部分,一部分是媒体播放应用,一部分是媒体播放服务(MediaServer,在系统启动时由init所启动,具可参考init.rc文件)。这两部分分别跑在不同的进程中。媒体播放应用包括Java程序和部分C++代码,媒体播放服务是C++代码,并且需要调用外部模块opencore来实现真正的媒体播放。媒体播放应用和媒体播放服务之间需要通过binder机制来进行相互调用,这些调用包括:

   (1)媒体播放应用向媒体播放服务发控制指令
(2)媒体播放服务向媒体播放应用发事件通知(notify)

   媒体播放服务对外提供多个接口,在上面得图中包括其中的2个接口:IMediaService和IMediaPlayer,IMediaplayer用于创建和管理播放实例,而IMediaplayer接口则是播放接口,用于实现指定媒体文件的播放以及播放过程的控制。

   上面的图中还有媒体播放应用向媒体播放服务提供的1个接口:IMediaPlayerClient,用于接收notify。

   这些接口因为需要跨进程调用,因此需要用到binder机制。每个接口包括两部分实现,一部分是接口功能的真正实现(BnInterface),这部分运行在接口提供进程中;另一部分是接口的proxy(BpInterface),这部分运行在调用接口的进程中。binder的作用就是让这两部分之间建立联系。下图是整个播放器的一个概要说明。

  

     媒体播放器比较复杂一些,总共实现了3个接口,不过要了解binder的机制,只需要研究其中一个接口就足够了。在这里选择IMediaPlayerService接口来看一下。

     IMediaPlayerService接口包括六个功能函数:create(url)、create(fd)、decode(url)、decode(fd)、createMediaRecord()、createMetadataRetriever()。在这里不介绍这些函数是做什么的,我们只关注如何通过binder还提供这些函数接口。


(二) 接口定义

(1) 定义接口类

     首先定义IMediaPlayerService类,这是一个接口类(C++的术语应该叫纯虚类)。该接口类定义在文件frameworks\base\include\media\IMediaPlayerService.h。代码如下:

class IMediaPlayerService: public IInterface 

public: 
DECLARE_META_INTERFACE(MediaPlayerService); 

virtual sp<IMediaRecorder>  createMediaRecorder(pid_t pid) = 0; 
virtual sp<IMediaMetadataRetriever> createMetadataRetriever(pid_t pid) = 0; 
virtual sp<IMediaPlayer>    create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url) = 0; 
virtual sp<IMediaPlayer>    create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0; 
virtual sp<IMemory>         decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; 
virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; 
}; 


    可以看到,在这个接口类中定义了IMediaPlayerService需要提供的6个函数接口,因为是接口类,所以定义为纯虚函数。需要注意这个接口类的名称有严格要求,必须是以大写字母I开始。

    重点关注在这些函数前面的一个宏定义: DECLARE_META_INTERFACE(MediaPlayerService)。这个宏定义必须要有,其中封装了实现binder所需要的一些类成员变量和成员函数通过这些成员函数可以为一个binder实现创建proxy。这个宏定义在问价frameworks\base\include\utils\IInterface.h里,在后面还会讲到。这个宏定义的参数必须是接口类的名称去除字母I后剩下的部分。

    另外说明一下,可以看到接口类中所定义的函数的返回值都是sp<xxxx>的形式,看起来有点怪异。sp是android中定义的一个模板类,用于实现智能指针功能。sp<IMediaPlayer>就是IMediaPlayer的智能指针,可以简单地把它看成是标准C++中的指针定义即 IMediaPlayer* 即可。

(2) 定义和实现binder类

    binder类包括两个,一个是接口实现类,一个接口代理类。接口代理类继承自BpInterface,接口实现类继承自BnInterface。这两个基类都是模板类,封装了binder的进程间通信机制,这样使用者无需关注底层通信实现细节。

    对于IMediaPlayerService接口,其binder接口实现类为BnMediaPlayerService,接口代理类为BpMediaPlayerService。需注意这两个类的名称有严格要求,必须以Bn和Bp开头,并且后面的部分必须是前面所定义的接口类的名称去除字母'I’。比如前面所定义的接口类为IMediaPlayerService,去除字母I后是MediaPlayerService,所以两个binder类的名称分别是BnMediaPlayerService和BpMediaPlayerService。为什么有这样的要求?原因就在前面提到的宏定义DECLARE_META_INTERFACE()和另一个宏定义IMPLEMENT_META_INTERFACE()里面。有兴趣的话可以去看一下,这两个宏定义都在文件frameworks\base\include\utils\IInterface.h里。

    BpMediaPlayerService是一个最终实现类。定义并且实现在在文件frameworks\base\media\libmidia\IMediaPlayerService.cpp中。在看BpMediaPlayerService的代码之前,先看一下在IMediaPlayerService.cpp文件的开始部分的一个枚举定义:

enum { 
CREATE_URL = IBinder::FIRST_CALL_TRANSACTION, 
CREATE_FD, 
DECODE_URL, 
DECODE_FD, 
CREATE_MEDIA_RECORDER, 
CREATE_METADATA_RETRIEVER, 
}; 

    这些6个枚举定义对应于IMediaPlayerService接口所提供的6个功能函数,可以称为这些功能函数的功能代码,用于在进程之间进行RPC是标识需要调用哪个函数。如果不想定义这些枚举值,在后面需要用到这些值的地方直接写上1,2,3,4,5,6也是可以的,不过……一个合适的程序员会这么干吗?

    下面看一下BpMediaPlayerService的代码。

(3) BpMediaPlayerService代码分析

class BpMediaPlayerService: public BpInterface<IMediaPlayerService> 

public: 
BpMediaPlayerService(const sp<IBinder>& impl) 
: BpInterface<IMediaPlayerService>(impl) 



virtual sp<IMediaMetadataRetriever> createMetadataRetriever(pid_t pid) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeInt32(pid); 
remote()->transact(CREATE_METADATA_RETRIEVER, data, &reply); 
return interface_cast<IMediaMetadataRetriever>(reply.readStrongBinder()); 


virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeInt32(pid); 
data.writeStrongBinder(client->asBinder()); 
data.writeCString(url); 
remote()->transact(CREATE_URL, data, &reply); 
return interface_cast<IMediaPlayer>(reply.readStrongBinder()); 


virtual sp<IMediaRecorder> createMediaRecorder(pid_t pid) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeInt32(pid); 
remote()->transact(CREATE_MEDIA_RECORDER, data, &reply); 
return interface_cast<IMediaRecorder>(reply.readStrongBinder()); 


virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeInt32(pid); 
data.writeStrongBinder(client->asBinder()); 
data.writeFileDescriptor(fd); 
data.writeInt64(offset); 
data.writeInt64(length); 
remote()->transact(CREATE_FD, data, &reply); 
return interface_cast<IMediaPlayer>(reply.readStrongBinder()); 


virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeCString(url); 
remote()->transact(DECODE_URL, data, &reply); 
*pSampleRate = uint32_t(reply.readInt32()); 
*pNumChannels = reply.readInt32(); 
*pFormat = reply.readInt32(); 
return interface_cast<IMemory>(reply.readStrongBinder()); 


virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeFileDescriptor(fd); 
data.writeInt64(offset); 
data.writeInt64(length); 
remote()->transact(DECODE_FD, data, &reply); 
*pSampleRate = uint32_t(reply.readInt32()); 
*pNumChannels = reply.readInt32(); 
*pFormat = reply.readInt32(); 
return interface_cast<IMemory>(reply.readStrongBinder()); 

}; 

    首先可以看到,这个类继承自模板类BpInterface,指定类型为接口类IMediaPlayerService。BpInterface模板类定义在文件IInterface.h。看一下BpInterface的定义就可以发现,BpMediaPlayerService这样定义了以后,事实上间接继承了IMediaPlayerService,从而可以提供IMediaPlayerService接口所定义的接口函数。BpMediaPlayerService需要实现这些接口函数。在一个简单的构造函数之后,就是这些接口函数的实现。可以看到,所有的接口函数的实现方法都是一致的,都是通过binder所提供的机制将参数仍给binder的实现类,并获取返回值。这也就是这个类之所以成为代理类的原因。下面具体看一下一个接口函数。这里选的是函数create(url)。

    virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url) 

Parcel data, reply; 
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); 
data.writeInt32(pid); 
data.writeStrongBinder(client->asBinder()); 
data.writeCString(url); 
remote()->transact(CREATE_URL, data, &reply); 
return interface_cast<IMediaPlayer>(reply.readStrongBinder()); 


    这个接口函数的参数指定了一个URL,函数将为这个URL创建一个播放器实例用于播放该URL。
    函数首先定义了两个局部变量data和reply,变量的类型都是Parcel。Parcel是一个专为binder通信的数据传送而定义的类,该类提供了对多种类型的数据的封装功能,同时提供多个数据读取和写入函数,用于多种类型的数据的写入和读取,支持的数据类型既包括简单数据类型,也包括对象。这里定义的变量data是用于封装create()函数调用所需要的输入参数,而reply则是用于封装调用的返回数据(包括输出参数的值和函数返回值)。
    函数首先向data中写入各种数据。第一个写入的是接口的一个描述字符串,binder的实现类中会用这个字符串来对接口做验证,防止调用错误。这个字符串也可以不写,如果不写,在binder实现类中相应的也就不要做验证了。跟在描述字符串后面写入的是该接口函数所需要的各种的输入参数。需要说明的是,Pacel提供一种先入先出的数据存储方式,即数据的写入顺序和读取顺序必须严格一致,否则将会出错。
    完成数据写入后,函数调用remote()->transact()用于完成binder通信。transact()函数的第一个参数就是前面提到过的功能代码。transact()的功能是将data中的数据传给binder的实现类,函数调用结束后,reply中将包含返回数据。首先来看看remote()成员函数。前面讲到过BpMediaPlayerService通过继承BpInterface模板类间接继承了IMediaPlayerService接口类,其实BpInterface类是一个有两个父类的多重继承子类,另一个父类是BpRefbase(frameworks\base\include\utils\Binder.h)。remote()就是继承自BpRefBase类的一个成员函数,该函数返回BpRefBase类中定义的一个私有属性mRemote。mRemote是对IBinder接口类的子类BpBinder的一个对象的引用(参考前面的类关系图)。transact()函数在IBinder接口类中定义(frameworks\base\include\utils\Binder.h),并在BpBinder类中实现(frameworks\base\include\utils\BpBinder.h、frameworks\base\libs\utils\BpBinder.cpp)。在transact()函数中将调用IPCThreadState类的transact()函数,并进而通过Lniux内核中的android共享内存驱动来实现进程间通信。不过这些细节这里就不多说了。在这里BpBinder类对象是一个关键,是实现Binder代理的核心之一。BpBinder类可以看成是一个通信handle(类似于网络编程中的socket),用于实现进程间通信。接下来需要研究的是这个BpBinder类对象(即mRemote成员变量的值)是从哪里来的。
    回过头来BpMediaPlayerService的构造函数(看前面的代码)。该构造函数的参数是一个IBinder对象的引用。mRemote的值就是在这里传进来的这个对象。那么这个对象又是怎么来的呢?要搞清楚这一点就需要找到创建BpMediaPlayerService类的实例的代码,这个代码就就跟在该类的定义代码的下面。继续看IMediaPlayerService.cpp文件,在BpMediaPlayerService类定义的后面,是下面这样一行代码:

    IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.hardware.IMediaPlayerService"); 

    这行代码调用了一个宏定义IMPLEMENT_META_INTERFACE()。这个宏定义与前面提到过的DECLARE_META_INTERFACE()相呼应。看名字就知道,IMPLEMENT_META_INTERFACE()宏是对DECLARE_META_INTERFACE()所定义的成员函数的具体实现。这个宏的第一个参数与DECLARE_META_INTERFACE()的参数需完全一样,第二参数是接口的描述字符串(这个字符串前面也已经讲到过了)。描述字符串不重要,重要的是宏里面定义的一个静态成员函数asInterface()。BpMediaPlayerService的类实例是在IMediaPlayerService的静态成员函数asInterface()中创建的,在IInterface.h中定义了一个内联函数interface_cast(),对这个成员函数进行了封装。通过看代码容易知道,BpMediaPlayerService的构造函数的参数是通过interface_cast()的参数传进来的。
    好,下面就该看看这个interface_cast()是在哪里调用的,它的参数到底是什么。找到frameworks\base\media\libmedia\mediaplayer.cpp文件,其中的MediaPlayer::getMediaPlayerService()的实现代码:
const sp<IMediaPlayerService>& MediaPlayer::getMediaPlayerService() 

Mutex::Autolock _l(sServiceLock); 
if (sMediaPlayerService.get() == 0) { 
sp<IServiceManager> sm = defaultServiceManager(); 
sp<IBinder> binder; 
do { 
binder = sm->getService(String16("media.player")); 
if (binder != 0) 
break; 
LOGW("MediaPlayerService not published, waiting..."); 
usleep(500000); // 0.5 s 
} while(true); 
if (sDeathNotifier == NULL) { 
sDeathNotifier = new DeathNotifier(); 

binder->linkToDeath(sDeathNotifier); 
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder); 

LOGE_IF(sMediaPlayerService==0, "no MediaPlayerService!?"); 
return sMediaPlayerService; 


    看一下上面这段代码中的红色字体部分。结合前面的分析,可知BpBinder类的对象实例是从android的服务管理器的getService()函数中获取,进一步追进去,会发现下面这样一段代码:


Parcel data, reply; 
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); 
data.writeString16(name); 
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply); 
return reply.readStrongBinder(); 


    Android的服务管理器是一个单独的进程,也向外提供接口。这段代码的含义,是通过Android的服务管理器的接口代理,请求调用服务管理器的checkService()接口函数,查找指定的服务(上面就是查找media.player服务),查找成功后返回一个BpBinder类的对象实例,用于供IMediaPlayerService代理使用。这个对象BpBinder是在Parcel::readStrongBinder()函数里面创建的。那么到底是怎么创建出来的呢?在这里没有必要追到ServiceManager的实现代码里去,毕竟我们只是想知道BpBinder的对象是如何创建的,我们可以换一个例子来看。回到前面的BpMediaPlayerService::create()函数的实现,是不是很眼熟。没错,在那个函数里也创建了一个BpBinder类对象,那个对象是是给IMediaPlayer接口代理使用的。虽然接口不同,但是创建原理是一样的。我们继续,下面该到binder的另一个类——实现类的代码了。

(3) BnMediaPlayerService代码分析
    BnMediaPlayerService类的定义在文件frameworks\base\include\media\IMediaPlayService.h,实现则与BpMediaPlayerService一样是在文件frameworks\base\media\libmidia\IMediaPlayerService.cpp中。类定义的代码如下:
class BnMediaPlayerService: public BnInterface<IMediaPlayerService> 

public: 
virtual status_t    onTransact( uint32_t code, 
const Parcel& data, 
Parcel* reply, 
uint32_t flags = 0); 
}; 

    这个类继承自BnInterface模板类,约束类型为IMediaPlayerService。看一下BnInterface模板类的定义(IInterface.h)就可以知道,BnMediaPlayerService间接继承了IMediaPlayerService接口类。不过BnInterface类并没有实现IMediaPlayerService所定义的6个接口函数,因此BnInterface还是一个纯虚类。这些接口需要在BnMediaPlayerService的子类中真正实现,这个子类就是MediaPlayerService(frameworks\base\media\libmidiaservice\MediaPlayerService.h,frameworks\base\media\libmidiaservice\MediaPlayerService.cpp)。在BnMediaPlayerService的成员函数onTransact()中,需要调用这6个接口函数。BnMediaPlayerService中主要就是定义并实现了onTransact()函数。当在代理那边调用了transact()函数后,这边的onTransact()函数就会被调用。BnMediaPlayerService的实现代码如下:
#define CHECK_INTERFACE(interface, data, reply) \ 
do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ 
LOGW("Call incorrectly routed to " #interface); \ 
return PERMISSION_DENIED; \ 
} } while (0) 

status_t BnMediaPlayerService::onTransact( 
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) 

switch(code) { 
case CREATE_URL: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
pid_t pid = data.readInt32(); 
sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder());
const char* url = data.readCString(); 
sp<IMediaPlayer> player = create(pid, client, url); 
reply->writeStrongBinder(player->asBinder()); 
return NO_ERROR; 
} break; 
case CREATE_FD: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
pid_t pid = data.readInt32(); 
sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder());
int fd = dup(data.readFileDescriptor()); 
int64_t offset = data.readInt64(); 
int64_t length = data.readInt64(); 
sp<IMediaPlayer> player = create(pid, client, fd, offset, length); 
reply->writeStrongBinder(player->asBinder()); 
return NO_ERROR; 
} break; 
case DECODE_URL: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
const char* url = data.readCString(); 
uint32_t sampleRate; 
int numChannels; 
int format; 
sp<IMemory> player = decode(url, &sampleRate, &numChannels, &format); 
reply->writeInt32(sampleRate); 
reply->writeInt32(numChannels); 
reply->writeInt32(format); 
reply->writeStrongBinder(player->asBinder()); 
return NO_ERROR; 
} break; 
case DECODE_FD: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
int fd = dup(data.readFileDescriptor()); 
int64_t offset = data.readInt64(); 
int64_t length = data.readInt64(); 
uint32_t sampleRate; 
int numChannels; 
int format; 
sp<IMemory> player = decode(fd, offset, length, &sampleRate, &numChannels, &format); 
reply->writeInt32(sampleRate); 
reply->writeInt32(numChannels); 
reply->writeInt32(format); 
reply->writeStrongBinder(player->asBinder()); 
return NO_ERROR; 
} break; 
case CREATE_MEDIA_RECORDER: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
pid_t pid = data.readInt32(); 
sp<IMediaRecorder> recorder = createMediaRecorder(pid); 
reply->writeStrongBinder(recorder->asBinder()); 
return NO_ERROR; 
} break; 
case CREATE_METADATA_RETRIEVER: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
pid_t pid = data.readInt32(); 
sp<IMediaMetadataRetriever> retriever = createMetadataRetriever(pid); 
reply->writeStrongBinder(retriever->asBinder()); 
return NO_ERROR; 
} break; 
default: 
return BBinder::onTransact(code, data, reply, flags); 



    首先是一个宏定义CHECK_INTERFACE(),这个宏定义的作用是检查接口的描述字符串,这个前面也提到过,不需细说。然后就是onTrasact()函数的实现。这个函数的结构也很简单,就是根据参数code的值分别执行不同的功能调用。code的取值就是前面提到过的接口功能代码。函数的参数除了code,还包括Parcel类的两个对象data和reply,分别用于传送输入参数和返回数据,与transact()函数的参数相对应。还有一个参数flag在这里用不上,不讨论。对应我们前面所选择的接口函数的例子create(url),看看这边对应的实现:
case CREATE_URL: { 
CHECK_INTERFACE(IMediaPlayerService, data, reply); 
pid_t pid = data.readInt32(); 
sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder()); 
const char* url = data.readCString(); 
sp<IMediaPlayer> player = create(pid, client, url); 
     reply->writeStrongBinder(player->asBinder()); 
return NO_ERROR; 


    首先是从data对象中依次取出各项输入参数,然后调用接口函数create()(将在子类MediaPlayerService中实现),最后向reply中写入返回数据。这个函数返回后,代理那边的transact()也会跟着返回。
    那么onTransact()函数是怎么被调用的呢?通过查看BnInterface模板类的定义可以看到,这个类也是一个多重继承类,另一个父类是BBinder(frameworks\base\include\utils\Binder.h,frameworks\base\libs\utils\Binder.cpp)。BBinder类继承自IBinder,也实现了transact()函数,在这个函数中调用onTransact()函数。而BBinder对象的transact()函数则是在IPCThreadState类的executeCommand()成员函数中调用的。这已经涉及到较底层的实现,在这里不再多说。
    上面这部分代码还与前面提到过的BpBinder对象的创建有关系。看其中的红色字体部分,通过create()函数调用会创建一个IMediaPlayer接口类的子类的对象,这个对象其实是MediaPlayerService::Client类(可以看一下MediaPlayerService的定义)的对象实例,而MediaPlayerService::Client类是继承自BnMediaPlayer类的,与BnMediaPlayerService类类似,BnMediaPlayer其实也是一个binder实现类(是BBinder的子类,进而也是IBinder的子类)。在上述代码中,通过Parcel的writeStrongBinder()函数将这个对象写入reply,而在代理侧,通过Parcel的readStrongBinder()函数读取则可以得到一个BpBinder的对象。至于类的具体创建过程已经封装在Parcel类的定义中,这里就不再多说了。

(4) 接口功能的真正实现
    到这里两个binder类就已经定义完了,下面就是IMediaPlayerService接口函数的真正实现。前面已经说过这些函数在类MediaPlayerService中实现。这个类继承自BnMediaPlayerService,也间接地继承了IMediaPlayerService接口类定义的6个功能函数,只需要按照正常方式实现这6个功能函数即可,当然为了实现这6个函数就需要其它一大堆的东西,不过这些具体的实现方法已经与binder机制无关,不再多说。
   在MediaPlayerService类中定义了一个静态函数instantiate(),在这个函数中创建MediaPlayerService的对象实例,并将这个对象注册到服务管理器中。这样需要使用的时候就可以从服务管理器获取IMediaPlayerService的代理对象。这个instantiate()是在MediaServer程序的main()函数中调用的。
void MediaPlayerService::instantiate() { 
defaultServiceManager()->addService( 
String16("media.player"), new MediaPlayerService()); 



(三) 总结一下
    说了这么多,总结一下。下图是binder机制的层次模型。

    如果一个服务需要通过binder机制对外提供跨进程的接口,需要做下面这些事情。
    (1) 第一步,需要为这个接口定义一个继承自IInterface的接口类,假设叫做IMyService。
    (2) 第二步,需要定义两个binder类,其中一个是代理类BpMyService,需继承自BpInterface;另一个是实现类BnMyService,需继承自BnInterface。
    (3) 第三步,定义BnMyService的子类,这个子类可以是任何名字,比如就叫MyService,在其中真正实现接口所提供的各个函数。

    (4) 第四步,创建MyService的实例,注册到服务管理器(如IMediaPlayerService),也可以在其它接口的函数中创建(如上面的IMediaPlayer)。


_____________________________________________________________________

Binder, BBinder和BpBinder

<iframe id="iframeu1788635_0" src="http://pos.baidu.com/bcpm?rdid=1788635&dc=2&di=u1788635&dri=0&dis=0&dai=2&ps=236x1186&dcb=BAIDU_SSP_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1466506446368&ti=IBinder%2C%20BBinder%E5%92%8CBpBinder%20%7C%20%E5%AD%A6%E6%AD%A5%E5%9B%AD&ari=1&dbv=2&drs=1&pcs=1920x979&pss=1920x256&cfv=0&cpl=5&chi=1&cce=true&cec=UTF-8&tlm=1466506446&ltu=http%3A%2F%2Fwww.xuebuyuan.com%2F1547905.html&ltr=https%3A%2F%2Fwww.google.com.hk%2F&ecd=1&psr=1920x1080&par=1920x1040&pis=-1x-1&ccd=24&cja=false&cmi=7&col=zh-CN&cdo=-1&tcn=1466506446&qn=52f08edfec8cc41d&tt=1466506446328.110.1274.1274" width="336" height="280" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="margin: 0px; padding: 0px; border-width: 0px; border-style: initial; vertical-align: bottom; background: transparent;"></iframe>

这3个类,是对Android Binder框架的抽象,其实这个BBinder,改成BnBinder可能更形象一些。

但是要注意的是,一个IXXXService的继承图中,BpBinder并不在这个继承关系之中,也就是说BpBinder并没有子类。但是BBinder是在这个继承关系当中的,它的子类就是BnInterface。

换句话说,BBinder和BpBinder的功能并不是对称的,以前就是没有理解到这一点,才会一直很糊涂。

BpBinder的是存在于BpRefBase中的mRemote的成员变量中。从Client调用Service的过程中分析,就更清楚了。

假设有一个IXXXService接口:

class IXXXService : public IInterface {

....

public void helloWorld(const char* str);

....

}

(1)client调用service

client得到一个BpXXXService以后

(a)会调用BpXXXService实现的helloWorld,它会将str参数打包到Parcel中。然后调用remote()->transact(xxx)

(b)remote()是在BpXXXService的父类BpRefBase中实现的,返回的就是一个BpBinder.实际上调用的就是BpBinder的transact

(c)BpBinder的transact实现,就是直接调用IPCThreadState::self()->transact()发送数据。

(2)service接收client请求:

(a)通过IPCThreadState接收到client的请求后,首先会调用BBinder的transact方法。

(b)BBinder的transact方法又会调用子类实现的虚拟方法onTransact。这个虚拟方法是在BnXXXService中实现的。

(c)onTransact方法,会通过传递进来的参数来判断,需要调用IXXXService中的那个方法,示例中只有一个helloWorld方法。

(d)直接调用helloWorld,就会找到它的真正实现,也就是BnXXXService的子类XXXService中的helloWorld方法。

总结一下,从上面的流程当中就可以看出前文说的,BpBinder并不在继承关系当中,它只是一个打包数据,并通过IPCThreadState::self()->transact()方法发送出去。

而BBinder和BnXXXService的作用,就是接收IPCThreadState传递过来的信息,解包数据,并调用XXXService真正的实现。

IPC的数据处理,Binder Driver和ServiceManager学习后会继续分析总结。


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

相关文章

  1. 韩顺平循序渐进学JAVA从入门到精通 视频全套,需要的联系我

    0讲-开山篇.avi 10讲-访问修饰符.重载.覆盖.avi 11讲-约瑟夫问题.avi 12讲-多态.avi 13讲-抽象类.接口.avi 14讲-final.作业评讲.avi 15讲-作业.测试题.avi 16讲-数组.avi 17讲-排序.avi 18讲-排序.查找.avi 19讲-多维数组.avi 1讲-内容介绍.项目演示.原理剖析.avi …...

    2024/5/1 22:42:10
  2. 电影《叶问4》终结篇终于来袭,这次甄子丹与美军特种部队对打!

    https://www.toutiao.com/a6682731940757373448/2019-04-24 06:45:00《叶问》系列电影,分别在2008年、2010年、2015年上映。连续讲述了叶问从19世纪30年代到60年代初,时间跨度长达30年,使咏春扬名的部分经历。第一部,讲述三四十年代,叶问使咏春在佛山打响,在抗日战争期间…...

    2024/5/2 1:13:09
  3. C++ socket TCP网络编程

    0.通信步骤流程图(左:服务器;右:客户端;)1.服务器代码1.1 服务器类头文件(CServer_Wins.h)#pragma once#include <winsock2.h>class CServer_Wins { public:CServer_Wins();void RecMsg();void stopRec();~CServer_Wins(); private:bool m_terminal;SOCKET m_sli…...

    2024/5/2 2:36:39
  4. [HTML] 表单提交与阻止表单提交

    1. submit按钮<input type="submit">按钮不在<form></form>中不会提交表单。2. <button>按钮<button>text<button>相当于submit按钮,在<form></form>中点击,会提交表单。3. 阻止submit默认事件submit按钮click事件…...

    2024/5/1 21:34:55
  5. 理解Android Binder机制(3/3):Java层

    本文是Android Binder机制解析的第三篇,也是最后一篇文章。本文会讲解Binder Framework Java部分的逻辑。Binder机制分析的前面两篇文章,请移步这里:理解Android Binder机制(1/3):驱动篇理解Android Binder机制(2/3):C层):驱动篇下文所讲内容的相关源码,在AOSP源码树中的…...

    2024/5/2 2:22:22
  6. 如何写一个清晰明了的Bug

    bug是不可避免。但如何让自己的bug写得清新脱俗,结构清楚则是需要我们不断努力的。在开始今天的话题之前,先抛出一个问题,代码结构好是好事吗?代码结构好事好事吗?该图是我的票圈里一位兄弟转发的。代码结构好了,别人接手容易,反倒是写得烂了,却可以成为焦点。你咋一听…...

    2024/5/2 2:25:48
  7. 用button的onlick事件打开一个新的网页

    我用button的onlick事件打开一个新的网页,怎么做???<button οnclick="javascript:window.open([url][,name][,features][,true | false])">Label</button><%@ Language=VBScript %> <HTML> <HEAD> <META NAME="GENERATOR…...

    2024/5/1 23:28:46
  8. 树莓派 Python 网络编程 (Socket入门)

    树莓派 Python 网络编程 (Socket入门)什么是 Socket?Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。Python 提供了两个级别访问的网络服务。:低级别的网络服务支持基本的 S…...

    2024/4/17 4:50:54
  9. 『微信小程序』优秀教程、轮子、开源项目 资源汇总,长期维护更新中... ... ...

    微信小程序征服指南『微信小程序』优秀教程、轮子、开源项目 资源汇总,长期维护更新中...微信小程序征服指南 『微信小程序』优秀教程、轮子、开源项目 资源汇总,长期维护更新中...本项目地址:http://weapp.masterstudio.tech/...

    2024/4/17 4:51:48
  10. socket编程实现简单DNS协议实现获取域名ip(TCP)

    http://www.isayme.org/socket-tcp-dns-ping-ip.html上次在文章《socket编程实现简单DNS协议实现获取域名ip(UDP)》中提到使用udp协议发送dns数据包查询站点ip,这次带来TCP版本的查询代码。 其实不管是tcp协议还是udp协议,都是构造dns报文,填写查询方式,发送数据包即可。…...

    2024/4/17 4:52:06
  11. Android系统进程间通讯之Binder机制(一)

    Android系统进程间通讯之Binder机制(一)----理论篇首先我们知道Android是基于Linux内核的,而Linux内核继承和兼容了丰富的Unix系统进程间通讯(IPC:Internet Process Connection),其中包括:1、Pipe:管道2、Signal:信号3、Trace:跟踪以上是传统的通讯手段,并且只能用…...

    2024/4/17 4:52:00
  12. 给button绑定click事件时,出现ajax请求时,一次点击两次提交的问题解决

    原文链接:http://blog.csdn.net/qingche456/article/details/62059433ajax请求时,一次点击两次提交的问题解决原代码<script>$(function(){$(#jsStayBtn).on(click, function(){$.ajax({cache: false,type: "POST",url:"/org/add_ask/",data:$(#js…...

    2024/4/28 18:46:42
  13. MongoDB——从入门到精通

    关于mongodb的好处,优点之类的这里就不说了,唯一要讲的一点就是mongodb中有三元素:数据库,集合,文档,其中“集合”就是对应关系数据库中的“表”,“文档”对应“行”。 一: 下载 上MongoDB官网 ,我们发现有32bit和64bit,这个就要看你系统了,不过这里有两点注意…...

    2024/5/1 21:34:36
  14. VC++ MFC socket编程(转)

    socket编程用法---- 随着计算机网络化的深入,计算机网络编程在程序设计的过程中变得日益重要。由于C++语言对底层操作的优越性,许多文章都曾经介绍过用VC++进行Socket编程的方法。但由于都是直接利用动态连接库wsock32.dll进行操作,实现比较繁琐。其实,VC++的MFC类库中提供…...

    2024/5/1 23:17:54
  15. 降薪去BAT是否值得?

    一个在创业公司拿高薪的小伙伴问:降薪去BAT是否值得? 答案是:不一定,需要具体情况具体分析。但怎么分析呢?得有一个框架。 我仔细琢磨过类似的工作机会选择难题,提炼出一个简单的框架——职场发展5要素,组合起来可以很好的解答这个问题。我们先来介绍职场发展 5 要素,文…...

    2024/4/19 13:35:29
  16. CRMEB 小程序版源码

    CRMEB小程序开源版码云 :https://gitee.com/ZhongBangKeJi/CRMEB Git clone https://gitee.com/ZhongBangKeJi/CRMEB.git github:https://github.com/sugar1569/CRMEBhttps://github.com/sugar1569/CRMEB.git 高级版待拼团、和砍价功能,请到淘宝店购买淘宝店地址:https://s.c…...

    2024/4/17 4:52:06
  17. html登陆按钮和注册按钮

    <html> <head> <meta http-equiv="content-type" content="text/html;charset=GBK"> <script type="text/javascript">/*** 注册*/function regist() {// 如果需要在新窗口打开注册界面;// window.open(Untitled-2.htm…...

    2024/4/17 4:52:00
  18. 知识总结 插件化学习 Binder机制原理

    Binder是android系统特有IPC方式,安卓平台中的各种服务交互基本都是Binder机制实现,理解和掌握Binder机制的实现原理可有效提升软件性能优化点,同时Binder机制的应用也是动态代理方式实现插件化基础。安卓插件化学习 Binder机制原理分析网上虽然已有很多Binder类似的优秀分析…...

    2024/3/31 21:04:12
  19. 这10部功夫片曾拿到金像奖最佳动作设计奖,李连杰主演的就有4部

    https://www.toutiao.com/a6701812089607946756/2019-06-16 17:17:26武侠片,功夫片和动作片是华语电影献给世界观众的一份“礼物”,也是当年香港电影工业里极其重要的片种类型。为鼓励从业者继续拍出好的类型电影,表彰他们为此所做出的突出贡献,因此从第二届香港电影金像奖…...

    2024/4/24 8:33:54
  20. 大数据教程,Flink框架教程,flink从入门到精通

    Apache Flink是由Apache软件基金开发的开源流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎。Flink以数据并行和流水线方式执行任意流数据程序,Flink的流水线运行时系统可以执行批处理和流处理程序。此外,Flink的运行时本身也支持迭代算法的执行在大数据的学习过…...

    2024/4/30 7:40:15

最新文章

  1. SCI一区 | MFO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测(Matlab)

    SCI一区 | MFO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测&#xff08;Matlab&#xff09; 目录 SCI一区 | MFO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测&#xff08;Matlab&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现MFO-CNN…...

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

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

    2024/3/20 10:50:27
  3. HiveSQL如何生成连续日期剖析

    HiveSQL如何生成连续日期剖析 情景假设&#xff1a; 有一结果表&#xff0c;表中有start_dt和end_dt两个字段&#xff0c;&#xff0c;想要根据开始和结束时间生成连续日期的多条数据&#xff0c;应该怎么做&#xff1f;直接上结果sql。&#xff08;为了便于演示和测试这里通过…...

    2024/5/1 10:20:40
  4. 从头开发一个RISC-V的操作系统(二)RISC-V 指令集架构介绍

    文章目录 前提ISA的基本介绍ISA是什么CISC vs RISCISA的宽度 RISC-V指令集RISC-V ISA的命名规范模块化的ISA通用寄存器Hart特权级别内存管理与保护异常和中断 目标&#xff1a;通过这一个系列课程的学习&#xff0c;开发出一个简易的在RISC-V指令集架构上运行的操作系统。 前提…...

    2024/5/1 12:59:53
  5. 磁盘管理与文件管理

    文章目录 一、磁盘结构二、MBR与磁盘分区分区的优势与缺点分区的方式文件系统分区工具挂载与解挂载 一、磁盘结构 1.硬盘结构 硬盘分类&#xff1a; 1.机械硬盘&#xff1a;靠磁头转动找数据 慢 便宜 2.固态硬盘&#xff1a;靠芯片去找数据 快 贵 硬盘的数据结构&#xff1a;…...

    2024/5/1 13:00:58
  6. 【外汇早评】美通胀数据走低,美元调整

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

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

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

    2024/4/30 18:14:14
  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/4/30 18:21:48
  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/4/25 18:39:16
  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/4/30 9:43:22
  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