1.Binder机制简介

Android Binder是源于Palm的OpenBinder,它为android设备跨进程访问而设计。Binder机制从表现上看可以实现java 和native层的对象实例可以从一个进程访问到另一个进程,从而透明的实现跨进程的调用。调用者感觉不到自己的请求是在另一个进程中完成并返回结果的。在android系统中使用Binder机制最多的是系统服务,这些服务一般运行在特定的进程中。服务的使用者成为Client,Client一般是应用程序,是跨进程访问的请求发起者。运行行在特定进程中的服务被成为server,这些server用于接受client端发起的请求并处理具体相关业务并返回结果。

Android系统中的Binder机制被涉及为java和c++层两部分,使得java和native层都可以实现Binder的client、server端通过系统的/dev/binder虚拟设备提供跨进程发起请求和接受请求处理业务返回结果的能力。其中java层的Binder是对C++层Binder机制的上层封装,通过JNI的方式调用到C++的实现逻辑中。

我将通过分别分析C++层的实现和java的封装两部分分析Binder机制。

2.C++层Binder的实现

我通过分析MediaServer的实现逻辑通过阅读源码了解一下Binder的实现逻辑.MediaServer执行程序的入口是main()方法:

.../frameworks/av/media/mediaserver/main_mediaserver.cpp
int main(int argc __unused, char** argv)
{.....................//获取一个ProcessState对象,同时通过pen_driver()打开/dev/binder的虚拟设备,通过mmap()为binder分配一块空间用于通信读写,通过//iotrl()为binder指定线程数sp<ProcessState> proc(ProcessState::self());//获取一个IServerManager对象,用于服务注册sp<IServiceManager> sm = defaultServiceManager();ALOGI("ServiceManager: %p", sm.get());//初始化音频系统AudioFlingerAudioFlinger::instantiate();//多媒体服务初始化MediaPlayerService::instantiate();ResourceManagerService::instantiate();//cameraServer 相机服务CameraService::instantiate();//音频系统AudioPolicy服务AudioPolicyService::instantiate();SoundTriggerHwService::instantiate();RadioService::instantiate();registerExtensions();//创建线程池?ProcessState::self()->startThreadPool();//加入线程池?IPCThreadState::self()->joinThreadPool();....................
}

1.1ProcessState的作用

从main_mediaServer.cpp的main()方法中可以看到 sp proc(ProcessState::self())获取一个ProcessState对象,先看一下ProcessState::self()函数都做了什么:

sp<ProcessState> ProcessState::self()
{Mutex::Autolock _l(gProcessMutex);if (gProcess != NULL) {return gProcess;}//创建一个新的ProcessState对象gProcess = new ProcessState;return gProcess;
}

实现很简单,self()方法就是一个简单的单例调用new 创建了一个ProcessState对象。那看一下ProcessState的构造方法都做了些什么:

.../frameworks/native/libs/binder/ProcessState.cppProcessState::ProcessState(): mDriverFD(open_driver())          //打开Binder, mVMStart(MAP_FAILED)              //映射内存的的起始地址, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER), mThreadCountDecrement(PTHREAD_COND_INITIALIZER), mExecutingThreadsCount(0), mMaxThreads(DEFAULT_MAX_BINDER_THREADS), mManagesContexts(false), mBinderContextCheckFunc(NULL), mBinderContextUserData(NULL), mThreadPoolStarted(false), mThreadPoolSeq(1)
{if (mDriverFD >= 0) {
#if !defined(HAVE_WIN32_IPC)//为Binder分配一块内存用于存取数据// mmap the binder, providing a chunk of virtual address space to receive transactions.mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);if (mVMStart == MAP_FAILED) {// *sigh*ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");close(mDriverFD);mDriverFD = -1;}
#elsemDriverFD = -1;
#endif}LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}

在ProcessState的构造函数中首先调用了open_driver()方法初始化了一个ProcessState的变量mDriverFD,这个open_driver()方法很重要,它打开/dev/binder这个虚拟设备为binder通信做准备。在打开binder设备后又调用系统函数mmap() 为打开的binder设备在内存中映射了一块内存空间并将内存地址返回给变量mVMStart。

那先看一下open_driver()是怎么样打开binder驱动设备的。

.../frameworks/native/libs/binder/ProcessState.cpp
static int open_driver()
{//打开/dev/binder虚拟设备int fd = open("/dev/binder", O_RDWR);```````````````````````//通过ioctl()告诉驱动最多支持的线程数 15size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);return fd;
}

在open_driver()中直接调用了linux系统函数open()打开了/dev/binder设备并为启动的Binder虚拟设备通过ioctl()[系统函数]为binder 设备指定了最大线程数;

分析到这里基本上就是main_mediaserver–>main()函数中sp proc(ProcessState::self())这句代码做的事情。总结起来主要做了两件事情:

  • open_driver()打开/dev/binder虚拟设备驱动
  • 为打开的Binder虚拟设备分配内存映射空间

那下边继续回到main_mediaserver的main()方法中继续分析,看在获取到ProcessState后的sp sm =defaultServiceManager()干了什么。

defaultServerManager

defaultServerManager()函数会返回一个实现了IServerManager接口的对象,通过这个对象我们可以和另外一个进程的ServerManager进行通信.先看一下defaultServerManager()方法都干了点什么:

.../frameworks/native/libs/binder/IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{if (gDefaultServiceManager != NULL) return gDefaultServiceManager;{AutoMutex _l(gDefaultServiceManagerLock);while (gDefaultServiceManager == NULL) {//gDefaultServiceManager对象创建的地方gDefaultServiceManager = interface_cast<IServiceManager>(ProcessState::self()->getContextObject(NULL));if (gDefaultServiceManager == NULL)sleep(1);}}return gDefaultServiceManager;
}

这个方法在IServerManager.cpp中定义的,这个方法又是一个单例来获取一个IServerManger对象。内容很简单但是当我看到interface_cast(…)后我感觉一点都不好,但是它是很重要的不过暂时先不管它。先看看ProcessState::self()->getContextObject(NULL)方法都干了点什么:

.../frameworks/native/libs/binder/ProcessState.cppsp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{//好吧,这里直接调用了getStrongProxyForHande()方法。//但是传入的参数 0很重要return getStrongProxyForHandle(0);
}sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{sp<IBinder> result;AutoMutex _l(mLock);//资源查找,找不到会创建一个新的对象handle_entry* e = lookupHandleLocked(handle);if (e != NULL) {IBinder* b = e->binder;//新创建的binder 一定为空if (b == NULL || !e->refs->attemptIncWeak(this)) {if (handle == 0) {························Parcel data;//这里是一次IPC的binder通信过程,暂时不分析//我们现在分析的是getStrongProxyForHander()方法返回的是什么status_t status = IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0);if (status == DEAD_OBJECT)return NULL;}//创建一个BpBinder对象b = new BpBinder(handle); e->binder = b;      //填充if (b) e->refs = b->getWeakRefs();result = b;···············}return result;
}

在getStrongProxyForHandle()函数中首先调用了lookupHandleLocked(0)方法获取一个handle_entry 类型的结构指针。lookupHandleLocked()就是在一个Vector中查找对象找不到就创建一个,不重要先忽略。然后这里创建了一个BpBinder对象,根据名字看应该是和Binder机制有关的我们先可以一下BpBinder的构造方法:

.../frameworks/native/libs/binder/BpBinder.cpp//BpBinder构造函数
BpBinder::BpBinder(int32_t handle): mHandle(handle)           //handle 为 0, mAlive(1), mObitsSent(0), mObituaries(NULL)
{```````````````//另外一个重要对象?IPCThreadState::self()->incWeakHandle(handle);
}

在BpBinder的构造函数中出现了一个新对象IPCThreadState,对象名字竟然以IPC应该是和Binder的跨进程通信又关系的。

.../frameworks/native/libs/binder/IPCThreadState.cppIPCThreadState* IPCThreadState::self()
{if (gHaveTLS) {
restart:const pthread_key_t k = gTLS;//TLS = thread location storage 线程本地空间 是线程独有空间//getSpecific()和setSpecific()可以操作这部分空间IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);if (st) return st;//返回一个创建的IPCThreadState对象return new IPCThreadState;}if (gShutdown) return NULL;````````goto restart;
}

当我第一次看到这个方法时还是有点震惊的,我虽然没有学习过C++但是大一那会儿学习过C语言。当时老师就告诉我们尽量少用goto这个逻辑跳转。这里竟然用了。

这个方法中IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k)是从线程的TLS中获取一个IPCThreadState对象指针,第一次调用市肯定是不存在这个对象的所以会new 一个IPCThreadState对象。

TLS是操作系统为每个线程都单独分配的一块内存空间,当前线程独占其他线程无法访问。所以在多线程下对它的访问的线程安全的。

既然这里调用了IPCThreadState的构造函数我们就看看它长什么样子:

.../frameworks/native/libs/binder/IPCThreadState.cppIPCThreadState::IPCThreadState(): mProcess(ProcessState::self()),       //获取上边我们创建的ProcessState对象mMyThreadId(gettid()),mStrictModePolicy(0),mLastTransactionBinderFlags(0)
{//在构造函数中将自己设置到TLS中pthread_setspecific(gTLS, this);clearCaller();mIn.setDataCapacity(256);mOut.setDataCapacity(256);
}

IPCThreadState的构造函数很简单就是创建自己后将自己设置到当前线程的TLS中,下次就可以直接从当前线程的TLS中获取了。

我们分析了这么多其实都是gDefaultServiceManager = interface_cast(ProcessState::self()->getContextObject(NULL))的getContextObject(null)方法开始分析。好吧,要不是不是回到看一下我都已经不知道自己现在在分析什么了。那现在我们知道了getContextObject()返回的是一个BpBinder对象。那我们再看看interface_cast()设个什么东西:

.../frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{return INTERFACE::asInterface(obj);
}

原来interface_cast()是一个定义在IInterface.h头文件中的模范方法,那我们现在根据我们上边的调用翻译一下这个模版方法现在的样子:

inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj) {return IServiceManager::asInterface(obj);
}

实话说分析到这里我有点懵了不知道怎么分析了. 既然模版方法中调用了IServiceManager::asInterface()方法我们就先看看IServiceManager接口的内容:

.../frameworks/native/include/binder/IServiceManager.hclass IServiceManager : public IInterface
{
public://这个鸿定义在IInterface.h中,他主要做了如下工作//1.定义了一个描述字符串//2.定义了一个asInterface函数//3.定义了一个getInterfaceDescriptor函数用于返回上边定义的描述字符串//4.定义了构造函数和析构函数DECLARE_META_INTERFACE(ServiceManager);virtual sp<IBinder>         getService( const String16& name) const = 0;virtual sp<IBinder>         checkService( const String16& name) const = 0;virtual status_t            addService( const String16& name,const sp<IBinder>& service,bool allowIsolated = false) = 0;virtual Vector<String16>    listServices() = 0;enum {GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,CHECK_SERVICE_TRANSACTION,ADD_SERVICE_TRANSACTION,LIST_SERVICES_TRANSACTION,};
};

我们都知道Android系统中的系统服务像AMS、WMS等都是有ServiceManager统一管理的。这里的IServiceManager.h就是定义了他的功能接口。在这个接口定义中我们看到一个宏DECLARE_META_INTERFACE(ServiceManager)的使用,那我们先分析一下这个宏定义是什么样子:

#define DECLARE_META_INTERFACE(INTERFACE)                               \static const android::String16 descriptor;                          \static android::sp<I##INTERFACE> asInterface(                       \const android::sp<android::IBinder>& obj);                  \virtual const android::String16& getInterfaceDescriptor() const;    \I##INTERFACE();                                                     \virtual ~I##INTERFACE();   

同样我还是先翻译一下这个宏在当前使用场景的样子:

    static const android::String16 descriptor;static android::sp<IServiceManager> asInterface(const android::sp<IBinder>& obj);virtual const android::String16 getInterfaceDescriptor() const;IServiceManager();virtual ~IServiceManager();

从对DECLARE_META_INTERFACE宏的翻译可以看出这是属性和方法的声明;

通过上边的对那个IServiceManager.h和对DECLARE_META_INTERFACE的翻译我们已经知道了IServiceManager接口定义的样子我们下面看一下他的具体实现,但是具体实现内容比较多我抽取其中重要部分分析一下:

.../frameworks/native/libs/binder/IServiceManager.cpp............省略.............//IMPLEMENT_META_INTERFACE这个宏是定义在IInterface.h文件中的
//1.定义常量字符串android.os.IServiceManager
//2.实现getInterfaceDescriptor()函数
//3.实现asInterface()函数,并返回一个BpServiceManager对象
IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");status_t BnServiceManager::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{//printf("ServiceManager received: "); data.print();switch(code) {...........继续省略..............}
}

在IServiceManager的实现中真正让我感兴趣的这个IMPLEMENT_META_INTERFACE 宏的使用。同样我们找找这个宏的定义并将这个宏翻译一下:

宏定义在IInterface.h文件中
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \const android::String16 I##INTERFACE::descriptor(NAME);             \const android::String16&                                            \I##INTERFACE::getInterfaceDescriptor() const {              \return I##INTERFACE::descriptor;                                \}                                                                   \android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \const android::sp<android::IBinder>& obj)                   \{                                                                   \android::sp<I##INTERFACE> intr;                                 \if (obj != NULL) {                                              \intr = static_cast<I##INTERFACE*>(                          \obj->queryLocalInterface(                               \I##INTERFACE::descriptor).get());               \if (intr == NULL) {                                         \intr = new Bp##INTERFACE(obj);                          \}                                                           \}                                                               \return intr;                                                    \}                                                                   \I##INTERFACE::I##INTERFACE() { }                                    \I##INTERFACE::~I##INTERFACE() { }                                   \

感觉当前这个宏的使用场景翻译一下:

const android::String16 IServiceManager::descriptor("android.os.IServiceManager");
const android::String16& IServiceManager::getInterfaceDescriptor() const {return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(const android::sp<android::IBinder>& obj) {android::sp<IBinder> intr;if(obj != null) {//这里也很重要intr = static_cast<IServiceManager>(obj->queryLocalInterface(IServicManager:;descriptor).get());if(intr == NULL) {//重点看这里intr = new BpServieceManager(obj);}}return intr;//构造函数我就不翻译了。这翻译真要命
}

现在我们已经翻译完了***IMPLEMENT_META_INTERFACE***宏的内容现在将翻译后内容替换到IServiceManager.cpp中看看

.../frameworks/native/libs/binder/IServiceManager.cpp ............省略.............//IMPLEMENT_META_INTERFACE这个宏是定义在IInterface.h文件中的
//1.定义常量字符串android.os.IServiceManager
//2.实现getInterfaceDescriptor()函数
//3.实现asInterface()函数,并返回一个BpServiceManager对象
<!--IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");-->const android::String16 IServiceManager::descriptor("android.os.IServiceManager");
const android::String16& IServiceManager::getInterfaceDescriptor() const {return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(const android::sp<android::IBinder>& obj) {android::sp<IBinder> intr;if(obj != null) {//这里也很重要intr = static_cast<IServiceManager>(obj->queryLocalInterface(IServicManager:;descriptor).get());if(intr == NULL) {//重点看这里intr = new BpServieceManager(obj);}}return intr;//构造函数我就不翻译了。这翻译真要命status_t BnServiceManager::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{//printf("ServiceManager received: "); data.print();switch(code) {...........继续省略..............}
}

这就是翻译后的IServiceManager.cpp的样子我们重点看一下asInterface()方法, 这里这个方法竟然返回了一个BpServiceManager对象。还记得我们先变分析中哪里调用了这个asInterface()方法吗?没错就是我们在interface_cast()模版方法调用,现在我们知道在当前分析场景下interface_cast()返回的是一个***BpServiceManager***类型的对象。那么问题现在有来了,还记得interface_cast()方法是在哪里调用的吗?没错就是defaultServiceManager()方法中调用的。

现在终于将defaultServiceManager()函数分析完了,最终这个方法会返回一个BpServiceManager类型的对象(累死人了,分析了这么多终于知道了这个方法返回了什么。要疯了,特别想爆粗口)。那现在我是特别想知道BpServiceManager到底是个什么东西。但是现在不着急,defautServiceManager()方法引发这么多的联动分析我还是先回味一下defaultServiceManager()函数都干了些啥:

  • 首先调用了ProcessState::getContextObject(null)函数,但是这个函数什么都没有干就直接调用了自己的getStrongProxyForHandle(0)函数。在getStrongProxyForHandle()函数中创建了一个BpBinder对象。
  • 在BpBinder构造函数中又引出了一个重要的类IPCThreadState类。在这里调用了IPCThreadState::self();
  • 在IPCThreadState::self()函数中首先从线程的TLS中获取IPCThreadState对象。到那时在首次点用时TLS中并未存储,所以会调用它的构造方法创建一个IPCThreadState对象并将创建的对象保存到TLS中。以后就可以直接从TLS中获取。
  • interface_cast()模版方法中调用了IServiceManager的asInterface()函数。通过我们对模版方法、DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏的分析翻译知道最终asInterface()返回了一个BpServiceManager对象。

在整个defaultServiceManager()函数中涉及到BpBinder、ProcessState、IPCThreadState、BpServiceManager等新的类。我们有必要分析一下目前他们的关系如何。

BpBinder类图

现在我们可以根据类图关系再来分析一下他们。首先看一下BpServiceManager:

class BpServiceManager : public BpInterface<IServiceManager>
{
//impl参数是IBinder类型,实际上就是上边创建的BpBinder
public BpServiceManager(const sp<IBinder>& impl)//调用了父类的构造方法,参数impl 是 BpBinder: BpInterface<IServiceManager>(impl){}
}

BpServiceManager继承自BpInterface。现在看一下BpInterface:

模版类
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:BpInterface(const sp<IBinder>& remote);protected:virtual IBinder*            onAsBinder();
};----------------------------
模版方法
template<typename INTERFACE>
//remote参数是BpBinder
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)//调用父类的构造方法: BpRefBase(remote)
{
}

继续看一下BpRefBase的实现:

//mRemote最终等于BpBinder(0)对象
BpRefBase::BpRefBase(const sp<IBinder>& o)
//重点在这里,Bpbinder本传递给了mRemote.Binder的通信需要通过mRemote来完成。: mRemote(o.get()), mRefs(NULL), mState(0)
{extendObjectLifetime(OBJECT_LIFETIME_WEAK);if (mRemote) {mRemote->incStrong(this);           // Removed on first IncStrong().mRefs = mRemote->createWeak(this);  // Held for our entire lifetime.}
}

现在我们知道BpServiceManager实现了IServiceManager接口同时持有了BpBinder,BpBinder可以用于进程的通信。此时BpServiceManager是ServiceManager客户端的代理对象,BpBinder是Binder通信机制的Client端代表。

2. MediaPlayerService addService()

我们继续回到MediaServer的main()函数中继续分析。看看MediaPlayerService::instantiate()实现:

void MediaPlayerService::instantiate() {
//通过上边的分析我们已经知道defaultServiceManager()返回的是一个BpServiceManager对象。在这里调用了他的addService()方法。defaultServiceManager()->addService(String16("media.player"), new MediaPlayerService());
}

在instantiate()函数中调用了BpServiceManager的addService():

.../frameworks/native/libs/binder/IServiceManager.cpp的内嵌类BpServiceManagervirtual status_t addService(const String16& name, const sp<IBinder>& service,bool allowIsolated){Parcel data, reply;//data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());data.writeString16(name);data.writeStrongBinder(service);data.writeInt32(allowIsolated ? 1 : 0);//remote()函数返回的是mRemote,就是BpRefBase中的mRemote,即BpBinder对象。这里调用了BpBinder的transact()方法。status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);return err == NO_ERROR ? reply.readExceptionCode() : err;}

从BpServiceManager的addService()方法可以看出最终将调用转移到了BpBinder中。由此我们知道BpBinder才是代表client进行跨进程通信的代表。下面我们继续分析一下BpBinder;

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{// Once a binder has died, it will never come back to life.if (mAlive) {//BpBinder将通信委托给了IPCThreadState的transcat()方法status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);if (status == DEAD_OBJECT) mAlive = 0;return status;}return DEAD_OBJECT;
}

看到这里还是有点震惊的,我本来认为BpBinder已经是实现通信的客户端的代表了。现在发现它竟然将通信委托给IPCThreadState来完成。原来BpBinder还是个壳子。继续看一下IPCThreadState::transact():

status_t IPCThreadState::transact(int32_t handle,uint32_t code, const Parcel& data,Parcel* reply, uint32_t flags)
{status_t err = data.errorCheck();`````````````````````````````if (err == NO_ERROR) {LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),(flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");//发送Binder通信消息err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);}if (err != NO_ERROR) {if (reply) reply->setError(err);return (mLastError = err);}if ((flags & TF_ONE_WAY) == 0) {#if 0if (code == 4) { // relayoutALOGI(">>>>>> CALLING transaction 4");} else {ALOGI(">>>>>> CALLING transaction %d", code);}#endifif (reply) {//等待通讯结果err = waitForResponse(reply);} else {Parcel fakeReply;err = waitForResponse(&fakeReply);}````````````````````````} else {err = waitForResponse(NULL, NULL);}return err;
}

在IPCThreadState的transact()方法中有两个重要的方法调用, err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL)和err = waitForResponse(reply)。我们先来分析一下writeTransactionData()的实现:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{binder_transaction_data tr;         //binder_transaction_data 是binder通信的数据结构tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */tr.target.handle = handle;      //现在handle是0;0代表ServiceManager。tr.code = code;tr.flags = binderFlags;tr.cookie = 0;tr.sender_pid = 0;tr.sender_euid = 0;const status_t err = data.errorCheck();if (err == NO_ERROR) {tr.data_size = data.ipcDataSize();tr.data.ptr.buffer = data.ipcData();tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);tr.data.ptr.offsets = data.ipcObjects();} else if (statusBuffer) {tr.flags |= TF_STATUS_CODE;*statusBuffer = err;tr.data_size = sizeof(status_t);tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);tr.offsets_size = 0;tr.data.ptr.offsets = 0;} else {return (mLastError = err);}//mOut的类型是Pracel.它是准备发送给Binder的Server端。mOut.writeInt32(cmd);//将Binder通信的数据写入到mOut中mOut.write(&tr, sizeof(tr));return NO_ERROR;
}

从writeTransactionData()方法的分析发现里边主要做了数据准备并将数据序列化到Pracel中。

我们继续分析分析waitForResponse(reply)函数:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{uint32_t cmd;int32_t err;//循环处理while (1) {//重点talkWithDriver()if ((err=talkWithDriver()) < NO_ERROR) break;err = mIn.errorCheck();if (err < NO_ERROR) break;if (mIn.dataAvail() == 0) continue;cmd = (uint32_t)mIn.readInt32();IF_LOG_COMMANDS() {alog << "Processing waitForResponse Command: "<< getReturnString(cmd) << endl;}switch (cmd) {case BR_TRANSACTION_COMPLETE:if (!reply && !acquireResult) goto finish;break;case BR_DEAD_REPLY:err = DEAD_OBJECT;goto finish;case BR_FAILED_REPLY:err = FAILED_TRANSACTION;goto finish;case BR_ACQUIRE_RESULT:{ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT");const int32_t result = mIn.readInt32();if (!acquireResult) continue;*acquireResult = result ? NO_ERROR : INVALID_OPERATION;}goto finish;case BR_REPLY:{binder_transaction_data tr;err = mIn.read(&tr, sizeof(tr));ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");if (err != NO_ERROR) goto finish;if (reply) {if ((tr.flags & TF_STATUS_CODE) == 0) {reply->ipcSetDataReference(reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),tr.data_size,reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),tr.offsets_size/sizeof(binder_size_t),freeBuffer, this);} else {err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);freeBuffer(NULL,reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),tr.data_size,reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),tr.offsets_size/sizeof(binder_size_t), this);}} else {freeBuffer(NULL,reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),tr.data_size,reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),tr.offsets_size/sizeof(binder_size_t), this);continue;}}goto finish;default://重点err = executeCommand(cmd);if (err != NO_ERROR) goto finish;break;}}

BR_开头的case都是Binder Server端的,BC_开头的case是Binder Client端使用。
在IPCThreadState的waitForResponse()函数中重要的方法有talkWithDriver()和case 的default:分支中的executeCommand()函数。现在我们先分析一下talkWithDriver():

status_t IPCThreadState::talkWithDriver(bool doReceive)
{if (mProcess->mDriverFD <= 0) {return -EBADF;}binder_write_read bwr;          //binder通信的数据结构// Is the read buffer empty?const bool needRead = mIn.dataPosition() >= mIn.dataSize();// We don't want to write anything if we are still reading// from data left in the input buffer and the caller// has requested to read the next data.const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;bwr.write_size = outAvail;bwr.write_buffer = (uintptr_t)mOut.data();// This is what we'll read.if (doReceive && needRead) {bwr.read_size = mIn.dataCapacity();bwr.read_buffer = (uintptr_t)mIn.data();} else {bwr.read_size = 0;bwr.read_buffer = 0;}IF_LOG_COMMANDS() {TextOutput::Bundle _b(alog);if (outAvail != 0) {alog << "Sending commands to driver: " << indent;const void* cmds = (const void*)bwr.write_buffer;const void* end = ((const uint8_t*)cmds)+bwr.write_size;alog << HexDump(cmds, bwr.write_size) << endl;while (cmds < end) cmds = printCommand(alog, cmds);alog << dedent;}alog << "Size of receive buffer: " << bwr.read_size<< ", needRead: " << needRead << ", doReceive: " << doReceive << endl;}// Return immediately if there is nothing to do.if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;bwr.write_consumed = 0;bwr.read_consumed = 0;status_t err;//又是一个循环do {IF_LOG_COMMANDS() {alog << "About to read/write, write size = " << mOut.dataSize() << endl;}
#if defined(HAVE_ANDROID_OS)//通过ioctl()进行读写if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)err = NO_ERROR;elseerr = -errno;
#elseerr = INVALID_OPERATION;
#endifif (mProcess->mDriverFD <= 0) {err = -EBADF;}IF_LOG_COMMANDS() {alog << "Finished read/write, write size = " << mOut.dataSize() << endl;}} while (err == -EINTR);IF_LOG_COMMANDS() {alog << "Our err: " << (void*)(intptr_t)err << ", write consumed: "<< bwr.write_consumed << " (of " << mOut.dataSize()<< "), read consumed: " << bwr.read_consumed << endl;}if (err >= NO_ERROR) {//清空客户端的Pracel的内存空间if (bwr.write_consumed > 0) {if (bwr.write_consumed < mOut.dataSize())mOut.remove(0, bwr.write_consumed);elsemOut.setDataSize(0);}//将ioctl()从sever端获取的数据写入到mIn中准备发送给client端if (bwr.read_consumed > 0) {mIn.setDataSize(bwr.read_consumed);mIn.setDataPosition(0);}````````````return NO_ERROR;}return err;
}

完成Binder通信其实是通过Binder驱动的共享内存来完成不同进程之间的通信,从对talkWithDriver()函数分析来看完成对Binder驱动的共享内存的操作是ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)函数来完成的。ioctl()是一个linux系统API。对ioctl()方法调用是在do{}while()中调用的,由此可见这个ioctl()会多次调用来完成通信。在ioctl()完成后请求结果被放到binder_write_read类型的bwr中,然后将bwr的结果内容写入到Pracel的mIn中为下一步返回客户端做做准备。

我们在回到IPCThreadState::waitForResponse()函数中分析一下另外一个executeCommand(cmd)方法:

status_t IPCThreadState::executeCommand(int32_t cmd)
{BBinder* obj;RefBase::weakref_type* refs;status_t result = NO_ERROR;switch ((uint32_t)cmd) {case BR_ERROR:result = mIn.readInt32();break;case BR_OK:break;case BR_ACQUIRE:```````````break;case BR_RELEASE:```````````break;case BR_INCREFS:`````````````break;`````````case BR_TRANSACTION:{binder_transaction_data tr;result = mIn.read(&tr, sizeof(tr));ALOG_ASSERT(result == NO_ERROR,"Not enough command data for brTRANSACTION");if (result != NO_ERROR) break;Parcel buffer;buffer.ipcSetDataReference(reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),tr.data_size,reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);const pid_t origPid = mCallingPid;const uid_t origUid = mCallingUid;const int32_t origStrictModePolicy = mStrictModePolicy;const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;mCallingPid = tr.sender_pid;mCallingUid = tr.sender_euid;mLastTransactionBinderFlags = tr.flags;int curPrio = getpriority(PRIO_PROCESS, mMyThreadId);if (gDisableBackgroundScheduling) {if (curPrio > ANDROID_PRIORITY_NORMAL) {setpriority(PRIO_PROCESS, mMyThreadId, ANDROID_PRIORITY_NORMAL);}} else {if (curPrio >= ANDROID_PRIORITY_BACKGROUND) {set_sched_policy(mMyThreadId, SP_BACKGROUND);}}Parcel reply;status_t error;if (tr.target.ptr) {//BnService从BBinder派生//这里的B实际上实现了BnServiceXXX的对象sp<BBinder> b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply, tr.flags);} else {error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);}if ((tr.flags & TF_ONE_WAY) == 0) {LOG_ONEWAY("Sending reply to %d!", mCallingPid);if (error < NO_ERROR) reply.setError(error);sendReply(reply, 0);} else {LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);}mCallingPid = origPid;mCallingUid = origUid;mStrictModePolicy = origStrictModePolicy;mLastTransactionBinderFlags = origTransactionBinderFlags;}break;
·····································return result;
}

看到executeCommand()函数中那么多的switch分支真不知道从哪里分析,脑子已经懵懵的。还是从BR_TRANSACTION:这个分支看看吧。在这个分支中我们可以看到//BnService从BBinder派生sp b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply, tr.flags);我们又看到了一个BBinder类的对象,有我们从BpBinder是Binder Client的代表可以猜测BBinder可能是Binder的Server端的代表。实际上这里的b是BnSerciceManager对象。那先放下executeCommaond()函数,这里我们先来分析一下BBinder和BnServiceManaer是什么然后在回来分析:

```/frameworks/native/include/binder/IServiceManager.h
class BnServiceManager : public BnInterface<IServiceManager>
{
public:virtual status_t    onTransact( uint32_t code,const Parcel& data,Parcel* reply,uint32_t flags = 0);
};

开始看张类图直观一些,手画丑的不行:
image

BnServiceManager继承自BnInterface,再看一下BnInterface是什么:

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);virtual const String16&     getInterfaceDescriptor() const;protected:virtual IBinder*            onAsBinder();
};

BnInterface是个模版类继承自BBinder,在BnServiceManager继承BnInterface时模版类中的INTERFACE 是 IServiceManager。用于BnInterface继承了INTERFACE,所以BnServicManager继承了ISeviceManager接口。下面看一下BBinder的定义:

```/frameworks/native/include/binder/Binder.h
class BBinder : public IBinder
{
.............
}

这样以来继承关系已经很明确。再BnServiceManager的接口声明中重新声明了onTransact()函数并在IServiceManager.cpp中实现了它:

status_t BnServiceManager::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{//printf("ServiceManager received: "); data.print();switch(code) {case GET_SERVICE_TRANSACTION: {········} break;case CHECK_SERVICE_TRANSACTION: {········} break;case ADD_SERVICE_TRANSACTION: {CHECK_INTERFACE(IServiceManager, data, reply);String16 which = data.readString16();sp<IBinder> b = data.readStrongBinder();//在这里有调用了addService()完成真正的服务添加。status_t err = addService(which, b);reply->writeInt32(err);return NO_ERROR;} break;case LIST_SERVICES_TRANSACTION: {········return NO_ERROR;} break;default://调用父类默认实现return BBinder::onTransact(code, data, reply, flags);}
}

我们对BnServiceManager和BBinder的分析先分析到这里,回到executeCommond()方法中再结合sp b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply,tr.flags);调用bnServiceManager的transact()方法。以分析的addService()最终会进入到case ADD_SERVICE_TRANSACTION:分支当中并在这里调用了addService()方法。
***看到这里我是有点模糊的这个分支中的addService()方法的实现在哪里,也没有找到一个类继承自BnServiceManager。***后来网上看别人的分析发现还是有个文件实现了这里的ServiceManager的功能,具体实现在Service_manager.c中实现的。

不太明白为什么不继续继承BnServiceManager来实现具体逻辑呢?

ServiceManager

ServiceManager的实现在Service_manager.c中,先从这个文件的main()函数开始了解一下它干了啥:

int main(int argc, char **argv)
{struct binder_state *bs;//打开binder虚拟设备bs = binder_open(128*1024);if (!bs) {ALOGE("failed to open binder driver\n");return -1;}//角色转换if (binder_become_context_manager(bs)) {ALOGE("cannot become context manager (%s)\n", strerror(errno));return -1;}//重点,处理客户端发过来的请求binder_loop(bs, svcmgr_handler);return 0;
}

main()函数比较简单主要通过调用了binder_open()函数打开Binder的虚拟设备.先看一下binder_open()都做了些什么:

/打开Binder设备
struct binder_state *binder_open(size_t mapsize)
{struct binder_state *bs;struct binder_version vers;bs = malloc(sizeof(*bs));if (!bs) {errno = ENOMEM;return NULL;}//打开虚拟设备,调用Linux系统方法打开文件bs->fd = open("/dev/binder", O_RDWR);if (bs->fd < 0) {fprintf(stderr,"binder: cannot open device (%s)\n",strerror(errno));goto fail_open;}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驱动做内存映射bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);if (bs->mapped == MAP_FAILED) {fprintf(stderr,"binder: cannot map device (%s)\n",strerror(errno));goto fail_map;}return bs;fail_map:close(bs->fd);
fail_open:free(bs);return NULL;
}

我在这个方法中又看到了goto的使用。有点兴奋!

在open_binder()函数中打开了Binder驱动,并给起映射了内存空间。我们继续看一下binder_become_context_manager(bs)干了什么:

int binder_become_context_manager(struct binder_state *bs)
{return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

很简单但是我没有看太懂,主要是对ioctl()这个函数不太懂。大概是给打开的Binder设备指定了映射名称为0;(谁懂给我普及一下

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。百度百科

还有一个处理Binder请求的方法binder_loop(bs, svcmgr_handler):

//binder_handler 是个函数指针,func现在是svcmgr_handler
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;readbuf[0] = BC_ENTER_LOOPER;//在binder_write中调用了ioctl函数,调用Binder设备的函数,标志serviceManager进入的Loop 状态。binder_write(bs, readbuf, sizeof(uint32_t));//循环不断的for (;;) {bwr.read_size = sizeof(readbuf);bwr.read_consumed = 0;bwr.read_buffer = (uintptr_t) readbuf;//通过ioctl()方法从Binder设备中读取缓冲区,检查是不是又IPC请求。res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);if (res < 0) {ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));break;}//接受到请求,然后解析结果 调用func函数res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);if (res == 0) {ALOGE("binder_loop: unexpected reply?!\n");break;}if (res < 0) {ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));break;}}
}

在binder_loop()中让binder设备进入loop状态,通过循环不断通过ioctl()方法获取Binder设备是不是有IPC请求,如果有将获取到的数据交给binder_parse()函数进行解析.

int binder_parse(struct binder_state *bs, struct binder_io *bio,uintptr_t ptr, size_t size, binder_handler func)
{.................数据处理.............................case BR_TRANSACTION: {....................if (func) {unsigned rdata[256/4];struct binder_io msg;struct binder_io reply;int res;bio_init(&reply, rdata, sizeof(rdata), 4);bio_init_from_txn(&msg, txn);//调用了func()函数,有func()函数去做真正的服务管理res = func(bs, txn, &msg, &reply);binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);}ptr += sizeof(*txn);break;}...................................}}return r;
}

在binder_prase()方法的binder_handler func参数指向的是svcmgr_handler函数,下面看看这个函数:

.../frameworks/native/cmds/servicemanager/service_manager.c//binder 通信处理数据的回调函数
int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{struct svcinfo *si;uint16_t *s;size_t len;uint32_t handle;uint32_t strict_policy;int allow_isolated;//ALOGI("target=%p code=%d pid=%d uid=%d\n",//      (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid);if (txn->target.ptr != BINDER_SERVICE_MANAGER)return -1;if (txn->code == PING_TRANSACTION)return 0;strict_policy = bio_get_uint32(msg);s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}if ((len != (sizeof(svcmgr_id) / 2)) ||memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {fprintf(stderr,"invalid id %s\n", str8(s, len));return -1;}if (sehandle && selinux_status_updated() > 0) {struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();if (tmp_sehandle) {selabel_close(sehandle);sehandle = tmp_sehandle;}}switch(txn->code) {case SVC_MGR_GET_SERVICE:case SVC_MGR_CHECK_SERVICE:..........case SVC_MGR_ADD_SERVICE://注册服务s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}handle = bio_get_ref(msg);allow_isolated = bio_get_uint32(msg) ? 1 : 0;//注册服务真正工作的地方if (do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid))return -1;break;case SVC_MGR_LIST_SERVICES: {......}default:ALOGE("unknown code %d\n", txn->code);return -1;}bio_put_uint32(reply, 0);return 0;
}

在svcmgr_handler()函数中SVC_ADD_SERVICES分支中注册服务,其中注册服务的实际处理交给了do_add_service()方法继续处理。

int do_add_service(struct binder_state *bs,const uint16_t *s, size_t len,uint32_t handle, uid_t uid, int allow_isolated,pid_t spid)
{struct svcinfo *si;if (!handle || (len == 0) || (len > 127))return -1;//验证UID是否有添加服务的权限。if (!svc_can_register(s, len, spid)) {ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",str8(s, len), handle, uid);return -1;}//检查服务是不是已经注册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);}si->handle = handle;} else {//如果没有注册分配内存si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));//si这个结构体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->next = svclist;//插入链表svclist = si;}binder_acquire(bs, handle);binder_link_to_death(bs, handle, &si->death);return 0;
}//存储注册服务的结构体,是个链表结构
struct svcinfo
{struct svcinfo *next;uint32_t handle;struct binder_death death;int allow_isolated;size_t len;uint16_t name[0];
};

do_add_service()方法通过坚定要注册的服务是否有权限被注册,然后再检查服务是否已经注册了,如果没有被注册则将其插入到链表中。这样我们的服务注册工作已经完成。
C++层的Binder通信的分析基本上分析完了。想抽时间再分析一下Java层对c++层的封装。

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

相关文章

  1. 12种开源Web安全扫描程序

    开源Web安全扫描程序来查找漏洞赛门铁克的一个有趣的报告显示,76%的被扫描网站有恶意软件如果您使用的是WordPress,那么SUCURI的另一份报告显示,超过70%的被扫描网站被感染了一个或多个漏洞。作为网络应用程序所有者,您如何确保您的网站免受在线威胁的侵害?不泄露敏感信…...

    2024/5/2 1:07:25
  2. 扩大Button的点击区域

    Android——扩大ImageButton的点击区域遭遇问题:在布局文件xml中定义了一个ImageButton,因为图标尺寸很小导致很难有效点击。具体代码如下所示:<ImageButton android:id="@+id/imageButtonAlbum" android:layout_width="wrap_content" android:layou…...

    2024/5/1 23:55:52
  3. 阿里与腾讯人的区别,就是明教与武当的反差

    博主说:在中国,阿里和腾讯可以说是平分天下,但从骨子里,两者的文化却不尽相同,甚至可以说是截然相反。在读过「南七道」的文章之后,感觉有趣之余,却也认为其分析的头头是道。因此,在此分享这篇文章!至于文中观点,相信大家自有判断。正文阿里 18 周年庆典,4 万门徒从…...

    2024/4/17 4:58:12
  4. 真正的java从入门到精通。

    一、工具篇JDK (Java Development Kit)JDK是整个Java的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具和Java基础的类库(rt.jar)。不论什么Java应用服务器实质都是内置了某个版本的JDK。因此掌握JDK是学好Java的第一步。最主流的JDK是Sun公司发布的JDK,除…...

    2024/5/1 23:58:11
  5. java socket编程(1)——利用socket实现聊天之消息推送

    网上已经有很多利用socket实现聊天的例子了,但是我看过很多,多多少有一些问题存在。 这里我将实现一个比较完整的聊天例子,并解释其中的逻辑。 由于socket这一块比较大,所以我将分出几篇来写一个比较完整的socket例子。 这里我们先来实现一个最简单的,服务器与客户端通…...

    2024/5/2 3:08:33
  6. 1.javascript基础

    文章目录一.JS概述二.js的摆放位置三.变量四.基本类型和常量五.运算符六.函数定义和调用七.全局变量和局部变量八.全局函数九.面向对象十.参数传值十一.内置对象十二.数组十三.数组的属性与方法十四.数组深入十五.prototype 一.JS概述基本特点是一种解释性脚本语言(代码不进行…...

    2024/4/19 18:26:27
  7. Binder机制详解

    Binder机制 ​ Android系统中进程间通讯(IPC)的一种方式,Android中ContentProvider、Intent、aidl都是基于Binder 内存管理 ​ Binder最大只能传1M的数据,因为Binder驱动只预留了一段1M大小的虚拟地址 mmap中定义BINDER_VM_SIZE为1M,Binder数据需要跨进程传递,需要在内核…...

    2024/4/17 4:58:00
  8. 视频教程-SQL Server 2005从入门到精通实训-SQL Server

    SQL Server 2005从入门到精通实训19年软件开发经验,设计开发40多个大型软件,10年从事高等教育,主要为java系列课程,带你轻松进入java生涯。赖国荣49.00立即订阅订阅后:请点击此处观看视频课程 视频教程-SQL Server 2005从入门到精通实训-SQL Server学习有效期:永久观看学…...

    2024/5/1 21:07:43
  9. 人工智能/数据科学比赛汇总 2019.4

    Special Sponsors内容来自 DataSciComp,人工智能/数据科学比赛整理平台。 Github:iphysresearch/DataSciComp 本项目由 ApacheCN 强力支持。 微博 | 知乎 | CSDN | 简书 | OSChina | 博客园Classification of Normal vs Malignant Cells in B-ALL White Blood Cancer Microsc…...

    2024/5/2 2:49:17
  10. HTML小技巧:按钮中的文字换行

    一般按钮的文字都是一行的。但是有的时候画面需要按钮中的文字换行。 刚开始有个开发人员说没法实现。\r\n 都用过了没有效果。其实google这个老师是非常强大的。 直接换行的方法:<input type="button" value="第一行文字 第二行文字" />还有一种办…...

    2024/4/17 4:57:48
  11. 推荐一个微信小程序 UI 框架 lin-ui

    lin-ui 是慕课网七月老师团队的开源项目,七月老师可以算是我的小程序导师,当初为了学小程序,也是找了好多资料,看了官方文档也是一知半解,最后在慕课网看了老师的课程后果断入坑,老师的课入坑绝对不坑!lin-ui 的GitHub地址 lin-ui lin-ui 文档 地址 lin-ui doc用过 lin-…...

    2024/4/17 4:58:18
  12. 超全的学习资料——Oracle 从入门到精通

    Oracle 从入门到精通目录一、SQL...................................................................................................................................................81.1、基本概念:...............................................................…...

    2024/4/17 4:57:42
  13. button 文字靠左 内容偏移

    [self.intoBtn setTitle:title forState:UIControlStateNormal];self.intoBtn.backgroundColor= KFontBlackColor2;self.intoBtn.titleLabel.font = [UIFont systemFontOfSize:15];self.intoBtn.titleLabel.numberOfLines= 0;//文字居左self.intoBtn.contentHorizontalAlignmen…...

    2024/4/17 4:58:00
  14. 简单理解Binder机制的原理

    一、概述Android系统中,涉及到多进程间的通信底层都是依赖于Binder IPC机制。例如当进程A中的Activity要向进程B中的Service通信,这便需要依赖于Binder IPC。不仅于此,整个Android系统架构中,大量采用了Binder机制作为IPC(进程间通信)方案。当然也存在部分其他的IPC方式,…...

    2024/5/1 23:41:24
  15. AES vs. RSA Encryption: What Are the Differences?

    原作者:Ron Franklin 转载地址:https://blog.syncsort.com/2019/03/data-security/aes-vs-rsa-encryption-differences/AES与RSA加密:有什么区别? 在互联网时代已经非常清楚的一件事是,防止未经授权的人访问存储在支持Web的计算机系统中的数据非常困难。工作人员要做的就是…...

    2024/4/17 4:59:06
  16. 电子工程学院的师兄弟姐们们,老师叫你们回家

    昨天写了很长的文章,接收到推送的同学们应该也会很开心,但是由于我的原因,需要把文章删除「你们能想到的原因肯定不是我删文的原因」,但是呢,也因为这样,又可以重写一篇,刚好可以多加点内容。后台回复的几个师弟,非常谢谢你们的支持,林子大了,什么鸟都会有,抱怨是不…...

    2024/3/31 20:11:17
  17. Java---Socket编程UDP/TCP

    • socket方便了应用程序访问通讯协议TCP/IP 。 • socket是作为通讯链入的端点。我们可以吧套接字看成是电话机,有了套接字,才有了通讯的工具。我们可以吧IP地址看成是电话号码,端口号看成是分机号。1、基于TCP的socket编程。• java.net.ServerSocket是用来创建服务器端的…...

    2024/5/2 0:24:45
  18. 博郡退席,黄“凉”一梦

    “将产品做到极致,就是我们与其他车企最大的差异。要把产品做到极致就不能有短板,有短板就意味着你整个车可能都不行。” 在汽车行业纵横了20年有余的黄希鸣总有一种区别于其他新造车企业创始人的骄傲,然而这份骄傲被“博郡还我工资”六个大字击碎了。2020年6月15日,已经成…...

    2024/5/2 3:24:37
  19. Binder机制总结

    在android中进行跨进程通信通常有以下几种方式:使用Intent使用文件共享使用Messenger使用AIDL使用ContentProvider 其中Intent中可以携带Bundle,而Bundle实现了Parcelable接口,所以可以在不同的进程间进行传输。文件共享就是读写文件,比如常用的SharedPreferences就是以XML来…...

    2024/5/1 21:50:39
  20. 让button有边框,或变成圆形

    很多时候我们需要一个圆角的button,而UIButton控件的边框默认是方的,或者不满足我们的需求。这时我们就需要用代码去修改它。[testBtn.layer setMasksToBounds:YES]; [testBtn.layer setCornerRadius:8.0]; //设置矩圆角半径 [testBtn.layer setBorderWidth:1.0]; //边框宽…...

    2024/4/17 4:58:54

最新文章

  1. Go 语言基础(二)【数组、切片、指针、map、struct】

    1、数组 特别需要注意的是&#xff1a;在 Go 语言中&#xff0c;数组长度也是数组类型的一部分&#xff01;所以尽管元素类型相同但是长度不同的两个数组&#xff0c;它们的类型并不相同。 1.1、数组的初始化 1.1.1、通过初始化列表{}来设置值 var arr [3]int // int类型的数…...

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

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

    2024/3/20 10:50:27
  3. 华为海思2024春招数字芯片岗机试题(共9套)

    huawei海思2024春招数字芯片岗机试题(共9套&#xff0c;有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09;&#xff08;WX:didadidadidida313&#xff0c;加我备注&#xff1a;CSDN huawei数字题目&#xff0c;谢绝白嫖哈&#xff09…...

    2024/5/1 10:20:41
  4. 基于AI智能识别技术的智慧展览馆视频监管方案设计

    一、建设背景 随着科技的不断进步和社会安全需求的日益增长&#xff0c;展览馆作为展示文化、艺术和科技成果的重要场所&#xff0c;其安全监控系统的智能化升级已成为当务之急。为此&#xff0c;旭帆科技&#xff08;TSINGSEE青犀&#xff09;基于视频智能分析技术推出了展览…...

    2024/4/30 17:11:11
  5. HIS系统是什么?一套前后端分离云HIS系统源码 接口技术RESTful API + WebSocket + WebService

    HIS系统是什么&#xff1f;一套前后端分离云HIS系统源码 接口技术RESTful API WebSocket WebService 医院管理信息系统(全称为Hospital Information System)即HIS系统。 常规模版包括门诊管理、住院管理、药房管理、药库管理、院长查询、电子处方、物资管理、媒体管理等&…...

    2024/5/1 13:22:01
  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