前言
Android无需权限显示悬浮窗, 兼谈逆向分析app这篇文章阅读量很大, 这篇文章是通过逆向分析UC浏览器的实现和兼容性处理来得到一个悬浮窗的实现小技巧, 但有很多问题没有弄明白, 比如为什么在API 18及以下TYPE_TOAST
的悬浮窗无法接受触摸事件, 为什么使用TYPE_TOAST
就不需要权限.
期间@廖祜秋liaohuqiu_秋百万和我有较多探讨, 原文贴的一个demo android-UCToast也是他做的, 他也有写Android 悬浮窗的小结. 这几篇关于悬浮窗的文章, 是我和他共同探索的结果, 非常感谢.
思路
老实说一开始我是想看看整个事件的传播过程, 从EventHub
开始, 到View.onTouchEvent
, 想看看Android系统内事件分发, 不过由于绝大部分代码在Native层, 我并没有搞清楚.
其实要想知道原因很简单, 只要grep一下TYPE_TOAST, 把每个用到的地方看一看, 自然就知道了, 但是恰好周末我手上没有源码, 只能在grepcode上面一个一个的查, 所以也花了不少时间.
正文
还是从最简单的地方开始, 我们调用了WindowManager.addView
, WindowManager是个接口, 我们使用的是他的实现类WindowManagerImpl
, 看看它的addView
方法:
1 2 3 4 |
@Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); } |
mGlobal
是WindowManagerGlobal
的实例, 再看看WindowManagerGlobal.addView
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 { ...... } ...... } } } |
对我们的分析来说最关键的代码是
代码中创建了一个ViewRootImpl
, 调用了它的setView
, 将我们要添加的view传入. 继续看ViewRootImpl.setView
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 { ...... } ...... } } } |
对我们的分析来说最关键的代码是
1 2 3 |
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel); |
mWindowSession的类型是IWindowSession
, mWindow的类型是IWindow.Stub
, 这句代码就是利用AIDL进行IPC, 实际被调用的是Session.addToDisplay
:
1 2 3 4 5 6 7 |
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams |