dialog动画的一个小问题

概述

dialog的windowAnimation肯定不会陌生,大家都习惯了这么使用


actionsheet_dialog_in.xml



actionsheet_dialog_out.xml



但是这样有个问题,每次页面退到后台和返回前台的时候,dialog的动画都会再执行一次;这是因为dialog是通过PhoneWindow创建的,每次页面显示/隐藏都会触发我们事先设置好的windowAnimation动画

如果duration设置的非常短,看起来不太明显. 假设在开发者模式里把动画时长设置*10,就能明显看到页面退出后,dialog的window动画还在执行,例如


看起来非常奇葩,当然你把动画时长设置10倍以后,所有的APP都这样,dialog.dismiss()到底发生了什么呢,来看看源码是怎么处理的

分析

  • dialog的dismissDialog方法
  void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }
  • WindowManagerImpl中的removeViewImmediate方法
 @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

调用removeViewLocked方法

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
  • root.die方法中调用了ViewRootImpl中的doDie方法
 void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
        ........
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

可以看到,dispatchDetachedFromWindow做了资源释放的操作

   void dispatchDetachedFromWindow() {
        mFirstInputStage.onDetachedFromWindow();
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }
  • mWindowSession.remove(mWindow)是当前window(这里是dialog的phoneWindow)被移除的地方
    mWindowSession是IWindowSession.aidl,我们找到class Session extends IWindowSession.Stub里重写的地方
    @Override
    public void remove(IWindow window) {
        mService.removeWindow(this, window);
    }
  • 查看WindowManagerService中的removeWindow方法
    void removeWindow(Session session, IWindow client) {
        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return;
            }
            win.removeIfPossible();
        }
    }
  • 调用了WindowState类的removeIfPossible方法
    private void removeIfPossible(boolean keepVisibleDeadWindow) {
        .....
        if (wasVisible) {
            final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;

            // Try starting an animation.
            if (mWinAnimator.applyAnimationLocked(transit, false)) {
                mAnimatingExit = true;

                // mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that
                // any change from that is performed immediately.
                setDisplayLayoutNeeded();
                mService.requestTraversal();
            }
            //TODO (multidisplay): Magnification is supported only for the default display.
            if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
                mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
            }
        }
    }
  • com.android.server.wm.WindowStateAnimator类中,能够找到applyAnimationLocked(transit, false)
    boolean applyAnimationLocked(int transit, boolean isEntrance) {
        .....
        if (mWin.mToken.okToAnimate()) {
            int anim = mPolicy.selectAnimationLw(mWin, transit);
            int attr = -1;
            Animation a = null;
            if (anim != 0) {
                a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
            } else {
                switch (transit) {
                    case WindowManagerPolicy.TRANSIT_ENTER:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_EXIT:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_SHOW:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_HIDE:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                        break;
                }
                if (attr >= 0) {
                    a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr, TRANSIT_NONE);
                }
            }
        }
    }
  • 查看com.android.server.wm.AppTransition中的loadAnimationAttr方法,Animation用到的mContext是在AppTransition的构造中创建的,接着往下看
    Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
        int resId = ResourceId.ID_NULL;
        Context context = mContext;
        if (animAttr >= 0) {
            AttributeCache.Entry ent = getCachedAnimations(lp);
            if (ent != null) {
                context = ent.context;
                resId = ent.array.getResourceId(animAttr, 0);
            }
        }
        resId = updateToTranslucentAnimIfNeeded(resId, transit);
        if (ResourceId.isValid(resId)) {
            return AnimationUtils.loadAnimation(context, resId);
        }
        return null;
    }
  • AppTransitionWindowManagerService中被创建
  public static WindowManagerService main(final Context context, final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
            WindowManagerPolicy policy) {
        DisplayThread.getHandler().runWithScissors(() ->
                sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                        onlyCore, policy), 0);
        return sInstance;
   }
    private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {
        ......
        mAppTransition = new AppTransition(context, this);
        mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
    }
  • WindowManagerServiceSystemServer中被创建
D:\platform_frameworks_base-master\services\java\com\android\server\SystemServer.java
   private void startOtherServices() {
        final Context context = mSystemContext;
        .....
        // WMS needs sensor service ready
        ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);
        mSensorServiceStart = null;
        wm = WindowManagerService.main(
            context, inputManager,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
            !mFirstBoot, mOnlyCore, new PhoneWindowManager ()
        );
    }
  • SystemServer中的systemContext是在ActivityThread里创建的,作为程序启动入口的ActivityThread,网上有相当多的文章介绍
    @UnsupportedAppUsage
    public ContextImpl getSystemContext() {
        synchronized (this) {
            if (mSystemContext == null) {
                mSystemContext = ContextImpl.createSystemContext(this);
            }
            return mSystemContext;
        }
    }
  • core/java/android/app/ContextImpl.java中调用了createSystemContext方法

   @UnsupportedAppUsage
    static ContextImpl createSystemContext(ActivityThread mainThread) {
        LoadedApk packageInfo = new LoadedApk(mainThread);
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                null);
        context.setResources(packageInfo.getResources());
        context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                context.mResourcesManager.getDisplayMetrics());
        return context;
    }

到这里我们不难发现,context是属于ActivityThread的成员变量, 并不是持有的dialog所在页面的context现象,因此 丑归丑,泄露是没有的
用profier测试一下,并没有泄露现象

image.png

你可能感兴趣的:(dialog动画的一个小问题)