美文网首页
dialog动画的一个小问题

dialog动画的一个小问题

作者: ukyoo | 来源:发表于2018-12-18 16:02 被阅读0次

    概述

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

    <style name="DialogAnimation" parent="@android:style/Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/actionsheet_dialog_in</item>
        <item name="android:windowExitAnimation">@anim/actionsheet_dialog_out</item>
    </style>
    

    actionsheet_dialog_in.xml

    <?xml version="1.0" encoding="utf-8"?>
    <translate xmlns:android="http://schemas.android.com/apk/res/android"
               android:duration="200"
               android:fromYDelta="100%"
               android:toYDelta="0" />
    

    actionsheet_dialog_out.xml

    <?xml version="1.0" encoding="utf-8"?>
    <translate xmlns:android="http://schemas.android.com/apk/res/android"
               android:duration="200"
               android:fromYDelta="0"
               android:toYDelta="100%"/>
    

    但是这样有个问题,每次页面退到后台和返回前台的时候,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动画的一个小问题

          本文链接:https://www.haomeiwen.com/subject/yggwhqtx.html