【android 9】【input】【9.发送按键事件3——Inputchannel的创建过程】

news2024/12/28 18:07:38

系列文章

本人系列文章-CSDN博客


目录

系列文章

目录

1.简介

1.1 主要步骤

1.2 时序图

2.源码分析

2.1 WindowManagerImpl的addView

2.2 WindowManagerGlobal的addView

2.3 ViewRootImpl

2.4 getWindowSession

 2.5 WMS中的openSession

 2.6 Session

2.7 class W

2.8 setView

2.9 addToDisplay

2.10 addWindow

2.11 openInputChannel

2.12 Java层openInputChannelPair

2.13 android_view_InputChannel_nativeOpenInputChannelPair

2.14 openInputChannelPair

2.15 transferTo

2.16 WMS向IMS注册并监听socket

2.17 nativeRegisterInputChannel

2.18 android_view_InputChannel_getInputChannel

2.19 android_server_InputWindowHandle_getHandle

2.20 registerInputChannel

2.21 InputDispatcher::registerInputChannel

2.22 Connection

2.23 android_view_InputChannel_setDisposeCallback

2.24 WindowInputEventReceiver

2.25 InputEventReceiver

2.26 nativeInit

2.27 NativeInputEventReceiver

2.28 initialize

2.29 setFdEvents

2.30 Looper::addFd


1.简介

上一篇中,主要介绍了按键事件中inputdispatcher线程的分发流程,最后会通过sokcet对发送按键消息到应用端,那么这个socket对是什么时候创建的呢?是什么时候和IMS建立连接的呢?本文便主要解答一下这部分内容。

1.1 主要步骤

1.首先当Activity启动后,应用程序端会创建一个空的InputChannel对象。

2.然后应用程序端会通过binder调用到WMS服务,WMS服务会通过openInputChannel 方法会创建一对 InputChannel,一个给到IMS,一个会通过binder调用返回给应用端。

3.然后WMS会将其中一个socket注册到IMS服务中,IMS服务通过epoll机制来监听,是否有来自应用端发送的消息,当应用程序端通过sokcet发送消息时,IMS中的handleReceiveCallback回调函数会执行。

4.然后此时应用端到WMS的binder调用函数返回,返回给应用程序端一个socket,应用程序端会创建一个NativeInputEventReceiver对象,同时应用程序端也会通过epoll机制来监听,是否有来自IMS发送的消息,当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

1.2 时序图

 (图片可保存到本地放大观看)

2.源码分析

首先当Activity启动后,最终会调用到WindowManagerImpl.addView()函数,我们便从WindowManagerImpl.addView()函数进行分析。

2.1 WindowManagerImpl的addView

主要作用:

1.调用WindowManagerGlobal对象的addview对象。

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) 
{
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//mGlobal就是WindowManagerGlobal对象
}

2.2 WindowManagerGlobal的addView

主要作用:

1.创建ViewRootImpl对象。

2.调用ViewRootImpl的setView函数,此函数会创建空的InputChannel对象,然后传给WMS,WMS会返回一个和IMS连接好的socket给应用程序端。

此时我们仍然在应用程序进程中。

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
			//view代表添加哪个窗口,此时view是DecorView
			//params窗口的参数
			//display显示到那块屏幕上
			//parentWindow父窗口是谁
       
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;


        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {

            int index = findViewLocked(view, false);//从mViews中查找此view是否已经存在
			/*
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }*/


            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);//将此DecorView保存到mViews容器中
            mRoots.add(root); //将此ViewRootImpl保存到容器中
            mParams.add(wparams);//保存参数

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

2.3 ViewRootImpl

主要作用:

1.获取IWindowSession代理类,此类用于应用端和wms进行通信。

2.new W(this);W 继承自 IWindow.Stub,用于wms服务端向应用端通信。在调用本类的setView时会将此W对象传递给WMS。

//ViewRootImpl.Java
public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();//获取IWindowSession代理类,此类用于应用和wms进行通信
        mDisplay = display;//显示到那个display中
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this);//w继承自class W extends IWindow.Stub,用于wms服务端向应用端通信
        mFirst = true; // true代表此view第一次被添加
        mChoreographer = Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
}

2.4 getWindowSession

主要作用:

1.获取和WMS通信用的Session对象。

//WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();//输入法
                    IWindowManager windowManager = getWindowManagerService();//获取wms的binder代理对象
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {//传入了客户端实现的WindowSessionCallback回调类,用于wms通信到应用程序
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
}
//aidl接口如下
IWindowSession openSession(in IWindowSessionCallback callback, in IInputMethodClient client,
            in IInputContext inputContext);

 2.5 WMS中的openSession

主要作用:

1.此时会走到WMS中,WMS中会创建一个session对象

//此时会调用到WMS中
public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs 
{
			public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) 
			{
				if (client == null) throw new IllegalArgumentException("null client");
				if (inputContext == null) throw new IllegalArgumentException("null inputContext");
				Session session = new Session(this, callback, client, inputContext);
				return session;//
		    }
}

 2.6 Session

主要作用为:
1.此类内部会保存WindowManagerService对象和客户端实现的IWindowSessionCallback类对象

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient 
{
	public Session(WindowManagerService service, IWindowSessionCallback callback,
            IInputMethodClient client, IInputContext inputContext) {
        mService = service;//此时service是WindowManagerService对象
        mCallback = callback;//callback是客户端实现的IWindowSessionCallback类对象,是一个binder对象
        mClient = client;//此时是输入法的客户端
        mUid = Binder.getCallingUid();
        mPid = Binder.getCallingPid();

        synchronized (mService.mWindowMap) {
            if (mService.mInputMethodManager == null && mService.mHaveInputMethods) {
                IBinder b = ServiceManager.getService(
                        Context.INPUT_METHOD_SERVICE);
                mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b);//获取输入法的binder代理对象
            }
        }
        long ident = Binder.clearCallingIdentity();
        try {
            // Note: it is safe to call in to the input method manager
            // here because we are not holding our lock.
            if (mService.mInputMethodManager != null) {
                mService.mInputMethodManager.addClient(client, inputContext,
                        mUid, mPid);//将此addClient添加到输入法中
            } else {
                client.setUsingInputMethod(false);
            }
            client.asBinder().linkToDeath(this, 0);
        } catch (RemoteException e) {
            // The caller has died, so we can just forget about this.
            try {
                if (mService.mInputMethodManager != null) {
                    mService.mInputMethodManager.removeClient(client);
                }
            } catch (RemoteException ee) {
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
	
}

2.7 class W

主要作用:

1.W 继承自 IWindow.Stub,用于wms服务端向应用端通信。在调用本类的setView时会将此W对象传递给WMS

static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;//客户端保存IWindowSession session通信对象
        }
}

2.8 setView

主要作用为:

1.应用端创建空的InputChannel对象。

2.应用端通过binder调用addToDisplay函数,此时会走到到wms服务中,在wms中会对mInputChannel赋值,并返回。返回的是一个已经和IMS连接的socket。

3.当应用端拿到socket后,会new WindowInputEventReceiver对象,此对象内部最终后调用应用端ui主线程的Looper::addFd函数,通过epoll机制监听此socket,当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

//frmaework/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
	    //view是view代表添加哪个窗口,此时view是DecorView
		//params窗口的参数
		//panelParentView,如果是子窗口,则子窗口存在父窗口,此时是普通窗口,则为null
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs);
                if (mWindowAttributes.packageName == null) {
                    mWindowAttributes.packageName = mBasePackageName;//给mWindowAttributes添加了包名
                }
                attrs = mWindowAttributes;

                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();//客户端创建InputChannel对象,此时的InputChannel是空的对象,并没有赋值的内容
                }
				
                try {
					//将空的mInputChannel传入其中,应用端通过binder调用到wms中,在wms中会对mInputChannel赋值
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
							//mWindow继承自IWindow.Stub,用于wms服务端向应用端通信
							//mWindowAttributes窗口属性
							//mInputChannel本质是socket,用于应用程序和ims进行输入事件的通信
                }
				

                if (mInputChannel != null) 
				{
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }

              
            }
        }
    }

2.9 addToDisplay

此时会通过binder走到WMS的系统服务中。

主要作用:

1.调用Windowmanagerservice的addWindow函数。

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
				//此时mWindow继承自IWindow.Stub,用于wms服务端向应用端通信
				//outInputChannel本质是socket,用于应用程序和ims进行输入事件的通信
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
        }
}

2.10 addWindow

主要作用:

1.调用WindowState类的openInputChannel函数。

//Windowmanagerservice.java
public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) 
			//此时client客户端的Window,继承自IWindow.Stub,用于wms服务端向应用端通信
			//outInputChannel本质是socket,用于应用程序和ims进行输入事件的通信
{
			//仅当窗口的inputFeatures未指定NO_INPUT_CHANNEL选项时才会为此窗口创建InputChannel对
			boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
			
            if (callingUid != SYSTEM_UID)
			{
                Slog.e(TAG_WM,"App trying to use insecure INPUT_FEATURE_NO_INPUT_CHANNEL flag. Ignoring");
                openInputChannels = true;
            }
            if  (openInputChannels) 
			{ 
				win.openInputChannel(outInputChannel);//win1是一个WindowState类对象,调用其类的openInputChannel函数
            }				
}

2.11 openInputChannel

主要作用为:

1.调用openInputChannelPair创建一个InputChannel对

2.将0号inputChannel调用InputManager.registerInputChannel注册到IMS中

3.将1号inputChannel传递给outInputChannel,即传递给应用端。

// frmaework/base/services/core/java/com/android/server/wm/WindowState.java
void openInputChannel(InputChannel outInputChannel) 
{
        if (mInputChannel != null) {
            throw new IllegalStateException("Window already has an input channel.");
        }
        String name = getName();
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//会创建一个sokcet对
        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
        mInputWindowHandle.inputChannel = inputChannels[0];//其中0号inputChannel交给InputWindowHandle保存
        if (outInputChannel != null) {
            mClientChannel.transferTo(outInputChannel);//将mClientChannel所持有的1号inputChannel传递给outInputChannel
            mClientChannel.dispose();
            mClientChannel = null;
        } 
		/*else {
            // If the window died visible, we setup a dummy input channel, so that taps
            // can still detected by input monitor channel, and we can relaunch the app.
            // Create dummy event receiver that simply reports all events as handled.
            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
        }*/
		
        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//将0号的InputChannel向IMS进行注册
		//mService定义是com.android.server.wm.WindowManagerService mService
		//故mInputManager是构造wms时传入的,mInputManager是InputManagerService对象
}

2.12 Java层openInputChannelPair


//路径:frameworks\base\core\java\android\view\InputChannel.java
public static InputChannel[] openInputChannelPair(String name)
{
        if (name == null) {
            throw new IllegalArgumentException("name must not be null");
        }

        if (DEBUG) {
            Slog.d(TAG, "Opening input channel pair '" + name + "'");
        }
        return nativeOpenInputChannelPair(name);
}

2.13 android_view_InputChannel_nativeOpenInputChannelPair

主要作用为:

1.调用InputChannel::openInputChannelPair创建两个c++类型的InputChannel对象。

2.将c++层的两个InputChannel对象转化为java层的InputChannel对象,并返回到java层。

路径:frameworks/base/core/jni/android_view_InputChannel.cpp
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    std::string name = nameChars;
    env->ReleaseStringUTFChars(nameObj, nameChars);

    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);//调用c++层的openInputChannelPair

    if (result) {
        String8 message;
        message.appendFormat("Could not open input channel pair.  status=%d", result);
        jniThrowRuntimeException(env, message.string());
        return NULL;
    }
	//创建两个存储gInputChannelClassInfo.clazz类型的对象的数组,默认值为null
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    if (env->ExceptionCheck()) {
        return NULL;
    }
	
	//将c++类型的serverChannel对象转化为java类型的serverChannelObj
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(serverChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(clientChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);//将java类型的serverChannelObj放入数组
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;//返回一个存储java类型的ChannelObj数组
}

2.14 openInputChannelPair

主要作用为:

1.通过socketpair创建一个socket对。

2.设置socket对的发送缓冲区和接受缓冲区的大小

3.用c++层的InputChannel封装socket对,一个封装给IMS,一个封装最终会通过binder调用返回给应用端。

// frameworks/native/libs/input/InputTransport.cpp
status_t InputChannel::openInputChannelPair(const std::string& name,sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {//将sockets数组传入,通过socketpair函数创建sockets对,返回值为0代表成功
        status_t result = -errno;
        ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                name.c_str(), errno);
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    }

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));//设置socket[0]的发送缓冲区大小
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));//设置socket[0]的接收缓冲区大小
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));//设置socket[1]的发送缓冲区大小
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));//设置socket[1]的接收缓冲区大小

    std::string serverChannelName = name;
    serverChannelName += " (server)";
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);//用InputChannel封装sockets[0],给服务端

    std::string clientChannelName = name;
    clientChannelName += " (client)";
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);//用InputChannel封装sockets[1],给客户端
    return OK;
}

2.15 transferTo

主要作用是:

1.转移socket给输入的参数。

// frameworks\base\core\java\android\view\InputChannel.java
public void transferTo(InputChannel outParameter) {
        if (outParameter == null) {
            throw new IllegalArgumentException("outParameter must not be null");
        }        
        nativeTransferTo(outParameter);
    }
static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,jobject otherObj) {
    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Other object already has a native input channel.");
        return;
    }

    NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, obj);
    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);//将调用者mClientChannel对应的NativeInputChannel对象,赋值给outInputChannel
    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);//将调用者mClientChannel的置空
}
static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,jobject inputChannelObj) {
    jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    return reinterpret_cast<NativeInputChannel*>(longPtr);
}

2.16 WMS向IMS注册并监听socket

//InputManagerService.java
//注册输入通道,以便将其用作输入事件目标。
//@param inputChannel要注册的输入通道。
//@param inputWindowHandle与输入通道关联的输入窗口句柄,如果没有,则为null。
public void registerInputChannel(InputChannel inputChannel,InputWindowHandle inputWindowHandle) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null.");
        }
        nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);//mPtr是nativeInit返回的值,是指向NativeInputManager的指针。
		//inputChannel输入通道
		//inputWindowHandle窗口的句柄
}

2.17 nativeRegisterInputChannel

主要作用为:

1.将java层获取的inputChannel对象转化为c++层的inputChannel。

2.将java层的窗口句柄inputWindowHandleObj对象转化为c++层的inputWindowHandle。

3.向IMS注册此inputChannel对象和对应的窗口句柄inputWindowHandle

4.设置一个回调函数handleInputChannelDisposed,此回调函数会在调用java层的InputChannel对象完成或者销毁时触发,其会调用unregisterInputChannel取消IMS中已经保存和监听的socket

static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);//从java层获取native层的inputChannel对象
    if (inputChannel == NULL) {
        throwInputChannelNotInitialized(env);
        return;
    }

    sp<InputWindowHandle> inputWindowHandle =
            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);//获取此窗口在input中的句柄

    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);//向ims注册监听此socket
    if (status) {
        std::string message;
        message += StringPrintf("Failed to register input channel.  status=%d", status);
        jniThrowRuntimeException(env, message.c_str());
        return;
    }

    if (! monitor) {//此时是false
        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                handleInputChannelDisposed, im);//设置一个回调函数,此回调函数会在调用java层的InputChannel对象完成或者销毁时,触发
				//主要是调用unregisterInputChannel取消IMS中已经保存和监听的socket
    }
}

2.18 android_view_InputChannel_getInputChannel

主要作用为:

1.通过java层的InputChannel对象获取指向c++层的InputChannel对象的指针。

sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) 
{
    NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
    return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
}


static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
        jobject inputChannelObj) 
{
    jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    return reinterpret_cast<NativeInputChannel*>(longPtr);
}


inline sp<InputChannel> getInputChannel() { return mInputChannel; }

2.19 android_server_InputWindowHandle_getHandle

主要作用为:

1.获取inputWindowHandle类对象,这是WMS的一个窗口的句柄。

//base/services/core/jni/com_android_server_input_InputWindowHandle.cpp
sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
        JNIEnv* env, jobject inputWindowHandleObj) {//inputWindowHandleObj是java层传入的inputWindowHandle类对象
    if (!inputWindowHandleObj) {//如果为空,返回
        return NULL;
    }

    AutoMutex _l(gHandleMutex);

    jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
    NativeInputWindowHandle* handle;
    if (ptr) {
        handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
    } else {
        jobject inputApplicationHandleObj = env->GetObjectField(inputWindowHandleObj,
                gInputWindowHandleClassInfo.inputApplicationHandle);
        sp<InputApplicationHandle> inputApplicationHandle =
                android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
        env->DeleteLocalRef(inputApplicationHandleObj);

        jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
        handle = new NativeInputWindowHandle(inputApplicationHandle, objWeak);
        handle->incStrong((void*)android_server_InputWindowHandle_getHandle);
        env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
                reinterpret_cast<jlong>(handle));
    }
    return handle;
}

2.20 registerInputChannel

主要作用为:

1.调用InputDispatcher的registerInputChannel函数,将窗口的inputChannel和其对应的窗口句柄inputWindowHandle注册到IMS中保存。

//com_android_server_input_InputManagerService.cpp
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
        const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    ATRACE_CALL();
    return mInputManager->getDispatcher()->registerInputChannel(
            inputChannel, inputWindowHandle, monitor);//mInputManager是InputManager.cpp类的对象,
			//所以,mInputManager->getDispatcher()是一个InputDispatcher.cpp类的对象
			//inputChannel是IMS服务端的socket,用于接收来自应用端的消息
			//inputWindowHandle是对应的窗口的句柄。一个窗口对应一个socket对。
			
}

2.21 InputDispatcher::registerInputChannel

主要作用为:

1.new了一个Connection对象,此对象里面保存了inputChannel和其对应的窗口句柄inputWindowHandle

2.将此socket的fd和此Connection对象,保存到一个容器中。当input事件发生时,会根据找到的目标窗口句柄取出对应的inputChannel,从中获取socket的fd,然后IMS会通过此socket将消息发送到应用端。

3.通过 mLooper->addFd,监听此socket的fd,从上文我们知道,WMS会创建两个已经连接好的shocket对,其中一个给到应用程序端,一个给到IMS,此处的作用便是监听来自应用程序端发送给IMS的消息,当应用程序端通过sokcet发送消息时,IMS中的handleReceiveCallback回调函数会执行。

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
#if DEBUG_REGISTRATION
    ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().c_str(),
            toString(monitor));
#endif

    { // acquire lock
        AutoMutex _l(mLock);

        if (getConnectionIndexLocked(inputChannel) >= 0) {//调用getConnectionIndexLocked方法,
		//根据inputChannel的fd值,查找mConnectionsByFd,看看是否此input channel已经注册
            ALOGW("Attempted to register already registered input channel '%s'",
                    inputChannel->getName().c_str());
            return BAD_VALUE;
        }

        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);//new了一个Connection对象,
		//此对象里面保存了inputChannel和其对应的窗口句柄inputWindowHandle

        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);//放入mConnectionsByFd中,表示已经注册过的inputChannel

        if (monitor) {//此时是false
            mMonitoringChannels.push(inputChannel);
        }

        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
		//监听Inputchannel的可读性。
		//mLooper的pollOnce()本质上就是epoll_wait(),因此Looper对象具有监听文件描述符可读性事件的能力,在此注册Inputchannel可读性事件,
		//并在事件到来时通过handleReceiveCallback()回调进行处理
    } // release lock

    // Wake the looper because some connections have changed.
    mLooper->wake();//唤醒InputDispatcher线程
    return OK;
}
ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
    ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
    if (connectionIndex >= 0) {
        sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
        if (connection->inputChannel.get() == inputChannel.get()) {
            return connectionIndex;
        }
    }

    return -1;
}

2.22 Connection

Connection类描述了从ImputDispatcher到目标窗口中的一个连接,其中保存了向窗口发送的事件的状态信息。
在 Connection中,重要的成员有:
1.mlnputPublisher,InputPublisher类的一个对象,它封装InputChannel并直接对其进行写入和读取。另外,它也负责ImputMessage结构体的封装与解析。
2.outboundQueue,用于保存等待通过此Connection进行发送的事件队列。
3.waitQueue,用于保存已经通过此Connection将事件发送给窗口,正在等待窗口反馈的事件队列。

InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) :
        status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
        monitor(monitor),
        inputPublisher(inputChannel), inputPublisherBlocked(false) {
}
class Connection : public RefBase {
    protected:
        virtual ~Connection();

    public:
        enum Status {
            // 连接状态正常
            STATUS_NORMAL,
            // 发生了不可恢复的通信错误
            STATUS_BROKEN,
            // input channel已注销。
            STATUS_ZOMBIE
        };

        Status status;
        sp<InputChannel> inputChannel; //永不为空
        sp<InputWindowHandle> inputWindowHandle; // 可能为空
        bool monitor;
        InputPublisher inputPublisher;
        InputState inputState;

        //如果套接字已满,并且在应用程序使用某些输入之前无法发送其他事件,则为True。
        bool inputPublisherBlocked;

        // 事件队列需要发送到Connection
        Queue<DispatchEntry> outboundQueue;

        //已发送到connection但尚未收到应用程序“完成”响应的事件队列。
        Queue<DispatchEntry> waitQueue;

        explicit Connection(const sp<InputChannel>& inputChannel,
                const sp<InputWindowHandle>& inputWindowHandle, bool monitor);

        inline const std::string getInputChannelName() const { return inputChannel->getName(); }

        const std::string getWindowName() const;
        const char* getStatusLabel() const;

        DispatchEntry* findWaitQueueEntry(uint32_t seq);
};

2.23 android_view_InputChannel_setDisposeCallback

主要作用为:

1.设置一个回调函数,此回调函数会在调用java层的InputChannel对象完成或者销毁时触发,主要是调用unregisterInputChannel取消IMS中已经保存和监听的socket,并清空此InputChannel对象对应的Connection对象的outboundQueue和waitQueue队列。

void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
        InputChannelObjDisposeCallback callback, void* data) {
    NativeInputChannel* nativeInputChannel =
            android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);//根据java层保存的native层的指针的值,获取nativeInputChannel指针
        ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
    } else {
        nativeInputChannel->setDisposeCallback(callback, data);//此时callback是handleInputChannelDisposed
		//data是NativeInputManager
    }
}



void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
    mDisposeCallback = callback;//此时callback是handleInputChannelDisposed
    mDisposeData = data;//data是NativeInputManager
}

 handleInputChannelDisposed主要作用是取消注册inputchannel。

static void handleInputChannelDisposed(JNIEnv* env,
        jobject /* inputChannelObj */, const sp<InputChannel>& inputChannel, void* data) {
    NativeInputManager* im = static_cast<NativeInputManager*>(data);

    ALOGW("Input channel object '%s' was disposed without first being unregistered with "
            "the input manager!", inputChannel->getName().c_str());
    im->unregisterInputChannel(env, inputChannel);
}

那么这个回调函数什么时候会被调用呢?

在Object类里面,有一个方法finalize()。
当VM的垃圾收集器检测到这个对象不可达的时候,也就是说这个对象为垃圾可以被回收的时候,这个对象的finalize ()方法就会被执行,默认情况下,它不做任何处理,我们可以重写这个方法来进行资源的释放。当回收分配的Object对象的内存之前垃圾收集器会调用对象的finalize()方法。

//InputChannel.java    
protected void finalize() throws Throwable {
        try {
            nativeDispose(true);
        } finally {
            super.finalize();
        }
    }

查看对应关系

{ "nativeDispose", "(Z)V",(void*)android_view_InputChannel_nativeDispose },
static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
    NativeInputChannel* nativeInputChannel =
            android_view_InputChannel_getNativeInputChannel(env, obj);
    if (nativeInputChannel) {
        if (finalized) {
            ALOGW("Input channel object '%s' was finalized without being disposed!",
                    nativeInputChannel->getInputChannel()->getName().c_str());
        }

        nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);

        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
        delete nativeInputChannel;
    }
}
static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
    NativeInputChannel* nativeInputChannel =
            android_view_InputChannel_getNativeInputChannel(env, obj);//根据java层的InputChannel获取native层的NativeInputChannel
    if (nativeInputChannel) {
        if (finalized) {
            ALOGW("Input channel object '%s' was finalized without being disposed!",
                    nativeInputChannel->getInputChannel()->getName().c_str());
        }

        nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);

        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);//设置Java曾保存的指向native层的NativeInputChannel的指针为空
        delete nativeInputChannel;//delete
    }
}
void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
    if (mDisposeCallback) {
        mDisposeCallback(env, obj, mInputChannel, mDisposeData);//会执行handleInputChannelDisposed回调函数
        mDisposeCallback = NULL;
        mDisposeData = NULL;
    }
}

  handleInputChannelDisposed主要作用是取消注册inputchannel。

static void handleInputChannelDisposed(JNIEnv* env,
        jobject /* inputChannelObj */, const sp<InputChannel>& inputChannel, void* data) {
    NativeInputManager* im = static_cast<NativeInputManager*>(data);

    ALOGW("Input channel object '%s' was disposed without first being unregistered with "
            "the input manager!", inputChannel->getName().c_str());
    im->unregisterInputChannel(env, inputChannel);
}

查看unregisterInputChannel

status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
        const sp<InputChannel>& inputChannel) {
    ATRACE_CALL();
    return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
}

查看InputDispatcher的unregisterInputChannel函数。

status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {


    { // acquire lock
        AutoMutex _l(mLock);

        status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/);
        if (status) {
            return status;
        }
    } // release lock

    // Wake the poll loop because removing the connection may have changed the current
    // synchronization state.
    mLooper->wake();
    return OK;
}

 主要作用为:

1.从容器中删除此Connection对象

2.从epoll_wait中取消此socket的监听

3.清空此connection的等待发送队列outboundQueue和发送成功等待回应的消息队列

status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& inputChannel,
        bool notify) {
    ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);//从保存所有inputChannel的容器中找到当前connection的索引
    if (connectionIndex < 0) {
        ALOGW("Attempted to unregister already unregistered input channel '%s'",
                inputChannel->getName().c_str());
        return BAD_VALUE;
    }

    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    mConnectionsByFd.removeItemsAt(connectionIndex);//从容器中删除此Connection对象,Connection中保存了inputChannel和其对应的窗口句柄

    if (connection->monitor) {
        removeMonitorChannelLocked(inputChannel);
    }

    mLooper->removeFd(inputChannel->getFd());//mLooper本质是epoll_wait,从epoll_wait中取消此socket的监听,即不再监听来自应用程序端的消息

    nsecs_t currentTime = now();
    abortBrokenDispatchCycleLocked(currentTime, connection, notify);//清空此connection的等待发送队列outboundQueue和发送成功等待回应的消息队列

    connection->status = Connection::STATUS_ZOMBIE;
    return OK;
}
void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection, bool notify) {

    // Clear the dispatch queues.
    drainDispatchQueueLocked(&connection->outboundQueue);
    traceOutboundQueueLengthLocked(connection);
    drainDispatchQueueLocked(&connection->waitQueue);
    traceWaitQueueLengthLocked(connection);

    
    if (connection->status == Connection::STATUS_NORMAL) {
        connection->status = Connection::STATUS_BROKEN;
        /*此时是false
        if (notify) {
            // Notify other system components.
            onDispatchCycleBrokenLocked(currentTime, connection);
        }*/
    }
}

2.24 WindowInputEventReceiver

此时我们已经知道了WMS将其中一个sokcet注册给了IMS。我们接下来看看第二个socket,应用端是如何处理的?

在上文的ViewRootImpl的setView最后,应用端会new一个WindowInputEventReceiver,然后会调用InputEventReceiver构造函数。

//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());//mInputChannel是客户端的socket[1],Looper.myLooper主线程的looper
}



final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);//调用InputEventReceiver的构造方法
        }
    }

2.25 InputEventReceiver

主要作用:

1.保存java层的inputChannel对象

2.获取UI主线程,此处主要是需要将客户端的socket添加到Looper中监听,其实Looper的底层也是epoll_wait

3.调用nativeInit函数,此函数内部会new一个NativeInputEventReceiver对象,然后调用此对象的初始化函数,初始化函数的内部会将此应用端的socket添加到Looper中监听。当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        mInputChannel = inputChannel;//socket[1]
        mMessageQueue = looper.getQueue();//UI 线程消息队列
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);
}

2.26 nativeInit

主要作用为:

1.new了一个NativeInputEventReceiver对象。

2.调用NativeInputEventReceiver的initialize函数。初始化函数的内部会将此应用端的socket添加到Looper中监听。当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);//获取c++层的inputChannel
    if (inputChannel == NULL) {
        jniThrowRuntimeException(env, "InputChannel is not initialized.");
        return 0;
    }

    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);//获取消息队列
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }

    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,//new了一个NativeInputEventReceiver对象
            receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();//初始化NativeInputEventReceiver对象
	/*
    if (status) {//如果初始化失败
        String8 message;
        message.appendFormat("Failed to initialize input event receiver.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return 0;
    }*/

    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
    return reinterpret_cast<jlong>(receiver.get());
}

2.27 NativeInputEventReceiver

里面创建了一个InputConsumer类对象,用来保存c++层的inputChannel

NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
        jobject receiverWeak, const sp<InputChannel>& inputChannel,
        const sp<MessageQueue>& messageQueue) :
        mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
        mInputConsumer(inputChannel), mMessageQueue(messageQueue),//创建了一个InputConsumer类对象,用来保存c++层的inputChannel
        mBatchedInputEventPending(false), mFdEvents(0) 
{
	/*
    if (kDebugDispatchCycle) {//默认false
        ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str());
    }*/
}
InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
        mResampleTouch(isTouchResamplingEnabled()),//是否触摸重新采样
        mChannel(channel), mMsgDeferred(false) 
{
}

2.28 initialize

主要作用:

1.调用setFdEvents函数。

status_t NativeInputEventReceiver::initialize() 
{
    setFdEvents(ALOOPER_EVENT_INPUT);//ALOOPER_EVENT_INPUT值是1
    return OK;
}

2.29 setFdEvents

主要作用:

1.调用了Looper的addfd函数,用于监听此fd,并传入了NativeInputEventReceiver对象,当InputManagerService发送消息到应用程序时,会调用NativeInputEventReceiver的handleEvent函数。

void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {//默认是0,此时为1
        mFdEvents = events;//设置为ALOOPER_EVENT_INPUT
        int fd = mInputConsumer.getChannel()->getFd();//获取socket[0]的fd
        if (events) {
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);//调用了Looper的addfd函数,用于监听此fd,并传入了NativeInputEventReceiver对象
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

2.30 Looper::addFd

主要作用为:

1.调用epoll_ctl监听此应用的socket,即监听来自IMS的输入事件。

int Looper::addFd(int fd, int ident, int events,
        const sp<LooperCallback>& callback, void* data) {
    {
        AutoMutex _l(mLock);
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.events = events;
        request.seq = mNextRequestSeq++;
        request.callback = callback; // 是指 NativeInputEventReceiver
        request.data = data;
        if (mNextRequestSeq == -1) mNextRequestSeq = 0;
        
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            // 通过 epoll 监听 fd
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            ......
            mRequests.add(fd, request); // 该fd 的 request 加入到 mRequests 队列
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            ......
            mRequests.replaceValueAt(requestIndex, request);
        }
    } 
    return 1;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1873976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASUS/华硕幻14 2023 GA402X系列 原厂Windows11-22H2系统

安装后恢复到您开箱的体验界面&#xff0c;带原机所有驱动和软件&#xff0c;包括myasus mcafee office 奥创等。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http:…

Android 14 独立编译 Setting apk

我们在setting 目录下是用 mm 会报错。 所以应该在 源码主目录 采用 make Settings 进行编译 很多时候如果在apk 目录下 mm 单独编译会出错&#xff0c; 都可以才用这种方式进行编译。

【51单片机入门】点亮数码管

文章目录 前言仿真图如何去绘制一个数字示例代码选择某个数码管显示某个数字 示例代码总结 前言 在嵌入式系统的世界中&#xff0c;单片机扮演着至关重要的角色。51单片机&#xff0c;作为最早的微控制器之一&#xff0c;至今仍被广泛应用在各种设备中。本文将介绍如何使用51单…

FTP 文件传输协议:概念、工作原理;上传下载操作步骤

目录 FTP 概念 工作原理 匿名用户 授权用户 FTP软件包 匿名用户上传下载实验步骤 环境配置 下载 上传 wget 授权用户上传下载步骤 root用户登录FTP步骤 监听 设置端口号范围 修改用户家目录 匿名用户 授权用户 FTP 概念 FTP&#xff08;File Transfer Prot…

如何保护应用?可快速部署的WAF服务器分享

Web应用攻击是安全事件和数据泄露的主要原因。相关统计表明&#xff0c;超过四分之三的网络犯罪直指应用及其漏洞。为保护数量日益增长的应用安全&#xff0c;Web应用防火墙(WAF)因此而生。本文则聚焦于WAF服务器&#xff0c;了解它的性能与具体的实践应用。   新加坡网络安全…

鸿蒙HarmonyOS自定义组件开发和使用

自定义组件的介绍 在开发和使用自定义组件直接&#xff0c;我们需要了解什么是自定义组件&#xff1f; 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常…

scatterlist的相关概念与实例分析

概念 scatterlist scatterlist用来描述一块内存&#xff0c;sg_table一般用于将物理不同大小的物理内存链接起来&#xff0c;一次性送给DMA控制器搬运 struct scatterlist {unsigned long page_link; //指示该内存块所在的页面unsigned int offset; //指示该内存块在页面中的…

CentOS7环境下DataX的安装、使用及问题解决

DataX概述 DataX 是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 为了解决异构数据源同步问题&#xff0c;DataX将复杂的网状的同步链路变…

SAP PP学习笔记24 - 生产订单(制造指图)的创建

上面两章讲了生产订单的元素。 SAP PP学习笔记22 - 生产订单&#xff08;制造指图&#xff09;的元素1-CSDN博客 SAP PP学习笔记23 - 生产订单&#xff08;制造指图&#xff09;的元素2 - 决济规则(结算规则)-CSDN博客 这一章讲生产订单的创建。比如 - 生产订单的流程&#…

端口扫描攻击检测及防御方案

端口扫描数据一旦落入坏人之手&#xff0c;可能会成为更大规模恶意活动的一部分。因此&#xff0c;了解如何检测和防御端口扫描攻击至关重要。 端口扫描用于确定网络上的端口是否开放以接收来自其他设备的数据包&#xff0c;这有助于网络安全团队加强防御。但恶意行为者也可以…

ETAS工具导入DEXT生成Dcm及Dem模块(一)

文章目录 前言Cfggen之前的修改ECU关联DcmDslConnectionDiagnostic ProtocolDiagnostic Ecu Instance PropsCommonContributionSetEvent修改communication channel总结前言 诊断模块开发一般是先设计诊断数据库,OEM会释放对应的诊断数据库,如.odx文件或.cdd文件。如果OEM没有…

博士最多8年?硕士6年清退?教育局可没这么说!

哈哈哈&#xff0c;教育部可没说过博士最多八年&#xff0c;教育部说的是 博士研究生教育的最长修业年限&#xff0c;一般为6-8年 并且 对于“因研究未结束而无法正常毕业的博士”&#xff0c;目前高校可以按规定统筹利用科研经费、学费收入、社会捐助等资金&#xff0c;设…

使用minio搭建oss

文章目录 1.minio安装1.拉取镜像2.启动容器3.开启端口1.9090端口2.9000端口 4.访问1.网址http://:9090/ 5.创建一个桶 2.minio文件服务基本环境搭建1.创建一个文件模块2.目录结构3.配置依赖3.application.yml 配置4.编写配置类MinioConfig.java&#xff0c;构建minioClient5.Fi…

【Python】已解决:Python正确安装文字识别库EasyOCR

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;Python正确安装文字识别库EasyOCR 一、分析问题背景 在使用Python进行图像处理和文字识别时&#xff0c;EasyOCR是一个流行的库&#xff0c;它基于PyTorch&…

一大波客户感谢信来袭,感谢认可!

“自美的置业数据中台项目启动以来&#xff0c;贵公司实施团队与服务运营始终以专业、敬业、合作的态度扎根用户、服务用户、与用户共成长。在此&#xff0c;我司表示由衷的感谢&#xff01;” 这是携手美的置业以来&#xff0c;我们收到的第二封客户感谢信。 △ 以上为美的置…

Vue2组件传值(通信)的方式

1.父传后代 ( 后代拿到了父的数据 ) 1. 父组件引入子组件&#xff0c;绑定数据 <List :str1‘str1’></List> 子组件通过props来接收props:{str1:{type:String,default:}}***这种方式父传子很方便&#xff0c;但是父传给孙子辈分的组件就很麻烦&#xff08;父》子…

PerplexityAI与《连线》杂志纠纷事件深度分析

引言 最近&#xff0c;PerplexityAI&#xff0c;这家人工智能搜索领域的新秀公司&#xff0c;因被《连线》杂志指控剽窃内容和捏造事实而陷入困境。这起事件引发了广泛关注&#xff0c;也揭示了AI技术在信息检索和内容生成领域面临的一系列挑战。本文将对该事件进行详细分析&a…

《昇思25天学习打卡营第5天|onereal》

ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作&#xff1a;Pointw…

KVB外汇:澳元/美元、澳元/纽元、英镑/澳元的走势如何?

摘要 本文对近期澳元/美元、澳元/纽元、英镑/澳元的技术走势进行了详细分析。通过对关键支撑位和阻力位的分析&#xff0c;我们可以更好地理解澳元在不同货币对中的表现。随着全球经济形势的变化&#xff0c;各国央行的货币政策对外汇市场的影响也愈发明显。本文旨在帮助投资者…

centos7+离线安装nginx

1.提取rpm包 链接&#xff1a;https://pan.baidu.com/s/1qLNPubAD_qt59Pzws4nnog 提取码&#xff1a;0124 --来自百度网盘超级会员V3的分享 2.安装流程 rpm -ivh nginx-1.20.1-1.el7.ngx.x86_64.rpm 在使用 nginx 时&#xff0c;通常需要掌握一些基本的命令来管理其启动、查…