最近在做一個播放組件端上防作弊的效果,播放的時候判斷廣告是否被部分或者完全遮擋了。
比較重要的載體是Window,實際的表現形式是View,所以重點往監控Window和View的創建這樣的思路出發。
目前的話一共想到三種方案:
①監控View的創建:
通過給LayoutInflater 設置factory2來感知View的創建:
LayoutInflater.from(context).setFactory2()
當LayoutInflater調用createViewFromTag時:
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
給設置的factory2接口方法onCreateView就能感知到。
缺點是場景有限,只有需要解析xml生成View的時候才能感知到,直接new View就不行了。
②監聽Window的創建:
創建Window是這麼創建的:
Window w = PolicyManager.makeNewWindow(mContext);
#PolicyManager
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
而這個spolicy是個靜態接口對象
private static final IPolicy sPolicy;
實現是com.android.internal.policy.impl.Policy
所以我們可以hook這個sPolicy對象,感知makeNewWindow的時機。
缺點:高版本中不用PolicyManager創建Window,相應的代碼處直接變爲new PhoneWindow()
③監聽WindowManager.addView方法:
WindowManagerService服務在app本地的代理對象爲WindowMangerImpl,而裏面依賴了WindowManagerGlobal這個對象,它是個單例,但是不是個接口,就不能用動態代理的方式去hook。hook WindowManagerGlobal的好處是兼容性強,這個類從低版本到高版本幾乎沒修改過。
在WindowManagerGlobal有幾個:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
他們會在調用addView,removeView,updateView時做操作,所以通過hook這些集合也可以感知到addView的出現:寫一個繼承ArrayList的ObserverList,覆寫add的方法,替換回去。
還有一個比較關鍵的是需要知道加進來這個View在屏幕的位置,通過是
view.getLocationOnScreen(int[])
這個方法可以得到view在屏幕的位置,接着拿到view的寬高判斷和我們View的重疊和層級關係。
④跨進程的Dialog,比如crash彈窗:
這個可以根據點擊事件的MotionEvent的flag來判斷是否有遮擋:
https://stackoverflow.com/questions/44197671/detecting-android-screen-overlays-programmatically