【Android surface 】二:源码分析App的surface创建过程

news2024/11/24 20:30:47

文章目录

  • 画布surface
  • ViewRoot的创建&setView分析
    • setView
      • requestLayout
      • ViewRoot和WMS的关系
  • activity的UI绘制
      • draw
  • surface
    • jni层分析
      • Surface无参构造
      • SurfaceSession
      • SurfaceSession_init
    • surface的有参构造
      • Surface_copyFrom
      • Surface_writeToParcel
      • Surface_readFromParcel
  • 总结

上一篇我们分析到了Android创建了UI控件,但是作画的地方在哪里呢?
这里继续进行分析。

画布surface

Handle onto a raw buffer that is being managed by the screen compositor.
A Surface is generally created by or from a consumer of image buffers (such as a android.graphics.SurfaceTexture, android.media.MediaRecorder, or android.renderscript.Allocation), and is handed to some kind of producer (such as OpenGL, MediaPlayer, or android.hardware.camera2.CameraDevice#createCaptureSession) to draw into.
Note: A Surface acts like a weak reference to the consumer it is associated with. By itself it will not keep its parent consumer from being reclaimed.
https://developer.android.com/reference/kotlin/android/view/Surface?hl=en

从官方文档的描述,我们可以知道surface其实就是我们苦苦思索的”在哪里作画”的画布。相机、OpenGL、媒体播放器可以作为画家在上面作画。
Handle onto a raw buffer that is being managed by the screen compositor.这里说的 screen compositor其实就是SurfaceFlinger。我们可以把他们来一次具象化理解:

在这里插入图片描述
我们需要理解的是SurfaceFlinger和Surface的联系,这对于我们开发Android很重要。

ViewRoot的创建&setView分析

frameworks/base/core/java/android/view/ViewRoot.java

构造函数:

```java
public ViewRoot(Context context) {
        super();

        if (MEASURE_LATENCY && lt == null) {
            lt = new LatencyTimer(100, 1000);
        }

        // For debug only
        //++sInstanceCount;

        // Initialize the statics when this class is first instantiated. This is
        // done here instead of in the static block because Zygote does not
        // allow the spawning of threads.
        getWindowSession(context.getMainLooper());
        
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this, context);
        mInputMethodCallback = new InputMethodCallback(this);
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
        mViewConfiguration = ViewConfiguration.get(context);
        mDensity = context.getResources().getDisplayMetrics().densityDpi;
    }

getWindowSession:

 public static IWindowSession getWindowSession(Looper mainLooper) {
        synchronized (mStaticInit) {
            if (!mInitialized) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
                    //binder通信的写法,很熟悉
                    sWindowSession = IWindowManager.Stub.asInterface(
                            ServiceManager.getService("window"))
                            .openSession(imm.getClient(), imm.getInputContext());
                    mInitialized = true;
                } catch (RemoteException e) {
                }
            }
            return sWindowSession;
        }
    }

这个函数其实会建立Activity里面ViewRoot和WindowManagerService的关系。
我们知道,WindowManagerService由SystemServer进程启动,surfaceflinger服务也在这个进程中。所以activity的显示是需要跨进程通信的。

setView

接着分析

  /**
     * We have one child
     */
     //第一个参数是decorview
    public void setView(View view, WindowManager.LayoutParams attrs,
            View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs);
                attrs = mWindowAttributes;
                Resources resources = mView.getContext().getResources();
                CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo();
                mTranslator = compatibilityInfo.getTranslator();

                if (mTranslator != null || !compatibilityInfo.supportsScreen()) {
                    mSurface.setCompatibleDisplayMetrics(resources.getDisplayMetrics(),
                            mTranslator);
                }

                boolean restore = false;
                if (mTranslator != null) {
                    restore = true;
                    attrs.backup();
                    mTranslator.translateWindowLayout(attrs);
                }
                if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs);

                if (!compatibilityInfo.supportsScreen()) {
                    attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
                }

                mSoftInputMode = attrs.softInputMode;
                mWindowAttributesChanged = true;
                mAttachInfo.mRootView = view;
                mAttachInfo.mScalingRequired = mTranslator != null;
                mAttachInfo.mApplicationScale =
                        mTranslator == null ? 1.0f : mTranslator.applicationScale;
                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                //重点
                requestLayout();
                try {
                //重点
                    res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    unscheduleTraversals();
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                }
                mPendingContentInsets.set(mAttachInfo.mContentInsets);
                mPendingVisibleInsets.set(0, 0, 0, 0);
                if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
                if (res < WindowManagerImpl.ADD_OKAY) {
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    unscheduleTraversals();
                    switch (res) {
                        case WindowManagerImpl.ADD_BAD_APP_TOKEN:
                        case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not valid; is your activity running?");
                        case WindowManagerImpl.ADD_NOT_APP_TOKEN:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not for an application");
                        case WindowManagerImpl.ADD_APP_EXITING:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- app for token " + attrs.token
                                + " is exiting");
                        case WindowManagerImpl.ADD_DUPLICATE_ADD:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- window " + mWindow
                                + " has already been added");
                        case WindowManagerImpl.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerImpl.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window " + mWindow +
                                " -- another window of this type already exists");
                        case WindowManagerImpl.ADD_PERMISSION_DENIED:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window " + mWindow +
                                " -- permission denied for this window type");
                    }
                    throw new RuntimeException(
                        "Unable to add window -- unknown error code " + res);
                }
                view.assignParent(this);
                mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
                mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
            }
        }
    }

这里做了几件事情,保存传入的decorview,调用了requestLayout,跨进程通信调用了add函数。

requestLayout

frameworks/base/core/java/android/view/ViewRoot.java
    /**
     * {@inheritDoc}
     */
    public void requestLayout() {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
frameworks/base/core/java/android/view/ViewRoot.java
 public void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            sendEmptyMessage(DO_TRAVERSAL);
        }
    }

ViewRoot和WMS进行了交互,ViewRoot调用openSession拿到了IWindowSession,调用它的add函数,把W类型的mWindow对象作为参数传入。

ViewRoot和WMS的关系

来看下WMS的openSession

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
   @Override
    public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }
frameworks/base/services/java/com/android/server/WindowManagerService.java
  public int add(IWindow window, WindowManager.LayoutParams attrs,
                int viewVisibility, Rect outContentInsets) {
            return addWindow(this, window, attrs, viewVisibility, outContentInsets);
        }
   public int addWindow(Session session, IWindow client,
            WindowManager.LayoutParams attrs, int viewVisibility,
            Rect outContentInsets) {
       ...
			//重点
            win = new WindowState(session, client, token,
                    attachedWindow, attrs, viewVisibility);
            if (win.mDeathRecipient == null) {
                // Client has apparently died, so there is no reason to
                // continue.
                Slog.w(TAG, "Adding window client " + client.asBinder()
                        + " that is dead, aborting.");
                return WindowManagerImpl.ADD_APP_EXITING;
            }

            mPolicy.adjustWindowParamsLw(win.mAttrs);

            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerImpl.ADD_OKAY) {
                return res;
            }

            // From now on, no exceptions or errors allowed!

            res = WindowManagerImpl.ADD_OKAY;

            final long origId = Binder.clearCallingIdentity();

            if (addToken) {
                mTokenMap.put(attrs.token, token);
                mTokenList.add(token);
            }
            //重点
            win.attach();
            mWindowMap.put(client.asBinder(), win);
	...

        return res;
    }

WindowState的attach:

     void attach() {
            if (localLOGV) Slog.v(
                TAG, "Attaching " + this + " token=" + mToken
                + ", list=" + mToken.windows);
            mSession.windowAddedLocked();
        }

     void windowAddedLocked() {
            if (mSurfaceSession == null) {
                if (localLOGV) Slog.v(
                    TAG, "First window added to " + this + ", creating SurfaceSession");
                    //重点
                mSurfaceSession = new SurfaceSession();
                if (SHOW_TRANSACTIONS) Slog.i(
                        TAG, "  NEW SURFACE SESSION " + mSurfaceSession);
                mSessions.add(this);
            }
            mNumWindow++;
        }

到这里,我们得总结一番:
在这里插入图片描述

activity的UI绘制

 @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
  
        case DO_TRAVERSAL:
            if (mProfile) {
                Debug.startMethodTracing("ViewRoot");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
            break;
       
    }

performTraversals:

 private void performTraversals() {
 ...
        // cache mView since it is used so much below...
        final View host = mView;
        //重点
   relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
   if (!cancelDraw && !newSurface) {
            mFullRedrawNeeded = false;
            //绘制
            draw(fullRedrawNeeded);
...

relayoutWindow:

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        ...
        //重点
        int relayoutResult = sWindowSession.relayout(
                mWindow, params,
                (int) (mView.mMeasuredWidth * appScale + 0.5f),
                (int) (mView.mMeasuredHeight * appScale + 0.5f),
                viewVisibility, insetsPending, mWinFrame,
                mPendingContentInsets, mPendingVisibleInsets,
                mPendingConfiguration, mSurface);//mSurface
  		...
        return relayoutResult;
    }

draw

frameworks/base/core/java/android/view/ViewRoot.java
private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;//viewroot的成员变量
    ...

        Rect dirty = mDirty;
        if (mUseGL) {
            if (!dirty.isEmpty()) {
                Canvas canvas = mGlCanvas;
                if (mGL != null && canvas != null) {
                    mGL.glDisable(GL_SCISSOR_TEST);
                    mGL.glClearColor(0, 0, 0, 0);
                    mGL.glClear(GL_COLOR_BUFFER_BIT);
                    mGL.glEnable(GL_SCISSOR_TEST);

                    mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                    mAttachInfo.mIgnoreDirtyState = true;
                    mView.mPrivateFlags |= View.DRAWN;

                    int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                    try {
                        canvas.translate(0, -yoff);
                        if (mTranslator != null) {
                            mTranslator.translateCanvas(canvas);
                        }
                        canvas.setScreenDensity(scalingRequired
                                ? DisplayMetrics.DENSITY_DEVICE : 0);
                        mView.draw(canvas);
                        if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                            mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                        }
                    } finally {
                        canvas.restoreToCount(saveCount);
                    }

                    mAttachInfo.mIgnoreDirtyState = false;

                    mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
                    checkEglErrors();

                    if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                        int now = (int)SystemClock.elapsedRealtime();
                        if (sDrawTime != 0) {
                            nativeShowFPS(canvas, now - sDrawTime);
                        }
                        sDrawTime = now;
                    }
                }
            }
            if (scrolling) {
                mFullRedrawNeeded = true;
                scheduleTraversals();
            }
            return;
        }

  ...

        Canvas canvas;
        try {
            int left = dirty.left;
            int top = dirty.top;
            int right = dirty.right;
            int bottom = dirty.bottom;
            //lock canvas
            canvas = surface.lockCanvas(dirty);

    ...

        try {
            if (!dirty.isEmpty() || mIsAnimating) {
                long startTime = 0L;

                if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                    Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
                            + canvas.getWidth() + ", h=" + canvas.getHeight());
                    //canvas.drawARGB(255, 255, 0, 0);
                }

                if (Config.DEBUG && ViewDebug.profileDrawing) {
                    startTime = SystemClock.elapsedRealtime();
                }

                // If this bitmap's format includes an alpha channel, we
                // need to clear it before drawing so that the child will
                // properly re-composite its drawing on a transparent
                // background. This automatically respects the clip/dirty region
                // or
                // If we are applying an offset, we need to clear the area
                // where the offset doesn't appear to avoid having garbage
                // left in the blank areas.
                if (!canvas.isOpaque() || yoff != 0) {
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }

                dirty.setEmpty();
                mIsAnimating = false;
                mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                mView.mPrivateFlags |= View.DRAWN;

                if (DEBUG_DRAW) {
                    Context cxt = mView.getContext();
                    Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
                            ", metrics=" + cxt.getResources().getDisplayMetrics() +
                            ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                }
                int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                try {
                    canvas.translate(0, -yoff);
                    if (mTranslator != null) {
                        mTranslator.translateCanvas(canvas);
                    }
                    canvas.setScreenDensity(scalingRequired
                            ? DisplayMetrics.DENSITY_DEVICE : 0);
                           //在canvas中绘制
                    mView.draw(canvas);
                } finally {
                    mAttachInfo.mIgnoreDirtyState = false;
                    canvas.restoreToCount(saveCount);
                }

                if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                    mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                }

                if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                    int now = (int)SystemClock.elapsedRealtime();
                    if (sDrawTime != 0) {
                        nativeShowFPS(canvas, now - sDrawTime);
                    }
                    sDrawTime = now;
                }

                if (Config.DEBUG && ViewDebug.profileDrawing) {
                    EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                }
            }

        } finally {
        	//unlock画布
            surface.unlockCanvasAndPost(canvas);
        }

        if (LOCAL_LOGV) {
            Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
        }

        if (scrolling) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
    }

总结下,ViewRoot能处理Handler的消息,Activity的显示就是由ViewRoot在它的performTraversals函数中完成的。
activity的绘制就是从mSurface中lock一块canvas,然后交给mView去画画,最后unlock并释放这块canvas。

surface

在ViewRoot构造时候,会创建一个surface,viewroot通过IWindowSession和WMS交互,WMS中调用的attach函数会构造一个SurfaceSession。而ViewRoot在performTraversal的过程会调用IWindowSession的relayout函数。
从relayout开始,relayout是一个跨进程调用,实际工作由WMS完成。

客户端的relayoutWindow:

    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        float appScale = mAttachInfo.mApplicationScale;
        boolean restore = false;
        if (params != null && mTranslator != null) {
            restore = true;
            params.backup();
            mTranslator.translateWindowLayout(params);
        }
        if (params != null) {
            if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params);
        }
        mPendingConfiguration.seq = 0;
        int relayoutResult = sWindowSession.relayout(
                mWindow, params,
                (int) (mView.mMeasuredWidth * appScale + 0.5f),
                (int) (mView.mMeasuredHeight * appScale + 0.5f),
                viewVisibility, insetsPending, mWinFrame,
                mPendingContentInsets, mPendingVisibleInsets,
                mPendingConfiguration, mSurface);//传递了surface
        if (restore) {
            params.restore();
        }
        
        if (mTranslator != null) {
            mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
            mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
            mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
        }
        return relayoutResult;
    }

服务端的relayoutWindow

frameworks/base/services/java/com/android/server/WindowManagerService.java
   public int relayoutWindow(Session session, IWindow client,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, boolean insetsPending,
            Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
            Configuration outConfig, Surface outSurface) {
        boolean displayed = false;
        boolean inTouchMode;
        boolean configChanged;
        long origId = Binder.clearCallingIdentity();

        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            win.mRequestedWidth = requestedWidth;
            win.mRequestedHeight = requestedHeight;

            if (attrs != null) {
                mPolicy.adjustWindowParamsLw(attrs);
            }

            int attrChanges = 0;
            int flagChanges = 0;
            if (attrs != null) {
                flagChanges = win.mAttrs.flags ^= attrs.flags;
                attrChanges = win.mAttrs.copyFrom(attrs);
            }

            if (DEBUG_LAYOUT) Slog.v(TAG, "Relayout " + win + ": " + win.mAttrs);

            if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
                win.mAlpha = attrs.alpha;
            }

            final boolean scaledWindow =
                ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0);

            if (scaledWindow) {
                // requested{Width|Height} Surface's physical size
                // attrs.{width|height} Size on screen
                win.mHScale = (attrs.width  != requestedWidth)  ?
                        (attrs.width  / (float)requestedWidth) : 1.0f;
                win.mVScale = (attrs.height != requestedHeight) ?
                        (attrs.height / (float)requestedHeight) : 1.0f;
            } else {
                win.mHScale = win.mVScale = 1;
            }

            boolean imMayMove = (flagChanges&(
                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) != 0;

            boolean focusMayChange = win.mViewVisibility != viewVisibility
                    || ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0)
                    || (!win.mRelayoutCalled);

            boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
                    && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;

            win.mRelayoutCalled = true;
            final int oldVisibility = win.mViewVisibility;
            win.mViewVisibility = viewVisibility;
            if (viewVisibility == View.VISIBLE &&
                    (win.mAppToken == null || !win.mAppToken.clientHidden)) {
                displayed = !win.isVisibleLw();
                if (win.mExiting) {
                    win.mExiting = false;
                    win.mAnimation = null;
                }
                if (win.mDestroying) {
                    win.mDestroying = false;
                    mDestroySurface.remove(win);
                }
                if (oldVisibility == View.GONE) {
                    win.mEnterAnimationPending = true;
                }
                if (displayed) {
                    if (win.mSurface != null && !win.mDrawPending
                            && !win.mCommitDrawPending && !mDisplayFrozen
                            && mPolicy.isScreenOn()) {
                        applyEnterAnimationLocked(win);
                    }
                    if ((win.mAttrs.flags
                            & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) {
                        if (DEBUG_VISIBILITY) Slog.v(TAG,
                                "Relayout window turning screen on: " + win);
                        win.mTurnOnScreen = true;
                    }
                    int diff = 0;
                    if (win.mConfiguration != mCurConfiguration
                            && (win.mConfiguration == null
                                    || (diff=mCurConfiguration.diff(win.mConfiguration)) != 0)) {
                        win.mConfiguration = mCurConfiguration;
                        if (DEBUG_CONFIGURATION) {
                            Slog.i(TAG, "Window " + win + " visible with new config: "
                                    + win.mConfiguration + " / 0x"
                                    + Integer.toHexString(diff));
                        }
                        outConfig.setTo(mCurConfiguration);
                    }
                }
                if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
                    // To change the format, we need to re-build the surface.
                    win.destroySurfaceLocked();
                    displayed = true;
                }
                try {
                //win就是WinStat,创建本地的surface对象
                    Surface surface = win.createSurfaceLocked();
                    if (surface != null) {
                    //在outSurface调用copyFrom,将本地surface拷贝到outSurface中
                        outSurface.copyFrom(surface);
                        win.mReportDestroySurface = false;
                        win.mSurfacePendingDestroy = false;
                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
                                "  OUT SURFACE " + outSurface + ": copied");
                    } else {
                        // For some reason there isn't a surface.  Clear the
                        // caller's object so they see the same state.
                        outSurface.release();
                    }
                } catch (Exception e) {
                    Slog.w(TAG, "Exception thrown when creating surface for client "
                             + client + " (" + win.mAttrs.getTitle() + ")",
                             e);
                    Binder.restoreCallingIdentity(origId);
                    return 0;
                }
                if (displayed) {
                    focusMayChange = true;
                }
                if (win.mAttrs.type == TYPE_INPUT_METHOD
                        && mInputMethodWindow == null) {
                    mInputMethodWindow = win;
                    imMayMove = true;
                }
                if (win.mAttrs.type == TYPE_BASE_APPLICATION
                        && win.mAppToken != null
                        && win.mAppToken.startingWindow != null) {
                    // Special handling of starting window over the base
                    // window of the app: propagate lock screen flags to it,
                    // to provide the correct semantics while starting.
                    final int mask =
                        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                        | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
                    WindowManager.LayoutParams sa = win.mAppToken.startingWindow.mAttrs;
                    sa.flags = (sa.flags&~mask) | (win.mAttrs.flags&mask);
                }
            } else {
                win.mEnterAnimationPending = false;
                if (win.mSurface != null) {
                    if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
                            + ": mExiting=" + win.mExiting
                            + " mSurfacePendingDestroy=" + win.mSurfacePendingDestroy);
                    // If we are not currently running the exit animation, we
                    // need to see about starting one.
                    if (!win.mExiting || win.mSurfacePendingDestroy) {
                        // Try starting an animation; if there isn't one, we
                        // can destroy the surface right away.
                        int transit = WindowManagerPolicy.TRANSIT_EXIT;
                        if (win.getAttrs().type == TYPE_APPLICATION_STARTING) {
                            transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
                        }
                        if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() &&
                              applyAnimationLocked(win, transit, false)) {
                            focusMayChange = true;
                            win.mExiting = true;
                            mKeyWaiter.finishedKey(session, client, true,
                                    KeyWaiter.RETURN_NOTHING);
                        } else if (win.isAnimating()) {
                            // Currently in a hide animation... turn this into
                            // an exit.
                            win.mExiting = true;
                        } else if (win == mWallpaperTarget) {
                            // If the wallpaper is currently behind this
                            // window, we need to change both of them inside
                            // of a transaction to avoid artifacts.
                            win.mExiting = true;
                            win.mAnimating = true;
                        } else {
                            if (mInputMethodWindow == win) {
                                mInputMethodWindow = null;
                            }
                            win.destroySurfaceLocked();
                        }
                    }
                }

                if (win.mSurface == null || (win.getAttrs().flags
                        & WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING) == 0
                        || win.mSurfacePendingDestroy) {
                    // We are being called from a local process, which
                    // means outSurface holds its current surface.  Ensure the
                    // surface object is cleared, but we don't want it actually
                    // destroyed at this point.
                    win.mSurfacePendingDestroy = false;
                    outSurface.release();
                    if (DEBUG_VISIBILITY) Slog.i(TAG, "Releasing surface in: " + win);
                } else if (win.mSurface != null) {
                    if (DEBUG_VISIBILITY) Slog.i(TAG,
                            "Keeping surface, will report destroy: " + win);
                    win.mReportDestroySurface = true;
                    outSurface.copyFrom(win.mSurface);
                }
            }

            if (focusMayChange) {
                //System.out.println("Focus may change: " + win.mAttrs.getTitle());
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
                    imMayMove = false;
                }
                //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
            }

            // updateFocusedWindowLocked() already assigned layers so we only need to
            // reassign them at this point if the IM window state gets shuffled
            boolean assignLayers = false;

            if (imMayMove) {
                if (moveInputMethodWindowsIfNeededLocked(false) || displayed) {
                    // Little hack here -- we -should- be able to rely on the
                    // function to return true if the IME has moved and needs
                    // its layer recomputed.  However, if the IME was hidden
                    // and isn't actually moved in the list, its layer may be
                    // out of data so we make sure to recompute it.
                    assignLayers = true;
                }
            }
            if (wallpaperMayMove) {
                if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
                    assignLayers = true;
                }
            }

            mLayoutNeeded = true;
            win.mGivenInsetsPending = insetsPending;
            if (assignLayers) {
                assignLayersLocked();
            }
            configChanged = updateOrientationFromAppTokensLocked();
            performLayoutAndPlaceSurfacesLocked();
            if (displayed && win.mIsWallpaper) {
                updateWallpaperOffsetLocked(win, mDisplay.getWidth(),
                        mDisplay.getHeight(), false);
            }
            if (win.mAppToken != null) {
                win.mAppToken.updateReportedVisibilityLocked();
            }
            outFrame.set(win.mFrame);
            outContentInsets.set(win.mContentInsets);
            outVisibleInsets.set(win.mVisibleInsets);
            if (localLOGV) Slog.v(
                TAG, "Relayout given client " + client.asBinder()
                + ", requestedWidth=" + requestedWidth
                + ", requestedHeight=" + requestedHeight
                + ", viewVisibility=" + viewVisibility
                + "\nRelayout returning frame=" + outFrame
                + ", surface=" + outSurface);

            if (localLOGV || DEBUG_FOCUS) Slog.v(
                TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange);

            inTouchMode = mInTouchMode;
        }

        if (configChanged) {
            sendNewConfiguration();
        }

        Binder.restoreCallingIdentity(origId);

        return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0)
                | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);
    }

createSurfaceLocked

frameworks/base/services/java/com/android/server/WindowManagerService.java:WindowState
        Surface createSurfaceLocked() {
            if (mSurface == null) {
                mReportDestroySurface = false;
                mSurfacePendingDestroy = false;
                mDrawPending = true;
                mCommitDrawPending = false;
                mReadyToShow = false;
                if (mAppToken != null) {
                    mAppToken.allDrawn = false;
                }

                int flags = 0;
                if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) {
                    flags |= Surface.PUSH_BUFFERS;
                }

                if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
                    flags |= Surface.SECURE;
                }
                if (DEBUG_VISIBILITY) Slog.v(
                    TAG, "Creating surface in session "
                    + mSession.mSurfaceSession + " window " + this
                    + " w=" + mFrame.width()
                    + " h=" + mFrame.height() + " format="
                    + mAttrs.format + " flags=" + flags);

                int w = mFrame.width();
                int h = mFrame.height();
                if ((mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
                    // for a scaled surface, we always want the requested
                    // size.
                    w = mRequestedWidth;
                    h = mRequestedHeight;
                }

                // Something is wrong and SurfaceFlinger will not like this,
                // try to revert to sane values
                if (w <= 0) w = 1;
                if (h <= 0) h = 1;

                mSurfaceShown = false;
                mSurfaceLayer = 0;
                mSurfaceAlpha = 1;
                mSurfaceX = 0;
                mSurfaceY = 0;
                mSurfaceW = w;
                mSurfaceH = h;
                try {
                    mSurface = new Surface(
                            mSession.mSurfaceSession, mSession.mPid,
                            mAttrs.getTitle().toString(),
                            0, w, h, mAttrs.format, flags);
                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "  CREATE SURFACE "
                            + mSurface + " IN SESSION "
                            + mSession.mSurfaceSession
                            + ": pid=" + mSession.mPid + " format="
                            + mAttrs.format + " flags=0x"
                            + Integer.toHexString(flags)
                            + " / " + this);
                } catch (Surface.OutOfResourcesException e) {
                    Slog.w(TAG, "OutOfResourcesException creating surface");
                    reclaimSomeSurfaceMemoryLocked(this, "create");
                    return null;
                } catch (Exception e) {
                    Slog.e(TAG, "Exception creating surface", e);
                    return null;
                }

                if (localLOGV) Slog.v(
                    TAG, "Got surface: " + mSurface
                    + ", set left=" + mFrame.left + " top=" + mFrame.top
                    + ", animLayer=" + mAnimLayer);
                if (SHOW_TRANSACTIONS) {
                    Slog.i(TAG, ">>> OPEN TRANSACTION");
                    if (SHOW_TRANSACTIONS) logSurface(this,
                            "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" +
                            mFrame.width() + "x" + mFrame.height() + "), layer=" +
                            mAnimLayer + " HIDE", null);
                }
                Surface.openTransaction();//打开一个事务
                try {
                    try {
                        mSurfaceX = mFrame.left + mXOffset;
                        mSurfaceY = mFrame.top + mYOffset;
                        mSurface.setPosition(mSurfaceX, mSurfaceY);
                        mSurfaceLayer = mAnimLayer;
                        mSurface.setLayer(mAnimLayer);
                        mSurfaceShown = false;
                        mSurface.hide();
                        if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) {
                            if (SHOW_TRANSACTIONS) logSurface(this, "DITHER", null);
                            mSurface.setFlags(Surface.SURFACE_DITHER,
                                    Surface.SURFACE_DITHER);
                        }
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "Error creating surface in " + w, e);
                        reclaimSomeSurfaceMemoryLocked(this, "create-init");
                    }
                    mLastHidden = true;
                } finally {
                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION");
                    Surface.closeTransaction();//关闭事务
                }
                if (localLOGV) Slog.v(
                        TAG, "Created surface " + this);
            }
            return mSurface;
        }

jni层分析

Surface无参构造

frameworks/base/core/java/android/view/Surface.java
    /**
     * Create an empty surface, which will later be filled in by
     * readFromParcel().
     * {@hide}
     */
    public Surface() {
        if (DEBUG_RELEASE) {
            mCreationStack = new Exception();
        }
        //CompatibleCanvas继承自Canvas
        mCanvas = new CompatibleCanvas();
    }

canvas 根据文档可以知道,canvas相关的类有几个。

  • Bitmap:存储像素,就是画布
  • canvas:记载画图的工作,比如画圆形、方形
  • drawing primitive:在计算机图形学中绘制基本形状的过程。基本形状通常指的是点、线和三角形。在OpenGL或其他图形渲染API中,绘制基本形状是构建复杂图形的基础。
  • paint:描述绘画时候使用的颜色、风格(实线、虚线)

canvas一般会封装一块bitmap,作画就基于这块bitmap。

SurfaceSession

frameworks/base/core/java/android/view/SurfaceSession.java
/**
 * An instance of this class represents a connection to the surface
 * flinger, in which you can create one or more Surface instances that will
 * be composited to the screen.
 * {@hide}
 */
public class SurfaceSession {
    /** Create a new connection with the surface flinger. */
    public SurfaceSession() {
        init();//native函数
    }

看下这个init的实现:

SurfaceSession_init

frameworks/base/core/jni/android_view_Surface.cpp
static void SurfaceSession_init(JNIEnv* env, jobject clazz)
{
//创建了SurfaceComposerClient对象
    sp<SurfaceComposerClient> client = new SurfaceComposerClient;
    client->incStrong(clazz);
    //在java对象中保存它
    env->SetIntField(clazz, sso.client, (int)client.get());
}

surface的有参构造

frameworks/base/core/java/android/view/Surface.java
  /**
     * create a surface with a name
     * {@hide}
     */
    public Surface(SurfaceSession s,//传入SurfaceSession
            int pid, String name, int display, int w, int h, int format, int flags)
        throws OutOfResourcesException {
        if (DEBUG_RELEASE) {
            mCreationStack = new Exception();
        }
        mCanvas = new CompatibleCanvas();
        //native函数,传递了display,w h是宽高
        init(s,pid,name,display,w,h,format,flags);
        mName = name;
    }

看下 init(s,pid,name,display,w,h,format,flags);

frameworks/base/core/jni/android_view_Surface.cpp
static void Surface_init(
        JNIEnv* env, jobject clazz, 
        jobject session,
        jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)
{
    if (session == NULL) {
        doThrow(env, "java/lang/NullPointerException");
        return;
    }
    //从 SurfaceSession对象中取出之前创建的之前创建的SurfaceComposerClient对象
    SurfaceComposerClient* client =
            (SurfaceComposerClient*)env->GetIntField(session, sso.client);

	//SurfaceControl
    sp<SurfaceControl> surface;
    if (jname == NULL) {
        surface = client->createSurface(pid, dpy, w, h, format, flags);
    } else {
        const jchar* str = env->GetStringCritical(jname, 0);
        const String8 name(str, env->GetStringLength(jname));
        env->ReleaseStringCritical(jname, str);
        //调用SurfaceComposerClient的createSurface,返回的是是SurfaceControl
        surface = client->createSurface(pid, name, dpy, w, h, format, flags);
    }

    if (surface == 0) {
        doThrow(env, OutOfResourcesException);
        return;
    }
    //把SurfaceControl设置到java层的surface对象中
    setSurfaceControl(env, clazz, surface);
}

Surface_copyFrom

static void Surface_copyFrom(
        JNIEnv* env, jobject clazz, jobject other)
{
    if (clazz == other)
        return;

    if (other == NULL) {
        doThrow(env, "java/lang/NullPointerException", NULL);
        return;
    }

    /*
     * This is used by the WindowManagerService just after constructing
     * a Surface and is necessary for returning the Surface reference to
     * the caller. At this point, we should only have a SurfaceControl.
     */
    
    const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
    const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
    if (!SurfaceControl::isSameSurface(surface, rhs)) {
        // we reassign the surface only if it's a different one
        // otherwise we would loose our client-side state.
        setSurfaceControl(env, clazz, rhs);
    }
}

Surface_writeToParcel

static void Surface_writeToParcel(
        JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
{
    Parcel* parcel = (Parcel*)env->GetIntField(
            argParcel, no.native_parcel);

    if (parcel == NULL) {
        doThrow(env, "java/lang/NullPointerException", NULL);
        return;
    }

    const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
    SurfaceControl::writeSurfaceToParcel(control, parcel);
    if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
        setSurfaceControl(env, clazz, 0);
    }
}

Surface_readFromParcel

static void Surface_readFromParcel(
        JNIEnv* env, jobject clazz, jobject argParcel)
{
    Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
    if (parcel == NULL) {
        doThrow(env, "java/lang/NullPointerException", NULL);
        return;
    }

    const sp<Surface>& control(getSurface(env, clazz));
    sp<Surface> rhs = new Surface(*parcel);
    if (!Surface::isSameSurface(control, rhs)) {
        // we reassign the surface only if it's a different one
        // otherwise we would loose our client-side state.
        setSurface(env, clazz, rhs);
    }
}

这里面WMS的surface调用了带参的SurfaceSession构造函数,ViewRoot的surface调用了无参的构造函数。
copyFrom就是把WMS的surface信息,拷贝到ViewRoot中的surface中。这其中使用了AIDL,xxx.aidl文件可以转换为java文件.

总结

我们来回顾和总结一路过来的分析,为后续破解surfaceflinger做准备。
创建了一个SurfaeComposerClient,调用它的createSurface,拿到一个SurfaceControl对象。调用SurfaceControl的writeToParcel把信息写道parcel中。根据parcel的信息构造一个surface对象,并保存到java层的mSurface对象中。
这样viewroot得到了一个native的surface对象。

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

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

相关文章

从商品图到海报生成 京东广告AIGC创意技术应用

一、前言 电商广告图片不仅能够抓住消费者的眼球&#xff0c;还可以传递品牌核心价值和故事&#xff0c;建立起与消费者之间的情感联系。然而现有的广告图片大多依赖人工制作&#xff0c;存在效率和成本的限制。尽管最近 AIGC 技术取得了卓越的进展&#xff0c;但其在广告图片…

嵌入式中常用的巧妙方法 - (汇总)

概述 做项目&#xff0c;掌握以下方法&#xff0c;可提高开发效率&#xff0c;把时间全部放在需求上。 1、快速获取结构体成员大小 #include <stdio.h> // 获取结构体成员大小 #define GET_MEMBER_SIZE(type, member) sizeof(((type*)0)->member)// 获取结构体成…

2024 大模型面试指南:兄弟们,冲啊

前言 老宋这俩月又跳槽了&#xff0c;自从去年从百度出来来到新公司&#xff0c;躺了一年&#xff0c;最近因为大模型技术发展&#xff0c;重新有了奋斗的方向和动力。 大模型的诞生必然会重塑整个 NLP 方向&#xff0c;因此&#xff0c;必须参与到这波浪潮中&#xff0c;果然…

HTTP快速面试笔记(速成版)

文章目录 1. HTTP概述1.1 HTTP简介1.2 HTTP的版本1.3 URL语法简介 2. HTTP报文2.1 HTTP报文格式2.2 HTTP的方法&#xff08;Method&#xff09;2.3 HTTP响应码2.4 HTTP请求头与响应头 3. HTTPS详解3.1 HTTPS介绍3.2 与HTTPS相关的加解密知识3.3 HTTPS交互流程 参考资料 1. HTTP…

2 万字 42 道Java经典面试题总结(2024修订版)- Java集合篇

目录 1、Java中常用的集合有哪些&#xff1f;2、Collection 和 Collections 有什么区别&#xff1f;3、为什么集合类没有实现 Cloneable 和 Serializable 接口&#xff1f;4、数组和集合有什么本质区别&#xff1f;5、数组和集合如何选择&#xff1f;6、list与Set区别7、HashMa…

基于深度学习的人脸表情识别系统(PyQT+代码+训练数据集)

基于深度学习的人脸表情识别系统&#xff08;PyQT代码训练数据集&#xff09; 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现 前言 本项目是基于mini_Xception深度学习网络模型的人脸表情识别系统&#x…

el-upload文件缩略图只显示一张图片

采用elementui库vue2版本&#xff0c;flask后端 el-upload组件上传一张图片之后不在出现新增加号 可以实现

基于公共转点的Alpha shapes有序边缘点提取

1、原理介绍 由Edelsbrunner H提出的alpha shapes算法是一种简单、有效的快速提取边界点算法。其克服了点云边界点形状影响的缺点,可快速准确提取边界点,其原理如下:对于任意形状的平面点云,若一个半径为a的圆,绕其进行滚动,其滚动的轨迹形成的点为轮廓点。需要注意的是,…

深入理解计算机系统 家庭作业 2.84

这题没有这个要求所以可以用 ? > : < 这种运算 以下代码用的是位级运算.因为我误解了题意 呜呜呜 想看用判断的代码请自行百度 ((((ux<<9>>9)<<((ux<<1>>24)-127)) - ((uy<<9>>9)<<((uy<<1>>24)-127)))>…

TMS320F280049 EPWM模块--TZ子模块(6)

下图是TZ子模块在epwm中的位置&#xff0c;可以看到TZ子模块接收内外部多种信号&#xff0c;经过处理后生成最终epwm波形&#xff0c;然后通过gpio向外发出。 TZ的动作有4个&#xff1a;拉高/拉低/高阻/不变。 TZ的内部框图见下图&#xff0c;可以看出&#xff1a; 1&#xf…

每日一题 — 水果成篮

思路&#xff1a; 通过阅读上面文字得出问题&#xff1a;就去只有两个种类的最大长度的连续子数组&#xff0c;这时我们可以想到用哈希表来存储数据&#xff0c;记录数据的种类和每个种类的数量。 解法一&#xff1a;暴力递归&#xff08;right每次遍历完都回退&#xff09; 解…

windows本地运行dreamtalk踩坑总结

dreamtalk是一个语音图片转视频的一个工具&#xff0c;就是给一段语音加一个头像图片&#xff0c;然后生成一段头像跟语音对口型的视频&#xff0c;其实还是很有意思的&#xff0c;最近阿里发布了一个类似的模型&#xff0c;但是还没开源&#xff0c;从展示视频看&#xff0c;阿…

Day31:贪心 LeedCode 455.分发饼干 376. 摆动序列 53. 最大子序和 蓝桥杯.填充

贪心算法一般分为如下四步&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 做题的时候&#xff0c;只要想清楚 局部最优 是什么&#xff0c;如果推导出全局最优&#xff0c;其实就够了。 假设你是一位很棒的家…

场景文本检测识别学习 day04(目标检测的基础概念)

经典的目标检测方法 one-stage 单阶段法&#xff1a;YOLO系列 one-stage方法&#xff1a;仅使用一个CNN&#xff0c;直接在特征图上预测每个物体的类别和边界框输入图像之后&#xff0c;使用CNN网络提取特征图&#xff0c;不加入任何补充&#xff08;锚点、锚框&#xff09;&…

QT系列教程(2) 创建项目和编译

新建Qt Widgets应用 我们启动qt creator 创建项目&#xff0c;选择Qt Widgets应用 接下来选择项目目录&#xff0c;项目名字就叫helloworld 构建系统选择qmake 我们创建一个名字为HelloDialog的类&#xff0c;继承于QDialog 构建套件选择你们安装的就行了&#xff0c;我这里选…

SV-704XT 100W网络有源音柱 校园广播音柱

SV-704XT 100W网络有源音柱 一、描述 SV-704XT是深圳锐科达电子有限公司的一款壁挂式网络有源音柱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;其采用防水设计&#xff0c;功率100W。SV-704XT作为网络广播播放系统的终…

嵌入式:第二天(C语言入门)

目录 一、基础语法 位运算符&#xff1a; & -&#xff08;与运算&#xff09; | -&#xff08;或运算&#xff09; ^ -&#xff08;异或运算&#xff09; ~ -&#xff08;取反运算&#xff09; << -&#xff08;左移运算符&#xff09; >> -&#xff0…

MyBatis 中的动态 SQL 的相关使用方法

为什么会有动态SQL&#xff0c;把SQL写死不是比较方便吗&#xff1f;其实有很多的举例&#xff0c;这里我那一个常见的来说&#xff0c;像我们用户注册&#xff0c;会有必填字段和非必填字段&#xff0c;有些传来的参数不一样&#xff0c;那对应的SQL也不一样&#xff0c;因此&…

【ctf.show】获得百分之百的快乐

1.打开靶场 2.根据页面代码&#xff0c;get请求值只能小于4位数&#xff0c;否则会回显hack&#xff01; 尝试后确实是这样的&#xff1a; 请求值小于4位数&#xff0c;页面无变化&#xff1a; 发送请求值ls查看内容 3.根据2返回的值&#xff0c;发送值为?1>nl 创建一个nl…

每日一练(力扣)

我的思路是暴力枚举: 情况1:相同&#xff0c;就让子串和原串同时后移继续比较 情况2:不相同&#xff0c;就只让原串后移 public int strStr(String haystack, String needle) {if (haystack.length() < needle.length()){return -1;}for (int i 0; i < h…