概述
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;
}
-
AppTransition
在WindowManagerService
中被创建
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);
}
-
WindowManagerService
在SystemServer
中被创建
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测试一下,并没有泄露现象