当前位置: 代码迷 >> 综合 >> Android悬浮窗TYPE_TOAST小结源码分析
  详细解决方案

Android悬浮窗TYPE_TOAST小结源码分析

热度:16   发布时间:2024-01-19 17:04:35.0

还是从最简单的地方开始, 我们调用了WindowManager.addView, WindowManager是个接口, 我们使用的是他的实现类WindowManagerImpl, 看看它的addView方法:

@Override
public void addView(View view, ViewGroup.LayoutParams    params) {mGlobal.addView(view, params, mDisplay, mParentWindow);
}

mGlobal是WindowManagerGlobal的实例, 再看看WindowManagerGlobal.addView:

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {......final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;......synchronized (mLock) {......root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);......}// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.......
}

代码中创建了一个ViewRootImpl, 调用了它的setView, 将我们要添加的view传入. 继续看ViewRootImpl.setView:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {......mWindowAttributes.copyFrom(attrs);if (mWindowAttributes.packageName == null) {mWindowAttributes.packageName = mBasePackageName;}......try {......res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);} catch (RemoteException e) {......throw new RuntimeException("Adding window failed", e);} finally {......}......}}
}

对我们的分析来说最关键的代码是

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);

mWindowSession的类型是IWindowSession, mWindow的类型是IWindow.Stub, 这句代码就是利用AIDL进行IPC, 实际被调用的是Session.addToDisplay:

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outContentInsets,InputChannel outInputChannel) {return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,outContentInsets, outInputChannel);
}

mService是WindowManagerService, 继续往下跟:

public int addWindow(Session session, IWindow client, int seq,WindowManager.LayoutParams attrs, int viewVisibility, int displayId,Rect outContentInsets, InputChannel outInputChannel) {int[] appOp = new int[1];int res = mPolicy.checkAddPermission(attrs, appOp);if (res != WindowManagerGlobal.ADD_OKAY) {return res;}......final int type = attrs.type;synchronized(mWindowMap) {......mPolicy.adjustWindowParamsLw(win.mAttrs);......}......return res;
}

mPolicy是标记为final的成员变量:

final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();

继续看PolicyManager.makeNewWindowManager:

public final class PolicyManager {private static final String POLICY_IMPL_CLASS_NAME ="com.android.internal.policy.impl.Policy";private static final IPolicy sPolicy;static {// Pull in the actual implementation of the policy at run-timetry {Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);sPolicy = (IPolicy)policyClass.newInstance();} catch (ClassNotFoundException ex) {throw new RuntimeException(POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);} catch (InstantiationException ex) {throw new RuntimeException(POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);} catch (IllegalAccessException ex) {throw new RuntimeException(POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);}}// Cannot instantiate this classprivate PolicyManager() {}......public static WindowManagerPolicy makeNewWindowManager() {return sPolicy.makeNewWindowManager();}......
}

这里sPolicy是com.android.internal.policy.impl.Policy对象, 再看看它的makeNewWindowManager方法返回的是什么:

public WindowManagerPolicy makeNewWindowManager() {return new PhoneWindowManager();
}

现在我们知道mPolicy实际上是PhoneWindowManager, 那么

int res = mPolicy.checkAddPermission(attrs, appOp);

实际调用的代码是:

@Override
public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {int type = attrs.type;outAppOp[0] = AppOpsManager.OP_NONE;if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW|| type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {return WindowManagerGlobal.ADD_OKAY;}String permission = null;switch (type) {case TYPE_TOAST:// XXX right now the app process has complete control over// this...  should introduce a token to let the system// monitor/control what they are doing.break;case TYPE_DREAM:case TYPE_INPUT_METHOD:case TYPE_WALLPAPER:case TYPE_PRIVATE_PRESENTATION:// The window manager will check these.break;case TYPE_PHONE:case TYPE_PRIORITY_PHONE:case TYPE_SYSTEM_ALERT:case TYPE_SYSTEM_ERROR:case TYPE_SYSTEM_OVERLAY:permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;break;default:permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;}if (permission != null) {if (mContext.checkCallingOrSelfPermission(permission)!= PackageManager.PERMISSION_GRANTED) {return WindowManagerGlobal.ADD_PERMISSION_DENIED;}}return WindowManagerGlobal.ADD_OKAY;
}

我截取的是4.4_r1的代码, 我们最关心的部分其实一直没有变, 那就是TYPE_TOAST根本没有做权限检查, 直接break出去了, 最后返回WindowManagerGlobal.ADD_OKAY.

不需要权限显示悬浮窗的原因已经找到了, 接着刚才addWindow方法的分析, 继续看下面一句:

mPolicy.adjustWindowParamsLw(win.mAttrs);

也就是PhoneWindowManager.adjustWindowParamsLw, 注意这里我给出了三个版本的实现, 一个是2.0到2.3.7实现的版本, 一个是4.0.1到4.3.1实现的版本, 一个是4.4实现的版本:

//Android 2.0 - 2.3.7 PhoneWindowManagerpublic void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {switch (attrs.type) {case TYPE_SYSTEM_OVERLAY:case TYPE_SECURE_SYSTEM_OVERLAY:case TYPE_TOAST:// These types of windows can't receive input events.attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;break;}}//Android 4.0.1 - 4.3.1 PhoneWindowManagerpublic void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {switch (attrs.type) {case TYPE_SYSTEM_OVERLAY:case TYPE_SECURE_SYSTEM_OVERLAY:case TYPE_TOAST:// These types of windows can't receive input events.attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;break;}}//Android 4.4 PhoneWindowManager@Overridepublic void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {switch (attrs.type) {case TYPE_SYSTEM_OVERLAY:case TYPE_SECURE_SYSTEM_OVERLAY:// These types of windows can't receive input events.attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;break;}}

grepcode上没有3.x的代码, 我也没查具体是什么, 没必要考虑3.x.
可以看到, 在4.0.1以前, 当我们使用TYPE_TOAST, Android会偷偷给我们加上FLAG_NOT_FOCUSABLE和FLAG_NOT_TOUCHABLE, 4.0.1开始, 会额外再去掉FLAG_WATCH_OUTSIDE_TOUCH, 这样真的是什么事件都没了. 而4.4开始, TYPE_TOAST被移除了, 所以从4.4开始, 使用TYPE_TOAST的同时还可以接收触摸事件和按键事件了, 而4.4以前只能显示出来, 不能交互.

API level 18及以下使用TYPE_TOAST无法接收触摸事件的原因也找到了.

  相关解决方案