背景
上上周连续加班后果有点大,又快跌进短视频漩涡了,家里人又特别擅长搞我心态,做点难度不大又有些意义时事情慢慢找回对技术的热情吧。
这篇文章不介绍悬浮窗是怎么实现的,郭霖大佬13年就有悬浮窗的文章;
我只有一年多一些的APP开发经验,如果有错误的地方,还请指正。
需求
公司有一个产品,用的Android8.1,但是系统是外包出去做的,刚入职不久,还没拿到系统权限,外包那边效率慢。目前想要监听右侧一定宽度,高度match_parent的区域的长按事件,来唤出悬浮窗,别的触摸事件就给下面的APP。
思路
隐形悬浮窗
刚开始是想放一个隐形悬浮窗接收触摸事件,长按唤出主要功能的悬浮窗,但是,隐形悬浮窗一旦接收了触摸事件,下面的APP那块区域就接收不到任何的触摸事件,为了让下面的APP接收到触摸事件,就需要把我的隐形悬浮窗设置为不可触摸,这时我的隐形悬浮窗就接收不到任何的触摸事件。
kotlin
// 让悬浮窗接收到触摸事件
LayoutParams.FLAG_NOT_TOUCH_MODAL or
LayoutParams.FLAG_NOT_FOCUSABLE
// 让悬浮窗下面接收到触摸事件
LayoutParams.FLAG_NOT_TOUCH_MODAL or
LayoutParams.FLAG_NOT_FOCUSABLE or
LayoutParams.FLAG_NOT_TOUCHABLE
矛盾产生了,这时我又找到了WindowManager的一个函数
java
updateViewLayout(View view, ViewGroup.LayoutParams params)
现在有两个个未验证的新想法:
- 先将隐形悬浮窗设置为可接收触摸事件,如果是长按,唤出自己的多功能悬浮窗;如果不是,将自己的隐形悬浮窗设置为不可触摸,再在原位置想办法模拟出原来的触摸事件。但是不够优雅。
- 在那块区域放数个小型的均匀分散的隐形悬浮窗。感觉有些问题。
无障碍服务/辅助服务/AccessibilityService
得益于系统外包的性质,我需要通过adb赋予无障碍服务权限。这块的内容也是昨天刚刚接触的,简单试了下,如果触摸位置没有view的话,无障碍服务就接收不到事件,而且view和view的体质是不一样的,有的能接收到事件有的不能接收到事件。不过无障碍服务好像能模拟一些手势。
反射
昨天下午翻文章看到说Android系统有做全局触摸监听,那么我是否可以通过反射把那个全局触摸监听弄出来
WindowManagerPolicy#PointerListener:全局触摸监听类
java
public interface PointerEventListener {
/**
* 1. onPointerEvent will be called on the service.UiThread.
* 2. motionEvent will be recycled after onPointerEvent returns so if it is needed
* copy() must be made and the copy must be recycled.
**/
void onPointerEvent(MotionEvent motionEvent);
/**
* @see #onPointerEvent(MotionEvent)
**/
default void onPointerEvent(MotionEvent motionEvent, int displayId) {
if (displayId == DEFAULT_DISPLAY) {
onPointerEvent(motionEvent);
}
}
}
android.googlesource.com/platform/fr...
类找到了,对象呢?
WindowManagerService
java
@Override
public void registerPointerEventListener(PointerEventListener listener) {
mPointerEventDispatcher.registerInputEventListener(listener);
}
@Override
public void unregisterPointerEventListener(PointerEventListener listener) {
mPointerEventDispatcher.unregisterInputEventListener(listener);
}
android.googlesource.com/platform/fr...
那我要是拿到了mPointerEventDispatcher是不是就能给我自己注册一个全局触摸监听了?那么WindowManager是怎么获取WindowManagerService的呢?因为我们只能接触到WindowManager对象
WindowManager
java
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager
android.googlesource.com/platform/fr...
一个接口类,几千行代码,Ctrl F没找到别的带WindowManager字符串的类,谁实现的呢?
WindowManagerImpl
java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have a token.
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
@Override
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId) {
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
List<KeyboardShortcutGroup> result =
resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);
receiver.onKeyboardShortcutsReceived(result);
}
};
try {
WindowManagerGlobal.getWindowManagerService()
.requestAppKeyboardShortcuts(resultReceiver, deviceId);
} catch (RemoteException e) {
}
}
@Override
public Display getDefaultDisplay() {
return mContext.getDisplay();
}
@Override
public Region getCurrentImeTouchRegion() {
try {
return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion();
} catch (RemoteException e) {
}
return null;
}
android.googlesource.com/platform/fr...
因为这个类就一百多行代码,就贴的多了点,主要看WindowManagerGlobal.getWindowManagerService(),至于mGlobal
java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
关键就在WindowManagerGlobal类了。
WindowManagerGlobal
android.googlesource.com/platform/fr...
贴个链接自己翻去吧,反正是静态方法获取的;
根据WindowManagerGlobal.getWindowManagerService()通过反射获取WindowManagerService对象,贴个刚刚让GPT3.5给我生成的代码:
kotlin
import java.lang.reflect.Method
fun getGlobalWindowManagerService(): Any? {
try {
val windowManagerGlobalClass = Class.forName("android.view.WindowManagerGlobal")
val getWindowManagerServiceMethod: Method = windowManagerGlobalClass.getDeclaredMethod("getWindowManagerService")
getWindowManagerServiceMethod.isAccessible = true
return getWindowManagerServiceMethod.invoke(null)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
以上,是我周五下班前的进度,今天又刷了一个白天的手机,WindowManagerService好像涉及到一块非常大的内容,叫什么WMS,而上面的mPointerEventDispatcher又涉及到InputManagerService,被叫做IMS,等我这两天看完这方面文章,验证一下这条路的可行性再更。
另外还有几条思路,分别是DecorView做悬浮窗看看能不能绕开WindowManager的触摸管控,感觉行不通;要系统级权限,试试stackoverflow.com/questions/4... 中的老方法;要外包暴露出全局触摸接口给我用;叫老板改需求