前言
嗯嗯 windowManager
是用来干嘛的?
从名字上可以看出windowManger是窗口管理的意思, 主要
window
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
/** Flag for the "options panel" feature. This is enabled by default. */
public static final int FEATURE_OPTIONS_PANEL = 0;
/** Flag for the "no title" feature, turning off the title at the top
* of the screen. */
public static final int FEATURE_NO_TITLE = 1;
//....
}
看下类的解释 :一个顶级窗口和行为策略的抽象基类, 实例作为顶级view添加到windowManager,提供了标准UI,唯一实现类是PhoneWindow .
哪些地方用到了windowManager
windowManager什么时候创建的
- IWindowSession mWindowSession;
Activity中的windowManager的创建
在dialog中调用
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
时
在Activity中
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
attach时
final void attach(...) {
//....
"1.创建window"
mWindow = new PhoneWindow(this, window, activityConfigCallback);
"2.设置window回调"
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//....
"4.创建windowManger并关联window和windowManger"
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
//...
}
在服务中获取windowManager
下图是输入法InputMethodService创建输入法dialog的过程
softInputWindow实际就是输入法的视图区
image.png
断点继续走到dialog的创建过程
用户
此时用户获取windowManger的上下文是service
image.png image.png image.pngimage.png
image.png
=========================================================
可以看到最终是通过了 SystemServiceRegistry 这个类获取windowManager
"管理所有可以通过Context的getSystemService()获取的系统服务"
/**
* Manages all of the system services that can be returned by {@link Context#getSystemService}.
* Used by {@link ContextImpl}.
*
*/
final class SystemServiceRegistry {
private static final String TAG = "SystemServiceRegistry";
// Service registry information.
// This information is never changed once static initialization has completed.
"保存了所有的系统服务的名字"
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
"保存了所有的系统服务的ServiceFetcher "
"getSystemService时 是通过服务的名字先获取ServiceFetcher ,然后通过fetcher.getService(ctx) 获取注册了的服务"
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
private static int sServiceCacheSize;
// Not instantiable.
private SystemServiceRegistry() { }
//类加载时注册所有的服务
static {
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
//太多服务了 这里先忽略..........
//WINDOW_SERVICE 在这里注册的
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
//太多服务了 这里先忽略..........
}
/**
* Creates an array which is used to cache per-Context service instances.
*/
public static Object[] createServiceCache() {
return new Object[sServiceCacheSize];
}
"here 通过contextImpl 和Context中的服务名(如Context.WINDOW_SERVICE) 获取服务"
"getSystemService时 是通过服务的名字先获取ServiceFetcher ,然后通过fetcher.getService(ctx) 获取注册了的服务"
/**
* Gets a system service from a given context.
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
/**
* Gets the name of the system-level service that is represented by the specified class.
*/
public static String getSystemServiceName(Class<?> serviceClass) {
return SYSTEM_SERVICE_NAMES.get(serviceClass);
}
"类加载时调用 注册系统服务"
/**
* Statically registers a system service with the context.
* This method must be called during static initialization only
*/
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
"系统服务获取的接口"
/**
* Base interface for classes that fetch services.
* These objects must only be created during static initialization.
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
"当system service需要 context时使用这个"
/**
* Override this class when the system service constructor needs a
* ContextImpl and should be cached and retained by that context.
*/
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
"如果服务为空通过 ContextImpl create 并保存到缓存中"
if (service == null) {
try {
service = createService(ctx);
"把ServiceFetcher create的服务加到缓存中,下次可以直接从缓存中获取"
cache[mCacheIndex] = service;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return (T)service;
}
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
"当system service不需要 context时使用这个"
/**
* Override this class when the system service does not need a ContextImpl
* and should be cached and retained process-wide.
*/
static abstract class StaticServiceFetcher<T> implements ServiceFetcher<T> {
private T mCachedInstance;
@Override
public final T getService(ContextImpl ctx) {
synchronized (StaticServiceFetcher.this) {
if (mCachedInstance == null) {
try {
mCachedInstance = createService();
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return mCachedInstance;
}
}
public abstract T createService() throws ServiceNotFoundException;
}
"每个进程只有一个实例的system service使用这个StaticApplicationContextServiceFetcher注册 ==>只有ConnectivityManager用到 "
/**
* Like StaticServiceFetcher, creates only one instance of the service per application, but when
* creating the service for the first time, passes it the application context of the creating
* application.
*
* TODO: Delete this once its only user (ConnectivityManager) is known to work well in the
* case where multiple application components each have their own ConnectivityManager object.
*/
static abstract class StaticApplicationContextServiceFetcher<T> implements ServiceFetcher<T> {
private T mCachedInstance;
@Override
public final T getService(ContextImpl ctx) {
synchronized (StaticApplicationContextServiceFetcher.this) {
if (mCachedInstance == null) {
Context appContext = ctx.getApplicationContext();
// If the application context is null, we're either in the system process or
// it's the application context very early in app initialization. In both these
// cases, the passed-in ContextImpl will not be freed, so it's safe to pass it
// to the service. http://b/27532714 .
try {
mCachedInstance = createService(appContext != null ? appContext : ctx);
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return mCachedInstance;
}
}
public abstract T createService(Context applicationContext) throws ServiceNotFoundException;
}
public static void onServiceNotFound(ServiceNotFoundException e) {
// We're mostly interested in tracking down long-lived core system
// components that might stumble if they obtain bad references; just
// emit a tidy log message for normal apps
if (android.os.Process.myUid() < android.os.Process.FIRST_APPLICATION_UID) {
Log.wtf(TAG, e.getMessage(), e);
} else {
Log.w(TAG, e.getMessage());
}
}
}
========================================================
image.png /**
* Context for decor views which can be seeded with pure application
context and not depend on the
* activity, but still provide some of the facilities that Activity has,
* e.g. themes, activity-based resources, etc.
*
* @hide
*/
class DecorContext extends ContextThemeWrapper {
private PhoneWindow mPhoneWindow;
private WindowManager mWindowManager;
private Resources mActivityResources;
public DecorContext(Context context, Resources activityResources) {
super(context, null);
mActivityResources = activityResources;
}
void setPhoneWindow(PhoneWindow phoneWindow) {
mPhoneWindow = phoneWindow;
mWindowManager = null;
}
@Override
public Object getSystemService(String name) {
if (Context.WINDOW_SERVICE.equals(name)) {
if (mWindowManager == null) {
WindowManagerImpl wm =
(WindowManagerImpl) super.getSystemService(Context.WINDOW_SERVICE);
mWindowManager = wm.createLocalWindowManager(mPhoneWindow);
}
return mWindowManager;
}
return super.getSystemService(name);
}
@Override
public Resources getResources() {
return mActivityResources;
}
@Override
public AssetManager getAssets() {
return mActivityResources.getAssets();
}
}
Dialog
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
//...
"qtip: look here!!! dialog 中的windowManger "
private final WindowManager mWindowManager;
final Context mContext;
"qtip: look here!!! dialog 中的Window "
final Window mWindow;
View mDecor;
//....
/**
* Creates a dialog window that uses the default dialog theme.
* <p>
* The supplied {@code context} is used to obtain the window manager and
* base theme used to present the dialog.
*
* @param context the context in which the dialog should run
* @see android.R.styleable#Theme_dialogTheme
*/
public Dialog(@NonNull Context context) {
this(context, 0, true);
}
//....... 以下略
}
//dialog的创建
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == ResourceId.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
"1.获取windowManager "
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
"2 创建window"
final Window w = new PhoneWindow(mContext);
mWindow = w;
"3.设置window回调"
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
" 4 给window设置windowManger "
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
PopupWindow
public class PopupWindow {
//...
private Context mContext;
" PopupWindow 中的windowManager ..."
private WindowManager mWindowManager;
//...
/** View that handles event dispatch and content transitions. */
private PopupDecorView mDecorView;
// ...... 以下 略
}
popupWindow的显示
/**
* Display the content view in a popup window at the specified location.
*
* @param token Window token to use for creating the new window
* @param gravity the gravity which controls the placement of the popup window
* @param x the popup's x location offset
* @param y the popup's y location offset
*
* @hide Internal use only. Applications should use
* {@link #showAtLocation(View, int, int, int)} instead.
*/
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}
"继续看invokePopup()"
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
"向windowManger中添加View 显示popupWindow "
mWindowManager.addView(decorView, p);
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
"popupWindow的隐藏"
/**
* Disposes of the popup window. This method can be invoked only after
* {@link #showAsDropDown(android.view.View)} has been executed. Failing
* that, calling this method will have no effect.
*
* @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
//如果没有显示或者正在消失 return
if (!isShowing() || isTransitioningToDismiss()) {
return;
}
final PopupDecorView decorView = mDecorView;
final View contentView = mContentView;
final ViewGroup contentHolder;
final ViewParent contentParent = contentView.getParent();
if (contentParent instanceof ViewGroup) {
contentHolder = ((ViewGroup) contentParent);
} else {
contentHolder = null;
}
// Ensure any ongoing or pending transitions are canceled.
decorView.cancelTransitions();
mIsShowing = false;
mIsTransitioningToDismiss = true;
// This method may be called as part of window detachment, in which
// case the anchor view (and its root) will still return true from
// isAttachedToWindow() during execution of this method; however, we
// can expect the OnAttachStateChangeListener to have been called prior
// to executing this method, so we can rely on that instead.
final Transition exitTransition = mExitTransition;
if (exitTransition != null && decorView.isLaidOut()
&& (mIsAnchorRootAttached || mAnchorRoot == null)) {
// The decor view is non-interactive and non-IME-focusable during exit transitions.
final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
mWindowManager.updateViewLayout(decorView, p);
final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
final Rect epicenter = getTransitionEpicenter();
// Once we start dismissing the decor view, all state (including
// the anchor root) needs to be moved to the decor view since we
// may open another popup while it's busy exiting.
decorView.startExitTransition(exitTransition, anchorRoot, epicenter,
new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
dismissImmediate(decorView, contentHolder, contentView);
}
});
} else {
"立即消失 ==============="
dismissImmediate(decorView, contentHolder, contentView);
}
// Clears the anchor view.
detachFromAnchor();
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss();
}
}
"再来看看是怎么立即消失的"
/**
* Removes the popup from the window manager and tears down the supporting
* view hierarchy, if necessary.
*/
private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
// If this method gets called and the decor view doesn't have a parent,
// then it was either never added or was already removed. That should
// never happen, but it's worth checking to avoid potential crashes.
if (decorView.getParent() != null) {
//qtip: 移除decorView
mWindowManager.removeViewImmediate(decorView);
}
if (contentHolder != null) {
contentHolder.removeView(contentView);
}
// This needs to stay until after all transitions have ended since we
// need the reference to cancel transitions in preparePopup().
mDecorView = null;
mBackgroundView = null;
mIsTransitioningToDismiss = false;
}
看看decorView是啥
从windowManager中移除的decorView就是PopupWindow中的一个成员变量 :mDecorView
popupWindow 调用showAtLocation()时会先调用preparePopup(),在preparePopup()中完成decorView 和 mBackgroundView的创建
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
if (p.accessibilityTitle == null) {
p.accessibilityTitle = mContext.getString(R.string.popup_window_default_title);
}
// The old decor view may be transitioning out. Make sure it finishes
// and cleans up before we try to create another one.
if (mDecorView != null) {
mDecorView.cancelTransitions();
}
// When a background is available, we embed the content view within
// another view that owns the background drawable.
if (mBackground != null) {
"qtip: 通过popupWindow设置的contentView创建一个背景PopupBackgroundView,PopupBackgroundView继承于FramLayout"
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
"qtip: 再通过背景创建一个装饰视图 mDecorView"
mDecorView = createDecorView(mBackgroundView);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/);
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
}
/**
* Wraps a content view in a FrameLayout.
*
* @param contentView the content view to wrap
* @return a FrameLayout that wraps the content view
*/
private PopupDecorView createDecorView(View contentView) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
final int height;
if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
height = WRAP_CONTENT;
} else {
height = MATCH_PARENT;
}
final PopupDecorView decorView = new PopupDecorView(mContext);
decorView.addView(contentView, MATCH_PARENT, height);
decorView.setClipChildren(false);
decorView.setClipToPadding(false);
return decorView;
}
PopupDecorView是popupWindow的一个内部类 继承于FramLayout ,重新处理了点击和按键等事件以控制popupWindow返回键和touch消失
是不是对比PopupWindow 和dialog 好像有些不一样
image.png image.pngToast
public class Toast {
static final String TAG = "Toast";
static final boolean localLOGV = false;
/** @hide */
@IntDef({LENGTH_SHORT, LENGTH_LONG})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
/**
* Show the view or text notification for a short period of time. This time
* could be user-definable. This is the default.
* @see #setDuration
*/
public static final int LENGTH_SHORT = 0;
/**
* Show the view or text notification for a long period of time. This time
* could be user-definable.
* @see #setDuration
*/
public static final int LENGTH_LONG = 1;
final Context mContext;
" look here !! TN是什么 "
final TN mTN;
int mDuration;
View mNextView;
//...... 以下略
}
private static class TN extends ITransientNotification.Stub {
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
private static final int SHOW = 0;
private static final int HIDE = 1;
private static final int CANCEL = 2;
final Handler mHandler;
int mGravity;
int mX, mY;
float mHorizontalMargin;
float mVerticalMargin;
View mView;
View mNextView;
int mDuration;
"look here !!! windowManager "
WindowManager mWM;
String mPackageName;
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
TN(String packageName, @Nullable Looper looper) {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mPackageName = packageName;
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called Looper.prepare()");
}
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break;
}
}
}
};
}
网友评论