Android悬浮窗TYPE_TOAST小结源码分析
2016-02-01 19:00
381 查看
还是从最简单的地方开始, 我们调用了WindowManager.addView, WindowManager是个接口, 我们使用的是他的实现类WindowManagerImpl, 看看它的addView方法:
mGlobal是WindowManagerGlobal的实例, 再看看WindowManagerGlobal.addView:
代码中创建了一个ViewRootImpl, 调用了它的setView, 将我们要添加的view传入. 继续看ViewRootImpl.setView:
对我们的分析来说最关键的代码是
mWindowSession的类型是IWindowSession, mWindow的类型是IWindow.Stub, 这句代码就是利用AIDL进行IPC, 实际被调用的是Session.addToDisplay:
mService是WindowManagerService, 继续往下跟:
mPolicy是标记为final的成员变量:
final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();
继续看PolicyManager.makeNewWindowManager:
这里sPolicy是com.android.internal.policy.impl.Policy对象, 再看看它的makeNewWindowManager方法返回的是什么:
现在我们知道mPolicy实际上是PhoneWindowManager, 那么
实际调用的代码是:
我截取的是4.4_r1的代码, 我们最关心的部分其实一直没有变, 那就是TYPE_TOAST根本没有做权限检查, 直接break出去了, 最后返回WindowManagerGlobal.ADD_OKAY.
不需要权限显示悬浮窗的原因已经找到了, 接着刚才addWindow方法的分析, 继续看下面一句:
也就是PhoneWindowManager.adjustWindowParamsLw, 注意这里我给出了三个版本的实现, 一个是2.0到2.3.7实现的版本, 一个是4.0.1到4.3.1实现的版本, 一个是4.4实现的版本:
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无法接收触摸事件的原因也找到了.
@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 things try { 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-time try { 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 class private 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 PhoneWindowManager public 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 PhoneWindowManager public 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 @Override public 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无法接收触摸事件的原因也找到了.
相关文章推荐
- 获取 Android 设备的唯一标识码
- android-cannot resolve the method(handlerMessage(android.os.Message))
- 保护Android resources文件不被反编译原理分析
- android *** Layout 10 DatePicker && TimePicker
- Android自定义View控件
- Android 显示获取服务器的超大图片 <19>
- android *** Layout 09 CheckBox
- Android之判断某个服务是否正在运行的方法
- android 进程优先级
- Android之Proguard语法
- android一个弹出菜单的动画(二)
- Glide 3.0 介绍
- TabLayout实现仿网易新闻客户端Tab标签
- Android APK瘦身之Android Studio Lint (代码审查)
- 1.Android JUnit Runner(使用AndroidStudio)
- 介绍一款Android小游戏--交互式人机对战五子棋
- Android中include标签的使用
- android 多媒体数据库详解
- 制作可独立分发的Android模拟器
- android 控件布局