6.0以下开悬浮窗只需在清单文件中申请权限
6.0以上需要动态权限申请,申请完权限设置type为TYPE_PHONE或TYPE_SYSTEM_ALERT就可以了
8.0需要添加的权限
在Android O之前的系统中申请了该权限后,再给对应的window设置
WindowManager.LayoutParams params = new WindowManager.LayoutParams(); // 如果设置为TYPE_PHONE; 那么优先级会降低一些,即拉下通知栏不可见 // params.type = WindowManager.LayoutParams.TYPE_PHONE; params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;悬浮窗口就可以显示出来。
但是在Android O的系统中,google规定申请 android.permission.SYSTEM_ALERT_WINDOW 权限的应用需要给悬浮窗口设置如下type:
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;如果不设置该TYPE,应用会Crash,报错如下(后面的2002表示设置的type为TYPE_PHONE):
AndroidRuntime: android.view.WindowManager BadTokenException:Unabletoaddwindowandroid.view.ViewRootImpl B a d T o k e n E x c e p t i o n : U n a b l e t o a d d w i n d o w a n d r o i d . v i e w . V i e w R o o t I m p l W@c8d1f1a – permission denied for window type 2002
动态权限申请 android.permission.SYSTEM_ALERT_WINDOW权限是比较特殊的,使用下面的方法:
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, 100);在 onActivityResult(int requestCode, int resultCode, Intent data) 回调函数中处理。
注意: LocalWindowManger可通过 activity.getWindowManager()或 activity.getWindow().getWindowManager()获取。
CompatModeWrapper可通过 getSystemService(Context.WINDOW_SERVICE)
当我们通过LocalWindowManger添加视图时,退出Activity,添加的视图也会随之消失。每一个Activity对应一个LocalWindowManger,每一个App对应一个CompatModeWrapper),所以要实现在App所在进程中运行的悬浮窗口,当然是得要获取CompatModeWrapper,而不是LocalWindowManger。
完整设置悬浮框代码如下:
private void createFloatView() { btn_floatView = new Button(getApplicationContext()); btn_floatView.setText("悬浮窗"); //获取LayoutParams对象 wmParams = new WindowManager.LayoutParams(); //获取的是LocalWindowManager对象 mWindowManager = getWindowManager(); Log.i(TAG, "mWindowManager1--->" + mWindowManager); // mWindowManager = getWindow().getWindowManager(); // Log.i(TAG, "mWindowManager2--->" + mWindowManager); // 获取的是CompatModeWrapper对象 // mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE); // Log.i(TAG, "mWindowManager3--->" + mWindowManager); /** * 设置window type * 分两种情况: * (1)显示在其它应用上面 * 8.0以上设置TYPE_APPLICATION_OVERLAY * 8.0以下设置TYPE_SYSTEM_ALERT * * (2)显示在当前应用 * 设置TYPE_PHONE */ // 如果设置为TYPE_PHONE; 那么优先级会降低一些,即拉下通知栏不可见 // wmParams.type = WindowManager.LayoutParams.TYPE_PHONE; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//8.0+,不设置这个flag可能会报错 wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; } // 设置图片格式,效果为背景透明 wmParams.format = PixelFormat.RGBA_8888; // 设置Window flag wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。 // wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL| LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE; //调整悬浮窗显示的停靠位置为左侧置顶 wmParams.gravity = Gravity.LEFT | Gravity.TOP; // 以屏幕左上角为原点,设置x、y初始值,相对于gravity wmParams.x = 0; wmParams.y = 0; //设置悬浮窗口长宽数据 wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; // 设置悬浮窗的Touch监听 btn_floatView.setOnTouchListener(new View.OnTouchListener() { int lastX, lastY; int paramX, paramY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); paramX = wmParams.x; paramY = wmParams.y; break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - lastX; int dy = (int) event.getRawY() - lastY; wmParams.x = paramX + dx; wmParams.y = paramY + dy; // 更新悬浮窗位置 mWindowManager.updateViewLayout(btn_floatView, wmParams); break; } return true; } }); mWindowManager.addView(btn_floatView, wmParams); }